Visitar URL original
asDateTime(format) and date(value, format) keep the wall clock of an offset-bearing string, so the same pattern converts when the schema applies it and does not when the caller names it · Issue #10596 · ArcadeData/arcadedb · GitHub
Skip to content

asDateTime(format) and date(value, format) keep the wall clock of an offset-bearing string, so the same pattern converts when the schema applies it and does not when the caller names it #10596

Closed

Description

@ruispereira

is "the same instant with a +02:00 offset is stored as 03:04:05 from a String and as 01:04:05 from an
OffsetDateTime"; #10498 (CLOSED) is the bulk path rebasing onto the database zone. Both are about the
chain that has NO format named - DateUtils.parseDateTime, the one that carries the OffsetPolicy.
Neither issue names asDateTime(format), asDate(format) or date(value, format), the three sites that
parse with a caller-named pattern and never reach that chain. #8090 (CLOSED) unified WHICH FORMATS the
no-format chain accepts, not what an explicit one does with an offset.

Summary

The rule #10498 installed and wrote into DateUtils's javadoc is: "One rule for an offset-bearing
input, on every write path: the instant it names is stored as the UTC wall clock ... so a client writing
over HTTP or SQL, one writing over Bolt or Java, and the bulk load store one value for one instant."

The rule lives in DateUtils.parseDateTime(database, string) / OffsetPolicy.CONVERT_TO_UTC /
dropZone. A caller that names a pattern does not go there. DateUtils.parse(text, format) is a
one-line LocalDateTime.parse(text, getFormatter(format)): LocalDateTime.from takes the LOCAL fields
of the parse and discards the offset the pattern just read, so the wall clock written is the wall clock
of the text, not of the instant.

The tell is that the SAME pattern obeys the rule when the schema applies it. A schema
dateTimeFormat that carries XXX reaches parseSchemaOrSqlTimestamp, which returns an
OffsetDateTime, which parseDateTime runs through dropZone to UTC. Written out by name in
asDateTime('<the same pattern>'), the same text yields a different instant into the same column.

Secondary: the timezone argument of date(value, format, timezone) is inert on this path too. It is
applied as formatter.withZone(...), which LocalDateTime.parse ignores whenever the pattern supplies
the local fields - which it always does here. date(s, p, 'UTC') and date(s, p) answer the same thing
(shown below), so the option looks like it controls the conversion and does not.

Where (HEAD 8b93bb7)

Repro

arcadedb.dateTimeImplementation = java.time.LocalDateTime (the default), database zone
America/New_York, alter database dateTimeFormat "yyyy/MM/dd HH:mm:ss XXX", a DATETIME property
T.ts. Input '2020/01/02 03:04:05 +02:00', whose instant is 2020-01-02T01:04:05Z. The pattern below
is BOTH the schema's dateTimeFormat and the one named in the call:

statement answer
select '2020/01/02 03:04:05 +02:00'.asDateTime() as r 2020-01-02T01:04:05
select '2020/01/02 03:04:05 +02:00'.asDateTime('yyyy/MM/dd HH:mm:ss XXX') as r 2020-01-02T03:04:05
select date('2020/01/02 03:04:05 +02:00') as r 2020-01-02T01:04:05
select date('2020/01/02 03:04:05 +02:00', 'yyyy/MM/dd HH:mm:ss XXX') as r 2020-01-02T03:04:05
insert into T set k='plain', ts = '2020/01/02 03:04:05 +02:00' then select ts 2020-01-02T01:04:05
insert into T set k='named', ts = '...'.asDateTime('yyyy/MM/dd HH:mm:ss XXX') then select ts 2020-01-02T03:04:05

With the ISO spelling and an explicit ISO-offset pattern, independent of the schema's format and of the
database zone (identical under zone UTC and America/New_York):

statement answer
select '2020-01-02T03:04:05+02:00'.asDateTime() as r 2020-01-02T01:04:05
select '2020-01-02T03:04:05+02:00'.asDateTime("yyyy-MM-dd'T'HH:mm:ssXXX") as r 2020-01-02T03:04:05
select date('2020-01-02T03:04:05+02:00') as r 2020-01-02T01:04:05
select date('2020-01-02T03:04:05+02:00', "yyyy-MM-dd'T'HH:mm:ssXXX") as r 2020-01-02T03:04:05
select date('2020-01-02T03:04:05+02:00', "yyyy-MM-dd'T'HH:mm:ssXXX", 'UTC') as r 2020-01-02T03:04:05 (the zone option changes nothing)

