# Time and time zones
> How KDB stores times (UTC milliseconds), how to write a local time with its offset (like +05:45), the session's time zone (SET TIME ZONE, --timezone), time zone names, and time ranges with IN '...'.
- Category: Time-series
- Keywords: time, times, UTC, Z, time zone, timezone, summer time, daylight saving, DST, America/New\_York, Europe/London, tzdata, zoneinfo, KDB\_ZONEINFO, SET TIME ZONE, SHOW TIME ZONE, --timezone, KDB\_TIMEZONE, CURRENT\_DATE, today, local time, zone offset, NPT, Nepal time, +05:45, Asia/Kathmandu, Asia/Katmandu, Asia/Kolkata, IST, GMT, daylight saving, TIMESTAMP, DATE, milliseconds, microseconds, epoch, time format, date format, ISO 8601, IN 'interval', time range, interval, trading hours, 20700000, to\_char, market open, market close
- See also: [SAMPLE BY (time-zone)](sample-by.md#time-zone), [WHERE (in-interval)](where.md#in-interval), [WHERE (between)](where.md#between), [Partitions, SORT BY and DEDUP KEY (partition-by)](partitions-and-dedup.md#partition-by), [Date and time functions](functions-date-time.md), [Operators (arithmetic)](operators.md#arithmetic), [Column types](column-types.md)
KDB keeps every time in **UTC** (world time, the time at longitude 0). A local time is UTC plus or minus an offset: New York in summer is `-04:00`, Tokyo is `+09:00`. Most examples here use Nepal time (NPT), which is **5 hours 45 minutes ahead** of UTC and has no daylight saving time. So 11:00 in Kathmandu is 05:15 UTC.
Three rules cover almost everything:
1. When you **write** a local time, add its zone offset, for example `+05:45`: `'2026-09-10T11:00:00+05:45'`.
2. A time **without** a zone is UTC: `'2026-09-10 11:00:00'` is 11:00 UTC, which is 16:45 in Nepal.
3. KDB always **shows** times in UTC, with a `Z` at the end.
To make `CURRENT_DATE`, `::date`, `date_trunc` and `EXTRACT` work on the Nepal clock, set the [session's time zone](#session-time-zone): `SET TIME ZONE 'Asia/Kathmandu'`, or start the server with `--timezone Asia/Kathmandu`.
**Example: A trading day: 11:00 to 15:00 Nepal time is 05:15 to 09:15 UTC** (sample data: sample)
```sql
SELECT min(time) AS first_minute, max(time) AS last_minute, count(*) AS minutes
FROM ochl_minutes
WHERE symbol = 'CITY' AND time >= '2026-09-10T11:00:00+05:45' AND time < '2026-09-10T15:00:00+05:45';
```
Result:
| first_minute (TIMESTAMP) | last_minute (TIMESTAMP) | minutes (LONG) |
|---|---|---|
| 2026-09-10T05:15:00.000Z | 2026-09-10T09:14:00.000Z | 240 |
(1 row)
## Storage
Syntax:
```sql
TIMESTAMP milliseconds since 1970-01-01 00:00:00 UTC
DATE days since 1970-01-01
```
A `TIMESTAMP` column holds one number: milliseconds since the start of 1970 in UTC. It does not keep the time zone you wrote. A `DATE` column holds only a day, with no time and no zone.
- **Returns:** a `TIMESTAMP` is shown as `2026-09-10T05:15:00.000Z`, a `DATE` as `2026-09-10`.
- **NULL:** a time can be `NULL` (no value).
- **Notes:**
- Milliseconds only. More digits after the second are **cut**: `.1234567` becomes `.123`. QuestDB keeps microseconds; KDB does not.
- Partitions (the files of a time-series table) are made by the **UTC** date. See [PARTITION BY](partitions-and-dedup.md#partition-by).
- `ROUND 1d` on a column cuts times to the UTC day when a row is written ([create-table](create-table.md)). `ROUND 1d TIME ZONE 'Asia/Kathmandu'` cuts to Nepal midnight instead ([TIME ZONE](sample-by.md#time-zone)).
- Adding a number to a `TIMESTAMP` adds milliseconds; `TIMESTAMP - TIMESTAMP` gives milliseconds. Adding a number to a `DATE` adds days ([operators](operators.md#arithmetic)).
**Example: The same moment written three ways is one stored value** (sample data: sample)
Setup:
```sql
CREATE TABLE time_test (time TIMESTAMP, note STRING) TIMESERIES(time) PARTITION BY DAY;
INSERT INTO time_test VALUES
('2026-09-10T11:00:00+05:45', 'Nepal time with +05:45'),
('2026-09-10T05:15:00Z', 'UTC with Z'),
(1789017300000, 'milliseconds since 1970'),
('2026-09-10T05:15:00.1234567Z', 'seven digits: cut to milliseconds');
```
```sql
SELECT time, note FROM time_test;
```
Result:
| time (TIMESTAMP) | note (STRING) |
|---|---|
| 2026-09-10T05:15:00.000Z | Nepal time with +05:45 |
| 2026-09-10T05:15:00.000Z | UTC with Z |
| 2026-09-10T05:15:00.000Z | milliseconds since 1970 |
| 2026-09-10T05:15:00.123Z | seven digits: cut to milliseconds |
(4 rows)
**Example: Milliseconds between two times, and one minute later** (sample data: sample)
```sql
SELECT CAST('2026-09-10T09:15:00Z' AS TIMESTAMP) - CAST('2026-09-10T05:15:00Z' AS TIMESTAMP) AS ms_open,
(CAST('2026-09-10T09:15:00Z' AS TIMESTAMP) - CAST('2026-09-10T05:15:00Z' AS TIMESTAMP)) / 3600000 AS hours_open,
CAST('2026-09-10T11:00:00+05:45' AS TIMESTAMP) + 60000 AS one_minute_later;
```
Result:
| ms_open (LONG) | hours_open (DOUBLE) | one_minute_later (TIMESTAMP) |
|---|---|---|
| 14400000 | 4 | 2026-09-10T05:16:00.000Z |
(1 row)
**Example: A DATE column: days, no time** (sample data: sample)
Setup:
```sql
CREATE TABLE holidays (day DATE, name STRING) TIMESERIES(day) PARTITION BY YEAR;
INSERT INTO holidays VALUES ('2026-10-20', 'Dashain'), ('2026-11-08', 'Tihar');
```
```sql
SELECT day, day + 1 AS next_day, CAST(day AS TIMESTAMP) AS as_timestamp, name FROM holidays;
```
Result:
| day (DATE) | next_day (DATE) | as_timestamp (TIMESTAMP) | name (STRING) |
|---|---|---|---|
| 2026-10-20 | 2026-10-21 | 2026-10-20T00:00:00.000Z | Dashain |
| 2026-11-08 | 2026-11-09 | 2026-11-08T00:00:00.000Z | Tihar |
(2 rows)
## Time text
Syntax:
```sql
'YYYY-MM-DD'
'YYYY-MM-DD HH:MM[:SS[.fff]]'
'YYYY-MM-DDTHH:MM[:SS[.fff]]'
'...Z' | '...+HH:MM' | '...+HHMM' | '...-HH:MM'
number (milliseconds since 1970, UTC)
```
KDB reads a time from text in these forms. It reads them in `INSERT`, `UPDATE`, `CAST`, and when text is compared with a time column in `WHERE`.
| You write | KDB reads it as |
|---|---|
| `'2026-09-10'` | 00:00 UTC of that day |
| `'2026-09-10 11:00'` or `'2026-09-10T11:00:00'` | 11:00 **UTC** (not Nepal time!) |
| `'2026-09-10T11:00:00Z'` | 11:00 UTC |
| `'2026-09-10T11:00:00+05:45'` or `'...+0545'` | 11:00 Nepal time = 05:15 UTC |
| `'2026-09-10T11:00:00-03:00'` | 11:00 at UTC-3 = 14:00 UTC |
| `'2026-09-10T11:00:00.250Z'` | with milliseconds |
| `1789017300000` | milliseconds since 1970 (UTC) |
- **NULL:** `NULL` stays `NULL`.
- **Notes:**
- The year has 4 digits; month, day, hour, minute and second have **2 digits**. `'2026-9-10'` and `'10/09/2026'` are errors.
- `T` or one space goes between the date and the time. No space before the zone: `'... 11:00:00 +05:45'` is an error.
- The date must exist: `'2026-02-30'` and hour `24` are errors.
- Only in `IN '...'` can the text stop after the year or month (`'2026-09'`). In `>=`, `<`, `=` and `BETWEEN` the text needs at least the day.
- Newer KDB builds also read `'+05:45:00'` (with seconds), which PostgreSQL drivers send.
- Change other formats in your program before you send them.
**Example: Many spellings of a time** (sample data: sample)
```sql
SELECT CAST('2026-09-10' AS TIMESTAMP) AS date_only,
CAST('2026-09-10 11:00' AS TIMESTAMP) AS no_zone_is_utc,
CAST('2026-09-10T11:00:00+05:45' AS TIMESTAMP) AS nepal,
CAST('2026-09-10T11:00:00+0545' AS TIMESTAMP) AS nepal_no_colon,
CAST('2026-09-10T11:00:00-03:00' AS TIMESTAMP) AS brazil,
CAST(1789017300000 AS TIMESTAMP) AS from_ms;
```
Result:
| date_only (TIMESTAMP) | no_zone_is_utc (TIMESTAMP) | nepal (TIMESTAMP) | nepal_no_colon (TIMESTAMP) | brazil (TIMESTAMP) | from_ms (TIMESTAMP) |
|---|---|---|---|---|---|
| 2026-09-10T00:00:00.000Z | 2026-09-10T11:00:00.000Z | 2026-09-10T05:15:00.000Z | 2026-09-10T05:15:00.000Z | 2026-09-10T14:00:00.000Z | 2026-09-10T05:15:00.000Z |
(1 row)
**Example: Text compared with a time column is read as a time** (sample data: sample)
```sql
SELECT time, symbol, close
FROM ochl_day
WHERE symbol = 'CITY' AND time >= '2026-09-08' AND time < '2026-09-11';
```
Result:
| time (TIMESTAMP) | symbol (SYMBOL) | close (DOUBLE) |
|---|---|---|
| 2026-09-08T00:00:00.000Z | CITY | 497.5 |
| 2026-09-09T00:00:00.000Z | CITY | 504.6 |
| 2026-09-10T00:00:00.000Z | CITY | 504.9 |
(3 rows)
**Example: Month and day need two digits** (sample data: sample; shows an error)
```sql
SELECT time, close FROM ochl_day WHERE time >= '2026-9-1';
```
Result:
Error: `line 1, column 45: cannot compare TIMESTAMP '2025-12-28T00:00:00.000Z' with STRING '2026-9-1'`
**Example: Day/month/year is not a time** (sample data: sample; shows an error)
```sql
INSERT INTO ochl_day VALUES ('10/09/2026', 'CITY', 1, 1, 1, 1, 1);
```
Result:
Error: `column 'time' (TIMESTAMP): '10/09/2026' is not a time like '2026-09-11 11:00:00' (UTC) or '2026-09-11T11:00:00+05:45'`
**Example: Seconds in the zone, as PostgreSQL drivers send it** (sample data: sample)
```sql
SELECT CAST('2026-09-10T11:00:00+05:45:00' AS TIMESTAMP) AS a;
```
Result:
| a (TIMESTAMP) |
|---|
| 2026-09-10T05:15:00.000Z |
(1 row)
## A local time: Nepal +05:45
Syntax:
```sql
'YYYY-MM-DDTHH:MM:SS+05:45'
time + 20700000 -- the Nepal clock time, still marked Z
SAMPLE BY interval TIME ZONE 'Asia/Kathmandu'
to_char(time, 'YYYY-MM-DD HH24:MI', 'Asia/Kathmandu')
```
The examples use Nepal Standard Time, UTC + 5 hours 45 minutes, all year; any other fixed offset works the same way. 5 h 45 min is **20,700,000 milliseconds**.
| Sample market event | Nepal time | UTC |
|---|---|---|
| Market opens | 11:00 | 05:15 |
| Market closes | 15:00 | 09:15 |
| Nepal midnight | 00:00 | 18:15 of the day before |
- **Notes:**
- **The most common mistake:** a local time written without its offset (here `+05:45`). KDB reads it as UTC, and the row is 5 h 45 min wrong. Fix bad rows with `UPDATE t SET time = time - 20700000 WHERE ...` (check the DEDUP KEY first).
- `hour()`, `day()`, `date_trunc()` and `is_trading_day()` work on the [session's time zone](#session-time-zone), which is **UTC** unless you set one. `time IN '...'` is always UTC. For the Nepal hour in a UTC session write `hour(to_timezone(time, 'Asia/Kathmandu'))`. For Nepal days: `time IN '2026-09-10' TIME ZONE 'Asia/Kathmandu'`, `date_trunc('day', time, 'Asia/Kathmandu')` ([date functions](functions-date-time.md#date_trunc)).
- `time + 20700000` shows the Nepal clock, but the result still ends with `Z`. Use it only for showing or for `hour()`, never to store.
- For candles on the Nepal clock use [SAMPLE BY ... TIME ZONE](sample-by.md#time-zone).
- The whole trading day of the sample data (05:15 to 09:15 UTC) is inside one UTC day, so `time IN '2026-09-10'` finds exactly one trading day.
- `to_char(t, format, 'Asia/Kathmandu')` makes Nepal-time text; it is in newer KDB builds ([date and time functions](functions-date-time.md)).
**Example: Trades between 11:00 and 11:05 Nepal time** (sample data: sample)
```sql
SELECT time, symbol, close
FROM ochl_minutes
WHERE symbol = 'CITY'
AND time >= '2026-09-10T11:00:00+05:45'
AND time < '2026-09-10T11:05:00+05:45';
```
Result:
| time (TIMESTAMP) | symbol (SYMBOL) | close (DOUBLE) |
|---|---|---|
| 2026-09-10T05:15:00.000Z | CITY | 504.4 |
| 2026-09-10T05:16:00.000Z | CITY | 504.6 |
| 2026-09-10T05:17:00.000Z | CITY | 504.2 |
| 2026-09-10T05:18:00.000Z | CITY | 504.4 |
| 2026-09-10T05:19:00.000Z | CITY | 504.5 |
(5 rows)
**Example: The mistake: Nepal time without +05:45 finds nothing** (sample data: sample)
```sql
SELECT count(*) AS rows_found
FROM ochl_minutes
WHERE symbol = 'CITY' AND time >= '2026-09-10 11:00:00' AND time < '2026-09-10 11:05:00';
```
Result:
| rows_found (LONG) |
|---|
| 0 |
(1 row)
**Example: Volume per Nepal clock hour** (sample data: sample)
```sql
SELECT hour(time + 20700000) AS npt_hour, sum(volume) AS volume
FROM ochl_minutes
WHERE symbol = 'CITY' AND time IN '2026-09-10'
GROUP BY hour(time + 20700000)
ORDER BY npt_hour;
```
Result:
| npt_hour (LONG) | volume (DOUBLE) |
|---|---|
| 11 | 47020 |
| 12 | 41230 |
| 13 | 46180 |
| 14 | 48610 |
(4 rows)
**Example: Show a time as Nepal time text** (sample data: sample)
```sql
SELECT timestamp, to_char(timestamp, 'YYYY-MM-DD HH24:MI', 'Asia/Kathmandu') AS nepal_time
FROM live_data
WHERE symbol = 'CITY';
```
Result:
| timestamp (TIMESTAMP) | nepal_time (STRING) |
|---|---|
| 2026-09-10T05:20:00.000Z | 2026-09-10 11:05 |
| 2026-09-10T07:15:00.000Z | 2026-09-10 13:00 |
| 2026-09-10T09:14:00.000Z | 2026-09-10 14:59 |
(3 rows)
**Example: Fix rows that were written without +05:45** (sample data: sample)
Setup:
```sql
CREATE TABLE ticks (time TIMESTAMP, symbol SYMBOL, price DOUBLE) TIMESERIES(time) PARTITION BY DAY;
INSERT INTO ticks VALUES ('2026-09-10 11:00:00', 'CITY', 510), ('2026-09-10 11:01:00', 'CITY', 511);
```
```sql
SELECT time, price FROM ticks;
UPDATE ticks SET time = time - 20700000 WHERE time IN '2026-09-10';
SELECT time, price FROM ticks;
```
Result:
`SELECT time, price FROM ticks`
| time (TIMESTAMP) | price (DOUBLE) |
|---|---|
| 2026-09-10T11:00:00.000Z | 510 |
| 2026-09-10T11:01:00.000Z | 511 |
(2 rows)
`UPDATE ticks SET time = time - 20700000 WHERE time IN '2026-09-10'`
```text
2 row(s) updated
```
`SELECT time, price FROM ticks`
| time (TIMESTAMP) | price (DOUBLE) |
|---|---|
| 2026-09-10T05:15:00.000Z | 510 |
| 2026-09-10T05:16:00.000Z | 511 |
(2 rows)
## The session's time zone: SET TIME ZONE
Syntax:
```sql
SET TIME ZONE 'Asia/Kathmandu' -- also: SET timezone = 'Asia/Kathmandu', SET TIME ZONE '+05:45'
SET TIME ZONE LOCAL -- back to the server's zone (also DEFAULT, RESET TIME ZONE)
SHOW TIME ZONE -- also SHOW timezone, current_setting('TimeZone')
kdb serve --timezone Asia/Kathmandu -- the server's zone: env KDB_TIMEZONE, kdb.conf "timezone"; else the computer's
ALTER USER asha SET timezone = 'Asia/Kathmandu' -- one user's zone: their sessions start on it
X-KDB-Time-Zone: Asia/Kathmandu -- HTTP header: the zone for one request
```
Times are still **stored** in UTC. The time zone only decides on which **day** and at which **clock time** a stored moment falls. A session starts with its user's zone when an admin gave the user one ([ALTER USER ... SET timezone](users-and-roles.md#user-timezone)), else the server's: `kdb serve --timezone`, else **the computer's own zone** (`SHOW TIME ZONE` says which; a container without a zone is UTC). `SET TIME ZONE` changes it for the rest of the session: a PostgreSQL connection, an HTTP session (`X-KDB-Session`), or the rest of one `/exec` request.
| What | In a UTC session | With `SET TIME ZONE 'Asia/Kathmandu'` |
|---|---|---|
| `CURRENT_DATE` | today in UTC | today in Nepal |
| `LOCALTIMESTAMP`, `LOCALTIME`, `CURRENT_TIME` | UTC clock | Nepal clock |
| `t::date`, `CAST(t AS DATE)`, `to_date(t)` | UTC day | Nepal day |
| `date_trunc('day', t)` | UTC midnight | Nepal midnight (18:15 UTC the day before) |
| `EXTRACT(hour FROM t)`, `hour(t)`, `day(t)`, `day_of_week(t)` | UTC | Nepal clock |
| `to_char(t, format)` | UTC | Nepal clock |
| a `DATE` compared with a `TIMESTAMP`: `t >= CURRENT_DATE` | UTC midnight | Nepal midnight |
| `now()::text` over PostgreSQL | `... +00` | `... +05:45` |
- **Returns:** `SHOW TIME ZONE` gives one row with the column `TimeZone`.
- **NULL:** not affected.
- **Notes:**
- What stays in UTC: how times are stored and shown in results (`...Z`), `now()`, time text without an offset (`'2026-09-10 11:00'` is still 11:00 UTC), `time IN '...'`, `SAMPLE BY` (add `TIME ZONE` to it), partitions, and the buckets of a [materialized view](views.md#materialized-view).
- A zone written in a call wins: `date_trunc('day', t, 'UTC')` and `to_char(t, format, 'UTC')` keep UTC.
- A value that is already a clock time is not moved again: `now() AT TIME ZONE 'Asia/Kathmandu'`, `to_timezone(...)` and `LOCALTIMESTAMP`.
- Zones with summer time (`'America/New_York'`, `'Europe/London'`) work too: each moment is shown on the offset in force at that moment ([summer time](#summer-time)).
- The PostgreSQL port tells the driver the zone (`TimeZone`) when it connects and after each `SET TIME ZONE`. With `--pg-timestamptz`, `TIMESTAMP` columns are sent as text on the session's clock, like `2026-09-10 11:00:00+05:45`.
- `--timezone` also sets the clock of timer and backup schedules: `daily 15:10` runs at 15:10 on that clock. Without it, schedules use the machine's clock (UTC in a container).
- An `+05:45` offset means 5 h 45 min **ahead** of UTC, as everywhere in KDB. (PostgreSQL reads `SET TIME ZONE '+05:45'` the other way round.)
**Example: Logins per UTC day and per Nepal day: 18:30 UTC is 00:15 the next day in Nepal** (sample data: sample)
Setup:
```sql
CREATE TABLE logins (time TIMESTAMP, user_name STRING) TIMESERIES(time) PARTITION BY DAY;
INSERT INTO logins VALUES ('2026-09-09T18:00:00Z', 'asha'), ('2026-09-09T18:30:00Z', 'bikash'), ('2026-09-10T03:00:00Z', 'chandra');
```
```sql
SELECT time::date AS utc_day, count(*) AS logins FROM logins GROUP BY 1 ORDER BY 1;
SET TIME ZONE 'Asia/Kathmandu';
SELECT time::date AS nepal_day, count(*) AS logins FROM logins GROUP BY 1 ORDER BY 1;
```
Result:
`SELECT CAST(time AS DATE) AS utc_day, count(*) AS logins FROM logins GROUP BY 1 ORDER BY 1`
| utc_day (DATE) | logins (LONG) |
|---|---|
| 2026-09-09 | 2 |
| 2026-09-10 | 1 |
(2 rows)
`SET timezone = 'Asia/Kathmandu'`
```text
the time zone of this session is Asia/Kathmandu; times are still stored as UTC
```
`SELECT CAST(time AS DATE) AS nepal_day, count(*) AS logins FROM logins GROUP BY 1 ORDER BY 1`
| nepal_day (DATE) | logins (LONG) |
|---|---|
| 2026-09-09 | 1 |
| 2026-09-10 | 2 |
(2 rows)
**Example: Today, the clock and the text of now() on the Nepal clock** (sample data: sample)
```sql
SET TIME ZONE 'Asia/Kathmandu';
SELECT CURRENT_DATE AS today_in_nepal, LOCALTIMESTAMP AS nepal_clock, now() AS utc_now,
now()::text AS as_text, current_setting('TimeZone') AS zone;
SHOW TIME ZONE;
```
Result:
`SET timezone = 'Asia/Kathmandu'`
```text
the time zone of this session is Asia/Kathmandu; times are still stored as UTC
```
`SELECT current_date() AS today_in_nepal, localtimestamp() AS nepal_clock, now() AS utc_now, CAST(now`
| today_in_nepal (DATE) | nepal_clock (TIMESTAMP) | utc_now (TIMESTAMP) | as_text (STRING) | zone (STRING) |
|---|---|---|---|---|
| 2026-10-22 | 2026-10-22T11:29:30.621Z | 2026-10-22T05:44:30.621Z | 2026-10-22 11:29:30.621+05:45 | Asia/Kathmandu |
(1 row)
`SHOW timezone`
| TimeZone (STRING) |
|---|
| Asia/Kathmandu |
(1 row)
**Example: A day on the Nepal clock: DATE '2026-09-10' stands for Nepal midnight, 18:15 UTC on the 9th** (sample data: sample)
Setup:
```sql
CREATE TABLE logins (time TIMESTAMP, user_name STRING) TIMESERIES(time) PARTITION BY DAY;
INSERT INTO logins VALUES ('2026-09-09T18:00:00Z', 'asha'), ('2026-09-09T18:30:00Z', 'bikash'), ('2026-09-10T03:00:00Z', 'chandra');
```
```sql
SET TIME ZONE 'Asia/Kathmandu';
SELECT time, user_name, to_char(time, 'YYYY-MM-DD HH24:MI') AS nepal_clock
FROM logins WHERE time >= DATE '2026-09-10' AND time < DATE '2026-09-11';
```
Result:
`SET timezone = 'Asia/Kathmandu'`
```text
the time zone of this session is Asia/Kathmandu; times are still stored as UTC
```
`SELECT time, user_name, to_char(time, 'YYYY-MM-DD HH24:MI') AS nepal_clock FROM logins WHERE time >=`
| time (TIMESTAMP) | user_name (STRING) | nepal_clock (STRING) |
|---|---|---|
| 2026-09-09T18:30:00.000Z | bikash | 2026-09-10 00:15 |
| 2026-09-10T03:00:00.000Z | chandra | 2026-09-10 08:45 |
(2 rows)
**Example: New York in summer and in winter: noon UTC is 08:00 in July and 07:00 in January** (sample data: sample)
```sql
SET TIME ZONE 'America/New_York';
SELECT to_char(TIMESTAMP '2026-07-01 12:00:00', 'HH24:MI') AS july, to_char(TIMESTAMP '2026-01-15 12:00:00', 'HH24:MI') AS january;
```
Result:
`SET timezone = 'America/New_York'`
```text
the time zone of this session is America/New_York; times are still stored as UTC
```
`SELECT to_char(CAST('2026-07-01 12:00:00' AS TIMESTAMP), 'HH24:MI') AS july, to_char(CAST('2026-01-1`
| july (STRING) | january (STRING) |
|---|---|
| 08:00 | 07:00 |
(1 row)
## Time zone names
Syntax:
```sql
'UTC' | 'GMT' | 'Z' | 'Etc/UTC'
'Asia/Kathmandu' | 'Asia/Katmandu' | 'NPT'
'Asia/Kolkata' | 'Asia/Calcutta' | 'IST' | 'Asia/Colombo'
'Asia/Dhaka' | 'Asia/Thimphu' | 'Asia/Karachi' | 'Asia/Dubai'
'Asia/Bangkok' | 'Asia/Jakarta' | 'Asia/Shanghai' | 'Asia/Hong_Kong'
'Asia/Singapore' | 'Asia/Taipei' | 'Asia/Tokyo' | 'Asia/Seoul'
'+HH:MM' | '-HH:MM'
```
The zone names KDB knows, for `SET TIME ZONE`, `kdb serve --timezone`, `SAMPLE BY ... TIME ZONE`, `time IN '...' TIME ZONE`, `ROUND ... TIME ZONE`, `date_trunc(unit, t, zone)`, `timestamp_floor`, `to_timezone`, `to_utc` and `to_char`.
| Name | Offset from UTC |
|---|---|
| `UTC`, `GMT`, `Z`, `Etc/UTC` | +00:00 |
| `Asia/Kathmandu`, `Asia/Katmandu`, `NPT` | **+05:45** |
| `Asia/Kolkata`, `Asia/Calcutta`, `IST`, `Asia/Colombo` | +05:30 |
| `Asia/Dhaka`, `Asia/Thimphu` | +06:00 |
| `Asia/Karachi` | +05:00 |
| `Asia/Dubai` | +04:00 |
| `Asia/Bangkok`, `Asia/Jakarta` | +07:00 |
| `Asia/Shanghai`, `Asia/Hong_Kong`, `Asia/Singapore`, `Asia/Taipei` | +08:00 |
| `Asia/Tokyo`, `Asia/Seoul` | +09:00 |
| `'+05:45'`, `'-03:00'`, ... | that offset (2-digit hours 00-23, minutes 00-59) |
- **Notes:**
- Names do not care about case: `'asia/kathmandu'` works.
- The names above have a **fixed** offset and need nothing more. Every other name of the IANA time zone list (`'Europe/London'`, `'America/New_York'`, `'Australia/Sydney'`, ...) works too, with its summer time: see [summer time](#summer-time).
- `IST` here means India Standard Time.
**Example: The same hour buckets with a name and with an offset** (sample data: sample)
```sql
SELECT time, count(*) AS minutes FROM ochl_minutes
WHERE symbol = 'COAST' AND time IN '2026-09-10' SAMPLE BY 1h TIME ZONE 'npt';
SELECT time, count(*) AS minutes FROM ochl_minutes
WHERE symbol = 'COAST' AND time IN '2026-09-10' SAMPLE BY 1h TIME ZONE '+05:45';
```
Result:
`SELECT time, count(*) AS minutes FROM ochl_minutes WHERE symbol = 'COAST' AND time IN '2026-09-10' S`
| time (TIMESTAMP) | minutes (LONG) |
|---|---|
| 2026-09-10T05:15:00.000Z | 60 |
| 2026-09-10T06:15:00.000Z | 56 |
| 2026-09-10T07:15:00.000Z | 60 |
| 2026-09-10T08:15:00.000Z | 60 |
(4 rows)
`SELECT time, count(*) AS minutes FROM ochl_minutes WHERE symbol = 'COAST' AND time IN '2026-09-10' S`
| time (TIMESTAMP) | minutes (LONG) |
|---|---|
| 2026-09-10T05:15:00.000Z | 60 |
| 2026-09-10T06:15:00.000Z | 56 |
| 2026-09-10T07:15:00.000Z | 60 |
| 2026-09-10T08:15:00.000Z | 60 |
(4 rows)
## Zones with summer time
A zone with **summer time** (daylight saving time) has two offsets a year: New York is `-05:00` in winter and `-04:00` in summer. KDB takes the rules from the IANA time zone files that every Linux keeps (package `tzdata`), so each moment gets the offset that was in force at that moment, in any year.
| Where | A zone with summer time |
|---|---|
| `SET TIME ZONE`, `kdb serve --timezone`, `X-KDB-Time-Zone` | yes: `CURRENT_DATE`, `::date`, `date_trunc`, `EXTRACT`, `to_char`, a `DATE` met by a `TIMESTAMP` |
| `t AT TIME ZONE 'zone'`, `to_timezone`, `to_utc` | yes |
| `SAMPLE BY ... TIME ZONE 'zone'` | yes: a day bucket is 23 or 25 hours on the days the clocks change |
| [calendars](calendars.md) `TIME ZONE` | yes |
| `ROUND ... TIME ZONE`, `time IN '...' TIME ZONE`, the zone argument of `date_trunc(unit, t, zone)`, `to_char(t, format, zone)`, `timestamp_floor` | not yet: fixed offsets only (an error says so) |
- **Where the files are:** KDB looks in `KDB_ZONEINFO`, `TZDIR`, `/usr/share/zoneinfo`, then a `zoneinfo` folder next to the program. The Docker image has them. Without them a zone with summer time is an error that says where KDB looked; fixed zones always work.
- **A time that happens twice** (the hour after the clocks go back) is the first of the two; **a time that never happens** (the hour skipped when they go forward) is read with the offset from before the change, as in PostgreSQL.
**Example: Day buckets in New York around the March change: 8 March is 23 hours long** (sample data: sample)
```sql
CREATE TABLE ticks_ny (time TIMESTAMP, v INT) TIMESERIES(time);
INSERT INTO ticks_ny VALUES ('2026-03-08T04:30:00Z', 1), ('2026-03-08T06:00:00Z', 1), ('2026-03-09T03:30:00Z', 1), ('2026-03-09T04:30:00Z', 1);
SELECT time AS day_starts_utc, count() AS ticks FROM ticks_ny SAMPLE BY 1d TIME ZONE 'America/New_York';
```
Result:
`CREATE TABLE ticks_ny (`
```text
table 'ticks_ny' created
```
`INSERT INTO ticks_ny VALUES ('2026-03-08T04:30:00Z', 1), ('2026-03-08T06:00:00Z', 1), ('2026-03-09T0`
```text
4 row(s) inserted
```
`SELECT time AS day_starts_utc, count() AS ticks FROM ticks_ny SAMPLE BY 1d TIME ZONE 'America/New_Yo`
| day_starts_utc (TIMESTAMP) | ticks (LONG) |
|---|---|
| 2026-03-07T05:00:00.000Z | 1 |
| 2026-03-08T05:00:00.000Z | 2 |
| 2026-03-09T04:00:00.000Z | 1 |
(3 rows)
**Example: AT TIME ZONE: noon UTC on the New York clock, summer and winter** (sample data: sample)
```sql
SELECT TIMESTAMPTZ '2026-07-01 12:00:00+00' AT TIME ZONE 'America/New_York' AS july,
TIMESTAMPTZ '2026-01-15 12:00:00+00' AT TIME ZONE 'America/New_York' AS january;
```
Result:
| july (TIMESTAMP) | january (TIMESTAMP) |
|---|---|
| 2026-07-01T08:00:00.000Z | 2026-01-15T07:00:00.000Z |
(1 row)
**Example: An offset needs two digits for the hour** (sample data: sample; shows an error)
```sql
SELECT time, count(*) FROM ochl_minutes SAMPLE BY 1h TIME ZONE '+5:45';
```
Result:
Error: `offset '+5:45' must look like '00:15', '+05:45' or '-03:00'`
## IN 'interval' for time ranges
Syntax:
```sql
time_column [NOT] IN 'YYYY'
time_column [NOT] IN 'YYYY-MM'
time_column [NOT] IN 'YYYY-MM-DD'
time_column [NOT] IN 'YYYY-MM-DDTHH[:MM[:SS]]'
time_column [NOT] IN '...+HH:MM'
```
`time IN '2026-09'` is true for every time **inside** that month. The text says how big the range is: a year, a month, a day, an hour, a minute or a second. It is the easy way to filter a time-series table by time, and KDB then reads only the partitions of that range.
| You write | Range (start included, end not included) |
|---|---|
| `time IN '2026'` | 2026-01-01 00:00 UTC to 2027-01-01 00:00 UTC |
| `time IN '2026-09'` | the whole month of September 2026 (UTC) |
| `time IN '2026-09-10'` | the whole UTC day |
| `time IN '2026-09-10T11'` | the hour 11:00 to 12:00 UTC |
| `time IN '2026-09-10T11:05'` | one minute |
| `time IN '2026-09-10+05:45'` | the whole **Nepal** day (18:15 UTC the day before, to 18:15 UTC) |
| `time IN '2026-09-10T12+05:45'` | the hour 12:00 to 13:00 Nepal time |
- **Returns:** `TRUE` or `FALSE`. `NOT IN '...'` is true outside the range.
- **NULL:** a `NULL` time gives `NULL`, so the row is not kept.
- **Notes:**
- Without an offset the range is in **UTC**. With `+05:45` it is in Nepal time: the same text, one more part.
- It works for `TIMESTAMP` and `DATE` columns. For a `DATE`, a day that touches the range counts.
- Better than `BETWEEN` for whole days: `time BETWEEN '2026-09-07' AND '2026-09-09'` **includes** the end moment `2026-09-09T00:00:00` and nothing later on that day.
- QuestDB's forms with `;` work too: `'2026-09-10;1d'` stretches the end by a day, and `'2026-09-10T05:15;4h;1d;5'` repeats a window five days in a row. See [WHERE](where.md#interval-lists).
- The left side must be a time: `symbol IN '2026'` is an error. For a list of values write `IN ('a', 'b')` ([WHERE](where.md#in-interval)).
**Example: A month, a day, an hour** (sample data: sample)
```sql
SELECT count(*) AS days_in_sept FROM ochl_day WHERE symbol = 'CITY' AND time IN '2026-09';
SELECT count(*) AS minutes_on_the_10th FROM ochl_minutes WHERE symbol = 'CITY' AND time IN '2026-09-10';
SELECT count(*) AS minutes_12_to_13_npt FROM ochl_minutes WHERE symbol = 'CITY' AND time IN '2026-09-10T12+05:45';
```
Result:
`SELECT count(*) AS days_in_sept FROM ochl_day WHERE symbol = 'CITY' AND time IN '2026-09'`
| days_in_sept (LONG) |
|---|
| 8 |
(1 row)
`SELECT count(*) AS minutes_on_the_10th FROM ochl_minutes WHERE symbol = 'CITY' AND time IN '2026-09-`
| minutes_on_the_10th (LONG) |
|---|
| 240 |
(1 row)
`SELECT count(*) AS minutes_12_to_13_npt FROM ochl_minutes WHERE symbol = 'CITY' AND time IN '2026-09`
| minutes_12_to_13_npt (LONG) |
|---|
| 60 |
(1 row)
**Example: One minute on the Nepal clock** (sample data: sample)
```sql
SELECT time, symbol, open, close
FROM ochl_minutes
WHERE time IN '2026-09-10T11:00+05:45';
```
Result:
| time (TIMESTAMP) | symbol (SYMBOL) | open (DOUBLE) | close (DOUBLE) |
|---|---|---|---|
| 2026-09-10T05:15:00.000Z | CITY | 504.6 | 504.4 |
| 2026-09-10T05:15:00.000Z | COAST | 380.1 | 380 |
(2 rows)
**Example: BETWEEN '2026-09-09' AND '2026-09-10' misses the trading hours of the 10th** (sample data: sample)
```sql
SELECT count(*) AS with_between FROM ochl_minutes
WHERE symbol = 'COAST' AND time BETWEEN '2026-09-09' AND '2026-09-10';
SELECT count(*) AS two_whole_days FROM ochl_minutes
WHERE symbol = 'COAST' AND time >= '2026-09-09' AND time < '2026-09-11';
```
Result:
`SELECT count(*) AS with_between FROM ochl_minutes WHERE symbol = 'COAST' AND time BETWEEN '2026-09-0`
| with_between (LONG) |
|---|
| 240 |
(1 row)
`SELECT count(*) AS two_whole_days FROM ochl_minutes WHERE symbol = 'COAST' AND time >= '2026-09-09' `
| two_whole_days (LONG) |
|---|
| 476 |
(1 row)
**Example: Everything except one day** (sample data: sample)
```sql
SELECT count(*) AS rows_not_on_the_10th FROM ochl_minutes WHERE time NOT IN '2026-09-10';
```
Result:
| rows_not_on_the_10th (LONG) |
|---|
| 480 |
(1 row)
**Example: Not a time range** (sample data: sample; shows an error)
```sql
SELECT count(*) FROM ochl_day WHERE time IN '2026-09-10 to 2026-09-11';
```
Result:
Error: `line 1, column 42: '2026-09-10 to 2026-09-11' is not a time range like '2026-09', '2026-09-11', '2026-09-11;1d' or '2026-09-11T10:00;30m;1d;5'`
**Example: The left side must be a time** (sample data: sample; shows an error)
```sql
SELECT count(*) FROM ochl_day WHERE symbol IN '2026';
```
Result:
Error: `line 1, column 44: IN '...' needs a time or date on the left`
## Times in results
Syntax:
```sql
TIMESTAMP -> YYYY-MM-DDTHH:MM:SS.fffZ
DATE -> YYYY-MM-DD
```
Every time in a result is shown in **UTC**, with 3 digits of milliseconds and a `Z`. The web console, `kdb sql`, the HTTP API and the PostgreSQL protocol all get UTC times.
- **Notes:**
- Your program changes UTC to Nepal time for the screen (for example in Go: `t.In(time.FixedZone("NPT", 20700))`).
- `SAMPLE BY ... TIME ZONE` buckets are also shown in UTC: the Nepal hour 11:00 is `05:15:00.000Z`.
- `now()` is the current UTC time. It is the same value in the whole statement.
- Over the PostgreSQL protocol a `TIMESTAMP` is sent as `timestamp without time zone` (the value is UTC) unless the server runs with `--pg-timestamptz`. With it, the text is on the [session's clock](#session-time-zone) with its offset.
**Example: Live snapshots were written in Nepal time; they come back in UTC** (sample data: sample)
```sql
SELECT timestamp, symbol, ltp
FROM live_data
WHERE symbol = 'TELCO';
```
Result:
| timestamp (TIMESTAMP) | symbol (SYMBOL) | ltp (DOUBLE) |
|---|---|---|
| 2026-09-10T05:20:00.000Z | TELCO | 860.5 |
| 2026-09-10T07:15:00.000Z | TELCO | 869 |
| 2026-09-10T09:14:00.000Z | TELCO | 897.7 |
(3 rows)
**Example: now() and a DATE** (sample data: sample)
```sql
SELECT now() > '2026-01-01' AS is_after_new_year, CAST('2026-09-10T20:00:00+05:45' AS DATE) AS utc_date;
```
Result:
| is_after_new_year (BOOL) | utc_date (DATE) |
|---|---|
| true | 2026-09-10 |
(1 row)