Как округлить до ближайшего 5-минутного интервала, если он на 1 или 2 минуты впереди или позади?

#java #java-8 #localdatetime

#java #java-8 #localdatetime

Вопрос:

Я хочу округлить Instant / LocalDateTime до ближайшего 5-минутного интервала в Java.

Предположим, что время:
Примеры: 2021-02-08T19:02:49.594 Ожидаемый результат: 2021-02-08T19:00:00.000

Предположим, что время: 2021-02-08T19:03:49.594 Ожидаемый результат: 2021-02-08T19:05:00.000

Аналогично, если время: 2021-02-08T19:56:49.594 Ожидаемый результат: 2021-02-08T19:55:00.000

Аналогично, если время: 2021-02-08T19:58:49.594 Ожидаемый результат: 2021-02-08T20:00:00.000

Но если время 2021-02-08T19:55:00.000 или 2021-02-08T19:05:00.000 или 2021-02-08T19:00:00.000, тогда ничего не делайте.

Комментарии:

1. Используйте внешние часы в качестве эталона.

2. 2021-02-08T19:05:00.000 это ближайшая 5-минутная остановка 2021-02-08T19:02:49.594 (потому что секунды прошли 30). Какова ваша логика для желания 19:00?

Ответ №1:

Вот решение общего назначения (не только 5 минут):

 public static Instant toNearest(Duration interval, Instant instant) {
    long intervalMillis = interval.toMillis();
    long adjustedInstantMillis = (instant.toEpochMilli()   (intervalMillis / 2)) / intervalMillis * intervalMillis;
    return Instant.ofEpochMilli(adjustedInstantMillis);
}

public static LocalDateTime toNearest(Duration interval, LocalDateTime dateTime, ZoneId zoneId) {
    ZoneRules zoneRules = zoneId.getRules();
    Instant instant = toNearest(interval, dateTime.toInstant(zoneRules.getOffset(dateTime)));
    return LocalDateTime.ofInstant(instant, zoneRules.getOffset(instant));
}

public static LocalDateTime toNearest(Duration interval, LocalDateTime dateTime) {
    return toNearest(interval, dateTime, ZoneId.systemDefault());
}

@Test
public void toNearestRoundsCorrectly() {
    assertThat(toNearest(Duration.ofMinutes(5), LocalDateTime.of(2021, 2, 8, 19, 0, 0)))
            .isEqualTo(LocalDateTime.of(2021, 2, 8, 19, 0, 0));
    assertThat(toNearest(Duration.ofMinutes(5), LocalDateTime.of(2021, 2, 8, 19, 2, 29, 999999999)))
            .isEqualTo(LocalDateTime.of(2021, 2, 8, 19, 0, 0));
    assertThat(toNearest(Duration.ofMinutes(5), LocalDateTime.of(2021, 2, 8, 19, 2, 30)))
            .isEqualTo(LocalDateTime.of(2021, 2, 8, 19, 5, 0));
    assertThat(toNearest(Duration.ofMinutes(5), LocalDateTime.of(2021, 2, 8, 19, 5, 0)))
            .isEqualTo(LocalDateTime.of(2021, 2, 8, 19, 5, 0));
}

@Test
public void toNearestTreatsDaylightSavingChangesCorrectly() {
    assertThat(toNearest(Duration.ofMinutes(5), LocalDateTime.of(2021,3,27, 23,57,30), ZoneId.of("Europe/London")))
            .isEqualTo(LocalDateTime.of(2021,3,28, 0,0,0));
    assertThat(toNearest(Duration.ofMinutes(5), LocalDateTime.of(2021,3,28, 0,57,29,999999999), ZoneId.of("Europe/London")))
            .isEqualTo(LocalDateTime.of(2021,3,28, 0,55,0));
    assertThat(toNearest(Duration.ofMinutes(5), LocalDateTime.of(2021,3,28, 0,57,30), ZoneId.of("Europe/London")))
            .isEqualTo(LocalDateTime.of(2021,3,28, 2,0,0));
    assertThat(toNearest(Duration.ofMinutes(5), LocalDateTime.of(2021,3,28, 2,5,0), ZoneId.of("Europe/London")))
            .isEqualTo(LocalDateTime.of(2021,3,28, 2,5,0));
}
 

Комментарии:

1. прежде всего, спасибо за ваши усилия. получил желаемый результат. Есть ли какой-нибудь способ округлить время только на 5 минут раньше? например: 2021-02-08T19:26:49.594, 2021-02-08T19:27:49.594. 2021-02-08T19:28:49.594, 2021-02-08T19:29:49.594 Ожидаемый результат: 2021-02-08T19:25:00.000

2. Да, просто замените 3-ю строку выше на long adjustedInstantMillis = instant.toEpochMilli() / intervalMillis * intervalMillis; (т. Е. Удалите добавление полуинтервала)

3. Спасибо, не могли бы вы объяснить эту логическую линию простым однострочником..

4. Он основан на целочисленном делении. При использовании интегрального типа данных, такого как int или long , выражение a / b * b эквивалентно truncate(a / b) * b (при условии, что существует метод truncate, который удаляет дробную часть). Это a/b*b приводит к значению, равному a с ближайшим кратным b, которое равно или меньше a. (Это становится немного сложнее с отрицательными числами, поэтому даты ранее 1970 года, которые дают отрицательное значение epochMilli, могут быть неверными.)

Ответ №2:

это должно округлить его соответствующим образом:

         long min5 = 1000 * 60 * 5;
        String time = "2021-02-08T19:56:49.594";
        time = time.replace("T", " "); // cannot deal with T
        SimpleDateFormat format = new SimpleDateFormat("yyyy-dd-MM HH:mm:ss.SSS");
        try{
            Date dt = format.parse(time);
            long t = dt.getTime();
            long d = t %min5;
            if(d > min5/2) { // round up
                t  = min5 - d;
            } else { // round down
                t -= d;
            }
            System.out.println(format.format(new Date(t)));
        }catch(ParseException e){
            e.printStackTrace();
        }
 

Ответ №3:

В этом коде приведены примеры как для Instant, так и для LocalDateTime.

         Instant instant = Instant.now();
        long instantLong = instant.toEpochMilli();
        long roundedEpochMilli = (instantLong/(60*5*1000) )  * 60 * 5 *1000; 
        if((instantLong % (60*5*1000))> (30*5*1000))
             roundedEpochMill  = 60*5*1000;
        Instant instantRounded = Instant.ofEpochMilli(roundedEpochMilli);
        System.out.println("instant "  instant  " -- "  instantRounded);
        
        LocalDateTime now = LocalDateTime.now();
        ZoneId systemZone = ZoneId.systemDefault(); // my timezone
        ZoneOffset currentOffsetForMyZone = systemZone.getRules().getOffset(instant);
        long nowLong = now.toEpochSecond(currentOffsetForMyZone);
        long roundedEpochSecond = (nowLong/(60*5) )  * 60 * 5; 
        if((nowLong % (60*5))> (30*5))
             roundedEpochSecond  = 60*5;
        LocalDateTime nowRounded = LocalDateTime.ofEpochSecond(roundedEpochSecond, 0, currentOffsetForMyZone);
        System.out.println("now "  instant  " -- "  nowRounded);