🐍 Python считаСт эти Π΄Π²Π΅ Π΄Π°Ρ‚Ρ‹ Ρ€Π°Π²Π½Ρ‹ΠΌΠΈ. Π₯отя ΠΌΠ΅ΠΆΠ΄Ρƒ Π½ΠΈΠΌΠΈ Ρ†Π΅Π»Ρ‹ΠΉ час. Π’Ρ€ΠΌΠ΅Π½Π½ΠΎΠΉ парадокс с ΠΊΠΎΠ΄ΠΎΠΌ, ΠΊΠΎΡ‚ΠΎΡ€Ρ‹ΠΉ тСбя ΡƒΠ΄ΠΈΠ²ΠΈΡ‚.

ΠŸΡ€Π΅Π΄ΡΡ‚Π°Π²ΠΈΠΌ Π½ΠΎΡ‡ΡŒ ΠΏΠ΅Ρ€Π΅Π²ΠΎΠ΄Π° часов Π² Π‘Π΅Ρ€Π»ΠΈΠ½Π΅:

25 октября 2026 Π³ΠΎΠ΄Π° врСмя пСрСводят с 03:00 Π½Π° 02:00.

ΠŸΠΎΡΡ‚ΠΎΠΌΡƒ локальноС врСмя 02:30 наступаСт Π΄Π²Π°ΠΆΠ΄Ρ‹:

  • ΠΏΠ΅Ρ€Π²ΠΎΠ΅ 02:30 – UTC+02:00;
  • Π²Ρ‚ΠΎΡ€ΠΎΠ΅ 02:30 – UTC+01:00.

Π­Ρ‚ΠΎ Π΄Π²Π° Ρ€Π°Π·Π½Ρ‹Ρ… ΠΌΠΎΠΌΠ΅Π½Ρ‚Π° Π²Ρ€Π΅ΠΌΠ΅Π½ΠΈ с Ρ€Π°Π·Π½ΠΈΡ†Π΅ΠΉ Ρ€ΠΎΠ²Π½ΠΎ Π² ΠΎΠ΄ΠΈΠ½ час.

from datetime import datetime
from zoneinfo import ZoneInfo

tz = ZoneInfo("Europe/Berlin")

a = datetime(
    2026, 10, 25, 2, 30,
    tzinfo=tz,
    fold=0,
)

b = datetime(
    2026, 10, 25, 2, 30,
    tzinfo=tz,
    fold=1,
)

А Ρ‚Π΅ΠΏΠ΅Ρ€ΡŒ Ρ€Π΅Π·ΡƒΠ»ΡŒΡ‚Π°Ρ‚:

print(a)
# 2026-10-25 02:30:00+02:00

print(b)
# 2026-10-25 02:30:00+01:00

print(a == b)
# True

print(b - a)
# 0:00:00

Выглядит Ρ‚Π°ΠΊ, Π±ΡƒΠ΄Ρ‚ΠΎ Python потСрял Ρ†Π΅Π»Ρ‹ΠΉ час.

Но Π²Ρ€Π΅ΠΌΠ΅Π½Π½Ρ‹Π΅ ΠΌΠ΅Ρ‚ΠΊΠΈ ΠΏΠΎΠ΄Ρ‚Π²Π΅Ρ€ΠΆΠ΄Π°ΡŽΡ‚, Ρ‡Ρ‚ΠΎ ΠΌΠΎΠΌΠ΅Π½Ρ‚Ρ‹ Ρ€Π°Π·Π½Ρ‹Π΅:

print(a.timestamp())
print(b.timestamp())

print(b.timestamp() - a.timestamp())
# 3600.0

ΠŸΠΎΡ‡Π΅ΠΌΡƒ это происходит

Π£ ΠΎΠ±ΡŠΠ΅ΠΊΡ‚ΠΎΠ² a ΠΈ b ΠΎΠ΄ΠΈΠ½ ΠΈ Ρ‚ΠΎΡ‚ ΠΆΠ΅ ΠΎΠ±ΡŠΠ΅ΠΊΡ‚ tzinfo.

