DecodeDateTime (datetime.c:979-1534): the state machine that turns
lexed fields into a date and time.
It walks the fields once, left to right, and each field CLAIMS the
parts of the result it set. A field that claims something already
claimed is an error -- that single rule is what makes
'2001-02-03 +05 +06' and 'Feb Feb 10 1997' errors rather than
last-one-wins, and it is why fmask and tmask are both needed:
one is everything set so far, the other is what this field set.
Two pieces of state carry ACROSS fields and are the reason this cannot be a fold over independent fields:
ptype is a prefix set by a UNITS or ISOTIME token that changes how
the NEXT field is read. J makes the next number a Julian day, T
makes it a time. It must be consumed by the following field --
a literal ending with a dangling J is an error.
text-month? records that a month arrived as a word, which changes
DecodeNumber's placement rules and enables a retroactive swap.
The zone is deliberately NOT resolved during the walk. An
abbreviation, a zone name and the session default all need the
DATE to resolve against -- an offset is a function of the instant --
so the walk only records WHICH zone, and the resolution happens at
the end, after ValidateDate. Getting this backwards resolves the
zone against the wrong day across a DST boundary.
`DecodeDateTime` (datetime.c:979-1534): the state machine that turns lexed fields into a date and time. It walks the fields once, left to right, and each field CLAIMS the parts of the result it set. A field that claims something already claimed is an error -- that single rule is what makes `'2001-02-03 +05 +06'` and `'Feb Feb 10 1997'` errors rather than last-one-wins, and it is why `fmask` and `tmask` are both needed: one is everything set so far, the other is what this field set. Two pieces of state carry ACROSS fields and are the reason this cannot be a fold over independent fields: `ptype` is a prefix set by a UNITS or ISOTIME token that changes how the NEXT field is read. `J` makes the next number a Julian day, `T` makes it a time. It must be consumed by the following field -- a literal ending with a dangling `J` is an error. `text-month?` records that a month arrived as a word, which changes `DecodeNumber`'s placement rules and enables a retroactive swap. The zone is deliberately NOT resolved during the walk. An abbreviation, a zone name and the session default all need the DATE to resolve against -- an offset is a function of the instant -- so the walk only records WHICH zone, and the resolution happens at the end, after `ValidateDate`. Getting this backwards resolves the zone against the wrong day across a DST boundary.
(decode-datetime fields {:keys [date-order now zone?] :as ctx})DecodeDateTime. Returns
{:dtype :date|:epoch|:late|:early :tm {…} :fields #{…} :zone …},
or throws a dterr.
ctx supplies what the C reads from global state: :date-order
(DateStyle), :now (the transaction timestamp, for now/today),
:zone? (whether this caller accepts a zone at all -- the C's
tzp != NULL, which is how date_in refuses 'J2451187 +05').
The ZONE is returned unresolved, as {:kind :offset|:abbrev|:named …}, because resolving it needs the validated date.
`DecodeDateTime`. Returns
`{:dtype :date|:epoch|:late|:early :tm {…} :fields #{…} :zone …}`,
or throws a `dterr`.
`ctx` supplies what the C reads from global state: `:date-order`
(DateStyle), `:now` (the transaction timestamp, for `now`/`today`),
`:zone?` (whether this caller accepts a zone at all -- the C's
`tzp != NULL`, which is how `date_in` refuses `'J2451187 +05'`).
The ZONE is returned unresolved, as `{:kind :offset|:abbrev|:named
…}`, because resolving it needs the validated date.(decode-time-only fields {:keys [date-order now zone?] :as ctx})DecodeTimeOnly (datetime.c:1838-2216): the same fields, read as a
TIME.
This is a separate function from decode-datetime in PostgreSQL and
it stays separate here. The two look similar enough to invite
factoring and they differ in a dozen places, every one of which
changes an answer:
The number thresholds are different. DecodeNumberField is reached
at flen > 4 here and at flen >= 6 there, and every call passes
fmask | DTK_DATE_M -- pretending the date is already complete. That
is what makes '040506' a TIME to this function and a DATE to the
other, from identical input.
A leading :date field is only read as a date when it is first AND
the field list looks like a date followed by a time -- the C tests
i == 0 && nf >= 2 && (ftype[nf-1] == DTK_DATE || ftype[1] == DTK_TIME), a positional test on the OTHER fields. Otherwise it is a
zone.
Only two RESERV words are allowed: now and allballs. today,
epoch and infinity are format errors for a time -- there is no
infinite time of day.
There is no MONTH case and no DOW case, so a month name or a weekday is a format error rather than being read or dropped.
time_overflows is checked ONCE at the end, after AM/PM, rather
than per field.
And the zone resolution differs: a named zone with a FIXED offset
needs no date, while one with DST rules does -- '12:00 UTC' is
fine and '12:00 America/New_York' is not, because the offset is
not a function of the time alone.
`DecodeTimeOnly` (datetime.c:1838-2216): the same fields, read as a TIME. This is a separate function from `decode-datetime` in PostgreSQL and it stays separate here. The two look similar enough to invite factoring and they differ in a dozen places, every one of which changes an answer: The number thresholds are different. `DecodeNumberField` is reached at `flen > 4` here and at `flen >= 6` there, and every call passes `fmask | DTK_DATE_M` -- pretending the date is already complete. That is what makes `'040506'` a TIME to this function and a DATE to the other, from identical input. A leading `:date` field is only read as a date when it is first AND the field list looks like a date followed by a time -- the C tests `i == 0 && nf >= 2 && (ftype[nf-1] == DTK_DATE || ftype[1] == DTK_TIME)`, a positional test on the OTHER fields. Otherwise it is a zone. Only two RESERV words are allowed: `now` and `allballs`. `today`, `epoch` and `infinity` are format errors for a time -- there is no infinite time of day. There is no MONTH case and no DOW case, so a month name or a weekday is a format error rather than being read or dropped. `time_overflows` is checked ONCE at the end, after AM/PM, rather than per field. And the zone resolution differs: a named zone with a FIXED offset needs no date, while one with DST rules does -- `'12:00 UTC'` is fine and `'12:00 America/New_York'` is not, because the offset is not a function of the time alone.
cljdoc builds & hosts documentation for Clojure/Script libraries
| Ctrl+k | Jump to recent docs |
| ← | Move to previous article |
| → | Move to next article |
| Ctrl+/ | Jump to the search field |