Look at any aviation weather report and you'll see a timestamp like 121651Z. That trailing Z — spoken "Zulu" — means the time is UTC, and it's on there because the entire global aviation system runs on one clock. For developers, that's mostly a gift: unambiguous timestamps you can trust. But flight data also hides a few local-time fields that will silently corrupt your app if you assume everything is Zulu. This post explains what Zulu time is, why aviation adopted it, and how to handle timezones correctly when you consume flight data.

What "Zulu" means

Zulu time is Coordinated Universal Time (UTC) — the time at the Greenwich (0°) meridian, with no daylight-saving adjustment, ever. The name comes from the military/aviation practice of dividing the world into 24 time zones and giving each a letter: the zone centered on Greenwich is Z, and in the NATO phonetic alphabet the letter Z is "Zulu." Zones to the east run A through M (M being UTC+12), zones to the west run N through Y (Y being UTC−12), and the letter J is reserved for "local time." So 1651Z is 16:51 UTC — the same instant everywhere on Earth, regardless of where you or the airport are.

An analog clock face — a single reference clock, the idea behind running everything on UTC

Why aviation runs on one clock

A single transatlantic flight can cross five or six local time zones in an afternoon, and the people coordinating it — the departure tower, the crew, oceanic control, the arrival airport, and a dispatcher who might be on a third continent — cannot afford to ask "your 3 o'clock or mine?" Everyone works in UTC so that a clearance, a filed departure time, and a weather observation all refer to the same unambiguous instant. It also sidesteps daylight-saving transitions, which shift local clocks twice a year on schedules that differ by country. In a safety-critical, worldwide system, one clock with no exceptions is the only sane choice — which is why UTC shows up on flight plans, METARs, TAFs, NOTAMs, and ATC transcripts alike.

Where you'll meet Zulu (and where you won't) in flight data

This is the part that matters for your code. Flight-data fields fall into three groups, and mixing them up is the classic timezone bug:

  • Explicit UTC (ISO 8601 with Z or an offset) — the safe kind. Live ADS-B last_seen timestamps and weather observation times come as 2026-06-16T14:21:45Z. Parse them as UTC and you're done.
  • Coded UTC strings — NOTAM effective/expiration values like 202603241038 are UTC in YYYYMMDDHHMM form. No offset is shown, but it is UTC — parse accordingly.
  • Local airport time (no offset) — the trap. Flight status fields like scheduled_time: "10:30" with scheduled_date: "11 Feb" are local to the airport, not UTC. A departure board shows local times because that's what passengers need — but if your code treats "10:30" as Zulu, every flight is suddenly off by hours.

The rule of thumb: if a timestamp doesn't carry a Z or an offset, do not assume UTC — find out from the field's documentation whether it's UTC or local, and for local-time fields you'll need the airport's own timezone to convert.

Circular star trails around the north star in a long-exposure night sky, Earth's rotation made visible

Handling it in code

The discipline is simple and worth adopting everywhere: store and compute in UTC, convert to local only at the moment of display. Parse explicit-UTC fields into timezone-aware datetimes so arithmetic and comparisons are unambiguous:

from datetime import datetime, timezone

# Explicit-UTC field (ISO 8601). Normalize the trailing Z to an offset.
last_seen = datetime.fromisoformat("2026-06-16T14:21:45Z".replace("Z", "+00:00"))
# -> aware datetime in UTC; safe to compare, subtract, and store

# Coded-UTC NOTAM string (YYYYMMDDHHMM) — attach UTC explicitly:
eff = datetime.strptime("202603241038", "%Y%m%d%H%M").replace(tzinfo=timezone.utc)

For a local-time field, you can't convert without knowing the airport's zone. Resolve the airport to its IANA timezone (e.g. America/New_York), then localize:

from zoneinfo import ZoneInfo

# scheduled_time "10:30" on scheduled_date "11 Feb" at KJFK is *local*:
naive = datetime.strptime("2026-02-11 10:30", "%Y-%m-%d %H:%M")
local = naive.replace(tzinfo=ZoneInfo("America/New_York"))
utc = local.astimezone(timezone.utc)   # now unambiguous

Three traps to watch for: daylight saving (an airport's UTC offset changes across the year, so hard-coding an offset is wrong — use a named IANA zone that knows the DST rules); midnight rollover (a flight departing 23:50 local and landing 01:30 crosses a date boundary — keep the date field, don't infer it); and the ambiguous hour when clocks fall back and a local time occurs twice.

The short version

Aviation runs on Zulu (UTC) so that everyone, everywhere, agrees on one instant. When you consume flight data, treat every Z-suffixed or offset timestamp as the reliable truth, treat coded NOTAM times as UTC, and treat bare local times — most visibly on flight status and departure boards — as local-to-the-airport that need the airport's timezone before you compare them to anything. Store UTC, display local, and the whole class of timezone bugs disappears. It pairs directly with reading METAR and TAF, where those Z timestamps show up first.

SkyLink API gives you a free tier of 1,000 requests/month across weather, status, and tracking — every UTC timestamp decoded and consistent — with paid plans starting at $19/mo for production traffic. It's available through the free trial — sign up, grab a key, and stop fighting timezones in your flight data.