Hot questions for Using Joda-Time in date difference

Top Java Programmings / Joda-Time / date difference

Question:

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


Answer:

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

Question:

How can I find the difference between two dates in JDK7, such that the difference can be added back to make the two dates equal?

I tried to use the solution from a similar StackOverflow question, subtracting one date's milliseconds from the other, but this can result in an incorrect difference between dates.

In this example, I try to make a equal to b by setting a = (b-a) + a

private void makeTwoDatesEqual(GregorianCalendar a, GregorianCalendar b, DatatypeFactory datatypeFactory) {
    long b_minus_a = b.getTimeInMillis() - a.getTimeInMillis();
    Duration delta = datatypeFactory.newDuration(b_minus_a);
    delta.addTo(a);
}

But the results are unexpected:

Before

a = "2015-08-29T00:00:00.000Z"
b = "2040-01-01T00:00:00.000Z"
delta = "P24Y4M5DT0H0M0.000S" // 2 days longer than it should be.

After

a = "2040-01-03T00:00:00.000Z"
b = "2040-01-01T00:00:00.000Z"
delta = "P24Y4M5DT0H0M0.000S"
a.equals(b) = false

Joda-Time has this same issue:

private void makeTwoDatesEqual(GregorianCalendar a, GregorianCalendar b) {
    DateTime date1 = new DateTime(a);
    DateTime date2 = new DateTime(b);
    Interval interval = new Interval(a.getTimeInMillis(), b.getTimeInMillis());
    Period offsetPeriod = interval.toPeriod();
    date1 = date1.plus(offsetPeriod);
    date1.equals(date2); // false in my example
}

Before

date1 = "2015-08-29T00:00:00.000-05:00"
date2 = "2040-01-01T00:00:00.000-05:00"
offsetPeriod = "P24Y4M2DT23H"
date1.equals(date2) = false

After

date1 = "2039-12-31T23:00:00.000-05:00"
date2 = "2040-01-01T00:00:00.000-05:00"
offsetPeriod = "P24Y4M2DT23H"
date1.equals(date2) = false

Before it's asked, there should be no need to account for daylight savings or leap years because in each example I'm adding the interval back over the exact same time period from which it was derived.


Answer:

ThreeTen Backport

There are a couple of possibilities using the modern Java date and time API known as JSR-310 or java.time. Can you use this with Java 7? Certainly! Get ThreeTen Backport and start coding. :-)

Difference between date-times

EDIT: If you want to perceive your a and b as points in time, in other words, both dates and times of day, you may use either a suitably small time unit for the difference, or the Duration class. Both will work the same. Since Instants have nanosecond precision, calculating a difference in nanos will make sure we don’t lose any precision:

    Instant a = Instant.parse("2015-08-29T00:00:00.000Z");
    Instant b = Instant.parse("2040-01-01T00:00:00.000Z");
    long delta = ChronoUnit.NANOS.between(a, b);
    a = a.plusNanos(delta);
    System.out.println("a = " + a);
    System.out.println("b = " + b);
    System.out.println("delta = " + delta);
    System.out.println("a.equals(b) = " + a.equals(b));

This prints

a = 2040-01-01T00:00:00Z
b = 2040-01-01T00:00:00Z
delta = 768182400000000000
a.equals(b) = true

The Duration class is meant for durations in hours, minutes, seconds and fraction of second. You can use it for longer durations, but it doesn’t know about weeks, months or years.

    Duration delta = Duration.between(a, b);
    a = a.plus(delta);

Otherwise as before. This time the output is

a = 2040-01-01T00:00:00Z
b = 2040-01-01T00:00:00Z
delta = PT213384H
a.equals(b) = true

So the difference is 213384 hours. Only because the minutes and seconds are 0 in this case, they are not printed, otherwise they would be; Duration too has nanosecond precision.

Difference between dates

Taking your title more literally and noticing that your example dates fall on midnight UTC. If you don’t care about the time of day and only want to count days, months and years, you may use either the Period class or simply count days. The Period class is for periods of days, months and years and has 1 day precision (that means, cannot be used for hours and smaller units).

    LocalDate a = LocalDate.of(2015, Month.AUGUST, 29);
    LocalDate b = LocalDate.of(2040, Month.JANUARY, 1);
    Period delta = Period.between(a, b);
    a = a.plus(delta);

