Remove TimeZoneRounding abstraction

Because the Rounding class now only deals with date based rounding of
values we can remove the TimeZoneRounding abstraction to simplify the
code.
This commit is contained in:
Colin Goodheart-Smithe 2016-08-03 16:05:52 +01:00
parent 5ab5cc69b8
commit c14155e4a8
6 changed files with 346 additions and 376 deletions

View File

@ -22,6 +22,10 @@ import org.elasticsearch.ElasticsearchException;
import org.elasticsearch.common.io.stream.StreamInput; import org.elasticsearch.common.io.stream.StreamInput;
import org.elasticsearch.common.io.stream.StreamOutput; import org.elasticsearch.common.io.stream.StreamOutput;
import org.elasticsearch.common.io.stream.Streamable; 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.io.IOException;
import java.util.Objects; import java.util.Objects;
@ -53,6 +57,279 @@ public abstract class Rounding implements Streamable {
@Override @Override
public abstract int hashCode(); public abstract int hashCode();
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 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 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);
}
return timeZoneRounding;
}
}
static class TimeUnitRounding extends Rounding {
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 Rounding {
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 = 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 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);
}
}
public static class OffsetRounding extends Rounding { public static class OffsetRounding extends Rounding {
static final byte ID = 8; static final byte ID = 8;
@ -95,12 +372,12 @@ public abstract class Rounding implements Streamable {
Rounding.Streams.write(rounding, out); Rounding.Streams.write(rounding, out);
out.writeLong(offset); out.writeLong(offset);
} }
@Override @Override
public int hashCode() { public int hashCode() {
return Objects.hash(rounding, offset); return Objects.hash(rounding, offset);
} }
@Override @Override
public boolean equals(Object obj) { public boolean equals(Object obj) {
if (obj == null) { if (obj == null) {
@ -126,8 +403,8 @@ public abstract class Rounding implements Streamable {
Rounding rounding = null; Rounding rounding = null;
byte id = in.readByte(); byte id = in.readByte();
switch (id) { switch (id) {
case TimeZoneRounding.TimeUnitRounding.ID: rounding = new TimeZoneRounding.TimeUnitRounding(); break; case TimeUnitRounding.ID: rounding = new TimeUnitRounding(); break;
case TimeZoneRounding.TimeIntervalRounding.ID: rounding = new TimeZoneRounding.TimeIntervalRounding(); break; case TimeIntervalRounding.ID: rounding = new TimeIntervalRounding(); break;
case OffsetRounding.ID: rounding = new OffsetRounding(); break; case OffsetRounding.ID: rounding = new OffsetRounding(); break;
default: throw new ElasticsearchException("unknown rounding id [" + id + "]"); default: throw new ElasticsearchException("unknown rounding id [" + id + "]");
} }

View File

@ -1,309 +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.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 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 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 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);
}
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 = 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 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);
}
}
}

View File

