Как узнать время начала и окончания дня?

121

Как узнать время начала и окончания дня?

такой код не точен:

 private Date getStartOfDay(Date date) {
    Calendar calendar = Calendar.getInstance();
    int year = calendar.get(Calendar.YEAR);
    int month = calendar.get(Calendar.MONTH);
    int day = calendar.get(Calendar.DATE);
    calendar.set(year, month, day, 0, 0, 0);
    return calendar.getTime();
}

private Date getEndOfDay(Date date) {
    Calendar calendar = Calendar.getInstance();
    int year = calendar.get(Calendar.YEAR);
    int month = calendar.get(Calendar.MONTH);
    int day = calendar.get(Calendar.DATE);
    calendar.set(year, month, day, 23, 59, 59);
    return calendar.getTime();
}

Это не точность до миллисекунды.

kalman03
источник
8
Что значит "неточно"?
Кирк Уолл
Что не так с кодом? Что вы получаете такого, чего не ожидаете? В каком часовом поясе вы хотите, чтобы ваши дни начинались и заканчивались?
Марк Рид
4
В приведенном выше коде даже не используется дата, переданная в метод. Он должен делать: calendar.setTime (date) после создания календаря.
Джерри Дестремпс
2
В этом вопросе предполагается, что значения даты и времени имеют разрешение в миллисекунды. Верно для java.util.Date и Joda-Time. Но False для пакета java.time в Java 8 (наносекунды), некоторых баз данных, таких как Postgres (микросекунды), библиотек unix (целые секунды) и, возможно, других. См. Мой ответ о более безопасном подходе с использованием Half-Open.
Basil Bourque
2
На самом деле вы должны получить начало следующего дня, и при итерации элементов в этот день (при условии, что вы хотите это сделать) вы укажете верхнюю границу, используя <вместо <=. Это исключает следующий день, но включает весь предыдущий день с точностью до наносекунды.
Daniel F

Ответы:

115

ТЛ; др

LocalDate                       // Represents an entire day, without time-of-day and without time zone.
.now(                           // Capture the current date.
    ZoneId.of( "Asia/Tokyo" )   // Returns a `ZoneId` object.
)                               // Returns a `LocalDate` object.
.atStartOfDay(                  // Determines the first moment of the day as seen on that date in that time zone. Not all days start at 00:00!
    ZoneId.of( "Asia/Tokyo" ) 
)                               // Returns a `ZonedDateTime` object.

Начало дня

Получите полную длину сегодняшнего дня в часовом поясе.

Использование полуоткрытого подхода, когда начало включено, а окончание - эксклюзивно . Такой подход устраняет ошибку в вашем коде, которая не учитывает самую последнюю секунду дня.

ZoneId zoneId = ZoneId.of( "Africa/Tunis" ) ;
LocalDate today = LocalDate.now( zoneId  ) ;

ZonedDateTime zdtStart = today.atStartOfDay( zoneId ) ;
ZonedDateTime zdtStop = today.plusDays( 1 ).atStartOfDay( zoneId ) ;

zdtStart.toString () = 2020-01-30T00: 00 + 01: 00 [Африка / Тунис]

zdtStop.toString () = 2020-01-31T00: 00 + 01: 00 [Африка / Тунис]

Смотрите те же моменты в UTC.

Instant start = zdtStart.toInstant() ;
Instant stop = zdtStop.toInstant() ;

start.toString () = 2020-01-29T23: 00: 00Z

stop.toString () = 2020-01-30T23: 00: 00Z

Если вы хотите, чтобы весь день даты отображался в формате UTC, а не в часовом поясе, используйте OffsetDateTime.

LocalDate today = LocalDate.now( ZoneOffset.UTC  ) ;

OffsetDateTime odtStart = today.atTime( OffsetTime.MIN ) ;
OffsetDateTime odtStop = today.plusDays( 1 ).atTime( OffsetTime.MIN ) ;

odtStart.toString () = 2020-01-30T00: 00 + 18: 00

odtStop.toString () = 2020-01-31T00: 00 + 18: 00

