All Downloads are FREE. Search and download functionalities are using the official Maven repository.

io.questdb.cairo.mv.TimeZoneIntervalIterator Maven / Gradle / Ivy

There is a newer version: 8.3.2
Show newest version
/*******************************************************************************
 *     ___                  _   ____  ____
 *    / _ \ _   _  ___  ___| |_|  _ \| __ )
 *   | | | | | | |/ _ \/ __| __| | | |  _ \
 *   | |_| | |_| |  __/\__ \ |_| |_| | |_) |
 *    \__\_\\__,_|\___||___/\__|____/|____/
 *
 *  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