Почему Java Date.after () возвращает true, если дата на самом деле раньше? - PullRequest
0 голосов
/ 09 сентября 2018

У меня была программа, в которой я сравнивал две даты друг с другом; хотя date1 было раньше date2, date1.after(date2) вернуло true. Часовые пояса не имели никакого эффекта; обе даты были в UTC.

import java.sql.Timestamp;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.TimeZone;

public class Test {
    public static void main(String[] args) throws Exception {
        TimeZone.setDefault(TimeZone.getTimeZone("Etc/UTC"));
        SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS");
        Date date1 = dateFormat.parse("2018-07-27 01:22:14.077");
        Date date2 = new Timestamp(1532654534390l);
        System.out.println(dateFormat.format(date1));
        System.out.println(dateFormat.format(date2));
        System.out.println(date1.getTime() > date2.getTime());
        System.out.println(date1.after(date2));
    }
}

Это выводит:

2018-07-27 01: 22: 14,077
2018-07-27 01:22: 14,390
ложь
правда

Что здесь происходит?


В моей реальной программе date1 анализируется из файла журнала, а date2 извлекается из базы данных с помощью Hibernate, что вызывает различные типы данных. Несмотря на то, что я нашел основную причину и знаю, как избежать этой проблемы, я все еще очень заинтересован в решениях, которые предотвращают эту ловушку.

Ответы [ 2 ]

0 голосов
/ 09 сентября 2018

ТЛ; др

Используйте современные java.time классы, никогда не ужасные устаревшие классы даты и времени.

Instant
.ofEpochMilli( 1_532_654_534_390L ) 
.isAfter(
    LocalDateTime
    .parse( 
        "2018-07-27 01:22:14.077"
        .replace( " " , "T" ) 
    )
    .atOffset( 
        ZoneOffset.UTC
    )
    .toInstant()
)

Док говорит: не используйте Timestamp объект как Date

Ваш код:

Date date2 = new Timestamp…  // Violates class documentation. 

… нарушает договор, установленный в документации класса.

Из-за различий между классом Timestamp и классом java.util.Date, упомянутых выше, рекомендуется, чтобы код не просматривал значения Timestamp в общем случае как экземпляр java.util.Date. Отношения наследования между меткой времени и java.util.Date действительно обозначают наследование реализации, а не наследование типов.

Док отмечает, что, хотя java.sql.Timestamp технически наследуется от java.util.Date, вам предписано игнорировать этот факт наследования. Вы не должны использовать Timestamp объект как Date. Ваш код делает именно то, что доктор сказал вам , а не .

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

Поведение, которое вы видели относительно несоответствия долей секунды и наносекунд миллисекунд, задокументировано:

Примечание. Этот тип является составной частью java.util.Date и отдельного значения наносекунд. В компоненте java.util.Date хранятся только целые секунды. Дробные секунды - нано - разделены. Метод Timestamp.equals (Object) никогда не возвращает true, если передан объект, который не является экземпляром java.sql.Timestamp, поскольку компонент nanos даты неизвестен. В результате метод Timestamp.equals (Object) не является симметричным относительно метода java.util.Date.equals (Object). Кроме того, метод hashCode использует базовую реализацию java.util.Date и, следовательно, не учитывает в своих вычислениях nanos.

java.time

Вы используете заведомо ужасные классы. Проблема, которую вы обнаружили, связана с их ужасным дизайном, который использовал плохие хаки. Не пытайтесь понять эти классы; просто избегайте их полностью .

Эти устаревшие классы были вытеснены несколько лет назад классами java.time .

Разбор вашей входной строки.

LocalDateTime ldt = LocalDateTime.parse( "2018-07-27 01:22:14.077".replace( " " , "T" ) ;  // Without a time zone or offset, this value has no specific meaning, is *not* a point on the timeline. 

Очевидно, вы точно знаете, что входная строка неявно представляла момент в UTC.

OffsetDateTime odt = ldt.atOffset( ZoneOffset.UTC ) ;  // Assign an offset-from-UTC to give the date and time a meaning as an actual point on the timeline. 

Разобрать другой ввод, по-видимому, отсчет миллисекунд с первого момента 1970 года в UTC. Класс Instant является более базовым, чем OffsetDateTime, момент в UTC, всегда UTC по определению.

Instant instant = Instant.ofEpochMilli( 1_532_654_534_390L ) ;  // Translate a count of milliseconds from 1970-01-01T00:00:00Z into a moment on the timeline in UTC. 

Сравнить.

Boolean stringIsAfterLong = odt.toInstant().isAfter( instant ) ;

О java.time

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

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

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

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

Где получить классы java.time?

Проект ThreeTen-Extra расширяет java.time дополнительными классами. Этот проект является полигоном для возможных будущих дополнений к java.time. Здесь вы можете найти некоторые полезные классы, такие как Interval, YearWeek, YearQuarter и more .

0 голосов
/ 09 сентября 2018

Основная «проблема» здесь заключается в том, что java.sql.Timestamp, продолжая java.util.Date, не сохраняет миллисекунды в указанном поле (fastTime, эквивалентно Unix time ), но в отдельном поле nanos. Метод after учитывает только поле fastTime (что имеет смысл, поскольку его можно использовать для всех объектов Date).

Что происходит в этой ситуации, так это то, что fastTime из Timestamp является округленным от 1532654534 390 до 1532654534 000 , что ниже чем 1532654534 077 другой даты (и ниже означает более раннюю дату). Следовательно, after() и before() не являются надежными в этом случае; решение состоит в том, чтобы использовать getTime() (который перегружен на Timestamp для обеспечения правильного значения) в обе даты и сравнить их.

...