#19781 Refactored Rounding simplify Date Histogram code
Refactored Rounding simplify Date Histogram code
This commit is contained in:
commit
a01475a20b
|
@ -19,10 +19,13 @@
|
|||
package org.elasticsearch.common.rounding;
|
||||
|
||||
import org.elasticsearch.ElasticsearchException;
|
||||
import org.elasticsearch.common.ParseField;
|
||||
import org.elasticsearch.common.io.stream.StreamInput;
|
||||
import org.elasticsearch.common.io.stream.StreamOutput;
|
||||
import org.elasticsearch.common.io.stream.Streamable;
|
||||
import org.elasticsearch.common.unit.TimeValue;
|
||||
import org.joda.time.DateTimeField;
|
||||
import org.joda.time.DateTimeZone;
|
||||
import org.joda.time.IllegalInstantException;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Objects;
|
||||
|
@ -54,103 +57,67 @@ public abstract class Rounding implements Streamable {
|
|||
@Override
|
||||
public abstract int hashCode();
|
||||
|
||||
/**
|
||||
* Rounding strategy which is based on an interval
|
||||
*
|
||||
* {@code rounded = value - (value % interval) }
|
||||
*/
|
||||
public static class Interval extends Rounding {
|
||||
|
||||
static final byte ID = 0;
|
||||
|
||||
public static final ParseField INTERVAL_FIELD = new ParseField("interval");
|
||||
|
||||
private long interval;
|
||||
|
||||
public Interval() { // for serialization
|
||||
public static Builder builder(DateTimeUnit unit) {
|
||||
return new Builder(unit);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new interval rounding.
|
||||
*
|
||||
* @param interval The interval
|
||||
*/
|
||||
public Interval(long interval) {
|
||||
this.interval = interval;
|
||||
public static Builder builder(TimeValue interval) {
|
||||
return new Builder(interval);
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte id() {
|
||||
return ID;
|
||||
public static class Builder {
|
||||
|
||||
private final DateTimeUnit unit;
|
||||
private final long interval;
|
||||
|
||||
private DateTimeZone timeZone = DateTimeZone.UTC;
|
||||
|
||||
public Builder(DateTimeUnit unit) {
|
||||
this.unit = unit;
|
||||
this.interval = -1;
|
||||
}
|
||||
|
||||
public static long roundKey(long value, long interval) {
|
||||
if (value < 0) {
|
||||
return (value - interval + 1) / interval;
|
||||
public Builder(TimeValue interval) {
|
||||
this.unit = null;
|
||||
if (interval.millis() < 1)
|
||||
throw new IllegalArgumentException("Zero or negative time interval not supported");
|
||||
this.interval = interval.millis();
|
||||
}
|
||||
|
||||
public Builder timeZone(DateTimeZone timeZone) {
|
||||
if (timeZone == null) {
|
||||
throw new IllegalArgumentException("Setting null as timezone is not supported");
|
||||
}
|
||||
this.timeZone = timeZone;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Rounding build() {
|
||||
Rounding timeZoneRounding;
|
||||
if (unit != null) {
|
||||
timeZoneRounding = new TimeUnitRounding(unit, timeZone);
|
||||
} else {
|
||||
return value / interval;
|
||||
timeZoneRounding = new TimeIntervalRounding(interval, timeZone);
|
||||
}
|
||||
return timeZoneRounding;
|
||||
}
|
||||
}
|
||||
|
||||
public static long roundValue(long key, long interval) {
|
||||
return key * interval;
|
||||
static class TimeUnitRounding extends Rounding {
|
||||
|
||||
static final byte ID = 1;
|
||||
|
||||
private DateTimeUnit unit;
|
||||
private DateTimeField field;
|
||||
private DateTimeZone timeZone;
|
||||
|
||||
TimeUnitRounding() { // for serialization
|
||||
}
|
||||
|
||||
@Override
|
||||
public long round(long value) {
|
||||
return roundKey(value, interval) * interval;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long nextRoundingValue(long value) {
|
||||
assert value == round(value);
|
||||
return value + interval;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void readFrom(StreamInput in) throws IOException {
|
||||
interval = in.readVLong();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeTo(StreamOutput out) throws IOException {
|
||||
out.writeVLong(interval);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(interval);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (obj == null) {
|
||||
return false;
|
||||
}
|
||||
if (getClass() != obj.getClass()) {
|
||||
return false;
|
||||
}
|
||||
Interval other = (Interval) obj;
|
||||
return Objects.equals(interval, other.interval);
|
||||
}
|
||||
}
|
||||
|
||||
public static class FactorRounding extends Rounding {
|
||||
|
||||
static final byte ID = 7;
|
||||
|
||||
public static final ParseField FACTOR_FIELD = new ParseField("factor");
|
||||
|
||||
private Rounding rounding;
|
||||
|
||||
private float factor;
|
||||
|
||||
FactorRounding() { // for serialization
|
||||
}
|
||||
|
||||
FactorRounding(Rounding rounding, float factor) {
|
||||
this.rounding = rounding;
|
||||
this.factor = factor;
|
||||
TimeUnitRounding(DateTimeUnit unit, DateTimeZone timeZone) {
|
||||
this.unit = unit;
|
||||
this.field = unit.field(timeZone);
|
||||
this.timeZone = timeZone;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -160,29 +127,49 @@ public abstract class Rounding implements Streamable {
|
|||
|
||||
@Override
|
||||
public long round(long utcMillis) {
|
||||
return rounding.round((long) (factor * utcMillis));
|
||||
long rounded = field.roundFloor(utcMillis);
|
||||
if (timeZone.isFixed() == false && timeZone.getOffset(utcMillis) != timeZone.getOffset(rounded)) {
|
||||
// in this case, we crossed a time zone transition. In some edge
|
||||
// cases this will
|
||||
// result in a value that is not a rounded value itself. We need
|
||||
// to round again
|
||||
// to make sure. This will have no affect in cases where
|
||||
// 'rounded' was already a proper
|
||||
// rounded value
|
||||
rounded = field.roundFloor(rounded);
|
||||
}
|
||||
assert rounded == field.roundFloor(rounded);
|
||||
return rounded;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long nextRoundingValue(long value) {
|
||||
return rounding.nextRoundingValue(value);
|
||||
public long nextRoundingValue(long utcMillis) {
|
||||
long floor = round(utcMillis);
|
||||
// add one unit and round to get to next rounded value
|
||||
long next = round(field.add(floor, 1));
|
||||
if (next == floor) {
|
||||
// in rare case we need to add more than one unit
|
||||
next = round(field.add(floor, 2));
|
||||
}
|
||||
return next;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void readFrom(StreamInput in) throws IOException {
|
||||
rounding = Rounding.Streams.read(in);
|
||||
factor = in.readFloat();
|
||||
unit = DateTimeUnit.resolve(in.readByte());
|
||||
timeZone = DateTimeZone.forID(in.readString());
|
||||
field = unit.field(timeZone);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeTo(StreamOutput out) throws IOException {
|
||||
Rounding.Streams.write(rounding, out);
|
||||
out.writeFloat(factor);
|
||||
out.writeByte(unit.id());
|
||||
out.writeString(timeZone.getID());
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(rounding, factor);
|
||||
return Objects.hash(unit, timeZone);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -193,28 +180,31 @@ public abstract class Rounding implements Streamable {
|
|||
if (getClass() != obj.getClass()) {
|
||||
return false;
|
||||
}
|
||||
FactorRounding other = (FactorRounding) obj;
|
||||
return Objects.equals(rounding, other.rounding)
|
||||
&& Objects.equals(factor, other.factor);
|
||||
TimeUnitRounding other = (TimeUnitRounding) obj;
|
||||
return Objects.equals(unit, other.unit) && Objects.equals(timeZone, other.timeZone);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "[" + timeZone + "][" + unit + "]";
|
||||
}
|
||||
}
|
||||
|
||||
public static class OffsetRounding extends Rounding {
|
||||
static class TimeIntervalRounding extends Rounding {
|
||||
|
||||
static final byte ID = 8;
|
||||
static final byte ID = 2;
|
||||
|
||||
public static final ParseField OFFSET_FIELD = new ParseField("offset");
|
||||
private long interval;
|
||||
private DateTimeZone timeZone;
|
||||
|
||||
private Rounding rounding;
|
||||
|
||||
private long offset;
|
||||
|
||||
OffsetRounding() { // for serialization
|
||||
TimeIntervalRounding() { // for serialization
|
||||
}
|
||||
|
||||
public OffsetRounding(Rounding intervalRounding, long offset) {
|
||||
this.rounding = intervalRounding;
|
||||
this.offset = offset;
|
||||
TimeIntervalRounding(long interval, DateTimeZone timeZone) {
|
||||
if (interval < 1)
|
||||
throw new IllegalArgumentException("Zero or negative time interval not supported");
|
||||
this.interval = interval;
|
||||
this.timeZone = timeZone;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -223,30 +213,98 @@ public abstract class Rounding implements Streamable {
|
|||
}
|
||||
|
||||
@Override
|
||||
public long round(long value) {
|
||||
return rounding.round(value - offset) + offset;
|
||||
public long round(long utcMillis) {
|
||||
long timeLocal = timeZone.convertUTCToLocal(utcMillis);
|
||||
long rounded = roundKey(timeLocal, interval) * interval;
|
||||
long roundedUTC;
|
||||
if (isInDSTGap(rounded) == false) {
|
||||
roundedUTC = timeZone.convertLocalToUTC(rounded, true, utcMillis);
|
||||
// check if we crossed DST transition, in this case we want the
|
||||
// last rounded value before the transition
|
||||
long transition = timeZone.previousTransition(utcMillis);
|
||||
if (transition != utcMillis && transition > roundedUTC) {
|
||||
roundedUTC = round(transition - 1);
|
||||
}
|
||||
} else {
|
||||
/*
|
||||
* Edge case where the rounded local time is illegal and landed
|
||||
* in a DST gap. In this case, we choose 1ms tick after the
|
||||
* transition date. We don't want the transition date itself
|
||||
* because those dates, when rounded themselves, fall into the
|
||||
* previous interval. This would violate the invariant that the
|
||||
* rounding operation should be idempotent.
|
||||
*/
|
||||
roundedUTC = timeZone.previousTransition(utcMillis) + 1;
|
||||
}
|
||||
return roundedUTC;
|
||||
}
|
||||
|
||||
private static long roundKey(long value, long interval) {
|
||||
if (value < 0) {
|
||||
return (value - interval + 1) / interval;
|
||||
} else {
|
||||
return value / interval;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine whether the local instant is a valid instant in the given
|
||||
* time zone. The logic for this is taken from
|
||||
* {@link DateTimeZone#convertLocalToUTC(long, boolean)} for the
|
||||
* `strict` mode case, but instead of throwing an
|
||||
* {@link IllegalInstantException}, which is costly, we want to return a
|
||||
* flag indicating that the value is illegal in that time zone.
|
||||
*/
|
||||
private boolean isInDSTGap(long instantLocal) {
|
||||
if (timeZone.isFixed()) {
|
||||
return false;
|
||||
}
|
||||
// get the offset at instantLocal (first estimate)
|
||||
int offsetLocal = timeZone.getOffset(instantLocal);
|
||||
// adjust instantLocal using the estimate and recalc the offset
|
||||
int offset = timeZone.getOffset(instantLocal - offsetLocal);
|
||||
// if the offsets differ, we must be near a DST boundary
|
||||
if (offsetLocal != offset) {
|
||||
// determine if we are in the DST gap
|
||||
long nextLocal = timeZone.nextTransition(instantLocal - offsetLocal);
|
||||
if (nextLocal == (instantLocal - offsetLocal)) {
|
||||
nextLocal = Long.MAX_VALUE;
|
||||
}
|
||||
long nextAdjusted = timeZone.nextTransition(instantLocal - offset);
|
||||
if (nextAdjusted == (instantLocal - offset)) {
|
||||
nextAdjusted = Long.MAX_VALUE;
|
||||
}
|
||||
if (nextLocal != nextAdjusted) {
|
||||
// we are in the DST gap
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long nextRoundingValue(long value) {
|
||||
return rounding.nextRoundingValue(value - offset) + offset;
|
||||
public long nextRoundingValue(long time) {
|
||||
long timeLocal = time;
|
||||
timeLocal = timeZone.convertUTCToLocal(time);
|
||||
long next = timeLocal + interval;
|
||||
return timeZone.convertLocalToUTC(next, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void readFrom(StreamInput in) throws IOException {
|
||||
rounding = Rounding.Streams.read(in);
|
||||
offset = in.readLong();
|
||||
interval = in.readVLong();
|
||||
timeZone = DateTimeZone.forID(in.readString());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeTo(StreamOutput out) throws IOException {
|
||||
Rounding.Streams.write(rounding, out);
|
||||
out.writeLong(offset);
|
||||
out.writeVLong(interval);
|
||||
out.writeString(timeZone.getID());
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(rounding, offset);
|
||||
return Objects.hash(interval, timeZone);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -257,9 +315,8 @@ public abstract class Rounding implements Streamable {
|
|||
if (getClass() != obj.getClass()) {
|
||||
return false;
|
||||
}
|
||||
OffsetRounding other = (OffsetRounding) obj;
|
||||
return Objects.equals(rounding, other.rounding)
|
||||
&& Objects.equals(offset, other.offset);
|
||||
TimeIntervalRounding other = (TimeIntervalRounding) obj;
|
||||
return Objects.equals(interval, other.interval) && Objects.equals(timeZone, other.timeZone);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -274,11 +331,8 @@ public abstract class Rounding implements Streamable {
|
|||
Rounding rounding = null;
|
||||
byte id = in.readByte();
|
||||
switch (id) {
|
||||
case Interval.ID: rounding = new Interval(); break;
|
||||
case TimeZoneRounding.TimeUnitRounding.ID: rounding = new TimeZoneRounding.TimeUnitRounding(); break;
|
||||
case TimeZoneRounding.TimeIntervalRounding.ID: rounding = new TimeZoneRounding.TimeIntervalRounding(); break;
|
||||
case TimeZoneRounding.FactorRounding.ID: rounding = new FactorRounding(); break;
|
||||
case OffsetRounding.ID: rounding = new OffsetRounding(); break;
|
||||
case TimeUnitRounding.ID: rounding = new TimeUnitRounding(); break;
|
||||
case TimeIntervalRounding.ID: rounding = new TimeIntervalRounding(); break;
|
||||
default: throw new ElasticsearchException("unknown rounding id [" + id + "]");
|
||||
}
|
||||
rounding.readFrom(in);
|
||||
|
|
|
@ -1,314 +0,0 @@
|
|||
/*
|
||||
* Licensed to Elasticsearch under one or more contributor
|
||||
* license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright
|
||||
* ownership. Elasticsearch licenses this file to you 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 org.elasticsearch.common.rounding;
|
||||
|
||||
import org.elasticsearch.common.ParseField;
|
||||
import org.elasticsearch.common.io.stream.StreamInput;
|
||||
import org.elasticsearch.common.io.stream.StreamOutput;
|
||||
import org.elasticsearch.common.unit.TimeValue;
|
||||
import org.joda.time.DateTimeField;
|
||||
import org.joda.time.DateTimeZone;
|
||||
import org.joda.time.IllegalInstantException;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* A rounding strategy for dates. It is typically used to group together dates
|
||||
* that are part of the same hour/day/month, taking into account time zones and
|
||||
* daylight saving times.
|
||||
*/
|
||||
public abstract class TimeZoneRounding extends Rounding {
|
||||
public static final ParseField INTERVAL_FIELD = new ParseField("interval");
|
||||
public static final ParseField TIME_ZONE_FIELD = new ParseField("time_zone");
|
||||
|
||||
public static Builder builder(DateTimeUnit unit) {
|
||||
return new Builder(unit);
|
||||
}
|
||||
|
||||
public static Builder builder(TimeValue interval) {
|
||||
return new Builder(interval);
|
||||
}
|
||||
|
||||
public static class Builder {
|
||||
|
||||
private final DateTimeUnit unit;
|
||||
private final long interval;
|
||||
|
||||
private DateTimeZone timeZone = DateTimeZone.UTC;
|
||||
|
||||
private float factor = 1.0f;
|
||||
|
||||
private long offset;
|
||||
|
||||
public Builder(DateTimeUnit unit) {
|
||||
this.unit = unit;
|
||||
this.interval = -1;
|
||||
}
|
||||
|
||||
public Builder(TimeValue interval) {
|
||||
this.unit = null;
|
||||
if (interval.millis() < 1)
|
||||
throw new IllegalArgumentException("Zero or negative time interval not supported");
|
||||
this.interval = interval.millis();
|
||||
}
|
||||
|
||||
public Builder timeZone(DateTimeZone timeZone) {
|
||||
if (timeZone == null) {
|
||||
throw new IllegalArgumentException("Setting null as timezone is not supported");
|
||||
}
|
||||
this.timeZone = timeZone;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder offset(long offset) {
|
||||
this.offset = offset;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder factor(float factor) {
|
||||
this.factor = factor;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Rounding build() {
|
||||
Rounding timeZoneRounding;
|
||||
if (unit != null) {
|
||||
timeZoneRounding = new TimeUnitRounding(unit, timeZone);
|
||||
} else {
|
||||
timeZoneRounding = new TimeIntervalRounding(interval, timeZone);
|
||||
}
|
||||
if (offset != 0) {
|
||||
timeZoneRounding = new OffsetRounding(timeZoneRounding, offset);
|
||||
}
|
||||
if (factor != 1.0f) {
|
||||
timeZoneRounding = new FactorRounding(timeZoneRounding, factor);
|
||||
}
|
||||
return timeZoneRounding;
|
||||
}
|
||||
}
|
||||
|
||||
static class TimeUnitRounding extends TimeZoneRounding {
|
||||
|
||||
static final byte ID = 1;
|
||||
|
||||
private DateTimeUnit unit;
|
||||
private DateTimeField field;
|
||||
private DateTimeZone timeZone;
|
||||
|
||||
TimeUnitRounding() { // for serialization
|
||||
}
|
||||
|
||||
TimeUnitRounding(DateTimeUnit unit, DateTimeZone timeZone) {
|
||||
this.unit = unit;
|
||||
this.field = unit.field(timeZone);
|
||||
this.timeZone = timeZone;
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte id() {
|
||||
return ID;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long round(long utcMillis) {
|
||||
long rounded = field.roundFloor(utcMillis);
|
||||
if (timeZone.isFixed() == false && timeZone.getOffset(utcMillis) != timeZone.getOffset(rounded)) {
|
||||
// in this case, we crossed a time zone transition. In some edge cases this will
|
||||
// result in a value that is not a rounded value itself. We need to round again
|
||||
// to make sure. This will have no affect in cases where 'rounded' was already a proper
|
||||
// rounded value
|
||||
rounded = field.roundFloor(rounded);
|
||||
}
|
||||
assert rounded == field.roundFloor(rounded);
|
||||
return rounded;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long nextRoundingValue(long utcMillis) {
|
||||
long floor = round(utcMillis);
|
||||
// add one unit and round to get to next rounded value
|
||||
long next = round(field.add(floor, 1));
|
||||
if (next == floor) {
|
||||
// in rare case we need to add more than one unit
|
||||
next = round(field.add(floor, 2));
|
||||
}
|
||||
return next;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void readFrom(StreamInput in) throws IOException {
|
||||
unit = DateTimeUnit.resolve(in.readByte());
|
||||
timeZone = DateTimeZone.forID(in.readString());
|
||||
field = unit.field(timeZone);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeTo(StreamOutput out) throws IOException {
|
||||
out.writeByte(unit.id());
|
||||
out.writeString(timeZone.getID());
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(unit, timeZone);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (obj == null) {
|
||||
return false;
|
||||
}
|
||||
if (getClass() != obj.getClass()) {
|
||||
return false;
|
||||
}
|
||||
TimeUnitRounding other = (TimeUnitRounding) obj;
|
||||
return Objects.equals(unit, other.unit)
|
||||
&& Objects.equals(timeZone, other.timeZone);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "[" + timeZone + "][" + unit +"]";
|
||||
}
|
||||
}
|
||||
|
||||
static class TimeIntervalRounding extends TimeZoneRounding {
|
||||
|
||||
static final byte ID = 2;
|
||||
|
||||
private long interval;
|
||||
private DateTimeZone timeZone;
|
||||
|
||||
TimeIntervalRounding() { // for serialization
|
||||
}
|
||||
|
||||
TimeIntervalRounding(long interval, DateTimeZone timeZone) {
|
||||
if (interval < 1)
|
||||
throw new IllegalArgumentException("Zero or negative time interval not supported");
|
||||
this.interval = interval;
|
||||
this.timeZone = timeZone;
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte id() {
|
||||
return ID;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long round(long utcMillis) {
|
||||
long timeLocal = timeZone.convertUTCToLocal(utcMillis);
|
||||
long rounded = Rounding.Interval.roundValue(Rounding.Interval.roundKey(timeLocal, interval), interval);
|
||||
long roundedUTC;
|
||||
if (isInDSTGap(rounded) == false) {
|
||||
roundedUTC = timeZone.convertLocalToUTC(rounded, true, utcMillis);
|
||||
// check if we crossed DST transition, in this case we want the last rounded value before the transition
|
||||
long transition = timeZone.previousTransition(utcMillis);
|
||||
if (transition != utcMillis && transition > roundedUTC) {
|
||||
roundedUTC = round(transition - 1);
|
||||
}
|
||||
} else {
|
||||
/*
|
||||
* Edge case where the rounded local time is illegal and landed
|
||||
* in a DST gap. In this case, we choose 1ms tick after the
|
||||
* transition date. We don't want the transition date itself
|
||||
* because those dates, when rounded themselves, fall into the
|
||||
* previous interval. This would violate the invariant that the
|
||||
* rounding operation should be idempotent.
|
||||
*/
|
||||
roundedUTC = timeZone.previousTransition(utcMillis) + 1;
|
||||
}
|
||||
return roundedUTC;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine whether the local instant is a valid instant in the given
|
||||
* time zone. The logic for this is taken from
|
||||
* {@link DateTimeZone#convertLocalToUTC(long, boolean)} for the
|
||||
* `strict` mode case, but instead of throwing an
|
||||
* {@link IllegalInstantException}, which is costly, we want to return a
|
||||
* flag indicating that the value is illegal in that time zone.
|
||||
*/
|
||||
private boolean isInDSTGap(long instantLocal) {
|
||||
if (timeZone.isFixed()) {
|
||||
return false;
|
||||
}
|
||||
// get the offset at instantLocal (first estimate)
|
||||
int offsetLocal = timeZone.getOffset(instantLocal);
|
||||
// adjust instantLocal using the estimate and recalc the offset
|
||||
int offset = timeZone.getOffset(instantLocal - offsetLocal);
|
||||
// if the offsets differ, we must be near a DST boundary
|
||||
if (offsetLocal != offset) {
|
||||
// determine if we are in the DST gap
|
||||
long nextLocal = timeZone.nextTransition(instantLocal - offsetLocal);
|
||||
if (nextLocal == (instantLocal - offsetLocal)) {
|
||||
nextLocal = Long.MAX_VALUE;
|
||||
}
|
||||
long nextAdjusted = timeZone.nextTransition(instantLocal - offset);
|
||||
if (nextAdjusted == (instantLocal - offset)) {
|
||||
nextAdjusted = Long.MAX_VALUE;
|
||||
}
|
||||
if (nextLocal != nextAdjusted) {
|
||||
// we are in the DST gap
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long nextRoundingValue(long time) {
|
||||
long timeLocal = time;
|
||||
timeLocal = timeZone.convertUTCToLocal(time);
|
||||
long next = timeLocal + interval;
|
||||
return timeZone.convertLocalToUTC(next, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void readFrom(StreamInput in) throws IOException {
|
||||
interval = in.readVLong();
|
||||
timeZone = DateTimeZone.forID(in.readString());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeTo(StreamOutput out) throws IOException {
|
||||
out.writeVLong(interval);
|
||||
out.writeString(timeZone.getID());
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(interval, timeZone);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (obj == null) {
|
||||
return false;
|
||||
}
|
||||
if (getClass() != obj.getClass()) {
|
||||
return false;
|
||||
}
|
||||
TimeIntervalRounding other = (TimeIntervalRounding) obj;
|
||||
return Objects.equals(interval, other.interval)
|
||||
&& Objects.equals(timeZone, other.timeZone);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -24,7 +24,6 @@ import org.apache.lucene.util.CollectionUtil;
|
|||
import org.elasticsearch.common.inject.internal.Nullable;
|
||||
import org.elasticsearch.common.lease.Releasables;
|
||||
import org.elasticsearch.common.rounding.Rounding;
|
||||
import org.elasticsearch.common.rounding.TimeZoneRounding;
|
||||
import org.elasticsearch.common.util.LongHash;
|
||||
import org.elasticsearch.search.DocValueFormat;
|
||||
import org.elasticsearch.search.aggregations.Aggregator;
|
||||
|
@ -45,8 +44,9 @@ import java.util.Map;
|
|||
|
||||
/**
|
||||
* An aggregator for date values. Every date is rounded down using a configured
|
||||
* {@link TimeZoneRounding}.
|
||||
* @see TimeZoneRounding
|
||||
* {@link Rounding}.
|
||||
*
|
||||
* @see Rounding
|
||||
*/
|
||||
class DateHistogramAggregator extends BucketsAggregator {
|
||||
|
||||
|
@ -60,14 +60,17 @@ class DateHistogramAggregator extends BucketsAggregator {
|
|||
private final ExtendedBounds extendedBounds;
|
||||
|
||||
private final LongHash bucketOrds;
|
||||
private long offset;
|
||||
|
||||
public DateHistogramAggregator(String name, AggregatorFactories factories, Rounding rounding, InternalOrder order, boolean keyed,
|
||||
public DateHistogramAggregator(String name, AggregatorFactories factories, Rounding rounding, long offset, InternalOrder order,
|
||||
boolean keyed,
|
||||
long minDocCount, @Nullable ExtendedBounds extendedBounds, @Nullable ValuesSource.Numeric valuesSource,
|
||||
DocValueFormat formatter, AggregationContext aggregationContext,
|
||||
Aggregator parent, List<PipelineAggregator> pipelineAggregators, Map<String, Object> metaData) throws IOException {
|
||||
|
||||
super(name, factories, aggregationContext, parent, pipelineAggregators, metaData);
|
||||
this.rounding = rounding;
|
||||
this.offset = offset;
|
||||
this.order = order;
|
||||
this.keyed = keyed;
|
||||
this.minDocCount = minDocCount;
|
||||
|
@ -100,7 +103,7 @@ class DateHistogramAggregator extends BucketsAggregator {
|
|||
long previousRounded = Long.MIN_VALUE;
|
||||
for (int i = 0; i < valuesCount; ++i) {
|
||||
long value = values.valueAt(i);
|
||||
long rounded = rounding.round(value);
|
||||
long rounded = rounding.round(value - offset) + offset;
|
||||
assert rounded >= previousRounded;
|
||||
if (rounded == previousRounded) {
|
||||
continue;
|
||||
|
@ -133,7 +136,7 @@ class DateHistogramAggregator extends BucketsAggregator {
|
|||
InternalDateHistogram.EmptyBucketInfo emptyBucketInfo = minDocCount == 0
|
||||
? new InternalDateHistogram.EmptyBucketInfo(rounding, buildEmptySubAggregations(), extendedBounds)
|
||||
: null;
|
||||
return new InternalDateHistogram(name, buckets, order, minDocCount, emptyBucketInfo, formatter, keyed,
|
||||
return new InternalDateHistogram(name, buckets, order, minDocCount, offset, emptyBucketInfo, formatter, keyed,
|
||||
pipelineAggregators(), metaData());
|
||||
}
|
||||
|
||||
|
@ -142,7 +145,7 @@ class DateHistogramAggregator extends BucketsAggregator {
|
|||
InternalDateHistogram.EmptyBucketInfo emptyBucketInfo = minDocCount == 0
|
||||
? new InternalDateHistogram.EmptyBucketInfo(rounding, buildEmptySubAggregations(), extendedBounds)
|
||||
: null;
|
||||
return new InternalDateHistogram(name, Collections.emptyList(), order, minDocCount, emptyBucketInfo, formatter, keyed,
|
||||
return new InternalDateHistogram(name, Collections.emptyList(), order, minDocCount, offset, emptyBucketInfo, formatter, keyed,
|
||||
pipelineAggregators(), metaData());
|
||||
}
|
||||
|
||||
|
|
|
@ -21,7 +21,6 @@ package org.elasticsearch.search.aggregations.bucket.histogram;
|
|||
|
||||
import org.elasticsearch.common.rounding.DateTimeUnit;
|
||||
import org.elasticsearch.common.rounding.Rounding;
|
||||
import org.elasticsearch.common.rounding.TimeZoneRounding;
|
||||
import org.elasticsearch.common.unit.TimeValue;
|
||||
import org.elasticsearch.search.aggregations.Aggregator;
|
||||
import org.elasticsearch.search.aggregations.AggregatorFactories;
|
||||
|
@ -95,24 +94,24 @@ public final class DateHistogramAggregatorFactory
|
|||
}
|
||||
|
||||
private Rounding createRounding() {
|
||||
TimeZoneRounding.Builder tzRoundingBuilder;
|
||||
Rounding.Builder tzRoundingBuilder;
|
||||
if (dateHistogramInterval != null) {
|
||||
DateTimeUnit dateTimeUnit = DATE_FIELD_UNITS.get(dateHistogramInterval.toString());
|
||||
if (dateTimeUnit != null) {
|
||||
tzRoundingBuilder = TimeZoneRounding.builder(dateTimeUnit);
|
||||
tzRoundingBuilder = Rounding.builder(dateTimeUnit);
|
||||
} else {
|
||||
// the interval is a time value?
|
||||
tzRoundingBuilder = TimeZoneRounding.builder(
|
||||
tzRoundingBuilder = Rounding.builder(
|
||||
TimeValue.parseTimeValue(dateHistogramInterval.toString(), null, getClass().getSimpleName() + ".interval"));
|
||||
}
|
||||
} else {
|
||||
// the interval is an integer time value in millis?
|
||||
tzRoundingBuilder = TimeZoneRounding.builder(TimeValue.timeValueMillis(interval));
|
||||
tzRoundingBuilder = Rounding.builder(TimeValue.timeValueMillis(interval));
|
||||
}
|
||||
if (timeZone() != null) {
|
||||
tzRoundingBuilder.timeZone(timeZone());
|
||||
}
|
||||
Rounding rounding = tzRoundingBuilder.offset(offset).build();
|
||||
Rounding rounding = tzRoundingBuilder.build();
|
||||
return rounding;
|
||||
}
|
||||
|
||||
|
@ -138,7 +137,7 @@ public final class DateHistogramAggregatorFactory
|
|||
// parse any string bounds to longs and round them
|
||||
roundedBounds = extendedBounds.parseAndValidate(name, context.searchContext(), config.format()).round(rounding);
|
||||
}
|
||||
return new DateHistogramAggregator(name, factories, rounding, order, keyed, minDocCount, roundedBounds, valuesSource,
|
||||
return new DateHistogramAggregator(name, factories, rounding, offset, order, keyed, minDocCount, roundedBounds, valuesSource,
|
||||
config.format(), context, parent, pipelineAggregators, metaData);
|
||||
}
|
||||
|
||||
|
|
|
@ -21,7 +21,6 @@ package org.elasticsearch.search.aggregations.bucket.histogram;
|
|||
import org.elasticsearch.common.ParseField;
|
||||
import org.elasticsearch.common.ParseFieldMatcher;
|
||||
import org.elasticsearch.common.ParsingException;
|
||||
import org.elasticsearch.common.rounding.Rounding;
|
||||
import org.elasticsearch.common.xcontent.XContentParser;
|
||||
import org.elasticsearch.common.xcontent.XContentParser.Token;
|
||||
import org.elasticsearch.search.aggregations.support.AbstractValuesSourceParser.NumericValuesSourceParser;
|
||||
|
@ -45,7 +44,7 @@ public class DateHistogramParser extends NumericValuesSourceParser {
|
|||
protected DateHistogramAggregationBuilder createFactory(String aggregationName, ValuesSourceType valuesSourceType,
|
||||
ValueType targetValueType, Map<ParseField, Object> otherOptions) {
|
||||
DateHistogramAggregationBuilder factory = new DateHistogramAggregationBuilder(aggregationName);
|
||||
Object interval = otherOptions.get(Rounding.Interval.INTERVAL_FIELD);
|
||||
Object interval = otherOptions.get(Histogram.INTERVAL_FIELD);
|
||||
if (interval == null) {
|
||||
throw new ParsingException(null, "Missing required field [interval] for histogram aggregation [" + aggregationName + "]");
|
||||
} else if (interval instanceof Long) {
|
||||
|
@ -55,7 +54,7 @@ public class DateHistogramParser extends NumericValuesSourceParser {
|
|||
} else {
|
||||
throw new IllegalStateException("Unexpected interval class: " + interval.getClass());
|
||||
}
|
||||
Long offset = (Long) otherOptions.get(Rounding.OffsetRounding.OFFSET_FIELD);
|
||||
Long offset = (Long) otherOptions.get(Histogram.OFFSET_FIELD);
|
||||
if (offset != null) {
|
||||
factory.offset(offset);
|
||||
}
|
||||
|
@ -83,12 +82,12 @@ public class DateHistogramParser extends NumericValuesSourceParser {
|
|||
protected boolean token(String aggregationName, String currentFieldName, Token token, XContentParser parser,
|
||||
ParseFieldMatcher parseFieldMatcher, Map<ParseField, Object> otherOptions) throws IOException {
|
||||
if (token.isValue()) {
|
||||
if (parseFieldMatcher.match(currentFieldName, Rounding.Interval.INTERVAL_FIELD)) {
|
||||
if (parseFieldMatcher.match(currentFieldName, Histogram.INTERVAL_FIELD)) {
|
||||
if (token == XContentParser.Token.VALUE_STRING) {
|
||||
otherOptions.put(Rounding.Interval.INTERVAL_FIELD, new DateHistogramInterval(parser.text()));
|
||||
otherOptions.put(Histogram.INTERVAL_FIELD, new DateHistogramInterval(parser.text()));
|
||||
return true;
|
||||
} else {
|
||||
otherOptions.put(Rounding.Interval.INTERVAL_FIELD, parser.longValue());
|
||||
otherOptions.put(Histogram.INTERVAL_FIELD, parser.longValue());
|
||||
return true;
|
||||
}
|
||||
} else if (parseFieldMatcher.match(currentFieldName, Histogram.MIN_DOC_COUNT_FIELD)) {
|
||||
|
@ -97,13 +96,13 @@ public class DateHistogramParser extends NumericValuesSourceParser {
|
|||
} else if (parseFieldMatcher.match(currentFieldName, Histogram.KEYED_FIELD)) {
|
||||
otherOptions.put(Histogram.KEYED_FIELD, parser.booleanValue());
|
||||
return true;
|
||||
} else if (parseFieldMatcher.match(currentFieldName, Rounding.OffsetRounding.OFFSET_FIELD)) {
|
||||
} else if (parseFieldMatcher.match(currentFieldName, Histogram.OFFSET_FIELD)) {
|
||||
if (token == XContentParser.Token.VALUE_STRING) {
|
||||
otherOptions.put(Rounding.OffsetRounding.OFFSET_FIELD,
|
||||
otherOptions.put(Histogram.OFFSET_FIELD,
|
||||
DateHistogramAggregationBuilder.parseStringOffset(parser.text()));
|
||||
return true;
|
||||
} else {
|
||||
otherOptions.put(Rounding.OffsetRounding.OFFSET_FIELD, parser.longValue());
|
||||
otherOptions.put(Histogram.OFFSET_FIELD, parser.longValue());
|
||||
return true;
|
||||
}
|
||||
} else {
|
||||
|
|
|
@ -178,14 +178,17 @@ public final class InternalDateHistogram extends InternalMultiBucketAggregation<
|
|||
private final DocValueFormat format;
|
||||
private final boolean keyed;
|
||||
private final long minDocCount;
|
||||
private final long offset;
|
||||
private final EmptyBucketInfo emptyBucketInfo;
|
||||
|
||||
InternalDateHistogram(String name, List<Bucket> buckets, InternalOrder order, long minDocCount, EmptyBucketInfo emptyBucketInfo,
|
||||
InternalDateHistogram(String name, List<Bucket> buckets, InternalOrder order, long minDocCount, long offset,
|
||||
EmptyBucketInfo emptyBucketInfo,
|
||||
DocValueFormat formatter, boolean keyed, List<PipelineAggregator> pipelineAggregators,
|
||||
Map<String, Object> metaData) {
|
||||
super(name, pipelineAggregators, metaData);
|
||||
this.buckets = buckets;
|
||||
this.order = order;
|
||||
this.offset = offset;
|
||||
assert (minDocCount == 0) == (emptyBucketInfo != null);
|
||||
this.minDocCount = minDocCount;
|
||||
this.emptyBucketInfo = emptyBucketInfo;
|
||||
|
@ -205,6 +208,7 @@ public final class InternalDateHistogram extends InternalMultiBucketAggregation<
|
|||
} else {
|
||||
emptyBucketInfo = null;
|
||||
}
|
||||
offset = in.readLong();
|
||||
format = in.readNamedWriteable(DocValueFormat.class);
|
||||
keyed = in.readBoolean();
|
||||
buckets = in.readList(stream -> new Bucket(stream, keyed, format));
|
||||
|
@ -217,6 +221,7 @@ public final class InternalDateHistogram extends InternalMultiBucketAggregation<
|
|||
if (minDocCount == 0) {
|
||||
emptyBucketInfo.writeTo(out);
|
||||
}
|
||||
out.writeLong(offset);
|
||||
out.writeNamedWriteable(format);
|
||||
out.writeBoolean(keyed);
|
||||
out.writeList(buckets);
|
||||
|
@ -234,7 +239,7 @@ public final class InternalDateHistogram extends InternalMultiBucketAggregation<
|
|||
|
||||
@Override
|
||||
public InternalDateHistogram create(List<Bucket> buckets) {
|
||||
return new InternalDateHistogram(name, buckets, order, minDocCount, emptyBucketInfo, format,
|
||||
return new InternalDateHistogram(name, buckets, order, minDocCount, offset, emptyBucketInfo, format,
|
||||
keyed, pipelineAggregators(), metaData);
|
||||
}
|
||||
|
||||
|
@ -328,7 +333,7 @@ public final class InternalDateHistogram extends InternalMultiBucketAggregation<
|
|||
long max = bounds.getMax();
|
||||
while (key <= max) {
|
||||
iter.add(new InternalDateHistogram.Bucket(key, 0, keyed, format, reducedEmptySubAggs));
|
||||
key = emptyBucketInfo.rounding.nextRoundingValue(key);
|
||||
key = nextKey(key).longValue();
|
||||
}
|
||||
}
|
||||
} else {
|
||||
|
@ -337,7 +342,7 @@ public final class InternalDateHistogram extends InternalMultiBucketAggregation<
|
|||
if (key < firstBucket.key) {
|
||||
while (key < firstBucket.key) {
|
||||
iter.add(new InternalDateHistogram.Bucket(key, 0, keyed, format, reducedEmptySubAggs));
|
||||
key = emptyBucketInfo.rounding.nextRoundingValue(key);
|
||||
key = nextKey(key).longValue();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -349,10 +354,10 @@ public final class InternalDateHistogram extends InternalMultiBucketAggregation<
|
|||
while (iter.hasNext()) {
|
||||
Bucket nextBucket = list.get(iter.nextIndex());
|
||||
if (lastBucket != null) {
|
||||
long key = emptyBucketInfo.rounding.nextRoundingValue(lastBucket.key);
|
||||
long key = nextKey(lastBucket.key).longValue();
|
||||
while (key < nextBucket.key) {
|
||||
iter.add(new InternalDateHistogram.Bucket(key, 0, keyed, format, reducedEmptySubAggs));
|
||||
key = emptyBucketInfo.rounding.nextRoundingValue(key);
|
||||
key = nextKey(key).longValue();
|
||||
}
|
||||
assert key == nextBucket.key;
|
||||
}
|
||||
|
@ -393,7 +398,7 @@ public final class InternalDateHistogram extends InternalMultiBucketAggregation<
|
|||
CollectionUtil.introSort(reducedBuckets, order.comparator());
|
||||
}
|
||||
|
||||
return new InternalDateHistogram(getName(), reducedBuckets, order, minDocCount, emptyBucketInfo,
|
||||
return new InternalDateHistogram(getName(), reducedBuckets, order, minDocCount, offset, emptyBucketInfo,
|
||||
format, keyed, pipelineAggregators(), getMetaData());
|
||||
}
|
||||
|
||||
|
@ -424,7 +429,7 @@ public final class InternalDateHistogram extends InternalMultiBucketAggregation<
|
|||
|
||||
@Override
|
||||
public Number nextKey(Number key) {
|
||||
return emptyBucketInfo.rounding.nextRoundingValue(key.longValue());
|
||||
return emptyBucketInfo.rounding.nextRoundingValue(key.longValue() - offset) + offset;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -435,7 +440,7 @@ public final class InternalDateHistogram extends InternalMultiBucketAggregation<
|
|||
buckets2.add((Bucket) b);
|
||||
}
|
||||
buckets2 = Collections.unmodifiableList(buckets2);
|
||||
return new InternalDateHistogram(name, buckets2, order, minDocCount, emptyBucketInfo, format,
|
||||
return new InternalDateHistogram(name, buckets2, order, minDocCount, offset, emptyBucketInfo, format,
|
||||
keyed, pipelineAggregators(), getMetaData());
|
||||
}
|
||||
|
||||
|
|
|
@ -1,92 +0,0 @@
|
|||
/*
|
||||
* Licensed to Elasticsearch under one or more contributor
|
||||
* license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright
|
||||
* ownership. Elasticsearch licenses this file to you 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 org.elasticsearch.common.rounding;
|
||||
|
||||
import org.elasticsearch.test.ESTestCase;
|
||||
|
||||
import static org.hamcrest.Matchers.equalTo;
|
||||
import static org.hamcrest.Matchers.greaterThan;
|
||||
import static org.hamcrest.Matchers.lessThanOrEqualTo;
|
||||
|
||||
public class RoundingTests extends ESTestCase {
|
||||
/**
|
||||
* simple test case to illustrate how Rounding.Interval works on readable input
|
||||
*/
|
||||
public void testInterval() {
|
||||
int interval = 10;
|
||||
Rounding.Interval rounding = new Rounding.Interval(interval);
|
||||
int value = 24;
|
||||
final long r = rounding.round(24);
|
||||
String message = "round(" + value + ", interval=" + interval + ") = " + r;
|
||||
assertEquals(value/interval * interval, r);
|
||||
assertEquals(message, 0, r % interval);
|
||||
}
|
||||
|
||||
public void testIntervalRandom() {
|
||||
final long interval = randomIntBetween(1, 100);
|
||||
Rounding.Interval rounding = new Rounding.Interval(interval);
|
||||
for (int i = 0; i < 1000; ++i) {
|
||||
long l = Math.max(randomLong(), Long.MIN_VALUE + interval);
|
||||
final long r = rounding.round(l);
|
||||
String message = "round(" + l + ", interval=" + interval + ") = " + r;
|
||||
assertEquals(message, 0, r % interval);
|
||||
assertThat(message, r, lessThanOrEqualTo(l));
|
||||
assertThat(message, r + interval, greaterThan(l));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Simple test case to illustrate how Rounding.Offset works on readable input.
|
||||
* offset shifts input value back before rounding (so here 6 - 7 -> -1)
|
||||
* then shifts rounded Value back (here -10 -> -3)
|
||||
*/
|
||||
public void testOffsetRounding() {
|
||||
final long interval = 10;
|
||||
final long offset = 7;
|
||||
Rounding.OffsetRounding rounding = new Rounding.OffsetRounding(new Rounding.Interval(interval), offset);
|
||||
assertEquals(-3, rounding.round(6));
|
||||
assertEquals(7, rounding.nextRoundingValue(-3));
|
||||
assertEquals(7, rounding.round(7));
|
||||
assertEquals(17, rounding.nextRoundingValue(7));
|
||||
assertEquals(7, rounding.round(16));
|
||||
assertEquals(17, rounding.round(17));
|
||||
assertEquals(27, rounding.nextRoundingValue(17));
|
||||
}
|
||||
|
||||
/**
|
||||
* test OffsetRounding with an internal interval rounding on random inputs
|
||||
*/
|
||||
public void testOffsetRoundingRandom() {
|
||||
for (int i = 0; i < 1000; ++i) {
|
||||
final long interval = randomIntBetween(1, 100);
|
||||
Rounding.Interval internalRounding = new Rounding.Interval(interval);
|
||||
final long offset = randomIntBetween(-100, 100);
|
||||
Rounding.OffsetRounding rounding = new Rounding.OffsetRounding(internalRounding, offset);
|
||||
long safetyMargin = Math.abs(interval) + Math.abs(offset); // to prevent range overflow
|
||||
long value = Math.max(randomLong() - safetyMargin, Long.MIN_VALUE + safetyMargin);
|
||||
final long r_value = rounding.round(value);
|
||||
final long nextRoundingValue = rounding.nextRoundingValue(r_value);
|
||||
assertThat("Rounding should be idempotent", r_value, equalTo(rounding.round(r_value)));
|
||||
assertThat("Rounded value smaller than unrounded, regardless of offset", r_value - offset, lessThanOrEqualTo(value - offset));
|
||||
assertThat("Rounded value <= value < next interval start", r_value + interval, greaterThan(value));
|
||||
assertThat("NextRounding value should be interval from rounded value", r_value + interval, equalTo(nextRoundingValue));
|
||||
}
|
||||
}
|
||||
}
|
|
@ -20,8 +20,8 @@
|
|||
package org.elasticsearch.common.rounding;
|
||||
|
||||
import org.elasticsearch.common.collect.Tuple;
|
||||
import org.elasticsearch.common.rounding.TimeZoneRounding.TimeIntervalRounding;
|
||||
import org.elasticsearch.common.rounding.TimeZoneRounding.TimeUnitRounding;
|
||||
import org.elasticsearch.common.rounding.Rounding.TimeIntervalRounding;
|
||||
import org.elasticsearch.common.rounding.Rounding.TimeUnitRounding;
|
||||
import org.elasticsearch.common.unit.TimeValue;
|
||||
import org.elasticsearch.test.ESTestCase;
|
||||
import org.hamcrest.Description;
|
||||
|
@ -47,29 +47,25 @@ import static org.hamcrest.Matchers.lessThanOrEqualTo;
|
|||
public class TimeZoneRoundingTests extends ESTestCase {
|
||||
|
||||
public void testUTCTimeUnitRounding() {
|
||||
Rounding tzRounding = TimeZoneRounding.builder(DateTimeUnit.MONTH_OF_YEAR).build();
|
||||
Rounding tzRounding = Rounding.builder(DateTimeUnit.MONTH_OF_YEAR).build();
|
||||
DateTimeZone tz = DateTimeZone.UTC;
|
||||
assertThat(tzRounding.round(time("2009-02-03T01:01:01")), isDate(time("2009-02-01T00:00:00.000Z"), tz));
|
||||
assertThat(tzRounding.nextRoundingValue(time("2009-02-01T00:00:00.000Z")), isDate(time("2009-03-01T00:00:00.000Z"), tz));
|
||||
|
||||
tzRounding = TimeZoneRounding.builder(DateTimeUnit.WEEK_OF_WEEKYEAR).build();
|
||||
tzRounding = Rounding.builder(DateTimeUnit.WEEK_OF_WEEKYEAR).build();
|
||||
assertThat(tzRounding.round(time("2012-01-10T01:01:01")), isDate(time("2012-01-09T00:00:00.000Z"), tz));
|
||||
assertThat(tzRounding.nextRoundingValue(time("2012-01-09T00:00:00.000Z")), isDate(time("2012-01-16T00:00:00.000Z"), tz));
|
||||
|
||||
tzRounding = TimeZoneRounding.builder(DateTimeUnit.WEEK_OF_WEEKYEAR).offset(-TimeValue.timeValueHours(24).millis()).build();
|
||||
assertThat(tzRounding.round(time("2012-01-10T01:01:01")), isDate(time("2012-01-08T00:00:00.000Z"), tz));
|
||||
assertThat(tzRounding.nextRoundingValue(time("2012-01-08T00:00:00.000Z")), isDate(time("2012-01-15T00:00:00.000Z"), tz));
|
||||
}
|
||||
|
||||
public void testUTCIntervalRounding() {
|
||||
Rounding tzRounding = TimeZoneRounding.builder(TimeValue.timeValueHours(12)).build();
|
||||
Rounding tzRounding = Rounding.builder(TimeValue.timeValueHours(12)).build();
|
||||
DateTimeZone tz = DateTimeZone.UTC;
|
||||
assertThat(tzRounding.round(time("2009-02-03T01:01:01")), isDate(time("2009-02-03T00:00:00.000Z"), tz));
|
||||
assertThat(tzRounding.nextRoundingValue(time("2009-02-03T00:00:00.000Z")), isDate(time("2009-02-03T12:00:00.000Z"), tz));
|
||||
assertThat(tzRounding.round(time("2009-02-03T13:01:01")), isDate(time("2009-02-03T12:00:00.000Z"), tz));
|
||||
assertThat(tzRounding.nextRoundingValue(time("2009-02-03T12:00:00.000Z")), isDate(time("2009-02-04T00:00:00.000Z"), tz));
|
||||
|
||||
tzRounding = TimeZoneRounding.builder(TimeValue.timeValueHours(48)).build();
|
||||
tzRounding = Rounding.builder(TimeValue.timeValueHours(48)).build();
|
||||
assertThat(tzRounding.round(time("2009-02-03T01:01:01")), isDate(time("2009-02-03T00:00:00.000Z"), tz));
|
||||
assertThat(tzRounding.nextRoundingValue(time("2009-02-03T00:00:00.000Z")), isDate(time("2009-02-05T00:00:00.000Z"), tz));
|
||||
assertThat(tzRounding.round(time("2009-02-05T13:01:01")), isDate(time("2009-02-05T00:00:00.000Z"), tz));
|
||||
|
@ -77,11 +73,11 @@ public class TimeZoneRoundingTests extends ESTestCase {
|
|||
}
|
||||
|
||||
/**
|
||||
* test TimeIntervalTimeZoneRounding, (interval < 12h) with time zone shift
|
||||
* test TimeIntervalRounding, (interval < 12h) with time zone shift
|
||||
*/
|
||||
public void testTimeIntervalTimeZoneRounding() {
|
||||
public void testTimeIntervalRounding() {
|
||||
DateTimeZone tz = DateTimeZone.forOffsetHours(-1);
|
||||
Rounding tzRounding = TimeZoneRounding.builder(TimeValue.timeValueHours(6)).timeZone(tz).build();
|
||||
Rounding tzRounding = Rounding.builder(TimeValue.timeValueHours(6)).timeZone(tz).build();
|
||||
assertThat(tzRounding.round(time("2009-02-03T00:01:01")), isDate(time("2009-02-02T19:00:00.000Z"), tz));
|
||||
assertThat(tzRounding.nextRoundingValue(time("2009-02-02T19:00:00.000Z")), isDate(time("2009-02-03T01:00:00.000Z"), tz));
|
||||
|
||||
|
@ -90,11 +86,11 @@ public class TimeZoneRoundingTests extends ESTestCase {
|
|||
}
|
||||
|
||||
/**
|
||||
* test DayIntervalTimeZoneRounding, (interval >= 12h) with time zone shift
|
||||
* test DayIntervalRounding, (interval >= 12h) with time zone shift
|
||||
*/
|
||||
public void testDayIntervalTimeZoneRounding() {
|
||||
public void testDayIntervalRounding() {
|
||||
DateTimeZone tz = DateTimeZone.forOffsetHours(-8);
|
||||
Rounding tzRounding = TimeZoneRounding.builder(TimeValue.timeValueHours(12)).timeZone(tz).build();
|
||||
Rounding tzRounding = Rounding.builder(TimeValue.timeValueHours(12)).timeZone(tz).build();
|
||||
assertThat(tzRounding.round(time("2009-02-03T00:01:01")), isDate(time("2009-02-02T20:00:00.000Z"), tz));
|
||||
assertThat(tzRounding.nextRoundingValue(time("2009-02-02T20:00:00.000Z")), isDate(time("2009-02-03T08:00:00.000Z"), tz));
|
||||
|
||||
|
@ -102,37 +98,37 @@ public class TimeZoneRoundingTests extends ESTestCase {
|
|||
assertThat(tzRounding.nextRoundingValue(time("2009-02-03T08:00:00.000Z")), isDate(time("2009-02-03T20:00:00.000Z"), tz));
|
||||
}
|
||||
|
||||
public void testDayTimeZoneRounding() {
|
||||
public void testDayRounding() {
|
||||
int timezoneOffset = -2;
|
||||
Rounding tzRounding = TimeZoneRounding.builder(DateTimeUnit.DAY_OF_MONTH).timeZone(DateTimeZone.forOffsetHours(timezoneOffset))
|
||||
Rounding tzRounding = Rounding.builder(DateTimeUnit.DAY_OF_MONTH).timeZone(DateTimeZone.forOffsetHours(timezoneOffset))
|
||||
.build();
|
||||
assertThat(tzRounding.round(0), equalTo(0L - TimeValue.timeValueHours(24 + timezoneOffset).millis()));
|
||||
assertThat(tzRounding.nextRoundingValue(0L - TimeValue.timeValueHours(24 + timezoneOffset).millis()), equalTo(0L - TimeValue
|
||||
.timeValueHours(timezoneOffset).millis()));
|
||||
|
||||
DateTimeZone tz = DateTimeZone.forID("-08:00");
|
||||
tzRounding = TimeZoneRounding.builder(DateTimeUnit.DAY_OF_MONTH).timeZone(tz).build();
|
||||
tzRounding = Rounding.builder(DateTimeUnit.DAY_OF_MONTH).timeZone(tz).build();
|
||||
assertThat(tzRounding.round(time("2012-04-01T04:15:30Z")), isDate(time("2012-03-31T08:00:00Z"), tz));
|
||||
|
||||
tzRounding = TimeZoneRounding.builder(DateTimeUnit.MONTH_OF_YEAR).timeZone(tz).build();
|
||||
tzRounding = Rounding.builder(DateTimeUnit.MONTH_OF_YEAR).timeZone(tz).build();
|
||||
assertThat(tzRounding.round(time("2012-04-01T04:15:30Z")), equalTo(time("2012-03-01T08:00:00Z")));
|
||||
|
||||
// date in Feb-3rd, but still in Feb-2nd in -02:00 timezone
|
||||
tz = DateTimeZone.forID("-02:00");
|
||||
tzRounding = TimeZoneRounding.builder(DateTimeUnit.DAY_OF_MONTH).timeZone(tz).build();
|
||||
tzRounding = Rounding.builder(DateTimeUnit.DAY_OF_MONTH).timeZone(tz).build();
|
||||
assertThat(tzRounding.round(time("2009-02-03T01:01:01")), isDate(time("2009-02-02T02:00:00"), tz));
|
||||
assertThat(tzRounding.nextRoundingValue(time("2009-02-02T02:00:00")), isDate(time("2009-02-03T02:00:00"), tz));
|
||||
|
||||
// date in Feb-3rd, also in -02:00 timezone
|
||||
tzRounding = TimeZoneRounding.builder(DateTimeUnit.DAY_OF_MONTH).timeZone(tz).build();
|
||||
tzRounding = Rounding.builder(DateTimeUnit.DAY_OF_MONTH).timeZone(tz).build();
|
||||
assertThat(tzRounding.round(time("2009-02-03T02:01:01")), isDate(time("2009-02-03T02:00:00"), tz));
|
||||
assertThat(tzRounding.nextRoundingValue(time("2009-02-03T02:00:00")), isDate(time("2009-02-04T02:00:00"), tz));
|
||||
}
|
||||
|
||||
public void testTimeTimeZoneRounding() {
|
||||
public void testTimeRounding() {
|
||||
// hour unit
|
||||
DateTimeZone tz = DateTimeZone.forOffsetHours(-2);
|
||||
Rounding tzRounding = TimeZoneRounding.builder(DateTimeUnit.HOUR_OF_DAY).timeZone(tz).build();
|
||||
Rounding tzRounding = Rounding.builder(DateTimeUnit.HOUR_OF_DAY).timeZone(tz).build();
|
||||
assertThat(tzRounding.round(0), equalTo(0L));
|
||||
assertThat(tzRounding.nextRoundingValue(0L), equalTo(TimeValue.timeValueHours(1L).getMillis()));
|
||||
|
||||
|
@ -144,23 +140,23 @@ public class TimeZoneRoundingTests extends ESTestCase {
|
|||
Rounding tzRounding;
|
||||
// testing savings to non savings switch
|
||||
DateTimeZone cet = DateTimeZone.forID("CET");
|
||||
tzRounding = TimeZoneRounding.builder(DateTimeUnit.HOUR_OF_DAY).timeZone(cet).build();
|
||||
tzRounding = Rounding.builder(DateTimeUnit.HOUR_OF_DAY).timeZone(cet).build();
|
||||
assertThat(tzRounding.round(time("2014-10-26T01:01:01", cet)), isDate(time("2014-10-26T01:00:00+02:00"), cet));
|
||||
assertThat(tzRounding.nextRoundingValue(time("2014-10-26T01:00:00", cet)),isDate(time("2014-10-26T02:00:00+02:00"), cet));
|
||||
assertThat(tzRounding.nextRoundingValue(time("2014-10-26T02:00:00", cet)), isDate(time("2014-10-26T02:00:00+01:00"), cet));
|
||||
|
||||
// testing non savings to savings switch
|
||||
tzRounding = TimeZoneRounding.builder(DateTimeUnit.HOUR_OF_DAY).timeZone(cet).build();
|
||||
tzRounding = Rounding.builder(DateTimeUnit.HOUR_OF_DAY).timeZone(cet).build();
|
||||
assertThat(tzRounding.round(time("2014-03-30T01:01:01", cet)), isDate(time("2014-03-30T01:00:00+01:00"), cet));
|
||||
assertThat(tzRounding.nextRoundingValue(time("2014-03-30T01:00:00", cet)), isDate(time("2014-03-30T03:00:00", cet), cet));
|
||||
assertThat(tzRounding.nextRoundingValue(time("2014-03-30T03:00:00", cet)), isDate(time("2014-03-30T04:00:00", cet), cet));
|
||||
|
||||
// testing non savings to savings switch (America/Chicago)
|
||||
DateTimeZone chg = DateTimeZone.forID("America/Chicago");
|
||||
Rounding tzRounding_utc = TimeZoneRounding.builder(DateTimeUnit.HOUR_OF_DAY).timeZone(DateTimeZone.UTC).build();
|
||||
Rounding tzRounding_utc = Rounding.builder(DateTimeUnit.HOUR_OF_DAY).timeZone(DateTimeZone.UTC).build();
|
||||
assertThat(tzRounding.round(time("2014-03-09T03:01:01", chg)), isDate(time("2014-03-09T03:00:00", chg), chg));
|
||||
|
||||
Rounding tzRounding_chg = TimeZoneRounding.builder(DateTimeUnit.HOUR_OF_DAY).timeZone(chg).build();
|
||||
Rounding tzRounding_chg = Rounding.builder(DateTimeUnit.HOUR_OF_DAY).timeZone(chg).build();
|
||||
assertThat(tzRounding_chg.round(time("2014-03-09T03:01:01", chg)), isDate(time("2014-03-09T03:00:00", chg), chg));
|
||||
|
||||
// testing savings to non savings switch 2013 (America/Chicago)
|
||||
|
@ -173,18 +169,21 @@ public class TimeZoneRoundingTests extends ESTestCase {
|
|||
}
|
||||
|
||||
/**
|
||||
* Randomized test on TimeUnitRounding.
|
||||
* Test uses random {@link DateTimeUnit} and {@link DateTimeZone} and often (50% of the time) chooses
|
||||
* test dates that are exactly on or close to offset changes (e.g. DST) in the chosen time zone.
|
||||
* Randomized test on TimeUnitRounding. Test uses random
|
||||
* {@link DateTimeUnit} and {@link DateTimeZone} and often (50% of the time)
|
||||
* chooses test dates that are exactly on or close to offset changes (e.g.
|
||||
* DST) in the chosen time zone.
|
||||
*
|
||||
* It rounds the test date down and up and performs various checks on the rounding unit interval that is
|
||||
* defined by this. Assumptions tested are described in {@link #assertInterval(long, long, long, TimeZoneRounding, DateTimeZone)}
|
||||
* It rounds the test date down and up and performs various checks on the
|
||||
* rounding unit interval that is defined by this. Assumptions tested are
|
||||
* described in
|
||||
* {@link #assertInterval(long, long, long, Rounding, DateTimeZone)}
|
||||
*/
|
||||
public void testTimeZoneRoundingRandom() {
|
||||
public void testRoundingRandom() {
|
||||
for (int i = 0; i < 1000; ++i) {
|
||||
DateTimeUnit timeUnit = randomTimeUnit();
|
||||
DateTimeZone tz = randomDateTimeZone();
|
||||
TimeZoneRounding rounding = new TimeZoneRounding.TimeUnitRounding(timeUnit, tz);
|
||||
Rounding rounding = new Rounding.TimeUnitRounding(timeUnit, tz);
|
||||
long date = Math.abs(randomLong() % (2 * (long) 10e11)); // 1970-01-01T00:00:00Z - 2033-05-18T05:33:20.000+02:00
|
||||
long unitMillis = timeUnit.field(tz).getDurationField().getUnitMillis();
|
||||
if (randomBoolean()) {
|
||||
|
@ -226,7 +225,7 @@ public class TimeZoneRoundingTests extends ESTestCase {
|
|||
public void testTimeIntervalCET_DST_End() {
|
||||
long interval = TimeUnit.MINUTES.toMillis(20);
|
||||
DateTimeZone tz = DateTimeZone.forID("CET");
|
||||
TimeZoneRounding rounding = new TimeIntervalRounding(interval, tz);
|
||||
Rounding rounding = new TimeIntervalRounding(interval, tz);
|
||||
|
||||
assertThat(rounding.round(time("2015-10-25T01:55:00+02:00")), isDate(time("2015-10-25T01:40:00+02:00"), tz));
|
||||
assertThat(rounding.round(time("2015-10-25T02:15:00+02:00")), isDate(time("2015-10-25T02:00:00+02:00"), tz));
|
||||
|
@ -246,7 +245,7 @@ public class TimeZoneRoundingTests extends ESTestCase {
|
|||
public void testTimeIntervalCET_DST_Start() {
|
||||
long interval = TimeUnit.MINUTES.toMillis(20);
|
||||
DateTimeZone tz = DateTimeZone.forID("CET");
|
||||
TimeZoneRounding rounding = new TimeIntervalRounding(interval, tz);
|
||||
Rounding rounding = new TimeIntervalRounding(interval, tz);
|
||||
// test DST start
|
||||
assertThat(rounding.round(time("2016-03-27T01:55:00+01:00")), isDate(time("2016-03-27T01:40:00+01:00"), tz));
|
||||
assertThat(rounding.round(time("2016-03-27T02:00:00+01:00")), isDate(time("2016-03-27T03:00:00+02:00"), tz));
|
||||
|
@ -263,7 +262,7 @@ public class TimeZoneRoundingTests extends ESTestCase {
|
|||
public void testTimeInterval_Kathmandu_DST_Start() {
|
||||
long interval = TimeUnit.MINUTES.toMillis(20);
|
||||
DateTimeZone tz = DateTimeZone.forID("Asia/Kathmandu");
|
||||
TimeZoneRounding rounding = new TimeIntervalRounding(interval, tz);
|
||||
Rounding rounding = new TimeIntervalRounding(interval, tz);
|
||||
assertThat(rounding.round(time("1985-12-31T23:55:00+05:30")), isDate(time("1985-12-31T23:40:00+05:30"), tz));
|
||||
assertThat(rounding.round(time("1986-01-01T00:16:00+05:45")), isDate(time("1986-01-01T00:15:00+05:45"), tz));
|
||||
assertThat(time("1986-01-01T00:15:00+05:45") - time("1985-12-31T23:40:00+05:30"), equalTo(TimeUnit.MINUTES.toMillis(20)));
|
||||
|
@ -281,7 +280,7 @@ public class TimeZoneRoundingTests extends ESTestCase {
|
|||
public void testIntervalRounding_NotDivisibleInteval() {
|
||||
DateTimeZone tz = DateTimeZone.forID("CET");
|
||||
long interval = TimeUnit.MINUTES.toMillis(14);
|
||||
TimeZoneRounding rounding = new TimeZoneRounding.TimeIntervalRounding(interval, tz);
|
||||
Rounding rounding = new Rounding.TimeIntervalRounding(interval, tz);
|
||||
|
||||
assertThat(rounding.round(time("2016-03-27T01:41:00+01:00")), isDate(time("2016-03-27T01:30:00+01:00"), tz));
|
||||
assertThat(rounding.round(time("2016-03-27T01:51:00+01:00")), isDate(time("2016-03-27T01:44:00+01:00"), tz));
|
||||
|
@ -298,7 +297,7 @@ public class TimeZoneRoundingTests extends ESTestCase {
|
|||
public void testIntervalRounding_HalfDay_DST() {
|
||||
DateTimeZone tz = DateTimeZone.forID("CET");
|
||||
long interval = TimeUnit.HOURS.toMillis(12);
|
||||
TimeZoneRounding rounding = new TimeZoneRounding.TimeIntervalRounding(interval, tz);
|
||||
Rounding rounding = new Rounding.TimeIntervalRounding(interval, tz);
|
||||
|
||||
assertThat(rounding.round(time("2016-03-26T01:00:00+01:00")), isDate(time("2016-03-26T00:00:00+01:00"), tz));
|
||||
assertThat(rounding.round(time("2016-03-26T13:00:00+01:00")), isDate(time("2016-03-26T12:00:00+01:00"), tz));
|
||||
|
@ -316,7 +315,7 @@ public class TimeZoneRoundingTests extends ESTestCase {
|
|||
TimeUnit unit = randomFrom(new TimeUnit[] {TimeUnit.MINUTES, TimeUnit.HOURS, TimeUnit.DAYS});
|
||||
long interval = unit.toMillis(randomIntBetween(1, 365));
|
||||
DateTimeZone tz = randomDateTimeZone();
|
||||
TimeZoneRounding rounding = new TimeZoneRounding.TimeIntervalRounding(interval, tz);
|
||||
Rounding rounding = new Rounding.TimeIntervalRounding(interval, tz);
|
||||
long mainDate = Math.abs(randomLong() % (2 * (long) 10e11)); // 1970-01-01T00:00:00Z - 2033-05-18T05:33:20.000+02:00
|
||||
if (randomBoolean()) {
|
||||
mainDate = nastyDate(mainDate, tz, interval);
|
||||
|
@ -356,8 +355,8 @@ public class TimeZoneRoundingTests extends ESTestCase {
|
|||
public void testIntervalRoundingMonotonic_CET() {
|
||||
long interval = TimeUnit.MINUTES.toMillis(45);
|
||||
DateTimeZone tz = DateTimeZone.forID("CET");
|
||||
TimeZoneRounding rounding = new TimeZoneRounding.TimeIntervalRounding(interval, tz);
|
||||
List<Tuple<String, String>> expectedDates = new ArrayList<Tuple<String, String>>();
|
||||
Rounding rounding = new Rounding.TimeIntervalRounding(interval, tz);
|
||||
List<Tuple<String, String>> expectedDates = new ArrayList<>();
|
||||
// first date is the date to be rounded, second the expected result
|
||||
expectedDates.add(new Tuple<>("2011-10-30T01:40:00.000+02:00", "2011-10-30T01:30:00.000+02:00"));
|
||||
expectedDates.add(new Tuple<>("2011-10-30T02:02:30.000+02:00", "2011-10-30T01:30:00.000+02:00"));
|
||||
|
@ -387,7 +386,7 @@ public class TimeZoneRoundingTests extends ESTestCase {
|
|||
public void testAmbiguousHoursAfterDSTSwitch() {
|
||||
Rounding tzRounding;
|
||||
final DateTimeZone tz = DateTimeZone.forID("Asia/Jerusalem");
|
||||
tzRounding = TimeZoneRounding.builder(DateTimeUnit.HOUR_OF_DAY).timeZone(tz).build();
|
||||
tzRounding = Rounding.builder(DateTimeUnit.HOUR_OF_DAY).timeZone(tz).build();
|
||||
assertThat(tzRounding.round(time("2014-10-26T00:30:00+03:00")), isDate(time("2014-10-26T00:00:00+03:00"), tz));
|
||||
assertThat(tzRounding.round(time("2014-10-26T01:30:00+03:00")), isDate(time("2014-10-26T01:00:00+03:00"), tz));
|
||||
// the utc date for "2014-10-25T03:00:00+03:00" and "2014-10-25T03:00:00+02:00" is the same, local time turns back 1h here
|
||||
|
@ -396,7 +395,7 @@ public class TimeZoneRoundingTests extends ESTestCase {
|
|||
assertThat(tzRounding.round(time("2014-10-26T02:30:00+02:00")), isDate(time("2014-10-26T02:00:00+02:00"), tz));
|
||||
|
||||
// Day interval
|
||||
tzRounding = TimeZoneRounding.builder(DateTimeUnit.DAY_OF_MONTH).timeZone(tz).build();
|
||||
tzRounding = Rounding.builder(DateTimeUnit.DAY_OF_MONTH).timeZone(tz).build();
|
||||
assertThat(tzRounding.round(time("2014-11-11T17:00:00", tz)), isDate(time("2014-11-11T00:00:00", tz), tz));
|
||||
// DST on
|
||||
assertThat(tzRounding.round(time("2014-08-11T17:00:00", tz)), isDate(time("2014-08-11T00:00:00", tz), tz));
|
||||
|
@ -406,17 +405,17 @@ public class TimeZoneRoundingTests extends ESTestCase {
|
|||
assertThat(tzRounding.round(time("2015-03-27T17:00:00", tz)), isDate(time("2015-03-27T00:00:00", tz), tz));
|
||||
|
||||
// Month interval
|
||||
tzRounding = TimeZoneRounding.builder(DateTimeUnit.MONTH_OF_YEAR).timeZone(tz).build();
|
||||
tzRounding = Rounding.builder(DateTimeUnit.MONTH_OF_YEAR).timeZone(tz).build();
|
||||
assertThat(tzRounding.round(time("2014-11-11T17:00:00", tz)), isDate(time("2014-11-01T00:00:00", tz), tz));
|
||||
// DST on
|
||||
assertThat(tzRounding.round(time("2014-10-10T17:00:00", tz)), isDate(time("2014-10-01T00:00:00", tz), tz));
|
||||
|
||||
// Year interval
|
||||
tzRounding = TimeZoneRounding.builder(DateTimeUnit.YEAR_OF_CENTURY).timeZone(tz).build();
|
||||
tzRounding = Rounding.builder(DateTimeUnit.YEAR_OF_CENTURY).timeZone(tz).build();
|
||||
assertThat(tzRounding.round(time("2014-11-11T17:00:00", tz)), isDate(time("2014-01-01T00:00:00", tz), tz));
|
||||
|
||||
// Two timestamps in same year and different timezone offset ("Double buckets" issue - #9491)
|
||||
tzRounding = TimeZoneRounding.builder(DateTimeUnit.YEAR_OF_CENTURY).timeZone(tz).build();
|
||||
tzRounding = Rounding.builder(DateTimeUnit.YEAR_OF_CENTURY).timeZone(tz).build();
|
||||
assertThat(tzRounding.round(time("2014-11-11T17:00:00", tz)),
|
||||
isDate(tzRounding.round(time("2014-08-11T17:00:00", tz)), tz));
|
||||
}
|
||||
|
@ -429,8 +428,8 @@ public class TimeZoneRoundingTests extends ESTestCase {
|
|||
DateTimeZone tz = DateTimeZone.forID("America/Sao_Paulo");
|
||||
long start = time("2014-10-18T20:50:00.000", tz);
|
||||
long end = time("2014-10-19T01:00:00.000", tz);
|
||||
Rounding tzRounding = new TimeZoneRounding.TimeUnitRounding(DateTimeUnit.MINUTES_OF_HOUR, tz);
|
||||
Rounding dayTzRounding = new TimeZoneRounding.TimeIntervalRounding(60000, tz);
|
||||
Rounding tzRounding = new Rounding.TimeUnitRounding(DateTimeUnit.MINUTES_OF_HOUR, tz);
|
||||
Rounding dayTzRounding = new Rounding.TimeIntervalRounding(60000, tz);
|
||||
for (long time = start; time < end; time = time + 60000) {
|
||||
assertThat(tzRounding.nextRoundingValue(time), greaterThan(time));
|
||||
assertThat(dayTzRounding.nextRoundingValue(time), greaterThan(time));
|
||||
|
@ -442,7 +441,7 @@ public class TimeZoneRoundingTests extends ESTestCase {
|
|||
// standard +/-1 hour DST transition, CET
|
||||
DateTimeUnit timeUnit = DateTimeUnit.HOUR_OF_DAY;
|
||||
DateTimeZone tz = DateTimeZone.forID("CET");
|
||||
TimeZoneRounding rounding = new TimeZoneRounding.TimeUnitRounding(timeUnit, tz);
|
||||
Rounding rounding = new Rounding.TimeUnitRounding(timeUnit, tz);
|
||||
|
||||
// 29 Mar 2015 - Daylight Saving Time Started
|
||||
// at 02:00:00 clocks were turned forward 1 hour to 03:00:00
|
||||
|
@ -466,7 +465,7 @@ public class TimeZoneRoundingTests extends ESTestCase {
|
|||
// which is not a round value for hourly rounding
|
||||
DateTimeUnit timeUnit = DateTimeUnit.HOUR_OF_DAY;
|
||||
DateTimeZone tz = DateTimeZone.forID("Asia/Kathmandu");
|
||||
TimeZoneRounding rounding = new TimeZoneRounding.TimeUnitRounding(timeUnit, tz);
|
||||
Rounding rounding = new Rounding.TimeUnitRounding(timeUnit, tz);
|
||||
|
||||
assertInterval(time("1985-12-31T22:00:00.000+05:30"), time("1985-12-31T23:00:00.000+05:30"), rounding, 60, tz);
|
||||
assertInterval(time("1985-12-31T23:00:00.000+05:30"), time("1986-01-01T01:00:00.000+05:45"), rounding, 105, tz);
|
||||
|
@ -479,7 +478,7 @@ public class TimeZoneRoundingTests extends ESTestCase {
|
|||
// at 02:00:00 clocks were turned backward 0:30 hours to Sunday, 3 March 1991, 01:30:00
|
||||
DateTimeUnit timeUnit = DateTimeUnit.HOUR_OF_DAY;
|
||||
DateTimeZone tz = DateTimeZone.forID("Australia/Lord_Howe");
|
||||
TimeZoneRounding rounding = new TimeZoneRounding.TimeUnitRounding(timeUnit, tz);
|
||||
Rounding rounding = new Rounding.TimeUnitRounding(timeUnit, tz);
|
||||
|
||||
assertInterval(time("1991-03-03T00:00:00.000+11:00"), time("1991-03-03T01:00:00.000+11:00"), rounding, 60, tz);
|
||||
assertInterval(time("1991-03-03T01:00:00.000+11:00"), time("1991-03-03T02:00:00.000+10:30"), rounding, 90, tz);
|
||||
|
@ -499,7 +498,7 @@ public class TimeZoneRoundingTests extends ESTestCase {
|
|||
// at 03:45:00 clocks were turned backward 1 hour to 02:45:00
|
||||
DateTimeUnit timeUnit = DateTimeUnit.HOUR_OF_DAY;
|
||||
DateTimeZone tz = DateTimeZone.forID("Pacific/Chatham");
|
||||
TimeZoneRounding rounding = new TimeZoneRounding.TimeUnitRounding(timeUnit, tz);
|
||||
Rounding rounding = new Rounding.TimeUnitRounding(timeUnit, tz);
|
||||
|
||||
assertInterval(time("2015-04-05T02:00:00.000+13:45"), time("2015-04-05T03:00:00.000+13:45"), rounding, 60, tz);
|
||||
assertInterval(time("2015-04-05T03:00:00.000+13:45"), time("2015-04-05T03:00:00.000+12:45"), rounding, 60, tz);
|
||||
|
@ -514,7 +513,7 @@ public class TimeZoneRoundingTests extends ESTestCase {
|
|||
}
|
||||
}
|
||||
|
||||
private static void assertInterval(long rounded, long nextRoundingValue, TimeZoneRounding rounding, int minutes,
|
||||
private static void assertInterval(long rounded, long nextRoundingValue, Rounding rounding, int minutes,
|
||||
DateTimeZone tz) {
|
||||
assertInterval(rounded, dateBetween(rounded, nextRoundingValue), nextRoundingValue, rounding, tz);
|
||||
assertEquals(DateTimeConstants.MILLIS_PER_MINUTE * minutes, nextRoundingValue - rounded);
|
||||
|
@ -527,7 +526,7 @@ public class TimeZoneRoundingTests extends ESTestCase {
|
|||
* @param nextRoundingValue the expected upper end of the rounding interval
|
||||
* @param rounding the rounding instance
|
||||
*/
|
||||
private static void assertInterval(long rounded, long unrounded, long nextRoundingValue, TimeZoneRounding rounding,
|
||||
private static void assertInterval(long rounded, long unrounded, long nextRoundingValue, Rounding rounding,
|
||||
DateTimeZone tz) {
|
||||
assert rounded <= unrounded && unrounded <= nextRoundingValue;
|
||||
assertThat("rounding should be idempotent ", rounding.round(rounded), isDate(rounded, tz));
|
||||
|
|
Loading…
Reference in New Issue