Skip to content

fix(swift): keep the written wall-clock time in formatDate and accept timestamps without an offset - #3380

Merged
pinieb merged 18 commits into
a2ui-project:mainfrom
karolkulesza:fix/swift-formatdate-wall-clock
Oct 30, 2026
Merged

pinieb merged 18 commits into
a2ui-project:mainfrom
karolkulesza:fix/swift-formatdate-wall-clock

Conversation

@karolkulesza

Copy link
Copy Markdown
Contributor

Description

Swift's formatDate formatted in the host time zone, so the same payload rendered differently
from device to device. 2026-09-04T23:30:00-05:00 with yyyy-MM-dd HH:mm gave
2026-09-05 06:30 on a Warsaw device. web_core, Dart and Python v1.0 give 2026-09-04 23:30.
A timestamp without an offset returned "", and 2026-02-30T10:00:00Z was rolled over into
March.

Changes:

  • FormatDateFunction parses an ISO 8601 timestamp into its instant and written offset (Z,
    +hh, +hhmm, +hh:mm; T or space; optional seconds and fraction), and formats the
    pattern in that offset. Without an offset, the timestamp or date is read as UTC. Fields that
    do not exist give "". ISO still emits the UTC instant. Numeric epochs now format in UTC
    instead of the host zone.
  • FormatDateFunctionTests asserts literal strings, so it no longer derives expectations from
    the host time zone. It no longer uses @testable import, and it adds cases for each offset
    form, no offset, date-only and impossible fields.
  • conformance/core/functions.yaml adds three cases. The offset case is pinned to
    protocolVersion: "v1.0" because Python's v0.9 catalog converts to UTC before formatting
    (2026-09-05 04:30). That case passes in the Dart and Python v1.0 runners; for web_core I checked parseTimestamp on the three inputs but did not run its yarn suite.

Verification:

Fixes #3379

Pre-launch Checklist

One time:

For this PR:

  • I have updated the relevant CHANGELOG.md file. (The Swift package has no CHANGELOG.md.)
  • I updated/added relevant documentation. (Not needed: no Swift doc describes formatDate time zones.)
  • My code changes (if any) have tests.
  • If my branch is on a fork, I have verified that scripts/e2e_test.sh passes.

FormatDateFunction formatted with a DateFormatter that had no time zone,
so the result depended on the host: 2026-09-04T23:30:00-05:00 formatted
as 2026-09-05 on a Warsaw device. It also returned "" for a timestamp
without an offset, and rolled 2026-02-30T10:00:00Z over into March.

Parse the timestamp into its instant and the offset it was written with,
and format in that offset, as web_core, Dart and Python v1.0 do. A
timestamp without an offset, or a date, is read as UTC. Fields that do
not exist return "". ISO still emits the UTC instant, and numeric epochs
now format in UTC.

The tests now assert literal strings instead of host-formatted ones, and
functions.yaml gains three shared cases. The offset case is pinned to
v1.0 because Python's v0.9 catalog converts to UTC first.

Fixes a2ui-project#3379

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request refactors the FormatDateFunction in Swift to parse and format dates using a new Timestamp struct, ensuring that timestamps are formatted in their written offset rather than depending on the host time zone. It also adds corresponding conformance and unit tests. The review feedback suggests improving robustness by ensuring parsed Double values are finite before initializing Timestamp, and setting the Calendar locale to en_US_POSIX to guarantee deterministic calendar calculations independent of host regional settings.

@github-actions github-actions Bot added the status: needs-triage auto-managed: https://github.com/a2ui-project/a2ui/blob/main/scripts/triage.mjs label Oct 21, 2026
@Varun-S10

Copy link
Copy Markdown
Collaborator

Hi @karolkulesza, thank you for raising the PR. We have checked the PR and confirmed that it fixes #3379. Before merging, please address the 2 bot review comments in FormatDateFunction.swift and also change [T ] to [Tt ] to support lowercase t in timestamps. Could you please let us know once it is updated? Thank you for your contribution and understanding.

@Varun-S10 Varun-S10 added status: waiting-for-author-response and removed status: needs-triage auto-managed: https://github.com/a2ui-project/a2ui/blob/main/scripts/triage.mjs labels Oct 22, 2026
- Return "" for a numeric epoch that is not finite (NaN, Infinity, also
  as the strings "NaN", "inf", "-Infinity") or lies outside the
  +-100,000,000 days a JavaScript Date can hold, checked before the
  Timestamp is built.
- Give the calendar that validates ISO 8601 fields the en_US_POSIX
  locale, so it never depends on the host's regional settings.
- Accept a lowercase t between date and time, as RFC 3339 allows and as
  web_core and Python already do. A lowercase z was already accepted.
@karolkulesza

Copy link
Copy Markdown
Contributor Author