Эти OffsetDateTime объекты уже будут в формате UTC, но вы можете позвонить, toInstantесли вам нужны такие объекты, которые по определению всегда находятся в формате UTC.

Instant start = odtStart.toInstant() ;
Instant stop = odtStop.toInstant() ;

start.toString () = 2020-01-29T06: 00: 00Z

stop.toString () = 2020-01-30T06: 00: 00Z

Совет: вас может заинтересовать добавление библиотеки ThreeTen-Extra в ваш проект, чтобы использовать ее Intervalкласс для представления этой пары Instantобъектов. Это обеспечивает класс полезные методы для сравнения , такие как abuts, overlaps, containsи многое другое.

Interval interval = Interval.of( start , stop ) ;

interval.toString () = 2020-01-29T06: 00: 00Z / 2020-01-30T06: 00: 00Z

Полуоткрытый

Ответ на mprivat правильно. Его цель - не пытаться получить конец дня, а скорее сравнить с «до начала следующего дня». Его идея известна как «полуоткрытый» подход, в котором у отрезка времени есть начало, включающее, а окончание - исключительное .

  • Текущие рамки времени и даты Java (java.util.Date/Calendar и Joda-Time ) оба используют миллисекунды от эпохи . Но в Java 8 новые классы JSR 310 java.time. * Используют разрешение наносекунды. Любой код, который вы написали, основанный на принудительном подсчете миллисекунд последнего момента дня, будет неправильным, если переключиться на новые классы.
  • Сравнение данных из других источников становится некорректным, если они используют другие разрешения. Например, библиотеки Unix обычно используют целые секунды, а базы данных, такие как Postgres, преобразуют дату и время в микросекунды.
  • Некоторые изменения летнего времени происходят за полночь, что может еще больше запутать ситуацию.

введите описание изображения здесь

Joda-Time 2.3 предлагает метод для этой цели, чтобы получить первый момент дня: withTimeAtStartOfDay(). Аналогично в java.time LocalDate::atStartOfDay.

Поищите в StackOverflow «joda half-open», чтобы увидеть больше обсуждений и примеров.

См. Этот пост Билла Шнайдера « Временные интервалы и другие диапазоны должны быть полуоткрытыми ».

Избегайте устаревших классов даты и времени

Классы java.util.Date и .Calendar, как известно, проблематичны. Избежать их.

Используйте классы java.time . Java.time структура является официальным преемником весьма успешной Joda-Time библиотеки.

java.time

Фреймворк java.time встроен в Java 8 и новее. Обратно перенесен на Java 6 и 7 в проекте ThreeTen-Backport , а затем адаптирован для Android в проекте ThreeTenABP .

An Instant- это момент на временной шкале в формате UTC с разрешением наносекунды .

Instant instant = Instant.now();

Примените часовой пояс, чтобы узнать время на настенных часах для некоторой местности.

ZoneId zoneId = ZoneId.of( "America/Montreal" );
ZonedDateTime zdt = ZonedDateTime.ofInstant( instant , zoneId );

Чтобы получить первое мгновение дня, просмотрите LocalDateкласс и его atStartOfDayметод.

ZonedDateTime zdtStart = zdt.toLocalDate().atStartOfDay( zoneId );

Используя подход Half-Open, получите первый момент следующего дня.

ZonedDateTime zdtTomorrowStart = zdtStart.plusDays( 1 );

Таблица всех типов даты и времени в Java, как современных, так и устаревших

В настоящее время в структуре java.time отсутствует Intervalкласс, описанный ниже для Joda-Time. Однако проект ThreeTen-Extra расширяет java.time дополнительными классами. Этот проект является испытательной площадкой для возможных будущих дополнений к java.time. Среди его классов есть Interval. Создайте Interval, передав пару Instantобъектов. Мы можем извлечь Instantиз наших ZonedDateTimeобъектов.

Interval today = Interval.of( zdtStart.toInstant() , zdtTomorrowStart.toInstant() );

О java.time

Java.time каркас встроен в Java 8 и более поздних версий. Эти классы вытеснять неприятные старые устаревшие классы даты и времени , такие как java.util.Date, Calendar, и SimpleDateFormat.

