Pour convertir une date de publication et un fuseau horaire nommé en un instant précis correspondant à minuit, résolvez l’heure locale 00:00:00 selon les règles de ce fuseau, vérifiez qu’elle existe, puis convertissez-la en UTC. Cette procédure dans le Terminal du Mac détecte également les minuits répétés.

Supposons qu’une fiche de publication synthétique contienne la date 2025-07-15 et le fuseau America/New_York. Votre tâche consiste à calculer l’horodatage correspondant à minuit au début de cette date. La procédure ci-dessous produit un horodatage que vous pouvez copier dans un outil acceptant l’UTC, tout en gardant visibles la date et le fuseau d’origine pour vérification.

Quelles informations définissent un instant correspondant à minuit ?

Gardez ces quatre valeurs distinctes tout au long du calcul :

Valeur Exemple synthétique Signification
Date civile 2025-07-15 Une date du calendrier grégorien sans heure ni fuseau
Date et heure locales 2025-07-15T00:00:00 L’heure indiquée par l’horloge locale demandée
Fuseau horaire nommé America/New_York Les règles servant à interpréter cette heure locale
Instant résolu 2025-07-15T04:00:00Z Un moment précis exprimé en UTC

Python représente une date du calendrier avec date, combine les champs de date et d’heure avec datetime et utilise les informations de fuseau horaire pour interpréter un objet datetime avec fuseau. Sa classe timezone représente des décalages fixes ; zoneinfo fournit les règles des fuseaux nommés. Consultez la documentation officielle sur datetime et la documentation sur zoneinfo.

Un décalage tel que -04:00 est la différence entre l’heure locale et l’UTC pour une heure donnée. Un fuseau nommé fournit des règles qui peuvent produire des décalages différents selon les dates. La RFC 9557 explique pourquoi remplacer un fuseau nommé par un décalage fixe peut entraîner des calculs ultérieurs incorrects. Conservez le nom du fuseau au lieu de copier un décalage provenant d’une autre fiche de publication. RFC 9557 : Date et heure sur Internet : horodatages avec informations supplémentaires

Pour cette tâche, « minuit » signifie exactement 00:00:00 au début de la date fournie. Précisez cette exigence avant tout calcul : une demande portant sur le premier instant valide d’un jour civil nécessite une règle différente lorsque minuit n’existe pas.

Pourquoi faut-il vérifier minuit avant la conversion ?

Les changements d’heure locale peuvent créer un intervalle manquant, pendant lequel une heure indiquée par l’horloge n’existe pas, ou une plage répétée, pendant laquelle elle se produit deux fois. La PEP 495 de Python définit ces cas et fournit l’attribut fold pour distinguer les heures répétées. Elle indique également que les constructeurs et replace() ne rejettent pas automatiquement les heures locales inexistantes. Associer un fuseau ne suffit donc pas à valider le minuit demandé. PEP 495 – Désambiguïsation de l’heure locale

Appliquez la règle de décision suivante pour une exigence de publication à minuit précis :

Nombre d’instants valides correspondant à minuit Interprétation Action
Un Minuit est unique Renvoyer cet instant
Deux Minuit se répète Rejeter jusqu’au choix d’une règle « plus tôt » ou « plus tard »
Aucun Minuit précis n’existe pas Indiquer que l’exigence ne peut pas être satisfaite avec ces entrées

Le programme ci-dessous applique cette règle en essayant les deux valeurs de fold, en convertissant chaque candidat en UTC, puis en le reconvertissant dans le fuseau nommé. Un candidat n’est retenu que si les champs locaux obtenus correspondent exactement au minuit demandé. Les résultats UTC en double sont regroupés en un seul candidat. Cette séquence de validation découle du comportement documenté des conversions et des folds ; elle constitue une logique applicative supplémentaire. datetime.astimezone(), PEP 495

Comment exécuter le calcul dans le Terminal du Mac ?

Ouvrez Spotlight, saisissez Terminal, puis lancez l’application. Vous pouvez également ouvrir /Applications/Utilities dans le Finder et double-cliquer sur Terminal. Apple décrit ces deux méthodes pour ouvrir une fenêtre de commande. Ouvrir ou quitter Terminal sur Mac

Exécutez la commande suivante pour vérifier votre version de Python :

python3 --version

Cet exemple nécessite Python 3.9 ou une version ultérieure, où zoneinfo est devenu disponible. Si Python est absent ou plus ancien, suivez le guide officiel d’installation pour macOS afin d’obtenir sur Python.org un programme d’installation de Python actuellement pris en charge, puis vérifiez à nouveau la version. La procédure utilise des modules de la bibliothèque standard. zoneinfo — Prise en charge des fuseaux horaires IANA, Utiliser Python sur macOS

Copiez le bloc entier ci-dessous dans le Terminal et appuyez sur Return. Il s’agit d’une commande complète contenant le programme Python. Pour calculer un autre horodatage de publication, modifiez DATE_TEXT et ZONE_NAME avant de coller le bloc. Laissez REPEATED à reject, sauf si vous avez délibérément choisi une règle pour les minuits répétés.

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

