Guides
Timezones, DST, and historical birth data
Every chart you compute depends on one number. That number is the tzone UTC offset that was in force at the birth moment and place. Send the wrong offset and the whole chart shifts.
Most bugs here come from the user's current offset, or from a fixed offset that ignores daylight saving. Send the offset that actually applied on the birth date.
What tzone actually means
tzone is a float. The value is the UTC offset in hours at the birth moment and place. It is not a timezone name like Asia/Kolkata. It is also not the offset where your user happens to be sitting today.
India uses a single offset. A birth in India takes tzone: 5.5.
The United States observes daylight saving, so the offset for one place changes across the year. A birth in New York in July takes tzone: -4.0. A birth in New York in December takes tzone: -5.0.
The offset feeds the astronomy directly. Every planet's fullDegree, sign, nakshatra, and house in a planets response derives from the exact birth moment in UTC.
The API computes that UTC moment from the local time and tzone you send. A wrong offset shifts that moment, and shifts where every planet lands. The shift is often enough to move a planet into a different sign or house.
| Field | Type | Required | Description |
|---|---|---|---|
tzone | float | Yes | Timezone, eg: 5.5 |
A full planets request looks like this. The same body works for astro_details. Every field is required.
POST https://json.astrologyapi.com/v1/planets
{
"day": 10,
"month": 5,
"year": 1990,
"hour": 19,
"min": 55,
"lat": 19.2056,
"lon": 25.2056,
"tzone": 5.5
}Where this goes wrong
The most common failure is daylight saving. Suppose a user was born in New York. Their browser reports its current offset, so a summer session reports -4 and a winter session reports -5.
If the birth happened in December but the user opens your app in July, a browser-derived offset sends -4 when the birth needed -5. The chart is off by a full hour.
The offset for a given place can differ from today's offset. Timezone rules change over time. The offset that applied on a birth date decades ago is not always the offset that place uses now.
Resolve the offset for the birth date, not for today. A one-hour error is large. As a rough guide, the ascendant moves about one degree for every four minutes of clock time.
The ascendant is the zodiac degree rising over the eastern horizon. Earth turns roughly 360 degrees in about 24 hours, which is approximately one degree every four minutes.
The actual rate varies with latitude and time of year, so treat it as an approximation. Even so, a small tzone error shifts the ascendant by degrees. That shift can change the rising sign and every house cusp.
The fix: resolve tzone from place and date, not from the browser
Resolve the offset on the server in three steps. Geocode the place, resolve the offset for the birth date, then compute the chart.
- Call
geo_detailswith the place name to get latitude and longitude. - Call
timezone_with_dstwith that lat/lon and the birth date to get the offset in force on that date. - Call
planetswith the coordinates and the resolvedtzone.
geo_details takes a place string and returns matches. One quirk matters: latitude comes back as a number, but longitude comes back as a string, such as "72.88261".
Run the longitude through parseFloat() before you pass it to any endpoint that expects a float.
POST https://json.astrologyapi.com/v1/geo_details
{
"place": "mum",
"maxRows": 2
}
Response:
{
"geonames": [
{
"place_name": "Mumbai",
"latitude": 19.07283,
"longitude": "72.88261",
"timezone_id": "Asia/Kolkata",
"country_code": "IN"
},
{
"place_name": "Navi Mumbai",
"latitude": 19.03681,
"longitude": "73.01582",
"timezone_id": "Asia/Kolkata",
"country_code": "IN"
}
]
}timezone_with_dst resolves the offset for a lat/lon on a given calendar date. Pass the birth date, not today's date.
The date param is optional, is a string, and the API describes its format as mm-dd-yyyy. The response returns timezone as the float offset you feed into planets.
POST https://json.astrologyapi.com/v1/timezone_with_dst
{
"latitude": 25.7464,
"longitude": 82.6837,
"date": "06-27-2000"
}
Response:
{
"status": true,
"timezone": 5.5,
"timezone_in_ms": 19800000,
"date": "1992-12-01T00:00:00.000Z"
}Complete example
This runs on Node 18 or later with the global fetch, no packages. Set USER_ID and API_KEY from your dashboard.
The script takes a place name plus a birth date and time. It then geocodes the place, parses the string longitude, resolves the offset for the birth date, and requests each planet's position.
// Node 18+ (global fetch). No external packages.
// USER_ID and API_KEY come from your AstrologyAPI dashboard.
const USER_ID = 'USER_ID'
const API_KEY = 'API_KEY'
const BASE_URL = 'https://json.astrologyapi.com/v1'
const AUTH =
'Basic ' + Buffer.from(USER_ID + ':' + API_KEY).toString('base64')
async function post(endpoint, body) {
const res = await fetch(BASE_URL + '/' + endpoint, {
method: 'POST',
headers: {
Authorization: AUTH,
'Content-Type': 'application/json',
},
body: JSON.stringify(body),
})
if (!res.ok) {
throw new Error(endpoint + ' failed: ' + res.status + ' ' + res.statusText)
}
return res.json()
}
async function getPlanetPositions(input) {
// 1. Geocode the place name to coordinates.
const geo = await post('geo_details', {
place: input.place,
maxRows: 1,
})
const match = geo.geonames && geo.geonames[0]
if (!match) {
throw new Error('No location found for: ' + input.place)
}
// geo_details returns latitude as a number but longitude as a string.
// Parse the longitude to a float before passing it on.
const lat = match.latitude
const lon = parseFloat(match.longitude)
// 2. Resolve the UTC offset in force at that place on the BIRTH date.
const zone = await post('timezone_with_dst', {
latitude: lat,
longitude: lon,
date:
String(input.month).padStart(2, '0') +
'-' +
String(input.day).padStart(2, '0') +
'-' +
input.year,
})
const tzone = zone.timezone
// 3. Get each planet's position with the resolved tzone.
const planets = await post('planets', {
day: input.day,
month: input.month,
year: input.year,
hour: input.hour,
min: input.min,
lat: lat,
lon: lon,
tzone: tzone,
})
return planets
}
getPlanetPositions({
place: 'mum',
day: 10,
month: 5,
year: 1990,
hour: 19,
min: 55,
})
.then((planets) => console.log(planets))
.catch((err) => {
console.error(err.message)
process.exit(1)
})Checklist
- Store the birth place and the resolved numeric
tzonealongside the birth datetime, not just a plain datetime string. - Never derive the historical
tzonefrom the browser's currentDateoffset.getTimezoneOffset()reflects today's rules, not the birth date's. - Re-resolve
tzoneserver-side from place and date every time. Do not let users typetzonedirectly unless they are an astrologer who already knows it.