4
\$\begingroup\$

Question originally posted on Stack Overflow here.

I'm trying to do an elegant round method that only use the JDK methods and leverage the TimeUnit class of that JDK.

/**
 * Get the date rounded to the given unit
 * @param date
 * @param unit
 * @return the rounded value
 */
public static Date round(Date date,TimeUnit unit) {
    long dateInMillies = date.getTime();
    long tzOffset = TimeZone.getDefault().getOffset(dateInMillies);
    long dateInMilliesWithoutOffset = dateInMillies + tzOffset;
    long dateInUnit = unit.convert(dateInMilliesWithoutOffset,TimeUnit.MILLISECONDS);
    long dateInMilliesRoundedWithoutOffset = unit.toMillis(dateInUnit);
    long dateInMilliesRoundedWithOffset = dateInMilliesRoundedWithoutOffset - tzOffset;
    return new Date(dateInMilliesRoundedWithOffset);
}

And my unit test is:

@Test
public void testRoundDate() {
    Date currentDayDate = new Date();
    Date currentDayBegin = DateUtils.getDayBegin(currentDayDate); // returns 00h00:00 and 000 milliseconds
    Date currentDayEnd = DateUtils.getDayEnd(currentDayDate,TimeUnit.MILLISECONDS); // returns 23h59:59 and 999 milliseconds
    //
    final Calendar initialCalBegin = Calendar.getInstance();
    initialCalBegin.setTime(currentDayBegin);
    final Calendar initialCalEnd = Calendar.getInstance();
    initialCalEnd.setTime(currentDayEnd);
    //
    Calendar testedCalendar = Calendar.getInstance();
    //
    testedCalendar.setTime( DateUtils.round(currentDayBegin, TimeUnit.DAYS) );
    Assert.assertEquals( testedCalendar.get(Calendar.DAY_OF_YEAR), initialCalBegin.get(Calendar.DAY_OF_YEAR) );
    Assert.assertEquals( testedCalendar.get(Calendar.HOUR_OF_DAY), 0 );
    Assert.assertEquals( testedCalendar.get(Calendar.MINUTE), 0 );
    Assert.assertEquals( testedCalendar.get(Calendar.SECOND), 0 );
    Assert.assertEquals( testedCalendar.get(Calendar.MILLISECOND), 0 );
    testedCalendar.setTime( DateUtils.round(currentDayEnd, TimeUnit.DAYS) );
    Assert.assertEquals( testedCalendar.get(Calendar.DAY_OF_YEAR), initialCalEnd.get(Calendar.DAY_OF_YEAR) );
    Assert.assertEquals( testedCalendar.get(Calendar.HOUR_OF_DAY), 0 );
    Assert.assertEquals( testedCalendar.get(Calendar.MINUTE), 0 );
    Assert.assertEquals( testedCalendar.get(Calendar.SECOND), 0 );
    Assert.assertEquals( testedCalendar.get(Calendar.MILLISECOND), 0 );
    //
    testedCalendar.setTime( DateUtils.round(currentDayBegin, TimeUnit.HOURS) );
    Assert.assertEquals( testedCalendar.get(Calendar.DAY_OF_YEAR), initialCalBegin.get(Calendar.DAY_OF_YEAR) );
    Assert.assertEquals( testedCalendar.get(Calendar.HOUR_OF_DAY), initialCalBegin.get(Calendar.HOUR_OF_DAY) );
    Assert.assertEquals( testedCalendar.get(Calendar.MINUTE), 0 );
    Assert.assertEquals( testedCalendar.get(Calendar.SECOND), 0 );
    Assert.assertEquals( testedCalendar.get(Calendar.MILLISECOND), 0 );
    testedCalendar.setTime( DateUtils.round(currentDayEnd, TimeUnit.HOURS) );
    Assert.assertEquals( testedCalendar.get(Calendar.DAY_OF_YEAR), initialCalEnd.get(Calendar.DAY_OF_YEAR) );
    Assert.assertEquals( testedCalendar.get(Calendar.HOUR_OF_DAY), initialCalEnd.get(Calendar.HOUR_OF_DAY) );
    Assert.assertEquals( testedCalendar.get(Calendar.MINUTE), 0 );
    Assert.assertEquals( testedCalendar.get(Calendar.SECOND), 0 );
    Assert.assertEquals( testedCalendar.get(Calendar.MILLISECOND), 0 );
    // 
    testedCalendar.setTime( DateUtils.round(currentDayBegin, TimeUnit.MINUTES) );
    Assert.assertEquals( testedCalendar.get(Calendar.DAY_OF_YEAR), initialCalBegin.get(Calendar.DAY_OF_YEAR) );
    Assert.assertEquals( testedCalendar.get(Calendar.HOUR_OF_DAY), initialCalBegin.get(Calendar.HOUR_OF_DAY) );
    Assert.assertEquals( testedCalendar.get(Calendar.MINUTE), initialCalBegin.get(Calendar.MINUTE) );
    Assert.assertEquals( testedCalendar.get(Calendar.SECOND), 0 );
    Assert.assertEquals( testedCalendar.get(Calendar.MILLISECOND), 0 );
    testedCalendar.setTime( DateUtils.round(currentDayEnd, TimeUnit.MINUTES) );
    Assert.assertEquals( testedCalendar.get(Calendar.DAY_OF_YEAR), initialCalEnd.get(Calendar.DAY_OF_YEAR) );
    Assert.assertEquals( testedCalendar.get(Calendar.HOUR_OF_DAY), initialCalEnd.get(Calendar.HOUR_OF_DAY) );
    Assert.assertEquals( testedCalendar.get(Calendar.MINUTE), initialCalEnd.get(Calendar.MINUTE) );
    Assert.assertEquals( testedCalendar.get(Calendar.SECOND), 0 );
    Assert.assertEquals( testedCalendar.get(Calendar.MILLISECOND), 0 );
    //
    testedCalendar.setTime( DateUtils.round(currentDayBegin, TimeUnit.SECONDS) );
    Assert.assertEquals( testedCalendar.get(Calendar.DAY_OF_YEAR), initialCalBegin.get(Calendar.DAY_OF_YEAR) );
    Assert.assertEquals( testedCalendar.get(Calendar.HOUR_OF_DAY), initialCalBegin.get(Calendar.HOUR_OF_DAY) );
    Assert.assertEquals( testedCalendar.get(Calendar.MINUTE), initialCalBegin.get(Calendar.MINUTE) );
    Assert.assertEquals( testedCalendar.get(Calendar.SECOND), initialCalBegin.get(Calendar.SECOND) );
    Assert.assertEquals( testedCalendar.get(Calendar.MILLISECOND), 0 );
    testedCalendar.setTime( DateUtils.round(currentDayEnd, TimeUnit.SECONDS) );
    Assert.assertEquals( testedCalendar.get(Calendar.DAY_OF_YEAR), initialCalEnd.get(Calendar.DAY_OF_YEAR) );
    Assert.assertEquals( testedCalendar.get(Calendar.HOUR_OF_DAY), initialCalEnd.get(Calendar.HOUR_OF_DAY) );
    Assert.assertEquals( testedCalendar.get(Calendar.MINUTE), initialCalEnd.get(Calendar.MINUTE) );
    Assert.assertEquals( testedCalendar.get(Calendar.SECOND), initialCalEnd.get(Calendar.SECOND) );
    Assert.assertEquals( testedCalendar.get(Calendar.MILLISECOND), 0 );
    //
    testedCalendar.setTime( DateUtils.round(currentDayBegin, TimeUnit.MILLISECONDS) );
    Assert.assertEquals( testedCalendar.get(Calendar.DAY_OF_YEAR), initialCalBegin.get(Calendar.DAY_OF_YEAR) );
    Assert.assertEquals( testedCalendar.get(Calendar.HOUR_OF_DAY), initialCalBegin.get(Calendar.HOUR_OF_DAY) );
    Assert.assertEquals( testedCalendar.get(Calendar.MINUTE), initialCalBegin.get(Calendar.MINUTE) );
    Assert.assertEquals( testedCalendar.get(Calendar.SECOND), initialCalBegin.get(Calendar.SECOND) );
    Assert.assertEquals( testedCalendar.get(Calendar.MILLISECOND), initialCalBegin.get(Calendar.MILLISECOND) );
    testedCalendar.setTime( DateUtils.round(currentDayEnd, TimeUnit.MILLISECONDS) );
    Assert.assertEquals( testedCalendar.get(Calendar.DAY_OF_YEAR), initialCalEnd.get(Calendar.DAY_OF_YEAR) );
    Assert.assertEquals( testedCalendar.get(Calendar.HOUR_OF_DAY), initialCalEnd.get(Calendar.HOUR_OF_DAY) );
    Assert.assertEquals( testedCalendar.get(Calendar.MINUTE), initialCalEnd.get(Calendar.MINUTE) );
    Assert.assertEquals( testedCalendar.get(Calendar.SECOND), initialCalEnd.get(Calendar.SECOND) );
    Assert.assertEquals( testedCalendar.get(Calendar.MILLISECOND), initialCalEnd.get(Calendar.MILLISECOND) );
    ////////////////////////////////////////////////////////////////////////////////////////////////////////////////
    // The TimeUnits under the millisecond precision should not procude any change to the date
    Assert.assertEquals(DateUtils.round(currentDayBegin, TimeUnit.MICROSECONDS) , currentDayBegin);
    Assert.assertEquals(DateUtils.round(currentDayEnd, TimeUnit.MICROSECONDS) , currentDayEnd);
    Assert.assertEquals(DateUtils.round(currentDayDate, TimeUnit.MICROSECONDS) , currentDayDate);
    //
    Assert.assertEquals(DateUtils.round(currentDayBegin, TimeUnit.NANOSECONDS) , currentDayBegin);
    Assert.assertEquals(DateUtils.round(currentDayEnd, TimeUnit.NANOSECONDS) , currentDayEnd);
    Assert.assertEquals(DateUtils.round(currentDayDate, TimeUnit.NANOSECONDS) , currentDayDate);
}

