Skip to content

Dates

FlyQL distinguishes calendar days (Type.Date) from instants-in-time (Type.DateTime) in column schemas, and uses that distinction to drive matcher coercion for ISO-8601 string literals. SQL generators, by contrast, pass string literals through verbatim — the database parses them.

Type.Date — calendar day only. Comparisons truncate to YYYYMMDD. Absorbs raw DB types date / date32.

Type.DateTime — point on the timeline at millisecond resolution. Comparisons happen in UTC milliseconds. Absorbs datetime, datetime64, timestamp, timestamptz, year.

Declaring the semantic type matters for the matcher: a Date column compared against '2026-04-06T15:30:00Z' truncates the record value to its Y/M/D component before comparison, whereas a DateTime column compares down to the millisecond.

If a column declared Type.Date receives a datetime-shaped value (native datetime / time.Time / Date instance, or an ISO string carrying a time component), FlyQL emits a one-time UserWarning (Python) / log line (Go) / console.warn (JS) per column suggesting migration to Type.DateTime.

Compare a date/datetime column to an ISO-8601 string literal. Generators pass the literal through; the database parses it at execute time.

due_date < '2023-12-31'
created_at >= '2023-12-31T00:00:00Z'
event_time >= '2023-01-01' and event_time <= '2023-12-31'

In the matcher, schema-declared Date / DateTime columns drive automatic parsing of ISO-8601 literals AND record values. No explicit conversion function is required for the common case.

The matcher’s iso8601 family is lenient:

  • YYYY-MM-DD — pure calendar date (for Date columns, or midnight-in-column-tz for DateTime columns).
  • YYYY-MM-DDTHH:MM:SS — naive datetime.
  • YYYY-MM-DD HH:MM:SS — naive datetime with space separator (normalised pre-parse).
  • YYYY-MM-DDTHH:MM:SS.ffffff — optional sub-second (ms resolution only — see “Precision” below).
  • Any of the above with a Z or ±HH:MM offset suffix.

A DateTime column declaration may carry optional metadata:

  • tz — IANA timezone name used to interpret naive ISO strings. Falls back to the Evaluator’s default_timezone, then to "UTC".
  • unit"ms" (default), "s", or "ns". Applied to numeric record values; non-numeric values ignore it.
{
"ts": { "type": "datetime", "tz": "Europe/Moscow", "unit": "s" },
"event_day": { "type": "date" }
}

For relative-time queries — “in the last hour”, “since the start of this week”, “since today” — FlyQL provides built-in temporal functions: ago(...), now(), today(), and startOf(...).

created_at > ago(1h)
updated_at >= ago(7d)
expires_at < now()
event_time > startOf('day')
scheduled_for >= today('Europe/Berlin')

The full grammar (units, ordering rules, timezone arguments) is covered in Values & Types — Temporal Functions.

When a naive datetime literal (no offset, no Z) is interpreted in a DST-observant tz, FlyQL picks the unambiguous resolution defined by the spec:

  • Fall-back (ambiguous wall clock) — the earlier occurrence is chosen (fold=0 in Python semantics). Example: '2026-11-01 01:30:00' in America/New_York resolves to 05:30 UTC (EDT), not 06:30 UTC (EST).
  • Spring-forward (non-existent wall clock) — the value is rejected. The matcher returns false for that record (skipped). Example: '2026-03-08 02:30:00' in America/New_York does not exist.

Records that fall into the spring-forward gap are treated as unparseable rather than silently normalized forward.

  • datetime.datetime — tz-aware or naive (naive uses column tz or Evaluator default).
  • datetime.date — calendar day only.
  • pandas.Timestamp — works because it subclasses datetime.
  • ISO-8601 strings — see accepted shapes above.
  • int / float — epoch milliseconds by default; set column unit="s" for epoch seconds or "ns" for nanoseconds.

Unsupported today:

  • numpy.datetime64 — convert before passing to the matcher: pd.Timestamp(value).to_pydatetime() or value.astype("datetime64[ms]").astype(int).

FlyQL’s matcher compares temporal values at millisecond granularity. Sub-millisecond precision in source data is truncated before comparison:

  • Python — int(dt.timestamp() * 1000) drops microseconds.
  • Go — UnixMilli() drops nanoseconds.
  • JavaScript — Date is natively ms.

Users who need nanosecond precision must pre-process records to bigint nanoseconds and declare unit="ns". The JS path uses Number which loses precision above 2^53 — the nanosecond BigInt path is reserved for a follow-up spec.

Numeric record values without column schema are treated as epoch milliseconds. There is no autodetection heuristic — a record {"ts": 1712434800} (epoch seconds, by convention) is interpreted as 1970-01-20 not 2024. Users with epoch-seconds data MUST declare unit="s":

{ "ts": { "type": "datetime", "unit": "s" } }

This is strict-by-design: an autodetection heuristic (e.g. value < 10^10 → seconds) fails at the boundary and on legitimate old-date values, so we prefer explicit declarations over silent guesses.

IntentUseExample
Compare to a fixed calendar date or timestampString literaldue_date < '2023-12-31'
Compare to a sliding time windowago()created_at > ago(1h)
Compare to the current momentnow()expires_at < now()
Compare to today (timezone-aware)today()scheduled_for = today('UTC')
Compare to the start of day / week / monthstartOf()event_time > startOf('week')

String literals stay constant — the same query string returns the same SQL, day after day. Temporal functions are evaluated at execute time, so the same FlyQL query produces a different time window every time it runs.