ΠŸΡ€ΠΈ сравнСнии Ρ‚Π°ΠΊΠΈΡ… datetime Python сравниваСт Π»ΠΎΠΊΠ°Π»ΡŒΠ½Ρ‹Π΅ ΠΊΠΎΠΌΠΏΠΎΠ½Π΅Π½Ρ‚Ρ‹:

Π³ΠΎΠ΄
мСсяц
дСнь
час
ΠΌΠΈΠ½ΡƒΡ‚Π°
сСкунда

ЗначСния tzinfo ΠΈ fold ΠΏΡ€ΠΈ этом сравнСнии ΠΈΠ³Π½ΠΎΡ€ΠΈΡ€ΡƒΡŽΡ‚ΡΡ.

Π£ ΠΎΠ±Π΅ΠΈΡ… Π΄Π°Ρ‚ локально ΡƒΠΊΠ°Π·Π°Π½ΠΎ ΠΎΠ΄Π½ΠΎ ΠΈ Ρ‚ΠΎ ΠΆΠ΅:

2026-10-25 02:30

ΠŸΠΎΡΡ‚ΠΎΠΌΡƒ Python Π²ΠΎΠ·Π²Ρ€Π°Ρ‰Π°Π΅Ρ‚:

a == b  # True

Π§Ρ‚ΠΎ Ρ‚Π°ΠΊΠΎΠ΅ fold

fold появился Π² PEP 495 ΡΠΏΠ΅Ρ†ΠΈΠ°Π»ΡŒΠ½ΠΎ для ΠΏΠΎΠ²Ρ‚ΠΎΡ€ΡΡŽΡ‰Π΅Π³ΠΎΡΡ Π²Ρ€Π΅ΠΌΠ΅Π½ΠΈ ΠΏΡ€ΠΈ ΠΏΠ΅Ρ€Π΅Ρ…ΠΎΠ΄Π΅ с Π»Π΅Ρ‚Π½Π΅Π³ΠΎ Π²Ρ€Π΅ΠΌΠ΅Π½ΠΈ Π½Π° Π·ΠΈΠΌΠ½Π΅Π΅.

fold=0 - ΠΏΠ΅Ρ€Π²ΠΎΠ΅ появлСниС 02:30
fold=1 - Π²Ρ‚ΠΎΡ€ΠΎΠ΅ появлСниС 02:30

Он позволяСт ΠΎΠΏΡ€Π΅Π΄Π΅Π»ΠΈΡ‚ΡŒ ΠΏΡ€Π°Π²ΠΈΠ»ΡŒΠ½ΠΎΠ΅ смСщСниС UTC:

print(a.utcoffset())
# 2:00:00

print(b.utcoffset())
# 1:00:00

Однако Π½Π°Π»ΠΈΡ‡ΠΈΠ΅ fold Π½Π΅ мСняСт описанноС ΠΏΠΎΠ²Π΅Π΄Π΅Π½ΠΈΠ΅ сравнСния ΠΎΠ±ΡŠΠ΅ΠΊΡ‚ΠΎΠ² с ΠΎΠ΄ΠΈΠ½Π°ΠΊΠΎΠ²Ρ‹ΠΌ tzinfo.

Production-Π±Π°Π³

ΠŸΡ€Π΅Π΄ΡΡ‚Π°Π²ΠΈΠΌ, Ρ‡Ρ‚ΠΎ систСма удаляСт Π΄ΡƒΠ±Π»ΠΈΠΊΠ°Ρ‚Ρ‹ событий Ρ‡Π΅Ρ€Π΅Π· set:

events = {a, b}

print(len(events))
# 1

Π”Π²Π° Ρ€Π΅Π°Π»ΡŒΠ½Ρ‹Ρ… события ΠΏΡ€Π΅Π²Ρ€Π°Ρ‚ΠΈΠ»ΠΈΡΡŒ Π² ΠΎΠ΄Π½ΠΎ.

