1

following problem is bothering me and I can't find any reasonable explanation and a solution. Perhaps someone could enlighten me.

I have an application that calculates date difference (in days) - in a Search mask and in a Details mask. In the first mask I use days to filter out records with a duration search criteria (Search mask) and I do this with an SQL query:

WHERE...
...
AND DECODE(TRUNC(to_number(SUBSTR((close_date - 
create_date),1,instr(close_date - create_date,' ')))), NULL, 
TRUNC(to_number(SUBSTR((systimestamp - create_date),1,instr(systimestamp - 
create_date,' ')))), TRUNC(to_number(SUBSTR((close_date - 
create_date),1,instr(close_date - create_date,' ')))) ) >=140

AND DECODE(TRUNC(to_number(SUBSTR((close_date - 
create_date),1,instr(close_date - create_date,' ')))), NULL, 
TRUNC(to_number(SUBSTR((systimestamp - create_date),1,instr(systimestamp - 
create_date,' ')))), TRUNC(to_number(SUBSTR((close_date - 
create_date),1,instr(close_date - create_date,' ')))) ) <=140

In this special case I try to find out all the records that have a duration of 140 days.

In the second mask (Details) I show the record details, including its duration. This is done with the following java code:

public static Integer getDuration(Date caseDate, Date closeDate) {
    Date beginDate = caseDate;
    Date endDate = (closeDate != null)? closeDate: new Date();

    long difference = endDate.getTime() - beginDate.getTime();   
    int daysDiff = (int) (difference / (24.0 * 60 * 60 * 1000));  
    return new Integer(daysDiff);
}

My problem is that when I search, I find some records that do correspond to the search criteria. For instance I find 4 records and all of them have a duration of 140 days. That's according to Oracle. But when I open the Details mask for some of them I get a duration of 139 days for example. So somehow Java and Oracle calculate date differences in a different way. It seems that in Oracle some rounding is being made, but I can't find where this happens. So any suggestions would be helpful. Thanks!

Greets, almaak

al.
  • 484
  • 11
  • 30
  • Isn't there a more elegant way to calculate the difference in Oracle? Can't you do exactly what you do in Java: substract the dates from each other? – Janick Bernet Jun 23 '10 at 09:18
  • That's pretty much the most elegant way I think. When I substract I get something like 140 0:0:0.0 or 140 10:13:15.54734. The substr-part is used to extract only the days from the result. – al. Jun 23 '10 at 09:56
  • Can you give us an example of dates which Oracle and Java evaluate differently? Might help us pin it down. – Bob Jarvis - Слава Україні Jun 23 '10 at 11:28
  • create_date - 11.02.09 00:00:00,000000000 close_date - 01.07.09 00:00:00,000000000 systimestamp - 23.06.10 14:05:35,927570000 +02:00 – al. Jun 23 '10 at 12:07

3 Answers3

3

The date could be the same but the time could be different. Resulting in 139 days when you calculate through milliseconds. (java)

I suggest not using millis but use the days to calculate.

Something like

public long daysBetween(Calendar startDate, Calendar endDate) {
   Calendar date = (Calendar) startDate.clone();
   long daysBetween = 0;
   while (date.before(endDate)) {
      date.add(Calendar.DAY_OF_MONTH, 1);
      daysBetween++;
   }}

or

/**
     *
     * @param c1 from
     * @param c2 to
     * @return amount of days between from and to
     */
    public int diff(Calendar c1, Calendar c2) {
        int years = c2.get(Calendar.YEAR) - c1.get(Calendar.YEAR);
        if (years == 0) {
            return c2.get(Calendar.DAY_OF_YEAR) - c1.get(Calendar.DAY_OF_YEAR);
        } else {
            Calendar endOfYear =  Calendar.getInstance();
            endOfYear.set(Calendar.YEAR, c1.get(Calendar.YEAR));
            endOfYear.set(Calendar.MONTH, 11);
            endOfYear.set(Calendar.DAY_OF_MONTH, 31);
            int days = endOfYear.get(Calendar.DAY_OF_YEAR) - c1.get(Calendar.DAY_OF_YEAR);
            for (int i=1;i <years;i++) {
                endOfYear.add(Calendar.YEAR, 1);
                days += endOfYear.get(Calendar.DAY_OF_YEAR);
            }
            days += c2.get(Calendar.DAY_OF_YEAR);
            return days;
        }
    }

Side note: The first example is slightly slower then the second, but if it's only for small differences it's neglectible.

Redlab
  • 3,110
  • 19
  • 17
  • Do you suggest some rounding in Java? – al. Jun 23 '10 at 09:57
  • Don't use while/for loops for Date Diff calculation - use something from this page: http://stackoverflow.com/questions/9767662/difference-between-two-particular-dates – nikib3ro Aug 24 '12 at 05:21
1

