Как в Go правильно хранить и обрабатывать время с учетом часовых поясов?

Ответ

Для работы со временем в Go следует использовать стандартный тип time.Time. Он изначально спроектирован для корректной работы с часовыми поясами.

Ключевые принципы:


  1. Храните время в UTC. Внутри вашего приложения, в базах данных и при передаче через API всегда используйте универсальное координированное время (UTC). Это единый стандарт, который исключает путаницу с часовыми поясами и летним/зимним временем.



  2. Используйте time.Location для работы с поясами. Для представления конкретного часового пояса (например, "Europe/Moscow") используйте объекты *time.Location. Они содержат все правила перехода на летнее время для данной зоны.



  3. Конвертируйте в локальное время только для отображения. Преобразование из UTC в локальный часовой пояс пользователя должно происходить на самом последнем этапе — непосредственно перед отображением данных пользователю.



  4. Для сериализации используйте time.RFC3339. При передаче времени в текстовом виде (например, в JSON API) используйте формат time.RFC3339 ("2006-01-02T15:04:05Z07:00"). Он является стандартом ISO 8601 и содержит информацию о смещении относительно UTC, что делает его однозначным.



  5. В базе данных используйте тип, поддерживающий часовые пояса, например TIMESTAMP WITH TIME ZONE (или TIMESTAMPTZ) в PostgreSQL.


Пример:

package main

import (
    "fmt"
    "time"
)

func main() {
    // 1. Получаем текущее время. Оно уже содержит информацию о системной зоне.
    now := time.Now()
    fmt.Printf("Текущее время: %sn", now)

    // 2. Конвертируем в UTC для хранения или передачи.
    utcTime := now.UTC()
    fmt.Printf("Время в UTC:   %sn", utcTime)

    // 3. Для отображения пользователю в Москве, загружаем нужную локацию.
    moscowLocation, err := time.LoadLocation("Europe/Moscow")
    if err != nil {
        panic(err)
    }
    moscowTime := utcTime.In(moscowLocation)
    fmt.Printf("Время в Москве: %sn", moscowTime)

    // 4. Сериализация в стандартный формат для API.
    fmt.Printf("Формат RFC3339: %sn", now.Format(time.RFC3339))
}