2

I have put together a method that is supposed to check if a given date is within a certain lower and upper limit.

It shouldn't consider the time, only the dates. It seemed to be a quite simple task but I am getting some strange behavior when debugging it.

I have tried different approaches for removing the time value from the input dates. (inDate.) but they all seem to give me the same unexpected behavior of putting my inLeft date before inLeft and thus making the function result as false.

debugging screenshot

As you can see from the screenshot inLeft and inLeft are equal (if we ignore time) so why is int leftLimit = DateTimeComparator.getDateOnlyInstance().compare(inLeft, inDate); resulting in 1 rather that 0?

My conclusion is that I am doing something wrong but cannot find what. Please forgive me if this is something totally obvious.

Here is my Code:

protected boolean checkDateInRange(Date inDate, Date inLeft, Date inRight) {
    System.out.println("inDate = " + inDate);
    System.out.println("inLeft = " + inLeft);
    System.out.println("inRight = " + inRight);

    int leftLimit = DateTimeComparator.getDateOnlyInstance().compare(inLeft, inDate);
    int rightLimit = DateTimeComparator.getDateOnlyInstance().compare(inDate, inRight);

    System.out.println("leftLimit = " + leftLimit);
    System.out.println("rightLimit = " + rightLimit);

    if (leftLimit > 0 || rightLimit > 0) {
        return false;
    }
    return true;
}

I/System.out: inDate = Mon Sep 26 00:00:00 GMT+02:00 2016
I/System.out: inLeft = Mon Sep 26 20:14:13 GMT+02:00 2016
I/System.out: inRight = Mon Sep 26 00:00:00 GMT+02:00 2016
I/System.out: leftLimit = 1
I/System.out: rightLimit = 0

The problem is that leftLimit == 1, instead of 0, inDate and inLeft without the time are the same. but leftLimit still results 1.

Edit (Final Solution): According to sumandas' and Roberts solution i have written a new method that gives me the expected behaviour.

New code:

protected boolean checkDateInRange(Date inDate, Date inLeft, Date inRight) {
    SimpleDateFormat simpleDateFormatter = new SimpleDateFormat("yyyy-MM-dd");
    String dateString = simpleDateFormatter.format(inDate);
    String leftString = simpleDateFormatter.format(inLeft);
    String rightString = simpleDateFormatter.format(inRight);

    if (leftString.compareTo(dateString) > 0 || dateString.compareTo(rightString) > 0)
        return false;

    return true;
}

I still don't understand why my initial solution should not work.

2 Answers2

0

Try this:

String date1 = "2014/09/12"
String date2 = "2016/09/12"
SimpleDateFormat simpleDateFormatter = new SimpleDateFormat("yyyy-MM-dd");

Date inDate = simpleDateFormatter.parse(date1);
Date inLeft = simpleDateFormatter.parse(date2);
Date rightDate = simpleDateFormatter.parse(date2);

Pass of of these to the function as below and do the comparison:

 protected boolean checkDateInRange(Date inDate, Date inLeft, Date inRight) {

    if (inDate.before(inLeft) || inDate.after(inRight)) {
      // Do something
    }
}

My two cents, I tried using Joda Time earlier and due to too many if else I switched to simpledateformat, but joda time helps a lot in ISO date formats.

Why the proposed solution didn't work: If you read the documentation it says LHS < RHS returns -ve Else returns +ve

Now with your example, getDateOnlyInstance returns only date where

LHS = Sep 26 2016 = RHS and returns a 1. // Hope this helps.
sumandas
  • 555
  • 7
  • 20
  • Hi sumandas, yes this code works, however i have to convert my input `Date`s to Strings and then convert those strings back to `Date`s in the manner i posted in the edited question, is there a better way? – WiserTheBassist Aug 18 '16 at 18:01
  • Instead of parsing the formatted date you could directly use the string comparison. As the date is formatted in ISO style the order of the parsed date and the formatted date-string is identical. – Robert Aug 18 '16 at 18:13
  • So there is no 0 when LHS == LFS? Quoting from the docs: `'zero if order does not matter, negative value if lhsObj < rhsObj, positive value otherwise.'` I automatically expected `'order doesn't matter'` to be equivalent to `equals` but i am not english so forgive me if i am wrong – WiserTheBassist Aug 18 '16 at 18:37
  • Try one more thing: Store the values from getDateInstance() and check the values, I was going through their documentation, java classes: https://github.com/JodaOrg/joda-time/blob/master/src/main/java/org/joda/time/DateTimeComparator.java. Let us know how it goes. It seems the instances returned are not right. Try switching the values of inDate and inLeft and check what happens too. – sumandas Aug 18 '16 at 18:44
0