Π­Ρ‚ΠΎ ΠΌΠΎΠΆΠ΅Ρ‚ Π·Π°Ρ‚Ρ€ΠΎΠ½ΡƒΡ‚ΡŒ:

  • ΠΏΠ»Π°Ρ‚Π΅ΠΆΠΈ;
  • Π·Π°ΠΊΠ°Π·Ρ‹;
  • расписания;
  • бронирования;
  • Π»ΠΎΠ³ΠΈ;
  • Ρ„ΠΎΠ½ΠΎΠ²Ρ‹Π΅ Π·Π°Π΄Π°Ρ‡ΠΈ;
  • начислСния;
  • Π°Π½Π°Π»ΠΈΡ‚ΠΈΠΊΡƒ;
  • Π΄Π΅Π΄ΡƒΠΏΠ»ΠΈΠΊΠ°Ρ†ΠΈΡŽ сообщСний.

НапримСр, Π΄Π²Π° ΠΏΠ»Π°Ρ‚Π΅ΠΆΠ° ΠΏΡ€ΠΎΠΈΠ·ΠΎΡˆΠ»ΠΈ Π² Ρ€Π°Π·Π½Ρ‹Π΅ ΠΌΠΎΠΌΠ΅Π½Ρ‚Ρ‹, Π½ΠΎ ΠΏΠΎΠ»ΡƒΡ‡ΠΈΠ»ΠΈ ΠΎΠ΄ΠΈΠ½Π°ΠΊΠΎΠ²ΠΎΠ΅ локальноС врСмя. Если Π΄Π°Ρ‚Π° Π²Ρ…ΠΎΠ΄ΠΈΡ‚ Π² ΠΊΠ»ΡŽΡ‡ Π΄Π΅Π΄ΡƒΠΏΠ»ΠΈΠΊΠ°Ρ†ΠΈΠΈ, ΠΎΠ΄Π½Π° опСрация ΠΌΠΎΠΆΠ΅Ρ‚ Π±Ρ‹Ρ‚ΡŒ ΠΎΡˆΠΈΠ±ΠΎΡ‡Π½ΠΎ ΠΎΡ‚Π±Ρ€ΠΎΡˆΠ΅Π½Π°.

Как ΠΏΡ€Π°Π²ΠΈΠ»ΡŒΠ½ΠΎ ΡΡ€Π°Π²Π½ΠΈΠ²Π°Ρ‚ΡŒ ΠΌΠΎΠΌΠ΅Π½Ρ‚Ρ‹

ΠŸΠ΅Ρ€Π΅Π΄ сравнСниСм Π½ΡƒΠΆΠ½ΠΎ привСсти Π΄Π°Ρ‚Ρ‹ ΠΊ UTC:

from datetime import UTC

a_utc = a.astimezone(UTC)
b_utc = b.astimezone(UTC)

print(a_utc)
# 2026-10-25 00:30:00+00:00

print(b_utc)
# 2026-10-25 01:30:00+00:00

print(a_utc == b_utc)
# False

print(b_utc - a_utc)
# 1:00:00

Π’Π΅ΠΏΠ΅Ρ€ΡŒ Python сравниваСт Ρ€Π΅Π°Π»ΡŒΠ½Ρ‹Π΅ ΠΌΠΎΠΌΠ΅Π½Ρ‚Ρ‹:

00:30 UTC
01:30 UTC

Π Π°Π·Π½ΠΈΡ†Π°:

print((b_utc - a_utc).total_seconds())
# 3600.0

МоТно ΡΡ€Π°Π²Π½ΠΈΠ²Π°Ρ‚ΡŒ ΠΈ Unix timestamp:

a.timestamp() == b.timestamp()
# False

ВаТная ошибка

НСльзя ΠΏΠ΅Ρ€Π΅Π²ΠΎΠ΄ΠΈΡ‚ΡŒ локальноС врСмя Π² UTC Ρ‡Π΅Ρ€Π΅Π· ΠΏΡ€ΠΎΡΡ‚ΡƒΡŽ Π·Π°ΠΌΠ΅Π½Ρƒ tzinfo:

a.replace(tzinfo=UTC)

replace() мСняСт Ρ‚ΠΎΠ»ΡŒΠΊΠΎ ΠΌΠ΅Ρ‚ΠΊΡƒ часового пояса. Π‘Π°ΠΌΠΎ врСмя Π½Π΅ пСрСсчитываСтся.

