Skip to content

Values, Nulls, and Text ​

Most q values have an obvious Python or JavaScript equivalent. This page covers the cases that do not: time, precision, nulls, and text. The complete tables are in the type mapping.

Time and time zones ​

A q timestamp has no time zone, so xqdb never attaches one.

Python ​

  • q timestamp and datetime atoms become naive datetime.datetime values (tzinfo is None). The same naive meaning applies to Arrow timestamp[ns] columns, so an atom and a column cell for the same q value compare equal.
  • A naive datetime argument is sent as the q wall-clock time unchanged. An aware datetime is converted to UTC first, using its own utcoffset(). That handles fixed offsets and zoneinfo zones correctly across daylight-saving changes. A tzinfo whose utcoffset() returns None counts as naive.
  • Python's datetime, time, and timedelta stop at microseconds. A q timestamp or timespan atom with a nonzero part below a microsecond raises ValueError rather than being truncated. Select the value as a one-row table instead: columns keep nanoseconds.
  • A q date or datetime atom outside Python's year range of 1 to 9999 raises OverflowError rather than being clamped.
  • A datetime.time argument must have whole milliseconds, because q time has millisecond precision. Otherwise xqdb raises ValueError.
python
from datetime import datetime, timezone
from zoneinfo import ZoneInfo

import xqdb

conn = xqdb.Q("localhost", 1800)
naive = conn.sync("2024.01.02D03:04:05.123456")  # datetime(2024, 1, 2, 3, 4, 5, 123456)
utc = naive.replace(tzinfo=timezone.utc)  # only if you know the q process stores UTC

# Aware arguments are converted to UTC; naive arguments are sent as-is.
conn.sync("{x}", datetime(2024, 1, 2, 9, 30, tzinfo=ZoneInfo("America/New_York")))  # 2024.01.02D14:30
conn.disconnect()

Upgrading from 0.1.3

Python releases up to 0.1.3 returned UTC-aware timestamp atoms. Since 0.1.4 they are naive. If your code relied on tzinfo, attach the zone yourself, and only when the q process really stores UTC. A process that stores .z.P holds local wall-clock times.

Node.js ​

Temporal values use small wrapper classes, so nanoseconds never pass through a JavaScript number or Date:

ClassHoldsUsed for
XqdbTimestampnanoseconds: bigint since the Unix epochq timestamp, and q datetime atoms on output
XqdbDatevalue: string in YYYY-MM-DD formq date, and q month atoms on output
XqdbTimenanoseconds: bigint since midnight, a whole number of milliseconds and less than one dayq time, and minute and second atoms on output
XqdbTimespannanoseconds: bigint, signedq timespan
ts
import { Q, XqdbDate, XqdbTime, XqdbTimespan, XqdbTimestamp } from "@xbbg/xqdb";

const q = await Q.connect({ host: "localhost", port: 1800 });
try {
  const stamp = new XqdbTimestamp(1_704_164_645_123_456_789n); // 2024-01-02T03:04:05.123456789
  const noon = new XqdbTime(43_200_000_000_000n);
  const back = new XqdbTimespan(-1n);
  const day = new XqdbDate("2024-01-02");
  console.log(await q.sync("{(x;y;z)}", stamp, noon, back)); // [XqdbTimestamp, XqdbTime, XqdbTimespan]
  console.log(await q.sync("{x+1}", day)); // XqdbDate with value "2024-01-03"
} finally {
  await q.disconnect();
}

JavaScript Date objects are not accepted. Convert them with new XqdbTimestamp(BigInt(date.getTime()) * 1_000_000n).

Month, minute, and second atoms ​

These q types have no exact counterpart, so their atoms decode to the nearest wider type:

  • a month atom, such as 2024.02m, becomes the first day of that month: datetime.date(2024, 2, 1) or XqdbDate("2024-02-01").
  • minute and second atoms become datetime.time or XqdbTime.

minute and second columns decode to Arrow time64[ns]. month lists and columns are not supported in 0.1.8: they raise XqdbError in Python and reject with XQDB_UNSUPPORTED in Node.js. Cast them in q, for example select "d"$month from t, before you fetch them.

