case 4697 - relative date filters use the user's time zone
Token date ranges (*today*, *lastmonth* ...) are built from the user's local time in clientTimeStamp but were converted to UTC with ToUniversalTime(), the server's time zone. They are now converted with clientTimeStamp.Offset. Lists and dashboard widgets share this builder. Deferred: daylight saving edge and dashboard grouping by UTC day (both need the client's time zone name). Changelog. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -122,7 +122,7 @@ in this branch or a follow-up release. UPDATE: John has decided these all go in
|
|||||||
- [x] **Report browser download location.** Fixed: downloads into `<AYANOVA_DATA_PATH>/report-browser` (derived `ServerBootConfig.REPORT_RENDER_BROWSER_DOWNLOAD_PATH`, not a new setting; empty when no data path is set, then beside the app as before) through `BrowserFetcherOptions.Path`, and launches the downloaded executable explicitly. `AYANOVA_REPORT_RENDER_BROWSER_PATH` still takes precedence. Existing installs keep an unused old download beside the app until it is removed. Confirmed: `new BrowserFetcher()` with the default path beside the app (`ReportBiz.cs:800`). Point it under the data directory in code. Beside the app it is read-only under a hardened systemd unit (`ProtectSystem=strict`), and a redeploy that replaces the app folder deletes it
|
- [x] **Report browser download location.** Fixed: downloads into `<AYANOVA_DATA_PATH>/report-browser` (derived `ServerBootConfig.REPORT_RENDER_BROWSER_DOWNLOAD_PATH`, not a new setting; empty when no data path is set, then beside the app as before) through `BrowserFetcherOptions.Path`, and launches the downloaded executable explicitly. `AYANOVA_REPORT_RENDER_BROWSER_PATH` still takes precedence. Existing installs keep an unused old download beside the app until it is removed. Confirmed: `new BrowserFetcher()` with the default path beside the app (`ReportBiz.cs:800`). Point it under the data directory in code. Beside the app it is read-only under a hardened systemd unit (`ProtectSystem=strict`), and a redeploy that replaces the app folder deletes it
|
||||||
- [x] **Report failures: return a specific error.** Done: download and launch each in their own `try`; on failure `HandleBrowserFailure` logs at Error (was Debug via the job processor) and writes `rendererror.exception` ("Report rendering browser could not be downloaded to … / started from … with arguments …: (reason)"), which the client already shows as `JobFailed: …`; no client change. The Sockeye crash is n/a: rendering runs as a job, and `JobsBiz.ProcessJobAsync` catches every exception, logs it through NLog and marks the job Failed (`JobsBiz.cs:390-439`), so the process survives. What remains: a browser download or launch failure (`ReportBiz.cs:800`, `:838`, outside the render `try`) reaches the client only as a generic job failure. Catch it in `DoRenderJob` and return a render error that says what failed
|
- [x] **Report failures: return a specific error.** Done: download and launch each in their own `try`; on failure `HandleBrowserFailure` logs at Error (was Debug via the job processor) and writes `rendererror.exception` ("Report rendering browser could not be downloaded to … / started from … with arguments …: (reason)"), which the client already shows as `JobFailed: …`; no client change. The Sockeye crash is n/a: rendering runs as a job, and `JobsBiz.ProcessJobAsync` catches every exception, logs it through NLog and marks the job Failed (`JobsBiz.cs:390-439`), so the process survives. What remains: a browser download or launch failure (`ReportBiz.cs:800`, `:838`, outside the render `try`) reaches the client only as a generic job failure. Catch it in `DoRenderJob` and return a render error that says what failed
|
||||||
- [x] **Data-list date filters, small fixes.** Fixed: `>=` and `<` use `ZeroSeconds`; `!=` is `< ZeroSeconds OR > MaxSeconds`; the `DataListField` null check runs first and in all builds, throwing `ArgumentException` naming the column. (An unknown column sent to `/data-list` is in practice rejected earlier by `DataListSqlSelectBuilder`, with its own "DEV ERROR" message; unchanged.) Sockeye 8.1.0 still has the old code. Confirmed: `>=` (`:790-793`) and `<` (`:795-798`) use `MaxSeconds` and should use `ZeroSeconds`; date `NotEqual` (`:806-814`) uses `MaxSeconds` on both sides and should exclude the whole minute (`< ZeroSeconds OR > MaxSeconds`). `DataListField` is dereferenced at `:24` and `:27` before the DEBUG-only null check at `:33`, so **release builds** throw a NullReferenceException on an unknown column, not only DEBUG builds
|
- [x] **Data-list date filters, small fixes.** Fixed: `>=` and `<` use `ZeroSeconds`; `!=` is `< ZeroSeconds OR > MaxSeconds`; the `DataListField` null check runs first and in all builds, throwing `ArgumentException` naming the column. (An unknown column sent to `/data-list` is in practice rejected earlier by `DataListSqlSelectBuilder`, with its own "DEV ERROR" message; unchanged.) Sockeye 8.1.0 still has the old code. Confirmed: `>=` (`:790-793`) and `<` (`:795-798`) use `MaxSeconds` and should use `ZeroSeconds`; date `NotEqual` (`:806-814`) uses `MaxSeconds` on both sides and should exclude the whole minute (`< ZeroSeconds OR > MaxSeconds`). `DataListField` is dereferenced at `:24` and `:27` before the DEBUG-only null check at `:33`, so **release builds** throw a NullReferenceException on an unknown column, not only DEBUG builds
|
||||||
- [ ] **Relative date tokens use the server's time zone** (`*today*`, `*yesterday*` …). Confirmed: `ClientToday = clientTimeStamp.Date` (`:399-400`) has `Kind=Unspecified`, and `ToUniversalTime()` (`:1060`, `:1065`) converts it with the server's zone. Wrong for any user in a different zone from the server. The fix builds the range from `clientTimeStamp.Offset`, with care for ranges that cross a DST change. **This changes behaviour** and affects the integration tests' `TIME_ZONE_ADJUSTMENT`. Probably better as a follow-up release
|
- [x] **Relative date tokens use the server's time zone** **Fixed in case 4697** (boundaries converted to UTC with `clientTimeStamp.Offset`). (`*today*`, `*yesterday*` …). Confirmed: `ClientToday = clientTimeStamp.Date` (`:399-400`) has `Kind=Unspecified`, and `ToUniversalTime()` (`:1060`, `:1065`) converts it with the server's zone. Wrong for any user in a different zone from the server. The fix builds the range from `clientTimeStamp.Offset`, with care for ranges that cross a DST change. **This changes behaviour** and affects the integration tests' `TIME_ZONE_ADJUSTMENT`. Probably better as a follow-up release
|
||||||
|
|
||||||
Not port items, listed so they are not forgotten: the three server bugs the integration
|
Not port items, listed so they are not forgotten: the three server bugs the integration
|
||||||
suite is waiting on (case 4653, case 4654, the contract issue).
|
suite is waiting on (case 4653, case 4654, the contract issue).
|
||||||
@@ -209,7 +209,7 @@ John's "next release" cases for AyaNova (exported 2026-10-24). Each is its own s
|
|||||||
| 4694 | Standalone launcher not shutting down PostgreSQL (raven-launcher) | 1. **Done** (launcher: `pg_ctl stop -m fast -w` when the server exits, replacing the `StandardInput.Close()` that threw, and on window close (SIGHUP/CTRL_CLOSE); `standalone.iss`: `PrepareToInstall` and uninstall stop a running embedded PostgreSQL with the installed `pg_ctl`. Sandbox: 7 `postgres` processes left running as by 8.2.4, then the upgrade showed no "files in use" page and stopped them; Shut down server and closing the window each left no `postgres`. Note: closing the window or Ctrl+C already ended PostgreSQL in 8.2.4 (same console); only the in-app shutdown left it) |
|
| 4694 | Standalone launcher not shutting down PostgreSQL (raven-launcher) | 1. **Done** (launcher: `pg_ctl stop -m fast -w` when the server exits, replacing the `StandardInput.Close()` that threw, and on window close (SIGHUP/CTRL_CLOSE); `standalone.iss`: `PrepareToInstall` and uninstall stop a running embedded PostgreSQL with the installed `pg_ctl`. Sandbox: 7 `postgres` processes left running as by 8.2.4, then the upgrade showed no "files in use" page and stopped them; Shut down server and closing the window each left no `postgres`. Note: closing the window or Ctrl+C already ended PostgreSQL in 8.2.4 (same console); only the in-app shutdown left it) |
|
||||||
| 4695 | Shipping the Visual C++ runtime DLLs with the standalone install | 1. **Done** (`msvcp140.dll`, `vcruntime140.dll`, `vcruntime140_1.dll` 14.44.35211.0, Microsoft-signed, added to `dist/win-x64/postgres-standalone/bin`, app-local; BUILD.md notes they must be replaced by hand. Sandbox: fresh Windows with no runtime, install started PostgreSQL, trial, report 1.6 s) |
|
| 4695 | Shipping the Visual C++ runtime DLLs with the standalone install | 1. **Done** (`msvcp140.dll`, `vcruntime140.dll`, `vcruntime140_1.dll` 14.44.35211.0, Microsoft-signed, added to `dist/win-x64/postgres-standalone/bin`, app-local; BUILD.md notes they must be replaced by hand. Sandbox: fresh Windows with no runtime, install started PostgreSQL, trial, report 1.6 s) |
|
||||||
| 4696 | JWT secret missing from the standalone config.json | 1. **Done, resolved by 4711, no change** (sandbox 2026-10-25: 8.2.4 warned at every boot that AYANOVA_JWT_SECRET is missing; after the upgrade to 8.3.0 the first boot generated `C:\ProgramDatayanova\jwt-signing.key`, the next boot reused it with no warning, and a fresh 8.3.0 install did the same) |
|
| 4696 | JWT secret missing from the standalone config.json | 1. **Done, resolved by 4711, no change** (sandbox 2026-10-25: 8.2.4 warned at every boot that AYANOVA_JWT_SECRET is missing; after the upgrade to 8.3.0 the first boot generated `C:\ProgramDatayanova\jwt-signing.key`, the next boot reused it with no warning, and a fresh 8.3.0 install did the same) |
|
||||||
| 4697 | Relative date filters using the server's time zone | 1 |
|
| 4697 | Relative date filters using the server's time zone | 1. **Done** (`BuildSQLBetweenTwoTokenDerivedDates` converts the user's local boundaries to UTC with `clientTimeStamp.Offset`, was `ToUniversalTime()` with the server's zone; the dashboard KPIs use the same builder, so their ranges are fixed too. Test `WorkOrderList_TodayIsTheUsersToday`: a work order at 00:30 today for a +10:00 user, before missing from *today*, after included; full suite = baseline. The manual already promised the user's time zone (`ay-start-data-types.md`, `ay-start-form-data-tables.md`), no manual change; changelog) |
|
||||||
| 4699 | Linux desktop data-path note in the manual | 1, docs |
|
| 4699 | Linux desktop data-path note in the manual | 1, docs |
|
||||||
| 4700 | Restore note for customers with a dedicated database role | 1, docs |
|
| 4700 | Restore note for customers with a dedicated database role | 1, docs |
|
||||||
| 4703 | Report JS console error logging | 1 |
|
| 4703 | Report JS console error logging | 1 |
|
||||||
@@ -277,6 +277,7 @@ Found during the port, out of scope. One line each.
|
|||||||
- **Improvement, own case after the port (John to create):** bring back logging of JavaScript error details from report page console errors, as Sockeye's port did. AyaNova logs only `ERROR: {message text}`; the per-argument detail (`arg instanceof Error ? arg.message : arg`) is commented out at `biz/ReportBiz.cs:873-876` since case 4586, because `IJSHandle.ExecutionContext` was removed. Sockeye 8.1.0 (`server/biz/ReportBiz.cs:575`) evaluates on the handle instead: `args.Message.Args.Select(arg => arg.EvaluateFunctionAsync("(arg) => arg instanceof Error ? arg.message : arg"))`, which also needs the console handler to be `async` again
|
- **Improvement, own case after the port (John to create):** bring back logging of JavaScript error details from report page console errors, as Sockeye's port did. AyaNova logs only `ERROR: {message text}`; the per-argument detail (`arg instanceof Error ? arg.message : arg`) is commented out at `biz/ReportBiz.cs:873-876` since case 4586, because `IJSHandle.ExecutionContext` was removed. Sockeye 8.1.0 (`server/biz/ReportBiz.cs:575`) evaluates on the handle instead: `args.Message.Args.Select(arg => arg.EvaluateFunctionAsync("(arg) => arg instanceof Error ? arg.message : arg"))`, which also needs the console handler to be `async` again
|
||||||
- Pre-existing, same in 8.2.4 (found in the 4579 sandbox test, John: Deferred): rendering a report for a record that does not exist (e.g. work order ids 1 and 2 in a trial without sample data) fails the job with a generic `ArgumentNullException` ("Value cannot be null. (Parameter 'o')") from `JObject.FromObject` in `WorkOrderBiz.GetReportData` (`WorkOrderBiz.cs:1030`), instead of skipping the missing record or saying it is not found. Other object types' `GetReportData` likely do the same
|
- Pre-existing, same in 8.2.4 (found in the 4579 sandbox test, John: Deferred): rendering a report for a record that does not exist (e.g. work order ids 1 and 2 in a trial without sample data) fails the job with a generic `ArgumentNullException` ("Value cannot be null. (Parameter 'o')") from `JObject.FromObject` in `WorkOrderBiz.GetReportData` (`WorkOrderBiz.cs:1030`), instead of skipping the missing record or saying it is not found. Other object types' `GetReportData` likely do the same
|
||||||
- **Roles audit, case 4735 (John, 2026-10-25, this release; found in 4719):** check every object's rights as documented in the manual (each page's "Authorization Roles required" section and `ay-biz-admin-roles.md`) against `biz/BizRoles.cs`. Report templates were changeable by Business administration - restricted although the roles page uses exactly that case as its example of a view-only restricted role (fixed in 4719). If that slipped through there could be others; for each mismatch decide whether the code or the page is wrong
|
- **Roles audit, case 4735 (John, 2026-10-25, this release; found in 4719):** check every object's rights as documented in the manual (each page's "Authorization Roles required" section and `ay-biz-admin-roles.md`) against `biz/BizRoles.cs`. Report templates were changeable by Business administration - restricted although the roles page uses exactly that case as its example of a view-only restricted role (fixed in 4719). If that slipped through there could be others; for each mismatch decide whether the code or the page is wrong
|
||||||
|
- From case 4697 (John: Deferred): (1) a relative date range that crosses a daylight saving change can be an hour off at its far end, because the client sends only its current offset with list requests; exact would need the client to send its IANA time zone name too (front-end change). (2) Dashboard widgets group by day, week or month with `date_trunc` on the stored UTC times, so for a user far from UTC an item near midnight lands in the neighbouring bar; same need for the time zone name
|
||||||
- Pre-existing (found in the 4632 IIS test, John: Deferred): emailed report attachments have the content type `application/pdf/pdf`, because `Mailer.cs:51` builds `new MimePart("application/pdf", "pdf")` (media type and subtype) instead of `new MimePart("application", "pdf")`. Mail programs still open the file by its `.pdf` name
|
- Pre-existing (found in the 4632 IIS test, John: Deferred): emailed report attachments have the content type `application/pdf/pdf`, because `Mailer.cs:51` builds `new MimePart("application/pdf", "pdf")` (media type and subtype) instead of `new MimePart("application", "pdf")`. Mail programs still open the file by its `.pdf` name
|
||||||
|
|
||||||
## Log
|
## Log
|
||||||
|
|||||||
@@ -31,6 +31,7 @@ IN PROGRESS
|
|||||||
- Server: new [trusted reverse proxies](ops-config-trusted-proxies.md) setting for a reverse proxy that runs on a different computer from the AyaNova server, so AyaNova sees each user's own address.
|
- Server: new [trusted reverse proxies](ops-config-trusted-proxies.md) setting for a reverse proxy that runs on a different computer from the AyaNova server, so AyaNova sees each user's own address.
|
||||||
- Server: while a report renders, its template no longer holds the User's actual access token or download key; the AyaNova server adds the User's access only to the report's requests to the `ayApiUrl` address. Templates that call the AyaNova API through any other address need to be changed. See [API usage in reports](ay-report-edit.md#api-usage).
|
- Server: while a report renders, its template no longer holds the User's actual access token or download key; the AyaNova server adds the User's access only to the report's requests to the `ayApiUrl` address. Templates that call the AyaNova API through any other address need to be changed. See [API usage in reports](ay-report-edit.md#api-usage).
|
||||||
- Server: users with the "Business administration - restricted" role can view [report templates](adm-report-templates.md) but no longer create, edit or import them, as described for that [role](ay-biz-admin-roles.md). Give the full "Business administration" role to users who edit report templates.
|
- Server: users with the "Business administration - restricted" role can view [report templates](adm-report-templates.md) but no longer create, edit or import them, as described for that [role](ay-biz-admin-roles.md). Give the full "Business administration" role to users who edit report templates.
|
||||||
|
- Server: relative date filters such as "today" or "last month" (in lists and dashboard widgets) now start and end the period at midnight in the user's own [time zone](ay-start-form-data-tables.md#filtering-dates-and-time-zones). Previously, for a user in a different time zone from the server, records close to the start or end of the period could be included or left out by mistake.
|
||||||
- Server: the SuperUser account can no longer be deleted.
|
- Server: the SuperUser account can no longer be deleted.
|
||||||
- Server: role rights corrected to match the documentation: "Service technician - restricted" users can view tax codes, projects, unit models and meter readings; "Business administration" users can view server metrics; Accounting users can no longer enter unit meter readings; only "Business administration" users can switch an [integrated application](api-integrated-applications.md) on or off or delete it.
|
- Server: role rights corrected to match the documentation: "Service technician - restricted" users can view tax codes, projects, unit models and meter readings; "Business administration" users can view server metrics; Accounting users can no longer enter unit meter readings; only "Business administration" users can switch an [integrated application](api-integrated-applications.md) on or off or delete it.
|
||||||
- Server: fixed direct notification messages to a user: only the first one within 12 hours was delivered, now every message is.
|
- Server: fixed direct notification messages to a user: only the first one within 12 hours was delivered, now every message is.
|
||||||
|
|||||||
@@ -416,20 +416,20 @@ namespace AyaNova.DataList
|
|||||||
dtAfter = ClientToday.AddDays(-1);
|
dtAfter = ClientToday.AddDays(-1);
|
||||||
dtAfter = dtAfter.AddSeconds(-1);
|
dtAfter = dtAfter.AddSeconds(-1);
|
||||||
dtBefore = ClientToday;
|
dtBefore = ClientToday;
|
||||||
BuildSQLBetweenTwoTokenDerivedDates(SqlColumnNameToFilter, sb, dtAfter, dtBefore);
|
BuildSQLBetweenTwoTokenDerivedDates(SqlColumnNameToFilter, sb, dtAfter, dtBefore, clientTimeStamp.Offset);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case "*today*":
|
case "*today*":
|
||||||
//Between yesterday at midnight and tommorow at midnight
|
//Between yesterday at midnight and tommorow at midnight
|
||||||
dtAfter = ClientToday.AddSeconds(-1);
|
dtAfter = ClientToday.AddSeconds(-1);
|
||||||
dtBefore = ClientToday.AddDays(1);
|
dtBefore = ClientToday.AddDays(1);
|
||||||
BuildSQLBetweenTwoTokenDerivedDates(SqlColumnNameToFilter, sb, dtAfter, dtBefore);
|
BuildSQLBetweenTwoTokenDerivedDates(SqlColumnNameToFilter, sb, dtAfter, dtBefore, clientTimeStamp.Offset);
|
||||||
break;
|
break;
|
||||||
case "*tomorrow*":
|
case "*tomorrow*":
|
||||||
//Between Tonight at midnight and day after tommorow at midnight
|
//Between Tonight at midnight and day after tommorow at midnight
|
||||||
dtAfter = ClientToday.AddDays(1).AddSeconds(-1);
|
dtAfter = ClientToday.AddDays(1).AddSeconds(-1);
|
||||||
dtBefore = ClientToday.AddDays(2);
|
dtBefore = ClientToday.AddDays(2);
|
||||||
BuildSQLBetweenTwoTokenDerivedDates(SqlColumnNameToFilter, sb, dtAfter, dtBefore);
|
BuildSQLBetweenTwoTokenDerivedDates(SqlColumnNameToFilter, sb, dtAfter, dtBefore, clientTimeStamp.Offset);
|
||||||
break;
|
break;
|
||||||
case "*lastweek*":
|
case "*lastweek*":
|
||||||
//Between two Sundays ago at midnight and last sunday at midnight
|
//Between two Sundays ago at midnight and last sunday at midnight
|
||||||
@@ -442,7 +442,7 @@ namespace AyaNova.DataList
|
|||||||
//go to very start of eighth dayahead
|
//go to very start of eighth dayahead
|
||||||
dtBefore = dtAfter.AddDays(8);
|
dtBefore = dtAfter.AddDays(8);
|
||||||
dtAfter = dtAfter.AddSeconds(-1);
|
dtAfter = dtAfter.AddSeconds(-1);
|
||||||
BuildSQLBetweenTwoTokenDerivedDates(SqlColumnNameToFilter, sb, dtAfter, dtBefore);
|
BuildSQLBetweenTwoTokenDerivedDates(SqlColumnNameToFilter, sb, dtAfter, dtBefore, clientTimeStamp.Offset);
|
||||||
break;
|
break;
|
||||||
case "*thisweek*":
|
case "*thisweek*":
|
||||||
//Between Sunday at midnight and Next sunday at midnight
|
//Between Sunday at midnight and Next sunday at midnight
|
||||||
@@ -467,7 +467,7 @@ namespace AyaNova.DataList
|
|||||||
dtBefore = dtBefore.AddDays(1);
|
dtBefore = dtBefore.AddDays(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
BuildSQLBetweenTwoTokenDerivedDates(SqlColumnNameToFilter, sb, dtAfter, dtBefore);
|
BuildSQLBetweenTwoTokenDerivedDates(SqlColumnNameToFilter, sb, dtAfter, dtBefore, clientTimeStamp.Offset);
|
||||||
break;
|
break;
|
||||||
case "*nextweek*":
|
case "*nextweek*":
|
||||||
//Between Next Sunday at midnight and Next Next sunday at midnight
|
//Between Next Sunday at midnight and Next Next sunday at midnight
|
||||||
@@ -486,7 +486,7 @@ namespace AyaNova.DataList
|
|||||||
dtBefore = dtAfter.AddDays(7);
|
dtBefore = dtAfter.AddDays(7);
|
||||||
dtAfter = dtAfter.AddSeconds(-1);
|
dtAfter = dtAfter.AddSeconds(-1);
|
||||||
|
|
||||||
BuildSQLBetweenTwoTokenDerivedDates(SqlColumnNameToFilter, sb, dtAfter, dtBefore);
|
BuildSQLBetweenTwoTokenDerivedDates(SqlColumnNameToFilter, sb, dtAfter, dtBefore, clientTimeStamp.Offset);
|
||||||
break;
|
break;
|
||||||
case "*lastmonth*":
|
case "*lastmonth*":
|
||||||
//start with the first day of this month
|
//start with the first day of this month
|
||||||
@@ -500,7 +500,7 @@ namespace AyaNova.DataList
|
|||||||
//case 1155
|
//case 1155
|
||||||
dtAfter = dtAfter.AddSeconds(-1);
|
dtAfter = dtAfter.AddSeconds(-1);
|
||||||
|
|
||||||
BuildSQLBetweenTwoTokenDerivedDates(SqlColumnNameToFilter, sb, dtAfter, dtBefore);
|
BuildSQLBetweenTwoTokenDerivedDates(SqlColumnNameToFilter, sb, dtAfter, dtBefore, clientTimeStamp.Offset);
|
||||||
break;
|
break;
|
||||||
case "*thismonth*":
|
case "*thismonth*":
|
||||||
//start with the first day of this month
|
//start with the first day of this month
|
||||||
@@ -512,7 +512,7 @@ namespace AyaNova.DataList
|
|||||||
//case 1155
|
//case 1155
|
||||||
dtAfter = dtAfter.AddSeconds(-1);
|
dtAfter = dtAfter.AddSeconds(-1);
|
||||||
|
|
||||||
BuildSQLBetweenTwoTokenDerivedDates(SqlColumnNameToFilter, sb, dtAfter, dtBefore);
|
BuildSQLBetweenTwoTokenDerivedDates(SqlColumnNameToFilter, sb, dtAfter, dtBefore, clientTimeStamp.Offset);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case "*nextmonth*":
|
case "*nextmonth*":
|
||||||
@@ -526,7 +526,7 @@ namespace AyaNova.DataList
|
|||||||
|
|
||||||
//case 1155
|
//case 1155
|
||||||
dtAfter = dtAfter.AddSeconds(-1);
|
dtAfter = dtAfter.AddSeconds(-1);
|
||||||
BuildSQLBetweenTwoTokenDerivedDates(SqlColumnNameToFilter, sb, dtAfter, dtBefore);
|
BuildSQLBetweenTwoTokenDerivedDates(SqlColumnNameToFilter, sb, dtAfter, dtBefore, clientTimeStamp.Offset);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case "*14daywindow*":
|
case "*14daywindow*":
|
||||||
@@ -540,21 +540,21 @@ namespace AyaNova.DataList
|
|||||||
//case 1155
|
//case 1155
|
||||||
dtAfter = dtAfter.AddSeconds(-1);
|
dtAfter = dtAfter.AddSeconds(-1);
|
||||||
|
|
||||||
BuildSQLBetweenTwoTokenDerivedDates(SqlColumnNameToFilter, sb, dtAfter, dtBefore);
|
BuildSQLBetweenTwoTokenDerivedDates(SqlColumnNameToFilter, sb, dtAfter, dtBefore, clientTimeStamp.Offset);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case "*past*":
|
case "*past*":
|
||||||
//Forever up to Now
|
//Forever up to Now
|
||||||
dtAfter = new DateTime(1753, 1, 2);//this was for sql server but even then was probably outdated good enough though for our purposes
|
dtAfter = new DateTime(1753, 1, 2);//this was for sql server but even then was probably outdated good enough though for our purposes
|
||||||
dtBefore = ClientNow;
|
dtBefore = ClientNow;
|
||||||
BuildSQLBetweenTwoTokenDerivedDates(SqlColumnNameToFilter, sb, dtAfter, dtBefore);
|
BuildSQLBetweenTwoTokenDerivedDates(SqlColumnNameToFilter, sb, dtAfter, dtBefore, clientTimeStamp.Offset);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case "*future*":
|
case "*future*":
|
||||||
//From Now to forever (999 years from now)
|
//From Now to forever (999 years from now)
|
||||||
dtAfter = ClientNow;
|
dtAfter = ClientNow;
|
||||||
dtBefore = dtAfter.AddYears(999);
|
dtBefore = dtAfter.AddYears(999);
|
||||||
BuildSQLBetweenTwoTokenDerivedDates(SqlColumnNameToFilter, sb, dtAfter, dtBefore);
|
BuildSQLBetweenTwoTokenDerivedDates(SqlColumnNameToFilter, sb, dtAfter, dtBefore, clientTimeStamp.Offset);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case "*lastyear*":
|
case "*lastyear*":
|
||||||
@@ -562,7 +562,7 @@ namespace AyaNova.DataList
|
|||||||
dtAfter = new DateTime(ClientNow.AddYears(-1).Year, 1, 1, 0, 0, 00).AddSeconds(-1);
|
dtAfter = new DateTime(ClientNow.AddYears(-1).Year, 1, 1, 0, 0, 00).AddSeconds(-1);
|
||||||
//To zero hour January 1 this year
|
//To zero hour January 1 this year
|
||||||
dtBefore = new DateTime(ClientNow.Year, 1, 1, 0, 0, 00);
|
dtBefore = new DateTime(ClientNow.Year, 1, 1, 0, 0, 00);
|
||||||
BuildSQLBetweenTwoTokenDerivedDates(SqlColumnNameToFilter, sb, dtAfter, dtBefore);
|
BuildSQLBetweenTwoTokenDerivedDates(SqlColumnNameToFilter, sb, dtAfter, dtBefore, clientTimeStamp.Offset);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case "*thisyear*":
|
case "*thisyear*":
|
||||||
@@ -570,7 +570,7 @@ namespace AyaNova.DataList
|
|||||||
dtAfter = new DateTime(ClientNow.Year, 1, 1).AddSeconds(-1);
|
dtAfter = new DateTime(ClientNow.Year, 1, 1).AddSeconds(-1);
|
||||||
//To zero hour Jan 1 next year
|
//To zero hour Jan 1 next year
|
||||||
dtBefore = new DateTime(ClientNow.AddYears(1).Year, 1, 1);
|
dtBefore = new DateTime(ClientNow.AddYears(1).Year, 1, 1);
|
||||||
BuildSQLBetweenTwoTokenDerivedDates(SqlColumnNameToFilter, sb, dtAfter, dtBefore);
|
BuildSQLBetweenTwoTokenDerivedDates(SqlColumnNameToFilter, sb, dtAfter, dtBefore, clientTimeStamp.Offset);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case "*last3months*":
|
case "*last3months*":
|
||||||
@@ -578,7 +578,7 @@ namespace AyaNova.DataList
|
|||||||
dtAfter = ClientNow.AddMonths(-3).AddSeconds(-1);
|
dtAfter = ClientNow.AddMonths(-3).AddSeconds(-1);
|
||||||
//To Now
|
//To Now
|
||||||
dtBefore = ClientNow;
|
dtBefore = ClientNow;
|
||||||
BuildSQLBetweenTwoTokenDerivedDates(SqlColumnNameToFilter, sb, dtAfter, dtBefore);
|
BuildSQLBetweenTwoTokenDerivedDates(SqlColumnNameToFilter, sb, dtAfter, dtBefore, clientTimeStamp.Offset);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case "*last6months*":
|
case "*last6months*":
|
||||||
@@ -586,7 +586,7 @@ namespace AyaNova.DataList
|
|||||||
dtAfter = ClientNow.AddMonths(-6).AddSeconds(-1);
|
dtAfter = ClientNow.AddMonths(-6).AddSeconds(-1);
|
||||||
//To Now
|
//To Now
|
||||||
dtBefore = ClientNow;
|
dtBefore = ClientNow;
|
||||||
BuildSQLBetweenTwoTokenDerivedDates(SqlColumnNameToFilter, sb, dtAfter, dtBefore);
|
BuildSQLBetweenTwoTokenDerivedDates(SqlColumnNameToFilter, sb, dtAfter, dtBefore, clientTimeStamp.Offset);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case "*pastyear*": //within the prior 365 days before today
|
case "*pastyear*": //within the prior 365 days before today
|
||||||
@@ -594,7 +594,7 @@ namespace AyaNova.DataList
|
|||||||
dtAfter = ClientNow.AddDays(-365).AddSeconds(-1);
|
dtAfter = ClientNow.AddDays(-365).AddSeconds(-1);
|
||||||
//To Now
|
//To Now
|
||||||
dtBefore = ClientNow;
|
dtBefore = ClientNow;
|
||||||
BuildSQLBetweenTwoTokenDerivedDates(SqlColumnNameToFilter, sb, dtAfter, dtBefore);
|
BuildSQLBetweenTwoTokenDerivedDates(SqlColumnNameToFilter, sb, dtAfter, dtBefore, clientTimeStamp.Offset);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case "*past90days*":
|
case "*past90days*":
|
||||||
@@ -602,21 +602,21 @@ namespace AyaNova.DataList
|
|||||||
dtAfter = ClientNow.AddDays(-90).AddSeconds(-1);
|
dtAfter = ClientNow.AddDays(-90).AddSeconds(-1);
|
||||||
//To Now
|
//To Now
|
||||||
dtBefore = ClientNow;
|
dtBefore = ClientNow;
|
||||||
BuildSQLBetweenTwoTokenDerivedDates(SqlColumnNameToFilter, sb, dtAfter, dtBefore);
|
BuildSQLBetweenTwoTokenDerivedDates(SqlColumnNameToFilter, sb, dtAfter, dtBefore, clientTimeStamp.Offset);
|
||||||
break;
|
break;
|
||||||
case "*past30days*":
|
case "*past30days*":
|
||||||
//From Now minus 30 days
|
//From Now minus 30 days
|
||||||
dtAfter = ClientNow.AddDays(-30).AddSeconds(-1);
|
dtAfter = ClientNow.AddDays(-30).AddSeconds(-1);
|
||||||
//To Now
|
//To Now
|
||||||
dtBefore = ClientNow;
|
dtBefore = ClientNow;
|
||||||
BuildSQLBetweenTwoTokenDerivedDates(SqlColumnNameToFilter, sb, dtAfter, dtBefore);
|
BuildSQLBetweenTwoTokenDerivedDates(SqlColumnNameToFilter, sb, dtAfter, dtBefore, clientTimeStamp.Offset);
|
||||||
break;
|
break;
|
||||||
case "*past7days*":
|
case "*past7days*":
|
||||||
//From Now minus 7 days
|
//From Now minus 7 days
|
||||||
dtAfter = ClientNow.AddDays(-7).AddSeconds(-1);
|
dtAfter = ClientNow.AddDays(-7).AddSeconds(-1);
|
||||||
//To Now
|
//To Now
|
||||||
dtBefore = ClientNow;
|
dtBefore = ClientNow;
|
||||||
BuildSQLBetweenTwoTokenDerivedDates(SqlColumnNameToFilter, sb, dtAfter, dtBefore);
|
BuildSQLBetweenTwoTokenDerivedDates(SqlColumnNameToFilter, sb, dtAfter, dtBefore, clientTimeStamp.Offset);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case "*past24hours*":
|
case "*past24hours*":
|
||||||
@@ -624,7 +624,7 @@ namespace AyaNova.DataList
|
|||||||
dtAfter = ClientNow.AddHours(-24).AddSeconds(-1);
|
dtAfter = ClientNow.AddHours(-24).AddSeconds(-1);
|
||||||
//To Now
|
//To Now
|
||||||
dtBefore = ClientNow;
|
dtBefore = ClientNow;
|
||||||
BuildSQLBetweenTwoTokenDerivedDates(SqlColumnNameToFilter, sb, dtAfter, dtBefore);
|
BuildSQLBetweenTwoTokenDerivedDates(SqlColumnNameToFilter, sb, dtAfter, dtBefore, clientTimeStamp.Offset);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case "*past6hours*":
|
case "*past6hours*":
|
||||||
@@ -632,7 +632,7 @@ namespace AyaNova.DataList
|
|||||||
dtAfter = ClientNow.AddHours(-6).AddSeconds(-1);
|
dtAfter = ClientNow.AddHours(-6).AddSeconds(-1);
|
||||||
//To Now
|
//To Now
|
||||||
dtBefore = ClientNow;
|
dtBefore = ClientNow;
|
||||||
BuildSQLBetweenTwoTokenDerivedDates(SqlColumnNameToFilter, sb, dtAfter, dtBefore);
|
BuildSQLBetweenTwoTokenDerivedDates(SqlColumnNameToFilter, sb, dtAfter, dtBefore, clientTimeStamp.Offset);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case "*january*":
|
case "*january*":
|
||||||
@@ -640,67 +640,67 @@ namespace AyaNova.DataList
|
|||||||
dtAfter = new DateTime(ClientNow.Year, 1, 1).AddSeconds(-1);
|
dtAfter = new DateTime(ClientNow.Year, 1, 1).AddSeconds(-1);
|
||||||
//To zero hour feb 1 this year
|
//To zero hour feb 1 this year
|
||||||
dtBefore = new DateTime(ClientNow.Year, 2, 1);
|
dtBefore = new DateTime(ClientNow.Year, 2, 1);
|
||||||
BuildSQLBetweenTwoTokenDerivedDates(SqlColumnNameToFilter, sb, dtAfter, dtBefore);
|
BuildSQLBetweenTwoTokenDerivedDates(SqlColumnNameToFilter, sb, dtAfter, dtBefore, clientTimeStamp.Offset);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case "*february*":
|
case "*february*":
|
||||||
dtAfter = new DateTime(ClientNow.Year, 2, 1).AddSeconds(-1);
|
dtAfter = new DateTime(ClientNow.Year, 2, 1).AddSeconds(-1);
|
||||||
dtBefore = new DateTime(ClientNow.Year, 3, 1);
|
dtBefore = new DateTime(ClientNow.Year, 3, 1);
|
||||||
BuildSQLBetweenTwoTokenDerivedDates(SqlColumnNameToFilter, sb, dtAfter, dtBefore);
|
BuildSQLBetweenTwoTokenDerivedDates(SqlColumnNameToFilter, sb, dtAfter, dtBefore, clientTimeStamp.Offset);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case "*march*":
|
case "*march*":
|
||||||
dtAfter = new DateTime(ClientNow.Year, 3, 1).AddSeconds(-1);
|
dtAfter = new DateTime(ClientNow.Year, 3, 1).AddSeconds(-1);
|
||||||
dtBefore = new DateTime(ClientNow.Year, 4, 1);
|
dtBefore = new DateTime(ClientNow.Year, 4, 1);
|
||||||
BuildSQLBetweenTwoTokenDerivedDates(SqlColumnNameToFilter, sb, dtAfter, dtBefore);
|
BuildSQLBetweenTwoTokenDerivedDates(SqlColumnNameToFilter, sb, dtAfter, dtBefore, clientTimeStamp.Offset);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case "*april*":
|
case "*april*":
|
||||||
dtAfter = new DateTime(ClientNow.Year, 4, 1).AddSeconds(-1);
|
dtAfter = new DateTime(ClientNow.Year, 4, 1).AddSeconds(-1);
|
||||||
dtBefore = new DateTime(ClientNow.Year, 5, 1);
|
dtBefore = new DateTime(ClientNow.Year, 5, 1);
|
||||||
BuildSQLBetweenTwoTokenDerivedDates(SqlColumnNameToFilter, sb, dtAfter, dtBefore);
|
BuildSQLBetweenTwoTokenDerivedDates(SqlColumnNameToFilter, sb, dtAfter, dtBefore, clientTimeStamp.Offset);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case "*may*":
|
case "*may*":
|
||||||
dtAfter = new DateTime(ClientNow.Year, 5, 1).AddSeconds(-1);
|
dtAfter = new DateTime(ClientNow.Year, 5, 1).AddSeconds(-1);
|
||||||
dtBefore = new DateTime(ClientNow.Year, 6, 1);
|
dtBefore = new DateTime(ClientNow.Year, 6, 1);
|
||||||
BuildSQLBetweenTwoTokenDerivedDates(SqlColumnNameToFilter, sb, dtAfter, dtBefore);
|
BuildSQLBetweenTwoTokenDerivedDates(SqlColumnNameToFilter, sb, dtAfter, dtBefore, clientTimeStamp.Offset);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case "*june*":
|
case "*june*":
|
||||||
dtAfter = new DateTime(ClientNow.Year, 6, 1).AddSeconds(-1);
|
dtAfter = new DateTime(ClientNow.Year, 6, 1).AddSeconds(-1);
|
||||||
dtBefore = new DateTime(ClientNow.Year, 7, 1);
|
dtBefore = new DateTime(ClientNow.Year, 7, 1);
|
||||||
BuildSQLBetweenTwoTokenDerivedDates(SqlColumnNameToFilter, sb, dtAfter, dtBefore);
|
BuildSQLBetweenTwoTokenDerivedDates(SqlColumnNameToFilter, sb, dtAfter, dtBefore, clientTimeStamp.Offset);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case "*july*":
|
case "*july*":
|
||||||
dtAfter = new DateTime(ClientNow.Year, 7, 1).AddSeconds(-1);
|
dtAfter = new DateTime(ClientNow.Year, 7, 1).AddSeconds(-1);
|
||||||
dtBefore = new DateTime(ClientNow.Year, 8, 1);
|
dtBefore = new DateTime(ClientNow.Year, 8, 1);
|
||||||
BuildSQLBetweenTwoTokenDerivedDates(SqlColumnNameToFilter, sb, dtAfter, dtBefore);
|
BuildSQLBetweenTwoTokenDerivedDates(SqlColumnNameToFilter, sb, dtAfter, dtBefore, clientTimeStamp.Offset);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case "*august*":
|
case "*august*":
|
||||||
dtAfter = new DateTime(ClientNow.Year, 8, 1).AddSeconds(-1);
|
dtAfter = new DateTime(ClientNow.Year, 8, 1).AddSeconds(-1);
|
||||||
dtBefore = new DateTime(ClientNow.Year, 9, 1);
|
dtBefore = new DateTime(ClientNow.Year, 9, 1);
|
||||||
BuildSQLBetweenTwoTokenDerivedDates(SqlColumnNameToFilter, sb, dtAfter, dtBefore);
|
BuildSQLBetweenTwoTokenDerivedDates(SqlColumnNameToFilter, sb, dtAfter, dtBefore, clientTimeStamp.Offset);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case "*september*":
|
case "*september*":
|
||||||
dtAfter = new DateTime(ClientNow.Year, 9, 1).AddSeconds(-1);
|
dtAfter = new DateTime(ClientNow.Year, 9, 1).AddSeconds(-1);
|
||||||
dtBefore = new DateTime(ClientNow.Year, 10, 1);
|
dtBefore = new DateTime(ClientNow.Year, 10, 1);
|
||||||
BuildSQLBetweenTwoTokenDerivedDates(SqlColumnNameToFilter, sb, dtAfter, dtBefore);
|
BuildSQLBetweenTwoTokenDerivedDates(SqlColumnNameToFilter, sb, dtAfter, dtBefore, clientTimeStamp.Offset);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case "*october*":
|
case "*october*":
|
||||||
dtAfter = new DateTime(ClientNow.Year, 10, 1).AddSeconds(-1);
|
dtAfter = new DateTime(ClientNow.Year, 10, 1).AddSeconds(-1);
|
||||||
dtBefore = new DateTime(ClientNow.Year, 11, 1);
|
dtBefore = new DateTime(ClientNow.Year, 11, 1);
|
||||||
BuildSQLBetweenTwoTokenDerivedDates(SqlColumnNameToFilter, sb, dtAfter, dtBefore);
|
BuildSQLBetweenTwoTokenDerivedDates(SqlColumnNameToFilter, sb, dtAfter, dtBefore, clientTimeStamp.Offset);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case "*november*":
|
case "*november*":
|
||||||
dtAfter = new DateTime(ClientNow.Year, 11, 1).AddSeconds(-1);
|
dtAfter = new DateTime(ClientNow.Year, 11, 1).AddSeconds(-1);
|
||||||
dtBefore = new DateTime(ClientNow.Year, 12, 1);
|
dtBefore = new DateTime(ClientNow.Year, 12, 1);
|
||||||
BuildSQLBetweenTwoTokenDerivedDates(SqlColumnNameToFilter, sb, dtAfter, dtBefore);
|
BuildSQLBetweenTwoTokenDerivedDates(SqlColumnNameToFilter, sb, dtAfter, dtBefore, clientTimeStamp.Offset);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case "*december*":
|
case "*december*":
|
||||||
@@ -708,7 +708,7 @@ namespace AyaNova.DataList
|
|||||||
dtAfter = new DateTime(ClientNow.Year, 12, 1).AddSeconds(-1);
|
dtAfter = new DateTime(ClientNow.Year, 12, 1).AddSeconds(-1);
|
||||||
//To zero hour Jan 1 next year
|
//To zero hour Jan 1 next year
|
||||||
dtBefore = new DateTime(ClientNow.AddYears(1).Year, 1, 1);
|
dtBefore = new DateTime(ClientNow.AddYears(1).Year, 1, 1);
|
||||||
BuildSQLBetweenTwoTokenDerivedDates(SqlColumnNameToFilter, sb, dtAfter, dtBefore);
|
BuildSQLBetweenTwoTokenDerivedDates(SqlColumnNameToFilter, sb, dtAfter, dtBefore, clientTimeStamp.Offset);
|
||||||
break;
|
break;
|
||||||
case "*lastyearlastmonth*":
|
case "*lastyearlastmonth*":
|
||||||
//start with the first day of this month
|
//start with the first day of this month
|
||||||
@@ -718,7 +718,7 @@ namespace AyaNova.DataList
|
|||||||
//Add one month to dtAfter to get end date
|
//Add one month to dtAfter to get end date
|
||||||
dtBefore = dtAfter.AddMonths(1);
|
dtBefore = dtAfter.AddMonths(1);
|
||||||
dtAfter = dtAfter.AddSeconds(-1);
|
dtAfter = dtAfter.AddSeconds(-1);
|
||||||
BuildSQLBetweenTwoTokenDerivedDates(SqlColumnNameToFilter, sb, dtAfter, dtBefore);
|
BuildSQLBetweenTwoTokenDerivedDates(SqlColumnNameToFilter, sb, dtAfter, dtBefore, clientTimeStamp.Offset);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case "*lastyearthismonth*":
|
case "*lastyearthismonth*":
|
||||||
@@ -729,7 +729,7 @@ namespace AyaNova.DataList
|
|||||||
//Add one month to dtAfter to get end date
|
//Add one month to dtAfter to get end date
|
||||||
dtBefore = dtAfter.AddMonths(1);
|
dtBefore = dtAfter.AddMonths(1);
|
||||||
dtAfter = dtAfter.AddSeconds(-1);
|
dtAfter = dtAfter.AddSeconds(-1);
|
||||||
BuildSQLBetweenTwoTokenDerivedDates(SqlColumnNameToFilter, sb, dtAfter, dtBefore);
|
BuildSQLBetweenTwoTokenDerivedDates(SqlColumnNameToFilter, sb, dtAfter, dtBefore, clientTimeStamp.Offset);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case "*lastyearnextmonth*":
|
case "*lastyearnextmonth*":
|
||||||
@@ -742,7 +742,7 @@ namespace AyaNova.DataList
|
|||||||
//Add one month to dtAfter to get end date
|
//Add one month to dtAfter to get end date
|
||||||
dtBefore = dtAfter.AddMonths(1);
|
dtBefore = dtAfter.AddMonths(1);
|
||||||
dtAfter = dtAfter.AddSeconds(-1);
|
dtAfter = dtAfter.AddSeconds(-1);
|
||||||
BuildSQLBetweenTwoTokenDerivedDates(SqlColumnNameToFilter, sb, dtAfter, dtBefore);
|
BuildSQLBetweenTwoTokenDerivedDates(SqlColumnNameToFilter, sb, dtAfter, dtBefore, clientTimeStamp.Offset);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
@@ -1075,19 +1075,27 @@ namespace AyaNova.DataList
|
|||||||
//This is only used by the token date query code above and that code does NOT convert to UTC time to match the DB so this function will handle that
|
//This is only used by the token date query code above and that code does NOT convert to UTC time to match the DB so this function will handle that
|
||||||
//The other non tokenized date criteria builders are all working with dates that come from the client in UTC already and they don't use this method
|
//The other non tokenized date criteria builders are all working with dates that come from the client in UTC already and they don't use this method
|
||||||
//so nothing required there
|
//so nothing required there
|
||||||
private static void BuildSQLBetweenTwoTokenDerivedDates(string sColumn, StringBuilder sb, DateTime dtAfter, DateTime dtBefore)
|
//case 4697: the token dates are the user's local times (from clientTimeStamp), so they are converted to UTC with the
|
||||||
|
//user's offset; ToUniversalTime() used the server's time zone. A range that crosses a daylight saving change can be
|
||||||
|
//an hour off at its far end, as the client sends only its current offset
|
||||||
|
private static void BuildSQLBetweenTwoTokenDerivedDates(string sColumn, StringBuilder sb, DateTime dtAfter, DateTime dtBefore, TimeSpan clientOffset)
|
||||||
{
|
{
|
||||||
|
|
||||||
sb.Append(">'");
|
sb.Append(">'");
|
||||||
sb.Append(PostgresDateFormat(MaxSeconds(dtAfter.ToUniversalTime())));
|
sb.Append(PostgresDateFormat(MaxSeconds(ClientLocalToUtc(dtAfter, clientOffset))));
|
||||||
sb.Append("' AND ");
|
sb.Append("' AND ");
|
||||||
sb.Append(sColumn);
|
sb.Append(sColumn);
|
||||||
sb.Append(" ");
|
sb.Append(" ");
|
||||||
sb.Append("<'");
|
sb.Append("<'");
|
||||||
sb.Append(PostgresDateFormat(ZeroSeconds(dtBefore.ToUniversalTime())));
|
sb.Append(PostgresDateFormat(ZeroSeconds(ClientLocalToUtc(dtBefore, clientOffset))));
|
||||||
sb.Append("'");
|
sb.Append("'");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static DateTime ClientLocalToUtc(DateTime clientLocal, TimeSpan clientOffset)
|
||||||
|
{
|
||||||
|
return DateTime.SpecifyKind(clientLocal - clientOffset, DateTimeKind.Utc);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
private static DateTime ZeroSeconds(DateTime d)
|
private static DateTime ZeroSeconds(DateTime d)
|
||||||
{
|
{
|
||||||
|
|||||||
Reference in New Issue
Block a user