XMLTV and EPG Data: How Programme Guides Are Structured
The data format behind electronic programme guides, its element structure, the timezone handling that causes most bugs, and how channel identity gets resolved.
An electronic programme guide is the grid showing what is on now and what is on next. From a user's perspective it is a simple thing. From a data perspective it is a scheduling problem with awkward edge cases, programmes that overrun, regional variations of the same channel, and timezone handling that goes wrong in ways nobody notices until a daylight saving transition.
XMLTV is the format most commonly used to move this data around. It is not a standard in the ISO sense; it began as a community project in the late 1990s and became a de facto interchange format because it was simple enough that everyone implemented it.
The Document Structure
An XMLTV document has exactly two kinds of child element under the root: channels and programmes.
All channels are conventionally declared before any programmes, though most parsers tolerate interleaving.
The structural point worth noticing is that a programme has no identity of its own. It is defined entirely by the combination of channel, start, and stop. There is no programme ID, no primary key. This has consequences: deduplicating across sources, detecting schedule changes, and matching a programme to a recording all become fuzzy matching problems rather than lookups.
Timestamps
The timestamp format is YYYYMMDDHHMMSS followed by a UTC offset, 20260811193000 +0100.
The offset is technically optional, and this is the single largest source of bugs in EPG handling. A timestamp without an offset is ambiguous: the consumer has to guess, and it will usually guess its own local timezone, which is wrong for anyone in a different one.
Two failure modes follow from careless handling:
Daylight saving transitions. A source that emits local wall-clock times without offsets produces a one-hour error across the entire guide twice a year. Worse, in the hour that repeats at the autumn transition, wall-clock times are genuinely ambiguous, because two distinct moments share the same representation.
Cross-timezone consumers. A guide generated for one region and consumed in another shifts wholesale if offsets are absent.
The correct handling on the consuming side is to parse the offset, convert immediately to UTC, store UTC, and convert to the viewer's local timezone only at render time. If a source omits offsets, apply the source's known timezone at ingest rather than deferring the problem, and treat a source that omits them as a source that needs auditing.
Channel Identity
The id attribute is an opaque string. XMLTV specifies no registry, no naming scheme, and no uniqueness guarantee beyond the document. Conventionally it looks like a reversed domain name, but that convention is not enforced and not universal.
This makes channel matching the other hard problem in EPG work. A player that receives channel information from one source and guide data from another has to decide which guide channel corresponds to which actual channel, and the identifiers frequently do not agree.
Practical approaches, roughly in order of reliability:
• Explicit mapping. A maintained table mapping source identifiers to your canonical channel IDs. Unglamorous, and the only approach that is actually correct.
• Display-name matching. Normalize case, strip punctuation and common suffixes like "HD", then compare. Works often enough to be useful, and fails on regional variants: "BBC One" and "BBC One West" are different services with largely identical schedules.
• Fuzzy matching with a confidence threshold. Useful for suggesting mappings to a human, not for applying automatically.
The regional variant problem is genuinely difficult. Two channels may share 95 percent of their schedule and diverge only for local news, and no amount of name matching distinguishes them reliably.
Episode Numbering
XMLTV supports several numbering systems via the system attribute, and they are not interchangeable.
The xmltv_ns system is the canonical one and it surprises people: it is zero-indexed, dot-separated as season.episode.part, and supports ranges and totals. 0.2. means season 1, episode 3, part unspecified. 2.9/12. means season 3, episode 10 of 12.
Also common is onscreen, which carries whatever string the broadcaster displays, "S02E05", or something less structured. It is human-readable and not reliably machine-parseable.
If you are generating XMLTV, emit xmltv_ns and additionally emit onscreen for display. If you are consuming it, prefer xmltv_ns when present and fall back to parsing onscreen with low expectations.
Practical Consumption Notes
A few things that come up in nearly every implementation:
Overlapping and gapped programmes. Nothing in the format prevents a programme's stop time from exceeding the next one's start, or from leaving a gap. Real schedules contain both, usually from live events overrunning. A renderer that assumes a clean partition of the timeline will produce visual artifacts. Decide explicitly whether to truncate overlaps, render them stacked, or fill gaps with a placeholder.
Document size. Two weeks of guide data across several hundred channels is a large XML document, often hundreds of megabytes uncompressed. Loading it fully into memory is viable on a server and not on a set-top box. Streaming SAX-style parsing is the usual answer on constrained devices.
Update cadence versus correctness. Schedules change, particularly around live sport. A guide fetched daily will be wrong for anything that shifted since. Weigh refresh frequency against bandwidth and the practical reality that most changes affect a small number of channels.
Character encoding. Declare and honour it. Programme titles carry accented characters, and mojibake in a guide is both common and entirely avoidable.
XMLTV is unusual in that its weaknesses (no programme identity, no channel registry, optional timezone offsets) are all consequences of the same design choice to keep it trivially simple to generate. That choice is why it is still in use nearly three decades on, and why every consumer of it ends up writing the same reconciliation logic.