mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-09 21:10:59 +00:00
feat(tz): isNearUtc helper for cross-tz fast-path guard
Pure helper returns true when the supplied IANA zone is within +-3h of UTC at the supplied instant. Exposed from both lib/tz/format and lib/tz/resolver so client and server callers share the same gate. Honours DST via the existing tzOffsetMinutes path. Used by the v1.4.38 analytics fast-path runtime guards (bp-in-target, correlations) to fall back to live SQL for users far from UTC, where the rollup table's UTC-midnight bucketStart slips a calendar day relative to the per-event mood / intake stream.
This commit is contained in:
1 parent
dcd0b0a591
commit
33dc00fe62
3 files changed
+79
No files matched your search
@@ -23,6 +23,7 @@ import {
|
||||
formatInUserTz,
|
||||
invalidateServerDefaultTimezone,
|
||||
invalidateUserTimezone,
|
||||
isNearUtc,
|
||||
isValidTimezone,
|
||||
resolveServerDefaultTimezone,
|
||||
resolveUserTimezone,
|
||||
@@ -261,6 +262,51 @@ describe("userDayKey", () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe("isNearUtc", () => {
|
||||
// Pin a summer-side instant so DST is on for Berlin (+2) and off for
|
||||
// most southern-hemisphere zones — keeps the assertions readable.
|
||||
const summer = new Date("2026-07-15T12:00:00.000Z");
|
||||
// Winter-side instant so Berlin is on CET (+1).
|
||||
const winter = new Date("2026-01-15T12:00:00.000Z");
|
||||
|
||||
it("returns true for UTC", () => {
|
||||
expect(isNearUtc("UTC", summer)).toBe(true);
|
||||
});
|
||||
|
||||
it("returns true for Europe/Berlin in both summer and winter", () => {
|
||||
expect(isNearUtc("Europe/Berlin", summer)).toBe(true);
|
||||
expect(isNearUtc("Europe/Berlin", winter)).toBe(true);
|
||||
});
|
||||
|
||||
it("returns true for the ±3h boundary zones", () => {
|
||||
// Europe/Moscow = +3 year-round.
|
||||
expect(isNearUtc("Europe/Moscow", summer)).toBe(true);
|
||||
// Atlantic/Azores = -1 in winter, 0 in summer.
|
||||
expect(isNearUtc("Atlantic/Azores", summer)).toBe(true);
|
||||
});
|
||||
|
||||
it("returns false for zones more than 3 hours from UTC", () => {
|
||||
// Pacific/Honolulu = -10 year-round.
|
||||
expect(isNearUtc("Pacific/Honolulu", summer)).toBe(false);
|
||||
// Asia/Tokyo = +9 year-round.
|
||||
expect(isNearUtc("Asia/Tokyo", summer)).toBe(false);
|
||||
// America/New_York = -4 in summer, -5 in winter.
|
||||
expect(isNearUtc("America/New_York", summer)).toBe(false);
|
||||
// Pacific/Auckland = +13 in southern summer (Jan), +12 in May.
|
||||
expect(isNearUtc("Pacific/Auckland", winter)).toBe(false);
|
||||
});
|
||||
|
||||
it("defaults to near-UTC when the zone string is invalid", () => {
|
||||
// Invalid zones fall back to Europe/Berlin, which is near-UTC.
|
||||
expect(isNearUtc("Mars/Olympus_Mons", summer)).toBe(true);
|
||||
});
|
||||
|
||||
it("uses the current instant when `now` is omitted", () => {
|
||||
// Pure smoke — exercises the default parameter branch.
|
||||
expect(typeof isNearUtc("Europe/Berlin")).toBe("boolean");
|
||||
});
|
||||
});
|
||||
|
||||
describe("DEFAULT_TIMEZONE", () => {
|
||||
it("is Europe/Berlin", () => {
|
||||
expect(DEFAULT_TIMEZONE).toBe("Europe/Berlin");
|
||||
|
||||
@@ -67,6 +67,38 @@ export function detectBrowserTimezone(): string {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* v1.4.38 W-A — cross-tz fast-path runtime guard.
|
||||
*
|
||||
* Returns `true` when the supplied IANA zone is within ±3 hours of UTC
|
||||
* at the supplied instant. Used by the analytics fast-paths
|
||||
* (`bp-in-target-fast-path`, `correlations-fast-path`) to gate the
|
||||
* rollup read-swap: those helpers key rollup rows on UTC-midnight
|
||||
* `bucketStart` and pair them with per-event streams keyed in the
|
||||
* user's local timezone. For Berlin (+1/+2) the day-key on the rollup
|
||||
* and the day-key on the per-event row line up; for Honolulu (-10)
|
||||
* they slip by a calendar day. Three hours is a conservative pin —
|
||||
* the worst-case slip inside the window is the wall-clock hour-of-day
|
||||
* the user happens to log at, and three hours covers every European
|
||||
* zone plus the western edge of Mid-Atlantic / eastern edge of the
|
||||
* Americas.
|
||||
*
|
||||
* Falls back to "near-UTC" (i.e. returns `true`) when the zone string
|
||||
* is invalid — the resolver layer is already defensive about junk
|
||||
* values, and a `true` here means the caller takes the rollup
|
||||
* fast-path which is the safer default for the canonical Berlin
|
||||
* tenant.
|
||||
*
|
||||
* The `now` argument lets callers honour DST transitions; the offset
|
||||
* of `Europe/Berlin` is +1 in January and +2 in July. Defaults to
|
||||
* "now" so most callers can `isNearUtc(userTz)`.
|
||||
*/
|
||||
export function isNearUtc(tz: string, now: Date = new Date()): boolean {
|
||||
const safeTz = isValidTimezone(tz) ? tz : DEFAULT_TIMEZONE;
|
||||
const offsetMinutes = tzOffsetMinutes(now, safeTz);
|
||||
return Math.abs(offsetMinutes) <= 3 * 60;
|
||||
}
|
||||
|
||||
export type FormatInUserTzShape =
|
||||
| "iso-with-offset"
|
||||
| "wall-clock"
|
||||
|
||||
@@ -47,6 +47,7 @@ import {
|
||||
export {
|
||||
DEFAULT_TIMEZONE,
|
||||
isValidTimezone,
|
||||
isNearUtc,
|
||||
listSupportedTimezones,
|
||||
detectBrowserTimezone,
|
||||
formatInUserTz,
|
||||
|
||||
Reference in new issue
Block a user