Joda-Time: What's the Difference Between Period, Interval and Duration

Joda-Time: what's the difference between Period, Interval and Duration?

3 classes are needed because they represent different concepts so it is a matter of picking the appropriate one for the job rather than of relative performance. From the documentation with comments added by me in italics:


An interval in Joda-Time represents an interval of time from one millisecond instant to another instant. Both instants are fully specified instants in the datetime continuum, complete with time zone. Specific times are defined e.g. this might be the interval between 20:00:00GMT yesterday and 09:00:00GMT this morning.

A duration in Joda-Time represents a duration of time measured in milliseconds. The duration is often obtained from an interval. i.e. we can subtract start from end of an interval to derive a duration

A period in Joda-Time represents a period of time defined in terms of fields, for example, 3 years 5 months 2 days and 7 hours. This differs from a duration in that it is inexact in terms of milliseconds. A period can only be resolved to an exact number of milliseconds by specifying the instant (including chronology and time zone) it is relative to. e.g. consider the period of 1 year, if we add this to January 1st we will always arrive at the next January 1st but the duration will depend on whether the intervening year is a leap year or not. Similarly if we add 1 month to the 1st of a month then we will arrive at the 1st of the next month but the duration (in milliseconds) will vary based on the month in question


For question 3, A specific method to divide a duration is not really needed because we can always get the number of milliseconds from the duration as a long (using getMillis()), divide it and construct a new duration (using new Duration(long duration)).

Dividing a period doesn't really have a real meaning based on the definition of a period above. e.g. what is half a month? (its length would depend on which month).

Joda Time minutes in a duration or interval

The problem is that you're not specifying the period type in the period constructor - so it's using the default of "years, months, weeks, days, hours, minutes, seconds and millis". You're only seeing 15 minutes because you're not asking for hours, which would return -18.

If you only want days and minutes, you should specify that:

PeriodType type = PeriodType.forFields(new DurationFieldType[] {
DurationFieldType.days(),
DurationFieldType.minutes()
});

Period period = new Period(currentDate, date, type);
// Now you'll just have minutes and days

It's important to understand the difference between a Duration which is "a certain number of milliseconds, which can be fetched according to different units" and a Period which is effectively a mapping from a set of field types (minutes, months, days etc) to values. There isn't one single time value in a period - it's a collection of values.

Joda Time Interval between two dates including time zone

You should spend some time (no pun intended) to grasp the Jodatime concepts (eg) if you need to do time calculations involving different timezones.

For example, consider the following code

DateTime nowHere = DateTime.now();
DateTime nowZur = nowHere.withZone(DateTimeZone.forID("Europe/Zurich"));
System.out.println("now Here: " + nowHere );
System.out.println("now Zurich: " + nowZur );

This outputs (for me, 4 hours offset from Zurich) :

now Here:   2013-01-27T10:19:24.460-03:00
now Zurich: 2013-01-27T14:19:24.460+01:00

Pause and try to guess the output of the following lines:

Interval interv = new Interval(nowHere, nowZur);
System.out.println("Interval: " + interv.toDurationMillis());

The above prints 0 (zero). As it should.

Because nowHere and nowZur represent the same instant of time (in the physical line time), as represented in two different countries. They differ only in how they are represented, but physically they are the same time point (like 2.54 cm and 1 in are the same length, represented in two different forms).

Likewise, 2013-01-27T10:19:24.460-03:00 and 2013-01-27T14:19:24.460+01:00 are the same instant, the moment in which I run that code, only represented according the conventions of different countries; a martian could represent that same instant with his own martian calendar, and it would still be the same instant.

As those DateTimes represent the same time point, the physical interval (duration) between them must be zero. Now, if you want to get the "civil time difference" between the two, that's a wholly different thing. The LocalDateTime and the Period (totally different from DateTime and Duration) would then be the right concepts. For example:

Period per=new Period(nowHere.toLocalDateTime(),nowZur.toLocalDateTime());
System.out.println(per.toStandardSeconds().getSeconds());

This prints 14400 for me (4 "standard" -not physical- hours)

Number of Periods in an Interval in Joda Time

Here's my preferred solution: use average length of a period to form a best guess and then refine it. This seems the most efficient and elegant way to do it.

import com.google.common.base.Function;
import com.google.common.collect.ImmutableMap;
import org.joda.time.*;

import static com.google.common.collect.FluentIterable.from;
import static java.util.Arrays.asList;
import static org.joda.time.DurationFieldType.*;

