/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
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"
}
}
}