60db Logo
  • Guides

Dates and times across locales and time zones

Resolve explicit dates, wall times, IANA zones, gaps, and folds with UTC round-trip checks and reproducible Python fixtures.

Editorial date
Reading time
25 min
Normal, gap, and fold clock motifs distinguish local-time mapping outcomes

TL;DR: Resolve a complete calendar date, explicit time, and IANA time zone before choosing an instant. A local time can identify one instant, no instant during a gap, or two during a fold. Validate by UTC round trip, deduplicate by UTC identity, and ask for an explicit occurrence when the time repeats.


A caller asks for a morning appointment. The agent repeats the date correctly, writes a local timestamp, and receives a successful response from its parser. None of those facts establishes which instant the appointment represents. The date order, missing time components, calendar, zone, or repeated-hour occurrence may still be unresolved.

The first useful scheduling result is an unambiguous candidate with its interpretation evidence. The Nepali-English scheduling guide discusses how conversational date and time context can cross language boundaries. This article focuses on the subsequent public engineering contract: turn a fully specified Gregorian wall time into validated UTC candidates without inventing missing context.

The reference uses Python's standard library and available system time-zone files. It does not parse arbitrary date phrases, query calendars, reserve appointments, infer a caller's location, or measure live scheduling accuracy. All dates in its fixtures are synthetic test inputs, and its printed report explicitly states that no booking occurred.

Table of contents

  1. Separate locale, calendar, zone, and instant
  2. Require every material time component
  3. Understand normal times, gaps, and folds
  4. Validate a wall time by UTC round trip
  5. Deduplicate by UTC identity
  6. Run the complete wall-time reference
  7. Read the fixture boundaries
  8. Keep time-zone data provenance visible
  9. Confirm the selected occurrence
  10. Preserve interpretation across adapters
  11. Build a scheduling acceptance matrix
  12. Separate spoken delivery from approval
  13. Test the actual adapter interpretation
  14. Frequently asked questions
  15. Ship a verified time candidate

Separate locale, calendar, zone, and instant

A locale describes conventions used to present or interpret text. A calendar defines how dates are organized. A time zone supplies civil-time rules for a location. An instant identifies a point on the timeline. These concepts interact, but one must not silently stand in for another.

CLDR's date-formatting specification describes locale-sensitive date and time patterns and calendar-related presentation. It does not make a language tag a reliable description of the caller's current time zone. A person can speak one language, use another locale's date order, and schedule an appointment in a third region.

BCP 47 language tags identify language information and related subtags. They are not a substitute for a selected IANA zone. Even a region subtag cannot generally identify a single applicable civil-time rule for the intended appointment location.

The language hints and autodetection guide makes a related distinction: evidence about speech language should not become authority for unrelated fields. Keep the scheduling zone explicit even when the application already knows which language the caller prefers.

Likewise, a telephone calling code is not the appointment's location. The phone-number capture guide explains why dialing context itself has limits. A caller may be traveling, using a shared phone, or booking for a different person or location.

An authoritative scheduling candidate should therefore retain the resolved wall date and time, selected zone, chosen occurrence where necessary, and the resulting instant. Keep the original spoken surface separately if the application's retention policy permits it. A localized display can then be regenerated without overwriting the interpretation that was actually confirmed.

Calendar date, explicit time, IANA zone, and repeated-time choice remain separate fields

Require every material time component

The reference accepts exactly a Gregorian date from 2000 through 2099 and a time down to seconds, written in a deliberately narrow ASCII form. Date-only input is rejected. A time missing seconds is rejected. Those choices prevent a permissive parser from quietly filling components that the caller or upstream contract never supplied.

That does not mean every scheduling conversation must ask callers to speak seconds. A workflow may explicitly define a minute-level appointment contract and intentionally supply zero seconds. The material requirement is that the interpretation layer owns that rule and records it. The parser should not invent midnight merely because it received a date.

