69

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?

Arvind Kumar Avinash
  • 71,965
  • 6
  • 74
  • 110
lock
  • 6,404
  • 18
  • 58
  • 76

17 Answers17

102

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);
B T
  • 57,525
  • 34
  • 189
  • 207
Sean Patrick Floyd
  • 292,901
  • 67
  • 465
  • 588
48

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

D-rk
  • 5,513
  • 1
  • 37
  • 55
  • 4
    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()`. – Basil Bourque Mar 18 '18 at 20:59
  • 5
    By the way, if you want half up, it could be (15 * (time.getMinute() + 7.5) / 15). – Shengfeng Li Feb 04 '19 at 21:54
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

Dev
  • 11,919
  • 3
  • 40
  • 53
  • 1
    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. _half_ – hc_dev Apr 08 '20 at 23:18
  • 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 : ceiling` – kistlers May 17 '22 at 09:09
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)

Peter Tillemans
  • 34,983
  • 11
  • 83
  • 114
  • Brute force approach, misses things like leap seconds. Although I agree there have to be many leap seconds to make a difference. – Sean Patrick Floyd Aug 24 '10 at 06:39
  • 4
    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. – Peter Tillemans Aug 24 '10 at 06:50
  • 3
    "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. – B T Aug 31 '12 at 01:53
  • @BT According to [this Wikipedia article](http://en.wikipedia.org/wiki/Time_zone), all time zone offsets are 60, 45, or 30 minutes. – Basil Bourque May 30 '14 at 16:36
  • @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: http://geography.about.com/od/culturalgeography/a/offsettimezones.htm – B T May 31 '14 at 22:50
  • 1
    @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. – Basil Bourque May 31 '14 at 23:54
  • 1
    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 comment – B T May 31 '14 at 23:57
  • I updated my answer to use the 15min timezone differences, thanks for the observation. – Peter Tillemans Jun 14 '14 at 17:12
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"

I'm_Pratik
  • 541
  • 8
  • 22
Lindhardt
  • 101
  • 1
  • 2
  • 2
    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);` – jgrocha Apr 25 '17 at 14:19
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()); 
lboix
  • 969
  • 12
  • 14
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);
Sean Bright
  • 118,630
  • 17
  • 138
  • 146
Kandha
  • 3,659
  • 12
  • 35
  • 50
  • 1
    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! – 5im Jun 16 '14 at 23:21
  • Concise and elegant snippet. Works as __refinement to previous solutions__, while missing context (where's `min`, `cal` defined) and explanation. – hc_dev Apr 08 '20 at 23:40
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);
Rakesh SKadam
  • 378
  • 1
  • 2
  • 18
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();
}
Sergey Shcherbakov
  • 4,534
  • 4
  • 40
  • 65
2

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.

Arvind Kumar Avinash
  • 71,965
  • 6
  • 74
  • 110
  • 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. – dualed Mar 08 '22 at 14:48
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));
Torres
  • 5,330
  • 4
  • 26
  • 26
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();
}
contrapost
  • 673
  • 12
  • 22
0
minutes = (int) (Math.round(minutes / 15.0) * 15.0);
Christopher Masser
  • 809
  • 13
  • 25
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;

christian
  • 391
  • 2
  • 10
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

Xander
  • 1
  • 1
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;
}
Yash
  • 9,250
  • 2
  • 69
  • 74
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);

    }
rortegat
  • 61
  • 7