公開日と名前付きタイムゾーンから正確な午前0時の時点を求めるには、そのタイムゾーンの規則に従って現地時刻の00:00:00を解決し、その時刻が存在することを確認してからUTCに変換します。このMacのターミナルを使う手順では、午前0時が繰り返される場合も検出します。

架空の公開記録に日付2025-07-15とタイムゾーンAmerica/New_Yorkが含まれているとします。目的は、その日付の始まりの午前0時を表すタイムスタンプを計算することです。以下の手順では、UTCを受け付けるツールにコピーできるタイムスタンプを出力し、確認できるよう元の日付とタイムゾーンも表示します。

午前0時の時点を定義する情報は何ですか?

計算中は、次の4つの値を別々に扱います。

値 架空の例 意味
暦日 2025-07-15 時刻やタイムゾーンを含まないグレゴリオ暦の日付
現地の日付と時刻 2025-07-15T00:00:00 指定された壁時計上の時刻
名前付きタイムゾーン America/New_York 現地時刻の解釈に使う規則
解決された時点 2025-07-15T04:00:00Z UTCで表された特定の瞬間

Pythonでは、暦日をdateで表し、日付と時刻のフィールドをdatetimeで組み合わせ、タイムゾーン情報を使ってタイムゾーン対応の日時を解釈します。timezoneクラスは固定オフセットを表し、zoneinfoは名前付きタイムゾーンの規則を提供します。公式のdatetimeドキュメントとzoneinfoドキュメントを参照してください。

-04:00のようなオフセットは、特定の時刻における現地時刻とUTCの差です。名前付きタイムゾーンは、日付によって異なるオフセットを返す規則を提供します。RFC 9557では、名前付きタイムゾーンを固定オフセットで置き換えると、その後の計算が誤る可能性がある理由を説明しています。別の公開記録からオフセットをコピーせず、タイムゾーン名を保持してください。RFC 9557: インターネット上の日付と時刻:追加情報付きタイムスタンプ

このタスクでの「午前0時」とは、指定された日付の始まりにある正確な00:00:00を意味します。計算を始める前に、この要件を明確にしてください。午前0時が存在しない場合、暦日の最初の有効な時刻を求めるには別の方針が必要です。

変換の前に午前0時を確認する必要があるのはなぜですか?

現地時刻の変更によって、壁時計上の時刻が存在しない「ギャップ」や、同じ時刻が2回現れる「フォールド」が生じることがあります。PythonのPEP 495ではこれらのケースを定義し、重複する時刻を区別するためのfold属性を提供しています。また、コンストラクターやreplace()は、存在しない現地時刻を自動的に拒否しないことも説明されています。そのため、タイムゾーンを割り当てただけでは、指定した午前0時が有効かどうかは確認できません。PEP 495 – 現地時刻の曖昧さの解消

正確な午前0時を求める公開要件には、次の判断基準を使います。

見つかった有効な午前0時の時点 解釈 対応
1つ 午前0時は一意 その時点を返す
2つ 午前0時が繰り返される 先か後かの方針が選ばれるまで拒否する
なし 正確な午前0時が存在しない その入力では要件を満たせないと報告する

以下のプログラムは、両方のfold値を試し、各候補をUTCに変換してから名前付きタイムゾーンに戻すことで、この規則を実装しています。戻した現地時刻の各フィールドが指定した午前0時と完全に一致した場合にのみ、その候補は有効です。重複するUTCの結果は1つにまとめられます。この検証手順は、文書化されている変換とfoldの動作に基づいており、追加のアプリケーションロジックです。datetime.astimezone()、PEP 495

Macのターミナルで計算を実行するには?

Spotlightを開き、「Terminal」と入力してアプリを開きます。または、Finderで/Applications/Utilitiesを開き、Terminalをダブルクリックします。これは、コマンドウインドウを開くためにAppleが案内している方法です。Macでターミナルを開く/終了する

次のコマンドを実行して、Pythonのバージョンを確認します。

python3 --version

この例には、zoneinfoが利用可能になったPython 3.9以降が必要です。Pythonがない場合やバージョンが古い場合は、公式のmacOSインストールガイドに従ってPython.orgから現在サポートされているPythonインストーラーを入手し、バージョンを再確認してください。この手順では標準ライブラリのモジュールを使います。zoneinfo — IANAタイムゾーンのサポート、macOSでPythonを使う

以下のブロック全体をターミナルにコピーして、Returnを押します。これはPythonプログラムを含む完全なコマンドです。別の公開タイムスタンプを計算するには、貼り付ける前にDATE_TEXTとZONE_NAMEを編集します。午前0時が繰り返される場合の方針を意図的に選んでいない限り、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

指定したサンプルでは、次のように出力されます。