It seems what I've done works fine and is quite elegant. However, I'm not used to using time zones, and I wonder if this code works fine on very specific cases. For example, do you see something that could happen a given day, for a country that has an offset change in its timezone? Please tell me if you think something is wrong in my code.

Can I safely create a new method that takes a TimeZone parameter and can thus round a date to a given unit on a given timezone?

\$\endgroup\$
3
  • 1
    \$\begingroup\$ Any special reason this has to done with the JDK only? The Joda Time library has rounding features and it's considered to be very reliable. \$\endgroup\$
    – RoToRa
    Commented May 24, 2012 at 13:48
  • \$\begingroup\$ I know.i'm not looking for an alternative solution but comments on my code. We have an existing code using TimeUnit and TimeZone and it would be a little pain to map all these stuff to joda. \$\endgroup\$ Commented May 24, 2012 at 14:11
  • \$\begingroup\$ I thought so, just wanted to mention it. \$\endgroup\$
    – RoToRa
    Commented May 24, 2012 at 14:22

2 Answers 2

2
\$\begingroup\$

One problem you might run into is that some timezone offsets aren't integral hours. For instance, the India timezone offset is UTC +5:30. It appears that your code will probably handle this case correctly, but you probably want to add a couple unit tests to verify this.

You might also run into some problems with the Brazil time zone, since its transition between Daylight Savings Time and Standard Time happens at midnight (instead of at 2am like in most places). I've had problems trying to get the Date class to work correctly in this situation in the past. Again, I see no specific problems, but some unit tests to verify would probably be in order.

I would also suggest steering clear of the built-in Date/Calendar implementation, as there are some bugs when dealing with time zones. In my project I eventually had to use Joda under the covers and convert to/from Java Date at the API boundary. You might eventually decide that's the best solution for you, too.

\$\endgroup\$
1
\$\begingroup\$

Just a few random notes:

  1. Comments like this are unnecessary:

     * @param date
     * @param unit
    

    It says nothing more than the code already does, it's rather noise. (Clean Code by Robert C. Martin: Chapter 4: Comments, Noise Comments)

  2. I'd split the unit test to smaller test methods. Too many assert in one test is a bad smell. It's Assertion Roulette and you lost Defect Localization. If the first assertEquals throws an exception you don't know anything about the results of the other assert calls which could be important because they could help debugging and defect localization.

  3. I'd consider a static import and use simply assertEquals instead of Assert.assertEquals.

    import static org.junit.Assert.*;
    
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.