Skip to content

Replace the exception-driven datetime format trial loop with tryparse - #105

Merged
tanmaykm merged 1 commit into
JuliaComputing:mainfrom
QXT-Energy:jd/fast-datetime-parse
Aug 21, 2026
Merged

Replace the exception-driven datetime format trial loop with tryparse#105
tanmaykm merged 1 commit into
JuliaComputing:mainfrom
QXT-Energy:jd/fast-datetime-parse

Conversation

@jd-lara

@jd-lara jd-lara commented Aug 21, 2026

Copy link
Copy Markdown
Contributor

Problem

str2zoneddatetime/str2datetime/str2date try each of 18 hardcoded DateFormats in a fixed order via try/catch, moving to the next format on any exception. Any input that only matches a format partway through the list (e.g. RFC3339 with a fractional-second component) throws and catches a real Julia exception on every earlier attempt before the winning one runs.

Measured on RFC3339 timestamps (Julia 1.12, TimeZones 1.22): str2zoneddatetime took ~430 µs (no fraction) to ~750 µs (ms fraction), and up to ~1.8 ms in the worst case (18 failed formats, then a Date fallback) — vs ~1 µs for a single successful tryparse. This function runs once per timestamp field on every OpenAPI deserialization; in a real time-series-heavy workload it was ~92% of a warm document export/import cycle.

Fix

Replace try/catch with tryparse(T, str, fmt) — Dates/TimeZones already support this for T<:TimeType, returning nothing on failure instead of throwing. Same trial order, same acceptance, zero exceptions on failing attempts.

str2zoneddatetime additionally gets its own format list (ZONED_DATETIME_FORMATS): 9 of the 18 shared formats have no z specifier and can never build a ZonedDateTime (verified: ZonedDateTime(str, fmt) always throws for those regardless of input), so they're dropped from its trial loop. str2datetime/str2date keep the original, unreordered 18-format list — reordering it caused a real regression on the date-only case (198 ns → 480 ns, since that shape used to hit format #1 immediately), so that list is untouched and the reorder+filter win is confined to the new zoned-only list.

Numbers

case function before after speedup
first-match (date only) str2zoneddatetime 1.795 ms 14.75 µs 122x
first-match (date only) str2datetime 197.8 ns 208.8 ns ~1x (no regression)
RFC3339, no fraction str2zoneddatetime 432.0 µs 904 ns 478x
RFC3339, no fraction str2datetime 424.6 µs 2.01 µs 211x
RFC3339, ms fraction str2zoneddatetime 743.4 µs 1.15 µs 644x
RFC3339, ms fraction str2datetime 763.7 µs 3.33 µs 229x
last-match (1-digit frac) str2zoneddatetime 1.500 ms 3.34 µs 450x
last-match (1-digit frac) str2datetime 1.451 ms 6.23 µs 233x

End-to-end (RTS-GMLC power-system export via a downstream OpenAPI-model pipeline, warm to_file): 848 ms → 142 ms overall (5.97x); the time-series row deserialization segment specifically: 783 ms → 71 ms (11x).

Correctness

  • Full test suite passes unchanged (1191 existing tests).
  • Added @test_throws coverage for the not-parseable-input path (previously uncovered).
  • The existing test_date() combinatorial test (2 dates × 2 separators × 5 time precisions × 4 timezones, through both str2zoneddatetime and str2datetime) already covers every format family and passes unchanged.
  • Reordering is value-safe by construction: wherever two formats in the list can both match the same (post-reduce_to_ms_precision) string, they produce an identical parsed value — so changing trial order only changes how many candidates are tried before success, never which value comes out.

Compat

No dependency/compat changes. tryparse(::Type{T}, str, fmt) where T<:TimeType is Dates stdlib machinery that ZonedDateTime already satisfies via the same Dates.tryparsenext hooks backing the ZonedDateTime(str, fmt) constructor this file already uses — present across the whole TimeZones = "1" range and unaffected by julia = "1.6".

str2zoneddatetime/str2datetime/str2date tried each of 18 hardcoded
DateFormats via try/catch until one worked. For inputs matching a
format partway through the list (any RFC3339 string with fractional
seconds, e.g.), every earlier attempt threw and caught a real
exception before the winning one ran -- 200-600x slower than a
single successful parse.

Swap tryparse (nothing on failure) for try/catch, and give
str2zoneddatetime its own filtered+reordered format list: half the
shared formats have no z specifier and can never build a
ZonedDateTime, so they're dropped from its loop instead of being
tried and discarded every call. str2datetime/str2date keep the
original format order untouched, so no case that used to hit its
match early gets slower.

Acceptance is unchanged: same formats accepted, same values
produced, same error on unparseable input.
@jd-lara

jd-lara commented Aug 21, 2026

Copy link
Copy Markdown
Contributor Author

We are hitting massive performance issues on this. I read the proposed change for 1.0 release but this is a valid fix that can live in the 0.2 world

@tanmaykm
tanmaykm merged commit 9eaa97e into JuliaComputing:main Aug 21, 2026
6 checks passed
jd-lara added a commit to Sienna-Platform/PowerOpenAPIModels that referenced this pull request Aug 21, 2026
0.2.8 carries the tryparse-based datetime parsing (JuliaComputing/OpenAPI.jl#105);
older releases pay an exception-driven format trial per timestamp field.
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.

2 participants