Нет времени, как сейчас

Головоломка

Как вы думаете, что напечатает следующая программа?

t1 := time.Now()
data, err := json.Marshal(t1)
if err != nil {
    log.Fatal(err)
}
var t2 time.Time
if err := json.Unmarshal(data, &t2); err != nil {
    log.Fatal(err)
}
fmt.Println(t1 == t2)

Эта программа напечатает: false.

Почему? Есть ли ошибка в пакете encoding/json?

Давайте посмотрим на определение time.Time (взято отсюда):

type Time struct {
    // ... (redacted)
 // If the hasMonotonic bit is 1, then the 33-bit field holds a 33-bit
 // unsigned wall seconds since Jan 1 year 1885, and ext holds a
 // signed 64-bit monotonic clock reading, nanoseconds since process start.
 wall uint64
 ext  int64

 // loc specifies the Location that should be used to
    // ... (redacted)
 loc *Location
}

Там упоминаются монотонные часы — что это?

Компьютеры и часы имеют долгую и сложную историю. У нас есть летнее время, високосные годы и даже високосные секунды. Настенные часы на вашем компьютере могутпрыгать, чтобы учитывать эти сдвиги во времени — они также могут корректировать время с точного сервера часов (см. NTP).

Эти временные сдвиги могут вызвать проблему, когда вы пытаетесь измерить, сколько времени занимает операция. Большинство компьютеров имеют монотонные часы, которые всегда увеличиваются. Монотонные часы — это источник времени, который не будет прыгать вперед или назад. Одно прочтение монотонного мало что значит, но разница между двумя точна.

Что подводит нас к нашей проблеме. Поскольку монотонные часы имеют смысл только на той же машине, нет причин включать их в сериализованные данные.

Если вы распечатаете время, вы увидите проблему:

fmt.Println("t1:", t1)
fmt.Println("t2:", t2)

Что выведет:

t1: 2022-11-29 14:21:10.981666007 +0200 IST m=+0.000013388
t2: 2022-11-29 14:21:10.981666007 +0200 IST

Как тогда можно сравнивать времена? Используя time.Equal . Следующее:

fmt.Println(t1.Equal(t2))

напечатает true .
В документации time.Time даже сказано:

Обратите внимание, что оператор Go == сравнивает не только момент времени, но также Location и монотонное показание часов. Таким образом, значения времени не следует использовать в качестве ключей карты или базы данных, не гарантируя сначала, что для всех значений было установлено идентичное местоположение, что может быть достигнуто с помощью метода UTC или локального метода, и что монотонное показание часов было удалено с помощью установка t = t.Round(0). В общем, предпочтительнее использовать t.Equal(u), чем t == u, так как t.Equal использует наиболее точное доступное сравнение и правильно обрабатывает случай, когда только один из его аргументов имеет монотонное показание часов.

Если вы думаете, что этот тибет о времени и программировании на Go запутан, я рекомендую вам прочитать Неправду, которую верят программисты о времени и Неправду, которую верят программисты о часовых поясах для еще большего ужаса.

об авторе



Если вам нравится решать задачи по программированию, ознакомьтесь с книгами Мики Тебека «Дразнилки для ума» на The Pragmatic Bookshelf. Вы можете сэкономить 35% на электронных версиях книг с промокодом brain_teasers_35 до 30 августа 2022 года: