How to round time to the nearest quarter hour in java?
Asked Answered
B

17

71

Given today's time e.g. 2:24PM, how do I get it to round to 2:30PM?

Similarly if the time was 2:17PM, how do I get it to round to 2:15PM?

Braque answered 24/8, 2010 at 6:24 Comment(0)
J
103

Rounding

You will need to use modulo to truncate the quarter hour:

Date whateverDateYouWant = new Date();
Calendar calendar = Calendar.getInstance();
calendar.setTime(whateverDateYouWant);

int unroundedMinutes = calendar.get(Calendar.MINUTE);
int mod = unroundedMinutes % 15;
calendar.add(Calendar.MINUTE, mod < 8 ? -mod : (15-mod));

As pointed out by EJP, this is also OK (replacement for the last line, only valid if the calendar is lenient):

calendar.set(Calendar.MINUTE, unroundedMinutes + mod);

Improvements

If you want to be exact, you will also have to truncate the smaller fields:

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

You can also use DateUtils.truncate() from Apache Commons / Lang to do this:

calendar = DateUtils.truncate(calendar, Calendar.MINUTE);
Jeramey answered 24/8, 2010 at 6:30 Comment(10)
uh i just construct it from the calendar classBraque
This method will floor the minutes to low closest quarter and you need to add some check if it was rounded to the next hour so that you need to update the hour too...Entreaty
The current version fixes these issuesJeramey
(unroundedMinutes+8) % 15 is simpler, and just set, don't add.Lovash
@EJP You are right, but I think on the semantic level setting the time to 10:60 is an awful thing to do, even if I know the calendar class is smart enough to turn it into 11:00Jeramey
So do I. I said nothing to the contrary.Lovash
@Braque it took me all day to realize that your comment was pointed in my direction, sorry. Updated my answer.Jeramey
@seanizer: sorry for not directing it to you in the first place hehe thanks for updating :DBraque
So this answer isn't full. A. where does the calendar instance come from? B. How do you get a specific date in there?Macneil
@BT the Calendar instance comes from the first listing and the specific Date is not in the scope of the question.Jeramey
S
49

If you just want to round down this is a more readable version using Java Time API:

LocalDateTime time = LocalDateTime.now();
LocalDateTime lastQuarter = time.truncatedTo(ChronoUnit.HOURS)
                                .plusMinutes(15 * (time.getMinute() / 15));

output:

2016-11-04T10:58:10.228

2016-11-04T10:45:00

Seigel answered 4/11, 2016 at 11:3 Comment(3)
By the way, LocalDateTime is the wrong class for actual moments, for a point on the timeline. Lacking any zone/offset concept, they are indefinite. Use same logic, but with ZonedDateTime.now().Halfslip
By the way, if you want half up, it could be (15 * (time.getMinute() + 7.5) / 15).Lownecked
Not an answer to a question. 2.24 will go to 2.15, but the Op wanted 2.24 goes to 2.30Theisen
R
13

A commented implementation for Java 8. Accepts arbitrary rounding units and increments:

 public static ZonedDateTime round(ZonedDateTime input, TemporalField roundTo, int roundIncrement) {
    /* Extract the field being rounded. */
    int field = input.get(roundTo);

    /* Distance from previous floor. */
    int r = field % roundIncrement;

    /* Find floor and ceiling. Truncate values to base unit of field. */
    ZonedDateTime ceiling = 
        input.plus(roundIncrement - r, roundTo.getBaseUnit())
        .truncatedTo(roundTo.getBaseUnit());

    ZonedDateTime floor = 
        input.plus(-r, roundTo.getBaseUnit())
        .truncatedTo(roundTo.getBaseUnit());

    /*
     * Do a half-up rounding.
     * 
     * If (input - floor) < (ceiling - input) 
     * (i.e. floor is closer to input than ceiling)
     *  then return floor, otherwise return ceiling.
     */
    return Duration.between(floor, input).compareTo(Duration.between(input, ceiling)) < 0 ? floor : ceiling;
  }

Source: myself