Чтобы узнать больше, см. Oracle Tutorial . И поищите в Stack Overflow множество примеров и объяснений. Спецификация - JSR 310 .

Проект Joda-Time , находящийся сейчас в режиме обслуживания , рекомендует перейти на классы java.time .

Вы можете обмениваться объектами java.time напрямую с вашей базой данных. Используйте драйвер JDBC, совместимый с JDBC 4.2 или новее. Нет необходимости в строках, нет необходимости в java.sql.*занятиях. Hibernate 5 и JPA 2.2 поддерживают java.time .

Где взять классы java.time?


Joda времени

ОБНОВЛЕНИЕ: проект Joda-Time сейчас находится в режиме обслуживания и советует перейти на классы java.time . Я оставляю этот раздел нетронутым для истории.

Joda время имеет три класса для представления промежуток времени различных способов: Interval, Periodи Duration. У An Intervalесть определенное начало и конец на временной шкале Вселенной. Это соответствует нашей потребности в представлении «дня».

Мы вызываем метод, withTimeAtStartOfDayа не устанавливаем время суток на нули. Из-за летнего времени и других аномалий первого момента дня может не быть 00:00:00.

Пример кода с использованием Joda-Time 2.3.

DateTimeZone timeZone = DateTimeZone.forID( "America/Montreal" );
DateTime now = DateTime.now( timeZone );
DateTime todayStart = now.withTimeAtStartOfDay();
DateTime tomorrowStart = now.plusDays( 1 ).withTimeAtStartOfDay();
Interval today = new Interval( todayStart, tomorrowStart );

При необходимости вы можете преобразовать его в файл java.util.Date.

java.util.Date date = todayStart.toDate();
Василий Бурк
источник
Мне нужно использоватьLocalDateTime
user924
170

Java 8


public static Date atStartOfDay(Date date) {
    LocalDateTime localDateTime = dateToLocalDateTime(date);
    LocalDateTime startOfDay = localDateTime.with(LocalTime.MIN);
    return localDateTimeToDate(startOfDay);
}

public static Date atEndOfDay(Date date) {
    LocalDateTime localDateTime = dateToLocalDateTime(date);
    LocalDateTime endOfDay = localDateTime.with(LocalTime.MAX);
    return localDateTimeToDate(endOfDay);
}

private static LocalDateTime dateToLocalDateTime(Date date) {
    return LocalDateTime.ofInstant(date.toInstant(), ZoneId.systemDefault());
}

private static Date localDateTimeToDate(LocalDateTime localDateTime) {
    return Date.from(localDateTime.atZone(ZoneId.systemDefault()).toInstant());
}

Обновление : я добавил эти 2 метода в свои классы утилит Java здесь

Он находится в центральном репозитории Maven по адресу:

<dependency>
  <groupId>com.github.rkumsher</groupId>
  <artifactId>utils</artifactId>
  <version>1.3</version>
</dependency>

Java 7 и более ранние версии


С помощью Apache Commons

public static Date atEndOfDay(Date date) {
    return DateUtils.addMilliseconds(DateUtils.ceiling(date, Calendar.DATE), -1);
}

public static Date atStartOfDay(Date date) {
    return DateUtils.truncate(date, Calendar.DATE);
}

Без Apache Commons

public Date atEndOfDay(Date date) {
    Calendar calendar = Calendar.getInstance();
    calendar.setTime(date);
    calendar.set(Calendar.HOUR_OF_DAY, 23);
    calendar.set(Calendar.MINUTE, 59);
    calendar.set(Calendar.SECOND, 59);
    calendar.set(Calendar.MILLISECOND, 999);
    return calendar.getTime();
}