Python's datetime documentation distinguishes date, time, naive datetime, and aware datetime objects. Its parsing APIs can accept useful representations beyond this reference's subset. The reference uses an explicit grammar before construction so its supported input surface remains visible and testable.

An ambiguous numeric date requires its own repair. Ask which month and day the caller intends or repeat the date with a clear month name under the selected calendar. Do not assume that a slash-separated date follows the application's server locale or the operator's preferred convention.

The Arabic digits, dates, and currencies guide addresses localized number surfaces. Translating digit characters does not resolve date order, calendar identity, or zone. Those are separate interpretation decisions even when a normalized string looks like a familiar machine date.

Relative phrases need reference context. Resolving "tomorrow" requires a reference instant and the civil-time zone in which tomorrow is meant. A long-running call can cross midnight, and a scheduling destination may have a different calendar day from the caller. This artifact deliberately accepts only already resolved absolute components.

The partial-transcript guide is relevant when those components arrive incrementally. An unfinished month or a revised time is not a completed scheduling instruction. Preserve the current candidate's material fields and wait for the input ownership and confirmation contract to establish a usable value.

Understand normal times, gaps, and folds

Most wall times under a selected zone map to one instant. During some offset transitions, a wall interval is skipped; a time inside that interval has no corresponding instant. During another kind of transition, a wall interval repeats; a time inside it can correspond to two distinct instants.

PEP 495 defines the fold attribute used to distinguish the two readings of an ambiguous local time. It also explains that constructors do not enforce strict validity of every local-time combination. Creating a datetime object with a zone attached does not prove that the wall time exists.

The reference calls those classifications normal, gap, and fold. They describe the mapping under the selected zone data, not whether a calendar slot is available or the caller has approved it. A normal time may still be outside business hours or unavailable; those checks belong to a separate scheduling contract.

Do not assume every transition changes the clock by exactly one hour. Historical and regional rules can differ. An algorithm that tests only a one-hour neighbor risks confusing a convenient test vector with a universal property. The reference discovers candidates through the zone's actual conversion behavior instead of adding a fixed interval.

The Python zoneinfo documentation describes IANA-zone support and fold behavior. The library provides conversion rules; the application still has to decide what to do when a caller selects a nonexistent or repeated wall time. That decision should be explicit, rather than hidden in a generic timestamp parser.

For a gap, this reference refuses to select an instant and requires a new value. It does not automatically push the appointment forward. For a fold, it returns both distinct candidate instants and requires an earlier or later occurrence choice. A normal time yields exactly one candidate after deduplication.

A normal time has one UTC candidate, a gap none, and a fold two distinct candidates

Validate a wall time by UTC round trip

Start with the exact naive wall components and the explicit zone. Construct candidate interpretations using both fold values. Convert each interpretation to UTC, convert that UTC instant back to the selected zone, and compare the returned wall components with the original request.

A candidate survives only if the round trip returns the requested wall date and time. A gap interpretation typically returns a different wall time, revealing that the original local components did not identify a real instant under the selected rules. Do not treat the mere existence of the intermediate object as validation.

This comparison deliberately removes time-zone metadata from the returned wall value before checking the original components. It asks whether the actual year, month, day, hour, minute, and second match. The resulting UTC identity is handled separately so an equality convenience cannot erase an ambiguous occurrence.

A real developer question about datetime equality demonstrates how gap and fold behavior can surprise an application. The question is historical evidence of a developer problem, not a survey of current failure frequency. Use the language documentation and explicit fixtures to establish this reference's behavior.

The algorithm does not inspect the operating system's current local zone or the current clock. Its mapping depends only on the supplied wall components and selected zone data. This makes the test reproducible without pretending that it understands an unrestricted phrase such as next Friday afternoon.

The regression-testing guide suggests why this boundary deserves its own tests. A conversation test may produce the expected date string while a later conversion silently shifts it. Keeping the conversion fixture independent exposes that defect before it is disguised as a speech-recognition problem.

Deduplicate by UTC identity

