Как проверить действительность типа даты Java?

Я написал две простые функции, чтобы получить значение моей даты в таблице MySQL. Оба столбца даты начала и даты окончания имеют тип данных Date. Итак, в моих этих двух функциях это выглядит примерно так:

public Date get_startdate(long nodeid,String ts) {
    try {
        String sql = "Select STARTDT FROM urllink WHERE URL='f0 = "+nodeid+"&ts = "+ts + "'";
        if (em == null) {
             throw new Exception("could not found URL object.");
        }
        return (Date) em.createNativeQuery(sql).getSingleResult();
    } catch (Exception e) {
        e.printStackTrace();
    }
    return null;
}

public Date get_enddate(long nodeid,String ts) {
    try {
        String sql = "Select ENDDT FROM urllink WHERE URL='f0 = "+nodeid+"&ts = "+ts + "'";
        if (em == null) {
            throw new Exception("could not found URL object.");
        }
        return (Date) em.createNativeQuery(sql).getSingleResult();
    } catch (Exception e) {
        e.printStackTrace();
    }
    return null;
}

Теперь, когда я вызываю эти функции на своей главной странице, я хочу проверить условия даты: если URL-адрес находится между этими двумя датами, то он должен быть действительным и что-то делать. Я выделяю то, что я имею в виду ниже:

Date file_getstartdate=fileFacade1.get_startdate(fileID,hash);
Date file_getenddate=fileFacade1.get_enddate(fileID,hash);

String currentDate = CoreUtil.parseDate(new Date());

if ( file_getstartdate<= currentDate<= file_getendDate){
    //URL is valid, do something
}else {
    //do nothing
}

Моя дата, хранящаяся в моей таблице, имеет формат YYYY-MM-DD, и проблема, с которой я сталкиваюсь, заключается в приведенном выше выражении if для сравнения. Я не могу использовать эти операторы для проверки. Есть ли способ достичь того, чего я желаю?

Я рекомендую вам не использовать Date, независимо от того, было ли это java.sql.Date или java.util.Date. Эти классы плохо разработаны и давно устарели. Вместо этого используйте LocalDate из java.time, современный API даты и времени Java. У него есть методы isAfter, isBefore и isEqual, которые сделают то, что вы хотите.

Ole V.V. 08.04.2019 13:30

Я знал об использовании localeDate, поскольку он новее и более обновлен, но он доступен только в Java 8. Я помню, что в прошлый раз вы предоставили альтернативное решение относительно использования некоторой функции обратной переносимости, если я правильно помню.

Daredevil 08.04.2019 13:47

Да, java.time был перенесен на Java 6 и 7 в версии Бэкпорт ThreeTen. К сожалению, он не интегрирован ни с Java Persistence, ни с JDBC, поэтому вам придется выполнить некоторое преобразование.

Ole V.V. 08.04.2019 15:52
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
В компьютерном программировании биты играют важнейшую роль в представлении и манипулировании данными на двоичном уровне. Побитовые операции...
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Приходилось ли вам сталкиваться с требованиями, в которых вас могли попросить поднять тревогу или выдать ошибку, когда метод Java занимает больше...
Полный курс Java для разработчиков веб-сайтов и приложений
Полный курс Java для разработчиков веб-сайтов и приложений
Получите сертификат Java Web и Application Developer, используя наш курс.
2
3
675
3
Перейти к ответу Данный вопрос помечен как решенный

Ответы 3

Если вы используете строки вместо объектов даты, оператор if будет работать для этого формата, если вы напишете его как (a <= b && b <= c)

В противном случае я не уверен, как getSingleResult() можно преобразовать в объект Date, так как это может быть что угодно, и вам действительно придется анализировать значение, а затем использовать соответствующие методы Date для проверки isBefore и isAfter

Java: как проверить, находится ли дата в определенном диапазоне?

получить одиночные результаты, скажем, дату начала как 2019-04-08

Daredevil 08.04.2019 07:07

Я не знаю, какую библиотеку sql вы используете, но я ожидаю, что объект ResultSet не может быть приведен к дате

OneCricketeer 08.04.2019 07:08

Будет ли работать, если я преобразую их в строку и использую < или > для выполнения оператора if?

Daredevil 08.04.2019 07:09

Должно, да.

OneCricketeer 08.04.2019 07:09

Поэтому мне нужно разобрать мой метод на String

Daredevil 08.04.2019 07:09

Но как String может проверить, что текущая дата больше или меньше конечной даты в yyyy-mm-dd?

Daredevil 08.04.2019 07:10

