7

Calculating the number of days between 1900-01-01 and a date after 1918-03-24 using Joda-Time seems to give an off-by-one result.

Using Java 8 java.time gives the correct result. What is the reason for Joda-Time not counting 1918-03-25?

Using Joda-time v2.9.9.

public static void main(String[] args) {
    jodaDiff("1918-03-24");
    javaDiff("1918-03-24");
    jodaDiff("1918-03-25");
    javaDiff("1918-03-25");
    jodaDiff("1918-03-26");
    javaDiff("1918-03-26");
    jodaDiff("2017-10-10");
    javaDiff("2017-10-10");
}
private static void jodaDiff(String date) {
    DateTime start = new DateTime(1900, 1, 1, 0, 0, 0, DateTimeZone.forID("UTC"));
    DateTimeFormatter dateDecoder = DateTimeFormat.forPattern("YYYY-MM-dd");
    DateTime end = dateDecoder.parseDateTime(date);
    int diff =  Days.daysBetween(start, end).getDays();
    System.out.println("Joda " + date + " " + diff);
}
private static void javaDiff(String date) {
    LocalDate start = LocalDate.parse("1900-01-01");
    LocalDate end = LocalDate.parse(date);
    int diff =  (int) ChronoUnit.DAYS.between(start, end);
    System.out.println("Java " + date + " " + diff + "\n");
}

Output:

Joda 1918-03-24 6656
Java 1918-03-24 6656

Joda 1918-03-25 6656
Java 1918-03-25 6657

Joda 1918-03-26 6657
Java 1918-03-26 6658

Joda 2017-10-10 43015
Java 2017-10-10 43016

Chro
  • 1,003
  • 2
  • 15
  • 27
  • Using Java 8? and Using Joda-time v2.9.9?, Before that you need implement any third party library after fully understanding how It's works. I don't find any issue Using Java 8? and Using Joda-time v2.9.9, Except your exceptional implementation post over stack overflow –  Oct 10 '17 at 10:05

4 Answers4

10

The problem is that your DateTimeFormatter is using the system default time zone. Ideally, you should parse to LocalDate values instead of DateTime, but you can fix it by using UTC for the formatter anyway:

DateTimeFormatter dateDecoder = DateTimeFormat.forPattern("YYYY-MM-dd").withZoneUTC();

To parse with LocalDate instead, just use:

org.joda.time.LocalDate start = new org.joda.time.LocalDate(1900, 1, 1);
DateTimeFormatter dateDecoder = DateTimeFormat.forPattern("YYYY-MM-dd");        
org.joda.time.LocalDate end = dateDecoder.parseLocalDate(date);

(Obviously you don't need to fully-qualify it if you're not using Java 8.)

Jon Skeet
  • 1,421,763
  • 867
  • 9,128
  • 9,194
  • Still doesn't explain why 1918-03-25, and only that day, is messed up in the original. Changing the start date to any time before that date still results in the same issue. Thanks for the fix though. – Chro Oct 10 '17 at 11:30
  • 2
    @Chro: Well we don't know what time zone you're using, which doesn't help - but the UK DST transition in 1918 was on March 24th, which is probably relevant. If you ask for a date in December 1918, the two results match, and in June 1919 they don't again (at least on my machine). – Jon Skeet Oct 10 '17 at 11:37
  • Ah yes, UK. So that must mean that when the UK transitioned that 1 hour was lost forever (or something). – Chro Oct 10 '17 at 12:05
  • 1
    @Chro I've posted [an answer](https://stackoverflow.com/a/46666582/7605325) below with more details about that. –  Oct 10 '17 at 12:19
4

@Jon Skeet's answer is correct and direct to the point. I'd just like to add more details about what's happening and why you get these results (as you asked in the comments - and Jon also replied with a hint, that's also correct).

Your JVM default timezone is probably Europe/London (or any other that has a DST change in March 24th 1918). You can check that by using DateTimeZone.getDefault() in Joda-Time and ZoneId.systemDefault() in Java 8.

The start date you created in Joda is 1900-01-01T00:00Z (January 1st 1900 at midnight in UTC). The end date, though, is created only by using the year, month and day. But the DateTime also needs the time (hour/minute/second/millisecond) and a timezone. As those are not specified, it's set to midnight at the JVM default timezone (which is not guaranteed to be UTC - depending on the JVM configuration, you can get a different result).

Assuming your default timezone is London (that's how I could reproduce the problem - in my JVM default timezone (America/Sao_Paulo) it doesn't happen). In March 25th 1981, London was in DST, so when you create your end date with 1918-03-25, the result is March 25th 1981 at midnight in London timezone - but due to DST change, the result is 1918-03-25T00:00+01:00 - during DST, London uses the offset +01:00, which means it's one hour ahead of UTC (so this end date is equivalent to 1918-03-24T23:00Z - or March 24th 1981 at 11 PM in UTC).

So, the difference in hours is 159767, which is not enough to complete 6657 days, so the difference is 6656 days (the rounding is always to the lowest value - the difference must be at least 159768 hours to complete 6657 days).

When you use a LocalDate, though, the time and DST effects are not considered (a LocalDate has only day, month and year), and you get the correct difference. If you set the end date to UTC, as well, you also get the correct results because UTC doesn't have DST changes.


By the way, if you use Java 8 ZonedDateTime, and use the start date with UTC and end date with London timezone (instead of using a LocalDate), you get the same difference in the results.


Not directly related, but in Joda-Time you can use the constant DateTimeZone.UTC to refer to UTC - calling forID("UTC") is redundant, as it returns the constant anyway (DateTimeZone.forID("UTC")==DateTimeZone.UTC returns true).

0

Joda-time off-by-one error when counting days after 1918-03-24

Try this, I corrected your program It gives you excepted answer with comparison with Java

public static void main(String[] args) {

    jodaDiff("1918-03-24");
    javaDiff("1918-03-24");
    jodaDiff("1918-03-25");
    javaDiff("1918-03-25");
    jodaDiff("1918-03-26");
    javaDiff("1918-03-26");
    jodaDiff("2017-10-10");
    javaDiff("2017-10-10");
}

private static void jodaDiff(String date) {
    DateTime start = new DateTime(1900, 1, 1, 0, 0, 0, DateTimeZone.forID("UTC"));
    DateTime end = new DateTime(date);
    int diff = Days.daysBetween(start.toLocalDate(), end.toLocalDate()).getDays();
    System.out.println("Joda " + date + " " + diff);
}

private static void javaDiff(String date) {
    LocalDate start = LocalDate.parse("1900-01-01");
    LocalDate end = LocalDate.parse(date);
    int diff = (int) ChronoUnit.DAYS.between(start, end);
    System.out.println("Java " + date + " " + diff + "\n");
}
0
DateTime start = new DateTime(1900, 1, 1, 0, 0, 0, DateTimeZone.forID("UTC"));
DateTimeFormatter dateDecoder = DateTimeFormat.forPattern("YYYY-MM-dd").withZone(DateTimeZone.forID("UTC"));

When you are creating first date, you are fixing the zone, with the second you don't. So either remove the zone, or set the same in both places.

You could also convert both DateTime to LocalDate, it also works for this example, but first solution should be a better case.

Beri
  • 11,470
  • 4
  • 35
  • 57