Timezone Safety & Civil Calendar Math
Understanding the architectural mismatch between timestamps and calendar dates in JavaScript, and how DaySafe eliminates timezone skew.
The Fundamental Mismatch: Timestamps vs. Calendar Days
JavaScript has historically had only one native representation for dates: the Date object. Internally, a JavaScript Date does not represent a calendar date; it represents an instantaneous timestamp: the number of milliseconds that have elapsed since midnight on January 1, 1970 UTC.
However, civil society organizes agreements, records, and schedules around calendar dates:
Born on 1995-04-12 in Tokyo, London, or New York — your birthday is always April 12th.
Due on 2026-11-30. It should not become Nov 29 when audited by an accountant in San Francisco.
PostgreSQL DATE stores pure dates. Attaching timezones corrupts data.
Trap 1: The Off-By-One Day Timezone Bug
The ECMAScript specification dictates that date-only ISO strings (such as "2026-11-02") parsed by new Date() are interpreted as UTC midnight: 2026-11-02T00:00:00.000Z.
// Executed in New York (America/New_York, UTC-5):400 font-semibold">const d = 400 font-semibold">new Date(400 font-semibold">class="text-emerald-400">"2026-11-02");// In UTC, this is midnight. But in New York, it is 7:00 PM on Nov 1st!console.log(d.getDate()); 400 font-semibold">class=400 font-semibold">class="text-emerald-400">"text-slate-500 italic">// Returns 1! (Shifted backward by one full day)console.log(d.toLocaleDateString()); 400 font-semibold">class=400 font-semibold">class="text-emerald-400">"text-slate-500 italic">// 400 font-semibold">class="text-emerald-400">"11/1/2026" (Wrong date!)
DaySafe operates purely on date parts without passing through an instantaneous timestamp. formatDate("2026-11-02") locks formatting to UTC, guaranteeing that the result is identical in every country and timezone on Earth.
Trap 2: Daylight Saving Time (DST) Arithmetic Distortions
A common mistake is calculating day differences by subtracting millisecond timestamps and dividing by 86,400,000 (1000 * 60 * 60 * 24):
// Spring DST transition: a calendar day is only 23 hours long!400 font-semibold">const t1 = 400 font-semibold">new Date(2026, 2, 7).getTime();400 font-semibold">const t2 = 400 font-semibold">new Date(2026, 2, 9).getTime();400 font-semibold">const diff = (t2 - t1) / (1000 * 60 * 60 * 24);console.log(diff); 400 font-semibold">class=400 font-semibold">class="text-emerald-400">"text-slate-500 italic">// 1.9583333333333333 ! (Fractional days!)console.log(Math.floor(diff)); 400 font-semibold">class=400 font-semibold">class="text-emerald-400">"text-slate-500 italic">// 1 (Missed a whole day!)
DaySafe's daysBetween() uses integer civil day numbers (Howard Hinnant's algorithms), ensuring that calendar day counts between dates are always exact integers regardless of clocks moving forward or backward.
Architectural Scope & Boundaries
What DaySafe Is For
- Calendar day calculations (birthdays, bookings, due dates).
- Strict YYYY-MM-DD input validation.
- Month-end clamping arithmetic.
- Civil calendar date comparisons and sorting.
- Timezone-safe localized string formatting.
What DaySafe Does Not Do
- It does not manage time-of-day (hours, minutes, seconds).
- It does not convert times between IANA timezones.
- It does not perform stopwatch or duration timers.
- It supports Gregorian calendar years 0001 through 9999.