В пакете time есть два разных понятия. time.Time как конкретный момент времени. time.Duration как промежуток между моментами. Форматирование и парсинг строк я уже разбирал в статье «Форматирование дат и времени в Go». Здесь сосредоточимся на другом: как складывать и вычитать время, измерять интервалы и работать с длительностями.
Это один из разборов в серии для начинающих. Общий маршрут изучения языка собран в статье «Go для начинающих: дорожная карта».
Что такое time.Duration
time.Duration внутри себя это один int64 в котором хранится количество наносекунд. Поэтому длительности можно складывать, вычитать и умножать на число как обычные числа.
package main
import (
"fmt"
"time"
)
func main() {
d := 2*time.Hour + 30*time.Minute
fmt.Println(d) // 2h30m0s
fmt.Println(d * 2) // 5h0m0s
fmt.Println(d / 3) // 50m0s
}
Константы time.Hour, time.Minute, time.Second, time.Millisecond и так далее — это значения Duration. Чтобы получить «3 секунды», нужно писать 3 * time.Second, а не просто 3:
timeout := 3 * time.Second // правильно
wrong := 3 // это 3 наносекунды, а не 3 секунды
Метод String() (вызывается автоматически при печати) выводит длительность в человекочитаемом виде: 2h3m45s.
Перевод в числа
У Duration есть методы для перевода в конкретные единицы. Для разных единиц измерения тоже разные: Hours, Minutes и Seconds возвращают float64, а Milliseconds, Microseconds и Nanoseconds уже возвращают int64.
d := 90 * time.Minute
fmt.Println(d.Hours()) // 1.5 (float64)
fmt.Println(d.Minutes()) // 90 (float64)
fmt.Println(d.Milliseconds()) // 5400000 (int64)
Если нужно собрать время формата HH:MM:SS вручную, делите с остатком:
d := 1*time.Hour + 14*time.Minute + 30*time.Second
h := int(d.Hours())
m := int(d.Minutes()) % 60
s := int(d.Seconds()) % 60
fmt.Printf("%02d:%02d:%02d\n", h, m, s) // 01:14:30
Арифметика моментов
С самим time.Time работают три метода.
Add сдвигает момент на некую длительность:
now := time.Now()
later := now.Add(15 * time.Minute) // через 15 минут
before := now.Add(-1 * time.Hour) // час назад
Sub возвращает разницу между двумя моментами как Duration:
start := time.Now()
// ... работа ...
elapsed := time.Now().Sub(start)
fmt.Println(elapsed) // 1.2s
AddDate сдвигает по календарю на годы, месяцы и дни. Это не то же самое, что прибавить 24 * time.Hour, потому что AddDate учитывает разную длину месяцев и переход на летнее время.
t := time.Date(2026, 1, 31, 0, 0, 0, 0, time.UTC)
fmt.Println(t.AddDate(0, 1, 0).Format("2006-01-02"))
// 2026-03-03
31 января плюс месяц даёт 3 марта, а не несуществующую дату в феврале: лишние дни нормализуются. Это поведение по умолчанию, его стоит держать в голове при расчёте сроков подписок и подобных задач.
time.Since и time.Until
Две частые операции, «сколько прошло» и «сколько осталось», оформлены отдельными функциями. Они короче, чем ручной Sub двух дат.
start := time.Now()
doWork()
fmt.Println("заняло:", time.Since(start)) // равно time.Now().Sub(start)
deadline := time.Date(2027, 1, 1, 0, 0, 0, 0, time.UTC)
fmt.Println("осталось:", time.Until(deadline)) // равно deadline.Sub(time.Now())
time.Since(start) — широко используется для замера время исполнения участка кода.
Парсинг длительностей: ParseDuration
Если длительность приходит строкой (например, из конфига или флага командной строки), её разбирает time.ParseDuration:
d, err := time.ParseDuration("1h30m")
if err != nil {
// обработка ошибки
}
fmt.Println(d) // 1h30m0s
Допустимые единицы: ns, us (или µs), ms, s, m, h. Единицы для дней нет — "1d" приведёт к ошибке:
_, err := time.ParseDuration("1d")
fmt.Println(err)
// time: unknown unit "d" in duration "1d"
Для суток пишите "24h". Строку можно делать дробной и знаковой: "-1.5h", "300ms", "2h45m".
Округление длительностей
Round округляет к ближайшему кратному, Truncate отбрасывает остаток вниз:
d := 1*time.Hour + 14*time.Minute + 30*time.Second
fmt.Println(d.Round(time.Hour)) // 1h0m0s
fmt.Println(d.Truncate(time.Hour)) // 1h0m0s
fmt.Println(d.Round(time.Minute)) // 1h15m0s (30s округлились вверх)
Те же методы есть и у time.Time. Они удобны, когда нужно «обнулить» секунды или привести момент к началу часа.
Сравнение моментов
Для сравнений time.Time есть методы Before, After и Equal. Оператором == сравнивать моменты не стоит: он учитывает не только сам момент, но и часовой пояс с монотонными часами, поэтому два «одинаковых» времени могут оказаться не равны.
if deadline.Before(time.Now()) {
fmt.Println("дедлайн прошёл")
}
// для проверки равенства момента — Equal, не ==
if t1.Equal(t2) {
fmt.Println("один и тот же момент")
}
Заключение
Две главные составляющие пакета time это момент времени (time.Time) и промежуток (time.Duration). Длительность под капотом представляет собой int64 наносекунд, поэтому её можно складывать и умножать как число, но константы вроде time.Second обязательны для использования: «голое» число 3 выразится как три наносекунды.
Для большинства работы хватает:
AddиSub— сдвиг момента и разница между моментами.AddDate— календарный сдвиг с учётом длины месяцев.time.Sinceиtime.Until— «сколько прошло» и «сколько осталось».ParseDuration— разбор строк вроде"1h30m"(без единицы для дней).
Форматирование и парсинг дат и времени из строк — отдельная тема, она разобрана в статье про reference time 2006-01-02.
Теги: