2

I wrote two simple functions to get the value of my date in MySQL table. Both start date and end date columns are both Date data type. So, in my these two function that goes something like this:

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;
}

Now that I call these functions in my main page like these, I want to check the date conditions that if the URL is between these two dates,then it should be valid and do something. I highlight what I mean below:

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
}

My date stored in my table is in the format YYYY-MM-DD and the problem I am facing is in the if statement above to make the comparison.I can't use those operators to do the checking. Is there a way to achieve what I desire?

halfer
  • 19,824
  • 17
  • 99
  • 186
Daredevil
  • 1,672
  • 3
  • 18
  • 47
  • 1
    I recommend you don’t use `Date`, no matter if that was `java.sql.Date` or `java.util.Date`. Those classes are poorly designed and long outdated. Instead use `LocalDate` from [java.time, the modern Java date and time API](https://docs.oracle.com/javase/tutorial/datetime/). It has methods `isAfter`, `isBefore` and `isEqual` that will do what you want. – Ole V.V. Apr 08 '19 at 11:30
  • I was aware of using `localeDate` since it's newer and more updated but it's only available in Java 8. I remember you did provide an alternative solution the last time regarding using some back portability feature if i recall correctly. – Daredevil Apr 08 '19 at 11:47
  • Yes, java.time has been backported to Java 6 and 7 in [ThreeTen Backport](http://www.threeten.org/threetenbp/). Unfortunately not integrated with Java Persistence nor JDBC, though, so you’d have to do some conversion. – Ole V.V. Apr 08 '19 at 13:52

3 Answers3

3

Don't use String concatenation to construct SQL queries

This is vulnerable to SQL injection, and is really dangerous:

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

Assuming em refers to an EntityManager object then you can build a Criteria based query, or if you really need to stick with native SQL then you should use a PreparedStatement instead.

For your comparison question, you have three issues:

  • You're trying to compare two different types (String and Date).
  • You're trying to use an operator that can only be used to compare primitives but not Objects (<=).
  • You're writing your condition like a mathematical statement instead of a programming conditional statement (a <= b <= c isn't a valid Java statement. It needs to be a <= b && b <= c).

Here is a way to compare using the Date class (note that since Java 8 LocalDate is a much better class to use if you have the option).

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) {
  ...

In response to your comment

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?

If someone told you to make a native query using string concatenation (the +nodeid+ and +ts + parts of your code) instead of using a PreparedStatement then they are wrong. An EntityManager will not protect you from injection in the code above, but a PreparedStatement, along with changing how your query is constructed, would.

A PreparedStatement would look something like

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

If you've been told to use an EntityManager instead of writing native SQL then that's actually good advice. You'll need to construct your query using the Criteria abstraction. How to do that is probably a separate question.

Player One
  • 607
  • 6
  • 12
  • But my table stores in `YYYY-MM-DD` format and not needing timestamp to do the comparison, is that possible? – Daredevil Apr 08 '19 at 05:19
  • YYYY-MM-DD has the advantage that a string comparison will give the same result as converting it to a date and then comparing. So either use two Strings and `String.compareTo()`, or two LocalDates and `LocalDate.compareTo()`. Either way you should get the same result. – Player One Apr 08 '19 at 05:26
  • I don't follow, can you provide an answer? – Daredevil Apr 08 '19 at 06:23
  • @Daredevil I've edited an example in. Also, please don't overlook what I said about SQL injection, it's really important. – Player One Apr 08 '19 at 06:39
  • I would like to add `localdate` is out of my reach since I am using java 7. How come in your example, you can make a comparision of Date type with string type which is `currentDate`, i am confused? – Daredevil Apr 08 '19 at 06:48
  • So you use `.before` and `.after` , it is better than compared using String compareTo? – Daredevil Apr 08 '19 at 06:55
  • @Daredevil I've edited some information on prepared statements in to my answer. Regarding `.before` and `.after` vs `String.compareTo` both ways are probably fine. The key is you need to make sure both objects you are comparing are the same type. You can compare Strings with other Strings, and Dates with other Dates, but not Strings with Dates. – Player One Apr 08 '19 at 07:04
  • I see. But in your answer this line `if (fileStartDate.before(currentDate) && fileEndDate.after(currentDate)`,it's using String and Date, why would this work? – Daredevil Apr 08 '19 at 07:06
  • Re an example of SQL injection, if an attacker passes a nodeId of `' or 1 = 1 --` then it will crash, possibly with an error message that gives them implementation details that they can exploit, and would definitely let them know that other, more sensitive areas of the code are probably vulnerable as well, so they could start probing more and find a way to access sensitive data and/or gain access they should not have. – Player One Apr 08 '19 at 07:08
  • @Daredevil "it's using String and Date, why would this work?" That was a typo sorry :) I've corrected it – Player One Apr 08 '19 at 07:09
  • Would SQL injection happen to a system used internally? – Daredevil Apr 08 '19 at 07:11
  • Regarding your updated answer, the format for comparison is still `YYYY-mm-dd`? Because `DATE()` has timestamp included as well though. – Daredevil Apr 08 '19 at 07:11
  • It might be less likely than in an external facing system, but it could. For example a disgruntled employee might want to cause havoc before they leave. Regardless it's always good to write robust code, and you never know when the internal only system could be exposed to third parties for perfectly valid business reasons. – Player One Apr 08 '19 at 07:13
  • @Daredevil When comparing `Date`s it won't compare in any format. You're right that `new Date()` will have the time component when it was constructed, and the other dates will be at midnight. If this is an issue you need to either truncate the time from your `new Date()`, or if you're more comfortable comparing Strings then format them all as YYYY-MM-DD and use `String.compare`. There are other questions and answers on SO that will be able to help with both of these tasks :) – Player One Apr 08 '19 at 07:21
  • Timestamp isn't really an issue, my main concern if just to make sure if the current date,,today is within the start date and end date, then the URL is valid. – Daredevil Apr 08 '19 at 07:22
  • @Daredevil then you should be fine with `new Date()`. Any time will be between midnight on the startDate and midnight on the endDate. – Player One Apr 08 '19 at 07:25
  • Hey let me take a test and if it works i'll mark your answer. – Daredevil Apr 08 '19 at 07:29
  • Thank you it worked. But i am surprised from debugging, that `currentdate` returns `Mon April 2019 ` and is able to be compared with start date and end date which is `yyyy-mm-dd` format. How can they be compared when they are different format? – Daredevil Apr 08 '19 at 07:48
  • If you're there, I posted another thread trying to perform sql injection on my program: https://stackoverflow.com/questions/55571337/java-how-to-perform-sql-injection-for-testing-purposes/55571521#55571521 and i couldn't inject to my program. can you advise? – Daredevil Apr 08 '19 at 10:41
  • @Daredevil It's confusing, but they actually aren't different formats - they are the same type of object. The dates can be *shown to the viewer* in different formats, but when you compare them in Java then they will be compared by the internal implementation of their `compareTo` method. – Player One Apr 08 '19 at 11:11