@Varun-S10 Updated in 1e138a9:

  • Numeric epochs are now checked with isFinite before the Timestamp is built, and values outside the ±100,000,000 days a JavaScript Date can hold also return "" (tests: returnsEmptyStringForNonFiniteEpoch, returnsEmptyStringForEpochOutOfRange).
  • The validating calendar in Timestamp(iso8601:) now uses the en_US_POSIX locale.
  • [T ] is now [Tt ], so 2026-09-04t23:30:00-05:00 formats as written. Lowercase z was already accepted ([zZ]); both are covered by acceptsLowercaseTimeSeparatorAndUTCDesignator.

I kept the lowercase t case Swift-only rather than adding it to functions.yaml: web_core and Python accept it, but Dart's DateTime.tryParse doesn't, and Dart accepts lowercase z while web_core and Python don't.

@github-actions github-actions Bot added status: needs-triage auto-managed: https://github.com/a2ui-project/a2ui/blob/main/scripts/triage.mjs and removed status: waiting-for-author-response labels Oct 22, 2026
@Varun-S10 Varun-S10 self-assigned this Oct 29, 2026
@Varun-S10

Copy link
Copy Markdown
Collaborator

Hi @karolkulesza, thank you for addressing all the review comments, adding the finite epoch and locale checks, and supporting lowercase t in timestamps. We have verified the updated changes and new test cases, and everything works now. I will escalate this PR to the team for review. Thank you for your patience and understanding.
Hi @pinieb, Could you please take a look?

@Varun-S10 Varun-S10 added status: needs review and removed status: needs-triage auto-managed: https://github.com/a2ui-project/a2ui/blob/main/scripts/triage.mjs labels Oct 29, 2026
@pinieb pinieb added status: waiting-for-author-response lang: swift Affects SDKs or apps in the Swift programming language and removed status: needs review labels Oct 29, 2026
Parse ISO 8601 timestamps with a Swift Regex literal and wholeMatch(of:),
so a trailing newline no longer matches, and read the fields through named
captures. Truncate the fraction to whole milliseconds, as web_core, Python
and Dart do, so 23:59:59.9999 no longer rounds into the next day. Treat
epoch numbers whose magnitude exceeds 10^10 as milliseconds, so pre-1970
millisecond timestamps are read correctly. Move the JSONValue dispatch into
a failable Timestamp initializer so evaluate unwraps its inputs in one guard.
@github-actions github-actions Bot added status: needs-triage auto-managed: https://github.com/a2ui-project/a2ui/blob/main/scripts/triage.mjs and removed status: waiting-for-author-response labels Oct 29, 2026
@karolkulesza
karolkulesza requested a review from pinieb October 29, 2026 20:10
@pinieb

pinieb commented Oct 29, 2026

Copy link
Copy Markdown
Collaborator

Ah, it looks like one of the new tests in the function conformance tests fails on Python. If you want to update the Python renderer to pass, we'll need a Python reviewer to take a look at this PR. Otherwise, you could remove the broken test from this PR and open an issue to add the test and get Python working. Up to you whether you want to take that task on leave it for someone else :)

@github-actions github-actions Bot removed the status: needs-triage auto-managed: https://github.com/a2ui-project/a2ui/blob/main/scripts/triage.mjs label Oct 29, 2026
@pinieb

pinieb commented Oct 30, 2026

Copy link
Copy Markdown
Collaborator

I thought I left a top-level comment about this, but it seems like I did not: We'll need someone a bit more familiar with Python to sign off on this one as well. Perhaps @josemontesp or @gspencergoog?

@pinieb pinieb added the python All issues relating to Python codebases label Oct 30, 2026
@karolkulesza

Copy link
Copy Markdown
Contributor Author

@pinieb thanks for flagging it. The Python failure wasn't in Python's formatDate. conformance/tests/test_conformance_yaml.py rejected the new test_evaluate_function_format_date_keeps_written_offset case because it still said protocolVersion: "v1.0", and #3726 changed the wire version to 1.0 while this PR was open. e0cae89 merges main and switches the case to "1.0".

Locally, test_conformance_yaml.py passes (89 passed), and so does python/a2ui_core (1671 passed). The a2ui_core tests run functions.yaml, including this case, so Python v1.0 already keeps the written offset. The Swift formatDate and conformance tests pass too.

This PR changes no Python code, only the shared YAML case and Swift. Python would only need a closer look for the v0.9 behaviour I mentioned in #3379, and that's why the case is pinned to v1.0. Happy to have a Python reviewer confirm anyway.

@github-actions github-actions Bot added the status: needs-triage auto-managed: https://github.com/a2ui-project/a2ui/blob/main/scripts/triage.mjs label Oct 30, 2026
@pinieb pinieb removed the python All issues relating to Python codebases label Oct 30, 2026

@pinieb pinieb left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good, thank you! 🎆

@pinieb
pinieb merged commit bb27bbf into a2ui-project:main Oct 30, 2026
51 checks passed
@github-project-automation github-project-automation Bot moved this from Todo to Done in A2UI Oct 30, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

lang: swift Affects SDKs or apps in the Swift programming language status: needs-triage auto-managed: https://github.com/a2ui-project/a2ui/blob/main/scripts/triage.mjs

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Swift] formatDate formats in the host time zone and rejects timestamps without an offset

3 participants