Docs

Time zones

A holiday is a civil date in its own jurisdiction, not an instant. Christmas Day in Tokyo is 25 December in Tokyo — it does not become the 24th because you asked from Berlin. Everything below follows from that one decision.

Dates are never shifted

Every date this API returns is the date as the jurisdiction itself keeps it. We do not convert it into your zone, because there is no such thing as Christmas-in-your-zone: the shops shut in Tokyo on Tokyo's 25th whatever your calendar says.

What you often need instead is the instant — when the closure starts and ends in UTC, and which of your days it lands on. That is what tz is for, and it adds information rather than changing any.

Passing your zone

Add tz to /v1/calendar. It takes an IANA nameAsia/Tokyo, America/Sao_Paulo, Europe/Athens. Not an offset, not an abbreviation.

curl -H "X-API-Key: $TIMESYS_KEY" \
  "https://api.timesys.pro/v1/calendar?jurisdictions=JP\
&from=2027-01-01&to=2027-12-31&tz=Europe/Berlin"

Each event then carries:

FieldWhat it is
dateThe civil date in the jurisdiction. Never shifted, with or without tz.
starts_utcMidnight at the start of that day, in the jurisdiction's zone, expressed in UTC.
ends_utcMidnight at the end of it. The closure occupies exactly this interval.
tzThe jurisdiction's own zone — the one the interval was computed in.
viewer_tzThe zone you passed, echoed so a stored response can be read later.
viewer_dateThe date starts_utc falls on for you. Frequently not date.
viewer_startsThe same instant written in your zone.
Why viewer_date is the useful one. A Tokyo holiday begins at 15:00 UTC the day before. If you are in Berlin planning a call, the question is not "what date is it in Japan" but "which of my afternoons disappears" — and the answer is the 24th, not the 25th. That is viewer_date. It is why the field exists and why it is not the same as converting the date.

An unknown zone is an error, not a fallback

A zone we cannot resolve returns 400, naming what it expected:

{"detail": "unknown timezone 'CET' — expects an IANA name such as 'America/Sao_Paulo'"}

We do not quietly fall back to UTC. A caller who mistypes a zone is asking a question we cannot answer, and answering a different one is how wrong times reach production. CET, EST and PST are all rejected: they are ambiguous, and several of them mean different offsets at different times of year.

Which zone a place is in

/v1/jurisdictions/{code} returns two fields, and they answer different questions:

FieldMeaning
timezoneThe presentation zone. The single zone this jurisdiction's dates are computed in.
timezonesEvery IANA zone the jurisdiction contains. The United States has 27.

For a single-zone country the two agree and nothing is lost. For a country spanning several, the presentation zone is a choice: national holidays are national, and they need one interval rather than 27.

A limit worth knowing before you rely on it. 300 subdivisions in 19 multi-zone countries currently present in their country's zone rather than their own. US-CA and US-HI both report America/New_York; CA-BC reports America/Toronto; AU-WA reports Australia/Sydney.

The dates are right — a Californian holiday is on the correct day. What is wrong is starts_utc for those subdivisions: it is computed from the national zone, so it can be several hours out. If you need exact instants for a subdivision in a multi-zone country, read timezones and compute the bound yourself for now. National-level requests are unaffected.

What we do not do

Not offeredWhy
UTC offsets (+02:00)An offset is not a zone. It cannot survive a DST transition, and a calendar that spans a year will cross one.
Zone abbreviationsCST is North American Central, China Standard and Cuba Standard time. Three answers, six hours apart.
Shifting the date itselfSee the top of this page. It would make the corpus wrong to make one caller's arithmetic shorter.
Working hoursA closure is a whole civil day here. Business hours are a separate layer and not yet published.

Practical recipes

"Is the Frankfurt office shut during my Tokyo morning?" Ask for DE with tz=Asia/Tokyo and read viewer_date.

"Store closures in a database with other timestamped events." Store starts_utc/ends_utc and keep date beside them. The interval sorts correctly against everything else; the date is what a human should be shown.

"Show a user their local calendar." Pass their zone from Intl.DateTimeFormat().resolvedOptions().timeZone — the browser already knows it, and it is an IANA name, which is exactly what tz wants.

const tz = Intl.DateTimeFormat().resolvedOptions().timeZone; // "Europe/Athens"
const r  = await fetch(`https://api.timesys.pro/v1/calendar?jurisdictions=GR`
  + `&from=2027-01-01&to=2027-12-31&tz=${encodeURIComponent(tz)}`,
  { headers: { "X-API-Key": key } });