Trying both fold values does not mean every wall time has two distinct readings. Outside a repeated interval, both candidates can resolve to the same instant. Deduplicate those results by their UTC identity so ordinary times remain normal rather than appearing spuriously ambiguous.

The reference uses normalized UTC ISO strings as identity keys. All candidates use the same UTC representation and second-level resolution, so distinct keys identify distinct instants within this contract. Sorting those keys orders the candidates by instant and gives earlier and later selection an explicit meaning.

Avoid deduplicating the two same-zone wall datetime objects directly. Python's equality rules around fold can differ from an application's intuitive expectation of instant identity. The published fold fixture asserts distinct UTC results explicitly, protecting against an implementation that collapses both occurrences into one local-looking object.

This distinction resembles identity boundaries elsewhere in the series. The DTMF and voice capture guide separates a repeated transport event from a genuinely new input event. Here the identity being preserved is the instant, rather than the visible wall label.

Retain the selected zone and wall components even after choosing a UTC instant. They explain what the caller was told and how the instant was derived. UTC is valuable for timeline identity, but a stored UTC string alone cannot reproduce the caller's intended civil-time interpretation if that evidence is discarded.

Run the complete wall-time reference

Run the full fence with a Python installation that has the named system TZPATH files. The code loads the exact file bytes itself, constructs the zone from those bytes, and reports their SHA256 digest. It intentionally does not install packages or silently fall back to the server's local zone when data is missing.

The standard library may normally use system data or a first-party tzdata package. This smaller artifact chooses only the system-file route so the loaded data's identity is directly observable. A machine without the required files receives an explicit unavailable-data error and needs a documented data-provisioning route before these fixtures can pass.

from dataclasses import dataclass, FrozenInstanceError
from datetime import datetime, timezone
from hashlib import sha256
from io import BytesIO
from pathlib import Path
import json
import re
from zoneinfo import ZoneInfo, TZPATH

@dataclass(frozen=True)
class Candidate:
    utc: str
    local: str
    fold: int

@dataclass(frozen=True)
class Resolution:
    wall: str
    zone: str
    tzif_sha256: str
    classification: str
    candidates: tuple

def load_zone(key):
    if (not isinstance(key, str) or len(key) > 100 or
            not re.fullmatch(r'[A-Za-z_+-]+(?:/[A-Za-z0-9_+-]+)*', key)):
        raise ValueError('supported IANA key required')
    for root in TZPATH:
        path = Path(root).joinpath(*key.split('/'))
        if path.is_file():
            data = path.read_bytes()
            return ZoneInfo.from_file(BytesIO(data), key=key), sha256(data).hexdigest()
    raise ValueError('zone data unavailable in system TZPATH')

def resolve_wall(wall, key):
    if (not isinstance(wall, str) or
            not re.fullmatch(r'[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}', wall)):
        raise ValueError('explicit Gregorian date and time to seconds required')
    try:
        naive = datetime.fromisoformat(wall)
    except ValueError as error:
        raise ValueError('invalid calendar components') from error
    if not 2000 <= naive.year <= 2099:
        raise ValueError('reference supports years 2000 through 2099')
    zone, digest = load_zone(key)
    by_utc = {}
    for fold in (0, 1):
        proposed = naive.replace(tzinfo=zone, fold=fold)
        utc = proposed.astimezone(timezone.utc)
        back = utc.astimezone(zone)
        if back.replace(tzinfo=None) == naive:
            identity = utc.isoformat()
            by_utc[identity] = Candidate(identity, back.isoformat(), back.fold)
    candidates = tuple(by_utc[k] for k in sorted(by_utc))
    classification = {0: 'gap', 1: 'normal', 2: 'fold'}[len(candidates)]
    return Resolution(wall, key, digest, classification, candidates)

def select_instant(resolution, occurrence=None):
    if occurrence not in (None, 'earlier', 'later'):
        raise ValueError('unsupported occurrence choice')
    if resolution.classification == 'gap':
        raise ValueError('nonexistent wall time; ask for a new value')
    if resolution.classification == 'fold':
        if occurrence is None:
            raise ValueError('explicit repeated-time choice required')
        return resolution.candidates[0 if occurrence == 'earlier' else -1]
    return resolution.candidates[0]

