Чтобы преобразовать дату публикации и название часового пояса в точный момент полуночи, определите местное время 00:00:00 по правилам этого часового пояса, проверьте, существует ли оно, а затем преобразуйте его в UTC. Этот рабочий процесс в Терминале Mac также обнаруживает повторяющуюся полночь.

Предположим, что синтетическая запись о публикации содержит дату 2025-07-15 и часовой пояс America/New_York. Вам нужно вычислить временную метку, обозначающую полночь в начале этой даты. Приведённый ниже процесс выдаёт временную метку, которую можно скопировать в инструмент, принимающий UTC, при этом исходные дата и часовой пояс остаются на виду для проверки.

Какие сведения определяют момент полуночи?

На протяжении всего вычисления храните отдельно следующие четыре значения:

Значение Синтетический пример Значение
Календарная дата 2025-07-15 Дата григорианского календаря без времени и часового пояса
Местные дата и время 2025-07-15T00:00:00 Требуемое показание местных часов
Название часового пояса America/New_York Правила для интерпретации этого местного показания
Определённый момент времени 2025-07-15T04:00:00Z Конкретный момент, выраженный в UTC

В Python календарную дату представляет date, поля даты и времени объединяет datetime, а сведения о часовом поясе используются для интерпретации объекта datetime с часовым поясом. Класс timezone задаёт фиксированные смещения, а zoneinfo предоставляет правила именованных часовых поясов. См. официальную документацию datetime и документацию zoneinfo.

Смещение, например -04:00, — это разница между местным временем и UTC для конкретного показания. Именованный часовой пояс задаёт правила, по которым в разные даты могут получаться разные смещения. В RFC 9557 объясняется, почему замена именованного часового пояса фиксированным смещением может привести к неверным последующим вычислениям. Сохраняйте название часового пояса, а не копируйте смещение из другой записи о публикации. RFC 9557: Дата и время в Интернете: временные метки с дополнительной информацией

В этой задаче «полночь» означает ровно 00:00:00 в начале указанной даты. До вычислений чётко сформулируйте это требование: если полночь отсутствует, для запроса о первом допустимом моменте календарного дня потребуется другая политика.

Почему нужно проверить полночь до преобразования?

Перевод часов может образовать разрыв, когда показание местных часов не встречается, или складку, когда оно встречается дважды. В PEP 495 для Python определены эти случаи и атрибут fold, позволяющий различать повторяющиеся показания. В документе также указано, что конструкторы и replace() автоматически не отклоняют отсутствующее местное время. Поэтому простое присоединение часового пояса само по себе не подтверждает, что запрошенная полночь существует. PEP 495 — Различение местного времени

Для требования получить точный момент полуночи публикации используйте следующее правило:

Найдено допустимых моментов полуночи Интерпретация Действие
Один Полночь уникальна Вернуть этот момент
Два Полночь повторяется Отклонить, пока не выбрана политика раннего или позднего момента
Ни одного Точный момент полуночи отсутствует Сообщить, что при этих входных данных требование выполнить нельзя

Приведённая ниже программа реализует это правило: пробует оба значения fold, преобразует каждый кандидат в UTC, а затем обратно в указанный часовой пояс. Кандидат остаётся в списке, только если возвращённые местные поля точно совпадают с запрошенной полуночью. Совпадающие результаты в UTC объединяются в один кандидат. Эта последовательность проверок основана на документированном поведении преобразования и fold; она является дополнительной логикой приложения. datetime.astimezone(), PEP 495

Как выполнить вычисление в Терминале Mac?

Откройте Spotlight, введите Terminal и запустите приложение. Также можно открыть /Applications/Utilities в Finder и дважды щёлкнуть Terminal. Это способы открыть окно команд, описанные Apple. Открытие и завершение работы Терминала на Mac

Выполните следующую команду, чтобы проверить версию Python:

python3 --version

Для этого примера требуется Python 3.9 или более поздняя версия, в которой появился zoneinfo. Если Python отсутствует или его версия устарела, следуйте официальному руководству по установке для macOS, чтобы загрузить актуальный установщик Python с Python.org, а затем повторите проверку версии. В этом процессе используются модули стандартной библиотеки. zoneinfo — поддержка часовых поясов IANA, Использование Python в macOS

Скопируйте весь блок ниже в Терминал и нажмите Return. Это полная команда с программой на Python. Чтобы вычислить другую временную метку публикации, перед вставкой отредактируйте DATE_TEXT и ZONE_NAME. Оставьте REPEATED равным reject, если только вы намеренно не выбрали политику обработки повторяющейся полуночи.

python3 - <<'PY'
from datetime import date, datetime, time, timezone
from zoneinfo import ZoneInfo, ZoneInfoNotFoundError

DATE_TEXT = '2025-07-15'
ZONE_NAME = 'America/New_York'
REPEATED = 'reject'  # 'reject', 'earlier', or 'later'