Pour l’exemple fourni, le résultat est :

Date civile : 2025-07-15
Fuseau nommé : America/New_York
Minuit local : 2025-07-15T00:00:00-04:00
Instant UTC : 2025-07-15T04:00:00Z

Lisez ensemble les deux lignes d’horodatage. La ligne locale confirme que l’heure indiquée par l’horloge demandée est minuit. La ligne UTC identifie le même instant : soustraire le décalage local de quatre heures en moins donne 04:00:00 en UTC. La RFC 3339 définit un horodatage à partir d’une date, d’une heure et d’un décalage numérique ou de Z. RFC 3339 : Date et heure sur Internet : horodatages

Comment vérifier les résultats ordinaires et exceptionnels ?

Les entrées suivantes sont des exemples de publication synthétiques choisis pour mettre le programme à l’épreuve. Leurs résultats ont été calculés avec le résolveur ci-dessus et la base de données de fuseaux horaires disponible ; ils ne correspondent pas à de véritables publications. Modifiez les deux valeurs d’entrée et relancez la commande complète pour chaque cas.

Date Fuseau nommé Résultat du résolveur
2025-01-15 America/New_York Un instant : 2025-01-15T05:00:00Z
2025-07-15 America/New_York Un instant : 2025-07-15T04:00:00Z
2018-11-04 America/Sao_Paulo Minuit précis inexistant ; rejet
2020-11-01 America/Havana Minuit répété ; rejet par défaut
2011-12-30 Pacific/Apia Minuit précis inexistant ; rejet

Les deux premières lignes montrent pourquoi la date doit intervenir dans le calcul : le résolveur associe minuit à des heures UTC différentes dans un même fuseau nommé. Pour l’exemple de minuit répété à La Havane, définir REPEATED = 'earlier' sélectionne 2020-11-01T04:00:00Z ; le définir à later sélectionne 2020-11-01T05:00:00Z. Ces noms désignent l’ordre chronologique sur la ligne du temps UTC.

Un résultat indiquant que minuit n’existe pas exige de préciser l’exigence de publication. Si minuit exact est indispensable, choisissez explicitement une autre date ou un autre fuseau. Si l’exigence visée est le premier horaire valide de cette date, définissez et implémentez cette opération séparément, y compris la marche à suivre lorsque la date entière n’existe pas. Ce résolveur signale délibérément un échec au lieu de remplacer silencieusement l’heure locale par une autre.

Que faire lorsqu’une entrée échoue ?

En cas d’erreur de date, saisissez une date grégorienne réelle au format YYYY-MM-DD, par exemple 2025-07-15. Le programme vérifie la validité du calendrier ainsi que le respect exact de ce format. Une entrée comme 2025-02-30 échoue au lieu d’être reportée au mois suivant.

En cas de message Zone not found, vérifiez l’identifiant IANA complet, y compris son orthographe et ses majuscules. Python indique que cette exception peut signaler une clé invalide ou l’indisponibilité des données de fuseaux horaires. La recherche standard utilise d’abord les données du système, puis le paquet officiel tzdata comme solution de repli. Si les données sont absentes, installez ce paquet dans l’environnement Python utilisé, puis relancez le résolveur. PEP 615 – Prise en charge de la base de données des fuseaux horaires IANA dans la bibliothèque standard

Conservez ensemble, dans vos notes de travail, la date d’origine, le nom du fuseau, le choix pour les heures répétées et l’horodatage UTC résolu. Si les calculs diffèrent d’un ordinateur à l’autre, comparez leurs sources de données sur les fuseaux horaires. L’installation d’un paquet de repli ne remplace pas automatiquement les données système que Python trouve déjà. Sources des données de zoneinfo

Quelle valeur copier dans un autre outil ?

Si l’outil de destination accepte un horodatage UTC, copiez la valeur Instant UTC, avec son Z. Vérifiez le format d’entrée indiqué dans la documentation de l’outil avant de saisir la valeur. Un simple champ de date ne peut pas conserver toutes les informations représentées par cet horodatage résolu ; gardez la date d’origine et le fuseau nommé à côté si vous devez préserver l’intention de programmation.

La RFC 9557 définit un format d’horodatage étendu pouvant inclure un suffixe de fuseau nommé, mais ce format représente tout de même un instant résolu. Elle reconnaît également que les mises à jour des données de fuseaux peuvent créer des incohérences entre les décalages enregistrés et les informations de fuseau. N’utilisez une représentation étendue que si l’outil destinataire indique qu’il la prend en charge. RFC 9557, portée et décalages incohérents

Pour terminer, vérifiez que la sortie locale affiche la date prévue à 00:00:00, que le fuseau nommé correspond à votre entrée et que tout choix concernant un minuit répété a été fait délibérément. Une fois ces vérifications effectuées, la sortie UTC correspond à l’instant résolu de minuit selon les règles utilisées pour ce calcul.

Sources