public Date atStartOfDay(Date date) {
    Calendar calendar = Calendar.getInstance();
    calendar.setTime(date);
    calendar.set(Calendar.HOUR_OF_DAY, 0);
    calendar.set(Calendar.MINUTE, 0);
    calendar.set(Calendar.SECOND, 0);
    calendar.set(Calendar.MILLISECOND, 0);
    return calendar.getTime();
}
RKumsher
источник
5
Я рекомендую отказаться от этого подхода из-за проблем крайнего случая, с которыми вы столкнетесь с часовыми поясами, переходом на летнее время и т. Д., О которых библиотека времени, такая как Joda, позаботится за вас.
Эдвард Андерсон
7
Думаю, в нем getStartOfDayдолжно быть: LocalDateTime startOfDay = localDateTime.with(LocalTime.MIN);т.е. LocalTime, а не LocalDateTime.
Константин Кулагин
Не думайте так getEndOfDay(Date date). getStartOfDay(Date date)методы в разделе Java 8 верны, а именно LocalDateTime.MAX и .MIN помещают вас в самые ранние возможные даты в прошлом и будущем, а не в начале и конце дня. Вместо этого я бы предложил .atStartOfDay () для начала и .plusDays (1) .atStartOfDay (). MinusNanos (1) для завершения.
Glen Mazza
Метод localDateTimeToDate(LocalDateTime startOfDay)выдает исключение - java.lang.IllegalArgumentException: java.lang.ArithmeticException: long overflow
sanluck
1
@GlenMazza, решение Java 8 использует LocalTime .MAX , а не LocalDateTime.MAX . Решение правильное.
Фредерико Пантуцца,
28

в getEndOfDay вы можете добавить:

calendar.set(Calendar.MILLISECOND, 999);

Хотя с математической точки зрения, вы не можете указать конец дня, кроме как «до начала следующего дня».

Таким образом , вместо того , чтобы говорить, if(date >= getStartOfDay(today) && date <= getEndOfDay(today))вы должны сказать: if(date >= getStartOfDay(today) && date < getStartOfDay(tomorrow)). Это гораздо более надежное определение (и вам не нужно беспокоиться о точности в миллисекундах).

mprivat
источник
1
Вторая половина этого ответа - лучший вариант. Этот подход известен как «полуоткрытый». Объясняю далее в своем ответе .
Basil Bourque
14

java.time

Использование java.timeфреймворка, встроенного в Java 8.

import java.time.LocalTime;
import java.time.LocalDateTime;

LocalDateTime now = LocalDateTime.now(); // 2015-11-19T19:42:19.224
// start of a day
now.with(LocalTime.MIN); // 2015-11-19T00:00
now.with(LocalTime.MIDNIGHT); // 2015-11-19T00:00
// end of a day
now.with(LocalTime.MAX); // 2015-11-19T23:59:59.999999999
Przemek
источник
3
Этот ответ игнорирует аномалии, такие как переход на летнее время (DST). У Local…классов нет понятия часовых поясов и поправок на аномалии. Например, в некоторых часовых поясах переход на летнее время происходит в полночь, поэтому первый момент дня приходится на время 01:00:00.0(1 час ночи), а не на 00:00:00.0значение, созданное этим кодом. ZonedDateTimeВместо этого используйте .
Basil Bourque
4

Дополнительный способ найти начало дня с помощью java8 java.time.ZonedDateTime вместо прохождения LocalDateTime- это просто усечение входного ZonedDateTime до DAYS:

zonedDateTimeInstance.truncatedTo( ChronoUnit.DAYS );
Laur
источник
2

Для java 8 работают следующие однострочные операторы. В этом примере я использую часовой пояс UTC. Пожалуйста, подумайте об изменении часового пояса, который вы в настоящее время используете.

System.out.println(new Date());

final LocalDateTime endOfDay       = LocalDateTime.of(LocalDate.now(), LocalTime.MAX);
final Date          endOfDayAsDate = Date.from(endOfDay.toInstant(ZoneOffset.UTC));

System.out.println(endOfDayAsDate);

final LocalDateTime startOfDay       = LocalDateTime.of(LocalDate.now(), LocalTime.MIN);
final Date          startOfDayAsDate = Date.from(startOfDay.toInstant(ZoneOffset.UTC));

System.out.println(startOfDayAsDate);

Если нет разницы во времени с выходом. Пытаться:ZoneOffset.ofHours(0)

