Time and ranges
Latest stable Based on Bluetape4k release 1.11.0
Problem
Section titled “Problem”The most expensive time-query defects are often boundary mismatches rather than type errors. If one side includes the end and another excludes it, aggregation duplicates or drops events. Treating a zone-less local time as a timeline point adds ambiguity at daylight-saving transitions.
Two independent decisions
Section titled “Two independent decisions”- Time representation: prefer
Instantfor storage/transport and aZonedDateTimeorLocalDatewith explicitZoneIdfor user schedules. - Range boundary: represent endpoint inclusion in the type, then use the same contract in repository queries and tests.
| Type | Notation | Equal start/end | Typical use |
|---|---|---|---|
ClosedClosedRange | [start, end] | One point | Value interval including both endpoints |
ClosedOpenRange | [start, end) | Empty | Time windows and paging cursors |
OpenClosedRange | (start, end] | Empty | Aggregation after a previous checkpoint |
OpenOpenRange | (start, end) | Empty | Mathematical interval excluding both endpoints |
Time-window example
Section titled “Time-window example”import io.bluetape4k.javatimes.toZonedDateTimeimport io.bluetape4k.ranges.closedOpenRangeOfimport java.time.Instantimport java.time.ZoneId
val zone = ZoneId.of("Asia/Seoul")val from = Instant.parse("2026-07-11T15:00:00Z")val until = Instant.parse("2026-07-12T15:00:00Z")val queryWindow = closedOpenRangeOf(from, until)
check(from in queryWindow)check(until !in queryWindow)check(from.toZonedDateTime(zone).toLocalDate().toString() == "2026-07-12")Adjacent [dayStart, nextDayStart) windows do not overlap, making them a good fit for daily queries. Do not derive the next local day with plusSeconds(86400); calculate the start of the next LocalDate in the business zone, then convert it to Instant.
Range-operation contract
Section titled “Range-operation contract”value in rangehonors open/closed endpoints.range.contains(other)checks that the whole other range is included.range.overlaps(other)checks for a real common element. Touching endpoints overlap only when both relevant sides include the value.- Reversed ranges are empty. For equal endpoints, only
[x, x]is non-empty. 1 until 3constructs Core’sClosedOpenRange.
val left = closedOpenRangeOf(0, 5) // [0, 5)val right = closedOpenRangeOf(5, 10) // [5, 10)check(!left.overlaps(right))
val includingFive = closedClosedRangeOf(0, 5)check(includingFive.overlaps(right)) // both ranges contain 5Conversion warning
Section titled “Conversion warning”(0..10).toClosedOpenRange() reuses the ClosedRange.endInclusive value 10 as the new endExclusive. The result is [0, 10), so the previously included 10 is excluded. This conversion does not preserve all integer elements.
If changing endpoint meaning is not the intention, calculate the desired exclusive endpoint and use the factory directly.
Choosing a time type
Section titled “Choosing a time type”| Situation | Representation/tool |
|---|---|
| Database storage, event timestamps, service transport | UTC Instant |
| A user’s “every day at 09:00” rule | LocalTime plus explicit ZoneId |
| Calendar-date business policy | LocalDate; do not convert to Instant too early |
| Offset is part of the protocol | OffsetDateTime |
| Regional timezone rules matter | ZonedDateTime |
Core’s instantOf, toLocalDateTime, toOffsetDateTime, and toZonedDateTime shorten JDK conversions. Helpers without a zone argument default to UTC; pass a zone whenever the value represents user-local time.
Failures and operations
Section titled “Failures and operations”- Never use the system-default timezone for business policy.
- Lock the chosen offset in DST gaps/overlaps with fixture tests.
- Logs and metrics must identify UTC, zone, or offset.
- Verify SQL comparison operators and the
Rangeendpoint contract in the same repository test. - Decide at the API boundary whether empty/reversed ranges mean “no result” or caller error.
Source and representative tests
Section titled “Source and representative tests”Range.ktClosedOpenRange.ktInstantSupport.ktRangeBoundaryTest.ktRangeSupportTest.ktInstantSupportTest.kt
Continue with Validation and invariants for input boundaries and Core recipes for assembly.