Menu

time в Golang: Now, Sleep, Format, Parse и Duration

Как работать с датами и временем в Go: time.Now и time.Sleep, арифметика Duration, эталонная раскладка 2006-01-02 15:04:05 для Format и Parse, часовые пояса, Unix-время, таймеры и тикеры.

На этой странице есть исполняемые редакторы: меняйте, запускайте и сразу видите результат.

Основы в одной программе

time.Now() возвращает текущее местное время, time.Sleep приостанавливает текущую горутину, time.Since измеряет прошедшее время, а time.Date создаёт конкретный момент. Дальше на странице каждая часть разобрана по очереди. Примеры используют фиксированные даты и UTC, чтобы вывод был одинаковым, где бы вы их ни запускали.

Sleep и Duration

time.Duration это int64, содержащий число наносекунд. В пакете есть константы для записи читаемых значений:

КонстантаЗначение
time.Nanosecond1
time.Microsecond1000 нс
time.Millisecond1000 мкс
time.Second1000 мс
time.Minute60 с
time.Hour60 мин

time.Day нет, потому что день не всегда длится 24 часа, когда пояс переходит на летнее время. Для календарных дней используйте AddDate (см. ниже).

Две частые ошибки происходят оттого, что Duration это целое число:

  • time.Sleep(5) спит 5 наносекунд. Всегда умножайте на единицу: time.Sleep(5 * time.Second).
  • time.Sleep(n * time.Second) не компилируется, когда n это переменная типа int. Преобразуйте её: time.Duration(n) * time.Second. Константа вроде 5 работает без преобразования, потому что нетипизированные константы подстраиваются под тип Duration.

ParseDuration принимает ns, us (или µs), ms, s, m и h в сочетаниях вроде "2h45m" или "1.5s". Единицы для дней у неё нет.

Форматирование: эталонная раскладка 2006-01-02

Go не использует %Y-%m-%d или yyyy-MM-dd. Раскладка это эталонное время

Mon Jan 2 15:04:05 MST 2006

записанное так, как должен выглядеть ваш вывод. Значения подобраны так, чтобы каждое было уникальным: месяц 1, день 2, час 3 (или 15), минута 4, секунда 5, год 6 (2006), смещение пояса -7 (-0700). Читайте это как 01/02 03:04:05PM '06 -0700.

Токены раскладки, которые понадобятся чаще всего:

ТокенЗначениеПример
2006 / 06год, 4 или 2 цифры2026 / 26
01 / 1 / Jan / Januaryмесяц03 / 3 / Mar / March
02 / 2 / _2день месяца (с нулём, без дополнения, с пробелом)05 / 5 / " 5"
Mon / Mondayдень неделиThu / Thursday
15час, 24-часовой формат09
03 / 3час, 12-часовой формат09 / 9
04 / 4минута07 / 7
05 / 5секунда03 / 3
PM / pmотметка AM или PMAM
.000 / .999доли секунды (фиксированно / без хвостовых нулей).250 / .25
MSTаббревиатура поясаUTC
-0700 / -07:00числовое смещение пояса+0000 / +00:00
Z07:00как -07:00, но для UTC печатает ZZ

Предопределённые раскладки:

КонстантаРаскладка
time.RFC33392006-01-02T15:04:05Z07:00 (используйте для API и JSON)
time.RFC3339Nano2006-01-02T15:04:05.999999999Z07:00
time.DateTime (Go 1.20)2006-01-02 15:04:05
time.DateOnly (Go 1.20)2006-01-02
time.TimeOnly (Go 1.20)15:04:05
time.Kitchen3:04PM
time.RFC1123Mon, 02 Jan 2006 15:04:05 MST (для дат HTTP вместо неё используют http.TimeFormat)

Классическая ошибка: раскладка с не теми числами, например "2023-01-01". Go её не отвергает. Незнакомые символы он копирует, а знакомые подставляет: каждая 2 это день, 3 это час в 12-часовом формате, а оба 01 это месяц, поэтому 5 марта в 9:07 форматируется как 5059-03-03. Раскладка вроде "YYYY-MM-DD" вообще не содержит токенов и печатает сама себя. Если отформатированные даты выглядят странно, проверьте, что раскладка использует ровно эталонные значения.

Разбор строк во время

time.Parse(layout, value) использует те же раскладки и возвращает ошибку, которую нужно проверять:

Если во вводе нет пояса, Parse возвращает UTC. Одна и та же строка с временем на часах может означать разные моменты в зависимости от пояса, поэтому и существует ParseInLocation. Сообщение об ошибке называет часть, которая не совпала, и это помогает при отладке раскладки.

Часовые пояса

time.Time это момент плюс пояс, используемый для отображения. Смена пояса через In меняет то, как значение печатается, но не то, какой это момент.

  • time.UTC доступен всегда. Храните и передавайте время в UTC (или в RFC 3339 со смещением), а преобразуйте только для отображения.
  • time.Local это пояс машины. На серверах и в контейнерах это часто UTC, а на ноутбуке нет, так что код, который от него зависит, ведёт себя в разных местах по-разному.
  • time.LoadLocation("Europe/Berlin") читает базу IANA из операционной системы. В минимальных образах контейнеров её часто нет, и тогда вызов возвращает ошибку. Пустой импорт _ "time/tzdata" встраивает базу в ваш бинарник (около 450 КБ), так что всё работает всегда. Всегда обрабатывайте ошибку.
  • time.FixedZone(name, offsetSeconds) создаёт пояс с постоянным смещением. Правил перехода на летнее время у него нет, поэтому используйте его для полученных смещений, а не для именованных регионов.

Арифметика и сравнение