The printing goes as before, but now it prints

a = 2040-01-01
b = 2040-01-01
delta = P24Y4M3D
a.equals(b) = true

Counting days goes just like nanoseconds above, only with days instead:

    long delta = ChronoUnit.DAYS.between(a, b);
    a = a.plusDays(delta);

This time the output contains:

delta = 8891
a.equals(b) = true

Please note that because months haven’t got the same length, even though 24 years 4 months 3 days seems to equal 8891 days, this will more often not be the exact case. This is explained in more detail in @Meno Hochschild’s comment and Hugo’s answer. So the choice between a Period and a number of days will make a difference, and you should choose based on your more exact requirements.

Combining Period and Duration

It’s a bit funny that Java doesn’t offer a class for a period or duration of years, months, days, hour, minutes and seconds. If you wanted, you might use a Period instance for the years, months and days, and then a Duration for the hours, minutes and seconds. It’s a little bit complicated, so I am not writing the code unless you say you need it, but it is viable. You may of course also work out the details yourself.

I am told that the ThreeTen-extra project includes a PeriodDuration class that combines the two. I haven’t got any experience with it myself.

Question:

I have a requirement to get the amount of months between two DateTime objects and then get the number of days left over.

Here is how I get the months between:

Months monthsBetween = Months.monthsBetween(dateOfBirth,endDate);

I am unsure how to then find out how many days are left over into the next month. I have tried the following:

int offset = Days.daysBetween(dateOfBirth,endDate)
              .minus(monthsBetween.get(DurationFieldType.days())).getDays();

But this does not have the desired effect.


Answer:

Use a org.joda.time.Period:

// fields used by the period - use only months and days
PeriodType fields = PeriodType.forFields(new DurationFieldType[] {
        DurationFieldType.months(), DurationFieldType.days()
    });
Period period = new Period(dateOfBirth, endDate)
    // normalize to months and days
    .normalizedStandard(fields);

The normalization is needed because the period usually creates things like "1 month, 2 weeks and 3 days", and the normalization converts it to "1 month and 17 days". Using the specific DurationFieldType's above also makes it convert years to months automatically.

Then you can get the number of months and days:

int months = period.getMonths();
int days = period.getDays();

Another detail is that when using DateTime objects, the Period will also consider the time (hour, minute, secs) to know if a day has passed.

If you want to ignore the time and consider only the date (day, month and year), don't forget to convert them to LocalDate:

// convert DateTime to LocalDate, so time is ignored
Period period = new Period(dateOfBirth.toLocalDate(), endDate.toLocalDate())
    // normalize to months and days
    .normalizedStandard(fields);

Question:


Answer:

Use this

 Days.daysBetween(intdt.toLocalDate(), targetDt.toLocalDate()).getDays() 

The LocalDate class does not store or represent a time or time-zone. Instead, it is a description of the date.

Question:

DateTimeZone timeZone = DateTimeZone.forID( "America/Montreal" );

DateTimeFormatter formatter = DateTimeFormat.forPattern( "yyyy/MM/dd" ).withZone( timeZone ); 
DateTime dateTimeStart = formatter.parseDateTime("2012/01/01");
DateTime dateTimeStop = formatter.parseDateTime("2017/06/12");
Period period = new Period( dateTimeStart, dateTimeStop );
PeriodFormatter periodFormatter = PeriodFormat.getDefault();

String output = periodFormatter.print( period);
System.out.println(output);

Actual Output is: 5 years, 5 months, 1 week and 4 days I want output (Recommended) : 5 years, 5 months, 11 days


Answer:

If you read the manual...

Period period = new Period(dateTimeStart, dateTimeStop, PeriodType.forFields(
        new DurationFieldType[]{
                DurationFieldType.years(),
                DurationFieldType.months(),
                DurationFieldType.days(),
                DurationFieldType.hours(),
                DurationFieldType.minutes(),
                DurationFieldType.seconds(),
                DurationFieldType.millis(),
        }));