Час¶
У пакеті time людей дивують три речі: макети (layouts) записуються як
приклад конкретної дати, а не як %Y-%m-%d, тривалість — це справжній
тип, над яким можна робити арифметику, і == — неправильний спосіб
порівняти два моменти часу.
t := time.Date(2026, time.September, 20, 15, 4, 5, 0, time.UTC)
fmt.Println(t.Format("2006-01-02 15:04:05")) // output: 2026-09-20 15:04:05
Еталонний макет¶
2006-01-02 15:04:05 — це не патерн з підстановочними символами. Це
конкретний момент — Mon Jan 2 15:04:05 MST 2006 — і ви записуєте
макет у тій формі, яку хочете отримати. У кожного компонента одне число:
| Компонент | Значення | Чому |
|---|---|---|
| місяць | 1 або 01 або Jan або January |
перший |
| день | 2 або 02 |
другий |
| година | 15 (24-годинний) або 3/03 (12-годинний) |
третій |
| хвилина | 4 або 04 |
четвертий |
| секунда | 5 або 05 |
п'ятий |
| рік | 2006 або 06 |
шостий |
| часовий пояс | -0700 або Z07:00 |
сьомий |
Мнемоніка — порядок 1, 2, 3, 4, 5, 6, 7. Отже:
fmt.Println(t.Format("2006-01-02")) // output: 2026-09-20
fmt.Println(t.Format("02/01/2006")) // output: 20/09/2026
fmt.Println(t.Format("Jan 2, 2006 at 3:04PM")) // output: Sep 20, 2026 at 3:04PM
Зверніть увагу на 15 проти 3: 15 просить 24-годинний формат, 3 —
12-годинний. Окремого прапорця для цього немає.
Пакет постачає поширені макети як константи — надавайте їм перевагу:
fmt.Println(t.Format(time.RFC3339)) // output: 2026-09-20T15:04:05Z
fmt.Println(t.Format(time.Kitchen)) // output: 3:04PM
RFC3339 — той, що варто використовувати для всього, що потім читатиме
машина.
Парсинг використовує той самий макет¶
p, err := time.Parse("2006-01-02", "2026-09-20")
fmt.Println(p.Format(time.RFC3339), err)
// output: 2026-09-20T00:00:00Z <nil>
Компоненти, яких немає, за замовчуванням дорівнюють нулю, тож парсинг лише дати дає опівніч за UTC. Коли вхідні дані не збігаються з макетом, помилка називає обидва боки, тож баги в макеті легко помітити:
_, err = time.Parse("2006-01-02", "20/09/2026")
fmt.Println(err)
// output: parsing time "20/09/2026" as "2006-01-02": cannot parse "20/09/2026" as "2006"
Використовуйте time.ParseInLocation, коли у вхідних даних немає
зміщення, але ви знаєте, у якому поясі їх записали — звичайний Parse
припускає UTC.
Тривалості — це тип, а не число¶
time.Duration — це int64-лічильник наносекунд з методом String,
тож він друкується читабельно й підтримує арифметику напряму:
d := 90 * time.Minute
fmt.Println(d) // output: 1h30m0s
fmt.Println(d.Hours(), d.Minutes()) // output: 1.5 90
fmt.Println(2*time.Hour + 30*time.Minute) // output: 2h30m0s
90 * time.Minute працює, бо 90 — це нетипізована константа, як
пояснює стаття про конвертацію
типів. Якби 90 було
змінною, це не скомпілювалося б — знадобиться
time.Duration(n) * time.Minute:
Тривалості також парсяться з рядків, і саме так вони приходять з конфігурації:
pd, _ := time.ParseDuration("1h30m")
fmt.Println(pd, pd == 90*time.Minute) // output: 1h30m0s true
fmt.Println(d.Round(time.Hour), d.Truncate(time.Hour))
// output: 2h0m0s 1h0m0s
Арифметика¶
Add приймає тривалість; Sub повертає тривалість; AddDate працює в
календарних одиницях:
later := t.Add(48 * time.Hour)
fmt.Println(later.Format(time.RFC3339)) // output: 2026-09-22T15:04:05Z
fmt.Println(later.Sub(t)) // output: 48h0m0s
fmt.Println(t.AddDate(0, 1, 0).Format("2006-01-02")) // output: 2026-10-20
time.Since(start) — це скорочення для time.Now().Sub(start), і саме
так зазвичай вимірюють, скільки часу щось зайняло.
Add і AddDate розходяться на межі переходу на літній/зимовий час¶
Саме тому існують обидва методи. «Додати один день» і «додати 24 години» — різні питання, і в ніч переведення годинників вони дають різні відповіді:
loc, _ := time.LoadLocation("America/New_York")
before := time.Date(2026, time.March, 7, 12, 0, 0, 0, loc)
fmt.Println(before.AddDate(0, 0, 1).Format(time.RFC3339))
// output: 2026-03-08T12:00:00-04:00
fmt.Println(before.Add(24 * time.Hour).Format(time.RFC3339))
// output: 2026-03-08T13:00:00-04:00
AddDate зберігає час на стінному годиннику — і далі опівдень. Add
просуває реальний минулий час, а оскільки та неділя коротша на годину,
опівдень плюс 24 години потрапляє на першу годину дня. Використовуйте
AddDate для «завтра», Add для «через 24 години».
Часові пояси¶
time.Time несе в собі локацію. In змінює, як значення
відображається, не змінюючи сам момент:
ny, _ := time.LoadLocation("America/New_York")
fmt.Println(t.In(ny).Format(time.RFC3339)) // output: 2026-09-20T11:04:05-04:00
fmt.Println(t.UTC().Format(time.RFC3339)) // output: 2026-09-20T15:04:05Z
LoadLocation читає системну базу часових поясів, тож може зазнати
невдачі на мінімальному образі контейнера. time.UTC і time.Local
завжди працюють.
Порівнюйте через Equal, не через ==¶
time.Time — це структура, що містить час на стінному годиннику,
монотонне значення та вказівник на локацію. == порівнює все це, тож
той самий момент у двох поясах не буде ==:
a := time.Date(2026, 1, 1, 0, 0, 0, 0, time.UTC)
b := a.In(ny)
fmt.Println(a == b) // output: false
fmt.Println(a.Equal(b)) // output: true
Завжди використовуйте Equal, Before і After. == для time.Time
компілюється й майже завжди є багом — та сама пастка, про яку попереджає
стаття про структури щодо
порівнюваних структур узагалі.
Нульове значення — це рік 1, і IsZero — спосіб сказати «не встановлено»:
var zero time.Time
fmt.Println(zero.IsZero(), zero.Format(time.RFC3339))
// output: true 0001-01-01T00:00:00Z
Саме тому «відсутню» часову позначку зазвичай оголошують як
*time.Time: нульове значення — це справжня дата, а не відсутність
значення.
Монотонні показники¶
time.Now() також записує показник монотонного годинника, яким
автоматично користуються Sub, Since, Before і After. Це означає,
що вимірювання минулого часу лишається коректним, навіть якщо системний
годинник скоригували під час вимірювання. Цей показник відкидається
методами Round, Truncate, UTC та In — тож спершу вимірюйте, а вже
потім конвертуйте.
З досвіду Python:
Format/Parseзамінюютьstrftime/strptime, але макет — це приклад, а не набір кодів із%.time.Duration— цеtimedeltaз арифметикою, яку зручніше читати. Звичку, яку варто позбутися, — це==для часових позначок:datetimeу Python порівнює за моментом часу, а порівняння структур у Go — ні.
Швидка довідка¶
| Задача | Виклик |
|---|---|
| поточний момент | time.Now() |
| побудувати момент | time.Date(y, time.Month, d, h, m, s, ns, loc) |
| форматувати | t.Format(time.RFC3339) або t.Format("2006-01-02") |
| розпарсити | time.Parse(layout, s), time.ParseInLocation |
| минулий час | time.Since(start) |
| додати години | t.Add(24 * time.Hour) |
| додати календарні дні | t.AddDate(0, 0, 1) |
| різниця | b.Sub(a) → time.Duration |
| порівняти | Equal, Before, After — ніколи == |
| тривалість з конфігурації | time.ParseDuration("1h30m") |
| тривалість зі змінної | time.Duration(n) * time.Second |
| змінити пояс відображення | t.In(loc), t.UTC() |
| «не встановлено» | t.IsZero() або поле *time.Time |