def rejects(wall, zone):
    try:
        resolve_wall(wall, zone)
    except ValueError:
        return
    raise AssertionError(('unexpected acceptance', wall, zone))

normal = resolve_wall('2026-02-10T12:00:00', 'America/New_York')
gap = resolve_wall('2026-03-08T02:30:00', 'America/New_York')
fold = resolve_wall('2026-11-01T01:30:00', 'America/New_York')
assert normal.classification == 'normal' and len(normal.candidates) == 1
assert select_instant(normal).utc == '2026-02-10T17:00:00+00:00'
assert gap.classification == 'gap' and not gap.candidates
assert fold.classification == 'fold' and len(fold.candidates) == 2
assert select_instant(fold, 'earlier').utc == '2026-11-01T05:30:00+00:00'
assert select_instant(fold, 'later').utc == '2026-11-01T06:30:00+00:00'
assert fold.candidates[0].utc != fold.candidates[1].utc
assert resolve_wall('2026-11-01T01:30:00', 'Asia/Kolkata').classification == 'normal'
for unresolved in (gap, fold):
    try:
        select_instant(unresolved)
    except ValueError:
        pass
    else:
        raise AssertionError('unresolved time selected')
for bad in (None, True, '', '2026-02-10', '2026-02-10T12:00',
            '2026-02-10T12:00:00Z', '03/04/2026 12:00:00',
            '2026-02-30T12:00:00', '2026-02-10T24:00:00',
            '2026-02-10T12:00:60', '1999-12-31T12:00:00',
            '2100-01-01T12:00:00'):
    rejects(bad, 'America/New_York')
for bad_zone in (None, True, '', '../UTC', 'Not_A_Real_Zone'):
    rejects('2026-02-10T12:00:00', bad_zone)
try:
    fold.candidates += (normal.candidates[0],)
except FrozenInstanceError:
    pass
else:
    raise AssertionError('mutable resolution')
print(json.dumps({'fixture_class': 'Synthetic', 'zone': fold.zone,
                  'loaded_tzif_sha256': fold.tzif_sha256,
                  'normal': normal.classification, 'gap': gap.classification,
                  'fold': fold.classification,
                  'candidate_utc_instants': [c.utc for c in fold.candidates],
                  'booking': 'not-performed'}))

The output is computed by the published code. It includes the classifications and both candidate UTC instants for the synthetic repeated-time fixture, plus the digest of the actually loaded New York TZif file. That digest identifies file content; it is not a fabricated global time-zone release label.

The helper select_instant refuses a gap and refuses a fold without an explicit occurrence. It performs no calendar booking. Its normal-time branch returns the single candidate already validated by the resolver. A caller-facing controller must still approve the current interpretation and satisfy the consuming calendar's own requirements.

Read the fixture boundaries

The normal fixture proves that testing both fold settings does not create a false ambiguity. The gap fixture proves that construction alone is insufficient. The fold fixture proves that two distinct UTC identities survive and that explicit earlier and later choices select different instants.

The date-only and missing-seconds fixtures protect against implicit component filling. The offset-suffixed fixture protects this specific wall-time API: an already offset-bearing timestamp belongs to a different input contract. Rejecting that representation here does not mean it is globally invalid or unsuitable for another API.

The invalid-calendar fixtures include a nonexistent day, an unsupported hour, and a leap-second spelling outside Python datetime's supported second range. These are explicit limitations of this reference. It does not claim leap-second processing or replace an astronomical time standard with a civil scheduling helper.

The zone fixtures reject missing metadata, booleans, empty keys, path traversal spelling, and an unavailable zone. None becomes an empty candidate list. Empty candidates mean a validated mapping was classified as a gap; unavailable rule data is a different failure and must remain distinguishable.