Fırat KÜÇÜK
источник
1
Как это увеличивает ценность по сравнению с существующими ответами? Кроме того, вы игнорируете важную проблему часового пояса. А передача константы UTC в toInstantнедопустимый синтаксис и концептуально избыточна.
Basil
@BasilBourque "Как это может повысить ценность существующих ответов?" Так работает stackoverflow. Кстати, это всего лишь шаблон. Люди могут меняться так, как они хотят. передача UTC в toInstant полностью законна, вы можете проверить результаты.
Fırat KÜÇÜK
2

Java 8 или ThreeTenABP

ZonedDateTime

ZonedDateTime curDate = ZonedDateTime.now();

public ZonedDateTime startOfDay() {
    return curDate
    .toLocalDate()
    .atStartOfDay()
    .atZone(curDate.getZone())
    .withEarlierOffsetAtOverlap();
}

public ZonedDateTime endOfDay() {

    ZonedDateTime startOfTomorrow =
        curDate
        .toLocalDate()
        .plusDays(1)
        .atStartOfDay()
        .atZone(curDate.getZone())
        .withEarlierOffsetAtOverlap();

    return startOfTomorrow.minusSeconds(1);
}

// based on https://stackoverflow.com/a/29145886/1658268

LocalDateTime

LocalDateTime curDate = LocalDateTime.now();

public LocalDateTime startOfDay() {
    return curDate.atStartOfDay();
}

public LocalDateTime endOfDay() {
    return startOfTomorrow.atTime(LocalTime.MAX);  //23:59:59.999999999;
}

// based on https://stackoverflow.com/a/36408726/1658268

Надеюсь, это кому-то поможет.

SudoPlz
источник
Метод "atTime" - очень хороший подход к объединению с константой "LocalTime.MAX" для конца дня. Ницца!
Мистер Андерсон,
2

Я пробовал этот код, и он хорошо работает!

final ZonedDateTime now = ZonedDateTime.now(ZoneOffset.UTC);
final ZonedDateTime startofDay =
    now.toLocalDate().atStartOfDay(ZoneOffset.UTC);
final ZonedDateTime endOfDay =
    now.toLocalDate().atTime(LocalTime.MAX).atZone(ZoneOffset.UTC);
Сиддхей Санкхе
источник
Если у вас есть ZonedDateTimeи Instantв вашем распоряжении нет необходимости использовать java.sql.Timestamp. Этот класс - один из ужасных старых устаревших классов, которые теперь вытесняются классами java.time . Начиная с JDBC 4.2, мы можем напрямую обмениваться объектами java.time с базой данных. Ваше смешение устаревших и современных классов не имеет для меня смысла.
Basil Bourque
1

Еще одно решение, не зависящее от какой-либо структуры:

static public Date getStartOfADay(Date day) {
    final long oneDayInMillis = 24 * 60 * 60 * 1000;
    return new Date(day.getTime() / oneDayInMillis * oneDayInMillis);
}

static public Date getEndOfADay(Date day) {
    final long oneDayInMillis = 24 * 60 * 60 * 1000;
    return new Date((day.getTime() / oneDayInMillis + 1) * oneDayInMillis - 1);
}

Обратите внимание, что он возвращает время в формате UTC.

Александр Чингарев
источник
1
private Date getStartOfDay(Date date) {
    Calendar calendar = Calendar.getInstance();
    int year = calendar.get(Calendar.YEAR);
    int month = calendar.get(Calendar.MONTH);
    int day = calendar.get(Calendar.DATE);
    calendar.setTimeInMillis(0);
    calendar.set(year, month, day, 0, 0, 0);
    return calendar.getTime();
    }

private Date getEndOfDay(Date date) {
    Calendar calendar = Calendar.getInstance();
    int year = calendar.get(Calendar.YEAR);
    int month = calendar.get(Calendar.MONTH);
    int day = calendar.get(Calendar.DATE);
    calendar.setTimeInMillis(0);
    calendar.set(year, month, day, 23, 59, 59);
    return calendar.getTime();
    }

calendar.setTimeInMillis (0); дает точность до миллисекунд

