Как проверить действительность типа даты 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 выше, чтобы сделать сравнение. Я не могу использовать эти операторы для проверки. Есть ли способ достичь того, чего я желаю?


person Daredevil    schedule 08.04.2019    source источник
comment
Я рекомендую вам не использовать Date, независимо от того, был ли это java.sql.Date или java.util.Date. Эти классы плохо разработаны и давно устарели. Вместо этого используйте LocalDate из java.time, современного API даты и времени Java. У него есть методы isAfter, isBefore и isEqual, которые сделают то, что вы хотите.   -  person Ole V.V.    schedule 08.04.2019
comment
Я знал об использовании localeDate, поскольку он новее и более обновленный, но он доступен только в Java 8. Я помню, что в прошлый раз вы предоставили альтернативное решение относительно использования некоторой функции обратной переносимости, если я правильно помню.   -  person Daredevil    schedule 08.04.2019
comment
Да, java.time был перенесен на Java 6 и 7 в ThreeTen Backport. К сожалению, он не интегрирован ни с Java Persistence, ни с JDBC, поэтому вам придется выполнить некоторое преобразование.   -  person Ole V.V.    schedule 08.04.2019


Ответы (3)


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

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

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

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

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

  • Вы пытаетесь сравнить два разных типа (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) {
  ...

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

Хорошо, но использование prepareStatement помогает с этой SQL-инъекцией. Предотвращает ли Entity Manager инъекцию? Мне сказали не использовать подготовленное заявление, есть ли другие альтернативы?

Если кто-то сказал вам сделать собственный запрос, используя конкатенацию строк (части +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. Как это сделать, наверное, отдельный вопрос.

person Player One    schedule 08.04.2019
comment
Но моя таблица хранится в формате YYYY-MM-DD, и для сравнения не требуется отметка времени, возможно ли это? - person Daredevil; 08.04.2019
comment
ГГГГ-ММ-ДД имеет то преимущество, что сравнение строк даст тот же результат, что и преобразование его в дату с последующим сравнением. Так что либо используйте две строки и String.compareTo(), либо две LocalDates и LocalDate.compareTo(). В любом случае вы должны получить тот же результат. - person Player One; 08.04.2019
comment
Я не понимаю, вы можете дать ответ? - person Daredevil; 08.04.2019
comment
@Daredevil Я отредактировал пример. Кроме того, пожалуйста, не забывайте о том, что я сказал о SQL-инъекциях, это действительно важно. - person Player One; 08.04.2019
comment
Я хотел бы добавить, что localdate вне моей досягаемости, так как я использую java 7. Как в вашем примере вы можете сравнить тип Date со строковым типом, который равен currentDate, я запутался? - person Daredevil; 08.04.2019
comment
Итак, вы используете .before и .after , это лучше, чем сравнивать с помощью String compareTo? - person Daredevil; 08.04.2019
comment
@ Daredevil Я отредактировал некоторую информацию о подготовленных заявлениях в своем ответе. Что касается .before и .after против String.compareTo, оба способа, вероятно, хороши. Ключ в том, что вам нужно убедиться, что оба объекта, которые вы сравниваете, имеют один и тот же тип. Вы можете сравнивать строки с другими строками и даты с другими датами, но не строки с датами. - person Player One; 08.04.2019
comment
Я понимаю. Но в вашем ответе эта строка if (fileStartDate.before(currentDate) && fileEndDate.after(currentDate) использует строку и дату, почему это должно работать? - person Daredevil; 08.04.2019
comment
В качестве примера SQL-инъекции: если злоумышленник передает nodeId ' or 1 = 1 --, происходит сбой, возможно, с сообщением об ошибке, которое дает им детали реализации, которые они могут использовать, и определенно сообщает им, что другие, более чувствительные области кода вероятно, также уязвимы, поэтому они могут начать больше исследовать и найти способ получить доступ к конфиденциальным данным и / или получить доступ, которого у них не должно быть. - person Player One; 08.04.2019
comment
@ Daredevil использует String и Date, почему это должно работать? Это была опечатка, извините :) Я исправил это - person Player One; 08.04.2019
comment
Произойдет ли SQL-инъекция в системе, используемой внутри страны? - person Daredevil; 08.04.2019
comment
Что касается вашего обновленного ответа, формат сравнения по-прежнему YYYY-mm-dd? Потому что DATE() также включает временную метку. - person Daredevil; 08.04.2019
comment
Это может быть менее вероятно, чем в системе с внешней облицовкой, но может. Например, недовольный сотрудник может захотеть устроить хаос перед уходом. Несмотря на это, всегда хорошо писать надежный код, и вы никогда не знаете, когда только внутренняя система может быть раскрыта третьим лицам по вполне уважительным деловым причинам. - person Player One; 08.04.2019
comment
@Daredevil При сравнении Dates он не будет сравниваться ни в каком формате. Вы правы, что new Date() будет иметь компонент времени, когда он был построен, а другие даты будут в полночь. Если это проблема, вам нужно либо обрезать время из вашего new Date(), либо, если вам удобнее сравнивать строки, отформатируйте их все как ГГГГ-ММ-ДД и используйте String.compare. На SO есть и другие вопросы и ответы, которые смогут помочь с обеими этими задачами :) - person Player One; 08.04.2019
comment
Временная метка на самом деле не проблема, моя главная забота - просто убедиться, что текущая дата, сегодня находится в пределах даты начала и даты окончания, тогда URL-адрес действителен. - person Daredevil; 08.04.2019
comment
@ Сорвиголова, тогда ты должен быть в порядке с new Date(). Любое время будет между полуночью startDate и полночью endDate. - person Player One; 08.04.2019
comment
Эй, позвольте мне пройти тест, и если он сработает, я отмечу ваш ответ. - person Daredevil; 08.04.2019
comment
Спасибо, это сработало. Но я удивлен отладкой, что currentdate возвращает Mon April 2019 и может сравниваться с датой начала и датой окончания, которая имеет формат yyyy-mm-dd. Как их можно сравнивать, если они разного формата? - person Daredevil; 08.04.2019
comment
Если вы там, я разместил еще один поток, пытающийся выполнить sql-инъекцию в моей программе: stackoverflow.com/questions/55571337/, и я не смог внедрить в свою программу. Вы можете посоветовать? - person Daredevil; 08.04.2019
comment
@Daredevil Это сбивает с толку, но на самом деле это не разные форматы - это объекты одного и того же типа. Даты могут быть показаны зрителю в разных форматах, но когда вы сравниваете их в Java, они будут сравниваться внутренней реализацией их метода compareTo. - person Player One; 08.04.2019

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

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

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

person OneCricketeer    schedule 08.04.2019
comment
получить одиночные результаты, скажем, дату начала как 2019-04-08 - person Daredevil; 08.04.2019
comment
Я не знаю, какую библиотеку sql вы используете, но я ожидаю, что объект ResultSet не может быть приведен к дате - person OneCricketeer; 08.04.2019
comment
Будет ли это работать, если я преобразую их в строку и использую < или > для выполнения оператора if? - person Daredevil; 08.04.2019
comment
Должно, да. - person OneCricketeer; 08.04.2019
comment
Поэтому мне нужно разобрать мой метод на String - person Daredevil; 08.04.2019
comment
Но как String может проверить, что текущая дата больше или меньше конечной даты в yyyy-mm-dd? - person Daredevil; 08.04.2019
comment
Подумай об этом. 2017 в виде строки меньше, чем 2019, посимвольно. Если у вас на компьютере есть список файлов с датами, как вы думаете, как он их сортирует? - person OneCricketeer; 08.04.2019
comment
Я понимаю. Таким образом, он может проверить сам day, например, 2019-04-08 больше, чем 2019-04-02? - person Daredevil; 08.04.2019
comment
Кроме того, вам не нужны два метода. Просто запустите один запрос sql="Select STARTDT, ENDDT FROM urllink... - person OneCricketeer; 08.04.2019
comment
Я также пытался использовать операторы <= для String, это не позволяло мне сравнивать. я не думаю, что это сработает - person Daredevil; 08.04.2019
comment
Да, так как 8 › 2 верно. Он основан на значениях ASCII - person OneCricketeer; 08.04.2019
comment
другой вариант - передать Date String в SQL и выполнить сравнение там - person Scary Wombat; 08.04.2019
comment
@ Страшно, для этого потребуется подзапрос, да? - person OneCricketeer; 08.04.2019
comment
Возможно, ответ PlayerOne кажется более разумным, если использовать String.compareTo() для решения этой проблемы. - person Daredevil; 08.04.2019

tl;dr

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.time классы, а не ужасные устаревшие классы (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.

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

если (file_getstartdate‹= currentDate‹= file_getendDate){

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

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

если (file_getstartdate ‹= текущая дата ‹ 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). 
;

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

Моя дата, хранящаяся в моей таблице, имеет формат ГГГГ-ММ-ДД.

Нет. Тип 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.time

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

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

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

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

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

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

person Basil Bourque    schedule 09.04.2019