Для настоящСго прСобразования ΠΈΡΠΏΠΎΠ»ΡŒΠ·ΡƒΠ΅Ρ‚ΡΡ:

a.astimezone(UTC)

ΠŸΡ€Π°ΠΊΡ‚ΠΈΡ‡Π΅ΡΠΊΠΎΠ΅ ΠΏΡ€Π°Π²ΠΈΠ»ΠΎ

Π’ Π±Π°Π·Π΅ ΠΈ ΠΊΠ»ΡŽΡ‡Π°Ρ… событий Ρ…Ρ€Π°Π½ΠΈΡ‚Π΅ ΠΌΠΎΠΌΠ΅Π½Ρ‚ Π² UTC:

created_at = local_time.astimezone(UTC)

Π›ΠΎΠΊΠ°Π»ΡŒΠ½Ρ‹ΠΉ часовой пояс ΠΈΡΠΏΠΎΠ»ΡŒΠ·ΡƒΠΉΡ‚Π΅ ΠΏΡ€ΠΈ ΠΎΡ‚ΠΎΠ±Ρ€Π°ΠΆΠ΅Π½ΠΈΠΈ ΠΏΠΎΠ»ΡŒΠ·ΠΎΠ²Π°Ρ‚Π΅Π»ΡŽ:

display_time = created_at.astimezone(
    ZoneInfo("Europe/Berlin")
)

Если бизнСс-Π»ΠΎΠ³ΠΈΠΊΠ° зависит ΠΎΡ‚ локального расписания, сохраняйтС ΠΎΡ‚Π΄Π΅Π»ΡŒΠ½ΠΎ:

UTC-ΠΌΠΎΠΌΠ΅Π½Ρ‚
Π½Π°Π·Π²Π°Π½ΠΈΠ΅ Π²Ρ€Π΅ΠΌΠ΅Π½Π½ΠΎΠΉ Π·ΠΎΠ½Ρ‹
локальноС врСмя
fold ΠΏΡ€ΠΈ нСобходимости

ΠœΠΈΠ½ΠΈΠΌΠ°Π»ΡŒΠ½Ρ‹ΠΉ тСст ΠΏΡ€ΠΎΡ‚ΠΈΠ² Ρ‚Π°ΠΊΠΎΠ³ΠΎ Π±Π°Π³Π°:

def test_repeated_local_time():
    tz = ZoneInfo("Europe/Berlin")

    first = datetime(
        2026, 10, 25, 2, 30,
        tzinfo=tz,
        fold=0,
    )

    second = first.replace(fold=1)

    first_utc = first.astimezone(UTC)
    second_utc = second.astimezone(UTC)

    assert first_utc != second_utc
    assert second_utc - first_utc == timedelta(hours=1)

ΠžΠ΄ΠΈΠ½Π°ΠΊΠΎΠ²Ρ‹Π΅ Ρ†ΠΈΡ„Ρ€Ρ‹ Π½Π° часах Π΅Ρ‰Ρ‘ Π½Π΅ ΠΎΠ·Π½Π°Ρ‡Π°ΡŽΡ‚ ΠΎΠ΄ΠΈΠ½ ΠΌΠΎΠΌΠ΅Π½Ρ‚ Π²Ρ€Π΅ΠΌΠ΅Π½ΠΈ.

https://docs.python.org/3/library/datetime.html
https://peps.python.org/pep-0495

t.me/ai_machinelearning_big_data

ΠŸΠΎΠ΄Π΅Π»ΠΈΡ‚ΡŒΡΡ:

ΠžΡ‚Π²Π΅Ρ‚ΠΈΡ‚ΡŒ

Π’Π°Ρˆ адрСс email Π½Π΅ Π±ΡƒΠ΄Π΅Ρ‚ ΠΎΠΏΡƒΠ±Π»ΠΈΠΊΠΎΠ²Π°Π½. ΠžΠ±ΡΠ·Π°Ρ‚Π΅Π»ΡŒΠ½Ρ‹Π΅ поля ΠΏΠΎΠΌΠ΅Ρ‡Π΅Π½Ρ‹ *