API reference

Difference between two times

Elapsed time and calendar difference between two instants, DST-aware.

GET POST /api/time/diff MCP tool time_diff

Computes end minus start. total is exact elapsed time (a day is 86,400,000 ms). calendar is the breakdown in years, months, days, hours, minutes, seconds and milliseconds as a calendar in zone sees it, counted forward from the earlier instant: adding it to the earlier instant with time_add in the same zone gives the later instant exactly. Across a DST change the two differ: 12:00 to 12:00 the next day is P1D on the calendar but 23 or 25 hours elapsed. calendar_days counts midnights crossed in zone.

Browser version (runs locally): Time & Time Zones.

Try it

Credentials
Remember these credentials

Not saved: you will paste them again next time.

No credentials yet? Get a client ID and secret. They are sent only to this site, in an Authorization: Basic header.

POST /api/time/diff with a JSON body

Parameters

Parameter Type Default Description
start required string (≤ 100 chars) — Start instant. ISO 8601 / RFC 3339 (2026-10-13T14:30:00Z, 2026-10-13T14:30:00+02:00, 2026-10-13 14:30, 2026-10-13, 2026-W42-2, with an optional RFC 9557 [Zone/Name] suffix), RFC 2822, HTTP-date (IMF-fixdate, RFC 850, asctime), a Unix timestamp (seconds, milliseconds, microseconds or nanoseconds; decimals allowed), or now.
end required string (≤ 100 chars) — End instant, same formats as start.
zone string (≤ 64 chars) UTC Zone for the calendar breakdown and calendar_days, and for reading start/end that have no UTC offset. IANA name, UTC, or fixed offset.
unit auto | seconds | milliseconds | microseconds | nanoseconds auto Unit of a numeric Unix timestamp. auto infers it from the magnitude: below 10^11 seconds, below 10^14 milliseconds, below 10^17 microseconds, otherwise nanoseconds. Only valid with numeric input.
disambiguation compatible | earlier | later | reject compatible How to resolve a wall-clock time that falls in a DST gap or overlap (same meaning as in JavaScript Temporal). compatible: gap → shift forward by the gap length, overlap → earlier instant. earlier / later: pick that instant (in a gap, the instant before or after the gap). reject: fail with 422.

Also accepted on every endpoint: format (json | text). Parameters may come from the query string, a JSON object body, an urlencoded or multipart form, or (for input) a text/plain body.

Global parameters
Parameter Type Default Description
format json | text json json returns the documented object. text returns only the primary value as text/plain (see "Text output").

Response 200 application/json