Based on the comments, I'm assuming you're using joda-time API (I used version 2.7 for this test).

To explain why your first attempt doesn't work, I've made a test. First, I created the equivalent DateTime objects for your test inputs:

// equivalent date/times in GMT+02:00
DateTime in = new DateTime(2016, 9, 26, 0, 0, 0, 0, DateTimeZone.forOffsetHours(2));
DateTime left = new DateTime(2016, 9, 26, 20, 14, 13, 0, DateTimeZone.forOffsetHours(2));
DateTime right = new DateTime(2016, 9, 26, 0, 0, 0, 0, DateTimeZone.forOffsetHours(2));

Then I called your method:

// using toDate to convert org.joda.time.DateTime to java.util.Date
checkDateInRangeWrong(in.toDate(), left.toDate(), right.toDate());

And the output I've got:

inDate = Sun Sep 25 19:00:00 BRT 2016
inLeft = Mon Sep 26 15:14:13 BRT 2016
inRight = Sun Sep 25 19:00:00 BRT 2016

Note that inDate's day is 25 (instead of 26), and time is 19 (instead of 00). Also note that the timezone is BRT (instead of GMT+02:00).

This happens because when DateTimeComparator.compare() is called with java.util.Date instances, it uses the default timezone to convert the objects.

As my default timezone (java.util.TimeZone.getDefault()) is "America/Sao_Paulo" (or BRT - Brazil Standard Time), the date/time 26/09 at 00:00 GMT+02:00 is converted to 25/09 at 19:00 in BRT.

So the code is actually comparing leftDate (26/09) with inDate (25/09), and that's why leftLimit is 1.

Depending on how you're creating the variables inDate, leftDate and rightDate, there might be variations during the day (so the code might work during only some part of the day, for example). And there could also be differences during Daylight Saving Time (when hours move forward or back 1 hour), which can also cause a shift in the day.

And your new code (using SimpleDateFormat) also doesn't work for me (it has the same problem, as SimpleDateFormat also uses the default timezone) - and the same error occurs if I create the Date objects with java.util.Calendar instead of using DateTime.toDate().

So, to solve this, I've used org.joda.time.LocalDate class (a date without the time fields), because I just need to compare the dates (day/month/year) and ignore the time (hour/minute/second). I used the constructor that receives the timezone, so I don't depend on the system's default and I can be sure that I'm always working on the same timezone.

protected boolean checkDateInRange(Date inDate, Date inLeft, Date inRight) {
    // convert inputs to LocalDate, using the specified timezone
    LocalDate left = new LocalDate(inLeft, DateTimeZone.forOffsetHours(2));
    LocalDate right = new LocalDate(inRight, DateTimeZone.forOffsetHours(2));
    LocalDate date = new LocalDate(inDate, DateTimeZone.forOffsetHours(2));
    System.out.println("inDate = " + date);
    System.out.println("inLeft = " + left);
    System.out.println("inRight = " + right);

    int leftLimit = left.compareTo(date);
    int rightLimit = date.compareTo(right);
    System.out.println("leftLimit = " + leftLimit);
    System.out.println("rightLimit = " + rightLimit);
    if (leftLimit > 0 || rightLimit > 0) {
        return false;
    }

    return true;
}

Now calling:

checkDateInRange(in.toDate(), left.toDate(), right.toDate());

produces the output:

inDate = 2016-10-26
inLeft = 2016-10-26
inRight = 2016-10-26
leftLimit = 0
rightLimit = 0
true

Notes:

  • As you're using joda-time, you can make a method that receives a DateTime object (or even better, a LocalDate), instead of using the old error-prone java.util.Date API. Changing to a LocalDate is better because it's making explicit that the method doesn't need the time fields:

    protected boolean checkDateInRange(LocalDate inDate, LocalDate inLeft, LocalDate inRight) {
        System.out.println("inDate = " + inDate);
        System.out.println("inLeft = " + inLeft);
        System.out.println("inRight = " + inRight);
    
        int leftLimit = inLeft.compareTo(inDate);
        int rightLimit = inDate.compareTo(inRight);
        System.out.println("leftLimit = " + leftLimit);
        System.out.println("rightLimit = " + rightLimit);
        if (leftLimit > 0 || rightLimit > 0) {
            return false;
        }
    
        return true;
    }
    
    // in, left and right are DateTime instances
    checkDateInRange(in.toLocalDate(), left.toLocalDate(), right.toLocalDate());
    
  • I'm assuming that DateTimeZone.forOffsetHours(2) is the timezone for all your inputs. If it's not the case, I recommend to handle each case accordingly and extract the LocalDate (using the constructor LocalDate(dateObject, DateTimeZone) described above) before doing any comparison.