The voice-agent outcome metrics guide explains why those distinctions matter in reporting. A missing-zone-data attempt must not inflate the count of caller-selected gap times. A parser rejection must not disappear from the ledger simply because it never reached a booking adapter.

The immutable-resolution fixture prevents ordinary assignment from changing the returned candidate tuple. Each candidate is itself a frozen record with scalar fields. A consuming formatter can derive a display from that snapshot without mutating the time that another component is about to confirm.

Missing rule data, unsupported input, a gap, and a fold require different explicit actions

Keep time-zone data provenance visible

IANA's time-zone database page explains that civil-time rules change through political decisions and that the database is updated periodically. Future scheduling depends on those rules. A test run should therefore identify the actual data used rather than assuming every host shares the same release.

PEP 615 describes the standard-library zoneinfo design and its data sources. A production deployment needs an explicit update and provenance policy. The reference's SHA digest is a small reproducibility mechanism for the one file it loads, not a replacement for that operational policy.

Keep a distinction between preserving an already confirmed instant and preserving a future local-time intention. Different products may define different behavior when zone rules change. The decision belongs to the scheduling contract and should be explained to the caller where material; this artifact does not silently reschedule persisted appointments.

The cache-key guide illustrates why material policy inputs belong in identity. A cached conversion produced under one time-zone file should not be presented as freshly verified under a different file without checking the contract. Include the rule-data identity in evidence used for reproducibility.

Do not label a digest with an inferred version. Distribution vendors can package data differently or apply updates independently. If a deployment records a release label, obtain it from its actual provisioning evidence. The published report sticks to the bytes it loaded and avoids claiming more than it can demonstrate.

Missing time-zone data is an operational limitation, not caller ambiguity. Route it to a visible unavailable state and a documented support path. Asking the caller to repeat a perfectly clear appointment time will not repair a host that lacks the required rule file.

Confirm the selected occurrence

A repeated time requires a meaningful distinction in the repeat-back. Present enough context to distinguish the earlier and later occurrence, including their applicable offsets or a clear description under the selected zone. Do not ask the caller to approve two identical wall labels and then choose one invisibly.

The confirmation prosody guide discusses the audible side of this boundary. A correct structured instant can still be presented unclearly. Test generated speech and the full caller reply separately from the resolver's deterministic assertions.

The yes-but repair guide applies when the caller approves the date but corrects the time or zone. Such a reply is a repair to the material candidate. It cannot confirm the original instant merely because the first word sounded affirmative.

A zone correction invalidates approval even when the wall date and time stay unchanged. An occurrence correction during a fold also changes the instant. A display-language change may leave the underlying instant unchanged, but the controller still needs to preserve which material candidate was actually presented and approved.

The self-correction guide explains how later input can replace an earlier interpretation. Keep the correction tied to a candidate revision. The resolver itself is pure with respect to conversation state; it returns a mapping and cannot decide which competing recognition result currently owns the input.

If a controller awaits a calendar lookup or a synthesized repeat-back, it must recheck the current candidate after the await. A late response for an older zone or occurrence cannot restore that candidate's authority. The interruption-safety guide describes the ownership principle that prevents late work from reviving canceled speech.

The reference does not implement a deadline or asynchronous confirmation state machine. A deployment adding those mechanisms must enforce expiry before correction and on policy queries, distinguish a new request from an exact retry, and preserve terminal states. Do not assume those guarantees are supplied by datetime conversion.

Preserve interpretation across adapters

A booking payload should carry the fields required by its actual API: perhaps an instant, perhaps a local datetime and zone, perhaps an explicit offset. Read that contract before translating the resolver's result. Two APIs accepting ISO-looking text may still interpret it differently.

RFC 8259 defines JSON values but no built-in date or time-zone type. A JSON string transports a spelling; the schema and adapter must define its temporal meaning. Preserve the selected interpretation explicitly instead of relying on the receiver's server locale.

The workflow-enforcement guide is relevant at the booking boundary. A valid candidate is not a confirmed appointment. Availability, authorization, idempotent operation identity, and the actual remote result remain separate requirements under the consuming workflow.

