Skip to content

freeTimeExpiry

  • Container free time, demurrage, billing deadlines, bill of lading dates and EDI timestamps.
freeTimeExpiry(clockStart: string, freeDays: number, options: FreeTimeOptions): FreeTime | null

Lay out a container’s free time from the instant its clock started, and say when it ends.

  • Free time is the money calculation in container logistics, and none of its terms is a fact about the port: the number of free days, whether the day of discharge is free day one, and whether weekends count are all set by the carrier’s tariff or the service contract. So every term is a parameter. No regulation or industry standard fixes how free time is counted (DCSA types only its unit: calendar days, working days, hours or day of discharge; GMT counts days); the result is the window every tariff states, and what an invoice can print about it: the free time allowed and the dates it starts and ends.
  • Days are the terminal’s local days. clockStart is an instant (Z, an offset, or a bracketed zone, of which only the instant is read, a minute-rounded offset as the zone’s real one, as isValidInstant describes); its local date in options.timeZone is the event day. A container discharged at 23:00 has used a free day by 00:01 under firstDay: "eventDay". Day boundaries are the zone’s real ones, as floorToZone and dwellTime find them: a 23- or 25-hour day is one day, a date the zone deleted is never a free day, and a fall-back that re-enters an earlier date never moves the count backwards.
  • firstDay has no default. "eventDay" makes the event day free day one; "nextDay" starts free time on the following counted day. The two differ by exactly one day of charges, and a silent default here is a wrong invoice, so omitting it returns null.
  • basis decides which days count. "calendar" counts every local day, so a weekend burns two free days while the terminal is shut. "working" counts only the working days of options.calendar (its weekend and holidays; calendar.timeZone is not read), and is required to have one: a working-day count without a calendar is a guess and returns null. Under "working" a Saturday discharge starts free time on Monday under either firstDay. A tariff counting “calendar days excluding bank holidays” (Hapag-Lloyd Japan, MSC Vietnam) is "working" with an empty weekend. How days after expiry are charged is a separate term; see chargeableDays’s chargeBasis.
  • expiresAt is half-open. It is the first instant of the local day after lastFreeDay, as a UTC instant. A gate-out at exactly expiresAt is not a chargeable day; see chargeableDays. Under "working" it can fall on a weekend, which is then simply not chargeable.
  • freeDays is a whole number of at least 1: with no free days there is no last free day to name, and chargeableDays accepts 0 for that tariff.
  • Returns null when clockStart is not an instant, freeDays is not a whole number of at least 1, options is not an object, basis or firstDay is missing or unknown, timeZone is invalid, basis is "working" without a valid BusinessCalendar, or the walk would pass 10,000 local days.
  • Limit at an offset with seconds. The instant is placed in the offset’s whole-minute zone, moved by its seconds, and the moved instant must be inside Temporal’s range. So within the offset’s seconds (under a minute) of the last instant Temporal supports (+275760-09-13T00:00:00Z) for an offset east of UTC, or of the first (-271821-04-20T00:00:00Z) for one west, this returns null. An offset to the minute has no such limit.
ParameterTypeDescription
clockStartstringISO 8601 instant string of the event that starts the clock (discharge, availability or gate-out)
freeDaysnumberwhole number of free days the tariff allows, at least 1

options

The tariff’s counting terms: which days count, in which zone, and where day one starts

Type: FreeTimeOptions.

OptionTypeDefaultDescription
basis"calendar" | "working" (FreeTimeBasis)RequiredWhich days use up free time. "calendar" counts every local day, so a weekend burns two free days while the terminal is shut; "working" counts only the days calendar marks as working, and the clock does not run on weekends or holidays.
timeZonestringRequiredThe time zone of the terminal, depot or yard whose local midnight ends a day, an IANA name or UTC offset: a time zone identifier (+05:30, +0530, -08) or a stored offset (±HH:MM[:SS], what getTimeZoneOffset returns). GMT does not resolve a port or terminal code to a zone. A UTC offset such as "+02:00" observes no DST.
firstDay"eventDay" | "nextDay" (FreeTimeFirstDay)RequiredWhere free time starts. "eventDay" makes the day of the event that starts the clock free day one; "nextDay" starts free time on the following counted day.
calendar?BusinessCalendarNone. A "working" basis without it is invalid input.The terminal’s working week and holidays. It is required when a counting basis is "working" and not read otherwise. Its own timeZone is not used: timeZone above is the counting zone.

the free-time start and last free day as local dates, and the expiry instant, or null on invalid input

What freeTimeExpiry returns: the free-time window as local dates, and the instant it ends.

import type { FreeTime } from "@northguild/gmt";
MemberTypeDescription
freeTimeStartstringThe first free day, as a local date in the counting zone.
lastFreeDaystringThe last free day, as a local date in the counting zone.
expiresAtstringThe instant free time ends: the start of the local day after lastFreeDay, as a UTC instant.
import { freeTimeExpiry } from "@northguild/gmt/intermodal/calculate";
freeTimeExpiry("2024-06-14T19:00:00Z", 3, { basis: "calendar", timeZone: "America/New_York", firstDay: "eventDay" }) // { freeTimeStart: "2024-06-14", lastFreeDay: "2024-06-16", expiresAt: "2024-06-17T04:00:00Z" } (a Friday discharge: the weekend is consumed)
output 

intermodal/calculate/freeTimeExpiry.ts