/**
  * test DST start with offset not fitting interval, e.g. Asia/Kathmandu adding 15min on
  * 1986-01-01T00:00:00 the interval from 1986-01-01T00:15:00+05:45 to 1986-01-01T00:20:00+05:45 to
  * only be 5min long
  */
 public void testTimeInterval_Kathmandu_DST_Start() {
   long interval = TimeUnit.MINUTES.toMillis(20);
   DateTimeZone tz = DateTimeZone.forID("Asia/Kathmandu");
   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)));
   assertThat(
       rounding.round(time("1986-01-01T00:26:00+05:45")),
       isDate(time("1986-01-01T00:20:00+05:45"), tz));
   assertThat(
       time("1986-01-01T00:20:00+05:45") - time("1986-01-01T00:15:00+05:45"),
       equalTo(TimeUnit.MINUTES.toMillis(5)));
   assertThat(
       rounding.round(time("1986-01-01T00:46:00+05:45")),
       isDate(time("1986-01-01T00:40:00+05:45"), tz));
   assertThat(
       time("1986-01-01T00:40:00+05:45") - time("1986-01-01T00:20:00+05:45"),
       equalTo(TimeUnit.MINUTES.toMillis(20)));
 }
  /**
   * test DST end with interval rounding CET: 25 October 2015, 03:00:00 clocks were turned backward
   * 1 hour to 25 October 2015, 02:00:00 local standard time
   */
  public void testTimeIntervalCET_DST_End() {
    long interval = TimeUnit.MINUTES.toMillis(20);
    DateTimeZone tz = DateTimeZone.forID("CET");
    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));
    assertThat(
        rounding.round(time("2015-10-25T02:35:00+02:00")),
        isDate(time("2015-10-25T02:20:00+02:00"), tz));
    assertThat(
        rounding.round(time("2015-10-25T02:55:00+02:00")),
        isDate(time("2015-10-25T02:40:00+02:00"), tz));
    // after DST shift
    assertThat(
        rounding.round(time("2015-10-25T02:15:00+01:00")),
        isDate(time("2015-10-25T02:00:00+01:00"), tz));
    assertThat(
        rounding.round(time("2015-10-25T02:35:00+01:00")),
        isDate(time("2015-10-25T02:20:00+01:00"), tz));
    assertThat(
        rounding.round(time("2015-10-25T02:55:00+01:00")),
        isDate(time("2015-10-25T02:40:00+01:00"), tz));
    assertThat(
        rounding.round(time("2015-10-25T03:15:00+01:00")),
        isDate(time("2015-10-25T03:00:00+01:00"), tz));
  }
 /** test for #10025, strict local to UTC conversion can cause joda exceptions on DST start */
 public void testLenientConversionDST() {
   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 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));
   }
 }
  /**
   * Special test for intervals that don't fit evenly into rounding interval. In this case, when
   * interval crosses DST transition point, rounding in local time can land in a DST gap which
   * results in wrong UTC rounding values.
   */
  public void testIntervalRounding_NotDivisibleInteval() {
    DateTimeZone tz = DateTimeZone.forID("CET");
    long interval = TimeUnit.MINUTES.toMillis(14);
    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));
    assertThat(
        rounding.round(time("2016-03-27T01:59:00+01:00")),
        isDate(time("2016-03-27T01:58:00+01:00"), tz));
    assertThat(
        rounding.round(time("2016-03-27T03:05:00+02:00")),
        isDate(time("2016-03-27T03:00:00+02:00"), tz));
    assertThat(
        rounding.round(time("2016-03-27T03:12:00+02:00")),
        isDate(time("2016-03-27T03:08:00+02:00"), tz));
    assertThat(
        rounding.round(time("2016-03-27T03:25:00+02:00")),
        isDate(time("2016-03-27T03:22:00+02:00"), tz));
    assertThat(
        rounding.round(time("2016-03-27T03:39:00+02:00")),
        isDate(time("2016-03-27T03:36:00+02:00"), tz));
  }
 /**
  * test DST start with interval rounding CET: 27 March 2016, 02:00:00 clocks were turned forward 1
  * hour to 27 March 2016, 03:00:00 local daylight time
  */
 public void testTimeIntervalCET_DST_Start() {
   long interval = TimeUnit.MINUTES.toMillis(20);
   DateTimeZone tz = DateTimeZone.forID("CET");
   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));
   assertThat(
       rounding.round(time("2016-03-27T03:15:00+02:00")),
       isDate(time("2016-03-27T03:00:00+02:00"), tz));
   assertThat(
       rounding.round(time("2016-03-27T03:35:00+02:00")),
       isDate(time("2016-03-27T03:20:00+02:00"), tz));
 }
  /**
   * Test that rounded values are always greater or equal to last rounded value if date is
   * increasing. The example covers an interval around 2011-10-30T02:10:00+01:00, time zone CET,
   * interval: 2700000ms
   */
  public void testIntervalRoundingMonotonic_CET() {
    long interval = TimeUnit.MINUTES.toMillis(45);
    DateTimeZone tz = DateTimeZone.forID("CET");
    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"));
    expectedDates.add(
        new Tuple<>("2011-10-30T02:25:00.000+02:00", "2011-10-30T02:15:00.000+02:00"));
    expectedDates.add(
        new Tuple<>("2011-10-30T02:47:30.000+02:00", "2011-10-30T02:15:00.000+02:00"));
    expectedDates.add(
        new Tuple<>("2011-10-30T02:10:00.000+01:00", "2011-10-30T02:15:00.000+02:00"));
    expectedDates.add(
        new Tuple<>("2011-10-30T02:32:30.000+01:00", "2011-10-30T02:15:00.000+01:00"));
    expectedDates.add(
        new Tuple<>("2011-10-30T02:55:00.000+01:00", "2011-10-30T02:15:00.000+01:00"));
    expectedDates.add(
        new Tuple<>("2011-10-30T03:17:30.000+01:00", "2011-10-30T03:00:00.000+01:00"));

    long previousDate = Long.MIN_VALUE;
    for (Tuple<String, String> dates : expectedDates) {
      final long roundedDate = rounding.round(time(dates.v1()));
      assertThat(roundedDate, isDate(time(dates.v2()), tz));
      assertThat(roundedDate, greaterThanOrEqualTo(previousDate));
      previousDate = roundedDate;
    }
    // here's what this means for interval widths
    assertEquals(
        TimeUnit.MINUTES.toMillis(45),
        time("2011-10-30T02:15:00.000+02:00") - time("2011-10-30T01:30:00.000+02:00"));
    assertEquals(
        TimeUnit.MINUTES.toMillis(60),
        time("2011-10-30T02:15:00.000+01:00") - time("2011-10-30T02:15:00.000+02:00"));
    assertEquals(
        TimeUnit.MINUTES.toMillis(45),
        time("2011-10-30T03:00:00.000+01:00") - time("2011-10-30T02:15:00.000+01:00"));
  }
  /**
   * perform a number on assertions and checks on {@link TimeUnitRounding} intervals
   *
   * @param rounded the expected low end of the rounding interval
   * @param unrounded a date in the interval to be checked for rounding
   * @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, Rounding rounding, DateTimeZone tz) {
    assert rounded <= unrounded && unrounded <= nextRoundingValue;
    assertThat("rounding should be idempotent ", rounding.round(rounded), isDate(rounded, tz));
    assertThat(
        "rounded value smaller or equal than unrounded" + rounding,
        rounded,
        lessThanOrEqualTo(unrounded));
    assertThat(
        "values less than rounded should round further down" + rounding,
        rounding.round(rounded - 1),
        lessThan(rounded));
    assertThat(
        "nextRounding value should be greater than date" + rounding,
        nextRoundingValue,
        greaterThan(unrounded));
    assertThat(
        "nextRounding value should be a rounded date",
        rounding.round(nextRoundingValue),
        isDate(nextRoundingValue, tz));
    assertThat(
        "values above nextRounding should round down there",
        rounding.round(nextRoundingValue + 1),
        isDate(nextRoundingValue, tz));

    long dateBetween = dateBetween(rounded, nextRoundingValue);
    assertThat(
        "dateBetween should round down to roundedDate",
        rounding.round(dateBetween),
        isDate(rounded, tz));
    assertThat(
        "dateBetween should round up to nextRoundingValue",
        rounding.nextRoundingValue(dateBetween),
        isDate(nextRoundingValue, tz));
  }
  /**
   * 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.
   *
   * <p>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 testRoundingRandom() {
    for (int i = 0; i < 1000; ++i) {
      DateTimeUnit timeUnit = randomTimeUnit();
      DateTimeZone tz = randomDateTimeZone();
      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()) {
        nastyDate(date, tz, unitMillis);
      }
      final long roundedDate = rounding.round(date);
      final long nextRoundingValue = rounding.nextRoundingValue(roundedDate);

      assertInterval(roundedDate, date, nextRoundingValue, rounding, tz);

      // check correct unit interval width for units smaller than a day, they should be fixed size
      // except for transitions
      if (unitMillis <= DateTimeConstants.MILLIS_PER_DAY) {
        // if the interval defined didn't cross timezone offset transition, it should cover
        // unitMillis width
        if (tz.getOffset(roundedDate - 1) == tz.getOffset(nextRoundingValue + 1)) {
          assertThat(
              "unit interval width not as expected for ["
                  + timeUnit
                  + "], ["
                  + tz
                  + "] at "
                  + new DateTime(roundedDate),
              nextRoundingValue - roundedDate,
              equalTo(unitMillis));
        }
      }
    }
  }
  public void testTimeRounding() {
    // hour unit
    DateTimeZone tz = DateTimeZone.forOffsetHours(-2);
    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()));

    assertThat(
        tzRounding.round(time("2009-02-03T01:01:01")), isDate(time("2009-02-03T01:00:00"), tz));
    assertThat(
        tzRounding.nextRoundingValue(time("2009-02-03T01:00:00")),
        isDate(time("2009-02-03T02:00:00"), tz));
  }
  public void testUTCTimeUnitRounding() {
    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 = 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));
  }
  /** test TimeIntervalRounding, (interval &lt; 12h) with time zone shift */
  public void testTimeIntervalRounding() {
    DateTimeZone tz = DateTimeZone.forOffsetHours(-1);
    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));

    assertThat(
        tzRounding.round(time("2009-02-03T13:01:01")),
        isDate(time("2009-02-03T13:00:00.000Z"), tz));
    assertThat(
        tzRounding.nextRoundingValue(time("2009-02-03T13:00:00.000Z")),
        isDate(time("2009-02-03T19:00:00.000Z"), tz));
  }
  /** Test for half day rounding intervals scrossing DST. */
  public void testIntervalRounding_HalfDay_DST() {
    DateTimeZone tz = DateTimeZone.forID("CET");
    long interval = TimeUnit.HOURS.toMillis(12);
    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));
    assertThat(
        rounding.round(time("2016-03-27T01:00:00+01:00")),
        isDate(time("2016-03-27T00:00:00+01:00"), tz));
    assertThat(
        rounding.round(time("2016-03-27T13:00:00+02:00")),
        isDate(time("2016-03-27T12:00:00+02:00"), tz));
    assertThat(
        rounding.round(time("2016-03-28T01:00:00+02:00")),
        isDate(time("2016-03-28T00:00:00+02:00"), tz));
    assertThat(
        rounding.round(time("2016-03-28T13:00:00+02:00")),
        isDate(time("2016-03-28T12:00:00+02:00"), tz));
  }
  public void testDayRounding() {
    int timezoneOffset = -2;
    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 = 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 = 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 = 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 = 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 testTimeUnitRoundingDST() {
    Rounding tzRounding;
    // testing savings to non savings switch
    DateTimeZone cet = DateTimeZone.forID("CET");
    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 = 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 =
        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 = 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)
    assertThat(
        tzRounding_utc.round(time("2013-11-03T06:01:01", chg)),
        isDate(time("2013-11-03T06:00:00", chg), chg));
    assertThat(
        tzRounding_chg.round(time("2013-11-03T06:01:01", chg)),
        isDate(time("2013-11-03T06:00:00", chg), chg));

    // testing savings to non savings switch 2014 (America/Chicago)
    assertThat(
        tzRounding_utc.round(time("2014-11-02T06:01:01", chg)),
        isDate(time("2014-11-02T06:00:00", chg), chg));
    assertThat(
        tzRounding_chg.round(time("2014-11-02T06:01:01", chg)),
        isDate(time("2014-11-02T06:00:00", chg), chg));
  }
  /** randomized test on {@link TimeIntervalRounding} with random interval and time zone offsets */
  public void testIntervalRoundingRandom() {
    for (int i = 0; i < 1000; i++) {
      TimeUnit unit = randomFrom(new TimeUnit[] {TimeUnit.MINUTES, TimeUnit.HOURS, TimeUnit.DAYS});
      long interval = unit.toMillis(randomIntBetween(1, 365));
      DateTimeZone tz = randomDateTimeZone();
      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);
      }
      // check two intervals around date
      long previousRoundedValue = Long.MIN_VALUE;
      for (long date = mainDate - 2 * interval;
          date < mainDate + 2 * interval;
          date += interval / 2) {
        try {
          final long roundedDate = rounding.round(date);
          final long nextRoundingValue = rounding.nextRoundingValue(roundedDate);
          assertThat(
              "Rounding should be idempotent", roundedDate, equalTo(rounding.round(roundedDate)));
          assertThat(
              "Rounded value smaller or equal than unrounded",
              roundedDate,
              lessThanOrEqualTo(date));
          assertThat(
              "Values smaller than rounded value should round further down",
              rounding.round(roundedDate - 1),
              lessThan(roundedDate));
          assertThat(
              "Rounding should be >= previous rounding value",
              roundedDate,
              greaterThanOrEqualTo(previousRoundedValue));

          if (tz.isFixed()) {
            assertThat(
                "NextRounding value should be greater than date",
                nextRoundingValue,
                greaterThan(roundedDate));
            assertThat(
                "NextRounding value should be interval from rounded value",
                nextRoundingValue - roundedDate,
                equalTo(interval));
            assertThat(
                "NextRounding value should be a rounded date",
                nextRoundingValue,
                equalTo(rounding.round(nextRoundingValue)));
          }
          previousRoundedValue = roundedDate;
        } catch (AssertionError e) {
          logger.error(
              "Rounding error at {}, timezone {}, interval: {},",
              new DateTime(date, tz),
              tz,
              interval);
          throw e;
        }
      }
    }
  }
  public void testUTCIntervalRounding() {
    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 = 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));
    assertThat(
        tzRounding.nextRoundingValue(time("2009-02-05T00:00:00.000Z")),
        isDate(time("2009-02-07T00:00:00.000Z"), tz));
  }
  /** special test for DST switch from #9491 */
  public void testAmbiguousHoursAfterDSTSwitch() {
    Rounding tzRounding;
    final DateTimeZone tz = DateTimeZone.forID("Asia/Jerusalem");
    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
    assertThat(time("2014-10-26T03:00:00+03:00"), isDate(time("2014-10-26T02:00:00+02:00"), tz));
    assertThat(
        tzRounding.round(time("2014-10-26T01:30:00+02:00")),
        isDate(time("2014-10-26T01: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
    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));
    // Day of switching DST on -> off
    assertThat(
        tzRounding.round(time("2014-10-26T17:00:00", tz)),
        isDate(time("2014-10-26T00:00:00", tz), tz));
    // Day of switching DST off -> on
    assertThat(
        tzRounding.round(time("2015-03-27T17:00:00", tz)),
        isDate(time("2015-03-27T00:00:00", tz), tz));

    // Month interval
    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 = 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 = 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));
  }