Field Type Description
start object
start.interpretation object
start.interpretation.input string The input as received (trimmed).
start.interpretation.format "now" | "iso8601" | "rfc2822" | "http-date" | "unix" How the input was parsed.
start.interpretation.unit string | null Unit of a Unix timestamp (seconds, milliseconds, microseconds, nanoseconds); null for other formats.
start.interpretation.unit_auto_detected boolean True when the timestamp unit was inferred from its magnitude.
start.interpretation.has_offset boolean False when the input was a wall-clock time without a UTC offset, resolved in local_zone.
start.interpretation.local_zone string | null Zone used to resolve a wall-clock input; null otherwise.
start.interpretation.local_time string | null For wall-clock input: unique, gap (did not exist; DST jump forward) or overlap (occurred twice; DST jump back). Null otherwise.
start.interpretation.notes array of string Every assumption made: auto-detected units and their alternatives, the zone used for wall-clock input, DST resolution, truncated precision.
start.instant object
start.instant.utc string The instant in UTC, ISO 8601 with Z.
start.instant.unix_seconds integer Whole seconds since 1970-01-01T00:00:00Z, rounded down (floor).
start.instant.unix_milliseconds integer Milliseconds since 1970-01-01T00:00:00Z.
start.local object The start instant in zone.
start.local.zone string Zone name as resolved: IANA name (letter case corrected), UTC, or UTC±HH:MM.
start.local.iso string ISO 8601 / RFC 3339 with the UTC offset (Z for UTC). Milliseconds appear only when non-zero.
start.local.local string Wall-clock date-time in the zone without an offset, YYYY-MM-DDTHH:mm:ss[.SSS].
start.local.date string Local calendar date, YYYY-MM-DD.
start.local.time string Local time, HH:mm:ss (24-hour), with .SSS only when milliseconds are non-zero.
start.local.offset string UTC offset, ±HH:MM.
start.local.offset_minutes integer UTC offset in minutes (east positive), e.g. -240 for UTC-04:00.
start.local.abbreviation string Short zone name as Intl (en-US) prints it: EDT, PST, or GMT+2 where English has no common abbreviation.
start.local.offset_name string Long zone name, e.g. Eastern Daylight Time.
start.local.dst boolean True when daylight saving time is in effect at this instant.
start.local.weekday string English weekday name, e.g. Tuesday.
start.local.weekday_number integer ISO 8601 weekday: 1 = Monday … 7 = Sunday.
start.local.iso_week string ISO 8601 week, YYYY-Www (the week-numbering year can differ from the calendar year near 1 January).
start.local.iso_week_year integer ISO 8601 week-numbering year.
start.local.iso_week_number integer ISO 8601 week number, 1-53.
start.local.day_of_year integer Ordinal day of the year, 1-366.
start.local.formatted map of string Extra renderings requested with formats (keyed by format name) and pattern (key pattern). Empty when none were requested.
end object
end.interpretation object
end.interpretation.input string The input as received (trimmed).
end.interpretation.format "now" | "iso8601" | "rfc2822" | "http-date" | "unix" How the input was parsed.
end.interpretation.unit string | null Unit of a Unix timestamp (seconds, milliseconds, microseconds, nanoseconds); null for other formats.
end.interpretation.unit_auto_detected boolean True when the timestamp unit was inferred from its magnitude.
end.interpretation.has_offset boolean False when the input was a wall-clock time without a UTC offset, resolved in local_zone.
end.interpretation.local_zone string | null Zone used to resolve a wall-clock input; null otherwise.
end.interpretation.local_time string | null For wall-clock input: unique, gap (did not exist; DST jump forward) or overlap (occurred twice; DST jump back). Null otherwise.
end.interpretation.notes array of string Every assumption made: auto-detected units and their alternatives, the zone used for wall-clock input, DST resolution, truncated precision.
end.instant object
end.instant.utc string The instant in UTC, ISO 8601 with Z.
end.instant.unix_seconds integer Whole seconds since 1970-01-01T00:00:00Z, rounded down (floor).
end.instant.unix_milliseconds integer Milliseconds since 1970-01-01T00:00:00Z.
end.local object The end instant in zone.
end.local.zone string Zone name as resolved: IANA name (letter case corrected), UTC, or UTC±HH:MM.
end.local.iso string ISO 8601 / RFC 3339 with the UTC offset (Z for UTC). Milliseconds appear only when non-zero.
end.local.local string Wall-clock date-time in the zone without an offset, YYYY-MM-DDTHH:mm:ss[.SSS].
end.local.date string Local calendar date, YYYY-MM-DD.
end.local.time string Local time, HH:mm:ss (24-hour), with .SSS only when milliseconds are non-zero.
end.local.offset string UTC offset, ±HH:MM.
end.local.offset_minutes integer UTC offset in minutes (east positive), e.g. -240 for UTC-04:00.
end.local.abbreviation string Short zone name as Intl (en-US) prints it: EDT, PST, or GMT+2 where English has no common abbreviation.
end.local.offset_name string Long zone name, e.g. Eastern Daylight Time.
end.local.dst boolean True when daylight saving time is in effect at this instant.
end.local.weekday string English weekday name, e.g. Tuesday.
end.local.weekday_number integer ISO 8601 weekday: 1 = Monday … 7 = Sunday.
end.local.iso_week string ISO 8601 week, YYYY-Www (the week-numbering year can differ from the calendar year near 1 January).
end.local.iso_week_year integer ISO 8601 week-numbering year.
end.local.iso_week_number integer ISO 8601 week number, 1-53.
end.local.day_of_year integer Ordinal day of the year, 1-366.
end.local.formatted map of string Extra renderings requested with formats (keyed by format name) and pattern (key pattern). Empty when none were requested.
zone string Zone used for the calendar breakdown.
sign integer 1 when end is after start, -1 when before, 0 when equal.
direction "forward" | "backward" | "same" forward (end after start), backward, or same.
total object
total.milliseconds integer Exact elapsed milliseconds, end - start (signed).
total.seconds number Elapsed seconds (signed, may be fractional).
total.minutes number Elapsed minutes (signed, may be fractional).
total.hours number Elapsed hours (signed, may be fractional).
total.days number Elapsed time in 24-hour days (signed, may be fractional).
total.weeks number Elapsed time in 7-day weeks (signed, may be fractional).
calendar object Calendar breakdown in zone, magnitudes only (see sign), from the earlier to the later instant.
calendar.years integer Whole years.
calendar.months integer Whole months after the years, 0-11.
calendar.days integer Whole days after the months.
calendar.hours integer Hours after the days, 0-23 except across DST.
calendar.minutes integer Minutes, 0-59.
calendar.seconds integer Seconds, 0-59.
calendar.milliseconds integer Milliseconds, 0-999.
calendar_days integer Signed difference between the two local calendar dates in zone (end date minus start date), ignoring time of day.
iso_duration string The calendar breakdown as an ISO 8601 duration, prefixed with - when end is before start, e.g. P1M2DT3H, -P1D, PT0S.
iso_duration_exact string Exact elapsed time as an ISO 8601 duration in hours, minutes and seconds only, e.g. PT23H.
human string English summary of the calendar breakdown, e.g. 1 month, 2 days later.
warnings array of string Warnings about the zones given (fixed offsets, legacy or inverted names).