def resolve_midnight(date_text, zone_name, repeated='reject'):
    if repeated not in {'reject', 'earlier', 'later'}:
        raise ValueError('Repeated-time policy must be reject, earlier, or later.')

    day = date.fromisoformat(date_text)
    if day.isoformat() != date_text:
        raise ValueError('Use a date in YYYY-MM-DD format.')

    zone = ZoneInfo(zone_name)
    wall = datetime.combine(day, time.min)
    candidates = set()

    for fold in (0, 1):
        guess = wall.replace(tzinfo=zone, fold=fold)
        instant = guess.astimezone(timezone.utc)
        back = instant.astimezone(zone)
        if back.replace(tzinfo=None) == wall:
            candidates.add(instant)

    ordered = sorted(candidates)
    if not ordered:
        raise ValueError('Exact midnight does not exist on this date in this zone.')

    if len(ordered) == 2 and repeated == 'reject':
        choices = ', '.join(x.isoformat() for x in ordered)
        raise ValueError(
            'Midnight repeats. Choose earlier or later. UTC choices: ' + choices
        )

    chosen = ordered[-1] if repeated == 'later' else ordered[0]
    return chosen.astimezone(zone), chosen


try:
    local, utc = resolve_midnight(DATE_TEXT, ZONE_NAME, REPEATED)
except ZoneInfoNotFoundError:
    raise SystemExit(
        'Zone not found. Check the IANA name and time zone data installation.'
    )
except (ValueError, OverflowError) as exc:
    raise SystemExit(str(exc))

print('Civil date:', DATE_TEXT)
print('Named zone:', ZONE_NAME)
print('Local midnight:', local.isoformat())
print('UTC instant:', utc.isoformat().replace('+00:00', 'Z'))
PY

Для приведённого примера результат будет таким:

Григорианская дата: 2025-07-15
Название часового пояса: America/New_York
Местная полночь: 2025-07-15T00:00:00-04:00
Момент времени в UTC: 2025-07-15T04:00:00Z

Читайте две строки с временными метками вместе. Местная строка подтверждает, что запрошенное показание часов — полночь. Строка UTC указывает тот же момент: если вычесть местное смещение минус четыре часа, получится 04:00:00 UTC. В RFC 3339 временная метка определяется как дата, время и числовое смещение либо Z. RFC 3339: Дата и время в Интернете: временные метки

Как проверить обычные и исключительные результаты?

Ниже приведены синтетические примеры публикаций, выбранные для проверки программы. Результаты вычислены описанным выше средством разрешения с использованием доступной базы часовых поясов; это не записи о реальных публикациях. Измените оба входных значения и повторно выполните всю команду для каждого случая.

Дата Название часового пояса Результат
2025-01-15 America/New_York Один момент: 2025-01-15T05:00:00Z
2025-07-15 America/New_York Один момент: 2025-07-15T04:00:00Z
2018-11-04 America/Sao_Paulo Точный момент полуночи отсутствует; отклонено
2020-11-01 America/Havana Полночь повторяется; по умолчанию отклонено
2011-12-30 Pacific/Apia Точный момент полуночи отсутствует; отклонено

Первые две строки показывают, почему дата участвует в вычислении: в одном и том же именованном часовом поясе полночь соответствует разным часам UTC. Для примера с повторяющейся полуночью в Гаване значение REPEATED = 'earlier' выбирает 2020-11-01T04:00:00Z, а значение later — 2020-11-01T05:00:00Z. Названия указывают хронологический порядок на временной шкале UTC.

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

Что делать, если входные данные не проходят проверку?

При ошибке даты укажите реальную дату григорианского календаря в формате YYYY-MM-DD, например 2025-07-15. Программа проверяет календарную корректность и точное соответствие этому формату. Ввод вроде 2025-02-30 приводит к ошибке, а не переносится на другой месяц.

Если появляется сообщение Zone not found, проверьте полный идентификатор IANA, включая правописание и регистр. Согласно документации Python, это исключение может означать как неверный ключ, так и отсутствие данных часовых поясов. Стандартный поиск сначала использует системные данные, а затем — пакет tzdata от первой стороны. Если данных нет, установите этот пакет в используемую среду Python, а затем повторно запустите средство разрешения. PEP 615 — Поддержка базы часовых поясов IANA в стандартной библиотеке

Сохраняйте исходные дату, название часового пояса, выбранный вариант повторяющегося времени и определённую временную метку UTC вместе в рабочих заметках. Если вычисления на разных компьютерах расходятся, сравните источники их данных о часовых поясах. Установка резервного пакета автоматически не заменяет системные данные, которые Python уже обнаруживает. Источники данных zoneinfo

Какое значение копировать в другой инструмент?

Если целевой инструмент принимает временную метку UTC, скопируйте значение Момент времени в UTC вместе с Z. Перед вводом проверьте документированный формат целевого инструмента. Простое поле даты не сохраняет всю информацию, представленную этой определённой временной меткой; чтобы сохранить намерение при планировании, храните рядом исходную дату и название часового пояса.

В RFC 9557 определён расширенный формат временной метки, который может включать суффикс с названием часового пояса, однако и этот формат обозначает уже определённый момент времени. В нём также признаётся, что обновления данных часовых поясов могут создавать несоответствия между сохранёнными смещениями и сведениями о часовом поясе. Используйте расширенный формат, только если целевой инструмент явно заявляет о его поддержке. RFC 9557: область применения и несогласованные смещения

В завершение проверьте, что в местном выводе указаны нужная дата и время 00:00:00, название часового пояса совпадает с входным, а выбранный вариант повторяющейся полуночи был намеренным. После этих проверок вывод UTC представляет определённый момент полуночи по правилам, использованным при вычислении.

Источники