Retain enough evidence to explain the booking request without confusing proposal and result. A locally selected instant can be ready for an adapter while the remote calendar later rejects it. A timeout can leave the remote outcome unknown. The truthful tool-waiting guide explains why spoken progress must respect that evidence boundary.

An exact retry should preserve its request identity and frozen material payload. A caller's new time or zone is a new semantic request and must not be disguised as a retry of the old booking. This article performs no side effect, so those guarantees require separate adapter fixtures rather than an invented success flag in its output.

Build a scheduling acceptance matrix

Start with the fields your workflow actually promises to resolve. Use a matrix to distinguish supported mapping from unsupported interpretation and untested booking behavior. The table describes this reference's decisions, not a universal user-interface policy for every scheduling application.

Criterion Supported boundary ✓ Unsupported shortcut ✗
Date interpretation Explicit Gregorian components Infer slash order from server locale
Time components Complete declared precision Turn a date into midnight silently
Zone Explicit supported IANA key Infer from telephone country
Normal time One deduplicated UTC candidate Count both fold trials as separate instants
Gap Ask for a new wall time Shift forward without a declared rule
Fold Explicit earlier or later occurrence Choose the first construction silently
Identity UTC candidate identity Collapse same-zone wall objects
Data unavailable Explicit operational failure Return a misleading empty result
Approval Current material interpretation Reuse approval after a zone correction

Extend the fixture with the zones and date ranges your product supports. The synthetic New York vectors are useful regression cases, not coverage of every historical transition or future political change. The Kolkata normal-time case confirms that the same wall spelling can have a different mapping under another explicit zone.

Record the data source used for each extension. If your production route uses a packaged tzdata dependency instead of system TZPATH files, test and document that route directly. Do not claim this system-file harness proves a different dependency's installed version or update behavior.

The observability guide can help organize the evidence when a caller reports a wrong appointment. Identify the original requested components, resolved interpretation, zone-data identity, selected instant, approval revision, submitted payload, and remote result under the application's retention policy. Each answers a different debugging question.

Separate spoken delivery from approval

An audible scheduling prompt is evidence about presentation, not approval. The playback-acknowledgment guide distinguishes what a delivery signal can establish. A prompt reaching the caller does not prove that the caller understood the zone or chose the correct repeated-hour occurrence.

Build conversation fixtures that separate these events. One fixture can present both candidate occurrences and receive a clear choice. Another can interrupt before the distinguishing phrase finishes. A third can receive a correction to the appointment location. Their expected state transitions should follow the actual input and candidate ownership contract, rather than assuming that successful synthesis completed confirmation.

Do not cache a personalized appointment readback as a timeless answer. The stale-account-fact cache guide explains why a reusable spoken surface can carry facts that no longer belong to the current request. A cached time string needs the same material zone, date, occurrence, and candidate evidence as a newly generated one before it is used in a confirmation flow.

The pure resolver makes none of these conversational claims. Its immutable snapshot allows a separate controller to associate a spoken prompt with an exact candidate. That controller must decide whether the caller's completed response approves that snapshot and whether it remains current. Keeping these roles explicit prevents an arithmetic mapping result from becoming an accidental booking authorization.

UTC round-trip validation retains only candidates whose returned wall components match

For the same reason, a date correction should be visible even if a formatter would produce a similar spoken phrase. A change in year, zone, or occurrence can move the instant substantially while leaving much of the readback unchanged. Test the material structured fields rather than deciding that two requests are equivalent because their surface text shares most words.

Test the actual adapter interpretation

Inspect the consuming calendar contract before constructing its payload. An API accepting a local timestamp plus a zone may preserve a civil-time intention differently from an API accepting only a UTC instant. Neither representation automatically establishes the other contract. Translate the selected candidate deliberately and record which interpretation the adapter is expected to use.

A useful synthetic integration fixture submits a known selected instant to an adapter sandbox or an isolated local validator and inspects the resulting interpreted fields. That is proposed test design, not a claim this article performed a remote request. The published fence never contacts a calendar, so its only asserted output is the resolver's computed evidence.

