diff --git a/devdocs/modernization/NET10-PORT.md b/devdocs/modernization/NET10-PORT.md index abb46031..780d434f 100644 --- a/devdocs/modernization/NET10-PORT.md +++ b/devdocs/modernization/NET10-PORT.md @@ -217,7 +217,7 @@ John's "next release" cases for AyaNova (exported 2026-10-24). Each is its own s | 4706 | Remove the unused RunProgram.Run | 1. **Done** (`Run` and its `RunWindows`/`RunLinuxShell`/`RunOSX` removed, no callers; `RunNoShell` stays. Backup through the API wrote the database dump and attachments zip; full suite = baseline) | | 4707 | Stray cypress.json in raven-client | 1. **Done** (raven-client `f2f10b81`: removed; from 2020, pointed at `tests/e2e`, which moved to `raven-test` in 2022 (`e0be8a7c`); Cypress is not a client dependency) | | 4687 | Generator polls every second (raven and sockeye) | 1. **Closed, no change** (John, 2026-10-26). Measured on an idle local server over 60 s: 4.5 database transactions per second (about 315 rows read per second, mostly the ready-jobs checks), AyaNova CPU 0.14 s per minute (0.2 % of one core). Wake-on-demand (5 s sleep, woken by `JobsBiz.AddJobAsync`) was considered and rejected: small gain, timing change in the loop that runs every background job | -| 4682 | Time-only picker keeps the old date's DST offset | 1 | +| 4682 | Time-only picker keeps the old date's DST offset | 1. **Done** (John: the backup time is the server's clock time, the schedule's first hour the user's. Backup: the time is stored as the UTC moment on the day it was set and was run at that UTC hour daily, so it moved an hour on the server's clock at each DST change (John saw backups start at an unexpected time); `ServerGlobalOpsSettingsCache.SetNextBackup` now recovers the clock time with the stored date's offset and runs it on the server's local days (`BackupTimeOnLocalDay`; a time skipped when clocks go forward runs an hour later). Proof (temporary log line, removed): 11:30 PM set in January (07:30Z) gave 00:30 PDT before, 23:30 PDT after. Schedule: `home-schedule.vue` already keeps `firstTime` as local "HH:MM", no change. No migration. Manual (backup time) and changelog. Sockeye has the same code: same fix in sockeye, takes effect when Sockeye is next deployed (matters for the droplet cron job that copies backups if its tzdata still has DST for America/Vancouver). Note: this workstation's Windows zone is "Pacific Time (US & Canada)", which still falls back on 2026-11-01 although BC abolished DST; AyaNova follows the OS zone) | | 4661 | Docs: password guidance | 1, docs | | 4636 | Docs: turn off 2FA before v7 to v8 migration | 1, docs | | 4634 | Docs: custom checkbox shows nothing for older records | 1, docs | diff --git a/docs/8.0/ayanova/docs/changelog.md b/docs/8.0/ayanova/docs/changelog.md index 027c3338..5c866725 100644 --- a/docs/8.0/ayanova/docs/changelog.md +++ b/docs/8.0/ayanova/docs/changelog.md @@ -33,6 +33,7 @@ IN PROGRESS - 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: when a report fails to render, objects and errors a report script passes to `console.error` are now shown with their contents and stack (line numbers within the script) in the error message, to help with [writing report scripts](ay-report-edit.md#script-errors). +- Server: on servers in a time zone with daylight saving time, the automatic backup could run an hour earlier or later than the chosen [backup time](ops-form-backup.md#backup-time) for part of the year; it now always runs at the chosen time on the server's clock. - 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: fixed direct notification messages to a user: only the first one within 12 hours was delivered, now every message is. diff --git a/docs/8.0/ayanova/docs/ops-form-backup.md b/docs/8.0/ayanova/docs/ops-form-backup.md index 5e575c66..16f39890 100644 --- a/docs/8.0/ayanova/docs/ops-form-backup.md +++ b/docs/8.0/ayanova/docs/ops-form-backup.md @@ -140,6 +140,8 @@ These defaults should be examined carefully and set to values that are appropria Choose a time for the automatic backup when the server will not be in use by Users. +The backup runs at this time on the AyaNova server's own clock, every day, including across daylight saving time changes, so make sure the server's operating system is set to the correct time zone. The time is entered and shown in your own time zone, so if you are in a different time zone from the server, the time you see is the matching time in your time zone. + **Windows server IIS Warning** if you are hosting AyaNova via IIS on Windows be aware that there is a setting in IIS that will regularly restart AyaNova automatically and it must be adjusted to not conflict with the backup time and general usage. If you're not sure then double check the IIS installation instructions [Application Pool settings](ops-install-windows-iis.md#74-edit-application-pool). diff --git a/server/AyaNova/util/ServerGlobalOpsSettingsCache.cs b/server/AyaNova/util/ServerGlobalOpsSettingsCache.cs index 8edab273..f795ab15 100644 --- a/server/AyaNova/util/ServerGlobalOpsSettingsCache.cs +++ b/server/AyaNova/util/ServerGlobalOpsSettingsCache.cs @@ -63,22 +63,36 @@ namespace AyaNova.Util internal static void SetNextBackup() { DateTime utcNow = DateTime.UtcNow; + //case 4682: the backup time is a time on the server's clock; days are the server's local days + DateTime localToday = TimeZoneInfo.ConvertTimeFromUtc(utcNow, TimeZoneInfo.Local).Date; //Has last backup run more than 24 hours ago? if (NextBackup < utcNow.AddHours(-24)) { //set it to today then - NextBackup = new DateTime(utcNow.Year, utcNow.Month, utcNow.Day, Backup.BackupTime.Hour, Backup.BackupTime.Minute, 0, DateTimeKind.Utc); + NextBackup = BackupTimeOnLocalDay(localToday); //Make sure next backup is in the future if (NextBackup < utcNow) - NextBackup = NextBackup.AddDays(1); + NextBackup = BackupTimeOnLocalDay(localToday.AddDays(1)); } else { //less than 24 hours, set it to tomorrow - NextBackup = new DateTime(utcNow.Year, utcNow.Month, utcNow.Day, Backup.BackupTime.Hour, Backup.BackupTime.Minute, 0, DateTimeKind.Utc); - NextBackup = NextBackup.AddDays(1); + NextBackup = BackupTimeOnLocalDay(localToday.AddDays(1)); } } + //case 4682: the backup time is stored as the UTC moment it meant on the day it was set, and was run at that UTC hour + //every day, so it moved an hour on the server's clock with each daylight saving change. The server's clock time is + //recovered with that day's offset and the backup is run at that clock time on the given local day (UTC returned) + private static DateTime BackupTimeOnLocalDay(DateTime localDay) + { + var chosen = TimeZoneInfo.ConvertTimeFromUtc(DateTime.SpecifyKind(Backup.BackupTime, DateTimeKind.Utc), TimeZoneInfo.Local); + var local = DateTime.SpecifyKind(localDay.Date + new TimeSpan(chosen.Hour, chosen.Minute, 0), DateTimeKind.Unspecified); + //a time skipped when the clocks go forward (e.g. 2:30 AM that night) runs an hour later + if (TimeZoneInfo.Local.IsInvalidTime(local)) + local = local.AddHours(1); + return TimeZoneInfo.ConvertTimeToUtc(local, TimeZoneInfo.Local); + } + }//eoc }//eons \ No newline at end of file