1

If you use strings instead of date objects, that if statement will work for that format, assuming you write it like (a <= b && b <= c)

Otherwise, I'm not sure how getSingleResult() can be cast into a Date object, as that could be anything, and you'd actually have to parse the value, then use proper Date methods to check isBefore and isAfter

Java: how do I check if a Date is within a certain range?

OneCricketeer
  • 179,855
  • 19
  • 132
  • 245
1

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.time

The Answer by Player One is correct, but can be improved by using the modern java.time classes rather than the terrible legacy classes (Date, etc.).

LocalDate

For a column of type SQL-standard DATE, use the LocalDate class. The LocalDate class represents a date-only value without time-of-day and without time zone or offset-from-UTC.

Time zone

A time zone is crucial in determining a date. For any given moment, the date varies around the globe by zone. For example, a few minutes after midnight in Paris France is a new day while still “yesterday” in Montréal Québec.

If no time zone is specified, the JVM implicitly applies its current default time zone. That default may change at any moment during runtime(!), so your results may vary. Better to specify your desired/expected time zone explicitly as an argument. If critical, confirm the zone with your user.

ZoneID

Specify a proper time zone name in the format of Continent/Region, such as America/Montreal, Africa/Casablanca, or Pacific/Auckland. Never use the 2-4 letter abbreviation such as EST or IST as they are not true time zones, not standardized, and not even unique(!).

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

If you want to use the JVM’s current default time zone, ask for it and pass as an argument. If omitted, the code becomes ambiguous to read in that we do not know for certain if you intended to use the default or if you, like so many programmers, were unaware of the issue.

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

Half-Open

if( file_getstartdate<= currentDate<= file_getendDate){

You will find your work easier if you consistently use the Half-Open approach to defining a span-of-time. In this approach, the beginning is inclusive while the ending is exclusive. So a month starts on the first and runs up to, but does not include, the first of the following month.

So your query logic would be, like the following, with <= & <.

if( file_getstartdate <= currentDate < file_getendDate)

This means in SQL, do not use BETWEEN for date-time work.

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

To retrieve DATE, values:

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

The same kind of half-open logic within Java code would look like the following code. Note that a shorter way of saying "is equal to or is later than" is "is not before".

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). 
;

Do not conflate date-time values with text

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

No it is not. The DATE type in MySQL stores a date by its own internally-defined mechanism, not plain text.

A date-time object such as DATE column in database or LocalDate in Java can parse a string input into a date value, and can generate a string from that date value. But the date value is not itself string. Do not confuse the two. In other words, a date value does not have “format”, only their textual representations have a format.

LocalDateRange

If doing much of this work, add the ThreeTen-Extra library to your project. This gives you access to the LocalDateRange class.

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

By default the LocalDateRange class works using Half-Open approach. But if you insist on your full-closed approach, override its default behavior with LocalDateRange.ofClosed method.


About java.time

The java.time framework is built into Java 8 and later. These classes supplant the troublesome old legacy date-time classes such as java.util.Date, Calendar, & SimpleDateFormat.

To learn more, see the Oracle Tutorial. And search Stack Overflow for many examples and explanations. Specification is JSR 310.

The Joda-Time project, now in maintenance mode, advises migration to the java.time classes.

You may exchange java.time objects directly with your database. Use a JDBC driver compliant with JDBC 4.2 or later. No need for strings, no need for java.sql.* classes.

Where to obtain the java.time classes?

The ThreeTen-Extra project extends java.time with additional classes. This project is a proving ground for possible future additions to java.time. You may find some useful classes here such as Interval, YearWeek, YearQuarter, and more.

Basil Bourque
  • 303,325
  • 100
  • 852
  • 1,154