iCalForge

Time zones in calendar invites: UTC, floating times and VTIMEZONE

6 min read · Updated June 2026

A sandstone clock tower standing between glass office towers on a city street

Most calendar problems that survive into production are time-zone problems. The event is an hour out for one attendee, an all-day holiday shows on the wrong day for the colleague who flew to Tokyo, a weekly call drifts every spring. All of these come from the same place: a calendar file can express "nine o'clock" in three different ways, and the three make different promises. Knowing which one you are writing solves most of it.

Three ways to write a time

The first is absolute UTC: DTSTART:20261005T090000Z. The trailing Z means Coordinated Universal Time, and every client converts it into local time for the viewer. It is unambiguous and perfect for a single one-off moment — a rocket launch, a market close, a deadline.

The second names a zone: DTSTART;TZID=Europe/Tallinn:20261005T090000. The value is wall-clock time in that zone, and the client works out the offset for that particular date, daylight saving included. This is the right form for a meeting: it says where the clock that governs the event hangs.

The third has neither a Z nor a TZID: DTSTART:20261005T090000. That is a floating time, and it means the same clock reading everywhere. A floating 09:00 is 09:00 in Tallinn and 09:00 in Tokyo, six hours apart in absolute terms. Floating is right for a birthday, a public holiday, a daily habit or a fasting window — anything defined by the local clock rather than by a shared moment.

Why the file carries a VTIMEZONE

A TZID is just a label. When iCalendar was designed, it could not assume the reader had the IANA time-zone database — many mail and calendar programs did not. So a file that uses a TZID is expected to define it, in a VTIMEZONE component sitting inside the same VCALENDAR.

A VTIMEZONE holds the zone identifier and one or more STANDARD and DAYLIGHT sub-components. Each of those has a DTSTART written in the zone's own local time, a TZOFFSETFROM and TZOFFSETTO giving the offsets on either side of the change, a TZNAME such as EET or EEST, and usually a yearly RRULE describing when the change repeats — for the European Union, the last Sunday of March and the last Sunday of October. Together they are a self-contained description of the zone, good for any date the file mentions.

Modern clients usually know the zone already and quietly prefer their own, more current data. The block still matters for the ones that do not, and for a file that outlives its author. What is genuinely dangerous is referencing a TZID without defining it: the behaviour is then undefined, and clients variously fall back to the viewer's own zone, to UTC, or refuse the event — a silent error of one to eleven hours.

The two awkward hours of the year

In spring the clock jumps forward and an hour disappears. In most of Europe, 03:00 becomes 04:00 on the last Sunday of March, so 03:30 does not exist that night. A calendar entry written at that wall time has no instant to point at; clients generally move it forward to the first valid time, but the standard does not require them to agree.

In autumn the opposite happens and an hour repeats: 03:30 occurs twice, once at the summer offset and once at the winter one. Here TZOFFSETFROM in the VTIMEZONE resolves the ambiguity for a client that reads it, and a floating time is simply ambiguous by nature. Neither case is common enough to plan around, but both are worth knowing about when a single event is inexplicably an hour out.

Zones also change their rules. Russia abolished seasonal clock changes in 2014, Turkey did the same in 2016, and several countries have shifted their transition dates since. A file written years ago can carry a VTIMEZONE that is simply out of date, which is another argument for naming the zone with a current TZID and letting up-to-date clients apply their own data.

All-day events have no time zone at all

An all-day event is written with a date value and no time: DTSTART;VALUE=DATE:20261005, with DTEND;VALUE=DATE:20261006 for a single day, because the end is exclusive. There is no TZID, and that is the point — the fifth of October is the fifth of October in every zone.

The mistake to avoid is faking an all-day event as a timed one from 00:00 to 23:59 in some zone. For a colleague five hours to the west it then starts the previous evening and spills over two days in the grid. If the thing you are describing is a day rather than a span of clock time, use the date form.

Travelling attendees and the mistakes that bite

The zone on an event should be the zone the event happens in, not the zone the organiser happens to be sitting in when they create it. A workshop in Lisbon is Europe/Lisbon even if you booked it from Tallinn; that way it stays correct for the attendees who are actually in the room, and everyone else sees the right converted time.

Four mistakes account for most of the damage. Pinning a recurring meeting to a UTC instant, which makes it drift an hour every time the clocks change. Referencing a TZID with no VTIMEZONE to back it up. Using an abbreviation such as EST or CET as the TZID — those are ambiguous between continents and are not IANA identifiers. And using a floating time for a meeting, which hands every attendee a different moment and no error message.

Choosing, in one page

If people from more than one place are meeting, name the zone with a full IANA identifier and ship a VTIMEZONE. If the event is a single absolute instant with no local character — a launch, a cut-off, an on-call handover — UTC with a trailing Z is the clearest thing you can write. If the event is defined by the local clock wherever the viewer is, use a floating time and say so in the description, because it will surprise somebody.

And if the event is a whole day, write it as dates. It is the one case where doing less is exactly right: no time, no zone, no conversion, no off-by-one day for the person who happened to be flying.

The takeaway

Decide what the time means before you write it. A named IANA zone with a VTIMEZONE for meetings, UTC with a Z for absolute moments, a floating time for things governed by the viewer's own clock, and bare dates for whole days. Never use a zone abbreviation as a TZID, and never pin a recurring meeting to UTC.

Related tool
iCalForge · .ics generator

Build an event and download the .ics file