Перейти к содержанию

412 уроков, 25 библиотек и челлендж «Что выведет код?» — бесплатно, код прямо в браузере

Начать обучение
Урок 13 из 20 Начальный 20 мин 130 XP

timestamp: unix-время и эпоха 1970

Unix-время — число секунд с полуночи 1 января 1970 года по UTC. Считаем timestamp у aware-дат, переводим число обратно через fromtimestamp — и всегда указываем пояс.

Редакция Питоники

В логах, ответах API и базах данных время часто лежит одним числом: 1792593045. Это unix-время — количество секунд, прошедших с условного нуля отсчёта. Python умеет переводить даты в такие числа и обратно, но у этого моста есть подводный камень с часовыми поясами — разберём обе стороны.

Эпоха: полночь 1 января 1970 по UTC

ноль секунд после эпохи
from datetime import datetime, timezone

epoch = datetime.fromtimestamp(0, tz=timezone.utc)
print(epoch)
print(repr(epoch))
Вывод
1970-01-01 00:00:00+00:00
datetime.datetime(1970, 1, 1, 0, 0, tzinfo=datetime.timezone.utc)

Ноль секунд после эпохи — это полночь 1 января 1970 года по UTC: с этого момента мир договорился считать unix-время. fromtimestamp переводит число в datetime, и здесь принципиален аргумент tz=timezone.utc: само число ничего не знает о поясах, поэтому пояс нужно показать явно. Только с указанным tz результат одинаков на любой машине — от ноутбука в Москве до сервера в Амстердаме.

дата в число
from datetime import datetime, timezone

d = datetime(2026, 10, 21, 14, 30, 45, tzinfo=timezone.utc)
ts = d.timestamp()
print(ts)
print(round(ts))
Вывод
1792593045.0
1792593045

Метод timestamp() у aware-объекта возвращает, сколько секунд прошло с эпохи до его момента: у 14:30:45 UTC 21 октября 2026 года получилось 1 792 593 045. Возвращается float, потому что datetime хранит микросекунды и дробная часть возможна; round(ts) даёт целое число. У дат без микросекунд, как здесь, дробной части нет.

Один момент — одно число

UTC и московское время
from datetime import datetime, timedelta, timezone

utc = datetime(2026, 10, 21, 11, 30, 45, tzinfo=timezone.utc)
msk = datetime(2026, 10, 21, 14, 30, 45, tzinfo=timezone(timedelta(hours=3)))
print(utc.timestamp())
print(msk.timestamp())
print(utc.timestamp() == msk.timestamp())
Вывод
1792582245.0
1792582245.0
True

11:30 по UTC и 14:30 по московскому времени — один и тот же момент, и timestamp это подтверждает: числа совпали. В этом смысл unix-времени. Число — сам момент, а пояс лишь решает, какими цифрами момент покажется на часах. Именно поэтому unix-время удобно хранить и сравнивать: одна шкала для всей планеты.

число обратно в дату
from datetime import datetime, timezone

d = datetime(2026, 10, 21, 14, 30, 45, tzinfo=timezone.utc)
back = datetime.fromtimestamp(d.timestamp(), tz=timezone.utc)
print(back)
print(back == d)
Вывод
2026-10-21 14:30:45+00:00
True

Круг замкнулся: datetime превратился в число и вернулся обратно равным себе — при условии, что обе стороны помнят про tz. Осталось разобраться, что такое пояса и откуда берётся запись +03:00: этим займётся следующий урок.

Миллисекунды: откуда берётся умножение на 1000

В реальных системах unix-время почти никогда не доходит до вас в секундах. JavaScript отдаёт Date.now() в миллисекундах, PostgreSQL возвращает timestamp с микросекундами, Kafka кладёт в ключ целое число миллисекунд. Python со своей стороны отдаёт float секунд — и разница в множителе становится самой частой ошибкой при разборе внешних данных. Хуже всего, что ошибка не бросается: число получается в тысячу раз меньше, чем должно, и выглядит как правдоподобная дата 1970 года.

секунды, миллисекунды, микросекунды
from datetime import datetime, timezone