Rive answered 24/5, 2016 at 20:48 Comment(2)
Semantically well written: variables & calculations are clear 👍 Alternative for half-up: return r < roundIncrement/2 ? floor : ceiling where roundIncrement/2 is the threshold, i.e. halfDufrene
I used your answer and adapted it into separate methods to ceil or truncate the input and found a small bug. For anyone with the same goal as me: ceiling in the given answer is always ceiled, even if not necessary. I adapted it the following way: ceiling = input.equals(floor) ? input : ceilingDric
T
10

With the answer above you end up with all kind of interesting code to handle overflows to hours, days etc.

I would use the time in ms since the epoch.

add 7.5minutes or 7.5x60x1000 = 450000

and truncate to a multiple of 900000

new Date(900000 * ((date.getTime() + 450000) / 900000))

This works, because the time where the ms time starts happens to be 00:00:00. And since all time zones in the world change in 15min steps, this does not affect rounding to quarters.

(Oops, I had a 0 too much and forgot some important parentheses : it is still too early)

Tomlinson answered 24/8, 2010 at 6:36 Comment(8)
Brute force approach, misses things like leap seconds. Although I agree there have to be many leap seconds to make a difference.Jeramey
You are absolutely right, I did not consider leap seconds. However last time I checked, there was no support for leap seconds in java.util.Date, so people would set their computer clock to match wall time (or an atomic clock) and the JVM would calculated the wrong start of the epoch (off by the number of leap seconds). So in practice this would work and would save a bunch of tricky code, which also must deal with leap seconds. Conclusion : use ntp to keep your computer clock synchronised.Tomlinson
"all time zones in the world change in 30min steps" - this isn't true for all time zones. There are time-zones that use incriments of 15 minutes.Macneil
@BT According to this Wikipedia article, all time zone offsets are 60, 45, or 30 minutes.Halfslip
@BasilBourque I admit I'm not sure of the details, but I might have meant that some time zones end up being an increment of 15 minutes away from GMT (which a 45 minute step would cause). Maybe that's also what this source means: geography.about.com/od/culturalgeography/a/offsettimezones.htmMacneil
@BT I did not mean to sound argumentative. I'm just educating myself about this perplexing topic of date-time. Your link leads to an interesting article, thanks.Halfslip
I didn't take it that way, and I didn't mean to sound that way either. I've legitimately forgotten the information that caused me to write my original commentMacneil
I updated my answer to use the 15min timezone differences, thanks for the observation.Tomlinson
P
10

It's simple, find the number of quaters since 1970 as double, round it and multiply by 15 minutes:

long timeMs = System.System.currentTimeMillis();

long roundedtimeMs = Math.round( (double)( (double)timeMs/(double)(15*60*1000) ) ) * (15*60*1000);

Set your Date or Calendar object with that. Change the time you want mutiple by "n"

Piccalilli answered 4/1, 2012 at 8:22 Comment(1)
The parentheses do not match. I think the code should be: long timeMs = System.currentTimeMillis(); long roundedtimeMs = Math.round( (double)timeMs/(15*60*1000) ) * (15*60*1000);Oleta
T
6

Wonderful post, thank you so much guys! It was exactly what I needed :)

Here's my code based on jour work.

My usecase is "Given it's 11:47 am, I want to set two dates symbolizing the current 5-minutes frame : 11:45 am and 11:50 am"

            Calendar calendar = Calendar.getInstance();
            calendar.set(Calendar.SECOND, 0);
            calendar.set(Calendar.MILLISECOND, 0);

            int modulo = calendar.get(Calendar.MINUTE) % 5;
            if(modulo > 0) {

                calendar.add(Calendar.MINUTE, -modulo);
            }

            myObject.setStartDate(calendar.getTime());

            calendar.add(Calendar.MINUTE, 5);
            myObject.setDueDate(calendar.getTime()); 
Tonjatonjes answered 30/5, 2014 at 15:48 Comment(1)
helped me to get a neat code @Iboix. small tweaks and i got my required datetime obj. thanksAnethole
U
4

You can use this simple code...

