Two Days Before Is Not 48 Hours
Two Days Before Is Not 48 Hours
A vessel sails at 18:00 on Friday 14 June 2024 from Amsterdam. The tariff closes documentation at 17:00 local, two days before sailing. When is the cut-off?
The naive approach
// The naive approach: two days is 48 hours
const sails = Date.parse("2024-06-14T16:00:00Z"); // Friday 18:00 in Amsterdam
const cutoff = new Date(sails - 48 * 36e5);
cutoff.toISOString(); // "2024-06-12T16:00:00.000Z" — Wednesday 18:00 in Amsterdam
// The terminal closed documentation at 17:00. Every filing made in that hour is late.Watch it break
The naive approach silently produces wrong results — there is no GMT equivalent to demonstrate because the failure is not using GMT.
Why
"Two days before, 17:00" names a day and a time of day on the terminal's clock, not a length of time. Subtracting 48 hours keeps the departure's own time of day, 18:00, so the cut-off is an hour late whenever the vessel sails at any time other than 17:00. When a DST transition falls between the two dates, 48 hours also lands on a different wall-clock hour. The pinned cut-off keeps its 17:00 and lets the elapsed time move instead: before a Monday 18:00 departure in New York, across the 10 March 2024 spring-forward, it is 48 elapsed hours earlier, not 49. `cutoffAt` with `atLocalTime` takes the two days off the local date and pins the time of day, so the cut-off is 17:00 on the terminal's clock whatever the departure time or the DST calendar.
The gmt approach
import { cutoffAt } from "@northguild/gmt/transport";
cutoffAt("2024-06-14T16:00:00Z", "P2D", { timeZone: "Europe/Amsterdam", atLocalTime: "17:00" });
// "2024-06-12T17:00:00+02:00[Europe/Amsterdam]" — Wednesday 17:00, 49 hours before sailing
cutoffAt("2024-06-14T16:00:00Z", "PT48H", { timeZone: "Europe/Amsterdam" });
// "2024-06-12T18:00:00+02:00[Europe/Amsterdam]" — what subtracting 48 hours gives
// Across the spring-forward the local time holds and the elapsed time moves.
cutoffAt("2024-03-11T22:00:00Z", "P2D", { timeZone: "America/New_York", atLocalTime: "17:00" });
// "2024-03-09T17:00:00-05:00[America/New_York]" — 48 elapsed hours before Monday 18:00 EDTSame widget, working
In the playground, change atLocalTime to "10:00" for a VGM cut-off, or remove it to see the
cut-off keep the departure’s 18:00. Add
calendar: { weekend: [6, 7], holidays: ["2024-06-12"], timeZone: "Europe/Amsterdam" }, roll: "preceding"
to make Wednesday a holiday: the cut-off rolls back to Tuesday. Change roll to "following"
and it moves to Thursday. Leave roll out and the result is "", because there is no default. The rules are
in Transport: Legs and
Dwell.
Or open this sailing in the Cut-off
Ruler:
with no clock change in between, P2D and PT48H agree at 18:00, and the pinned reading is
17:00. Across New York’s
fall-back
the three readings land an hour apart. Across the
spring-forward,
PT48H and the pinned 17:00 coincide, and P2D is 47 hours.