Подумай об этом. 2017 в виде строки меньше, чем 2019, посимвольно. Если у вас на компьютере есть список файлов с датами, как вы думаете, как он их сортирует?

OneCricketeer 08.04.2019 07:12

Понимаю. Значит, он может проверить сам day, например, 2019-04-08 больше, чем 2019-04-02?

Daredevil 08.04.2019 07:13

Кроме того, вам не нужны два метода. Просто запустите один запрос sql = "Select STARTDT, ENDDT FROM urllink...

OneCricketeer 08.04.2019 07:14

Я также пытался использовать операторы <= для String, это не позволяло мне сравнивать. я не думаю, что это сработает

Daredevil 08.04.2019 07:15

Да, так как "8" > "2" верно. Он основан на значениях ASCII

OneCricketeer 08.04.2019 07:16

другой вариант - передать Date String в SQL и выполнить сравнение там

Scary Wombat 08.04.2019 07:16

@ Страшно, для этого потребуется подзапрос, да?

OneCricketeer 08.04.2019 07:19

Возможно, ответ PlayerOne кажется более разумным, если использовать String.compareTo() для решения этой проблемы.

Daredevil 08.04.2019 08:27
Ответ принят как подходящий

Не используйте конкатенацию строк для создания SQL-запросов.

Это уязвимо для SQL-инъекций и действительно опасно:

String sql = "Select STARTDT FROM urllink WHERE URL='f0 = "+nodeid+"&ts = "+ts + "'";
...
return (Date) em.createNativeQuery(sql).getSingleResult();

Предполагая, что em относится к объекту EntityManager, вы можете построить запрос на основе Criteria, или, если вам действительно нужно придерживаться собственного SQL, вместо этого вы должны использовать Подготовленное заявление.

Что касается вашего вопроса о сравнении, у вас есть три проблемы:

  • Вы пытаетесь сравнить два разных типа (String и Date).
  • Вы пытаетесь использовать оператор, который можно использовать только для сравнения примитивов, но не объектов (<=).
  • Вы пишете свое условие как математический оператор вместо условного оператора программирования (a <= b <= c не является допустимым оператором Java. Он должен быть a <= b && b <= c).

Вот способ сравнения с использованием класса Date (обратите внимание, что, поскольку Java 8 LocalDate — гораздо лучший класс для использования, если у вас есть возможность).

Date fileStartDate = fileFacade1.get_startdate(fileID, hash);
Date fileEndDate = fileFacade1.get_enddate(fileID, hash);

Date currentDate = new Date();

