/api/time/add
MCP tool time_add Applies the duration the way JavaScript Temporal does (ZonedDateTime.add): years, months, weeks and days move the wall-clock date in zone and keep the wall-clock time, so P1D across a DST change lands at the same local time (23 or 25 hours later); a day of the month that does not exist is clamped to the last day (Jan 31 + P1M = Feb 28/29); then hours, minutes, seconds and milliseconds are added as exact elapsed time (PT24H is always 86,400 s). If the intermediate wall-clock time falls in a DST gap or overlap it is resolved with disambiguation and reported in notes.
Browser version (runs locally): Time & Time Zones.
Try it
Parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
input 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. |
duration | string (≤ 64 chars) | — | ISO 8601 duration: P1D, PT36H, P1Y2M10DT2H30M, P2W, -P1M (negative subtracts), PT1.5H. Fractions only on the smallest of hours/minutes/seconds, to millisecond precision. Use either duration or amount + amount_unit. |
amount | integer | — | Signed whole number of amount_unit to add (negative subtracts). Alternative to duration. |
amount_unit | years | months | weeks | days | hours | minutes | seconds | milliseconds | — | Unit of amount. Required with amount. |
subtract | boolean | false | Subtract the duration instead of adding it (negates it). |
zone | string (≤ 64 chars) | UTC | Zone whose calendar and wall clock govern years/months/weeks/days, used for reading an input without offset, and for the output. The offset inside input does not choose the zone: pass America/New_York to get New York DST behaviour. |
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. |
duration | object | |
duration.iso | string | The duration applied, normalised ISO 8601 (sign included; subtract folded in). |
duration.sign | integer | 1 to add, -1 to subtract. |
duration.years | integer | Years (magnitude). |
duration.months | integer | Months (magnitude). |
duration.weeks | integer | Weeks (magnitude). |
duration.days | integer | Days (magnitude). |
duration.hours | integer | Hours (magnitude). |
duration.minutes | integer | Minutes (magnitude). |
duration.seconds | integer | Seconds (magnitude). |
duration.milliseconds | integer | Milliseconds (magnitude). |
zone | string | Zone used. |
result | object | |
result.instant | object | |
result.instant.utc | string | The instant in UTC, ISO 8601 with Z. |
result.instant.unix_seconds | integer | Whole seconds since 1970-01-01T00:00:00Z, rounded down (floor). |
result.instant.unix_milliseconds | integer | Milliseconds since 1970-01-01T00:00:00Z. |
result.local | object | The result in zone. |
result.local.zone | string | Zone name as resolved: IANA name (letter case corrected), UTC, or UTC±HH:MM. |
result.local.iso | string | ISO 8601 / RFC 3339 with the UTC offset (Z for UTC). Milliseconds appear only when non-zero. |
result.local.local | string | Wall-clock date-time in the zone without an offset, YYYY-MM-DDTHH:mm:ss[.SSS]. |
result.local.date | string | Local calendar date, YYYY-MM-DD. |
result.local.time | string | Local time, HH:mm:ss (24-hour), with .SSS only when milliseconds are non-zero. |
result.local.offset | string | UTC offset, ±HH:MM. |
result.local.offset_minutes | integer | UTC offset in minutes (east positive), e.g. -240 for UTC-04:00. |
result.local.abbreviation | string | Short zone name as Intl (en-US) prints it: EDT, PST, or GMT+2 where English has no common abbreviation. |
result.local.offset_name | string | Long zone name, e.g. Eastern Daylight Time. |
result.local.dst | boolean | True when daylight saving time is in effect at this instant. |
result.local.weekday | string | English weekday name, e.g. Tuesday. |
result.local.weekday_number | integer | ISO 8601 weekday: 1 = Monday … 7 = Sunday. |
result.local.iso_week | string | ISO 8601 week, YYYY-Www (the week-numbering year can differ from the calendar year near 1 January). |
result.local.iso_week_year | integer | ISO 8601 week-numbering year. |
result.local.iso_week_number | integer | ISO 8601 week number, 1-53. |
result.local.day_of_year | integer | Ordinal day of the year, 1-366. |
result.local.formatted | map of string | Extra renderings requested with formats (keyed by format name) and pattern (key pattern). Empty when none were requested. |
elapsed_milliseconds | integer | Exact elapsed time from start to result (signed). |
local_time | string | null | Status of the wall-clock time reached by the calendar part: unique, gap or overlap; null when the duration has no calendar part. |
notes | array of string | What happened: day-of-month clamping, DST offset changes, gap/overlap resolution. |
warnings | array of string | Warnings about the zones given (fixed offsets, legacy or inverted names). |
Text output (format=text, text/plain): The result as ISO 8601 in zone.
Errors
Errors are JSON: {"error": "<message>", "code": "<code>", "param": "<name>"?}.
| Status | Code | When |
|---|---|---|
400 | invalid_parameter | zone is unknown, both or neither of duration and amount are given, or amount lacks amount_unit. |
422 | unprocessable_input | input or duration cannot be parsed (e.g. P1.5D), the result is out of range, or a gap/overlap is hit 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.
P1D across the US spring DST change keeps 12:00
curl 'https://util.caruana.me/api/time/add?input=2026-03-07T12%3A00%3A00-05%3A00&duration=P1D&zone=America%2FNew_York'
Response 200
{
"start": {
"interpretation": {
"input": "2026-03-07T12:00:00-05:00",
"format": "iso8601",
"unit": null,
"unit_auto_detected": false,
"has_offset": true,
"local_zone": null,
"local_time": null,
"notes": []
},
"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": {}
}
},
"duration": {
"iso": "P1D",
"sign": 1,
"years": 0,
"months": 0,
"weeks": 0,
"days": 1,
"hours": 0,
"minutes": 0,
"seconds": 0,
"milliseconds": 0
},
"zone": "America/New_York",
"result": {
"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": {}
}
},
"elapsed_milliseconds": 82800000,
"local_time": "unique",
"notes": [
"The UTC offset in America/New_York changed from -05:00 to -04:00; the wall-clock time was kept, so the elapsed time differs from 1 × 24 hours."
],
"warnings": []
}
Same call as an MCP tools/call
{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/call",
"params": {
"name": "time_add",
"arguments": {
"input": "2026-03-07T12:00:00-05:00",
"duration": "P1D",
"zone": "America/New_York"
}
}
}Month-end clamping: Jan 31 + 1 month
curl 'https://util.caruana.me/api/time/add?input=2026-01-31&amount=1&amount_unit=months'
Response 200
{
"start": {
"interpretation": {
"input": "2026-01-31",
"format": "iso8601",
"unit": null,
"unit_auto_detected": false,
"has_offset": false,
"local_zone": "UTC",
"local_time": "unique",
"notes": [
"No offset in the input: read as wall-clock time in UTC (set by zone)."
]
},
"instant": {
"utc": "2026-01-31T00:00:00Z",
"unix_seconds": 1769817600,
"unix_milliseconds": 1769817600000
},
"local": {
"zone": "UTC",
"iso": "2026-01-31T00:00:00Z",
"local": "2026-01-31T00:00:00",
"date": "2026-01-31",
"time": "00: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": {}
}
},
"duration": {
"iso": "P1M",
"sign": 1,
"years": 0,
"months": 1,
"weeks": 0,
"days": 0,
"hours": 0,
"minutes": 0,
"seconds": 0,
"milliseconds": 0
},
"zone": "UTC",
"result": {
"instant": {
"utc": "2026-02-28T00:00:00Z",
"unix_seconds": 1772236800,
"unix_milliseconds": 1772236800000
},
"local": {
"zone": "UTC",
"iso": "2026-02-28T00:00:00Z",
"local": "2026-02-28T00:00:00",
"date": "2026-02-28",
"time": "00:00:00",
"offset": "+00:00",
"offset_minutes": 0,
"abbreviation": "UTC",
"offset_name": "UTC",
"dst": false,
"weekday": "Saturday",
"weekday_number": 6,
"iso_week": "2026-W09",
"iso_week_year": 2026,
"iso_week_number": 9,
"day_of_year": 59,
"formatted": {}
}
},
"elapsed_milliseconds": 2419200000,
"local_time": "unique",
"notes": [
"Day 31 does not exist in February 2026; clamped to the last day of the month (2026-02-28)."
],
"warnings": []
}
Same call as an MCP tools/call
{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/call",
"params": {
"name": "time_add",
"arguments": {
"input": "2026-01-31",
"amount": 1,
"amount_unit": "months"
}
}
}Subtract 90 minutes
curl 'https://util.caruana.me/api/time/add?input=2026-10-13T00%3A30%3A00Z&duration=PT90M&subtract=true'
Response 200
{
"start": {
"interpretation": {
"input": "2026-10-13T00:30:00Z",
"format": "iso8601",
"unit": null,
"unit_auto_detected": false,
"has_offset": true,
"local_zone": null,
"local_time": null,
"notes": []
},
"instant": {
"utc": "2026-10-13T00:30:00Z",
"unix_seconds": 1791851400,
"unix_milliseconds": 1791851400000
},
"local": {
"zone": "UTC",
"iso": "2026-10-13T00:30:00Z",
"local": "2026-10-13T00:30:00",
"date": "2026-10-13",
"time": "00:30:00",
"offset": "+00:00",
"offset_minutes": 0,
"abbreviation": "UTC",
"offset_name": "UTC",
"dst": false,
"weekday": "Tuesday",
"weekday_number": 2,
"iso_week": "2026-W42",
"iso_week_year": 2026,
"iso_week_number": 42,
"day_of_year": 286,
"formatted": {}
}
},
"duration": {
"iso": "-PT90M",
"sign": -1,
"years": 0,
"months": 0,
"weeks": 0,
"days": 0,
"hours": 0,
"minutes": 90,
"seconds": 0,
"milliseconds": 0
},
"zone": "UTC",
"result": {
"instant": {
"utc": "2026-10-12T23:00:00Z",
"unix_seconds": 1791846000,
"unix_milliseconds": 1791846000000
},
"local": {
"zone": "UTC",
"iso": "2026-10-12T23:00:00Z",
"local": "2026-10-12T23:00:00",
"date": "2026-10-12",
"time": "23:00:00",
"offset": "+00:00",
"offset_minutes": 0,
"abbreviation": "UTC",
"offset_name": "UTC",
"dst": false,
"weekday": "Monday",
"weekday_number": 1,
"iso_week": "2026-W42",
"iso_week_year": 2026,
"iso_week_number": 42,
"day_of_year": 285,
"formatted": {}
}
},
"elapsed_milliseconds": -5400000,
"local_time": null,
"notes": [],
"warnings": []
}
Same call as an MCP tools/call
{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/call",
"params": {
"name": "time_add",
"arguments": {
"input": "2026-10-13T00:30:00Z",
"duration": "PT90M",
"subtract": true
}
}
}