Хороший способ для сравнения даты Java без времени

Необходимо проверить, является ли какая-либо дата (например, дата истечения срока действия) больше или равной сегодняшнему дню. В настоящее время для этого простого сравнения используется библиотека времени JODA. Даже некоторые сообщения рекомендуют, например, это.

Но недавно обнаружил проблему с часовыми поясами. Дата существует в PST, и при преобразовании в LocalDate следующее преобразование становится ложным в 17:00 по тихоокеанскому времени, когда оно должно быть истинным -

LocalDate now = LocalDate.fromDateFields(new Date()); // Current date in PST
LocalDate expiryDate = LocalDate.fromDateFields(expiresOn); // expiresOn is java.util.Date
boolean notExpired = expiryDate.isEqual(now) || expiryDate.isAfter(now);

При внимательном рассмотрении LocalDate expiryDate использовал хронологию UTC. Таким образом, в 17:00 по тихоокеанскому стандартному времени, когда переменная expiryDate содержит 2021-01-16, переменная now становится 2021-01-17

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

Пожалуйста, порекомендуйте, как лучше решить эту проблему. Я пытаюсь понять, какие особые преимущества я могу получить, используя время joda, потому что такое же сострадание можно сделать, используя SimpleDateFormatter.


person Anuja Paradkar    schedule 17.01.2021    source источник
comment
Всегда указывайте время в формате UTC и настраивайтесь на часовой пояс пользователя.   -  person maio290    schedule 17.01.2021
comment
Вы должны решить, что означает сегодняшний день в вашем контексте. Если сегодняшний день следует интерпретировать в часовом поясе, в котором работает ваш код, вам нужно преобразовать оба времени в местный часовой пояс, а затем выполнить сравнение части даты этого времени. Но если вам нужно сравнение, которое всегда дает один и тот же ответ, независимо от того, в каком часовом поясе вы находитесь, вам нужно преобразовать оба времени в любой часовой пояс, который вы хотите использовать для определения сегодняшнего дня. В любом случае вам нужно преобразовать оба значения времени в общий часовой пояс, прежде чем сравнение только их дат будет иметь какой-либо смысл...   -  person CryptoFool    schedule 17.01.2021
comment
Как только вы поймете необходимую логику и решите, как определить сегодняшний день, вы сможете написать правильное решение, используя практически любой API времени/даты. Если вы можете предположить, что у вас довольно свежая (Java 8+) версия Java, я бы рекомендовал вам использовать более новый API даты/времени, встроенный в Java, а не использовать JodaTime. В любом случае я бы держался подальше от старого Java API.   -  person CryptoFool    schedule 17.01.2021
comment
Значит, истечение должно произойти позже в Америке, потому что, скажем, 16 января там заканчивается позже, чем, например, в Азии и Европе?   -  person Ole V.V.    schedule 17.01.2021
comment
Что вы подразумеваете под PST? Вы имели в виду тихоокеанское время (PST или PDT в зависимости от времени года)?   -  person Ole V.V.    schedule 17.01.2021


Ответы (2)


API даты и времени java.util и их API форматирования SimpleDateFormat устарели и подвержены ошибкам. Рекомендуется полностью прекратить их использование и перейти на современный API даты и времени.

Узнайте о современном API даты и времени из Trail: Date Time.

LocalDate по умолчанию использует часовой пояс JVM

Всякий раз, когда задействован часовой пояс, обязательно укажите его при создании экземпляра LocalDate. LocalDate использует часовой пояс JVM по умолчанию, и вы никогда не должны сравнивать LocalDate из одного часового пояса с другим без преобразования их обоих в один и тот же часовой пояс (рекомендуется UTC). То же самое и в случае с LocalDateTime. Вместо использования LocalDate вы должны выполнять всю обработку с объектами, у которых есть и дата, и время (например, LocalDateTime), и при необходимости вы можете получить из них LocalDate.

Кроме того, объект java.util.Date просто представляет количество миллисекунд со стандартного базового времени, известного как эпоха, а именно January 1, 1970, 00:00:00 GMT (или UTC). Когда вы печатаете объект java.util.Date, его метод toString возвращает дату и время в часовом поясе JVM, вычисленные из этого значения в миллисекундах.