d = datetime(2026, 10, 21, 14, 30, 45, tzinfo=timezone.utc)
ts = d.timestamp()

print(ts)
print(int(ts * 1000))
print(int(ts * 1_000_000))
Вывод
1792593045.0
1792593045000
1792593045000000

Три числа — один и тот же момент в трёх единицах. Множители разные, а риск общий: float хранит примерно 15-16 значащих цифр, и при умножении на миллион запас кончается гораздо раньше, чем кажется. Отсюда правило: чем крупнее единица, тем ближе к границе точности вы приближаетесь, а для целых чисел в базе переходите на int — точные целые Python хранит без потерь.

int срезает, а не округляет
from datetime import datetime, timezone

half = datetime(2026, 10, 21, 14, 30, 45, 500000, tzinfo=timezone.utc)

print(half.timestamp())
print(int(half.timestamp()))
print(round(half.timestamp()))

before = datetime(1969, 12, 31, 23, 59, 59, 500000, tzinfo=timezone.utc)
print(int(before.timestamp()))
Вывод
1792593045.5
1792593045
1792593046
0

int отбрасывает дробную часть к нулю, а round округляет по правилу банка — к ближайшему чётному. На половине секунды они расходятся на целую секунду: 1792593045.5 превращается то в 1792593045, то в 1792593046. Если вы переводите timestamp в целые миллисекунды для метрики или ключа, выбирайте одну функцию и не меняйте её между слоями: неделю мы ловили метрику, которая на ровно полусекундных границах считала событие то в одном бакете, то в соседнем.

Где кончается точность float

Двоичная плавающая точка хранит 53 значащих бита. Это примерно 9 007 199 254 740 992 целых — тот предел, дальше которого соседние целые float не различает и округляет к ближайшему чётному. Секунды unix-времени живут внутри этого предела с колоссальным запасом: 2**53 секунд — это около 285 миллионов лет. Микросекунды в том же запасе не помещаются.