маник венкат
источник
1
Эти ужасные классы даты и времени были много лет назад вытеснены современными классами java.time, определенными в JSR 310.
Бэзил Бурк,
@BasilBourque пакет java.time появился только в Java 8, который доступен только с Android N (API 26)
ivan8m8
@ ivan8m8 Большая часть функциональности java.time перенесена на Java 6 и 7 в библиотеке ThreeTen-Backport . Дальнейшая адаптация для ранних версий Android (<26) в библиотеке ThreeTenABP .
Базиль Бурк,
@BasilBourque, но ThreeTen использует крайне неэффективный механизм на Android .
ivan8m8
@ ivan8m8 Возможно, вы думаете о предшественнике java.time , Joda-Time . Опять же, см. Проект ThreeTenABP . Я не слышал о такой неэффективности использования этой библиотеки на Android.
Базиль Бурк,
0

Я знаю, что это немного поздно, но в случае Java 8, если вы используете OffsetDateTime (который предлагает множество преимуществ, таких как TimeZone, Nanoseconds и т. Д.), Вы можете использовать следующий код:

OffsetDateTime reallyEndOfDay = someDay.withHour(23).withMinute(59).withSecond(59).withNano(999999999);
// output: 2019-01-10T23:59:59.999999999Z
Флорин
источник
0

У меня было несколько неудобств со всеми решениями, потому что мне нужен был тип переменной Instant, а часовой пояс всегда мешал менять все, а затем, комбинируя решения, я увидел, что это хороший вариант.

        LocalDate today = LocalDate.now();
        Instant startDate = Instant.parse(today.toString()+"T00:00:00Z");
        Instant endDate = Instant.parse(today.toString()+"T23:59:59Z");

и мы в результате

        startDate = 2020-01-30T00:00:00Z
        endDate = 2020-01-30T23:59:59Z

Я надеюсь это поможет тебе

Йоши
источник
Фактически, день заканчивается с наступлением первого момента следующего дня. Ваш код пропускает последнюю полную секунду дня, миллиард наносекунд, о которых никогда не сообщается. См. Мой ответ, чтобы узнать о полуоткрытом подходе к определению промежутка времени.
Базиль Бурк,
Управление строками как способ обработки значений даты и времени, как правило, является плохим подходом. Используйте для этой работы умные объекты, а не глупые строки. В java.time классы предоставляют все функции, необходимые для этой проблемы , не прибегая к манипуляциям строки.
Базиль Бурк,
Я только что добавил в свой ответ раздел tl; dr, показывающий, как получить диапазон дня в виде пары объектов. Совет: вам может быть интересно добавить в свой проект библиотеку ThreeTen-Extra, чтобы использовать ее класс. InstantInterval
Базиль Бурк,
0

Думаю, проще всего было бы что-то вроде:

// Joda Time

DateTime dateTime=new DateTime(); 

StartOfDayMillis = dateTime.withMillis(System.currentTimeMillis()).withTimeAtStartOfDay().getMillis();
EndOfDayMillis = dateTime.withMillis(StartOfDayMillis).plusDays(1).minusSeconds(1).getMillis();

Эти миллисекунды можно затем преобразовать в Calendar, Instant или LocalDate в соответствии с вашими требованиями с помощью Joda Time.

happyvirus
источник
-2
public static Date beginOfDay(Date date) {
    Calendar cal = Calendar.getInstance();
    cal.setTime(date);
    cal.set(Calendar.HOUR_OF_DAY, 0);
    cal.set(Calendar.MINUTE, 0);
    cal.set(Calendar.SECOND, 0);
    cal.set(Calendar.MILLISECOND, 0);

    return cal.getTime();
}

public static Date endOfDay(Date date) {
    Calendar cal = Calendar.getInstance();
    cal.setTime(date);
    cal.set(Calendar.HOUR_OF_DAY, 23);
    cal.set(Calendar.MINUTE, 59);
    cal.set(Calendar.SECOND, 59);
    cal.set(Calendar.MILLISECOND, 999);

    return cal.getTime();
}
Джек Джин
источник