If the adapter rejects a time, preserve the difference between a representation failure and unavailable capacity. A valid local-to-UTC mapping can still describe a slot the service does not offer. A booking conflict does not invalidate the wall-time arithmetic, and a successful availability lookup does not repair a missing occurrence choice.

The small resolver deliberately limits accepted years to 2000 through 2099. That bounded modern interval avoids pretending to cover every historical calendar use or datetime endpoint conversion. It does not promise that future civil-time rules will remain unchanged. The fixture identifies the actual loaded data, and an application supporting another year range must review that range and add its own boundary tests.

The output snapshot records the original wall spelling, explicit zone, loaded-file digest, classification, and immutable candidates. The printed report emits the subset needed to inspect the synthetic fold result. No advertised booking identifier or confirmation token is hidden in memory: those outputs do not exist because the artifact did not perform those operations.

When extending the artifact, prefer a narrow new contract to a permissive catch-all parser. Supporting minute precision requires an explicit missing-seconds rule. Supporting a relative date requires a reference instant and intended zone. Supporting another calendar requires its own conversion evidence. Each addition changes the interpretation boundary and should receive negative fixtures that demonstrate what remains unresolved.

Finally, reproduce failures using the exact published function and actual source data. A parallel reimplementation can accidentally correct the bug under investigation while the shipped fence remains wrong. Extracting and executing the full fence checks the artifact a reader receives, and identifying the loaded TZif bytes explains why two hosts might produce different evidence without inventing a single universal data version.

Interpretation evidence, selected UTC identity, and remote booking evidence remain separate

The accepted year interval is also a test boundary rather than an inference from a date's spelling. The fixture rejects both the preceding and following unsupported years. That check happens before time-zone conversion, so endpoint arithmetic cannot accidentally masquerade as a successful mapping within the advertised modern range. A caller requesting an unsupported date needs an alternate route or a separately supported contract.

Keep the exact local components visible when inspecting a rejected candidate. A gap classification says that the requested wall spelling has no surviving instant under the loaded rules. It does not say that the day is invalid, that the caller used the wrong language, or that the whole appointment request should be discarded. A targeted repair can ask for another time on the same intended day while retaining all other approved context according to the controller's policy.

Likewise, a fold is evidence of ambiguity that must be preserved. Returning two candidates is a useful result because it exposes the choice a permissive parser would hide. The application can now present a meaningful distinction, obtain a current occurrence choice, and submit one selected instant. That sequence is observable and testable without claiming that a local conversion alone completed the caller's scheduling task.

Frequently asked questions

Why does a locale not identify a timezone?

A locale describes conventions such as language and formatting. People using the same locale can be in different zones or schedule for another location. Keep the appointment's intended zone explicit rather than treating language detection, a region preference, or server configuration as authority.

What information makes a scheduling time unambiguous?

The calendar date, declared time components, intended zone, and any required repeated-time occurrence must be resolved. The consuming workflow may also require business-hour, availability, and authorization checks. A syntactically complete timestamp does not establish those separate booking conditions.

What is a DST gap?

It is a skipped interval of local wall time during a relevant offset transition. A requested time in that interval has no corresponding instant under those rules. This reference returns no candidates and refuses selection, rather than automatically moving the appointment to another wall time.

What is a DST fold?

It is a repeated local-time interval in which one wall label can identify two distinct instants. The reference retains both UTC candidates. The caller-facing workflow must distinguish the occurrences before choosing one; a successful datetime construction does not supply that choice.

Does attaching ZoneInfo validate a local time?

No. Construction can produce an object for wall components that do not represent a real local instant. The reference tests candidate interpretations through UTC and back, retaining only those that reproduce the requested wall components under the selected rule data.

How does UTC round-trip checking detect invalid times?

Each proposed interpretation is converted to UTC and then back to the intended zone. If the returned local components differ from the request, that interpretation is not accepted. The algorithm then deduplicates surviving UTC identities to classify normal, gap, and fold mappings.

