Every "Add to calendar" button, conference invitation and booking confirmation ends up as the same thing: a small text file in the iCalendar format, defined by RFC 5545, usually with the extension .ics. It is simple enough to read by eye and strict enough that small mistakes produce events at the wrong hour, repeats that skip months, or titles full of broken characters. This article opens a real file, line by line, and then covers the three areas where most files go wrong: time zones, repeat rules and text encoding. The examples were generated with the ICS calendar generator.
Inside an .ics file
A weekly meeting in Ho Chi Minh City, every Tuesday 09:00 to 09:30, ten times, with a 15-minute reminder (lines end with CRLF; the indented line is a continuation):
BEGIN:VCALENDAR
VERSION:2.0
PRODID:-//Example//ICS//EN
CALSCALE:GREGORIAN
METHOD:PUBLISH
BEGIN:VTIMEZONE
TZID:Asia/Ho_Chi_Minh
BEGIN:STANDARD
DTSTART:20260101T070000
TZOFFSETFROM:+0700
TZOFFSETTO:+0700
END:STANDARD
END:VTIMEZONE
BEGIN:VEVENT
UID:[email protected]
DTSTAMP:20261014T030000Z
DTSTART;TZID=Asia/Ho_Chi_Minh:20261020T090000
DTEND;TZID=Asia/Ho_Chi_Minh:20261020T093000
RRULE:FREQ=WEEKLY;BYDAY=TU;COUNT=10
SUMMARY:Họp giao ban tuần
DESCRIPTION:Chương trình: báo cáo tiến độ\, vướng mắc\, kế
hoạch tuần sau\; mọi người chuẩn bị trước nhé.
LOCATION:Phòng 3\, Tầng 5
BEGIN:VALARM
ACTION:DISPLAY
DESCRIPTION:Họp giao ban tuần
TRIGGER:-PT15M
END:VALARM
END:VEVENT
END:VCALENDARThe pieces that matter:
- UID identifies the event forever. Import a corrected file with the same UID and calendars update the event; change the UID and they add a duplicate.
- DTSTAMP is when the file was made, in UTC.
- DTSTART/DTEND with TZID are wall-clock times in a named zone, and the VTIMEZONE block tells the reader what that zone's offsets are.
- RRULE describes the repeats; VALARM the reminder, here 15 minutes before.
- METHOD:PUBLISH says "here is an event to add". Real invitations that track RSVPs use METHOD:REQUEST and are sent by a calendar server; importing a file never emails anyone.
Time zones: why invites arrive an hour off
A time can be written three ways: with a zone (TZID=America/New_York:...T090000), in UTC (...T140000Z), or "floating" (...T090000, no zone). The difference shows up when daylight saving starts. Take a weekly 09:00 New York meeting on 3 and 10 March 2026 (US clocks went forward on 8 March), as seen by a colleague in Hanoi:
| Week | Written with a zone | Written in UTC (14:00Z) |
|---|---|---|
| 3 March | 09:00 New York, 21:00 Hanoi | 09:00 New York, 21:00 Hanoi |
| 10 March | 09:00 New York, 20:00 Hanoi | 10:00 New York, 21:00 Hanoi |
With a zone, the meeting stays at 09:00 for the people who set it, and the Hanoi time moves by an hour, which is correct: Vietnam has no daylight saving, New York does. Written in UTC, it stays at the same instant, so New Yorkers find their 09:00 meeting at 10:00 from March. That is the classic "everyone was an hour early" bug. Floating times are worse for meetings: 09:00 means 09:00 in whatever zone the calendar is set to, so participants in different countries see different instants.
Rule of thumb: meetings with a place get a zone, alarms such as "take medicine at 8:00" can float, and UTC is fine for one-off events that do not repeat.
Repeat rules that surprise
RRULE is powerful and literal. Things that catch people:
- The 31st skips months.
FREQ=MONTHLY;BYMONTHDAY=31starting 31 January 2026 gives 31 January, 31 March, 31 May, 31 July. The standard says invalid dates are ignored, not moved to the 30th. - 29 February is every four years. A yearly event on 29 February 2024 next appears in 2028.
- "Last" is not "fourth". A monthly rule from Thursday 29 October 2026 as "the last Thursday" (
BYDAY=-1TH) gives 26 November and 31 December; "the 4th Thursday" would give 26 November and 24 December. - UNTIL has rules. If DTSTART has a zone, UNTIL must be in UTC; for all-day events it must be a date. UNTIL=20261231T165959Z is 23:59:59 on 31 December in Ho Chi Minh City. Mixing forms is a common reason Outlook ignores the end.
- COUNT counts occurrences, not weeks.
FREQ=WEEKLY;BYDAY=MO,WE,FR;COUNT=6ends after two weeks.
The occurrence list in the generator shows the actual dates before you send anything.
Encoding: why "Họp" becomes "H�p"
Two rules interact. Text values must escape commas, semicolons and backslashes, so "Phòng 3, Tầng 5" is written Phòng 3\, Tầng 5; forget it and some readers treat the comma as a list separator. And lines longer than 75 octets must be folded: a CRLF followed by a space. Octets, not characters: the first DESCRIPTION line above has 58 characters but 75 bytes, because most Vietnamese letters take 2 or 3 bytes in UTF-8. A generator that counts characters writes lines that are too long; one that cuts at exactly 75 bytes can split a 3-byte letter in half, and the reader shows a replacement character. The fold must fall between characters, as it does after "kế" above, and the next line starts with the space that marks the continuation (plus the original space before "hoạch").
Common mistakes
- Reusing one UID for different events. The second import overwrites the first.
- All-day events ending on the last day. DTEND for all-day events is exclusive: a one-day event on 20 October ends on 21 October.
- Sending the file and expecting RSVPs. Replies only flow through calendar servers; an attached .ics is just an event to add.
- Using UTC for repeating meetings across a daylight-saving change.
- Missing VTIMEZONE. Most calendars know the IANA names, but the standard says the zone must be defined in the file, and some clients fall back to floating time without it.
Try it
Build your event in the ICS calendar generator, choose "in a time zone", and check the next occurrences before downloading. To see what 09:00 in one city is elsewhere, use the time zone converter.