Make scheduling and dates timezone-safe
This commit is contained in:
@@ -3,14 +3,17 @@
|
||||
import { describe, expect, test } from "bun:test";
|
||||
import {
|
||||
formatZonedDateTime,
|
||||
addZonedCalendarInterval,
|
||||
getDefaultScheduledSendAt,
|
||||
isValidTimeZone,
|
||||
toLocalDateTimeInputValue,
|
||||
zonedDateTimeToInstant,
|
||||
} from "../src/time-zone";
|
||||
import {
|
||||
EXPENSE_CATEGORIES,
|
||||
formatElapsedSeconds,
|
||||
getEffectiveInvoiceStatus,
|
||||
getDaysPastDue,
|
||||
parseReceiptText,
|
||||
} from "../src";
|
||||
|
||||
@@ -26,6 +29,17 @@ describe("shared domain behavior", () => {
|
||||
expect(getEffectiveInvoiceStatus("paid", yesterday)).toBe("paid");
|
||||
});
|
||||
|
||||
test("counts calendar days rather than 24-hour blocks across fall DST", () => {
|
||||
expect(
|
||||
getDaysPastDue(
|
||||
"sent",
|
||||
"2026-11-01",
|
||||
"America/New_York",
|
||||
new Date("2026-11-02T17:00:00.000Z"),
|
||||
),
|
||||
).toBe(1);
|
||||
});
|
||||
|
||||
test("formats elapsed time", () => {
|
||||
expect(formatElapsedSeconds(3_661)).toBe("01:01:01");
|
||||
});
|
||||
@@ -56,6 +70,69 @@ describe("time-zone helpers", () => {
|
||||
expect(isValidTimeZone("not/a-zone")).toBe(false);
|
||||
});
|
||||
|
||||
test("converts Eastern wall time to the correct absolute instant", () => {
|
||||
expect(
|
||||
zonedDateTimeToInstant(
|
||||
"2026-08-17T09:00",
|
||||
"America/New_York",
|
||||
).toISOString(),
|
||||
).toBe("2026-08-17T13:00:00.000Z");
|
||||
});
|
||||
|
||||
test("rejects nonexistent spring-forward wall times", () => {
|
||||
expect(() =>
|
||||
zonedDateTimeToInstant("2026-03-08T02:30", "America/New_York"),
|
||||
).toThrow("does not exist");
|
||||
});
|
||||
|
||||
test("disambiguates both occurrences of a fall-back wall time", () => {
|
||||
expect(
|
||||
zonedDateTimeToInstant(
|
||||
"2026-11-01T01:30",
|
||||
"America/New_York",
|
||||
"earlier",
|
||||
).toISOString(),
|
||||
).toBe("2026-11-01T05:30:00.000Z");
|
||||
expect(
|
||||
zonedDateTimeToInstant(
|
||||
"2026-11-01T01:30",
|
||||
"America/New_York",
|
||||
"later",
|
||||
).toISOString(),
|
||||
).toBe("2026-11-01T06:30:00.000Z");
|
||||
});
|
||||
|
||||
test("supports half-hour DST transitions", () => {
|
||||
const earlier = zonedDateTimeToInstant(
|
||||
"2026-04-05T01:45",
|
||||
"Australia/Lord_Howe",
|
||||
"earlier",
|
||||
);
|
||||
const later = zonedDateTimeToInstant(
|
||||
"2026-04-05T01:45",
|
||||
"Australia/Lord_Howe",
|
||||
"later",
|
||||
);
|
||||
expect(later.getTime() - earlier.getTime()).toBe(30 * 60_000);
|
||||
});
|
||||
|
||||
test("preserves Eastern wall time across DST and clamps month end", () => {
|
||||
expect(
|
||||
addZonedCalendarInterval(
|
||||
new Date("2026-03-01T14:00:00.000Z"),
|
||||
"weekly",
|
||||
"America/New_York",
|
||||
).toISOString(),
|
||||
).toBe("2026-03-08T13:00:00.000Z");
|
||||
expect(
|
||||
addZonedCalendarInterval(
|
||||
new Date("2026-01-31T14:00:00.000Z"),
|
||||
"monthly",
|
||||
"America/New_York",
|
||||
).toISOString(),
|
||||
).toBe("2026-02-28T14:00:00.000Z");
|
||||
});
|
||||
|
||||
test("rounds the default schedule to the next local hour", () => {
|
||||
const result = getDefaultScheduledSendAt(new Date(2026, 7, 17, 10, 42, 19));
|
||||
expect(toLocalDateTimeInputValue(result)).toBe("2026-08-17T11:00");
|
||||
|
||||
Reference in New Issue
Block a user