Why deduplicate candidate UTC instants?

Outside a repeated interval, both fold trials can represent the same instant. Counting them twice would create false ambiguity. Deduplicating their normalized UTC identities leaves one normal candidate while preserving the two genuinely distinct instants in a repeated-time fixture.

When should fold=0 or fold=1 be used?

They distinguish the earlier and later readings in an ambiguous interval under Python's model. The resolver tries both to discover valid candidates. A caller-facing application must still obtain the appropriate occurrence choice rather than assigning a fold value merely to make construction succeed.

Should two wall times be compared directly?

Use wall components when checking the requested local representation and UTC identity when comparing instants. Those are different questions. Same-zone datetime equality around fold is not a safe substitute for explicit candidate-instant identity in this resolver.

How do I confirm an ambiguous date like 03/04?

Resolve the intended date order and calendar, then repeat it using a clear month name or another agreed representation. Do not let server locale decide silently. The reference rejects slash-separated input because date-phrase interpretation belongs to a separate upstream contract.

What calendar does the example support?

It accepts Gregorian components from 2000 through 2099 within the declared exact grammar. It does not convert other calendars or parse arbitrary localized date phrases. A workflow supporting those inputs or another year range needs a separately verified interpretation and conversion boundary.

What does tomorrow require as context?

It needs a reference instant and the civil-time zone in which the relative day is intended. Calls and appointment locations can span different dates. The reference avoids this ambiguity by accepting only already resolved absolute date and time components.

Can a phone country code select a zone?

It should not silently select the appointment zone. A calling code can cover multiple regions, and the caller can be traveling or booking elsewhere. Obtain explicit authorized scheduling context instead of treating a telephone attribute as a location guarantee.

Why use an IANA zone instead of a fixed offset?

An IANA zone provides location-based civil-time rules across dates, while a fixed offset supplies one offset value. Which representation a booking API requires depends on its contract. Preserve the intended zone when future local-time behavior matters rather than assuming a current offset is permanent.

How does tzdata version affect future schedules?

Civil-time rules can change. Record the actual data used and define how your product handles updates for future appointments. The reference reports the loaded TZif file digest without inventing a release label or silently redefining previously confirmed scheduling intentions.

What happens when timezone data is missing?

This reference raises an explicit unavailable-data error. It does not use the server's local zone, return a gap, or pretend the caller gave an invalid time. Provision the required rule data through a documented deployment route before extending its mapping claims.

Should nonexistent times shift forward automatically?

Only an explicitly declared product contract could authorize that transformation. This reference does not. It asks for a new wall time because automatic shifting would change the requested appointment and could conceal a material scheduling decision from the caller.

How do I present both repeated-time candidates?

Use enough context to distinguish their occurrences, such as earlier versus later with their applicable offsets and intended zone. Test the spoken presentation separately. Two identical wall labels without an audible distinction cannot provide meaningful approval of one instant.

What is outside the parser contract?

Natural-language date interpretation, relative dates, non-Gregorian conversion, leap-second processing, availability lookup, approval state, and booking are outside it. The artifact validates a complete wall-time mapping using identified system zone data and returns immutable candidate evidence.

How do I regression-test scheduling boundaries?

Execute normal, gap, fold, missing-component, invalid-calendar, unavailable-zone, and immutable-output fixtures against the actual resolver. Record rule-data identity and test the consuming adapter separately. Do not present synthetic mapping assertions as evidence from live calls or completed appointments.

Ship a verified time candidate

Make the intended local components and zone explicit, validate their mapping, and preserve every distinct candidate instant until the caller's occurrence choice resolves ambiguity. Keep data availability and parser limitations visible rather than translating them into guessed appointments.

For 60db Agent integrations, this gives an engineer a small reproducible first boundary: the complete published fixture proves which candidates the selected system data produces. Conversation approval, calendar availability, and remote booking evidence then remain explicit next contracts, each with its own observable result.