Як отримати час початку та час закінчення дня?


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();
}

Це не точно до мілісекунди.


8
Що ви маєте на увазі, що це "не точно"?
Кірк Волл

Що не так з кодом? Що ви отримуєте, чого не очікуєте? За яким часовим поясом ви хочете, щоб ваші дні починалися та закінчувалися?
Марк Рід

4
У наведеному вище коді навіть не використовується дата, передана в метод. Це слід робити: calendar.setTime (дата) після створення календаря.
Джеррі Дестремпс

2
Це питання передбачає, що значення дати та часу мають роздільну здатність мілісекунд. Це стосується java.util.Date та Joda-Time. Але False for java.time пакет у Java 8 (наносекунд), деякі бази даних, такі як Postgres (мікросекунди), бібліотеки unix (цілі секунди) та, можливо, інші. Дивіться мою відповідь щодо більш безпечного підходу за допомогою Half-Open.
Василь Бурк

2
Насправді ви повинні отримати початок наступного дня, і при повторенні елементів у цей день (якщо припустити, що ви хочете це зробити) ви встановите верхню межу, використовуючи <замість <=. Це виключає наступний день, але включає в себе весь попередній день аж до наносекунди.
Даніель 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 напіввідкритим", щоб переглянути більше обговорень та прикладів.

Дивіться цю публікацію, Білль Шнайдер , часові інтервали та інші діапазони повинні бути напіввідкритими .

Уникайте застарілих занять з датами

Класи java.util.Date та .Calendar, як відомо, непросто. Уникайте їх.

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

java.time

Рамка java.time вбудована в Java 8 та новіші версії. Перенесено назад на Java 6 і 7 у програмі ThreeTen-Backport , додатково адаптований до Android у проекті ThreeTenABP .

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 );

Використовуючи напіввідкритий підхід, отримайте перший момент наступного дня.

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 . І шукайте переповнення стека за багатьма прикладами та поясненнями. Специфікація - JSR 310 .

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

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

Де отримати класи java.time?


Joda-Time

ОНОВЛЕННЯ: Проект Joda-Time зараз перебуває в режимі обслуговування та радить перейти на java.time класів . Цей розділ я залишаю недоторканим для історії.

Joda час має три класи для подання проміжок часу різних способів: Interval, Periodі Duration. AnInterval має певний початок і закінчення на часовій шкалі Всесвіту. Це відповідає нашій потребі представляти "день".

Ми називаємо метод 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 Utility тут

Він знаходиться в центральному сховищі Мейвена за адресою:

<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();
}

5
Я рекомендую проти такого підходу через проблеми, що стосуються кращих випадків, з якими ви зіткнетеся з часовими поясами, літньою економією тощо, про те, щоб піклувалася про вас таку бібліотеку часу, як Joda.
Едвард Андерсон

7
Я думаю, що getStartOfDayмає бути: LocalDateTime startOfDay = localDateTime.with(LocalTime.MIN);Ie LocalTime, а не LocalDateTime.
Костянтин Кулагін

Не думаю, що це getEndOfDay(Date date). getStartOfDay(Date date)Методи в розділі Java 8 є правильними, а саме LocalDateTime.MAX і .MIN ставлять вас на найбільш ранні дати в минулому і в майбутньому, а не на початок і кінець дня. Замість цього я б запропонував .atStartOfDay () для запуску та .plusDays (1) .atStartOfDay (). MinusNanos (1) для кінця.
Глен Мацца

Метод localDateTimeToDate(LocalDateTime startOfDay)кидає виняток - java.lang.IllegalArgumentException: java.lang.ArithmeticException: довгий перелив
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)). Це набагато більш солідне визначення (і вам не доведеться турбуватися про мілісекундну точність).


1
Друга половина цієї відповіді - найкращий спосіб. Цей підхід відомий як "Напіввідкритий". Я пояснюю далі у своїй відповіді .
Василь Бурк

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

3
Ця відповідь ігнорує аномалії, такі як літній час (DST). На Local…заняттях немає поняття часових поясів і коригування на аномалії. Наприклад, у деяких часових поясах перепадає DST опівночі, тому перший момент дня припадає на час 01:00:00.0(1:00), а не на 00:00:00.0значення, що створюється цим кодом. Використовуйте ZonedDateTimeзамість цього.
Василь Бурк

4

Додатковий спосіб пошуку початку дня з java8 java.time.ZonedDateTime замість того, щоб пройти, LocalDateTime- це просто обрізання вводу ZonedDateTime до DAYS:

zonedDateTimeInstance.truncatedTo( ChronoUnit.DAYS );

2

Для java 8 працюють наступні однорядкові оператори. У цьому прикладі я використовую часовий пояс UTC. Будь ласка, подумайте про зміну TimeZone, яку ви використовували.

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)


1
Як це додає цінність за рамки існуючих відповідей? Крім того, ви ігноруєте вирішальну проблему часового поясу. А передача константи UTC на toInstantнелегальний синтаксис та надмірна концептуально.
Василь Бурк

@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

Я сподіваюся, що хтось допомагає.


Метод "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 з базою даних. Ваше змішання спадщини та сучасних класів для мене не має сенсу.
Василь Бурк

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

У мене було декілька незручностей з усіма рішеннями, тому що мені потрібен був тип змінної миттєвого, і часова зона завжди втручалася змінювати все, потім, поєднуючи рішення, я побачив, що це хороший варіант.

        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 до своєї відповіді, в якому показано, як отримати діапазон дня як пари Instantоб’єктів. Порада: можливо, вам буде цікаво додати бібліотеку ThreeTen-Extra до проекту, щоб використовувати його Intervalклас.
Василь Бурк

0

Я думаю, що найпростіше було б щось на кшталт:

// Joda Time

DateTime dateTime=new DateTime(); 

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

Ці мільйони можна потім перетворити на календар, миттєвий або LocalDate відповідно до ваших вимог за допомогою Joda Time.


-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();
}
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.