Census of what the rest of the family answers for the same input, all 2020-01-02T01:04:05, i.e. the
rule holds everywhere a pattern is not named: INSERT ... SET ts = <string> (SQL), a bound ?
parameter, an OffsetDateTime/ZonedDateTime/Instant/java.util.Date parameter, openCypher
datetime('<string>'), openCypher SET n.ts = '<string>', DateUtils.parseDateTime called directly,
and the schema-dateTimeFormat parse.

Refutation attempted: that an explicitly named pattern means "read this text literally, offset and all"

Suggested fix

Give DateUtils.parse(text, format) the same OffsetPolicy the rest of DateUtils carries, and have
it run an offset-bearing parse through dropZone exactly as parseDateTime does: parse to a
TemporalAccessor, and if it yielded an offset, convert. SQLMethodAsDate then asks for
KEEP_WALL_CLOCK (its documented rule), SQLMethodAsDateTime and SQLFunctionDate for
CONVERT_TO_UTC. SQLFunctionDate's inline LocalDateTime.parse(...) should call the shared one
rather than hold a third copy.

The guarantee that stops it coming back: make LocalDateTime.parse(CharSequence, DateTimeFormatter)
unreachable outside DateUtils (a source-scan test over the modules, the same shape as the existing
census tests) so that every text-to-DATETIME conversion has to pass through a method that takes an
OffsetPolicy and therefore has to answer the question. Today the rule is a property of one method and
nothing stops a second parse being written next to it - which is how these three got there.

Scope / confidence

Reproduced against HEAD 8b93bb7ee2. Affects any asDateTime(format) / date(value, format) whose
format carries an offset field (X, XXX, Z, ZZZZ, O, VV) and whose input carries one, on
every surface that runs SQL. Not affected: inputs without an offset (no instant to convert), and
asDate(format) whose target has no zone to keep.


Swept clean

Each item below was checked empirically against HEAD 8b93bb7ee2, not only read.

Still present at the current main revision: every code point named above was re-checked there before this was filed.

Activity

  1. added
    engineCore engine: document/schema API
    severity:minorCosmetic, documentation, edge case, or a workaround exists
    on Oct 22, 2026
  2. changed the title [-]asDateTime(format) and `date(value, format)` keep the wall clock of an offset-bearing string, so the SAME pattern stores 03:04:05 when the caller names it and 01:04:05 when the schema applies it[/-] [+]asDateTime(format) and date(value, format) keep the wall clock of an offset-bearing string, so the same pattern converts when the schema applies it and does not when the caller names it[/+] on Oct 22, 2026
  3. added this to the 26.11.1 milestone on Oct 22, 2026
  4. added
    in progressThe team is actively working on this issue
    on Oct 22, 2026
  5. self-assigned this
    on Oct 22, 2026
  6. added a commit that references this issue on Oct 22, 2026
  7. lvca commented on Oct 22, 2026

    @lvca
    Member

    Fixed in #10628 (26.11.1).

    A date format you name yourself now follows the same rule as the schema's own: text carrying an offset or zone is stored as the UTC instant it names, so '2020-01-02T03:04:05+02:00'.asDateTime("yyyy-MM-dd'T'HH:mm:ssXXX") is 2020-01-02T01:04:05. This applies to asDateTime(format), date(value, format), date.parse(), date.fields() and the importer's datetimeFormat. asDate(format) keeps the day written.

    The timezone argument of date(value, format, timezone) was ignored; it is now the zone a text without any zone was written in. Values already stored are not rewritten.


    ❤️ Thanks for helping make ArcadeDB better! ArcadeDB is free and open source, sustained by its community. If it's useful to you or your company, please consider becoming a sponsor to keep development going.

  8. removed
    in progressThe team is actively working on this issue
    on Oct 22, 2026
  9. added a commit that references this issue on Nov 5, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

bugengineCore engine: document/schema APIseverity:minorCosmetic, documentation, edge case, or a workaround existssql

Type

No type

Projects

No projects

    Milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions