Skip to content

Codegen train: timestamp date strings with a UTC or GMT zone word - #330

Merged
luisleo526 merged 4 commits into
mainfrom
train/codegen-20261106a
Nov 6, 2026
Merged

luisleo526 merged 4 commits into
mainfrom
train/codegen-20261106a

Conversation

@luisleo526

@luisleo526 luisleo526 commented Nov 6, 2026 •

Copy link
Copy Markdown
Contributor

Two changes to how timestamp("...") reads the UTC / GMT zone word of a date string, in this order on top of main. The first was opened as #329 and checked on its own; the second was checked on top of it. This pull request carries both.

Closes #329.

1. A zone word of 24 hours or more no longer stops the translator (#329)

timestamp("01 Feb 2024 00:00 UTC+24"), the same string with GMT-24 and with UTC+25 stopped the translator with an internal ValueError (offset must be a timedelta strictly between -timedelta(hours=24) and timedelta(hours=24)), because the zone word was turned into a datetime.timezone, which Python limits to under 24 hours. TradingView accepts these strings and reads the word as plain arithmetic on the date fields. Its exported trade lists for scripts built on them decode to the values below (01 Feb 2024 00:00 read as UTC is 1706745600000 ms):

date string value (ms)
01 Feb 2024 00:00 UTC+24 1706659200000
01 Feb 2024 00:00 GMT-24 1706832000000
01 Feb 2024 00:00 UTC+25 1706655600000

The zone word is now kept as a timedelta and subtracted from the UTC reading of the fields.

2. Zone word forms that gave another value than TradingView

TradingView's trade lists for fourteen more date strings decode to values the translator did not give; two of them were refused as unparseable. The zone word is now read the way the lists show:

form example TradingView before
negative zone with a zero hour GMT-00:30, GMT-0:45, UTC-0030, UTC-0:30 that many minutes west of UTC east (the sign was lost through int("-00"))
minutes of 60 or more UTC+00:60, GMT+00:60, UTC+00:90, UTC+01:60 minutes dropped, hours kept minutes added
number without a sign after the word UTC30, GMT30, UTC5 ignored (UTC) added as minutes; UTC5 refused
numeric offset and word in one string +0000 UTC+2, +0000 GMT-3, UTC+2 +0000 the last zone token decides the numeric offset used; UTC+2 +0000 refused

A string without a zone word, a zone word with a plain signed offset (UTC+2, GMT-5:30, UTC-23) and a string that carries only a numeric offset (+0300) translate to the same value as before; the tests pin six such neighbours.

Verification

All runs below are against engine main 9ee1cc7d. The state with both fixes was run on a tree identical to this branch's head (tree 008b207ab1932ce2e69c298d3ddc6310a5a6e403); the states with only the first fix, or only the tests, were run on the trees of the corresponding commits of this branch.

  • New tests, written and run red before each fix. tests/test_timestamp_zone_offsets.py (13 tests) on main with only its tests: 7 fail with the ValueError above, 6 pass. tests/test_timestamp_zone_grammar.py (34 tests) on the first fix with only its tests: 16 fail (12 with another value, 4 with PF-E5027 because two forms are refused) and 18 pass. With both fixes: 47 passed, none skipped, including the 17 rows that build the emitted C++ against the engine headers. The expected values in both files are the TradingView values above, not values computed by the fixed function.
  • The 12 existing test files that contain a string timestamp(...) call: 1300 passed, none skipped, with and without each fix.
  • Translation of 1,175 Pine sources (826 tracked in the repository, 312 corpus strategies and 37 date-string probes), before and after each fix. First fix: 4 sources change, all of them the ones that crashed and now emit C++. Second fix: 14 change (12 emit a different constant, 2 are no longer refused); the other 1,161 are byte-identical.
  • Value check against TradingView's exported trade lists: 35 probes whose date string carries a UTC or GMT zone word, run on the engine and joined to each list by entry time. Before the second fix 21 probes were as they are now; after it 14 move and every move is toward the list: the seven two-entry probes go from 1 of 2 to 2 of 2 exact quantities, the five twelve-entry probes from 9 to 11 of 12, and UTC5 and UTC+2 +0000 go from refused to measured at 11 of 12. UTC+24 and GMT-24 match 2 of 2 entries; UTC+25 matches 11 of 12.

Limit of the evidence: the TradingView lists cover date strings written as DD Mon YYYY hh:mm <zone>. The zone word is peeled off before the date part is parsed, so the same rules apply to the other accepted date forms (YYYY-MM-DD hh:mm <zone>, Mon DD YYYY ...); the forms in the second table have no list of their own in those spellings.

Known difference, not caused by this change

One entry of every twelve-entry probe still differs from TradingView: the UTC+25 probe, the seven other date-string probes of that kind, and eleven probes that use the timezone-first form timestamp("UTC+13:59", 2024, 2, 1, 0, 0), which this change does not touch. The probes compute int(math.floor(x)) % 1000 for a value of about 1.7e12; the generated code reads that cast as the integer na (a value outside the 32-bit range), TradingView gives 1700000000000. It is the same entry with the same quantity on all nineteen, so it does not depend on the zone word, and it is tracked separately.

🤖 Generated with Claude Code

luisleo526 and others added 4 commits November 6, 2026 10:06
timestamp("01 Feb 2024 00:00 UTC+24"), the same string with GMT-24 and with
UTC+25 are accepted by TradingView, which reads the zone word as plain
arithmetic on the wall-clock fields. The translator stops on them with an
internal ValueError instead of emitting the constant.

The new file pins the three values TradingView's exported trade lists decode
to (1706659200000, 1706832000000 and 1706655600000 ms), the same string as an
input.time default, the build of the emitted C++ for the three strings, and
six neighbouring forms that already translate (no zone, a bare UTC word,
UTC+2, GMT-5:30, UTC+23, UTC-23) so that the repair can be seen to move none
of them.

Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
…trings

timestamp("01 Feb 2024 00:00 UTC+24") raised an internal ValueError because
the zone word was turned into a datetime.timezone, which refuses an offset of
24 hours or more. TradingView treats the word as plain arithmetic on the date
fields: its exported trade lists for UTC+24, GMT-24 and UTC+25 decode to the
fields read as UTC minus the signed offset. The offset is now kept as a
timedelta and subtracted from the UTC reading of the fields.

The value for an offset under 24 hours, a bare UTC word, a string without a
zone and a string that carries its own numeric offset is unchanged.

Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
TradingView's exported trade lists for fourteen date strings with a UTC or GMT
zone word decode to values the translator does not give. A negative zone with a
zero hour (GMT-00:30, UTC-0:30) loses its sign, minutes of 60 or more
(UTC+00:60, UTC+01:60) are added instead of dropped, a number without a sign
after the word (UTC30) is added as minutes, and with a numeric offset and a
word in one string (+0000 UTC+2) the numeric offset is used instead of the
last zone token. Two of the strings (UTC5 and UTC+2 +0000) are refused as
unparseable.

The new file pins the fourteen values, which are decoded from the trade lists
and not computed by the translator, the build of the emitted C++ for each, and
six neighbouring forms that already agree with TradingView and must not move.

Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
TradingView's exported trade lists for fourteen date strings with a UTC or GMT
zone word decode to values the translator did not give. The zone word is now
read the way those lists show:

- a signed zone keeps its sign even when its hour is zero (GMT-00:30 and
  UTC-0:30 are west of UTC; the sign was lost in an int conversion of "-00");
- minutes of 60 or more are dropped and the hours kept (UTC+01:60 is UTC+1,
  UTC+00:90 is UTC);
- a number without a sign after the word is ignored (UTC30, UTC5);
- with a numeric offset and a word in one string the last zone token decides
  (+0000 UTC+2 is UTC+2, UTC+2 +0000 is UTC).

UTC5 and UTC+2 +0000 are no longer refused as unparseable. A string without a
zone word, a zone word with a plain signed offset and a string that carries
only a numeric offset translate to the same value as before.

Co-Authored-By: Claude Sonnet 5.5 <[email protected]>

@graphify-labs graphify-labs Bot left a comment •

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Graphify reviewed this change.

No issues found by static checks — no coupling regressions or blocking issues in the code graph. That is not the same as safe to merge: see what was not checked.

Not checked: tests were not run; formal verification proved 0 of 1 changed function(s) (1 sampled, not proven).

Formal verification. PR-changed functions: 1/1 verified (0 proven, 1 may-equivalent, 0 distinguished) · 0 not verified.


Graphify review — findings

Fixes timestamp("...") date strings with a UTC/GMT zone word so the value matches TradingView. A signed zero-hour offset keeps its sign, minutes of 60 or more are dropped, an unsigned number after the word is ignored, and the last zone token wins when a numeric offset and a word share one string. UTC5 and UTC+2 +0000 now translate instead of being refused. Replaces the datetime.timezone offset with plain arithmetic on the fields read as UTC, so zone words of 24 hours or more (UTC+24, GMT-24, UTC+25) emit a constant instead of stopping with an internal ValueError. The "last token decides" handling only strips a trailing ±HHMM that sits next to the word, so an ISO-style offset followed by a later zone word is the case that can still resolve to the wrong zone.

No blocking issues surfaced. 2 lower-confidence candidates did not survive cross-model review.

Analysis details — impact, health, verification

Impact & health

Graphify review

Impact — 216 functions depend on the 210 functions this change touches.

Health — grade A; no new coupling hotspots.

Verification — 216 functions in the blast radius were not formally verified this run (proofs are advisory here).

Gate & verification

graphify gate

PASS — objectively clean (no health regressions, tests not run — proofs not run this pass (advisory)). Grounded, not self-assessed.

Advisory (not blocking):

  • verification_scope: 216 function(s) in the blast radius were not formally verified this run

Cross-repo impact

Cross-repo impact

No cross-repo impact detected among the indexed peer repo(s).

1 peer repo(s) in this group are not yet indexed — cross-repo impact may be incomplete.

Cross-repo linkage is derived (SCIP-moniker matches are sound; name/contract matches are heuristic) and ADVISORY — it never affects this check's conclusion.

Formal verification

No difference found (not proven): No behavior difference found in \_parse\_pine\_datestring\_ms (not a proof).

The verifier ran both versions of \_parse\_pine\_datestring\_ms on many inputs and saw identical behavior every time. Strong evidence the change is safe, but evidence, not a proof.

Guarantee: Empirical: differential testing (both versions run on many generated inputs). A divergence on an untested input remains possible, so this is 'no counterexample found', not 'proven equivalent'.

Note: An input the sampler did not try could still differ.

@luisleo526
luisleo526 merged commit fc6759b into main Nov 6, 2026
13 checks passed
luisleo526 added a commit that referenced this pull request Nov 6, 2026
Two files conflicted, both in how timestamp(dateString) reads a date string.

pineforge_codegen/codegen/visit_call.py, _parse_pine_datestring_ms: the branch
had added full month names and kept the written sign of a zero-hour zone
(GMT-00:30) by reading the old two-group zone regex; main (#330) replaced that
regex and the zone arithmetic with the zone-word grammar that reads the sign,
hours and minutes separately and lets the last zone token decide. The merge
keeps the branch's full month names (the docstring wording and the %B formats
merged without conflict) and takes main's zone-word code, which already keeps
the written sign of a negative zone with a zero hour, so both behaviours hold.

CHANGELOG.md: both sides added entries under "Unreleased"; all of them are kept.

Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
luisleo526 added a commit that referenced this pull request Nov 6, 2026
After merging codegen main into this branch, four rows of
tests/test_timestamp_zero_hour_offset.py failed: UTC and GMT with +24:00 and
-24:00 were expected to keep raising ValueError. That error came from building
the zone word as a datetime.timezone, which refuses an offset of 24 hours or
more. Main replaced it (the zone-word change in #330): the offset is plain
arithmetic on the date fields, as in TradingView, whose exported trade lists
for UTC+24, GMT-24 and UTC+25 match that rule. So the rows now pin the value
instead of the old failure: the fields read as UTC minus the signed offset
(2024-01-01 00:00 +24:00 is 2023-12-31 00:00 UTC, -24:00 is 2024-01-02 00:00
UTC), with the expected fields written independently of the parser like the
rows above them.

The +-24:00 spelling is the same rule applied; the exported lists cover the
spellings UTC+24 and GMT-24.

Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant