Comparing and Sorting

Every comparison of dates goes through one set of rules: == in a script, the predicates (P.eq, P.gt, P.between, P.within, ...), dedup(), group() and group_count() keys, order(), min()/max() and the schema bounds.

datetime: by its instant

Two datetimes are equal when they are the same instant, whatever their offsets (12:00Z equals 14:00+02:00). This is PostgreSQL's equality and TinkerPop's ("dates are evaluated based on the numerical comparison of Unix Epoch time"). The offset is kept and shown, but never compared:

datetime("2023-08-02T12:00Z") == datetime("2023-08-02T14:00+02:00")   // true
g.inject(datetime("2023-08-02T12:00Z"), datetime("2023-08-02T14:00+02:00")).dedup().count().next()   // 1
g.inject(datetime("2023-08-02T14:00+02:00")).is(P.gt(datetime("2023-08-02T11:00Z"))).count().next()   // 1

Of two values with the same instant and different offsets the first one met is kept (dedup(), a group() key); their order in a sort is unspecified. To compare the wall clocks instead, convert first: as_local() gives the wall-clock localdatetime (Time Zones).

duration: field-wise equal, ordered by length

Two durations are equal when their months, days and microseconds are equal: P1M is not P30D, and P1D is not PT24H (a day in a time zone may have 23 or 25 hours). Sorting compares their length as PostgreSQL does for an interval, counting a month as 30 days and a day as 24 hours; equal lengths are ordered by months, then days, then microseconds:

duration("P1M") == duration("P30D")   // false
g.inject(duration("P1M"), duration("P30D"), duration("PT719H")).order().to_list()   // [PT719H, P30D, P1M]

The other types: by calendar and clock

A date, a localdatetime and a time compare by their calendar or clock value.

g.inject(time("10:00")).is(P.between(time("09:00"), time("17:00"))).count().next()   // 1
g.inject(date("2023-08-02")).is(P.within(date("2023-08-02"), date("2023-08-03"))).count().next()   // 1

Different types

Values of different types are never equal, and an ordering predicate between them does not match (no error): a date is not a datetime, and a date is never equal to a string, as a UUID is not. Convert one side first (Conversions).

g.inject(date("2023-08-02")).is(P.eq("2023-08-02")).count().next()   // 0
g.inject(date("2023-08-02")).is(P.lt(datetime("2024-01-01T00:00Z"))).count().next()   // 0
g.inject(date("2023-08-02")).as_date().is(P.lt(datetime("2024-01-01T00:00Z"))).count().next()   // 1

order() of a mixed stream puts the dates after the numbers and before the strings (TinkerPop's orderability places its date type there), in the order date, localdatetime, datetime, time, duration:

g.inject("x", 1, duration("P1D"), time("01:00"), datetime("2023-08-02T00:00Z"), date("2023-08-02")).order().to_list()

Property indexes

A property index on a key holding dates and times serves has() with these same rules: an equality or P.within reads the values equal to the operand (a datetime by its instant, a duration field by field), a range (P.gt, P.between, ...) reads the values of the operand's type in that range. The results are always the same as without the index.

g.create_index(#{label: "person", key: "joined"});
g.v().has("name", "marko").property("joined", datetime("2023-08-02T12:00Z")).iterate();
g.v().has_label("person").has("joined", datetime("2023-08-02T14:00+02:00")).count().next()   // 1