Следовательно, если вы получаете expiryDate из объекта java.util.Date, это по сути дата-время в UTC.

Вы можете преобразовать now-in-PST и expiryDate в java.time.Instant и сравните их. java.time.Instant — это мгновенная точка на временной шкале UTC.

Демонстрация с использованием современного API даты и времени:

import java.time.Instant;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.util.Calendar;
import java.util.Date;

public class Main {
    public static void main(String[] args) {
        LocalDateTime nowInPST = LocalDateTime.now(ZoneId.of("America/Los_Angeles"));
        System.out.println(nowInPST);

        // Convert it to date in UTC
        Instant nowInPSTConvertedToInstant = nowInPST.atZone(ZoneId.of("America/Los_Angeles"))
                                                .withZoneSameInstant(ZoneId.of("Etc/UTC"))
                                                .toInstant();

        // Some java.util.Date
        Calendar calendar = Calendar.getInstance();
        calendar.set(2020, 0, 10, 10, 10, 10);
        Date date = calendar.getTime();
        Instant expiry = date.toInstant();
        
        System.out.println(nowInPSTConvertedToInstant.isBefore(expiry));
    }
}

Вывод:

2021-01-17T10:58:38.490041
false

Примечание. Ознакомьтесь со следующим уведомлением на главной странице Joda-Time

Joda-Time — это де-факто стандартная библиотека даты и времени для Java до Java SE 8. Теперь пользователям предлагается перейти на java.time (JSR-310).

Упростите свое выражение

Следующее утверждение

boolean notExpired = expiryDate.isEqual(now) || expiryDate.isAfter(now);

можно упростить как

boolean notExpired = !expiryDate.isBefore(now);
person Arvind Kumar Avinash    schedule 17.01.2021

Вы должны рассмотреть два API:

  1. Joda-Time, который вы использовали до сих пор, является хорошей библиотекой, но в режиме обслуживания.
  2. Главный разработчик Joda-Time Стивен Коулбурн продолжил разработку java.time, современного API даты и времени Java, опираясь на уроки хорошего и не очень хорошего опыта Joda-Time.

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

Джода-Время

    System.setProperty("user.timezone", "America/Vancouver");
    
    Date expiresOn = new Date(1_610_841_600_000L); // Jan 17 UTC
    System.out.println(expiresOn);
    
    LocalDate now = LocalDate.now(DateTimeZone.UTC);
    System.out.println(now);
    LocalDate expiryDate = new DateTime(expiresOn, DateTimeZone.UTC).toLocalDate();
    System.out.println(expiryDate);
    
    boolean notExpired = expiryDate.isEqual(now) || expiryDate.isAfter(now);
    System.out.println("Expired? " + (notExpired ? "No" : "Yes"));

Вывод при запуске сейчас:

Sat Jan 16 16:00:00 PST 2021
2021-01-17
2021-01-17
Expired? No

На домашней странице Joda-Time говорится:

Обратите внимание, что Joda-Time считается в значительной степени «законченным» проектом. Серьезных доработок не планируется. Если вы используете Java SE 8, перейдите на java.time (JSR-310).

Java.время

    LocalDate now = LocalDate.now(ZoneOffset.UTC);
    System.out.println(now);
    LocalDate expiryDate = expiresOn.toInstant()
            .atOffset(ZoneOffset.UTC)
            .toLocalDate();
    System.out.println(expiryDate);
    
    boolean notExpired = expiryDate.isEqual(now) || expiryDate.isAfter(now);
    System.out.println("Expired? " + (notExpired ? "No" : "Yes"));
2021-01-17
2021-01-17
Expired? No

Примечание о вкусе

Я предпочитаю избегать ненужных отрицаний в именах переменных (и в других местах). Я бы сделал проще:

    boolean expired = expiryDate.isBefore(now);
    System.out.println("Expired? " + expired);

Истекший? ЛОЖЬ

Ссылки

person Ole V.V.    schedule 17.01.2021
comment
Я применил ваше предложение java.time, но все еще задаюсь вопросом, почему время joda может давать неправильный результат. Решил проверить у вас, если знаете. LocalDate now = LocalDate.fromDateFields(new Date()); значение now равно следующему дню в UTC, а не в PST, если я попробую в 16:00 по тихоокеанскому времени. - person Anuja Paradkar; 18.01.2021