Trabalhando com data e hora em Go (Golang): um guia detalhado
Что это такое и зачем? (аналогия из реальной жизни)
Представьте, что вы — директор школы. У вас есть расписание звонков, даты каникул, дни рождения учеников и сроки сдачи отчетов. Без часов и календаря в школе наступил бы хаос: уроки начинались бы когда вздумается, а каникулы — зимой вместо лета.
В программировании то же самое. Работа с датой и временем — это умение программы понимать, когда что-то происходит: сейчас, вчера, через неделю или 1 января 2025 года в 15:30. Без этого не работает ни один современный сервис:
- 📅 Бронирование отелей — нужно знать, свободен ли номер 12 августа
- ⏰ Будильники и напоминания — разбудить в 7 утра
- 📊 Аналитика — сколько пользователей зашли вчера
- 💳 Банки — когда придет зарплата и не просрочен ли платеж
В Go (Golang) работа с датой и временем — это встроенная возможность. Вам не нужно скачивать дополнительные библиотеки: всё уже есть в пакете time. Это как если бы в каждой школьной парте уже были встроены часы и календарь — осталось только научиться ими пользоваться.
💡 Важно: В Go нет отдельного типа «дата». Есть только момент времени (time.Time), который включает и дату, и время. Нельзя сказать «15 марта» — нужно сказать «15 марта 2025 года 00:00:00». Это особенность языка, к которой быстро привыкаешь.
Теория: как это работает изнутри
Чтобы понять, как Go работает со временем, давайте заглянем «под капот». Компьютер не понимает «понедельник» или «июнь». Он понимает только числа. Поэтому все даты и время в программировании хранятся как одно большое число.
Unix Time — «всемирное время»
В Go (как и в большинстве языков) время хранится в формате Unix time — это количество секунд, прошедших с 1 января 1970 года 00:00:00 UTC. Почему именно эта дата? Потому что в 1970 году появилась операционная система Unix, и разработчики договорились считать время от этого момента.
Например, число 1 704 000 000 — это 1 января 2024 года в 00:00:00 по UTC. Go хранит это число внутри структуры time.Time и умеет преобразовывать его в понятный человеку вид: год, месяц, день, час, минута, секунда.
Тип time.Time — главный герой
В Go время представлено структурой time.Time. Она содержит:
- wall clock — настенные часы (текущее время)
- monotonic clock — монотонные часы (для измерения интервалов, не сбрасываются при переводе часов)
- location — часовой пояс
Это сделано для точности: если вы замеряете, сколько выполняется функция, вам не важно, что кто-то перевел часы на зимнее время. Монотонные часы всегда идут вперед.
Часовые пояса — головная боль всего мира
Земля разделена на 24 часовых пояса. Когда в Москве полдень, в Нью-Йорке — 4 утра, а в Токио — 6 вечера. Go хранит время в UTC (всемирное координированное время), а при выводе преобразует в нужный часовой пояс.
⚠️ Золотое правило: В базе данных и в логике программы всегда храните время в UTC. Преобразуйте в локальное время только при показе пользователю. Это спасет вас от бесконечных ошибок с часовыми поясами.
Тип Duration — промежуток времени
Кроме момента времени, Go умеет работать с длительностью — типом time.Duration. Это просто количество наносекунд (1 секунда = 1 000 000 000 наносекунд).
Вы можете сказать: «добавь 2 часа 30 минут к текущему времени». Go переведет это в наносекунды и выполнит операцию.
Монотонные часы — почему это важно
Представьте, что вы засекаете время выполнения функции. Если в этот момент Windows переведет часы на час назад (из-за перехода на летнее время), то обычные часы покажут, что функция выполнялась -50 минут. Абсурд! Монотонные часы не зависят от перевода стрелок — они всегда увеличиваются.
В Go при вызове time.Now() вы получаете оба значения. При сравнении и вычислении разницы используются монотонные часы, если они доступны.
Синтаксис в Go (Golang): все формы и варианты
Пакет time — это стандартная библиотека Go. Его не нужно устанавливать, только импортировать:
import "time"
Основные функции и методы
| Функция/Метод | Описание | Пример |
|---|---|---|
time.Now() |
Текущий момент времени | now := time.Now() |
time.Date() |
Создать дату из компонентов | t := time.Date(2025, 3, 15, 10, 30, 0, 0, time.UTC) |
t.Format() |
Преобразовать время в строку | t.Format("2006-01-02") |
time.Parse() |
Преобразовать строку во время | t, _ := time.Parse("2006-01-02", "2025-03-15") |
t.Add() |
Прибавить длительность | t.Add(2 * time.Hour) |
t.Sub() |
Разница между двумя моментами | diff := t2.Sub(t1) |
t.Before() |
Проверить, раньше ли | t1.Before(t2) |
t.After() |
Проверить, позже ли | t1.After(t2) |
t.Equal() |
Проверить равенство | t1.Equal(t2) |
time.Since() |
Время, прошедшее с момента | time.Since(start) |
time.Until() |
Время до момента | time.Until(deadline) |
time.Sleep() |
Приостановить выполнение | time.Sleep(1 * time.Second) |
t.In() |
Сменить часовой пояс | t.In(time.FixedZone("MSK", 3*60*60)) |
t.Unix() |
Получить Unix timestamp | t.Unix() |
Формат времени — «магическое число» 2006-01-02
В Go используется уникальный способ форматирования даты. Вместо символов типа YYYY-MM-DD используется конкретная дата:
Mon Jan 2 15:04:05 MST 2006
Это 2 января 2006 года, 15:04:05, понедельник, часовой пояс MST. Каждая часть этой даты — шаблон:
| Шаблон | Значение | Пример вывода |
|---|---|---|
2006 |
Год (4 цифры) | 2025 |
06 |
Год (2 цифры) | 25 |
01 |
Месяц (2 цифры) | 03 |
1 |
Месяц (1-2 цифры) | 3 |
Jan |
Месяц (сокращенный) | Mar |
January |
Месяц (полный) | March |
02 |
День (2 цифры) | 15 |
2 |
День (1-2 цифры) | 15 |
Mon |
День недели (сокращенный) | Sat |
Monday |
День недели (полный) | Saturday |
15 |
Час (24-часовой) | 10 |
3 |
Час (12-часовой) | 10 |
04 |
Минуты | 30 |
05 |
Секунды | 00 |
PM |
AM/PM | AM |
MST |
Часовой пояс | UTC |
-0700 |
Смещение часового пояса | +0300 |
🔥 Запомните: 2006-01-02 15:04:05 — это не случайные числа. Это 1:04:05 после полудня 2 января 2006 года. Выучите эту дату, и форматирование в Go станет для вас родным.
Константы длительности
Для удобства в Go определены константы:
time.Nanosecond // 1 наносекунда
time.Microsecond // 1000 наносекунд
time.Millisecond // 1000 микросекунд
time.Second // 1000 миллисекунд
time.Minute // 60 секунд
time.Hour // 60 минут
Их можно комбинировать: 2*time.Hour + 30*time.Minute + 15*time.Second
Примеры кода — от простого к сложному
Пример 1: Текущее время и его компоненты
package main
import (
"fmt"
"time"
)
func main() {
// Получаем текущий момент времени
now := time.Now()
// Выводим целиком
fmt.Println("Сейчас:", now)
// Получаем отдельные компоненты
year := now.Year() // год
month := now.Month() // месяц (тип time.Month)
day := now.Day() // день месяца
hour := now.Hour() // часы
minute := now.Minute() // минуты
second := now.Second() // секунды
weekday := now.Weekday() // день недели
fmt.Printf("Сегодня: %d %s %d года
", day, month, year)
fmt.Printf("Точное время: %02d:%02d:%02d
", hour, minute, second)
fmt.Printf("День недели: %s
", weekday)
// Unix timestamp — количество секунд с 1970 года
unixTime := now.Unix()
fmt.Printf("Unix time: %d
", unixTime)
}
Объяснение: Мы получаем текущее время через time.Now(). Затем извлекаем из него год, месяц, день, часы, минуты, секунды и день недели. Обратите внимание на %02d — это формат вывода с ведущим нулем (чтобы было 09, а не 9). В конце получаем Unix timestamp — число, понятное компьютеру.
Пример 2: Создание конкретной даты
package main
import (
"fmt"
"time"
)
func main() {
// Создаем дату: 15 марта 2025 года, 10:30:00, UTC
// Параметры: год, месяц, день, час, мин, сек, наносек, часовой пояс
meeting := time.Date(2025, time.March, 15, 10, 30, 0, 0, time.UTC)
fmt.Println("Встреча назначена на:", meeting)
// Можно использовать число вместо time.Month
birthday := time.Date(2000, 7, 20, 0, 0, 0, 0, time.UTC)
fmt.Println("День рождения:", birthday)
// Сравниваем даты
now := time.Now()
if meeting.After(now) {
fmt.Println("Встреча еще в будущем")
} else if meeting.Before(now) {
fmt.Println("Встреча уже прошла")
} else {
fmt.Println("Встреча прямо сейчас!")
}
}
Объяснение: Функция time.Date() принимает 8 параметров. Месяц можно указать как константу (time.March) или как число (3). Часовой пояс time.UTC — это всемирное время. Методы After() и Before() возвращают true или false, позволяя сравнивать даты.
Пример 3: Форматирование даты в строку
package main
import (
"fmt"
"time"
)
func main() {
now := time.Now()
// Стандартные форматы
fmt.Println("RFC3339:", now.Format(time.RFC3339))
fmt.Println("ANSIC:", now.Format(time.ANSIC))
fmt.Println("Kitchen:", now.Format(time.Kitchen))
// Пользовательские форматы (используем магическую дату 2006-01-02)
fmt.Println("YYYY-MM-DD:", now.Format("2006-01-02"))
fmt.Println("DD.MM.YYYY:", now.Format("02.01.2006"))
fmt.Println("ЧЧ:ММ:СС:", now.Format("15:04:05"))
fmt.Println("Полная дата:", now.Format("02 January 2006, Monday"))
fmt.Println("Для файла:", now.Format("2006-01-02_15-04-05"))
// 12-часовой формат с AM/PM
fmt.Println("12-часовой:", now.Format("03:04 PM"))
}
Объяснение: Go использует «магическую дату» 2006-01-02 для форматирования. time.RFC3339 — это встроенный формат для обмена данными (JSON, API). Обратите внимание: в пользовательском формате мы пишем именно 2006, 01, 02, 15, 04, 05 — это не текущие значения, а шаблоны.
Пример 4: Разбор строки в дату (парсинг)
package main
import (
"fmt"
"time"
)
func main() {
// Парсим дату из строки
// Первый аргумент — шаблон (как выглядит строка)
// Второй — сама строка
dateStr := "2025-03-15"
parsed, err := time.Parse("2006-01-02", dateStr)
if err != nil {
fmt.Println("Ошибка парсинга:", err)
return
}
fmt.Println("Распарсено:", parsed)
// Парсим с временем
datetimeStr := "15.03.2025 14:30:00"
parsed2, _ := time.Parse("02.01.2006 15:04:05", datetimeStr)
fmt.Println("С временем:", parsed2)
// Парсим с часовым поясом
withTZ := "2025-03-15T10:30:00+03:00"
parsed3, _ := time.Parse(time.RFC3339, withTZ)
fmt.Println("С часовым поясом:", parsed3)
// Ошибка: несовпадение формата
wrong := "2025/03/15"
_, err = time.Parse("2006-01-02", wrong)
if err != nil {
fmt.Println("Ожидаемая ошибка:", err)
}
}
Объяснение: time.Parse() — обратная операция к Format(). Шаблон должен точно соответствовать формату строки. Если строка "15.03.2025", то и шаблон должен быть "02.01.2006" с точками. При ошибке возвращается error — всегда проверяйте его!
Пример 5: Работа с интервалами времени
package main
import (
"fmt"
"time"
)
func main() {
start := time.Now()
// Имитируем какую-то работу
time.Sleep(2 * time.Second)
end := time.Now()
// Разница между двумя моментами
duration := end.Sub(start)
fmt.Println("Прошло:", duration)
// Более удобный способ
elapsed := time.Since(start)
fmt.Println("Прошло (since):", elapsed)
// Сколько осталось до дедлайна
deadline := time.Date(2025, 12, 31, 23, 59, 59, 0, time.UTC)
remaining := time.Until(deadline)
fmt.Println("До Нового года осталось:", remaining)
// Прибавляем время
now := time.Now()
later := now.Add(3 * time.Hour) // через 3 часа
tomorrow := now.Add(24 * time.Hour) // завтра
nextWeek := now.Add(7 * 24 * time.Hour) // через неделю
fmt.Println("Через 3 часа:", later)
fmt.Println("Завтра:", tomorrow)
fmt.Println("Через неделю:", nextWeek)
// Вычитаем время
yesterday := now.Add(-24 * time.Hour)
fmt.Println("Вчера:", yesterday)
}
Объяснение: time.Since() и time.Until() — удобные обертки для Sub(). Add() принимает time.Duration (положительное или отрицательное). Обратите внимание: time.Sleep() приостанавливает программу на заданное время — полезно для имитации работы.
Пример 6: Часовые пояса
package main
import (
"fmt"
"time"
)
func main() {
// Текущее время в UTC
now := time.Now().UTC()
fmt.Println("UTC:", now)
// Загружаем часовой пояс
msk, err := time.LoadLocation("Europe/Moscow")
if err != nil {
fmt.Println("Не удалось загрузить часовой пояс:", err)
return
}
// Преобразуем в московское время
moscowTime := now.In(msk)
fmt.Println("Москва:", moscowTime)
// Нью-Йорк
ny, _ := time.LoadLocation("America/New_York")
nyTime := now.In(ny)
fmt.Println("Нью-Йорк:", nyTime)
// Токио
tokyo, _ := time.LoadLocation("Asia/Tokyo")
tokyoTime := now.In(tokyo)
fmt.Println("Токио:", tokyoTime)
// Создаем свой часовой пояс (UTC+5)
ekb := time.FixedZone("Екатеринбург", 5*60*60)
ekbTime := now.In(ekb)
fmt.Println("Екатеринбург:", ekbTime)
// Сравнение: разные пояса, но один момент времени
fmt.Println("Равны ли?", moscowTime.Equal(nyTime)) // true — это один и тот же момент!
}
Объяснение: time.LoadLocation() загружает часовой пояс из базы данных IANA (встроена в Go). time.FixedZone() создает свой пояс с фиксированным смещением. Важно: Equal() сравнивает моменты времени, а не строки. 12:00 MSK и 04:00 EST — это одно и то же время.
Пример 7: Таймеры и тикеры
package main
import (
"fmt"
"time"
)
func main() {
// Таймер — срабатывает один раз через заданное время
fmt.Println("Запускаем таймер на 2 секунды...")
timer := time.NewTimer(2 * time.Second)
// Ждем сигнала от таймера
<-timer.C
fmt.Println("Таймер сработал!")
// Тикер — срабатывает периодически
fmt.Println("
Запускаем тикер (каждую секунду, 5 раз):")
ticker := time.NewTicker(1 * time.Second)
// Создаем свой таймер для остановки
stopTimer := time.NewTimer(5 * time.Second)
// Бесконечный цикл, но мы выйдем через 5 секунд
for {
select {
case t := <-ticker.C:
fmt.Println("Тик:", t.Format("15:04:05"))
case <-stopTimer.C:
fmt.Println("Стоп! Прошло 5 секунд.")
ticker.Stop() // Останавливаем тикер
return
}
}
}
Объяснение: time.NewTimer() создает канал, который пришлет сигнал через указанное время. time.NewTicker() создает канал, который присылает сигналы через равные промежутки. select — это как switch, но для каналов: выполняет тот case, который готов первым. Тикер нужно обязательно останавливать через Stop(), чтобы не было утечки памяти.
Пример 8: Измерение времени выполнения функции
package main
import (
"fmt"
"time"
)
// Функция, которую будем измерять
func hardWork() {
// Имитируем сложные вычисления
sum := 0
for i := 0; i < 1000000; i++ {
sum += i
}
time.Sleep(500 * time.Millisecond) // Имитация задержки
fmt.Println("Результат:", sum)
}
func main() {
// Способ 1: вручную
start := time.Now()
hardWork()
elapsed := time.Since(start)
fmt.Printf("Функция выполнялась: %v
", elapsed)
// Способ 2: с помощью defer (выполняется при выходе из функции)
measure := func() {
start := time.Now()
hardWork()
fmt.Printf("Измерение через defer: %v
", time.Since(start))
}
measure()
// Способ 3: точность до наносекунд
start = time.Now()
hardWork()
fmt.Printf("Наносекунд: %d
", time.Since(start).Nanoseconds())
fmt.Printf("Миллисекунд: %.2f
", float64(time.Since(start).Microseconds())/1000)
}
Объяснение: Измерение времени — важнейшая задача. time.Since(start) — самый простой способ. Duration можно преобразовать в наносекунды (.Nanoseconds()), микросекунды (.Microseconds()), миллисекунды (.Milliseconds()) или секунды (.Seconds()). Для точности используйте монотонные часы — Go делает это автоматически.
Сравнение с Python: чем похоже и чем отличается
Если вы уже знакомы с Python, вам будет проще понять Go. Но есть важные отличия:
| Характеристика | Go (time) | Python (datetime) |
|---|---|---|
| Основной тип | time.Time (всегда дата+время) |
datetime.date, datetime.time, datetime.datetime |
| Только дата | Нет отдельного типа. Используйте time.Date() с нулевым временем |
Есть datetime.date |
| Только время | Нет отдельного типа | Есть datetime.time |
| Форматирование | Через «магическую дату» 2006-01-02 | Через strftime с %Y, %m, %d |
| Парсинг | time.Parse(шаблон, строка) |
datetime.strptime(строка, шаблон) |
| Интервалы | time.Duration (наносекунды) |
datetime.timedelta (дни, секунды) |
| Часовые пояса | Встроенная поддержка, база IANA | Требуется pytz или zoneinfo (с 3.9) |
| Монотонные часы | Встроены в time.Now() |
time.monotonic() отдельно |
| Unix timestamp | t.Unix(), t.UnixNano() |
t.timestamp() |
| Сравнение | Before(), After(), Equal() |
Операторы <, >, == |
Главные отличия:
- Нет отдельной даты: В Go нельзя создать «15 марта» без года. Всегда нужен полный момент времени.
- Форматирование: Вместо
%Y-%m-%dиспользуется2006-01-02. Это непривычно, но очень наглядно. - Часовые пояса: В Go они встроены и работают «из коробки». В Python нужно устанавливать
pytz. - Монотонные часы: В Go они автоматически используются при измерении интервалов. В Python нужно вызывать
time.monotonic()отдельно.
💡 Совет: Если вы переходите с Python на Go, запомните: 2006-01-02 15:04:05 — это ваш новый %Y-%m-%d %H:%M:%S. Напишите эту дату на стикере и приклейте на монитор.