Text output (format=text, text/plain): The calendar ISO 8601 duration (iso_duration).

Errors

Errors are JSON: {"error": "<message>", "code": "<code>", "param": "<name>"?}.

Status Code When
400 invalid_parameter zone is unknown or an abbreviation.
422 unprocessable_input start or end cannot be parsed, is out of range, or falls in a DST gap/overlap with disambiguation=reject.
400 missing_parameter A required parameter is absent.
400 unknown_parameter A parameter name is not in the table above (typos are rejected, not ignored).
413 payload_too_large The request body exceeds 1 MiB.
405 method_not_allowed A method other than GET, POST or OPTIONS.

Behaviour

  • Idempotent: the same arguments always return the same result.
  • Requires a client ID and secret (authentication). Each call is recorded as one metadata row against your client ID; inputs and outputs are never stored or logged. Responses carry cache-control: no-store.
  • CORS: any origin may call this endpoint from a browser, using your credentials.

Examples

Responses below were produced by running each request through this code at build time. Long strings are shortened.

Across the US spring DST change: one calendar day, 23 hours

curl 'https://util.caruana.me/api/time/diff?start=2026-03-07T12%3A00&end=2026-03-08T12%3A00&zone=America%2FNew_York'

Response 200

{
  "start": {
    "interpretation": {
      "input": "2026-03-07T12:00",
      "format": "iso8601",
      "unit": null,
      "unit_auto_detected": false,
      "has_offset": false,
      "local_zone": "America/New_York",
      "local_time": "unique",
      "notes": [
        "No offset in the input: read as wall-clock time in America/New_York (set by zone)."
      ]
    },
    "instant": {
      "utc": "2026-03-07T17:00:00Z",
      "unix_seconds": 1772902800,
      "unix_milliseconds": 1772902800000
    },
    "local": {
      "zone": "America/New_York",
      "iso": "2026-03-07T12:00:00-05:00",
      "local": "2026-03-07T12:00:00",
      "date": "2026-03-07",
      "time": "12:00:00",
      "offset": "-05:00",
      "offset_minutes": -300,
      "abbreviation": "EST",
      "offset_name": "Eastern Standard Time",
      "dst": false,
      "weekday": "Saturday",
      "weekday_number": 6,
      "iso_week": "2026-W10",
      "iso_week_year": 2026,
      "iso_week_number": 10,
      "day_of_year": 66,
      "formatted": {}
    }
  },
  "end": {
    "interpretation": {
      "input": "2026-03-08T12:00",
      "format": "iso8601",
      "unit": null,
      "unit_auto_detected": false,
      "has_offset": false,
      "local_zone": "America/New_York",
      "local_time": "unique",
      "notes": [
        "No offset in the input: read as wall-clock time in America/New_York (set by zone)."
      ]
    },
    "instant": {
      "utc": "2026-03-08T16:00:00Z",
      "unix_seconds": 1772985600,
      "unix_milliseconds": 1772985600000
    },
    "local": {
      "zone": "America/New_York",
      "iso": "2026-03-08T12:00:00-04:00",
      "local": "2026-03-08T12:00:00",
      "date": "2026-03-08",
      "time": "12:00:00",
      "offset": "-04:00",
      "offset_minutes": -240,
      "abbreviation": "EDT",
      "offset_name": "Eastern Daylight Time",
      "dst": true,
      "weekday": "Sunday",
      "weekday_number": 7,
      "iso_week": "2026-W10",
      "iso_week_year": 2026,
      "iso_week_number": 10,
      "day_of_year": 67,
      "formatted": {}
    }
  },
  "zone": "America/New_York",
  "sign": 1,
  "direction": "forward",
  "total": {
    "milliseconds": 82800000,
    "seconds": 82800,
    "minutes": 1380,
    "hours": 23,
    "days": 0.9583333333333334,
    "weeks": 0.13690476190476192
  },
  "calendar": {
    "years": 0,
    "months": 0,
    "days": 1,
    "hours": 0,
    "minutes": 0,
    "seconds": 0,
    "milliseconds": 0
  },
  "calendar_days": 1,
  "iso_duration": "P1D",
  "iso_duration_exact": "PT23H",
  "human": "1 day later",
  "warnings": []
}
Same call as an MCP tools/call
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/call",
  "params": {
    "name": "time_diff",
    "arguments": {
      "start": "2026-03-07T12:00",
      "end": "2026-03-08T12:00",
      "zone": "America/New_York"
    }
  }
}

