On this page
First decide whether the value is a date or an instant
The source dates in this guide identify documentation checks on 22 October 2026, not original publication dates. Follow each citation for the standard or documentation edition being discussed.
A date such as 2026-10-22 can mean a reporting day, a billing period, a publication date, or a calendar date in a jurisdiction. It does not identify an instant. Do not append midnight and call the result an event time unless the source contract explicitly defines that meaning.
A timestamp with an offset, such as local time plus offset, identifies an instant relative to UTC. A timestamp with a named zone carries more context for future conversions and daylight-saving rules. A timestamp with neither an offset nor a zone is a wall-clock value that needs source metadata before it can be compared with another event.
| Source value | Safe initial state | Required action |
|---|---|---|
| Timestamp with a known explicit offset | Aware candidate | Parse strictly and retain the original string and offset. |
RFC 3339 timestamp ending in -00:00 | Unknown local offset | Preserve that distinction; do not silently rewrite it as a known UTC offset. |
| Timestamp with a named zone | Aware candidate with zone context | Validate the zone identifier and preserve it beside the derived instant. |
| Timestamp without offset or zone | Naive or unknown | Find the source rule or stop with an unresolved-time status. |
| Date only | Date, not an instant | Keep it as a date and record its coverage meaning. |
| Invalid or mixed-format value | Parse failure | Preserve the raw value in controlled evidence and exclude it from normalized comparisons until resolved. |
The labels in this table are review states, not a promise that an export has passed them.
Preserve the source values and review status
For an event field, a durable normalization record can contain the fields below. Keep the source value even after deriving UTC. A normalized value is a convenience for comparison, not a replacement for evidence. Keep the zone name when available because offset rules can change across dates and a numeric offset alone does not carry the location context.
| Field | Purpose |
|---|---|
source_value | Exact text received from the export, without rewriting it. |
source_offset | Offset stated by the source, if any. |
source_zone | Named IANA zone supplied by the source or its documented export rule, if any. |
normalized_utc | Derived comparable instant, only when the source context is sufficient. |
normalization_status | normalized, date_only, unknown_zone, unknown_offset, ambiguous, invalid, or another documented state. |
parse_rule | The parser and source contract used, not a guessed format. |
evidence_ref | Controlled reference to the source schema or export note. |
Parse aware values and reject silent assumptions
This local example accepts a bounded timestamp format with seconds and an offset. It preserves the source spelling and returns the comparable UTC instant separately from local-offset status. RFC 9557 updates RFC 3339: Z and -00:00 represent a known UTC instant without asserting the local offset, while +00:00 retains UTC as the preferred reference Internet Engineering Task Force, (opens in a new tab). This is not a complete standards parser: leap seconds and named-zone annotations require a separate policy.
Strict normalization example
from datetime import datetime, timezone
from re import fullmatch
def normalize_timestamp(raw: str) -> dict[str, str]:
if not fullmatch(
r"[0-9]{4}-[0-9]{2}-[0-9]{2}T(?:[01][0-9]|2[0-3]):[0-5][0-9]:[0-5][0-9]"
r"(?:\.[0-9]{1,6})?(?:Z|[+-](?:[01][0-9]|2[0-3]):[0-5][0-9])",
raw,
):
raise ValueError("timestamp is outside the documented input format")
unknown_offset = raw.endswith("-00:00") or raw.endswith("Z")
source_offset = "Z" if raw.endswith("Z") else raw[-6:]
value = datetime.fromisoformat(raw.replace("Z", "+00:00"))
if value.tzinfo is None:
raise ValueError("timestamp has no offset or named source zone")
return {
"source_value": raw,
"source_offset": source_offset,
"normalized_utc": value.astimezone(timezone.utc).isoformat().replace("+00:00", "Z"),
"normalization_status": "unknown_offset" if unknown_offset else "normalized",
}Python documents astimezone() as preserving the instant while changing its representation, and warns that a naive value is treated as system-local time Python Software Foundation, (opens in a new tab). The explicit input rule prevents that workstation-dependent assumption. An unknown_offset result still has a usable UTC instant, but cannot establish local wall time or location. Keep the status with the derived value; do not infer a local zone from the output Z.
If the source supplies a separate named zone for a timezone-free value, treat attaching the zone as a source-specific parsing step, not a generic fallback. Validate the zone through the application’s time-zone database, record the rule and source evidence, and test for nonexistent or repeated wall times. Do not use a fixed offset as a substitute for a location that observes daylight-saving changes.
Handle daylight-saving boundaries explicitly
At a clock change, a local wall time can be nonexistent or can occur twice. Python exposes the fold attribute for a repeated interval, and its documentation shows that two UTC instants can map to the same local hour while carrying different fold values Python Software Foundation, (opens in a new tab).
| Condition | Normalization decision |
|---|---|
| Offset and zone agree | Derive UTC and retain both. |
| Offset is present but zone is absent | Derive UTC; mark location context unavailable. |
| Zone is present but wall time is repeated | Require the source’s fold or an explicit policy; otherwise mark ambiguous. |
| Zone is present but wall time is nonexistent | Mark invalid or resolve under a documented source rule; do not shift silently. |
| Offset and zone disagree | Stop and investigate the source or parser. Preserve both values. |
| No offset or zone | Keep the raw value and mark unknown; do not derive UTC. |
The source system may have a documented convention for these cases. Cite that convention in the evidence record. A reviewer should be able to tell whether the normalized instant came from the source, from a stated assumption, or not at all.
Validate ordering and coverage after normalization
- Compare events using normalized instants only when their statuses permit it.
- Keep date-only coverage separate from event timestamps.
- Record rows that could not be normalized; do not drop them without a count and reason in controlled evidence.
- Check that the declared start and end coverage use the same interpretation.
- Compare the normalized result with the source timezone and offset columns.
- Re-run the check after a source schema, timezone database, or export rule changes.
Do not publish a sorted chart that hides unknown or ambiguous times. A missing timezone can change the apparent order of events, and a wrong assumption can affect retention, consent, incident, or contractual timelines.
Minimize location and personal detail
A named timezone can be useful technical evidence, but it can also reveal a person’s location or operating pattern when attached to a detailed event stream. Keep only the granularity needed for the review, use role or record identifiers instead of personal names, and keep raw event rows in controlled storage. This normalization guide does not authorize collecting, uploading, or publishing private records.
Pair the timestamp record with the data provenance log when source, processing, exclusions, and evidence need a durable trail. The broader export guidance covers tool-specific export quality; this guide owns time context and unresolved timestamp states.
Once the owner has a documented time policy and has separated normalized values from unresolved records, the Rightmark appraisal path is a qualified preparation reference for an eligible owner. It does not normalize exports, validate time zones, or decide whether an archive can be licensed. Escalate rights, privacy, retention, and contract questions to the applicable owner or counsel.
Sources
- Internet Engineering Task Force: Date and Time on the Internet: Timestamps (opens in a new tab)
- Internet Engineering Task Force: Date and Time on the Internet: Timestamps with Additional Information (opens in a new tab)
- Python Software Foundation: datetime, Basic date and time types (opens in a new tab)