Civil date: 2025-07-15
Named zone: America/New_York
Local midnight: 2025-07-15T00:00:00-04:00
UTC instant: 2025-07-15T04:00:00Z

2つのタイムスタンプ行を合わせて確認してください。現地時刻の行で、指定した壁時計上の時刻が午前0時であることを確認できます。UTCの行は同じ時点を示しています。現地オフセットのマイナス4時間を差し引くと、UTCの04:00:00になります。RFC 3339では、日付、時刻、数値オフセットまたはZを使ったタイムスタンプが定義されています。RFC 3339: インターネット上の日付と時刻:タイムスタンプ

通常の結果と例外的な結果を確認するには?

以下の入力は、プログラムの動作を確認するために選んだ架空の公開例です。結果は利用可能なタイムゾーンデータベースを使い、上記のリゾルバーで計算したもので、実際の公開記録ではありません。それぞれのケースで2つの入力値を変更し、コマンド全体を再実行してください。

日付 名前付きタイムゾーン リゾルバーの結果
2025-01-15 America/New_York 1つの時点:2025-01-15T05:00:00Z
2025-07-15 America/New_York 1つの時点:2025-07-15T04:00:00Z
2018-11-04 America/Sao_Paulo 正確な午前0時が存在しないため拒否
2020-11-01 America/Havana 午前0時が繰り返されるため、既定では拒否
2011-12-30 Pacific/Apia 正確な午前0時が存在しないため拒否

最初の2行は、日付を計算に含める必要性を示しています。同じ名前付きタイムゾーンでも、午前0時は異なるUTCの時刻に解決されます。ハバナの重複例では、REPEATED = 'earlier'に設定すると2020-11-01T04:00:00Zが選ばれ、laterに設定すると2020-11-01T05:00:00Zが選ばれます。これらの名称は、UTCの時間軸上での前後関係を表しています。

午前0時が存在しない場合は、公開要件について判断が必要です。正確な午前0時が不可欠なら、別の日付またはタイムゾーンを明示的に選んでください。要件がその日の最初の有効な時刻を意味するなら、その操作を別途定義して実装し、日付全体が存在しない場合の扱いも決めてください。このリゾルバーは、別の現地時刻に黙って置き換えるのではなく、意図的に失敗を報告します。

入力が失敗した場合はどうすればよいですか?

日付エラーが出た場合は、2025-07-15のような、YYYY-MM-DD形式の有効なグレゴリオ暦の日付を指定してください。プログラムは、暦上の日付として有効かどうかと、指定形式に正確に一致するかどうかの両方を確認します。2025-02-30のような入力は、別の月に繰り越されず、エラーになります。

Zone not foundが表示された場合は、綴りと大文字・小文字を含め、IANA識別子全体を確認してください。Pythonの説明によると、この例外はキーが無効な場合と、タイムゾーンデータを利用できない場合のどちらでも発生します。標準の検索では、まずシステムデータを使い、次に公式のtzdataパッケージをフォールバックとして使います。データがない場合は、使用しているPython環境にそのパッケージをインストールしてから、リゾルバーを再実行してください。PEP 615 – 標準ライブラリでのIANAタイムゾーンデータベースのサポート

作業メモには、元の日付、タイムゾーン名、重複時刻の選択、解決されたUTCタイムスタンプをまとめて記録してください。コンピューター間で計算結果が異なる場合は、それぞれが使っているタイムゾーンデータの入手元を比較してください。フォールバックパッケージをインストールしても、Pythonがすでに認識しているシステムデータが自動的に置き換わるわけではありません。zoneinfoのデータソース

別のツールにコピーする値はどれですか?

コピー先がUTCタイムスタンプを受け付ける場合は、Zを含めてUTC instantの値をコピーしてください。入力する前に、コピー先のドキュメントで入力形式を確認してください。日付だけのフィールドでは、解決されたタイムスタンプが表す情報をすべて保持できません。スケジュールの意図を維持する必要がある場合は、元の日付と名前付きタイムゾーンも併せて記録してください。

RFC 9557では、名前付きタイムゾーンのサフィックスを含められる拡張タイムスタンプ形式が定義されていますが、その形式でも解決済みの時点を表します。また、タイムゾーンデータの更新によって、保存されたオフセットとタイムゾーン情報の間に不整合が生じる可能性も認めています。受信側のツールが対応を明記している場合にのみ、拡張形式を使ってください。RFC 9557:適用範囲と不整合なオフセット

最後に、現地時刻の出力が意図した日付の00:00:00を示していること、名前付きタイムゾーンが入力と一致していること、午前0時が繰り返される場合の選択が意図的であることを確認してください。これらの確認が済めば、UTCの出力が、その計算で使った規則に基づく午前0時の解決済みの時点です。

参考資料