API reference

Add or subtract a duration

Add or subtract an ISO 8601 duration to an instant, calendar-aware in a time zone.

GET POST /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

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/add with a JSON body

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