I calculated this same way but I used dayDiff as long not as integer. So try it also, don't cast it. It should work fine.

Xorty
  • 18,367
  • 27
  • 104
  • 155
  • I didn't quite understand your comment. How would that affect "difference"? – al. Jun 23 '10 at 09:59
  • it would not, only if the result of the calculation was higher then Integer.MAX_VALUE – Redlab Jun 23 '10 at 10:29
  • well I am not sure. I just checked my sources and it worked that way :O Anyway, Calendar solution wins! – Xorty Jun 23 '10 at 10:32
  • I haven't tried the Calendar solution yet. What I've tried however is to put the days into double in Java. The result was - 139.95833333333334. So what happens, in my opinion is, that the casting is to blame. What do you think? – al. Jun 23 '10 at 11:11
  • those milliseconds are just treacherous :D But still, 139.9583 isn't 140. So java shouldn't be wrong, Oracle should be. Or give a try to that Calendar solution, it's much cleaner and you'll see the results. – Xorty Jun 23 '10 at 12:21
  • I have a curious problem now, though. Now I get records in the result list, that has a duration of 141 days. Stupid time! :-/ It's getting round up somewhere. – al. Jun 24 '10 at 07:39
1

The problem here is that you think both columns are DATE columns, where at least one of the two is really a TIMESTAMP column. When you extract one date from another, you get a NUMBER. But when you extract a date from a timestamp, or vice versa, you get an INTERVAL.

An example

A table with a DATE and a TIMESTAMP:

SQL> create table mytable (close_date,create_date)
  2  as
  3  select date '2010-11-01', systimestamp from dual union all
  4  select date '2010-11-11', systimestamp from dual union all
  5  select date '2010-12-01', systimestamp from dual
  6  /

Table created.

SQL> desc mytable
 Name                                      Null?    Type
 ----------------------------------------- -------- ----------------------------
 CLOSE_DATE                                         DATE
 CREATE_DATE                                        TIMESTAMP(6) WITH TIME ZONE

Extract a TIMESTAMP from a DATE column, leads to an INTERVAL:

SQL> select close_date - create_date
  2    from mytable
  3  /

CLOSE_DATE-CREATE_DATE
---------------------------------------------------------------------------
+000000130 11:20:11.672623
+000000140 11:20:11.672623
+000000160 11:20:11.672623

3 rows selected.

And there is no need to fiddle with TO_NUMBER's and SUBSTR's. Just use the EXTRACT function to get the component you want from an interval:

SQL> select extract(day from (close_date - create_date))
  2    from mytable
  3  /

EXTRACT(DAYFROM(CLOSE_DATE-CREATE_DATE))
----------------------------------------
                                     130
                                     140
                                     160

3 rows selected.

Regards, Rob.

And here is an example with two TIMESTAMPS and which shows that INTERVAL's are truncated, not rounded:

SQL> create table mytable (close_date,create_date)
  2  as
  3  select to_timestamp('2010-11-01','yyyy-mm-dd'), trunc(systimestamp,'dd') + interval '6' hour from dual union all
  4  select to_timestamp('2010-11-11','yyyy-mm-dd'), trunc(systimestamp,'dd') + interval '12' hour from dual union all
  5  select to_timestamp('2010-12-01','yyyy-mm-dd'), trunc(systimestamp,'dd') + interval '18' hour from dual
  6  /

Table created.

SQL> desc mytable
 Name                                      Null?    Type
 ----------------------------------------- -------- ----------------------------
 CLOSE_DATE                                         TIMESTAMP(9)
 CREATE_DATE                                        DATE

SQL> select close_date - create_date
  2    from mytable
  3  /

CLOSE_DATE-CREATE_DATE
---------------------------------------------------------------------------
+000000130 18:00:00.000000000
+000000140 12:00:00.000000000
+000000160 06:00:00.000000000

3 rows selected.

SQL> select extract(day from (close_date - create_date))
  2    from mytable
  3  /

EXTRACT(DAYFROM(CLOSE_DATE-CREATE_DATE))
----------------------------------------
                                     130
                                     140
                                     160

3 rows selected.
Rob van Wijk
  • 17,555
  • 5
  • 39
  • 55
  • I've checked and both are timestamp columns - close_date and create_date. I've also used the extract function. That didn't affect the result. What I suspect is that Oracle makes some rounding of the days. – al. Jun 23 '10 at 11:08
  • I just checked that with two TIMESTAMPS, the result is still the same. Oracle doesn't round the days, it truncates it when using the EXTRACT function. See the updated answer. – Rob van Wijk Jun 23 '10 at 13:20
  • Yep, that was also my result yesterday. and that seems to be also the problem now, because the Calender solution seems to be rounding the days up. So now I get results with 141 days, when I search with <=140 and =>140. – al. Jun 24 '10 at 07:52