Month-end arithmetic in UTC

curl 'https://util.caruana.me/api/time/diff?start=2026-01-31T10%3A00%3A00Z&end=2026-03-01T09%3A00%3A00Z'

Response 200

{
  "start": {
    "interpretation": {
      "input": "2026-01-31T10:00:00Z",
      "format": "iso8601",
      "unit": null,
      "unit_auto_detected": false,
      "has_offset": true,
      "local_zone": null,
      "local_time": null,
      "notes": []
    },
    "instant": {
      "utc": "2026-01-31T10:00:00Z",
      "unix_seconds": 1769853600,
      "unix_milliseconds": 1769853600000
    },
    "local": {
      "zone": "UTC",
      "iso": "2026-01-31T10:00:00Z",
      "local": "2026-01-31T10:00:00",
      "date": "2026-01-31",
      "time": "10:00:00",
      "offset": "+00:00",
      "offset_minutes": 0,
      "abbreviation": "UTC",
      "offset_name": "UTC",
      "dst": false,
      "weekday": "Saturday",
      "weekday_number": 6,
      "iso_week": "2026-W05",
      "iso_week_year": 2026,
      "iso_week_number": 5,
      "day_of_year": 31,
      "formatted": {}
    }
  },
  "end": {
    "interpretation": {
      "input": "2026-03-01T09:00:00Z",
      "format": "iso8601",
      "unit": null,
      "unit_auto_detected": false,
      "has_offset": true,
      "local_zone": null,
      "local_time": null,
      "notes": []
    },
    "instant": {
      "utc": "2026-03-01T09:00:00Z",
      "unix_seconds": 1772355600,
      "unix_milliseconds": 1772355600000
    },
    "local": {
      "zone": "UTC",
      "iso": "2026-03-01T09:00:00Z",
      "local": "2026-03-01T09:00:00",
      "date": "2026-03-01",
      "time": "09:00:00",
      "offset": "+00:00",
      "offset_minutes": 0,
      "abbreviation": "UTC",
      "offset_name": "UTC",
      "dst": false,
      "weekday": "Sunday",
      "weekday_number": 7,
      "iso_week": "2026-W09",
      "iso_week_year": 2026,
      "iso_week_number": 9,
      "day_of_year": 60,
      "formatted": {}
    }
  },
  "zone": "UTC",
  "sign": 1,
  "direction": "forward",
  "total": {
    "milliseconds": 2502000000,
    "seconds": 2502000,
    "minutes": 41700,
    "hours": 695,
    "days": 28.958333333333332,
    "weeks": 4.136904761904762
  },
  "calendar": {
    "years": 0,
    "months": 1,
    "days": 0,
    "hours": 23,
    "minutes": 0,
    "seconds": 0,
    "milliseconds": 0
  },
  "calendar_days": 29,
  "iso_duration": "P1MT23H",
  "iso_duration_exact": "PT695H",
  "human": "1 month, 23 hours later",
  "warnings": []
}
Same call as an MCP tools/call
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/call",
  "params": {
    "name": "time_diff",
    "arguments": {
      "start": "2026-01-31T10:00:00Z",
      "end": "2026-03-01T09:00:00Z"
    }
  }
}