Skip to content

Demurrage Across a Weekend

  • Container free time, demurrage, billing deadlines, bill of lading dates and EDI timestamps.

Demurrage Across a Weekend

Discharged Friday 15:00 with three free calendar days, the container gates out on Monday morning. How many days of demurrage are due, and for which dates?

The naive approach

// The naive approach: elapsed hours beyond the free time, rounded up
const discharged = new Date("2024-06-14T19:00:00Z");
const gatedOut = new Date("2024-06-17T04:00:01Z");
const hours = (gatedOut.getTime() - discharged.getTime()) / 36e5; // 57.0003
const chargeable = Math.max(0, Math.ceil((hours - 72) / 24));
// 0 — but free time ended at Monday 00:00, and the box left one second into Monday

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

Free time ended at 00:00 on Monday, the start of the local day after the last free day. The dwell is half-open, so a gate-out at exactly that instant is not a chargeable day, and a gate-out one second later is one: Monday 17 June, which `chargedDates` lists, so the count can be checked date by date (the dates an itemised invoice can list). The weekend was consumed on the calendar basis whether or not the terminal was open. `freeDaysUsed` plus `chargeableDays` is exactly `dwellTime`'s `calendarDays` for the same dwell.

The gmt approach

import { chargeableDays } from "@northguild/gmt/intermodal";

const tariff = { basis: "calendar", chargeBasis: "calendar", timeZone: "America/New_York", firstDay: "eventDay" };

chargeableDays("2024-06-14T19:00:00Z", "2024-06-17T04:00:00Z", 3, tariff);
// { freeDaysUsed: 3, chargeableDays: 0, expiresAt: "2024-06-17T04:00:00Z",
//   chargedDates: [], byTier: [{ from: 1, to: null, days: 0 }] }

chargeableDays("2024-06-14T19:00:00Z", "2024-06-17T04:00:01Z", 3, tariff);
// { freeDaysUsed: 3, chargeableDays: 1, expiresAt: "2024-06-17T04:00:00Z",
//   chargedDates: ["2024-06-17"], byTier: [{ from: 1, to: null, days: 1 }] }

// Twelve chargeable days in bands of five, five and the rest.
chargeableDays("2024-06-14T19:00:00Z", "2024-06-28T15:00:00Z", 3, { ...tariff, tiers: [5, 10] });
// { freeDaysUsed: 3, chargeableDays: 12, expiresAt: "2024-06-17T04:00:00Z",
//   chargedDates: ["2024-06-17", "2024-06-18", "2024-06-19", "2024-06-20", "2024-06-21",
//                  "2024-06-22", "2024-06-23", "2024-06-24", "2024-06-25", "2024-06-26",
//                  "2024-06-27", "2024-06-28"],
//   byTier: [{ from: 1, to: 5, days: 5 }, { from: 6, to: 10, days: 5 }, { from: 11, to: null, days: 2 }] }

Same widget, working

output 

Change firstDay to "nextDay" above and the Monday-morning gate-out is still free, because free time then ends on Tuesday. Change basis to "working", add calendar: { weekend: [6, 7], holidays: ["2024-06-19"], timeZone: "America/New_York" } and gate out at 2024-06-24T15:00:00Z: the free days are Friday, Monday and Tuesday, and free time ends as Juneteenth begins. With chargeBasis: "calendar", the most common shape where free time is in working days, all six days from the 19th to the 24th are charged, holiday and weekend included. Change chargeBasis to "working", as some tariffs do, and only the 20th, 21st and 24th are. To drag the gate-out across the expiry yourself, open it in the Free Time Ledger. The full rules are in Intermodal: Free Time and Demurrage.