Nulls and infinities ​

q stores most nulls and infinities as reserved values inside each type. xqdb maps those values to real nulls in columns and typed lists. Atoms are converted value by value, so their nulls are not recognized.

q valueColumn or typed listPython atomNode.js atom
0Nh, 0Ni, 0Nj (short, int, long null)nullThe raw int, for example -9223372036854775808The raw number or bigint
0Wh, 0Wi, 0Wj and their negativesnullThe raw intThe raw number or bigint
0Ne, 0n (real, float null)nullnanNaN
0w, -0w (float infinity)inf, -infinf, -infInfinity, -Infinity
0Np (timestamp null)nulldatetime(1970, 1, 1), the Unix epochXqdbTimestamp(0n), the Unix epoch
-0Wp1707-09-22 00:12:43.145224193The Unix epochThe Unix epoch
0Wp2262-04-11 23:47:16.854775807ValueErrorXqdbTimestamp holding 9223372036854775807n
0Nd, 0Wd (date)null for 0NdXqdbErrorRejects with XQDB_CONVERSION
0Nn (timespan null)nullValueErrorXqdbTimespan(-9223372036854775808n)
0Nt, 0Nu, 0Nv (time, minute, second null)nullXqdbErrorRejects with XQDB_UNSUPPORTED
:: (generic null)Not applicable(), an empty tuplenull

Fetch nullable values as lists, not atoms

A null timestamp atom decodes as 1970-01-01, which looks like a real value. A null long atom decodes as its sentinel integer, and some null atoms raise errors. When a value can be null, fetch it inside a list or table, for example enlist x or ([] x:enlist x), so the null arrives as a real null.

When you send data, None (Python) or null (Node.js) becomes the generic null ::. Arrow nulls inside a column become the q null of the column's type, such as 0N for longs, 0n for floats, and the null symbol for symbols.

Text and UTF-8 ​

q stores symbols and strings as raw bytes and never checks their encoding. Arrow string columns and Python and JavaScript strings must be valid UTF-8. The symbol_encoding (Python) or symbolEncoding (Node.js) setting decides what xqdb does with text that is not valid UTF-8:

PolicyBehavior
"strict" (default)The whole response fails with an error that names the bad value: XqdbError in Python, XQDB_CONVERSION in Node.js.
"lossy"Each invalid byte sequence becomes U+FFFD (�). Every other value in the response is decoded normally.

The policy covers symbol atoms, symbol lists and columns, char and string columns, lambda source and context, dictionary keys, and table column names. Valid UTF-8 decodes the same way under both policies. q error messages are always decoded lossily, so a bad byte never hides the real error.

python
import xqdb

conn = xqdb.Q("localhost", 1800, symbol_encoding="lossy")
conn.symbol_encoding = "strict"  # can also be changed later
latin1 = xqdb.read_binary6("legacy.bin", symbol_encoding="lossy")  # same option for files
ts
import { Q, readBinary6 } from "@xbbg/xqdb";

const q = await Q.connect({ host: "localhost", port: 1800, symbolEncoding: "lossy" });
const latin1 = await readBinary6("legacy.bin", { symbolEncoding: "lossy" });
await q.disconnect();

Any value other than "strict" or "lossy" is rejected when you set it: ValueError in Python, RangeError in Node.js.

A top-level q string (a char vector) is not affected by the policy:

  • Python returns it as str when it is valid UTF-8 and as bytes otherwise.
  • Node.js always returns a Buffer, so arbitrary bytes survive.

A single q char atom is a one-character str in Python and its byte value as a number in Node.js. Inside Arrow columns, char data must be valid UTF-8, so a q char column holds only ASCII under the strict policy.

When sending, Python str and JavaScript string become symbols, which cannot contain NUL characters. Python bytes and Node.js Buffer or Uint8Array become q strings and keep every byte.

xbbg is independent open-source software and is not affiliated with, endorsed by, sponsored by, or approved by Bloomberg Finance L.P.