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
timestampanddatetimeatoms become naivedatetime.datetimevalues (tzinfo is None). The same naive meaning applies to Arrowtimestamp[ns]columns, so an atom and a column cell for the same q value compare equal. - A naive
datetimeargument is sent as the q wall-clock time unchanged. An awaredatetimeis converted to UTC first, using its ownutcoffset(). That handles fixed offsets andzoneinfozones correctly across daylight-saving changes. Atzinfowhoseutcoffset()returnsNonecounts as naive. - Python's
datetime,time, andtimedeltastop at microseconds. A qtimestamportimespanatom with a nonzero part below a microsecond raisesValueErrorrather than being truncated. Select the value as a one-row table instead: columns keep nanoseconds. - A q
dateordatetimeatom outside Python's year range of 1 to 9999 raisesOverflowErrorrather than being clamped. - A
datetime.timeargument must have whole milliseconds, because qtimehas millisecond precision. Otherwise xqdb raisesValueError.
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:
| Class | Holds | Used for |
|---|---|---|
XqdbTimestamp | nanoseconds: bigint since the Unix epoch | q timestamp, and q datetime atoms on output |
XqdbDate | value: string in YYYY-MM-DD form | q date, and q month atoms on output |
XqdbTime | nanoseconds: bigint since midnight, a whole number of milliseconds and less than one day | q time, and minute and second atoms on output |
XqdbTimespan | nanoseconds: bigint, signed | q timespan |
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
monthatom, such as2024.02m, becomes the first day of that month:datetime.date(2024, 2, 1)orXqdbDate("2024-02-01"). minuteandsecondatoms becomedatetime.timeorXqdbTime.
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 value | Column or typed list | Python atom | Node.js atom |
|---|---|---|---|
0Nh, 0Ni, 0Nj (short, int, long null) | null | The raw int, for example -9223372036854775808 | The raw number or bigint |
0Wh, 0Wi, 0Wj and their negatives | null | The raw int | The raw number or bigint |
0Ne, 0n (real, float null) | null | nan | NaN |
0w, -0w (float infinity) | inf, -inf | inf, -inf | Infinity, -Infinity |
0Np (timestamp null) | null | datetime(1970, 1, 1), the Unix epoch | XqdbTimestamp(0n), the Unix epoch |
-0Wp | 1707-09-22 00:12:43.145224193 | The Unix epoch | The Unix epoch |
0Wp | 2262-04-11 23:47:16.854775807 | ValueError | XqdbTimestamp holding 9223372036854775807n |
0Nd, 0Wd (date) | null for 0Nd | XqdbError | Rejects with XQDB_CONVERSION |
0Nn (timespan null) | null | ValueError | XqdbTimespan(-9223372036854775808n) |
0Nt, 0Nu, 0Nv (time, minute, second null) | null | XqdbError | Rejects with XQDB_UNSUPPORTED |
:: (generic null) | Not applicable | (), an empty tuple | null |
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:
| Policy | Behavior |
|---|---|
"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.
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 filesimport { 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
strwhen it is valid UTF-8 and asbytesotherwise. - 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.
