2
\$\begingroup\$

I calculate the lowest quarter-hour using the following code:

public Date getQuarter() {
    Calendar calendar = Calendar.getInstance();
    int mins = calendar.get(Calendar.MINUTE);

    if (mins < 15) {
        mins = 0;
    } else if (mins < 30) {
        mins = 15;
    } else if (mins < 45) {
        mins = 30;
    } else {
        mins = 45;
    }

    calendar.set(Calendar.MINUTE, mins);
    calendar.set(Calendar.SECOND, 0);
    calendar.set(Calendar.MILLISECOND, 0);

    return calendar.getTime();
}

How can this become better / more elegant?

\$\endgroup\$
3
\$\begingroup\$

Java has integer division, so you can divide the minutes by 15 and multiply them again:

int mins = calendar.get(Calendar.MINUTE);
calendar.set(Calendar.MINUTE, mins / 15 * 15);

The division will round the number of 15-minute intervals down to the nearest integer, and multiplying it back gets you the interval you want.


To do this down to the milliseconds, do the same trick but with setTime() directly:

calendar.setTimeInMillis(calendar.getTimeInMillis() / 900000 * 900000);
\$\endgroup\$
  • \$\begingroup\$ The second method disregards 1) any local time offsets that aren't multiples of 15 minutes and 2) leap seconds that have been injected since epoch. It also rounds to the next multiple of 15 minutes for dates before epoch. \$\endgroup\$ – David Foerster Feb 1 '17 at 22:09
  • \$\begingroup\$ One minor thing: you can speed up the expression mins / 15 * 15 a little if you use the integer division remainder to replace one multiplicative operation with an additive operation like so: mins - mins % 15 \$\endgroup\$ – David Foerster Feb 1 '17 at 22:11
  • \$\begingroup\$ @DavidFoerster I'm not aware of any time zones that aren't in multiples of 15 minutes, although you're right that if you wanted to round down to 10 minutes, for example, the millisecond solution might fail in some timezones. Also, Unix time ignores leap seconds. \$\endgroup\$ – asdf Feb 2 '17 at 16:53
  • \$\begingroup\$ Just because there are no such time zones at the moment doesn't mean there have never been or will never be such time zones. Local time changes hundreds of times per year all across the world (though most are switches between existing time zones). Unix time being unaware of leap seconds is exactly my point: if you just round down to the closest lower multiple of 15 min. in Unix time, the resulting local time will be off by the amount of leap seconds between the two. I'm assuming that the multiples are supposed to be in local time, not in seconds since epoch. \$\endgroup\$ – David Foerster Feb 2 '17 at 18:18
  • \$\begingroup\$ By "ignores leap seconds", I mean that Unix time pretends they don't exist, and just drops a second every time a leap second is introduced. So Unix time is (as of right now) actually 37 seconds less than the actual number of seconds that have passed since the epoch. \$\endgroup\$ – asdf Feb 7 '17 at 17:58
1
\$\begingroup\$

Your approach is really clean. Enhancements to this would only be polishing like extract named constants.

Depending on if you have no use for the hourly quarter constants (QUARTER_0, QUARTER_15, ...) you can shorten your implementation.

Either using legacy date...

public static Date getQuarter() {

    Calendar calendar = Calendar.getInstance();

    int currentMinutes = calendar.get(Calendar.MINUTE);
    int correctionToLowestQuarterHour = currentMinutes % QUARTER_OF_HOUR_IN_MINUTES;

    calendar.set(Calendar.MINUTE, currentMinutes - correctionToLowestQuarterHour);
    calendar.set(Calendar.SECOND, 0);
    calendar.set(Calendar.MILLISECOND, 0);

    return calendar.getTime();
}

... or new java.time API ...

public static Date getQuarter() {

    LocalDateTime now = LocalDateTime.now();

    int currentMinutes = now.get(ChronoField.MINUTE_OF_HOUR);
    int correctionToLowestQuarterHour = currentMinutes % QUARTER_OF_HOUR_IN_MINUTES;

    LocalDateTime quantizedDate = now.withMinute(currentMinutes - correctionToLowestQuarterHour).withSecond(0).withNano(0);

    ZonedDateTime zdt = quantizedDate.atZone(ZoneId.systemDefault());

    return Date.from(zdt.toInstant());

}
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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