if (fileStartDate.before(currentDate) && fileEndDate.after(currentDate) {
  ...

В ответ на ваш комментарий

Okay but does using preparedStatement helps with this SQL inejction. Does entity manager prevent the injection? I was told not to use preparedstatement,are there any other alternatives?

Если кто-то сказал вам сделать собственный запрос, используя конкатенацию строк (части вашего кода +nodeid+ и +ts +) вместо использования PreparedStatement, то они ошибаются. EntityManager не защитит вас от внедрения кода выше, но PreparedStatement вместе с изменением того, как построен ваш запрос, защитит.

PreparedStatement будет выглядеть примерно так

String url = "f0 = " + nodeid + "&ts = " + ts;
PreparedStatement preparedStatement = connection.prepareStatement("Select STARTDT FROM urllink WHERE URL= ?");
preparedStatement.setString(1, url);
ResultSet resultSet = preparedStatement.executeQuery();

Если вам сказали использовать EntityManager вместо написания собственного SQL, то это действительно хороший совет. Вам нужно будет построить свой запрос, используя абстракцию Criteria. Как это сделать, наверное, отдельный вопрос.

Но моя таблица хранится в формате YYYY-MM-DD, и для сравнения не требуется отметка времени, возможно ли это?

Daredevil 08.04.2019 07:19

ГГГГ-ММ-ДД имеет то преимущество, что сравнение строк даст тот же результат, что и преобразование его в дату с последующим сравнением. Так что либо используйте две строки и String.compareTo(), либо две LocalDates и LocalDate.compareTo(). В любом случае вы должны получить тот же результат.

Player One 08.04.2019 07:26

Я не понимаю, вы можете дать ответ?

Daredevil 08.04.2019 08:23

@Daredevil Я отредактировал пример. Кроме того, пожалуйста, не забывайте о том, что я сказал о SQL-инъекциях, это действительно важно.

Player One 08.04.2019 08:39

Я хотел бы добавить, что localdate вне моей досягаемости, так как я использую java 7. Как в вашем примере вы можете сравнить тип Date со строковым типом, который является currentDate, я запутался?

Daredevil 08.04.2019 08:48

Итак, вы используете .before и .after , это лучше, чем сравнивать с помощью String compareTo?

Daredevil 08.04.2019 08:55

@ Daredevil Я отредактировал некоторую информацию о подготовленных заявлениях в своем ответе. Что касается .before и .after против String.compareTo, оба варианта, вероятно, хороши. Ключ в том, что вам нужно убедиться, что оба объекта, которые вы сравниваете, имеют один и тот же тип. Вы можете сравнивать строки с другими строками и даты с другими датами, но не строки с датами.

Player One 08.04.2019 09:04

Понимаю. Но в вашем ответе эта строка if (fileStartDate.before(currentDate) && fileEndDate.after(currentDate) использует строку и дату, почему это должно работать?

Daredevil 08.04.2019 09:06

В качестве примера SQL-инъекции: если злоумышленник передаст nodeId ' or 1 = 1 --, произойдет сбой, возможно, с сообщением об ошибке, которое предоставит им детали реализации, которые они могут использовать, и определенно сообщит им, что другие, более чувствительные области кода вероятно, также уязвимы, поэтому они могут начать больше исследовать и найти способ получить доступ к конфиденциальным данным и / или получить доступ, которого у них не должно быть.

Player One 08.04.2019 09:08

@ Daredevil «он использует String и Date, почему это должно работать?» Это была опечатка, извините :) Я исправил это

Player One 08.04.2019 09:09

Произойдет ли SQL-инъекция в системе, используемой внутри страны?

Daredevil 08.04.2019 09:11

Что касается вашего обновленного ответа, формат сравнения все еще YYYY-mm-dd? Потому что DATE() также включает временную метку.

Daredevil 08.04.2019 09:11

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

Player One 08.04.2019 09:13

@ Daredevil При сравнении Dates он не будет сравниваться ни в каком формате. Вы правы, что new Date() будет иметь компонент времени, когда он был построен, а остальные даты будут в полночь. Если это проблема, вам нужно либо обрезать время из вашего new Date(), либо, если вам удобнее сравнивать строки, отформатируйте их все как ГГГГ-ММ-ДД и используйте String.compare. На SO есть и другие вопросы и ответы, которые смогут помочь с обеими этими задачами :)

Player One 08.04.2019 09:21

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

Daredevil 08.04.2019 09:22

@ Сорвиголова, тогда ты должен быть в порядке с new Date(). Любое время будет между полуночью startDate и полночью endDate.

Player One 08.04.2019 09:25

Эй, позвольте мне пройти тест, и если он сработает, я отмечу ваш ответ.

Daredevil 08.04.2019 09:29

Спасибо, это сработало. Но я удивлен отладкой, что currentdate возвращает Mon April 2019 и может сравниваться с датой начала и датой окончания в формате yyyy-mm-dd. Как их можно сравнивать, если они разного формата?

Daredevil 08.04.2019 09:48

Если вы там, я разместил еще одну ветку, пытающуюся выполнить sql-инъекцию в моей программе: stackoverflow.com/questions/55571337/…, и я не смог внедрить ее в свою программу. Вы можете посоветовать?

Daredevil 08.04.2019 12:41

@Daredevil Это сбивает с толку, но на самом деле это не разные форматы - это объекты одного и того же типа. Даты могут быть показано зрителю в разных форматах, но когда вы сравниваете их в Java, они будут сравниваться внутренней реализацией их метода compareTo.

Player One 08.04.2019 13:11

тл;др

if ( file_getstartdate<= currentDate<= file_getendDate) { … }

LocalDateRange                                // Represent a span-of-time as a pair of `LocalDate` (date-only) objects.
.ofClosed( startLocalDate , stopLocalDate )   // Making the date range in fully-closed approach, against my advice of using half-open approach.
.contains(                                    // Compares a specified `LocalDate` against the start and stop dates of the range.
    LocalDate.now(                            // Use `java.time.LocalDate` to represent a date-only value without a time-of-day and without a time zone.
        ZoneId.of( "Africa/Tunis" )           // Specify the time zone by which we want to perceive the calendar date for the current moment. "Tomorrow" arrives in Paris France while still "yesterday" in Montréal Québec.
    )                                         // Returns a `LocalDate` object.
)                                             // Returns a `boolean` primitive.

Java.время

Ответ первого игрока правильный, но его можно улучшить, используя современные классы Java.время, а не ужасные устаревшие классы (Date и т. д.).

LocalDate

Для столбца стандартного для SQL типа DATE используйте класс LocalDate. Класс LocalDate представляет значение только для даты без времени суток и без часовой пояс или смещение от UTC.

Часовой пояс

Часовой пояс имеет решающее значение при определении даты. В любой момент дата варьируется по всему земному шару в зависимости от зоны. Например, несколько минут после полуночи в Париж, Франция — это новый день, а в Монреаль, Квебек — все еще «вчера».

Если часовой пояс не указан, JVM неявно применяет свой текущий часовой пояс по умолчанию. Это значение по умолчанию может быть измениться в любой момент во время выполнения (!), поэтому ваши результаты могут отличаться. Лучше указать желаемый/ожидаемый часовой пояс явно в качестве аргумента. Если критично, подтвердите зону у своего пользователя.

ZoneID

Укажите правильное название часового пояса в формате Continent/Region, например America/Montreal, Africa/Casablanca или Pacific/Auckland. Никогда не используйте сокращения из 2-4 букв, такие как EST или IST, так как это нет истинные часовые пояса, не стандартизированные и даже не уникальные(!).

ZoneId z = ZoneId.of( "America/Montreal" ) ;  
LocalDate today = LocalDate.now( z ) ;

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

ZoneId z = ZoneId.systemDefault() ;  // Get JVM’s current default time zone.

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

if ( file_getstartdate<= currentDate<= file_getendDate){

Вам будет легче работать, если вы будете последовательно использовать полуоткрытый подход к определению промежутка времени. В этом подходе начало — включительно, а окончание — эксклюзивный. Таким образом, месяц начинается с первого числа и продолжается до первого месяца следующий, но не включает его.

Таким образом, логика вашего запроса будет выглядеть следующим образом: с <= и <.

if ( file_getstartdate <= currentDate < file_getendDate)

Это означает, что в SQL нет использует BETWEEN для работы с датой и временем.

String sql = "SELECT * from tbl WHERE when >= ? AND when < ? ;" ;
…
myPreparedStatement.setObject( 1 , today ) ;
myPreparedStatement.setObject( 2 , today ) ;

Чтобы получить DATE, значения:

LocalDate localDate = myResultSet.getObject( … , LocalDate.class ) ;

Такая же полуоткрытая логика в коде Java будет выглядеть следующим образом. Обратите внимание, что более короткий способ сказать «равно или позже, чем» — это «не раньше».

boolean isTodayWithinDateRange = 
    ( ! today.isBefore( startDate ) )  // Is today same or later than start…
    &&                                 // AND
    today.isBefore( stopDate )         // Is today before the stop (for Half-Open approach). 
;

Не путайте значения даты и времени с текстом

My date stored in my table is in the format YYYY-MM-DD

Нет это не так. Тип DATE в MySQL хранит дату с помощью собственного внутреннего механизма, а не обычного текста.

Объект даты и времени, такой как столбец DATE в базе данных или LocalDate в Java, может анализировать входную строку в значение даты и может генерировать строку из этого значения даты. Но значение даты не является строкой. Не путайте их. Другими словами, значение даты не имеет «формата», формат имеют только их текстовые представления.

LocalDateRange

Если вы выполняете большую часть этой работы, добавьте в свой проект библиотеку ThreeTen-Extra. Это дает вам доступ к классу LocalDateRange.

LocalDate start = … ;
LocalDate stop = … ;
LocalDateRange range = LocalDateRange.of( start , stop ) ;
boolean rangeContainsToday = range.contains( today ) ;

По умолчанию класс LocalDateRange работает с полуоткрытым подходом. Но если вы настаиваете на своем полностью закрытом подходе, переопределите его поведение по умолчанию с помощью метода LocalDateRange.ofClosed.


О Java.время

Платформа Java.время встроена в Java 8 и более поздние версии. Эти классы заменяют проблемные старые классы даты и времени наследие, такие как java.util.Date, Calendar и SimpleDateFormat.

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

Проект Джода-Время, теперь именуемый Режим технического обслуживания, рекомендует перейти на классы Java.время.

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

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

  • Ява SE 8, Ява SE 9, Ява SE 10, Ява SE 11 и более поздние версии — часть стандартного Java API со встроенной реализацией.
    • Java 9 добавляет некоторые незначительные функции и исправления.
  • Ява SE 6 и Ява SE 7
    • Большая часть функциональности Java.время обратно перенесена на Java 6 и 7 в ThreeTen-Backport.
  • Андроид
    • Более поздние версии реализации пакетов Android классов Java.время.
    • Для более ранних версий Android (<26) проект тридесятьABP адаптирует ThreeTen-Backport (упомянутый выше). См. Как использовать ThreeTenABP….

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

Другие вопросы по теме