public class PeriodArithmetic {

public static long periodsInAnInterval(Interval interval, Period period) {
int bestGuess = (int) (interval.toDurationMillis() / toAverageMillis(period));
if (bestGuess < 0) return 0;
if (startPlusScaledPeriodIsAfterEnd(interval, period, bestGuess + 1)) {
return searchDownwards(interval, period, bestGuess);
} else {
return searchUpwards(interval, period, bestGuess);
}
}

private static long searchDownwards(Interval interval, Period period, int currentGuess) {
if (startPlusScaledPeriodIsAfterEnd(interval, period, currentGuess)) {
return searchDownwards(interval, period, currentGuess - 1);
} else {
return currentGuess;
}
}

private static long searchUpwards(Interval interval, Period period, int currentGuess) {
if (!startPlusScaledPeriodIsAfterEnd(interval, period, currentGuess + 1)) {
return searchUpwards(interval, period, currentGuess + 1);
} else {
return currentGuess;
}
}

private static boolean startPlusScaledPeriodIsAfterEnd(Interval interval, Period period, int scalar) {
return interval.getStart().plus(period.multipliedBy(scalar)).isAfter(interval.getEnd());
}

private static final long MILLIS_IN_DAY = Days.ONE.toStandardSeconds().getSeconds() * 1000L;
private static final long MILLIS_IN_YEAR = Days.ONE.toStandardSeconds().getSeconds() * 365250L;

private static final ImmutableMap<DurationFieldType, Long> averageLengthMillis
= ImmutableMap.<DurationFieldType, Long>builder()
.put(millis(), 1L)
.put(seconds(), 1000L)
.put(minutes(), Minutes.ONE.toStandardSeconds().getSeconds() * 1000L)
.put(hours(), Hours.ONE.toStandardSeconds().getSeconds() * 1000L)
.put(halfdays(), MILLIS_IN_DAY / 2)
.put(days(), MILLIS_IN_DAY)
.put(weeks(), Weeks.ONE.toStandardSeconds().getSeconds() * 1000L)
.put(months(), MILLIS_IN_YEAR / 12)
.put(years(), MILLIS_IN_YEAR)
.put(weekyears(), MILLIS_IN_YEAR)
.put(centuries(), MILLIS_IN_YEAR * 100)
.put(eras(), Long.MAX_VALUE)
.build();

private static long toAverageMillis(Period period) {
final Iterable<Long> milliValues = from(asList(period.getFieldTypes())).transform(toAverageMillisForFieldType(period));
return total(milliValues);
}

private static Function<DurationFieldType, Long> toAverageMillisForFieldType(final Period period) {
return new Function<DurationFieldType, Long>() {
@Override
public Long apply(DurationFieldType durationFieldType) {
final Long averageDuration = averageLengthMillis.get(durationFieldType);
return period.get(durationFieldType) * averageDuration;
}
};
}

private static long total(Iterable<Long> milliValues) {
long acc = 0;
for (Long milliValue : milliValues) {
acc += milliValue;
}
return acc;
}
}

How to calculate difference between two dates in years...etc with Joda-Time

Period gives you this out of the box.

Period period = new Period(d1, d2);
System.out.print(period.getYears() + " years, ");
System.out.print(period.getMonths() + " months, ");
// ...

To prettify and get a little more control over the output, you can use a PeriodFormatterBuilder.

Calculating the difference between two Java date instances

The JDK Date API is horribly broken unfortunately. I recommend using Joda Time library.

Joda Time has a concept of time Interval:

Interval interval = new Interval(oldTime, new Instant());

EDIT: By the way, Joda has two concepts: Interval for representing an interval of time between two time instants (represent time between 8am and 10am), and a Duration that represents a length of time without the actual time boundaries (e.g. represent two hours!)

If you only care about time comparisions, most Date implementations (including the JDK one) implements Comparable interface which allows you to use the Comparable.compareTo()

How to represent any duration or period in Joda time?

There's this tempting Period class... Just don't set date fields on it, only time fields.

Or there's LocalTime, which represents a time of day.


Based on your edit, it sounds like a combination of LocalTime plus a number indicating how long the slot is (in minutes or whatever units make sense to you). So you might have a ScheduleEntry class which has LocalTime member for when the schedule entry starts, and an int for how long it lasts.

Joda-time Period doesn't seem to calculate days correctly

Turns out that Joda-time is wicked smaaaaart. I guess that it can't know the number of hours in a day because of daylight savings time and other timezone issues. There might be 23 or 25 hours in any particular day for example.

To force it to a 24 hours/day, you need to specific a Chronology that is consistent about hours per day.

long durationMillis = 26 * 3600 * 1000;
Period period = new Period(durationMillis, PeriodType.dayTime(),
ISOChronology.getInstanceUTC());
// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ this is needed to have 1 day == 24 hours
// this works!
assertEquals(1, period.getDays());
// this works!
assertEquals(2, period.getHours());

Joda Time - repeating/weekly Interval

There is no such option in Joda Time because all the relevant classes would either contain full information about time boundaries (that is the case with Duration and Interval) or would store a period in given unit (that is the case with Period).

I would recommend simply (of course with exporting all the constants to separate variables):

public boolean isWithinInterval(Instant instant) {
DateTime dateTime = instant.toDateTime();
return dateTime.getDayOfWeek() == MONDAY
&& dateTime.getHourOfDay() >= 8
&& dateTime.getHourOfDay() <= 13;

Note that you could of course create separate Duration and Period objects and use both of them in the comparison but I don't think this would be cleaner than the presented solution.



Related Topics



Leave a reply



Submit