Skip to content

Timezones & ambiguity

The largest source of wrong birth charts in production systems is not the ephemeris — it is timezone and daylight-saving handling. An hour of error moves the Moon by roughly half a degree and the Ascendant by around fifteen degrees on average, which is routinely enough to change a rising sign or a house placement. This page states exactly what the API does and does not do with time, so that class of error stays visible and fixable.

Today’s contract: explicit offset, no guessing

Section titled “Today’s contract: explicit offset, no guessing”

In v1, subject.datetime must be an ISO 8601 timestamp with an explicit UTC offset:

{ "subject": { "datetime": "1992-11-22T08:45:00+05:30" } }

With that input the API does precisely two things:

  1. Converts to Universal Time for the ephemeris — 08:45:00+05:30 becomes 03:15:00Z.
  2. Echoes both facts back in meta.subject_resolved: the resolved utc_datetime and the utc_offset you supplied, so you can verify the conversion on every single response.
Terminal window
curl -s https://astral-external-api-iksoi6t3nq-ue.a.run.app/v1/western/positions \
-H "Authorization: Bearer $API_KEY" \
-H "Content-Type: application/json" \
-d '{
"subject": { "datetime": "1992-11-22T08:45:00+05:30" }
}' | jq .meta.subject_resolved

The API never infers an offset — not from coordinates, not from a timezone guess, not from your account settings. A datetime without one is rejected, with a stable error code rather than a silent assumption:

Terminal window
curl -s https://astral-external-api-iksoi6t3nq-ue.a.run.app/v1/western/positions \
-H "Authorization: Bearer $API_KEY" \
-H "Content-Type: application/json" \
-d '{
"subject": { "datetime": "1990-05-15T14:30:00" }
}' | jq .code

Accepting a local wall-clock time and resolving the offset ourselves would be more convenient — and it is coming. But the design rule is strict core, forgiving edges: convenience is only acceptable when everything resolved on your behalf is echoed back where you can check it. Forgiveness without echo is silent guessing, and silent guessing is exactly how timezone bugs become “your API is wrong” tickets that neither side can diagnose. v1 ships the strict core first; the forgiving edges ship together with the echo and error contract that make them safe.

Here is why offset resolution can never be fully automatic. On October 25, 1992, clocks in America/New_York fell back from daylight time to standard time at 2:00 a.m. As a result, the local time 1:30 a.m. occurred twice:

  • once at offset -04:00 (EDT), which is 1992-10-25T05:30:00Z, and
  • once, an hour later, at offset -05:00 (EST), which is 1992-10-25T06:30:00Z.

A birth certificate that says “1:30 a.m.” does not say which one. The two candidate moments are a full hour apart — a different Moon degree and, most of the time, a different Ascendant. No API can resolve this from the wall-clock time alone; the ambiguity is a fact about the input, and the only honest behaviors are to require the offset (today’s contract) or to refuse loudly with both candidates spelled out (the upcoming one).

The mirror-image case also exists: during the spring-forward transition, wall-clock times inside the skipped hour never occurred at all.

Until local-time input ships, offset resolution lives in your stack — where every mainstream platform already has the right tool, the IANA time zone database:

  1. Resolve the offset from an IANA zone at the birth place and time, not from a lookup of the place’s current offset. Historical transitions matter enormously for birth data: DST rules, zone boundary changes, and one-off political changes are all recorded in the tz database and nowhere else. Use zoneinfo (Python), java.time (JVM), Luxon or date-fns-tz (JavaScript), chrono-tz (Rust), or your platform’s equivalent.
  2. Decide ambiguous times explicitly. Most tz libraries let you choose the earlier or later instant for a fall-back time — make that choice deliberately, record it, and pass the resulting offset.
  3. Send the explicit offset in subject.datetime.
  4. Verify the echo. Assert that meta.subject_resolved.utc_datetime is the instant you intended — in development at minimum, ideally in an integration test. It is one comparison, and it catches the entire class of double-conversion bugs (applying the offset in your code and letting a datetime library apply it again is the classic one).

API spec v0.1.0 · docs 0e4f5ec