tl; dr
ZonedDateTime.toInstant()
настраивает момент от часового пояса до UTC.Вы получите один и тот же момент, другое время на настенных часах и , возможно, другую дату для одной и той же точки на временной шкале.То, что вы видите, не является проблемой, не является несоответствием .
Ваша проблема не в вычитании 30 дней.Реальные проблемы:
- Непонимание того, что часовой пояс влияет на дату
- Сопоставление дат с днями
Кроме того, ваш вопрос неопределенный.Сказать «30 дней назад» может означать как минимум три разные вещи:
- 30 * 24 часа
- Диапазон от 22:44 тридцать календарных дней назад в часовом поясе Нью-Йорка до 22: 44 сейчас по нью-йоркскому времени
- Весь сегодняшний день, как это видно в Нью-Йорке, и целые дни, возвращающиеся на 30 дней в календаре, как в Нью-Йорке.
Все три возможности описаны ниже, с примером кода, помеченного ➥
.
⑦? ??? ↔ ??? ⑧?
7 декабря, незадолго до полуночи (22:44), Алиса в своей нью-йоркской квартире решает позвонить своему другу Бобув Рейкьявике, Исландия.Боб не может поверить, что его телефон звонит, и, глядя на часы на тумбочке, видит, что почти 4 часа утра (03:44).И модные цифровые часы Боба показывают дату 8-го декабря, а не 7-го.Тот же самый момент, та же точка на временной шкале, другое время на настенных часах, другая дата .
Люди Исландии используют UTC в качестве своего часового пояса круглый год.Нью-Йорк на пять часов отстает от UTC в декабре 2018 года, и поэтому на пять часов отстает от Исландии.В Нью-Йорке «вчера» 7-е, а в Исландии «завтра» 8-е.Разные даты, один и тот же момент.
Так что забудьте о вычитании тридцати дней.Каждый раз, когда вы находите момент в Нью-Йорке, который близок к полуночи, и затем настраиваетесь на UTC, вы будете перемещать дату вперед.
Нет расхождений, дополнительный день не добавляется. В любой момент времени дата меняется по всему земному шару в зависимости от часового пояса.С диапазоном часовых поясов около 26-27 часов, это всегда где-то «завтра» и «вчера».
Другой ответ предлагает включить LocalDateTime
в эту проблему.Это опрометчиво.В этом классе намеренно отсутствует понятие часового пояса или смещения от UTC.Это означает, что LocalDateTime
не может представлять момент.LocalDateTime
представляет потенциальных моментов в диапазоне 26-27 часов, упомянутых выше.Не имеет смысла использовать этот класс здесь.
Вместо этого используйте OffsetDateTime
для момента просмотра со смещением от UTC, по сравнению с [ZonedDateTime][2]
, в котором используется часовой пояс.
В чем разница между смещением и зоной?Смещение - это просто количество часов-минут-секунд, ни больше, ни меньше.Зона, напротив, на намного * на 1065 * больше.Зона - это история прошлых, настоящих и будущих изменений в смещении, используемом людьми определенного региона.Поэтому часовой пояс всегда предпочтительнее простого смещения, так как он приносит больше информации.Если вы хотите использовать UTC, вам нужно только смещение, ноль часов, минут и секунд.
OffsetDateTime odt = zdt.toOffsetDateTime().withOffsetSameInstant( ZoneOffset.UTC ) ; // Adjust from a time zone to UTC.
Видимые здесь zdt
и odt
представляют один и тот же момент, одну и ту же точку на временной шкале, разное время на настенных часах, как в примере Алисы и Боба выше.
Дни! = Даты
Если вы хотите сделать запрос для диапазона тридцати дней назад, вы должны определить, что вы подразумеваете под «днями».
Дней
you Вы имеете в виду 30 кусков продолжительностью 24 часа?Если это так, работайте с Instant
.Этот класс представляет момент в UTC, всегда в UTC.
ZoneId z = ZoneId.of( "America/New_York" ) ;
ZonedDateTime zdtNow = ZonedDateTime.now( z ) ;
Instant instantNow = zdt.toInstant() ; // Adjust from time zone to UTC. Same moment, different wall-clock time.
Instant instantThirtyDaysAgo = instantNow.minus( 30 , ChronoUnit.DAYS ) ; // Subtract ( 30 * 24 hours ) without regard for dates.
Вы можете иметь возможность обмениваться Instant
с вашей базой данных через драйвер JDBC.Но Instant
является необязательным, в то время как поддержка OffsetDateTime
требуется для JDBC 4.2 и более поздних версий.Если это так, давайте перепишем этот код.
ZoneId z = ZoneId.of( "America/New_York" ) ;
ZonedDateTime zdtNow = ZonedDateTime.now( z ) ;
OffsetDateTime odtNow = zdt.toOffsetDateTime().withOffsetSameInstant( ZoneOffset.UTC ) ; // Adjust from time zone to UTC. Same moment, different wall-clock time.
OffsetDateTime odtThirtyDaysAgo = odtNow.minusDays( 30 ) ;
Ваш SQL может выглядеть примерно так:
Обратите внимание, что мы используем подход Half-Open для определения промежутка времени, где начало включительно , а окончание исключительно .Как правило, это лучшая практика, так как она позволяет избежать проблемы нахождения бесконечно делимого последнего момента и обеспечивает аккуратные стыковки без разрывов.Поэтому мы не используем команду SQL BETWEEN
, будучи полностью закрытыми (включительно с обоих концов).
SELECT * FROM event_ WHERE when_ >= ? AND when_ < ? ;
Установите значения для заполнителей в подготовленном выражении.
myPreparedStatement.setObject( 1 , odtThirtyDaysAgo ) ;
myPreparedStatement.setObject( 2 , odtNow ) ;
Даты
➥ Если под «30 дней назад» вы имели в виду 30 коробок на календаре, висящем на стене в нью-йоркском офисе, это совсем другая проблема.
В то же время суток
И если да, то имеете ли вы в виду текущий момент и переход назад на 30 дней к тому же времени дня?
ZoneId z = ZoneId.of( "America/New_York" ) ;
ZonedDateTime zdtNow = ZonedDateTime.now( z ) ;
ZonedDateTime zdtThirtyDaysAgo = zdtNow.minusDays( 30 ) ; // `ZonedDateTime` will try to keep the same time-of-day but will adjust if that time on that date in that zone is not valid.
С помощью кода, показанного выше, класс ZonedDateTime
попытается использовать то же самое время дня в более раннюю дату.Но это время может быть недействительным на эту дату в этой зоне из-за таких аномалий, как переход на летнее время (DST).При такой аномалии класс ZonedDateTime
настраивается на допустимое время.Обязательно изучите JavaDoc, чтобы понять алгоритм и проверить, соответствует ли он вашим бизнес-правилам.
Перейдите к подготовленному заявлению.
myPreparedStatement.setObject( 1 , zdtThirtyDaysAgo ) ;
myPreparedStatement.setObject( 2 , zdtNow ) ;
Весь день
➥Или под «30 дней назад» вы подразумеваете даты, а под датами вы подразумеваете весь день?
Если это так, нам нужно сосредоточиться на значении только для даты, используя класс LocalDate
,без времени суток и без часового пояса.
ZoneId z = ZoneId.of( "America/New_York" ) ;
LocalDate today = LocalDate.now( z ) ;
LocalDate tomorrow = today.plusDays( 1 ) ;
LocalDate thirtyDaysAgo = tomorrow.minusDays( 30 ) ;
Теперь нам нужно перейти от даты к определенному моменту, назначив время дня и часовой пояс.Мы хотим, чтобы время было первым моментом дня.Не думайте, что это означает 00:00.Из-за аномалий, таких как DST, день может начаться в другое время, например 01:00.Пусть java.time определит первый момент дня в эту дату в этой зоне.
ZonedDateTime zdtStart = thirtyDaysAgo.atStartOfDay( z ) ;
ZonedDateTime zdtStop = tomorrow.atStartOfDay( z ) ;
Перейдите к подготовленному заявлению.
myPreparedStatement.setObject( 1 , zdtStart ) ;
myPreparedStatement.setObject( 2 , zdtStop ) ;