Что заметить в выводе:

  • AddDate(0, 1, 0) от 31 января даёт 3 марта, а не 28 февраля. Go прибавляет месяц, получает «31 февраля» и нормализует переполнение. Если нужно «тот же день следующего месяца, но не дальше конца месяца», напишите эту логику сами.
  • Sub возвращает Duration, у которой потолок примерно 292 года. Для разницы в календарных днях сравнивайте даты в полночь UTC и делите на 24 часа.
  • Чтобы получить начало дня, соберите время заново из Date(). t.Truncate(24 * time.Hour) округляет относительно нулевого времени в UTC, поэтому для любого пояса, кроме UTC, даёт неверный ответ.

Сравнивайте через Equal, Before, After и никогда через ==. time.Now() включает показание монотонных часов, чтобы time.Since оставался правильным, даже если системные часы переведут. == сравнивает это показание и пояс, поэтому два значения Time для одного момента могут быть неравны. По той же причине не используйте time.Time как ключ мапы без предварительной нормализации (t.UTC().Round(0) убирает монотонное показание).

Нулевой time.Time это 1 января 1 года, 00:00 UTC. Проверяйте его через t.IsZero().

Измерение прошедшего времени

start := time.Now()
doWork()
log.Printf("doWork took %v", time.Since(start))

time.Since(start) это time.Now().Sub(start), а time.Until(deadline) это deadline.Sub(time.Now()). Обе функции используют монотонные часы, когда они доступны, поэтому переводы системных часов им не страшны. Для бенчмарков используйте поддержку бенчмарков Go в пакете testing, а не ручные замеры.

Таймеры и тикеры

time.After(d) возвращает канал, который получает значение один раз через d. time.Timer это то же самое с методом Stop. time.Ticker выдаёт значение каждый период, пока вы его не остановите.

Три тика на 20, 40 и 60 мс приходят задолго до таймера на 110 мс, поэтому программа останавливается после третьего тика. time.AfterFunc(d, f) запускает f в отдельной горутине через d, что удобно для разовой отложенной работы.

Начиная с Go 1.23 таймеры и тикеры, на которые больше никто не ссылается, собираются сборщиком мусора, даже если вы так и не вызвали Stop, а канал остановленного или сброшенного таймера больше не выдаёт устаревшее значение. Вызов Stop через defer всё равно остаётся понятным способом сказать, что жизнь тикера закончена. Когда ограничение по времени касается всей операции, а не одного ожидания, context.WithTimeout обычно читается лучше таймера.

Время в JSON

time.Time автоматически кодируется в JSON строкой RFC 3339 и декодируется из неё, с точностью до наносекунд:

type Event struct {
	Name string    `json:"name"`
	At   time.Time `json:"at"`
}
// {"name":"deploy","at":"2026-09-23T14:30:00Z"}

Строка JSON в другом формате не декодируется. Для Unix-времени или своих форматов храните int64 или строку и преобразуйте, либо определите тип со своим UnmarshalJSON.

Частые ошибки

  • time.Sleep(1) или time.Sleep(n) с голым числом. Это наносекунды.
  • Раскладки с не теми цифрами. "2023-01-01" или "YYYY-MM-DD" не раскладки. Используйте "2006-01-02".
  • Путаница минут и месяцев. Минуты это 04, месяцы 01. "15:01" печатает час, а затем месяц, а не минуту.
  • Сравнение через ==. Используйте Equal.
  • Расчёт на time.Local. Он разный на разных машинах. Указывайте явно time.UTC или загруженный пояс.
  • Игнорирование ошибки от LoadLocation или Parse. Обе функции падают на реальном вводе. Неудачный Parse возвращает нулевое время, которое печатается как 1 год, а неудачный LoadLocation возвращает nil-пояс, из-за которого t.In(loc) паникует.

Часто задаваемые вопросы

Как сделать паузу (sleep) в Go?

Вызовите time.Sleep с time.Duration: time.Sleep(2 * time.Second) или time.Sleep(500 * time.Millisecond). Голое число вроде time.Sleep(2) компилируется, но спит 2 наносекунды, потому что Duration считает наносекунды. Sleep блокирует только текущую горутину.

Почему в Go формат даты задаётся через 2006-01-02 15:04:05?

Go форматирует даты по образцу. Раскладка это эталонное время Mon Jan 2 15:04:05 MST 2006, записанное так, как должен выглядеть ваш вывод. Его части идут по порядку в американской записи: месяц 1, день 2, час 3 (15 в 24-часовом формате), минута 4, секунда 5, год 6 (2006), смещение пояса 7 (-0700). Поэтому "2006-01-02" означает год-месяц-день, а "02/01/2006" день/месяц/год.

Как разобрать строку с датой в Go?

Используйте time.Parse(layout, value) с раскладкой, записанной через эталонное время: t, err := time.Parse("2006-01-02", "2026-09-23"). Всегда проверяйте err. Если в строке нет информации о поясе, результат будет в UTC; чтобы трактовать строку в другом поясе, используйте time.ParseInLocation.

Как получить Unix timestamp в Go?

time.Now().Unix() возвращает секунды с 1 января 1970 года UTC как int64. UnixMilli(), UnixMicro() и UnixNano() дают более мелкие единицы. Для обратного преобразования используйте time.Unix(sec, 0) или time.UnixMilli(ms).

Как сравнить два момента времени в Go?

Используйте t1.Before(t2), t1.After(t2) и t1.Equal(t2). Не используйте ==: он сравнивает ещё и пояс, и показание монотонных часов, так что два значения одного и того же момента могут оказаться неравными. t2.Sub(t1) даёт разницу в виде Duration.

Coddy programming languages illustration

Учитесь программировать с Coddy

НАЧАТЬ