feat(sql): TICK - Temporal Interval Calendar Kit for interval literals#6674
feat(sql): TICK - Temporal Interval Calendar Kit for interval literals#6674bluestreak01 merged 58 commits intomasterfrom
Conversation
Co-Authored-By: Claude Opus 4.5 <[email protected]>
|
Important Review skippedAuto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the You can disable this status message by setting the WalkthroughAdds bracket-expansion parsing to interval handling: new public parseBracketInterval, sink-aware parseAndApplyInterval, recursive bracket expansion with zero-padding and unioning, and updates tests and callers to use the new API. No behavioral changes outside bracket-aware paths reported. Changes
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes Suggested labels
Suggested reviewers
🚥 Pre-merge checks | ✅ 3 | ❌ 2❌ Failed checks (1 warning, 1 inconclusive)
✅ Passed checks (3 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
|
@CodeRabbit review |
✅ Actions performedReview triggered.
|
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Fix all issues with AI agents
In `@core/src/main/java/io/questdb/griffin/model/IntervalUtils.java`:
- Around line 870-872: The bracket expansion loop in IntervalUtils that adds
values (for example the loop using variables value and endValue) can materialize
enormous IntList and the cartesian product computation later uses int
multiplication which may overflow; add a configurable hard cap check before
expanding a range (e.g., compute (long)endValue - (long)value + 1 and compare to
MAX_EXPANSION) and throw a clear SqlException if exceeded, switch any
combination-size arithmetic to long and use Math.multiplyExact to detect
overflow when computing product of sizes (and convert to SqlException on
failure), and apply the same guards to the other expansion site mentioned (lines
around 937-941) so both expansion and cartesian computations are protected.
…rsing - Replace iterative cartesian product with recursive expansion - Remove object allocations (ObjList, IntList, int[]) during parsing - Add recursion depth limit (MAX_BRACKET_DEPTH = 8) - Add sink unwinding via clear() for proper state restoration on error - Remove dead code: redundant !afterT check, unreachable value < 0 check, always-true !Character.isWhitespace check - Add comprehensive test coverage for all code paths including: - Space separator, microseconds, year position brackets - Whitespace in ranges, trailing comma, nested brackets - Overflow errors, negative durations, invalid dates - Semicolon inside brackets, depth limit exceeded Co-Authored-By: Claude Opus 4.5 <[email protected]>
|
@CodeRabbit review |
✅ Actions performedReview triggered.
|
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Fix all issues with AI agents
In
`@core/src/main/java/io/questdb/griffin/model/RuntimeIntervalModelBuilder.java`:
- Around line 137-146: In RuntimeIntervalModelBuilder, bracketed interval
expansion must be rejected when dynamicRangeList is non-empty to avoid breaking
the dynamic 4-long encoding; add a guard before handling bracket syntax (i.e.,
before calling IntervalUtils.parseBracketInterval) that throws a SqlException
with a clear message if dynamicRangeList.size() != 0 (same change also applied
to the other bracket-handling block around the 343-353 range), so bracket syntax
is disallowed in dynamic mode rather than expanding into incompatible 2-long
intervals.
♻️ Duplicate comments (1)
core/src/main/java/io/questdb/griffin/model/IntervalUtils.java (1)
834-842: (Duplicate) Guard against unbounded bracket expansion and overflow.Range expansion still iterates with
intmath; very large ranges can OOM or overflow (e.g.,Integer.MAX_VALUEwrapping). This was already flagged previously—worth addressing with caps/Math.multiplyExactas suggested.
core/src/main/java/io/questdb/griffin/model/RuntimeIntervalModelBuilder.java
Outdated
Show resolved
Hide resolved
|
@CodeRabbit review again, see if there are more bugs |
|
🧠 Learnings used✅ Actions performedReview triggered.
|
|
@CodeRabbit review the bugfix (last commit) |
|
Tip For best results, initiate chat on the files or code changes. 🧩 Analysis chain
🏁 Script executed: Length of output: 311 🏁 Script executed: Length of output: 121 🏁 Script executed: Length of output: 10943
Issue: Inefficient and Potentially Buggy Union LogicIn the new int prevSize;
do {
prevSize = tempList.size();
if (tempList.size() > 2) {
unionInPlace(tempList, tempList.size() - 2);
}
} while (tempList.size() < prevSize);Problems:
Example scenario:
Suggested FixSince the intervals are already sorted by // After sorting, union all intervals in one pass
if (tempList.size() > 2) {
unionInPlace(tempList, 2); // Union first interval with all remaining
}Or better yet, implement a linear merge: // Linear merge of sorted intervals (more explicit and efficient)
int writeIdx = 0;
long currentLo = tempList.getQuick(0);
long currentHi = tempList.getQuick(1);
for (int readIdx = 1; readIdx < bracketCount; readIdx++) {
long nextLo = tempList.getQuick(2 * readIdx);
long nextHi = tempList.getQuick(2 * readIdx + 1);
if (nextLo <= currentHi + 1) {
// Overlapping or adjacent - merge
currentHi = Math.max(currentHi, nextHi);
} else {
// Gap found - write current interval and start new one
tempList.setQuick(2 * writeIdx, currentLo);
tempList.setQuick(2 * writeIdx + 1, currentHi);
writeIdx++;
currentLo = nextLo;
currentHi = nextHi;
}
}
// Write final interval
tempList.setQuick(2 * writeIdx, currentLo);
tempList.setQuick(2 * writeIdx + 1, currentHi);
tempList.setPos(2 * (writeIdx + 1));Positive Aspects✅ The fix correctly identifies and addresses the root cause (unsorted bracket values) The tests should catch any issues with the current implementation, but I'd recommend simplifying the union logic for clarity and correctness. 🧠 Learnings used |
|
@CodeRabbit review one more time, check if the issue was fixed in 01197e4 |
|
Tip For best results, initiate chat on the files or code changes. 🧩 Analysis chain🏁 Script executed: Length of output: 3531
The commit addresses all the problems I identified: What Changed ✅Eliminated allocations:
Fixed the buggy union logic:
Correct implementation: // Single linear pass through sorted intervals
int writeIdx = startIndex + 2;
for (int readIdx = startIndex + 2; readIdx < out.size(); readIdx += 2) {
long lo = out.getQuick(readIdx);
long hi = out.getQuick(readIdx + 1);
long prevHi = out.getQuick(writeIdx - 1);
if (lo <= prevHi) {
// Overlapping - extend previous interval
out.setQuick(writeIdx - 1, Math.max(hi, prevHi));
} else {
// Non-overlapping - write new interval
out.setQuick(writeIdx, lo);
out.setQuick(writeIdx + 1, hi);
writeIdx += 2;
}
}
out.setPos(writeIdx);This is exactly the linear merge approach I recommended. It's efficient (O(n) after sorting), correct, allocation-free, and easy to understand. The overlap detection ( Excellent fix! 👍 🧠 Learnings used |
|
@CodeRabbit review the syntax addition and changes |
|
Tip For best results, initiate chat on the files or code changes. 🧩 Analysis chain
🏁 Script executed: Length of output: 3871 🏁 Script executed: Length of output: 0 🏁 Script executed: Length of output: 4524 🏁 Script executed: Length of output: 15420 🏁 Script executed: Length of output: 3217 🏁 Script executed: Length of output: 1966 🏁 Script executed: Length of output: 524 🏁 Script executed: Length of output: 0 🏁 Script executed: Length of output: 0 🏁 Script executed: Length of output: 2891 🏁 Script executed: Length of output: 1522 🏁 Script executed: Length of output: 2052 🏁 Script executed: Length of output: 0 🏁 Script executed: Length of output: 2712 🏁 Script executed: Length of output: 1450 🏁 Script executed: Length of output: 2317 🏁 Script executed: Length of output: 0 🏁 Script executed: Length of output: 1815 🏁 Script executed: Length of output: 535 🏁 Script executed: Length of output: 459 🏁 Script executed: Length of output: 2021
✅ Syntax Design: Intuitive & PowerfulThe bracket syntax is well-designed and covers multiple use cases: 1. Field Expansion (Single Bracket Group)-- Specific values
'2018-01-[10,15]' → Jan 10, Jan 15
-- Ranges with ..
'2018-01-[10..12]' → Jan 10, 11, 12
-- Mixed values and ranges
'2018-01-[5,10..12,20]' → Jan 5, 10, 11, 12, 202. Cartesian Products (Multiple Bracket Groups)-- Multiple fields
'2018-[01,06]-[10,15]' → 4 intervals (Jan 10, Jan 15, Jun 10, Jun 15)
-- Works in any timestamp field (month, day, hour, minute, second, microsecond)
'2018-01-10T[10,14]:30' → 10:30 and 14:303. Date List Syntax (Issue
|
|
@CodeRabbit new feature, pls review |
|
Tip For best results, initiate chat on the files or code changes. 🧩 Analysis chain
🏁 Script executed: Length of output: 3591 🏁 Script executed: Length of output: 11455 🏁 Script executed: Length of output: 8242 🏁 Script executed: Length of output: 2431 🏁 Script executed: Length of output: 5622 🏁 Script executed: Length of output: 67899 🏁 Script executed: Length of output: 281 🏁 Script executed: Length of output: 5566 🏁 Script executed: Length of output: 12986
🎯 Major New Feature: Time List Brackets (commits d0db842, 85aef93, b1cc6c9, 9ea9616)This feature allows specifying multiple complete time values within brackets, providing a clean syntax for expressing multiple times on the same date without repetition. Syntax Design ✅Time List Format: Smart Disambiguation — Time list vs numeric expansion: -- Time list (has ':' inside bracket) - multiple complete time values
'2024-01-15T[09:00,14:30]' → Two intervals at 09:00 and 14:30
-- Numeric expansion (no ':' inside bracket) - expands hour field only
'2024-01-15T[09,14]:30' → Two intervals at 09:30 and 14:30The Composability: Full Feature Integration ✅1. With Date Expansion (Cartesian Product) -- 4 intervals: 2 dates × 2 times
'2024-01-[15,16]T[09:00,18:00];1h'2. With Duration Suffixes -- Two 1-hour intervals starting at different times
'2024-01-15T[09:00,14:30];1h'3. With Timezone (Per-Element and Global) -- Per-element timezone
'2024-01-15T[09:00@UTC,14:30@Europe/London];1h'
-- Global timezone (fallback for elements without per-element TZ)
'2024-01-15T[09:00,14:30]@+02:00;1h'4. With Suffix Bracket Expansion -- Time list + seconds expansion = 4 intervals
'2024-01-15T[09:00,14:30]:[00,30]'
→ 09:00:00, 09:00:30, 14:30:00, 14:30:305. With Multi-Unit Duration '2024-01-15T[09:00,14:00];1h30m15s'6. With Day Filters '2024-01-[01..31]T[09:00,14:00]#Mon,Wed,Fri;6h'7. With ISO Week Dates '2024-W01-1T[09:00,14:00];1h'Implementation Quality ✅Detection Logic (Lines 1369): if (isTimeListBracket(seq, bracketStart, bracketEnd)) {
expandTimeListBracket(...);
return true; // Signal that time list handled TZ internally
}Key Algorithm in
Smart Details:
Test Coverage: Comprehensive (20+ tests) ✅
Excellent error messages:
Real-World Use Cases Unlocked 🎉Trading Hours Across Multiple Times: -- Market open + midday update + close
ts IN '2024-01-[15..19]T[09:30,12:00,16:00]@America/New_York;5m'Multi-Shift Operations: -- Morning, afternoon, night shifts with break periods
ts IN '2024-01-15T[08:00,12:00,16:00,20:00];3h45m'Event Sampling at Specific Times: -- Sample at top of each hour during business hours
ts IN '2024-W01-[1..5]T[09:00,10:00,11:00,12:00,13:00,14:00,15:00,16:00]'Combined with Date Lists and Day Filters: -- Specific dates, specific times, specific days of week
ts IN '[2024-01-15,2024-02-20,2024-03-18]T[09:00,14:00]#Mon,Wed,Fri;2h'🎯 Additional ChangesCommit "rename" (d0db842)Method renamed: This is a great naming choice that reflects the evolution from simple interval parsing to a complete temporal expression language. "TICK (Temporal Interval Calendar Kit)" is a fitting acronym. Commit "unused method" (a22c145)Removed unused 🎉 Summary
Why This is Exceptional
The Complete TICK ExpressionUsers can now write extraordinarily complex temporal queries in a readable syntax: -- The ultimate TICK expression combining ALL features:
ts IN '[2024-W01-[1..5]#Mon,Wed,Fri,$today+1bd]T[09:00,14:00]@America/New_York;6h30m'
-- Translation:
-- - Weekdays (Mon-Fri) of ISO week 1 in 2024
-- - Plus next business day from today (date variable)
-- - Filtered to Mondays, Wednesdays, and Fridays only (day filter)
-- - At 9:00 AM and 2:00 PM New York time (time list with timezone)
-- - Each interval lasting 6.5 hours (multi-unit duration)This PR has evolved from "bracket expansion" to a complete temporal interval query language. The time list feature is the final piece that makes TICK a comprehensive solution for expressing complex time-based queries. No issues found. The implementation is production-ready:
Recommendation: This feature is ready to ship! 🚀 🧠 Learnings used |
|
@CodeRabbit review again, find bugs |
|
Tip For best results, initiate chat on the files or code changes. 🧩 Analysis chain
🏁 Script executed: Length of output: 4058 🏁 Script executed: Length of output: 2519 🏁 Script executed: Length of output: 868 🏁 Script executed: Length of output: 12823 🏁 Script executed: Length of output: 2343 🏁 Script executed: Length of output: 0 🏁 Script executed: Length of output: 14156 I see commit 🏁 Script executed: Length of output: 29970 🏁 Script executed: Length of output: 7121 🏁 Script executed: Length of output: 2822 🏁 Script executed: Length of output: 10451 🏁 Script executed: Length of output: 3326 🏁 Script executed: Length of output: 1648 🏁 Script executed: Length of output: 255 I found the off-by-one bug that was just fixed, and I'm analyzing the codebase for similar issues. Let me investigate further: 🏁 Script executed: Length of output: 122 🏁 Script executed: Length of output: 0 🏁 Script executed: Length of output: 45 🏁 Script executed: Length of output: 5291 🏁 Script executed: Length of output: 0 🏁 Script executed: Length of output: 0 🏁 Script executed: Length of output: 3357 Let me dig deeper into potential bugs: 🏁 Script executed: Length of output: 633 🏁 Script executed: Length of output: 3613 🏁 Script executed: Length of output: 30 🏁 Script executed: Length of output: 0 🏁 Script executed: Length of output: 0 🏁 Script executed: Length of output: 939 🏁 Script executed: Length of output: 1165 Let me look more carefully at potential bugs in the implementation and recent changes: 🏁 Script executed: Length of output: 4999 🏁 Script executed: Length of output: 3937 🏁 Script executed: Length of output: 7719 🏁 Script executed: Length of output: 2970 🏁 Script executed: Length of output: 1858 🏁 Script executed: Length of output: 2862 🏁 Script executed: Length of output: 670
✅ No Major Bugs Found in Core LogicI've examined:
🔍 Observations About Recent Changes1. Test Refactoring (commit
|
|
@CodeRabbit I added new features and test, check for bugs, review |
|
Tip For best results, initiate chat on the files or code changes. 🧩 Analysis chain🏁 Script executed: Length of output: 4255 🏁 Script executed: Length of output: 2038 🏁 Script executed: Length of output: 23795 🏁 Script executed: Length of output: 9444 🏁 Script executed: Length of output: 1432 🏁 Script executed: Length of output: 5360 🏁 Script executed: Length of output: 0 🏁 Script executed: Length of output: 8327 🏁 Script executed: Length of output: 29970 🏁 Script executed: Length of output: 8463
🚨 Critical Bug: GroupByUtf8Sink.zeroPaddedSixPrefix() (Master Merge)The BugIn `@Override`
public long zeroPaddedSixPrefix() {
// MIN_CAPACITY is 8, so it's safe to read 8 bytes even for short varchars.
return Unsafe.getUnsafe().getLong(ptr + HEADER_SIZE) & VARCHAR_INLINED_PREFIX_MASK;
}The problem: If Why This Happens
The FixAdd a null pointer check in `@Override`
public long zeroPaddedSixPrefix() {
if (ptr == 0) {
return 0L; // Empty string has zero prefix
}
// MIN_CAPACITY is 8, so it's safe to read 8 bytes even for short varchars.
return Unsafe.getUnsafe().getLong(ptr + HEADER_SIZE) & VARCHAR_INLINED_PREFIX_MASK;
}Or (more efficient) update final int ll = l.size();
final int rl = r.size();
if (ll == 0 && rl == 0) {
return 0;
}
if (ll == 0) {
return -1;
}
if (rl == 0) {
return 1;
}
final long lPrefix = l.zeroPaddedSixPrefix(); // Now safe
final long rPrefix = r.zeroPaddedSixPrefix();✅ Test Refactoring Analysis (Commit 4162bbe)What ChangedThe test refactoring converted explicit test code to helper methods: Before (-372 lines): final TimestampDriver timestampDriver = timestampType.getDriver();
LongList out = new LongList();
String interval = "2024-01-15T08:00@+02:00";
parseTickExpr(timestampDriver, interval, 0, interval.length(), 0, out, IntervalOperation.INTERSECT);
Assert.assertEquals(4, out.size());
String expected = "...";
TestUtils.assertEquals(expected, intervalToString(timestampDriver, out));After (+146 lines, net -226): assertBracketInterval(
"[{lo=2024-01-15T06:00:00.000000Z, hi=2024-01-15T06:00:59.999999Z}]",
"2024-01-15T08:00@+02:00"
);Benefits ✅
Potential Risk
|
| Issue | Severity | Location | Fix Required |
|---|---|---|---|
| GroupByUtf8Sink ptr == 0 crash | 🚨 CRITICAL | Master merge (not TICK code) | Add null check in zeroPaddedSixPrefix() |
| Test refactoring quality | ✅ Good | TICK tests | None - well done! |
| TICK implementation | ✅ Excellent | All files | None - production ready! |
Recommendations
- Fix the VARCHAR bug immediately — it's a crash risk in production for GroupBy queries with VARCHAR columns
- The TICK implementation is solid — no bugs found in the interval parsing logic
- Test refactoring is excellent — improves maintainability significantly
The VARCHAR bug is NOT in your TICK code — it came from the master branch merge. You should report it to the team or fix it in the master branch before merging this PR.
Would you like me to provide the exact code changes to fix the GroupByUtf8Sink.zeroPaddedSixPrefix() bug, or create a separate issue for it?
🧠 Learnings used
Learnt from: bluestreak01
Repo: questdb/questdb PR: 6673
File: core/src/test/java/io/questdb/test/griffin/WindowJoinTest.java:2616-2618
Timestamp: 2026-01-19T21:43:19.228Z
Learning: In test `testWindowJoinFailsWhenSlaveDoesNotSupportTimeFrames` in `core/src/test/java/io/questdb/test/griffin/WindowJoinTest.java`, the use of `AND` between timestamp equality conditions (e.g., `ts = 'X' AND ts = 'Y'`) is intentional to prevent intrinsic interval creation. This ensures the window join fails as expected when the slave doesn't support time frames, which is necessary after PR `#6673` added OR timestamp intrinsics recognition.
Added a check in c6d0dac |
|
@CodeRabbit can you review the segmentation fault mitigation and see if there are any more bugs? regarding the helper - it is impossible to have different number of intervals produce the same string. Each value in interval is serialised to string sink |
|
Tip For best results, initiate chat on the files or code changes. 🧩 Analysis chain🏁 Script executed: Length of output: 579 🏁 Script executed: Length of output: 0 🏁 Script executed: Length of output: 0 🏁 Script executed: Length of output: 2070 🏁 Script executed: Length of output: 863 🏁 Script executed: Length of output: 2899 🏁 Script executed: Length of output: 896 🏁 Script executed: Length of output: 6120 🏁 Script executed: Length of output: 4200 🏁 Script executed: Length of output: 4910 🏁 Script executed: Length of output: 3542 🏁 Script executed: Length of output: 87 🏁 Script executed: Length of output: 93 🏁 Script executed: Length of output: 0 🏁 Script executed: Length of output: 1132 🏁 Script executed: Length of output: 547 🏁 Script executed: Length of output: 867 🏁 Script executed: Length of output: 2225 🏁 Script executed: Length of output: 0 🏁 Script executed: Length of output: 0 🏁 Script executed: Length of output: 0 🏁 Script executed: Length of output: 344 🏁 Script executed: Length of output: 1619 🏁 Script executed: Length of output: 7021 🏁 Script executed: Length of output: 3587
✅ Segmentation Fault Fix: PerfectThe fix by public long zeroPaddedSixPrefix() {
return ptr != 0 ? Unsafe.getUnsafe().getLong(ptr + HEADER_SIZE) & VARCHAR_INLINED_PREFIX_MASK : 0;
}This prevents the crash when 🔍 Exhaustive Bug Hunt Results: NO BUGS FOUNDI've verified all critical areas: 1. Safety Limits: All Enforced ✅
2. Bounds Validation: All Correct ✅
3. Resource Management: No Leaks ✅
4. Array Access: All Safe ✅
5. Integer Overflow: Protected ✅
6. Error Messages: Comprehensive ✅
🎯 Test Helper ClarificationThanks for the clarification! You're absolutely right:
Since 🎉 Final AssessmentThe TICK implementation is bug-free and production-ready. The code demonstrates:
The segfault fix addresses the only issue found (in master merge, not TICK code). Your implementation has production-grade quality with 89.57% coverage and comprehensive edge case handling. No additional bugs found! 🚀 🧠 Learnings used |
|
|
|
[PR Coverage check]😍 pass : 1443 / 1611 (89.57%) file detail
|
closes #6669
TICK: Temporal Interval Calendar Kit
TICK is a powerful syntax for expressing complex temporal intervals in QuestDB. It enables concise specification of multiple disjoint time intervals with timezone awareness in a single expression.
Features
'2024-01-[10,15,20]''2024-01-[10..15]'..'[2024-01-15,2024-03-20]''T[09:00,14:30,18:00]''T09:00@America/New_York''T[09:00@UTC,14:30@Europe/London]''#workday','#Mon,Wed,Fri'';6h',';30m',';1h30m'';5h3m31s788T897u''2024-W01-1','2024-W[01..04]-[1..5]''[$today]','[$today + 2bd]''[$today..$today+5bd]''[01,06]-[10,15]'Day-of-Week Filter
The
#suffix allows filtering intervals by day of week. This is applied based on local time (before timezone conversion), so Monday inEurope/Londonis correctly identified regardless of UTC offset.Syntax
The day filter appears after the timezone (if present) but before the duration suffix:
Supported Filters
#workday#wd#weekend#Mon#Mon,Wed,FriDay Name Formats
Both 3-letter abbreviations and full names are supported (case-insensitive):
MonMondayTueTuesdayWedWednesdayThuThursdayFriFridaySatSaturdaySunSundayBasic Examples
With Duration Suffix
With Timezone
The day filter is applied based on local time before timezone conversion. This ensures that "Monday" means Monday in the specified timezone, not Monday in UTC.
The syntax order is:
date@timezone#dayFilter;durationWith Date Lists
Use Cases
Error Handling
Empty day filter after '#'Invalid day name: <name>Multi-Unit Duration Format
Duration suffixes now support combining multiple time units in a single expression. This allows precise duration specifications without manual unit conversion.
Syntax
Durations are specified after a semicolon (
;) and consist of one or more<number><unit>pairs concatenated together:Supported Units
y1yM6Mw2wd5dh8hm30ms45sT500Tu250un100nNote: Units are case-sensitive. Use uppercase
Mfor months and uppercaseTfor milliseconds.Basic Examples
Precise Sub-Second Durations
Multi-unit format is especially useful for precise sub-second specifications:
Unit Order
Units can appear in any order, though descending order (largest to smallest) is recommended for readability:
Combining with Other Features
Multi-unit durations work seamlessly with all TICK features:
Calendar Units (Years and Months)
Years (
y) and months (M) are calendar-aware, meaning they account for varying month lengths and leap years:Error Handling
Expected number before unit 'h'Duration not a numberMissing unit at end of durationInvalid duration unit: xBracket Expansion
Basic Syntax
Brackets
[]in interval strings expand to multiple intervals:[a,b,c]2018-01-[10,15,20]→ days 10, 15, 20[a..b]2018-01-[10..12]→ days 10, 11, 12[a,b..c,d]2018-01-[5,10..12,20]→ days 5, 10, 11, 12, 20Whitespace
Whitespace inside brackets is allowed and ignored:
Multiple Bracket Groups (Cartesian Product)
Multiple bracket groups produce the cartesian product of all combinations:
Supported Fields
Brackets can be used in any numeric field of the timestamp:
2018-[01,06]-152018-01-[10,15]2018-01-10T[10,14]:302018-01-10T10:[00,30]2018-01-10T10:30:[00,30]Zero-Padding
Single-digit values are automatically zero-padded based on field position:
With Duration Suffix
Bracket expansion works with QuestDB's interval duration syntax:
Time List Brackets
Time list brackets allow specifying multiple complete time values in a single bracket, where each element contains a colon (
:). This is different from numeric expansion which expands single fields.Key Difference from Numeric Expansion
T[09,14]:30T09:30andT14:30T[09:00,14:30]T09:00andT14:30The presence of
:inside the bracket determines which mode is used.Basic Time List
Multiple Times
Time List Without Duration
Whitespace in Time List
Combined with Date Expansion
Time list brackets work with date field expansion (cartesian product):
Overlapping Intervals Get Merged
When expanded intervals overlap, they are automatically merged:
Time List with Per-Element Timezone
Each time in a time list can have its own timezone using
@:Basic Per-Element Timezone
Named Timezones Per Element
Global Timezone with Time List
A global timezone applies to all times in the list:
Mixed: Per-Element Takes Precedence Over Global
When both per-element and global timezones are present, per-element wins:
This enables flexible scenarios like:
Date List Syntax
When an interval string starts with
[, it is treated as a comma-separated list of date expressions. Each element in the list can itself contain field-expansion brackets.Basic Date List
Date List with Nested Field Expansion
Date list elements can use bracket expansion for field values:
Date List with Time Suffix
A time suffix after the closing bracket applies to all dates:
Date List with Time List in Suffix
Date List with Per-Element Timezones
Each date in a date list can have its own timezone:
Mixed Timezones in Date List
Per-element timezones take precedence over global timezone:
Per-Element Named Timezones with DST
Useful for comparing the same local time across different seasons:
Whitespace Handling
Whitespace inside the date list brackets is trimmed:
Disambiguation
[, ends with]T...or];...or]@...or just][2025-01-01,2025-01-05][, followed by]-or]:etc.[2018,2019]-01-10Timezone Support
The
@timezonesyntax allows you to specify that the time components are in a specific timezone. The timestamps are automatically converted to UTC for storage and querying.Basic Syntax
Timezone Position
The timezone marker
@must appear:#) if present;) if presentDST-Aware Named Timezones
Named timezones automatically handle Daylight Saving Time transitions:
Supported Timezone Formats
+HH:MM@+03:00-HH:MM@-05:00+HHMM@+0300-HHMM@-0500+HH@+03-HH@-05Z@Z@+00:00)UTC@UTCGMT@GMT@Europe/London@America/New_York@Asia/TokyoComplete Usage Examples
Basic Queries
Using Day-of-Week Filters
Using Time Lists
Using Multi-Unit Durations
Date Lists with Time Lists
Timezone-Aware Queries
Multi-Market Global Trading
Complex Schedules
Error Handling
Unclosed '[' in intervalEmpty bracket expansionRange must be ascending: 15..10Expected number in bracket expansionExpected number after '..'Empty date listEmpty element in date listEmpty time list bracketEmpty element in time listinvalid timezone: <tz>Expected number before unit 'h'Duration not a numberMissing unit at end of durationInvalid duration unit: xEmpty day filter after '#'Invalid day name: <name>Test Plan
Bracket Expansion
..syntaxDate Lists
Time Lists
Timezone Support
@+03:00)@-05:00)@Europe/London)Multi-Unit Duration
1h30m)2h15m30s)500T250u)Day-of-Week Filter
#workday,#wd)#weekend)#Mon,#Tue, etc.)#Mon,Wed,Fri)#Monday,#Wednesday)#WEEKEND,#mon)Date Variables
Date variables provide dynamic date references that are resolved at query execution time. This enables writing queries that automatically adapt to the current date without hardcoding specific dates.
Supported Variables
$today2026-01-22T00:00:00$now2026-01-22T14:35:22$yesterday2026-01-21T00:00:00$tomorrow2026-01-23T00:00:00Variables are case-insensitive:
$TODAY,$Today, and$todayare all equivalent.Basic Usage
Date variables are used inside date list brackets
[...]:With Time Suffix
Add a time suffix to specify a particular time on the resolved date:
With Duration Suffix
Combine date variables with duration to create time windows:
Date Arithmetic
Date variables support arithmetic with calendar days (
d) and business days (bd).Calendar Days (
d)Add or subtract calendar days (all 7 days of the week):
Business Days (
bd)Add or subtract business days (Monday through Friday only, skipping Saturday and Sunday):
Business Day Examples
If today is Thursday, January 22, 2026:
$today + 1bd$today + 2bd$today + 3bd$today - 1bd$today - 5bdIf today is Saturday, January 24, 2026:
$today + 1bd$today - 1bdArithmetic with Time and Duration
Combine arithmetic with time suffixes and durations:
Whitespace in Expressions
Whitespace around operators is allowed and ignored:
Date Variable Ranges
Date variables support range expansion with the
..operator, generating multiple intervals from a start date to an end date.Basic Syntax
Range Type Detection
The range type is determined by the end expression:
dbdExamples
'[$today..$today+5d]''[$today..$today+5bd]''[$today-5bd..$today-1bd]''[$yesterday..$tomorrow]''[$today+1bd..$today+10bd]'Business Day Range Behavior
If today is Thursday, January 22, 2026:
With Time Suffix
Add a time suffix that applies to each generated date:
With Timezone
Apply timezone conversion to all generated dates:
With Day-of-Week Filter
Apply a day filter to further restrict results:
Combining All Features
Error Handling
Invalid date range: start is after endEmpty start expression in date rangeEmpty end expression in date rangeUnknown date variable: ...Mixed Date Lists
Date variables can be mixed with static dates in the same date list:
With Common Time Suffix
A time suffix after the closing bracket applies to all dates in the list:
With Timezone
Apply timezone conversion to date variables:
With Day-of-Week Filter
Combine date variables with day-of-week filters:
Complete Usage Examples
Daily Reports
Trading and Market Data
Scheduled Tasks and Monitoring
Time-Based Analysis
Combining with Other TICK Features
Error Handling
Unknown date variable: $invalidExpected '+' or '-' operatorExpected number after operatorInvalid unit, expected 'd' or 'bd'Expected unit 'd' or 'bd' after number🤖 Generated with Claude Code
ISO Week Date Format
The ISO 8601 week date format allows specifying dates using year-week-day notation (
YYYY-Www-D). This is particularly useful for weekly schedules, fiscal calendars, and recurring weekly events.Syntax
YYYY2024WwwW01,W52D1,5,7THH:MM:SST09:00,T14:30:00Basic Examples
With Time Component
Bracket Expansion
ISO week format supports full bracket expansion for weeks and days:
With Duration Suffix
With Timezone
ISO Week Year Boundary
The ISO week year can differ from the calendar year near year boundaries. Week 1 is defined as the week containing the first Thursday of the year.
Week Number Validation
The week number is validated against the actual number of weeks in the ISO week year:
2024-W53is invalid2020-W53is valid2015-W53is validA year has 53 weeks if it starts on Thursday, or if it's a leap year starting on Wednesday.
Day of Week Values
Use Cases
Error Cases
Invalid dateInvalid dateInvalid dateInvalid dateDate Variables
$today$yesterday$tomorrow[$today]T09:30)[$today]T09:30;1h)$today + 5d,$today - 3d)$today + 2bd,$today - 1bd)[$today]T09:00@America/New_York)$TODAY,$ToDay)ISO Week Date Format
2024-W01)2024-W01-1,2024-W01-5,2024-W01-7)2024-W01-1T09:00,2024-W01-1T09:30:15)2024-W[01..04])2024-W01-[1..5])2024-W[01,02]-[1,5])2024-W01-1T09:00;1h)2020-W53)2024-W53)