
io.questdb.cairo.mv.TimeZoneIntervalIterator Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of questdb Show documentation
Show all versions of questdb Show documentation
QuestDB is high performance SQL time series database
/*******************************************************************************
* ___ _ ____ ____
* / _ \ _ _ ___ ___| |_| _ \| __ )
* | | | | | | |/ _ \/ __| __| | | | _ \
* | |_| | |_| | __/\__ \ |_| |_| | |_) |
* \__\_\\__,_|\___||___/\__|____/|____/
*
* Copyright (c) 2014-2019 Appsicle
* Copyright (c) 2019-2024 QuestDB
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
******************************************************************************/
package io.questdb.cairo.mv;
import io.questdb.griffin.engine.groupby.TimestampSampler;
import io.questdb.griffin.model.IntervalUtils;
import io.questdb.std.LongList;
import io.questdb.std.Numbers;
import io.questdb.std.datetime.TimeZoneRules;
import io.questdb.std.datetime.microtime.Timestamps;
import org.jetbrains.annotations.NotNull;
public class TimeZoneIntervalIterator implements SampleByIntervalIterator {
// Contains intervals with clock shifts, in UTC. Both low and high
// boundaries are inclusive.
//
// Example:
// In 'Europe/Berlin' a backward DST switch happened at '2021-10-31T01:00', UTC.
// Namely, at 03:00, local time, the clocks are shifted one hour back.
// This means that 02:00-03:00 local time interval happens twice:
// at 00:00-01:00 and at 01:00-02:00, UTC time. Then, if the sampling interval
// is '1m', 02:00-02:01 local time interval corresponds to two UTC intervals:
// 00:00-00:01 and 01:00-01:01. Thus, we want to include the whole 00:00-02:00
// UTC time interval into a single iteration. As a result, the 02:00-03:00 local
// time interval will be stored in the list.
//
// Similar to local shifts, this list also contains local time "gaps" that happen
// due to forward clock shifts.
//
// Example:
// In 'Europe/Berlin' a forward DST switch happened at '2021-03-28T01:00', UTC.
// Namely, at 02:00, local time, the clocks are shifted one hour ahead.
// This means that 02:00-03:00 local time interval never happens. We need to
// avoid iterating in local time over this interval as such nonexistent timestamps
// are converted to the next UTC hour. As a result, the 02:00-03:00 local time
// interval will be stored in the list.
private final LongList localShifts = new LongList();
private long localMaxTimestamp;
private long localMinTimestamp;
private long localTimestampHi;
private TimestampSampler sampler;
// index of next shift interval to check against
private int shiftIndex;
// time zone offset active up to the shiftIndex interval
private long shiftOffset;
private int step;
private TimeZoneRules tzRules;
private long utcMaxTimestamp; // computed from localMaxTimestamp
private long utcMinTimestamp; // computed from localMinTimestamp
private long utcTimestampHi; // computed from localTimestampHi
private long utcTimestampLo;
@Override
public long getMaxTimestamp() {
return utcMaxTimestamp;
}
@Override
public long getMinTimestamp() {
return utcMinTimestamp;
}
@Override
public int getStep() {
return step;
}
@Override
public long getTimestampHi() {
return utcTimestampHi;
}
@Override
public long getTimestampLo() {
return utcTimestampLo;
}
@Override
public boolean next() {
if (localTimestampHi == localMaxTimestamp) {
return false;
}
utcTimestampLo = utcTimestampHi;
localTimestampHi = Math.min(sampler.nextTimestamp(localTimestampHi, step), localMaxTimestamp);
if (localTimestampHi == localMaxTimestamp) {
utcTimestampHi = utcMaxTimestamp;
return true;
}
// Make sure to adjust the right boundary in case if we ended up
// in a gap or a backward shift interval.
for (int n = (localShifts.size() >>> 1); shiftIndex < n; shiftIndex++) {
final int idx = shiftIndex << 1;
final long shiftLo = IntervalUtils.getEncodedPeriodLo(localShifts, idx);
if (localTimestampHi <= shiftLo) {
break;
}
final long shiftHi = IntervalUtils.getEncodedPeriodHi(localShifts, idx);
if (localTimestampHi <= shiftHi) { // localTimestampHi > shiftLo is already true
localTimestampHi = sampler.nextTimestamp(sampler.round(shiftHi - 1));
shiftOffset = tzRules.getLocalOffset(shiftHi);
shiftIndex++;
break;
}
shiftOffset = tzRules.getLocalOffset(shiftHi);
}
utcTimestampHi = localTimestampHi - shiftOffset;
return true;
}
public TimeZoneIntervalIterator of(
@NotNull TimestampSampler sampler,
@NotNull TimeZoneRules tzRules,
long fixedOffset,
long minTs,
long maxTs,
int step
) {
this.sampler = sampler;
this.tzRules = tzRules;
sampler.setStart(fixedOffset);
final long localMinTs = minTs + tzRules.getOffset(minTs);
localMinTimestamp = sampler.round(localMinTs);
final long localMaxTs = maxTs + tzRules.getOffset(maxTs);
localMaxTimestamp = sampler.nextTimestamp(sampler.round(localMaxTs));
// Collect shift intervals.
localShifts.clear();
final long limitTs = Timestamps.ceilYYYY(localMaxTimestamp);
long ts = tzRules.getNextDST(Timestamps.floorYYYY(localMinTimestamp));
while (ts < limitTs) {
long offsetBefore = tzRules.getOffset(ts - 1);
long offsetAfter = tzRules.getOffset(ts);
long duration = offsetAfter - offsetBefore;
if (duration < 0) { // backward shift
final long localStart = ts + offsetAfter;
final long localEnd = localStart - duration;
localShifts.add(localStart, localEnd);
} else { // forward shift (gap)
final long localStart = ts + offsetBefore;
final long localEnd = localStart + duration;
// We don't want the gap to be used as the iterated interval, so increment
// the right boundary to force the next sample by bucket to be included.
localShifts.add(localStart, localEnd + 1);
}
ts = tzRules.getNextDST(ts);
}
// Adjust min/max boundaries in case if they're in a backward shift.
localMinTimestamp = adjustLoBoundary(localMinTimestamp);
localMaxTimestamp = adjustHiBoundary(localMaxTimestamp);
utcMinTimestamp = Timestamps.toUTC(localMinTimestamp, tzRules);
utcMaxTimestamp = Timestamps.toUTC(localMaxTimestamp, tzRules);
toTop(step);
return this;
}
@Override
public void toTop(int step) {
this.utcTimestampLo = Numbers.LONG_NULL;
this.localTimestampHi = localMinTimestamp;
this.utcTimestampHi = utcMinTimestamp;
this.shiftIndex = 0;
this.shiftOffset = tzRules.getLocalOffset(localMinTimestamp);
this.step = step;
}
private long adjustHiBoundary(long localTs) {
final int idx = IntervalUtils.findInterval(localShifts, localTs);
if (idx != -1) {
final long shiftHi = IntervalUtils.getEncodedPeriodHi(localShifts, idx << 1);
return sampler.nextTimestamp(sampler.round(shiftHi - 1));
}
return localTs;
}
private long adjustLoBoundary(long localTs) {
final int idx = IntervalUtils.findInterval(localShifts, localTs);
if (idx != -1) {
final long shiftLo = IntervalUtils.getEncodedPeriodLo(localShifts, idx << 1);
return sampler.round(shiftLo);
}
return localTs;
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy