public Rounding build() {
   Rounding timeZoneRounding;
   if (unit != null) {
     if (preTz.equals(DateTimeZone.UTC) && postTz.equals(DateTimeZone.UTC)) {
       timeZoneRounding = new UTCTimeZoneRoundingFloor(unit);
     } else if (preZoneAdjustLargeInterval
         || unit.field().getDurationField().getUnitMillis()
             < DateTimeConstants.MILLIS_PER_HOUR * 12) {
       timeZoneRounding = new TimeTimeZoneRoundingFloor(unit, preTz, postTz);
     } else {
       timeZoneRounding = new DayTimeZoneRoundingFloor(unit, preTz, postTz);
     }
   } else {
     if (preTz.equals(DateTimeZone.UTC) && postTz.equals(DateTimeZone.UTC)) {
       timeZoneRounding = new UTCIntervalTimeZoneRounding(interval);
     } else if (preZoneAdjustLargeInterval
         || interval < DateTimeConstants.MILLIS_PER_HOUR * 12) {
       timeZoneRounding = new TimeIntervalTimeZoneRounding(interval, preTz, postTz);
     } else {
       timeZoneRounding = new DayIntervalTimeZoneRounding(interval, preTz, postTz);
     }
   }
   if (preOffset != 0 || postOffset != 0) {
     timeZoneRounding = new PrePostRounding(timeZoneRounding, preOffset, postOffset);
   }
   if (factor != 1.0f) {
     timeZoneRounding = new FactorRounding(timeZoneRounding, factor);
   }
   return timeZoneRounding;
 }
  /**
   * 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));
        }
      }
    }
  }
 private static DateTimeUnit randomTimeUnit() {
   byte id = (byte) randomIntBetween(1, 8);
   return DateTimeUnit.resolve(id);
 }
 @Override
 public void writeTo(StreamOutput out) throws IOException {
   out.writeByte(unit.id());
   out.writeSharedString(preTz.getID());
   out.writeSharedString(postTz.getID());
 }
 @Override
 public long nextRoundingValue(long value) {
   return unit.field().getDurationField().getUnitMillis() + value;
 }
 @Override
 public void readFrom(StreamInput in) throws IOException {
   unit = DateTimeUnit.resolve(in.readByte());
   preTz = DateTimeZone.forID(in.readSharedString());
   postTz = DateTimeZone.forID(in.readSharedString());
 }
 @Override
 public long roundKey(long utcMillis) {
   long time = utcMillis + preTz.getOffset(utcMillis);
   return unit.field().roundFloor(time);
 }
 @Override
 public void writeTo(StreamOutput out) throws IOException {
   out.writeByte(unit.id());
 }
 @Override
 public void readFrom(StreamInput in) throws IOException {
   unit = DateTimeUnit.resolve(in.readByte());
 }
 @Override
 public long nextRoundingValue(long value) {
   return unit.field().roundCeiling(value + 1);
 }
 @Override
 public long roundKey(long utcMillis) {
   return unit.field().roundFloor(utcMillis);
 }