предел точности в разных единицах
# 53 значащих бита: целые дальше этой отметки float не различает
print(2 ** 53)
print(2 ** 53 // 1000)
print(2 ** 53 // 1000 // 1000)
Вывод
9007199254740992
9007199254740
9007199254

Переведите вторую строку в годы — примерно 285 лет от эпохи, нормальный запас. Третья строка — микросекунды, и там всего около двух с половиной часов. Практический вывод: unix-время в микросекундах внутри float начинает врать задолго до 2262 года, а не в 2262-м. Мы перестали хранить микросекунды в float и перешли на int — там переполнение можно хотя бы заметить, потому что оно выдаёт себя круглым числом.

2038: конец 32-битного счётчика

предел знакового 32-битного int
from datetime import datetime, timezone

limit = 2 ** 31 - 1

print(limit)
print(datetime.fromtimestamp(limit, tz=timezone.utc))
Вывод
2147483647
2038-01-19 03:14:07+00:00

2147483647 секунд — это 19 января 2038 года, 03:14:07 UTC. Ровно в эту секунду 32-битный знаковый int переполняется и уезжает в отрицательные значения, то есть в начало шкалы, к 1970 году. Если timestamp лежит в колонке INT, а не BIGINT, или в 32-битном поле протокола, через двенадцать лет значения начнут прыгать назад на полвека. Мы нашли такое в чужом экспорте: счётчик событий стоял в INT, и вместо номера события мы получали дату 1970 года.

Сортировка: числам не нужен datetime

Если события уже лежат числами, сортировать их можно вообще без всяких дат: обычный sorted() по списку целых работает и быстрее, и без риска, что кто-то поставит datetime в один контейнер, а строку — в другой. Обратное преобразование делают уже по одному, когда значение понадобилось показать.

сортировка сырых timestamp
from datetime import datetime, timezone

events = [1792582245, 1792582200, 1792582300]

for ts in sorted(events):
    print(datetime.fromtimestamp(ts, tz=timezone.utc).strftime('%H:%M:%S'))
Вывод
11:30:00
11:30:45
11:31:40

Сортировка чисел даёт тот же порядок, что и сортировка моментов, — но по числу вы ещё и получаете разницу между соседними простым вычитанием, без timedelta. Мы так делали в счётчиках: прирост за час считали как ts_now - ts_prev, а из уже разобранных дат собирали только то, что показываем человеку.

Что лежит в полеПримерКогда берут
BIGINT, секунды1792593045счётчики, ключи, сравнение в базе
BIGINT, миллисекунды1792593045000то, что отдаёт JavaScript
TIMESTAMP с поясом2026-10-21 14:30:45+00когда нужны даты и арифметика прямо в SQL
Строка ISO 86012026-10-21T14:30:45+00:00JSON и выгрузки, где формат важнее скорости

Выбор между ними — не вопрос вкуса, а вопрос того, что вы будете делать с данными. Число сравнивается, сортируется и складывается без единого преобразования, но человек его прочитать не может. ISO-строка читается глазами и едет в JSON без потерь, но сортировать её придётся осторожно: одинаковый формат ещё не гарантия одинаковой длины, а часовой пояс в строке без разбора текста не виден. Мы в базе держим TIMESTAMP, наружу отдаём ISO — подробнее об этом в уроке про isoformat — а для счётчиков и ключей держим отдельное BIGINT-поле.

Unix-время живёт и без datetime

  • заголовок Date в HTTP-ответе — тот же самый момент, но в формате RFC 1123;
  • поля iat и exp в JWT — целые секунды с эпохи, их и проверяет сервер аутентификации;
  • атрибут Max-Age у cookie — целое число секунд, а не дата;
  • метрики и метки в системах мониторинга — миллисекунды с начала эпохи Unix.

Ни один из этих форматов не требует datetime вообще. Но тот, кто их читает, рано или поздно захочет посчитать «сколько секунд осталось» или «что было раньше» — и вот тут уже нужны либо арифметика на числах, либо один-единственный вызов timestamp() с явным поясом. Ровно эту границу и держит правило урока: число — сам момент, а пояс решает, какими цифрами момент покажется на часах.

замена устаревшего utcnow()
from datetime import datetime, timezone

# Устаревший способ получить время UTC: datetime.utcnow()
# Актуальный способ: datetime.now(timezone.utc)
print(datetime.now(timezone.utc))
Блок намеренно не запускается: now() возвращает текущий момент, и вывод не был бы воспроизводимым между запусками. Заодно он показывает замену устаревшего utcnow() — в Python 3.13 тот вызов помечен предупреждением DeprecationWarning, а в 3.12 ещё молчит, поэтому полагаться на него не стоит.

Что дальше

Всё, что разобрали выше, работает, пока вы сравниваете моменты, записанные в одном поясе. Как только в одной таблице встречаются записи из разных зон — а в логах они встречаются всегда — число перестаёт быть достаточным. Дальше — урок про часовые пояса: там naive-дата противётся aware, а astimezone переносит один и тот же момент в разные цифры на часах.

Что выведет код?

Сначала предскажи ответ в голове — это главный навык программиста.

from datetime import datetime, timezone

epoch = datetime.fromtimestamp(0, tz=timezone.utc)
print(epoch.year)
from datetime import datetime, timezone

d = datetime(2026, 10, 21, 14, 30, 45, tzinfo=timezone.utc)
print(round(d.timestamp()))
from datetime import datetime, timedelta, timezone

utc = datetime(2026, 10, 21, 11, 30, 45, tzinfo=timezone.utc)
msk = datetime(2026, 10, 21, 14, 30, 45, tzinfo=timezone(timedelta(hours=3)))
print(round(msk.timestamp()) - round(utc.timestamp()))
from datetime import datetime, timezone

d = datetime(2026, 10, 21, 14, 30, 45, tzinfo=timezone.utc)
print(int(d.timestamp() * 1000))
from datetime import datetime, timezone

d = datetime(2026, 10, 21, 14, 30, 45, 500000, tzinfo=timezone.utc)
print(int(d.timestamp()))
print(round(d.timestamp()))
Проверь себя
0 / 9

1. Что такое unix-время?

2. Зачем в fromtimestamp аргумент tz=timezone.utc?

3. Какой тип возвращает метод d.timestamp()?

4. Один момент записан в UTC и в UTC+3. Что вернут их timestamp()?

5. Что вернёт datetime.fromtimestamp(0, tz=timezone.utc)?

6. Чем int(ts) отличается от round(ts) для значения 1792593045.5?

7. Что произойдёт с int(-0.5) и почему это важно для дат до 1970 года?

8. Почему unix-время в микросекундах опасно хранить во float?

9. Что такое проблема 2038 года?

Карточки терминов
Запомнено: 0 / 11
Практика

Возьмите aware-дату datetime(2026, 10, 21, 6, 0, tzinfo=timezone.utc). Посчитайте её timestamp, округлите round() и выведите число. Затем верните дату через fromtimestamp с tz=timezone.utc и выведите её.

practice.py
Вопросы и ответы по уроку

Как получить timestamp даты в Python?

Методом d.timestamp() у объекта datetime: он вернёт число секунд с эпохи 1970 года как float. Считайте его только у aware-дат — с tzinfo, иначе результат зависит от пояса машины: datetime(2026, 10, 21, 14, 30, 45, tzinfo=timezone.utc).timestamp() всегда даст 1792593045.0.

Почему timestamp без пояса — ошибка?

Потому что naive-объект не знает своего пояса, и метод молча предполагает местное время той машины, где запущен код: сервер в UTC и ноутбук в Москве получат разные числа из одного 14:30. Ошибки при этом не будет — числа просто разъедутся между машинами. Спасают явный tzinfo у даты и tz= в fromtimestamp.

Что такое эпоха Unix и почему 1970 год?

Так исторически выбрали ноль отсчёта unix-времени: полночь 1 января 1970 года по UTC. Все моменты времени измеряются секундами от неё, поэтому число 0 — это ровно 1970-01-01 00:00:00+00:00, что показывает datetime.fromtimestamp(0, tz=timezone.utc).

Как перевести timestamp обратно в дату?

Методом datetime.fromtimestamp(ts, tz=timezone.utc) — с обязательным аргументом tz. Тогда число однозначно превратится в aware-дату, одинаковую на любой машине, и круговая проверка datetime.fromtimestamp(d.timestamp(), tz=timezone.utc) == d вернёт True.

Урок 14: часовые пояса

Как перевести дату в миллисекунды unix-времени?

Умножьте timestamp на 1000 и возьмите int: int(d.timestamp() * 1000) даст 1792593045000 для 21 октября 2026 года 14:30:45 UTC. Микросекунды — умножение на 1 000 000, но там уже стоит следить за пределом точности float в 2**53 целых.

Урок 13: timestamp

Что такое проблема 2038 года в Python?

Ограничение не Python, а типа данных: знаковый 32-битный int вмещает максимум 2147483647, а это 19 января 2038 года 03:14:07 UTC. Если timestamp хранится в колонке INT, после этой даты значения переполняются и прыгают назад к 1970 году. В базе под такие значения нужен BIGINT, а в JSON — целое число вместо float.

Почему timestamp возвращает float, а не целое число?

Объект datetime хранит микросекунды, поэтому метод возвращает дробное число: у даты 14:30:45.5 получится 1792593045.5. Если нужны целые секунды, применяйте round(ts) — причём round, а не int: int срезает к нулю и на отрицательных значениях до эпохи теряет секунду.

Можно ли хранить timestamp в JSON?

Да, но только как число: json.dumps объекта datetime падает с TypeError: Object of type datetime is not JSON serializable. Число уезжает без потерь, а если нужен читаемый вид — сначала превратите дату в строку через isoformat(), а на приёме разберите обратно через datetime.fromisoformat().

Понравился урок? Сошлитесь на него

«Число — сам момент, а пояс лишь решает, какими цифрами момент покажется на часах.»

Скопируйте готовую ссылку в формате HTML, Markdown или чистый адрес и вставьте в статью на Habr, VC, Telegram-канал или свой блог — так о проекте узнают новые читатели.

TelegramVK

Что дальше

Осталось в разделе: 20 из 20. Прогресс хранится в вашем браузере.

Похожие уроки по темам

Подобраны автоматически по пересечению тем и ключевых слов.