Local time, UTC offset, daylight-saving behaviour and current weather for 400 cities in 199 countries. Plain JSON over HTTPS. No signup, no key, no rate limit.
This is a static API. Every endpoint is a file on disk regenerated by a cron job — there is no application server, no database and no request accounting. That has consequences worth knowing before you build on it.
If you need weather for an arbitrary point or a forecast rather than current conditions, go to Open-Meteo directly — that is where this data comes from, and their API is free too. What this API adds on top is the time-zone layer: IANA zone, current offset, and whether and how the city shifts its clocks during the year.
Metadata: version, counts, list of endpoints, licence.
All 400 cities with slug, name, ISO country code, time zone and the URL of the full document.
curl https://worldtimeweather.com/api/v1/cities.json
{
"version": "v1",
"cities": 400,
"timezones": 208,
"data": [
{
"slug": "madrid",
"name": "Madrid",
"country_code": "ES",
"timezone": "Europe/Madrid",
"url": "https://worldtimeweather.com/api/v1/city/madrid.json"
}
]
}
Everything known about one city.
curl https://worldtimeweather.com/api/v1/city/madrid.json
{
"slug": "madrid",
"name": "Madrid",
"country_code": "ES",
"coordinates": { "latitude": 40.4169, "longitude": -3.7033 },
"time": {
"timezone": "Europe/Madrid",
"iso": "2026-07-31T07:52:58+02:00",
"utc_offset_seconds": 7200,
"utc_offset": "UTC+2",
"dst": {
"observes_dst": true,
"winter_offset_seconds": 3600,
"summer_offset_seconds": 7200
}
},
"weather": {
"temperature_c": 24.8,
"temperature_f": 76.6,
"feels_like_c": 23.1,
"humidity_percent": 33,
"wind_speed_kmh": 6.5,
"wind_speed_mph": 4.0,
"condition": "overcast",
"wmo_code": 3,
"is_day": true,
"observed_at": "2026-07-31T07:45:00+02:00",
"stale": false
},
"page": {
"en": "https://worldtimeweather.com/city/madrid-en.html",
"es": "https://worldtimeweather.com/city/madrid-es.html",
"ar": "https://worldtimeweather.com/city/madrid-ar.html"
},
"license": { "attribution_required": false, "attribution_requested": true },
"generated_at": "2026-07-31T05:52:58+00:00"
}
IANA zone → list of city slugs in that zone. Handy for grouping a UI by offset.
| Field | Notes |
|---|---|
time.iso | Local time at the moment the file was generated, with offset. For a live clock, take time.timezone and compute in the client — do not poll this field. |
time.dst | observes_dst tells you whether the city shifts at all; the two offsets are the January and July values for the current year. Enough to warn a user that a difference will change, without shipping the whole tz database. |
weather.condition | Stable string key derived from wmo_code. Full list: clear, mainly_clear, partly_cloudy, overcast, fog, rime_fog, light_drizzle, drizzle, heavy_drizzle, light_rain, rain, heavy_rain, light_snow, snow, heavy_snow, rain_showers, violent_showers, thunderstorm, thunderstorm_hail, unknown. Keys are only ever added, never renamed. wmo_code is the raw WMO 4677 number as returned by Open-Meteo (code table). |
weather.observed_at | Local time of the observation with the city's UTC offset (ISO 8601, e.g. 2026-09-05T09:30:00+02:00), not the time of file generation. Weather for each city is refreshed roughly every 2 hours; the time fields every 30 minutes. |
weather.stale | Boolean. true means the observation behind this record is more than 3 hours old: the upstream weather source was unreachable and the previous reading was kept rather than dropped. In normal operation the oldest observation is about 2.3 hours old, so false is the expected value. Time fields are unaffected — they are recomputed on every run. |
generated_at | UTC, ISO 8601. Files are rewritten every 30 minutes; if this is older than 2 hours, treat the data as stale. Live check: /api/health.php returns {"status":"ok"}, or HTTP 503 with {"status":"stale"}. Responses carry Cache-Control: max-age=900, so polling more often than every 15 minutes returns the same file. |
climate_normals | Present when available. period is the span of daily observations (currently 2021-2025); months.N (N = 1–12) holds tmax / tmin — mean daily maximum / minimum, °C; prcp — mean monthly precipitation total, mm; wet — mean number of days with ≥ 1 mm of precipitation. |
const r = await fetch('https://worldtimeweather.com/api/v1/city/tokyo.json');
const city = await r.json();
// live clock — compute locally from the IANA zone
setInterval(() => {
const now = new Date().toLocaleTimeString('en-GB', { timeZone: city.time.timezone });
document.getElementById('clock').textContent = now;
}, 1000);
console.log(`${city.name}: ${city.weather.temperature_c}°C, ${city.weather.condition}`);
import urllib.request, json
from zoneinfo import ZoneInfo
from datetime import datetime
url = 'https://worldtimeweather.com/api/v1/city/madrid.json'
city = json.load(urllib.request.urlopen(url))
now = datetime.now(ZoneInfo(city['time']['timezone']))
print(city['name'], now.strftime('%H:%M'), city['time']['utc_offset'])
print(city['weather']['temperature_c'], '°C', city['weather']['condition'])
curl -s https://worldtimeweather.com/api/v1/city/cairo.json \
| jq '{name, tz: .time.timezone, t: .weather.temperature_c}'
https://worldtimeweather.com/ — in your README, footer or about page — is appreciated and helps keep the API free, but it is a request, not an enforcement. Feel free to mark it rel="nofollow". Weather data additionally comes from Open-Meteo and their attribution requirement applies too.
Ready-made attribution:
<a href="https://worldtimeweather.com/">World Time & Weather</a>
The v1 path is frozen. Fields may be added; existing ones will not be renamed or removed under v1. If something has to change incompatibly it will appear at /api/v2/ and v1 will keep working.
Found a wrong figure or want a city added? The 400 cities are the ones with pages on this site — the full list is here.
If you just want a clock on a page and not a data pipeline, the website widget does it in two lines with seven layouts and six themes.