int mode = min % 15;
if (mode > 15 / 2) {
    min = 15 - mode;
} else {
    min = 0 - mode;
}
cal.add(Calendar.MINUTE, min);
Underfeed answered 24/8, 2010 at 6:54 Comment(2)
thanks for the hint where the 8 comes from (15/2). helped me to implement a version for other roundings like 5 minutes and so on!Thanhthank
Concise and elegant snippet. Works as refinement to previous solutions, while missing context (where's min, cal defined) and explanation.Dufrene
H
4

One more alternate approach using java Instant api.

Instant instant =  Instant.now();
int intervalInMinutes = 10;
instant.truncatedTo(ChronoUnit.MINUTES).minus(instant.atZone(ZoneId.of("UTC")).getMinute() % (1* intervalInMinutes),ChronoUnit.MINUTES);
Hessian answered 7/10, 2019 at 8:58 Comment(0)
M
4

If you need to round down time to the nearest arbitrary level provided as Duration:

static long truncateTo(long timeEpochMillis, Duration d) {
    long x = timeEpochMillis / d.toMillis();
    return x * d.toMillis();
}
Muscle answered 2/4, 2021 at 20:44 Comment(0)
B
3

java.time

I recommend you do it using the the modern date-time API*:

import java.time.LocalDate;
import java.time.LocalTime;
import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.time.temporal.ChronoUnit;
import java.util.stream.Stream;

public class Main {
    public static void main(String[] args) {
        
        // Change it to the applicable ZoneId e.g. ZoneId.of("Asia/Kolkata")
        ZoneId zoneId = ZoneId.systemDefault();
        
        Stream.of(
                        "10:00", 
                        "10:05", 
                        "10:10", 
                        "10:15", 
                        "10:20", 
                        "10:25", 
                        "10:30"
            ).forEach(t -> System.out.println(roundToNearestQuarter(t, zoneId)));
    }

    static ZonedDateTime roundToNearestQuarter(String strTime, ZoneId zoneId) {
        LocalTime time = LocalTime.parse(strTime);
        return LocalDate.now()
                        .atTime(time)
                        .atZone(zoneId)
                        .truncatedTo(ChronoUnit.HOURS)
                        .plusMinutes(15 * Math.round(time.getMinute() / 15.0));
    }
}

Output:

2021-04-02T10:00+01:00[Europe/London]
2021-04-02T10:00+01:00[Europe/London]
2021-04-02T10:15+01:00[Europe/London]
2021-04-02T10:15+01:00[Europe/London]
2021-04-02T10:15+01:00[Europe/London]
2021-04-02T10:30+01:00[Europe/London]
2021-04-02T10:30+01:00[Europe/London]

In case you are looking for just time, use ZonedDateTime#toLocalTime to get the LocalTime from the obtained ZonedDateTime.

Learn more about the modern date-time API from Trail: Date Time.


* The java.util date-time API and their formatting API, SimpleDateFormat are outdated and error-prone. It is recommended to stop using them completely and switch to the modern date-time API. For any reason, if you have to stick to Java 6 or Java 7, you can use ThreeTen-Backport which backports most of the java.time functionality to Java 6 & 7. If you are working for an Android project and your Android API level is still not compliant with Java-8, check Java 8+ APIs available through desugaring and How to use ThreeTenABP in Android Project.

Bayle answered 2/4, 2021 at 22:17 Comment(1)
Btw. this ignores seconds, so since the median between 15'' is 7'' 30''', rounding will be incorrect between 7'' 0''' and 7'' 29''' of each quarter hour.Interknit
F
1

Maybe you can use an utility library for manipulating Dates, here for example you have a round method which can be useful for you:

http://commons.apache.org/lang/api-2.4/org/apache/commons/lang/time/DateUtils.html#round%28java.util.Calendar,%20int%29

Here an example in code:

    FastDateFormat formatter = DateFormatUtils.ISO_DATETIME_TIME_ZONE_FORMAT;

    Date now = new Date();
    System.out.println("now = " + formatter.format(now));       

    // Get nearest second
    Date nearestSecond = DateUtils.round(now, Calendar.SECOND);
    System.out.println("nearestSecond = " + formatter.format(nearestSecond));

    // Get nearest minute
    Date nearestMinute = DateUtils.round(now, Calendar.MINUTE);
    System.out.println("nearestMinute = " + formatter.format(nearestMinute));

    // Get nearest hour
    Date nearestHour   = DateUtils.round(now, Calendar.HOUR);
    System.out.println("nearestHour = " + formatter.format(nearestHour));
Fleetwood answered 24/8, 2010 at 7:3 Comment(1)
I know about these, but I don't think there is an out of the box solution here for 15 minute intervalsJeramey
T
1
public static Date getCurrentDateTimeWithQuarterRounding() {
    final Calendar calendar = new GregorianCalendar();
    calendar.setTime(new Date());
    calendar.set(Calendar.MILLISECOND, 0);
    calendar.set(Calendar.SECOND, 0);
    final int minutes = calendar.get(Calendar.MINUTE);

    if (minutes < 15) {
        calendar.set(Calendar.MINUTE, 0);
    } else if (minutes >= 45) {
        calendar.set(Calendar.MINUTE, 45);
    } else if (minutes < 30) {
        calendar.set(Calendar.MINUTE, 15);
    } else {
        calendar.set(Calendar.MINUTE, 30);
    }

    return calendar.getTime();
}
Tresa answered 20/9, 2017 at 7:2 Comment(0)
E
0

if you have the minutes you can round them with the following function:
int minutes = i % 15 < 8 ? i / 15 * 15 : (i / 15 + 1) * 15;

Erectile answered 24/8, 2010 at 6:46 Comment(1)
why so complicated? and why divide through float values when what you want is an integer result?Jeramey
E
0
minutes = (int) (Math.round(minutes / 15.0) * 15.0);
Eulogist answered 5/8, 2013 at 5:52 Comment(0)
S
0

Using some code on I found on Stackoverflow, I have created the following code. It will output for every minute the quarter it will be rounded to.

import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;

DateTimeFormatter Datum_Format = DateTimeFormatter.ofPattern("HH:mm");

LocalDateTime time = LocalDateTime.now();
for(int i=0; i<=59; i++) {
  time = time.withMinute(i);
  int Minute = time.getMinute();
  int Quarter = 15 * (int) Math.round(Minute / 15);
  if (Quarter == 60) { 
    Time2 = time.plusHours(1);
    Time2 = Time2.withMinute(0); 
    LOG.info (Datum_Format.format(time) + "," + Datum_Format.format(Time2));
  }
  else {
    Time2 = time; 
    Time2 = Time2.withMinute(Quarter); 
    LOG.info (Datum_Format.format(time) + "," + Datum_Format.format(Time2));
  }
}

As I output the code to a console, you will have to replace the LOG.info with something like System.out.println.

Result:

2016-08-16 15:14:31 INFO 15:05,15:00
2016-08-16 15:14:31 INFO 15:06,15:00
2016-08-16 15:14:31 INFO 15:07,15:00
2016-08-16 15:14:31 INFO 15:08,15:15
2016-08-16 15:14:31 INFO 15:09,15:15
2016-08-16 15:14:31 INFO 15:10,15:15

Stoddart answered 16/8, 2016 at 13:26 Comment(1)
please describe why this answer is better than other answers or what it does different and whyCachou
D
0

Use the following functions to get the minutes rounded to last quarter getRecentQuater():Date, getSysDate_LastQuarterMins("dd.MM.yyyy HH:mm:ss"):String: Converting LocalDateTime to Date

public static Date getRecentQuater() {
    LocalDateTime time = LocalDateTime.now();
    LocalDateTime lastQuarter = time.truncatedTo(ChronoUnit.HOURS).plusMinutes(getLastQuarterValue(time.getMinute()));
    System.out.println("lastQuarter LocalDateTime: " + lastQuarter);

    Date date = Date.from(lastQuarter.atZone(ZoneId.systemDefault()).toInstant());
    System.out.println("lastQuarter Date: " + lastQuarter);
    return date;
}
public static String getSysDate_LastQuarterMins(String dateFormat) {
    Date date = getRecentQuater();
    SimpleDateFormat ft = new SimpleDateFormat (dateFormat);
    String sysDate_RoundMin = ft.format(date);
    System.out.println("getSysDate_LastQuarterMins() LocalDateTime : "+sysDate_RoundMin);
    return sysDate_RoundMin;
}

getSysDate_LastQuarterMins() : Mon Jan 20 17:30:00 CET 2020

public static Date getSysDate_LastQuarterMins() {
    Calendar cal = Calendar.getInstance();
    cal.setTime( new Date(System.currentTimeMillis()) );

    int min = cal.get(Calendar.MINUTE);

    cal.set(Calendar.MINUTE, getLastQuarterValue(min));
    cal.set(Calendar.SECOND, 00);
    Date lastQuarter = cal.getTime();
    System.out.println("getSysDate_LastQuarterMins() Calendar : "+lastQuarter);
    return lastQuarter;
}

You can find the LastQuarter Value Round value from the follwing fucntions, provided with some outputs on function call diaplayLastQuarter_RoundValue(min):

Min: 10, LastQuarter:  0, Round: 15
Min: 24, LastQuarter: 15, Round: 30
Min: 36, LastQuarter: 30, Round: 30
Min: 37, LastQuarter: 30, Round: 30
Min: 38, LastQuarter: 30, Round: 45
Min: 39, LastQuarter: 30, Round: 45
Min: 44, LastQuarter: 30, Round: 45
Min: 57, LastQuarter: 45, Round: 00 [57, 07:45:00, 08:00:00]
public static void diaplayLastQuarter_RoundValue(int minutes) {
    System.out.format("Min: %2d, LastQuarter: %2d, Round: %2d\n",
            minutes, getLastQuarterValue(minutes), getRoundValue(minutes));
}
public static int getLastQuarterValue(int minutes) {
    int min = 15 * (minutes / 15);
    //System.out.println("Min: "+minutes+", getLastQuarterValue : "+ min);
    return min;
}
public static int getRoundValue(int minutes) {
    getLastQuarterValue(minutes);
    int minRound = (int) (Math.round(minutes / 15.0) * 15.0);
    //System.out.println("Min: "+minutes+", getRoundValue : "+minRound);
    return minRound;
}
Diglot answered 20/1, 2020 at 16:24 Comment(0)
C
0

If someone is interested to get the nearest (up or down) five or fifteen interval, I made a function using module that does the job.

public LocalTime roundToTheNearestInterval(LocalTime original, Integer measurementInterval) {
        LocalTime nearest;
        int mod;
        switch (measurementInterval) {
            case 5:
                mod = original.getMinute() % 5;
                nearest = mod >= 3 ?
                        original.truncatedTo(ChronoUnit.HOURS)
                                .plusMinutes((long) 5 * (original.getMinute() / 5) + 5) :
                        original.truncatedTo(ChronoUnit.HOURS)
                                .plusMinutes((long) 5 * (original.getMinute() / 5));
                break;
            case 15:
                mod = original.getMinute() % 15;
                nearest = mod >= 8 ?
                        original.truncatedTo(ChronoUnit.HOURS)
                                .plusMinutes((long) 15 * (original.getMinute() / 15) + 15) :
                        original.truncatedTo(ChronoUnit.HOURS)
                                .plusMinutes((long) 15 * (original.getMinute() / 15));
                break;
            default:
                nearest = original;
        }
        return nearest;
    }

You can try it with this unit test

 @Test
    void roundToTheNearestInterval() {
        //given
        LocalTime originalTime1 = LocalTime.of(6, 31, 15);
        LocalTime originalTime2 = LocalTime.of(19, 13, 42);
        LocalTime originalTime3 = LocalTime.of(6, 37, 11);
        LocalTime originalTime4 = LocalTime.of(19, 40, 34);
        Integer measurementInterval_5min = 5;
        Integer measurementInterval_15min = 15;

        MyService myService = new MyService();

        //when
        LocalTime rounded1_5min = myService.roundToTheNearestInterval(originalTime1, measurementInterval_5min);
        LocalTime rounded2_5min = myService.roundToTheNearestInterval(originalTime2, measurementInterval_5min);

        LocalTime rounded1_15min = myService.roundToTheNearestInterval(originalTime3, measurementInterval_15min);
        LocalTime rounded2_15min = myService.roundToTheNearestInterval(originalTime4, measurementInterval_15min);

        //then
        assertEquals(LocalTime.of(6, 30, 0), rounded1_5min);
        assertEquals(LocalTime.of(19, 15, 0), rounded2_5min);
        assertEquals(LocalTime.of(6, 30, 0), rounded1_15min);
        assertEquals(LocalTime.of(19, 45, 0), rounded2_15min);

    }
Cassandry answered 16/7, 2021 at 16:58 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.