Название, место, дата и время, часовой пояс, повторение, приглашённые, напоминания и заметки — затем скачайте файл .ics или откройте его прямо в календаре. Также можно вставить расписание или прикрепить документ либо фотографию, чтобы создать сразу несколько событий.

Почему моё событие в календаре сдвинуто на час или на день?

Файл .ics может задавать время тремя способами, и почти всегда причина в том, что использован не тот способ. Плавающее время (DTSTART:20260815T090000, без пояса) — это девять часов независимо от того, где файл открыт. Время UTC (с завершающей Z) — это один абсолютный момент. Именованный пояс (DTSTART;TZID=Europe/London:...) — это момент, который сохраняет одно и то же местное время по часам при переходе на летнее/зимнее время. Смещение на час при импорте обычно вызвано названием пояса в стиле Windows, например «GMT Standard Time», которое читающий календарь не распознаёт и по умолчанию принимает за UTC; смещение на день для событий «весь день» обычно возникает из-за отметки времени в полночь там, где нужно было указать VALUE=DATE, либо из-за даты окончания, указанной как последний день события, а не следующий за ним, поскольку DTEND не включает указанную дату. ICS Maker по умолчанию записывает плавающее время, а при выборе пояса — TZID с названием пояса IANA, без встроенного блока VTIMEZONE, который любой распространённый календарь определяет по самому названию пояса.

Три способа указать время в файле

Плавающее — вообще без пояса
DTSTART:20260815T090000. Девять утра независимо от того, где файл открыт. Подходит для локальной встречи, напоминания или всего, что определяется собственными часами читающего; перестаёт подходить, как только людям в разных странах нужно встретиться в одно и то же время.
UTC — абсолютный момент
DTSTART:20260815T090000Z, с завершающей Z. Один момент времени, который преобразуется в местное время тем, что его открывает. Однозначно, но теряется тот факт, что встреча была «в десять часов по лондонскому времени», поэтому переход на летнее/зимнее время сдвигает её.
Именованный пояс — момент, который переживает переход на летнее/зимнее время
DTSTART;TZID=Europe/London:20260815T090000. Именно это нужно для повторяющейся встречи: серия остаётся в десять часов по местному времени, даже когда смещение под ней меняется. RFC 5545 требует соответствующего блока VTIMEZONE, определяющего смещения этого пояса.

Этот конструктор по умолчанию записывает первый вариант, а третий — когда вы выбираете пояс из списка. Такой выбор по умолчанию сделан намеренно: приём у стоматолога в девять должен быть в девять, а привязка пояса той машины, на которой случайно был создан файл, — это как раз то, из-за чего событие сдвигается на час для того, кто его читает.

Он записывает TZID без встроенного блока VTIMEZONE, и это стоит знать, прежде чем отправлять файл куда-то нестандартное. Apple Calendar, Google Calendar, Outlook и Thunderbird — все содержат базу данных часовых поясов IANA и определяют Europe/London по одному лишь названию, так что этот блок был бы несколькими сотнями строк, повторяющими то, что читающему уже известно. Строгий парсер, который распознаёт только пояса, определённые в самом файле, — это случай, когда такой компромисс не оправдывает себя: вставьте блок VTIMEZONE вручную, если вы работаете именно с таким парсером.

Чтение файла, который пришёл с ошибкой

Откройте его в текстовом редакторе и посмотрите на строку DTSTART — ответ всегда там. Завершающая Z со смещением, которого вы не ожидали, означает, что отправитель преобразовал время в UTC из пояса, который вы не разделяете. TZID с названием вроде GMT Standard Time вместо Europe/London — это написание, принятое в Microsoft Exchange, и календари, знающие только названия IANA, могут по умолчанию переключиться на UTC — это классический случай смещения на час при импорте. TZID с названием пояса, которое не является ни названием IANA, ни определено блоком VTIMEZONE в этом же файле, — это тот случай, который каждый читающий календарь трактует по-своему, и достаточно часто по умолчанию принимает за UTC, поэтому это второе, что стоит проверить.

Событие «весь день», которое сдвигается на день

Событие «весь день» — это дата, а не время: DTSTART;VALUE=DATE:20260815. Если вместо этого записать его как отметку времени в полночь, оно становится реальным моментом времени, и любое преобразование в западном направлении смещает его на предыдущий день. Дата окончания — вторая часть этой ловушки: она не включает указанный день, поэтому однодневное событие 15-го числа заканчивается 16-го, а файл, в котором датой окончания указано 15-е число, в некоторых календарях не отображается вовсе.

Вопросы

Почему моё событие в календаре сдвинуто на час?
Почти всегда это несоответствие, связанное с переходом на летнее/зимнее время: в файле указано фиксированное смещение UTC либо название пояса в стиле Windows, которое читающий календарь не распознаёт, и в результате он переключается на UTC по умолчанию. Проверьте строку DTSTART — если она заканчивается на Z, время указано как абсолютное UTC, а если она содержит TZID, этот пояс должен быть записан как название IANA, например Europe/Paris, либо определён блоком VTIMEZONE в этом же файле. Распространённые календари определяют название IANA по собственной копии базы данных часовых поясов и не нуждаются в таком блоке.
Стоит ли указывать часовой пояс или лучше его не указывать?
Не указывайте его, если событие определяется местным временем по часам — встреча, напоминание, занятие, которое начинается в девять независимо от того, где вы находитесь. Указывайте его, если событие происходит в единый момент времени, который гостям в других часовых поясах нужно пересчитать, например звонок, вебинар или трансляция.
Что такое плавающее время?
Дата-время, записанные без указания пояса и без завершающей Z, которые согласно RFC 5545 должны трактоваться как местное время там, где они читаются. Это верный выбор чаще, чем принято думать, и неверный всякий раз, когда двум читающим в разных часовых поясах нужно договориться об одном и том же моменте.
Почему моё событие «весь день» отображается не в тот день?
Потому что оно было записано как отметка времени в полночь, а не как значение даты. Настоящее событие «весь день» использует VALUE=DATE вообще без времени, поэтому преобразовывать нечего. Также помните, что дата окончания не включает указанный день: у однодневного события 15-го числа DTEND приходится на 16-е.