Automation · · 5 min read
Python zoneinfo: resolve AI-worker handoff times
Test a concrete time-validation step: ask which occurrence a customer means when a local time repeats, reject missing times, and return an explicit UTC instant.
By Sociologix Editorial

Make the handoff time unambiguous
An AI worker collecting a callback request needs more than “1:30 on Sunday.” In the synthetic Chicago fixture below, November 1, 2026 at 01:30 identifies two different instants, an hour apart. A form that silently picks one can pass the wrong time to the next system.
This AI-assisted editorial guide tests a local Python validation step for an AI-enabled intake workflow. It returns either a specific UTC instant or a request for clarification. It does not schedule appointments, persist records, send reminders or call a language model. All dates are test fixtures; none represents a customer booking.
1. Set up a reproducible timezone test
Our local run used Python 3.13.15 on Windows and tzdata 2026.6, which reports IANA database version 2026f. The tzdata package supplies timezone data and uses Apache-2.0 licensing. We verified the package version on November 1, 2026. [1]
zoneinfo is part of Python’s standard library. It normally searches system timezone data before falling back to the first-party tzdata package; Windows applications may need that package. Our standalone demo calls reset_tzpath(()) before creating any ZoneInfo objects, so its results use the pinned package. This process-wide test setting does not clear previously cached ZoneInfo objects; run the example in a fresh process. [2]
Run these PowerShell commands in a new folder. Save the Python block below as handoff_time.py before the last command. Package installation needs internet access; the resolver then works with local data.
mkdir handoff-time-demo
cd handoff-time-demo
python -m venv .venv
.\.venv\Scripts\python.exe -m pip install "tzdata==2026.6"
# Save handoff_time.py below first.
.\.venv\Scripts\python.exe handoff_time.py2. Resolve candidates before accepting the request
Python’s fold flag distinguishes the earlier and later occurrence of a repeated local time. Creating a datetime with a timezone does not itself reject a nonexistent local time. We therefore try both folds, convert each to UTC, and roundtrip back to the requested zone. Only candidates returning the original wall-clock fields survive. [3]
The dictionary keys are UTC datetimes. That matters because comparisons between local datetimes sharing the same timezone can ignore fold. Deduplicating local values could collapse the two occurrences we need the customer to distinguish. [3]
Our adapter accepts minute-resolution local input, with a separate allowlisted IANA timezone. It uses datetime.fromisoformat only after checking the input shape. Invalid calendar dates still raise ValueError. An embedded offset is rejected because this particular form requires a separate zone selection. [4]
Two candidates produce needs_time_choice unless the caller supplies earlier or later. Zero candidates raise a validation error. An ordinary time has one unique UTC candidate. The adapter also rejects an occurrence choice attached to an ordinary time, which helps catch a stale selection after someone changes the date.
import json
import platform
import re
from datetime import UTC, datetime
from zoneinfo import ZoneInfo, reset_tzpath
import tzdata
ALLOWED_ZONES = {"America/Chicago", "America/New_York", "UTC"}
def resolve_local(local_text, zone_key, occurrence=None):
if not isinstance(local_text, str) or not re.fullmatch(
r"[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}", local_text
):
raise ValueError("Use a local time in YYYY-MM-DDTHH:MM format")
local = datetime.fromisoformat(local_text)
if zone_key not in ALLOWED_ZONES:
raise ValueError("Choose a supported IANA zone")
if occurrence not in (None, "earlier", "later"):
raise ValueError("Occurrence must be earlier, later, or omitted")
zone = ZoneInfo(zone_key)
by_utc = {}
for fold in (0, 1):
aware = local.replace(tzinfo=zone, fold=fold)
instant = aware.astimezone(UTC)
back = instant.astimezone(zone)
if back.replace(tzinfo=None) == local:
by_utc[instant] = back
options = [
{"utc": instant.isoformat(), "local": by_utc[instant].isoformat(),
"fold": by_utc[instant].fold}
for instant in sorted(by_utc)
]
if not options:
raise ValueError("This local time does not exist; choose another time")
base = {"local_input": local_text, "timezone": zone_key,
"tzdata": tzdata.__version__}
if len(options) == 2:
if occurrence is None:
return {**base, "status": "needs_time_choice", "options": options}
selected = options[0 if occurrence == "earlier" else 1]
else:
if occurrence is not None:
raise ValueError("No occurrence choice is needed for this time")
selected = options[0]
return {**base, "status": "resolved", "occurrence": occurrence,
"selected": selected}
if __name__ == "__main__":
# Standalone demo: use the pinned tzdata package.
reset_tzpath(())
print(json.dumps({
"python": platform.python_version(), "iana": tzdata.IANA_VERSION,
"examples": [
resolve_local("2026-11-01T01:30", "America/Chicago"),
resolve_local("2026-11-01T01:30", "America/Chicago", "later"),
resolve_local("2026-11-02T09:00", "America/Chicago"),
],
}, indent=2))
3. Check the observed fixture results
With no occurrence supplied, 2026-11-01T01:30 in America/Chicago returned needs_time_choice. Its options were 2026-11-01T06:30:00+00:00 with local offset -05:00 and fold 0, and 2026-11-01T07:30:00+00:00 with offset -06:00 and fold 1. Selecting later resolved to the second option.
The ordinary fixture, 2026-11-02T09:00 in America/Chicago, resolved once to 2026-11-02T15:00:00+00:00. Our separate spring-transition fixture, 2026-03-08T02:30, raised “This local time does not exist; choose another time.” The script deliberately does not move that request to a different hour.
Twelve local tests passed: pinned data selection, ambiguous output, earlier selection, later selection, gap rejection, ordinary-time deduplication, a UTC fixture, rejection of embedded offsets, an invalid calendar date, an unsupported zone, an invalid occurrence label and a stale occurrence choice. These are functional fixtures, not an exhaustive test of historical timezone transitions.
4. Connect the result to an intake form
A proposed form integration should display both local timestamps with their numeric offsets when status is needs_time_choice. Ask the requester to select the first or second occurrence; do not let a model infer a preference from conversational tone. Resubmit the original local time, timezone and selected occurrence to the server for validation.
For a gap error, keep the original input visible and ask for another time. An unsupported zone should prompt the user to choose one of the configured service locations. Extend ALLOWED_ZONES only when the business supports those locations; do not replace an unknown zone with the server’s default.
Once resolved and confirmed, a future persistence adapter could store the original local input, IANA zone, occurrence choice, selected UTC instant, resolved offset and data version. Add the requester’s confirmation and the business record identifier. This tutorial prints those conversion fields but does not implement storage or a booking API.
5. Keep scheduling policy separate
Use this resolver for a single explicitly dated handoff. A recurring request such as “every weekday at 09:00” needs a separate policy: whether to preserve local business time, how to handle future gaps and repeated times, and what to do when timezone rules change. Pinning data makes this exercise repeatable; production still needs an update and retest process.
Before enabling real bookings, add authorized project access, business-hour checks, conflict detection and a tested scheduler adapter. Repeat validation on the server when the customer confirms. Also test your deployment’s actual Python and timezone-data versions. Correct conversion alone does not reserve a slot or establish permission to contact a customer.
Sources & further reading
Build a clearer AI-worker handoff
Bring Sociologix one intake form, your service locations and the handoff rules your team uses. We can scope timezone validation, customer confirmation and a reviewed connection to your scheduling workflow.
Talk to Sociologix