@ -24,7 +24,6 @@ import org.apache.lucene.util.CollectionUtil;
import org.elasticsearch.common.inject.internal.Nullable; import org.elasticsearch.common.inject.internal.Nullable;
import org.elasticsearch.common.lease.Releasables; import org.elasticsearch.common.lease.Releasables;
import org.elasticsearch.common.rounding.Rounding; import org.elasticsearch.common.rounding.Rounding;
import org.elasticsearch.common.rounding.TimeZoneRounding;
import org.elasticsearch.common.util.LongHash; import org.elasticsearch.common.util.LongHash;
import org.elasticsearch.search.DocValueFormat; import org.elasticsearch.search.DocValueFormat;
import org.elasticsearch.search.aggregations.Aggregator; 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 * An aggregator for date values. Every date is rounded down using a configured
* {@link TimeZoneRounding}. * {@link Rounding}.
* @see TimeZoneRounding *
* @see Rounding
*/ */
class DateHistogramAggregator extends BucketsAggregator { class DateHistogramAggregator extends BucketsAggregator {

View File

@ -21,7 +21,6 @@ package org.elasticsearch.search.aggregations.bucket.histogram;
import org.elasticsearch.common.rounding.DateTimeUnit; import org.elasticsearch.common.rounding.DateTimeUnit;
import org.elasticsearch.common.rounding.Rounding; import org.elasticsearch.common.rounding.Rounding;
import org.elasticsearch.common.rounding.TimeZoneRounding;
import org.elasticsearch.common.unit.TimeValue; import org.elasticsearch.common.unit.TimeValue;
import org.elasticsearch.search.aggregations.Aggregator; import org.elasticsearch.search.aggregations.Aggregator;
import org.elasticsearch.search.aggregations.AggregatorFactories; import org.elasticsearch.search.aggregations.AggregatorFactories;
@ -95,19 +94,19 @@ public final class DateHistogramAggregatorFactory
} }
private Rounding createRounding() { private Rounding createRounding() {
TimeZoneRounding.Builder tzRoundingBuilder; Rounding.Builder tzRoundingBuilder;
if (dateHistogramInterval != null) { if (dateHistogramInterval != null) {
DateTimeUnit dateTimeUnit = DATE_FIELD_UNITS.get(dateHistogramInterval.toString()); DateTimeUnit dateTimeUnit = DATE_FIELD_UNITS.get(dateHistogramInterval.toString());
if (dateTimeUnit != null) { if (dateTimeUnit != null) {
tzRoundingBuilder = TimeZoneRounding.builder(dateTimeUnit); tzRoundingBuilder = Rounding.builder(dateTimeUnit);
} else { } else {
// the interval is a time value? // the interval is a time value?
tzRoundingBuilder = TimeZoneRounding.builder( tzRoundingBuilder = Rounding.builder(
TimeValue.parseTimeValue(dateHistogramInterval.toString(), null, getClass().getSimpleName() + ".interval")); TimeValue.parseTimeValue(dateHistogramInterval.toString(), null, getClass().getSimpleName() + ".interval"));
} }
} else { } else {
// the interval is an integer time value in millis? // the interval is an integer time value in millis?
tzRoundingBuilder = TimeZoneRounding.builder(TimeValue.timeValueMillis(interval)); tzRoundingBuilder = Rounding.builder(TimeValue.timeValueMillis(interval));
} }
if (timeZone() != null) { if (timeZone() != null) {
tzRoundingBuilder.timeZone(timeZone()); tzRoundingBuilder.timeZone(timeZone());

View File

@ -37,7 +37,7 @@ public class OffsetRoundingTests extends ESTestCase {
final long interval = 10; final long interval = 10;
final long offset = 7; final long offset = 7;
Rounding.OffsetRounding rounding = new Rounding.OffsetRounding( Rounding.OffsetRounding rounding = new Rounding.OffsetRounding(
new TimeZoneRounding.TimeIntervalRounding(interval, DateTimeZone.UTC), offset); new Rounding.TimeIntervalRounding(interval, DateTimeZone.UTC), offset);
assertEquals(-3, rounding.round(6)); assertEquals(-3, rounding.round(6));
assertEquals(7, rounding.nextRoundingValue(-3)); assertEquals(7, rounding.nextRoundingValue(-3));
assertEquals(7, rounding.round(7)); assertEquals(7, rounding.round(7));
@ -53,7 +53,7 @@ public class OffsetRoundingTests extends ESTestCase {
public void testOffsetRoundingRandom() { public void testOffsetRoundingRandom() {
for (int i = 0; i < 1000; ++i) { for (int i = 0; i < 1000; ++i) {
final long interval = randomIntBetween(1, 100); final long interval = randomIntBetween(1, 100);
Rounding internalRounding = new TimeZoneRounding.TimeIntervalRounding(interval, DateTimeZone.UTC); Rounding internalRounding = new Rounding.TimeIntervalRounding(interval, DateTimeZone.UTC);
final long offset = randomIntBetween(-100, 100); final long offset = randomIntBetween(-100, 100);
Rounding.OffsetRounding rounding = new Rounding.OffsetRounding(internalRounding, offset); Rounding.OffsetRounding rounding = new Rounding.OffsetRounding(internalRounding, offset);
long safetyMargin = Math.abs(interval) + Math.abs(offset); // to prevent range overflow long safetyMargin = Math.abs(interval) + Math.abs(offset); // to prevent range overflow

View File

@ -20,8 +20,8 @@
package org.elasticsearch.common.rounding; package org.elasticsearch.common.rounding;
import org.elasticsearch.common.collect.Tuple; import org.elasticsearch.common.collect.Tuple;
import org.elasticsearch.common.rounding.TimeZoneRounding.TimeIntervalRounding; import org.elasticsearch.common.rounding.Rounding.TimeIntervalRounding;
import org.elasticsearch.common.rounding.TimeZoneRounding.TimeUnitRounding; import org.elasticsearch.common.rounding.Rounding.TimeUnitRounding;
import org.elasticsearch.common.unit.TimeValue; import org.elasticsearch.common.unit.TimeValue;
import org.elasticsearch.test.ESTestCase; import org.elasticsearch.test.ESTestCase;
import org.hamcrest.Description; import org.hamcrest.Description;
@ -47,29 +47,29 @@ import static org.hamcrest.Matchers.lessThanOrEqualTo;
public class TimeZoneRoundingTests extends ESTestCase { public class TimeZoneRoundingTests extends ESTestCase {
public void testUTCTimeUnitRounding() { 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; DateTimeZone tz = DateTimeZone.UTC;
assertThat(tzRounding.round(time("2009-02-03T01:01:01")), isDate(time("2009-02-01T00:00:00.000Z"), tz)); 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)); 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.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)); 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(); tzRounding = Rounding.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.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)); assertThat(tzRounding.nextRoundingValue(time("2012-01-08T00:00:00.000Z")), isDate(time("2012-01-15T00:00:00.000Z"), tz));
} }
public void testUTCIntervalRounding() { public void testUTCIntervalRounding() {
Rounding tzRounding = TimeZoneRounding.builder(TimeValue.timeValueHours(12)).build(); Rounding tzRounding = Rounding.builder(TimeValue.timeValueHours(12)).build();
DateTimeZone tz = DateTimeZone.UTC; DateTimeZone tz = DateTimeZone.UTC;
assertThat(tzRounding.round(time("2009-02-03T01:01:01")), isDate(time("2009-02-03T00:00:00.000Z"), tz)); 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.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.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)); 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.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.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)); assertThat(tzRounding.round(time("2009-02-05T13:01:01")), isDate(time("2009-02-05T00:00:00.000Z"), tz));
@ -77,11 +77,11 @@ public class TimeZoneRoundingTests extends ESTestCase {
} }
/** /**
* test TimeIntervalTimeZoneRounding, (interval &lt; 12h) with time zone shift * test TimeIntervalRounding, (interval &lt; 12h) with time zone shift
*/ */
public void testTimeIntervalTimeZoneRounding() { public void testTimeIntervalRounding() {
DateTimeZone tz = DateTimeZone.forOffsetHours(-1); 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.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)); assertThat(tzRounding.nextRoundingValue(time("2009-02-02T19:00:00.000Z")), isDate(time("2009-02-03T01:00:00.000Z"), tz));
@ -90,11 +90,11 @@ public class TimeZoneRoundingTests extends ESTestCase {
} }
/** /**
* test DayIntervalTimeZoneRounding, (interval &gt;= 12h) with time zone shift * test DayIntervalRounding, (interval &gt;= 12h) with time zone shift
*/ */
public void testDayIntervalTimeZoneRounding() { public void testDayIntervalRounding() {
DateTimeZone tz = DateTimeZone.forOffsetHours(-8); 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.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)); assertThat(tzRounding.nextRoundingValue(time("2009-02-02T20:00:00.000Z")), isDate(time("2009-02-03T08:00:00.000Z"), tz));
@ -102,37 +102,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)); 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; 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(); .build();
assertThat(tzRounding.round(0), equalTo(0L - TimeValue.timeValueHours(24 + timezoneOffset).millis())); assertThat(tzRounding.round(0), equalTo(0L - TimeValue.timeValueHours(24 + timezoneOffset).millis()));
assertThat(tzRounding.nextRoundingValue(0L - TimeValue.timeValueHours(24 + timezoneOffset).millis()), equalTo(0L - TimeValue assertThat(tzRounding.nextRoundingValue(0L - TimeValue.timeValueHours(24 + timezoneOffset).millis()), equalTo(0L - TimeValue
.timeValueHours(timezoneOffset).millis())); .timeValueHours(timezoneOffset).millis()));
DateTimeZone tz = DateTimeZone.forID("-08:00"); 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)); 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"))); 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 // date in Feb-3rd, but still in Feb-2nd in -02:00 timezone
tz = DateTimeZone.forID("-02:00"); 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.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)); 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 // 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.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)); 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 // hour unit
DateTimeZone tz = DateTimeZone.forOffsetHours(-2); 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.round(0), equalTo(0L));
assertThat(tzRounding.nextRoundingValue(0L), equalTo(TimeValue.timeValueHours(1L).getMillis())); assertThat(tzRounding.nextRoundingValue(0L), equalTo(TimeValue.timeValueHours(1L).getMillis()));
@ -144,23 +144,23 @@ public class TimeZoneRoundingTests extends ESTestCase {
Rounding tzRounding; Rounding tzRounding;
// testing savings to non savings switch // testing savings to non savings switch
DateTimeZone cet = DateTimeZone.forID("CET"); 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.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-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)); 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 // 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.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-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)); 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) // testing non savings to savings switch (America/Chicago)
DateTimeZone chg = DateTimeZone.forID("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)); 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)); 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) // testing savings to non savings switch 2013 (America/Chicago)
@ -173,18 +173,21 @@ public class TimeZoneRoundingTests extends ESTestCase {
} }
/** /**
* Randomized test on TimeUnitRounding. * Randomized test on TimeUnitRounding. Test uses random
* Test uses random {@link DateTimeUnit} and {@link DateTimeZone} and often (50% of the time) chooses * {@link DateTimeUnit} and {@link DateTimeZone} and often (50% of the time)
* test dates that are exactly on or close to offset changes (e.g. DST) in the chosen time zone. * 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 * It rounds the test date down and up and performs various checks on the
* defined by this. Assumptions tested are described in {@link #assertInterval(long, long, long, TimeZoneRounding, DateTimeZone)} * 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) { for (int i = 0; i < 1000; ++i) {
DateTimeUnit timeUnit = randomTimeUnit(); DateTimeUnit timeUnit = randomTimeUnit();
DateTimeZone tz = randomDateTimeZone(); 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 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(); long unitMillis = timeUnit.field(tz).getDurationField().getUnitMillis();
if (randomBoolean()) { if (randomBoolean()) {
@ -226,7 +229,7 @@ public class TimeZoneRoundingTests extends ESTestCase {
public void testTimeIntervalCET_DST_End() { public void testTimeIntervalCET_DST_End() {
long interval = TimeUnit.MINUTES.toMillis(20); long interval = TimeUnit.MINUTES.toMillis(20);
DateTimeZone tz = DateTimeZone.forID("CET"); 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-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)); assertThat(rounding.round(time("2015-10-25T02:15:00+02:00")), isDate(time("2015-10-25T02:00:00+02:00"), tz));
@ -246,7 +249,7 @@ public class TimeZoneRoundingTests extends ESTestCase {
public void testTimeIntervalCET_DST_Start() { public void testTimeIntervalCET_DST_Start() {
long interval = TimeUnit.MINUTES.toMillis(20); long interval = TimeUnit.MINUTES.toMillis(20);
DateTimeZone tz = DateTimeZone.forID("CET"); DateTimeZone tz = DateTimeZone.forID("CET");
TimeZoneRounding rounding = new TimeIntervalRounding(interval, tz); Rounding rounding = new TimeIntervalRounding(interval, tz);
// test DST start // 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-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)); assertThat(rounding.round(time("2016-03-27T02:00:00+01:00")), isDate(time("2016-03-27T03:00:00+02:00"), tz));
@ -263,7 +266,7 @@ public class TimeZoneRoundingTests extends ESTestCase {
public void testTimeInterval_Kathmandu_DST_Start() { public void testTimeInterval_Kathmandu_DST_Start() {
long interval = TimeUnit.MINUTES.toMillis(20); long interval = TimeUnit.MINUTES.toMillis(20);
DateTimeZone tz = DateTimeZone.forID("Asia/Kathmandu"); 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("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(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))); assertThat(time("1986-01-01T00:15:00+05:45") - time("1985-12-31T23:40:00+05:30"), equalTo(TimeUnit.MINUTES.toMillis(20)));
@ -281,7 +284,7 @@ public class TimeZoneRoundingTests extends ESTestCase {
public void testIntervalRounding_NotDivisibleInteval() { public void testIntervalRounding_NotDivisibleInteval() {
DateTimeZone tz = DateTimeZone.forID("CET"); DateTimeZone tz = DateTimeZone.forID("CET");
long interval = TimeUnit.MINUTES.toMillis(14); 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: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)); assertThat(rounding.round(time("2016-03-27T01:51:00+01:00")), isDate(time("2016-03-27T01:44:00+01:00"), tz));
@ -298,7 +301,7 @@ public class TimeZoneRoundingTests extends ESTestCase {
public void testIntervalRounding_HalfDay_DST() { public void testIntervalRounding_HalfDay_DST() {
DateTimeZone tz = DateTimeZone.forID("CET"); DateTimeZone tz = DateTimeZone.forID("CET");
long interval = TimeUnit.HOURS.toMillis(12); 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-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)); assertThat(rounding.round(time("2016-03-26T13:00:00+01:00")), isDate(time("2016-03-26T12:00:00+01:00"), tz));
@ -316,7 +319,7 @@ public class TimeZoneRoundingTests extends ESTestCase {
TimeUnit unit = randomFrom(new TimeUnit[] {TimeUnit.MINUTES, TimeUnit.HOURS, TimeUnit.DAYS}); TimeUnit unit = randomFrom(new TimeUnit[] {TimeUnit.MINUTES, TimeUnit.HOURS, TimeUnit.DAYS});
long interval = unit.toMillis(randomIntBetween(1, 365)); long interval = unit.toMillis(randomIntBetween(1, 365));
DateTimeZone tz = randomDateTimeZone(); 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 long mainDate = Math.abs(randomLong() % (2 * (long) 10e11)); // 1970-01-01T00:00:00Z - 2033-05-18T05:33:20.000+02:00
if (randomBoolean()) { if (randomBoolean()) {
mainDate = nastyDate(mainDate, tz, interval); mainDate = nastyDate(mainDate, tz, interval);
@ -356,8 +359,8 @@ public class TimeZoneRoundingTests extends ESTestCase {
public void testIntervalRoundingMonotonic_CET() { public void testIntervalRoundingMonotonic_CET() {
long interval = TimeUnit.MINUTES.toMillis(45); long interval = TimeUnit.MINUTES.toMillis(45);
DateTimeZone tz = DateTimeZone.forID("CET"); DateTimeZone tz = DateTimeZone.forID("CET");
TimeZoneRounding rounding = new TimeZoneRounding.TimeIntervalRounding(interval, tz); Rounding rounding = new Rounding.TimeIntervalRounding(interval, tz);
List<Tuple<String, String>> expectedDates = new ArrayList<Tuple<String, String>>(); List<Tuple<String, String>> expectedDates = new ArrayList<>();
// first date is the date to be rounded, second the expected result // 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-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")); expectedDates.add(new Tuple<>("2011-10-30T02:02:30.000+02:00", "2011-10-30T01:30:00.000+02:00"));
@ -387,7 +390,7 @@ public class TimeZoneRoundingTests extends ESTestCase {
public void testAmbiguousHoursAfterDSTSwitch() { public void testAmbiguousHoursAfterDSTSwitch() {
Rounding tzRounding; Rounding tzRounding;
final DateTimeZone tz = DateTimeZone.forID("Asia/Jerusalem"); 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-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)); 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 // 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 +399,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)); assertThat(tzRounding.round(time("2014-10-26T02:30:00+02:00")), isDate(time("2014-10-26T02:00:00+02:00"), tz));
// Day interval // 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)); assertThat(tzRounding.round(time("2014-11-11T17:00:00", tz)), isDate(time("2014-11-11T00:00:00", tz), tz));
// DST on // DST on
assertThat(tzRounding.round(time("2014-08-11T17:00:00", tz)), isDate(time("2014-08-11T00:00:00", tz), tz)); assertThat(tzRounding.round(time("2014-08-11T17:00:00", tz)), isDate(time("2014-08-11T00:00:00", tz), tz));
@ -406,17 +409,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)); assertThat(tzRounding.round(time("2015-03-27T17:00:00", tz)), isDate(time("2015-03-27T00:00:00", tz), tz));
// Month interval // 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)); assertThat(tzRounding.round(time("2014-11-11T17:00:00", tz)), isDate(time("2014-11-01T00:00:00", tz), tz));
// DST on // DST on
assertThat(tzRounding.round(time("2014-10-10T17:00:00", tz)), isDate(time("2014-10-01T00:00:00", tz), tz)); assertThat(tzRounding.round(time("2014-10-10T17:00:00", tz)), isDate(time("2014-10-01T00:00:00", tz), tz));
// Year interval // 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)); 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) // 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)), assertThat(tzRounding.round(time("2014-11-11T17:00:00", tz)),
isDate(tzRounding.round(time("2014-08-11T17:00:00", tz)), tz)); isDate(tzRounding.round(time("2014-08-11T17:00:00", tz)), tz));
} }
@ -429,8 +432,8 @@ public class TimeZoneRoundingTests extends ESTestCase {
DateTimeZone tz = DateTimeZone.forID("America/Sao_Paulo"); DateTimeZone tz = DateTimeZone.forID("America/Sao_Paulo");
long start = time("2014-10-18T20:50:00.000", tz); long start = time("2014-10-18T20:50:00.000", tz);
long end = time("2014-10-19T01:00:00.000", tz); long end = time("2014-10-19T01:00:00.000", tz);
Rounding tzRounding = new TimeZoneRounding.TimeUnitRounding(DateTimeUnit.MINUTES_OF_HOUR, tz); Rounding tzRounding = new Rounding.TimeUnitRounding(DateTimeUnit.MINUTES_OF_HOUR, tz);
Rounding dayTzRounding = new TimeZoneRounding.TimeIntervalRounding(60000, tz); Rounding dayTzRounding = new Rounding.TimeIntervalRounding(60000, tz);
for (long time = start; time < end; time = time + 60000) { for (long time = start; time < end; time = time + 60000) {
assertThat(tzRounding.nextRoundingValue(time), greaterThan(time)); assertThat(tzRounding.nextRoundingValue(time), greaterThan(time));
assertThat(dayTzRounding.nextRoundingValue(time), greaterThan(time)); assertThat(dayTzRounding.nextRoundingValue(time), greaterThan(time));
@ -442,7 +445,7 @@ public class TimeZoneRoundingTests extends ESTestCase {
// standard +/-1 hour DST transition, CET // standard +/-1 hour DST transition, CET
DateTimeUnit timeUnit = DateTimeUnit.HOUR_OF_DAY; DateTimeUnit timeUnit = DateTimeUnit.HOUR_OF_DAY;
DateTimeZone tz = DateTimeZone.forID("CET"); 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 // 29 Mar 2015 - Daylight Saving Time Started
// at 02:00:00 clocks were turned forward 1 hour to 03:00:00 // at 02:00:00 clocks were turned forward 1 hour to 03:00:00
@ -466,7 +469,7 @@ public class TimeZoneRoundingTests extends ESTestCase {
// which is not a round value for hourly rounding // which is not a round value for hourly rounding
DateTimeUnit timeUnit = DateTimeUnit.HOUR_OF_DAY; DateTimeUnit timeUnit = DateTimeUnit.HOUR_OF_DAY;
DateTimeZone tz = DateTimeZone.forID("Asia/Kathmandu"); 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-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); assertInterval(time("1985-12-31T23:00:00.000+05:30"), time("1986-01-01T01:00:00.000+05:45"), rounding, 105, tz);
@ -479,7 +482,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 // 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; DateTimeUnit timeUnit = DateTimeUnit.HOUR_OF_DAY;
DateTimeZone tz = DateTimeZone.forID("Australia/Lord_Howe"); 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-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); assertInterval(time("1991-03-03T01:00:00.000+11:00"), time("1991-03-03T02:00:00.000+10:30"), rounding, 90, tz);
@ -499,7 +502,7 @@ public class TimeZoneRoundingTests extends ESTestCase {
// at 03:45:00 clocks were turned backward 1 hour to 02:45:00 // at 03:45:00 clocks were turned backward 1 hour to 02:45:00
DateTimeUnit timeUnit = DateTimeUnit.HOUR_OF_DAY; DateTimeUnit timeUnit = DateTimeUnit.HOUR_OF_DAY;
DateTimeZone tz = DateTimeZone.forID("Pacific/Chatham"); 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-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); assertInterval(time("2015-04-05T03:00:00.000+13:45"), time("2015-04-05T03:00:00.000+12:45"), rounding, 60, tz);
@ -514,7 +517,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) { DateTimeZone tz) {
assertInterval(rounded, dateBetween(rounded, nextRoundingValue), nextRoundingValue, rounding, tz); assertInterval(rounded, dateBetween(rounded, nextRoundingValue), nextRoundingValue, rounding, tz);
assertEquals(DateTimeConstants.MILLIS_PER_MINUTE * minutes, nextRoundingValue - rounded); assertEquals(DateTimeConstants.MILLIS_PER_MINUTE * minutes, nextRoundingValue - rounded);
@ -527,7 +530,7 @@ public class TimeZoneRoundingTests extends ESTestCase {
* @param nextRoundingValue the expected upper end of the rounding interval * @param nextRoundingValue the expected upper end of the rounding interval
* @param rounding the rounding instance * @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) { DateTimeZone tz) {
assert rounded <= unrounded && unrounded <= nextRoundingValue; assert rounded <= unrounded && unrounded <= nextRoundingValue;
assertThat("rounding should be idempotent ", rounding.round(rounded), isDate(rounded, tz)); assertThat("rounding should be idempotent ", rounding.round(rounded), isDate(rounded, tz));