Hot questions for Using Joda-Time in period

Question:

I am storing two DateTimes (Joda) in an object and then I get a Period from the object by new Period(dateTime1, dateTime2). I then want to add all the periods from different objects together. I am both adding all the periods together in a variable and summing up some periods in smaller periods stored in a HashMap<long, Period>.

The result and issue is this. The first period gets "2 hours and 30 minutes" with a PeriodFormat.getDefault().print(p) (the values are the same if i concatenate getHours and getMinutes). The second value "5 hours and 52 minutes". So far so good. But when I do it with the 3rd and 4th, the minutes stop converting to hours.

"5 hours and 103 minutes"

"8 hours and 132 minutes"

It should be 10h and 12m, but as you can see. That's not what I am getting. What is the issue? How can Period just forget to do the conversion? I don't have any problems with the selected sums, yet.

code: (with variable names changed)

mainSum= new Period();
tasksSum= new HashMap<Long, Period>();
for(Entry entry: entries){
        long main_id= entry.getMain_id();
        long task_id = entry.getTask_id();
        Period entryPeriod = entry.getPeriod();

        if(main_id == mainStuff.getId()){
            mainSum = entryPeriod.plus(mainSum);
            Timber.d("mainSum: " + PeriodFormat.getDefault().print(mainSum));
            Timber.d("sum of workplace: " + mainSum.getHours() + " : " + mainSum.getMinutes());
            Period taskPeriod = tasksPeriodSums.remove(task_id);
            if(taskPeriod == null){
                tasksPeriodSums.put(task_id, entryPeriod);
            } else {
                tasksPeriodSums.put(task_id, taskPeriod.plus(entryPeriod));
            }
        }
    }

Please help, thank you :)


Answer:

This is documented behaviour, check out the Javadoc for the plus(Period) function:

/**
 * Returns a new period with the specified period added.
 * <p>
 * Each field of the period is added separately. Thus a period of
 * 2 hours 30 minutes plus 3 hours 40 minutes will produce a result
 * of 5 hours 70 minutes - see {@link #normalizedStandard()}.
 * <p>
...

Drilling down into the Javadoc of the normalizedStandard(..) function itself, we see what's the tradeoff:

/**
 * Normalizes this period using standard rules, assuming a 12 month year,
 * 7 day week, 24 hour day, 60 minute hour and 60 second minute,
 * 
...
 * However to achieve this it makes the assumption that all years are
 * 12 months, all weeks are 7 days, all days are 24 hours,
 * all hours are 60 minutes and all minutes are 60 seconds. This is not
 * true when daylight savings time is considered, and may also not be true
 * for some chronologies.
...

Question:

The result of Period for the following 2 cases (starting from 2016 Feb 28/29 to 2017 Mar 1) are same as each other.

Could you please help to explain this strange behavior?

Case 1: 2016 Feb 28 to 2017 Mar 1

Calendar start1 = Calendar.getInstance();
start1.set(2016, Calendar.FEBRUARY, 28, 0, 0, 0);
Calendar end1 = Calendar.getInstance();
end1.set(2017, Calendar.MARCH, 1, 0, 0, 0);
Interval i1 = new Interval(new DateTime(start1.getTime()), new DateTime(end1.getTime()))
System.out.println(i1.toPeriod());

result: P1Y1D


Case 2: 2016 Feb 29 to 2017 Mar 1

Calendar start2 = Calendar.getInstance();
start2.set(2016, Calendar.FEBRUARY, 29, 0, 0, 0);
Calendar end2 = Calendar.getInstance();
end2.set(2017, Calendar.MARCH, 1, 0, 0, 0);
Interval i2 = new Interval(new DateTime(start2.getTime()), new DateTime(end2.getTime()))
System.out.println(i2.toPeriod());

result: P1Y1D


Answer:

Consider

public static void main(String[] args) throws Exception
{
    LocalDate start1 = LocalDate.of(2016, Month.FEBRUARY, 28);
    LocalDate start2 = LocalDate.of(2016, Month.FEBRUARY, 29);
    LocalDate end    = LocalDate.of(2017, Month.MARCH,     1);
    Period    year   = Period.ofYears(1);

    System.out.println(start1);
    System.out.println(start2);
    System.out.println(end);
    System.out.println(start1.plus(year));
    System.out.println(start2.plus(year));
    System.out.println(start1.until(end));
    System.out.println(start2.until(end));
}

Output

2016-02-28
2016-02-29
2017-03-01
2017-02-28
2017-02-28
P1Y1D
P1Y1D

This may not be how you'd like for it to work, but it seems the implementations are consistent.

What exactly does it mean to add 1 year to February 29th? It can be equally taken as "The last day in February" or the start date plus 365 days. The latter definition causes a problem for days prior to February 29th of a leap year:

February 1 2016 + 1 year == January 31 2017
February 1 2017 + 1 year == February 1 2018

which would confuse people.

This is a quirk of the way our calendar works, and the existing behavior seems to minimize (but not eliminate) the dates for which the behavior is "surprising".

Question:

I am doing some unit testing and have happened across this:

2015 (Not a Leap Year)

LocalDate endDate = LocalDate.parse("01/03/2015", new DateTimeFormatterFactory("dd/MM/yyyy").createDateTimeFormatter());
LocalDate startDate = LocalDate.parse("25/02/2015", new DateTimeFormatterFactory("dd/MM/yyyy").createDateTimeFormatter());

org.joda.time.Period.fieldDifference(startDate, endDate).getDays(); // is -24

2016 (Leap Year)

LocalDate endDate = LocalDate.parse("01/03/2016", new DateTimeFormatterFactory("dd/MM/yyyy").createDateTimeFormatter());
LocalDate startDate = LocalDate.parse("25/02/2016", new DateTimeFormatterFactory("dd/MM/yyyy").createDateTimeFormatter());

org.joda.time.Period.fieldDifference(startDate, endDate).getDays(); // is ALSO -24

I would expect these values to be at least different.

Any ideas?


Answer:

The doc for fieldDifference says

Calculation by field difference works by extracting the difference one field at a time and not wrapping into other fields. Thus 2005-06-09/2007-04-12 will yield P1Y-2M3D.

The difference in the day field between "01/03/2016" and "25/02/2016" is 1 - 25 = -24. It is not saying that is the number of days between the two dates. -24 would not make sense as the number of days between those two dates in any year.

To find the number of days between two dates, you could use

org.joda.time.Days.daysBetween(startDate, endDate).getDays()

which will indeed return a different number in 2015 from 2016 if it crosses the end of February.

Question:

I'm having strange behavior from Joda-Time's period calculation for getting someone's age. If someone is born on January 5, 1970 then as of today they should be 46 years old and 1 day. However, if I use the following age calculation:

LocalDate birthdate = new LocalDate(1970,1,5);
LocalDate today = new LocalDate();
Period period = new Period(birthdate, today, PeriodType.yearMonthDay());
int age = period.getYears();

And joda-time says period is P45Y1D. I've tried this on wolfram to verify I'm not crazy and it agrees with me. What is joda-time doing here that's giving it a different result?


Answer:

I believe this is because your computer's clock is set incorrectly. Hardcoding the date as follows produces the expected result:

LocalDate birthdate = new LocalDate(1970,1,5);
LocalDate today = new LocalDate(2016, 1, 6);  // Rather than relying on system clock. 
Period period = new Period(birthdate, today, PeriodType.yearMonthDay());
int age = period.getYears();  // 46.

Question:

I am seeing some strange behavior around the Joda-time Period class -- specifically the days processing. In the following example code, I am specifying a period of 26 hours as milliseconds.

// 26 hour duration
long durationMillis = 26 * 3600 * 1000;
Period period = new Period(durationMillis, PeriodType.dayTime());
// this fails because days == 0
assertEquals(1, period.getDays());
// this would fail because hours == 26
assertEquals(2, period.getHours());

I was expecting that Period would see that 26 hours is 1 day and 2 hours but it doesn't seem to be recognizing that a day == 24 hours.

Any idea what am I doing wrong?


Answer:

Turns out that Joda-time is wicket smaaart. 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());

Question:

Joda DateTime API returns date with incorrect timezone after plus operation. Below is the code:

Interval interval = new Interval("2011-03-21/2011-10-31", ISOChronology.getInstance(DateTimeZone.forID("Europe/Prague")));
Period period = Period.weeks(1);

DateTime start = interval.getStart();
DateTime end = start.plus(period);

And in the debug mode, I see:

interval = "2011-03-21T00:00:00.000+01:00/2011-10-31T00:00:00.000+01:00"
start = "2011-03-21T00:00:00.000+01:00"
end = "2011-03-28T00:00:00.000+02:00"

As you can see, interval has +01:00 for start and end. but end shows +02:00 after plus operation.

How to do plus(Period) and keep zone ?


Answer:

On 27/03/2011 the daylight time change occurred...

edit: written wrong year :P

Question:

I have a scenario to generate specific time between two dates. Let's say Mar 1st to Mar 31st as my input. I need to generate datetime with specific hours as below.

Mar 1st 03:00 - Mar 1st 06:59

Mar 1st 07:00 - Mar 1st 14:59

Mar 1st 15:00 - Mar 2nd 02:59

Mar 2nd 03:00 - Mar 2nd 06:59

Mar 2nd 07:00 - Mar 2nd 14:59

.

.

.

.

Mar 31st 15:00 - Mar 31st 02:59

I am confused how to generate these different time periods using joda. Help me to generate these times.


Answer:

I have implemented a solution for practicing the Joda library:

public class JodaDateTimeExercise {

    private static final String PATTERN = "MMM $$ HH:mm";

    public static void main(String[] args) {
        DateTime dateTimeBegin = new DateTime(2000, 3, 1, 3, 0);
        DateTime dateTimeEnd = dateTimeBegin.plusMinutes(239);

        DateTime dateTimeBeginCopy = dateTimeBegin;
        DateTime dateTimeEndCopy = dateTimeEnd;

        for (int dayIndex = 0; dayIndex < 31; dayIndex++) {
            printDateTime(dateTimeBeginCopy, dateTimeEndCopy);

            dateTimeBeginCopy = dateTimeBeginCopy.plusHours(4);
            dateTimeEndCopy = dateTimeEndCopy.plusHours(8);

            printDateTime(dateTimeBeginCopy, dateTimeEndCopy);

            dateTimeBeginCopy = dateTimeBeginCopy.plusHours(8);
            dateTimeEndCopy = dateTimeEndCopy.plusHours(12);

            printDateTime(dateTimeBeginCopy, dateTimeEndCopy);

            dateTimeBegin = dateTimeBegin.plusDays(1);
            dateTimeEnd = dateTimeEnd.plusDays(1);

            dateTimeBeginCopy = dateTimeBegin;
            dateTimeEndCopy = dateTimeEnd;
        }
    }

    private static void printDateTime(DateTime dateTimeBegin, DateTime dateTimeEnd) {
        System.out.print(dateTimeBegin.toString(PATTERN, Locale.US).replace("$$", formatDayOfMonth(dateTimeBegin.dayOfMonth().get())));
        System.out.print(" - ");
        System.out.println(dateTimeEnd.toString(PATTERN, Locale.US).replace("$$", formatDayOfMonth(dateTimeEnd.dayOfMonth().get())));
        System.out.println();
    }

    public static String formatDayOfMonth(int dayOfMonthIndex) {
        String suffix;

        switch ((dayOfMonthIndex < 20) ? dayOfMonthIndex : dayOfMonthIndex % 10) {
            case 1:
                suffix = "st";
                break;
            case 2:
                suffix = "nd";
                break;
            case 3:
                suffix = "rd";
                break;
            default:
                suffix = "th";
                break;
        }

        return dayOfMonthIndex + suffix;
    }

}

The output is the following:

Mar 1st 03:00 - Mar 1st 06:59

Mar 1st 07:00 - Mar 1st 14:59

Mar 1st 15:00 - Mar 2nd 02:59

Mar 2nd 03:00 - Mar 2nd 06:59

Mar 2nd 07:00 - Mar 2nd 14:59

Mar 2nd 15:00 - Mar 3rd 02:59

...

Mar 31st 03:00 - Mar 31st 06:59

Mar 31st 07:00 - Mar 31st 14:59

Mar 31st 15:00 - Apr 1st 02:59

As you can notice, there is a small difference between my output and your output. If you take the time to understand what I have written, you can easily fix it by yourself.

Question:


Answer:

Change the following lines of code

Interval interval =   new Interval(start, end);
Period period = interval.toPeriod();

To

Period period = new Period(start, end, PeriodType.yearMonthDay());

Question:

I'm building an android app and I would do a sum of two periods, using jodatime library. Actually, I've a problem to manage the sign because I could have only one period with negative sign. I tried a code to do this, but the result is wrong for me.

example:

case 1: 
time1 = 4:55   
time2 = -7:10  
time1 + time2 = -01:-55  --> this is wrong... I'm expected -3:45

case 2:
time1 = -4:55 
time2 = 7:10
time1 + time2 = 4:05  --> this is correct

This is my sample code:

public static void main(String[] args) 
    {
        String output;
        String time1 = "-4:55";
        String time2 = "7:10";

        Duration durationSum = Duration.ZERO;
        PeriodFormatter formatter = getFormatterBuilder();
        Period period1 = formatter.parsePeriod(time1);
        Duration duration1 = period1.toStandardDuration();
        Period period2 = formatter.parsePeriod(time2);
        Duration duration2 = period2.toStandardDuration();

        output = formatter.print(durationSum.plus(duration1).plus(duration2).toPeriod());

        System.out.println(output);

    }

    private static PeriodFormatter getFormatterBuilder()
    {
        return new PeriodFormatterBuilder()
                .minimumPrintedDigits(2)
                .printZeroAlways()
                .appendHours()
                .appendLiteral(":")
                .appendMinutes()
                .toFormatter();
    }

What am I wrong?

Thanks!


Answer:

Maybe the exotic sign handling of Joda-Time has confused you (like me on first try). In detail:

case 1:  
time1 = 4:55
time2 = -7:10
time1 + time2 = -01:-55  --> this is wrong... I'm expected -3:45

The Joda-Time strategy for interpreting the negative sign means the sign to be applied on every single time component, NOT on the whole duration itself. So we don't have 55 minutes - 10 minutes but 55 minutes + 10 minutes:

time1 + time2 = (4:55) + (-7:10) = (+4 hours + 55 minutes) + (-7 hours + 10 minutes) = (4 * 60 minutes + 55 minutes) + (-7 * 60 minutes + 10 minutes) = 295 minutes - 410 minutes = -115 minutes = -1 hour - 55 minutes

We see that Joda-Time is consequently doing its sign strategy.

The same sign strategy confirms your second expectation however:

case 2:
time1 = -4:55 
time2 = 7:10
time1 + time2 = 4:05  --> this is correct

time1 + time2 = (-4:55) + (7:10) = (-4 hours + 55 minutes) + (7 hours + 10 minutes) = (-4 * 60 minutes + 55 minutes) + (7 * 60 minutes + 10 minutes) = -185 minutes + 430 minutes = 245 minutes = 4 hours + 5 minutes

Important side note about the strategy of Joda-Time: As we can see in the examples given by you, Joda-Time violates the symmetry rule

-(time1 - time2) != (time2 - time1)

In math, this symmetry rule represents a specialized form of common distributive law where the minus sign can be interpreted as multiplication by factor -1. If Joda-Time had choosen the much more appropriate sign strategy to apply the sign on the whole duration instead then the calculation would yield:

case 1: (4:55) + (-7:10) = (4 hours + 55 minutes) + (-7 hours - 10 minutes) = 295 minutes - 430 minutes = -135 minutes = -2:15 (meaning -2 hours - 15 minutes)

case 2: (-4:55) + (7:10) = (-4 hours - 55 minutes) + (7 hours + 10 minutes) = -295 minutes + 430 minutes = 135 minutes = 2:15

This intuitive strategy would have ensured the mentioned symmetry rule above. But Jodas strategy (and now overtaken by Period-class in JSR-310 aka java.time library in Java-8, too) is set in stone (unfortunately) and must be seen as "feature" if we like it or not.

Question:

I need to calculate period between two hours in my app which uses JodaTime, here is my code:

DateTimeFormatter format = DateTimeFormat.forPattern("HH:mm");
        String s1 = textView9.getText().toString();
        String s2 = textView10.getText().toString();
        final DateTime dateTime1 = format.parseDateTime(s1);
        final DateTime dateTime2 = format.parseDateTime(s2);
        final PeriodFormatter periodFormatter = new PeriodFormatterBuilder()
                .printZeroAlways()
                .appendHours()
                .appendLiteral("h")
                .appendSeparator(":")
                .printZeroIfSupported()
                .appendMinutes()
                .appendLiteral("min")
                .toFormatter();
        String sum = periodFormatter.print(new Period(dateTime1, dateTime2));
        textView11.setText(sum);

So far this code works and displays period, but I noticed that if the s1 is let say 23:00 and s2 is time after midnight, let say 19:00 of next day the output of this period will be -4:00. Here is my question, does JodaTime have function to easy prevent it or it is some kind of issue that I need to figure out by myself? I was searching for the answer in SO but couldn't find something that can help me. I'm glad for every input answer and comment.


Answer:

What's happening here is that you're using the DateTime type (which, somewhat obviously, has both a "date" and a "time" component), but you're initializing it with data that only has information about the time of day (your HH:mm string).

A better representation of this data would be the LocalTime type, which only represents the "time" component.

It sounds like you know for sure that, for each pair of times, one of the following is true:

  • If the second time is "after" the first, they're on the same day
  • If the second time is "before" the first, the second time is actually on the next day

So, here's what I'd do. Change this:

final DateTime dateTime1 = format.parseDateTime(s1);
final DateTime dateTime2 = format.parseDateTime(s2);
...
String sum = periodFormatter.print(new Period(dateTime1, dateTime2));

to this:

LocalTime time1 = format.parseLocalTime(s1);
LocalTime time2 = format.parseLocalTime(s2);
Period period;

if (time1.isBefore(time2)) {
    period = new Period(time1, time2);
} else {
    DateTime dateTime1 = time1.toDateTimeToday();
    DateTime dateTime2 = time2.toDateTimeToday().plusDays(1);
    period = new Period(dateTime1, dateTime2);
}
...
String sum = periodFormatter.print(period);

The general outline here:

  • Build two LocalTime objects so that we're only thinking about time
  • If they're "in order", we can compute the period from just the time
  • If they're not "in order", turn the first into a "today" date-time and the second into a "tomorrow" date-time, then compute the period

Question:

So I have 2 date pickers for a start and end time, which give back a DateTime.

I need to calculate the duration between these two times in the format of "hours hr mins hr", so for example, "1 hr 30 mins". If there is a day difference, it will display "24 hr", so we use hours instead of days.

The problem I am having is that whatever way I got this working, it breaks if the user selects a month ahead. They never should choose a month ahead but are given the option to.

I get this error:

java.lang.UnsupportedOperationException: Unable to normalize as PeriodType is missing either years or months but period has a month/year amount: P1M3W6DT1H
        at org.joda.time.Period.normalizedStandard(Period.java:1640)

And my code is:

Period durationHoursAndMinutes =  new Period(startDate.toLocalDateTime(), endDate.toLocalDateTime()).normalizedStandard(PeriodType.time());
PeriodFormatter formatter = new PeriodFormatterBuilder()
  .appendHours()
  .appendSuffix(getResources().getString(R.string.mytrips_tripview_rail_waitTime_hr))
  .appendSeparator(" ")
  .appendMinutes()
  .appendSuffix(getResources().getString(R.string.mytrips_tripview_rail_waitTime_min))
.toFormatter();

if(durationHoursAndMinutes.getMinutes() > 0){
  return formatter.print(durationHoursAndMinutes);
}else {
  return null;
}

I looked at the code for the PeriodType and it says to throw the exception if the months != 0, which it won't be. Just wondering if there is a way I can solve this. Thanks for any help


Answer:

I suspect the simplest way to do this is just to pass the PeriodType into the constructor instead:

new Period(
    startDate.toLocalDateTime(),
    endDate.toLocalDateTime(),
    PeriodType.time());

Then you don't need to perform any other normalization.

Question:

I have 2 joda.DateTimes, which I use to create a Duration. The Duration is then converted to a Period, and although the two dates are 3 years apart, the Period.getDays, Period.getMonths, and Period.getYears all return 0. why?

Example from the Scala REPL, but should be easy to understand/convert to Java (I can convert if needed)

import org.joda.time.format.PeriodFormat
import org.joda.time.{DateTime, Period}
import org.joda.time.Duration
import org.joda.time.format.PeriodFormatterBuilder

scala> val start = DateTime.now
start: org.joda.time.DateTime = 2017-02-22T16:09:13.131Z

scala>     val end = start.plusYears(2).plusMinutes(1).plusDays(3)
end: org.joda.time.DateTime = 2019-02-25T16:10:13.131Z

scala>     val duration = new Duration(start, end)
duration: org.joda.time.Duration = PT63331260S

scala>     duration.getMillis
res0: Long = 63331260000

scala>     duration.getStandardDays
res1: Long = 733

scala>     duration.getStandardHours
res2: Long = 17592

scala>     duration.getStandardMinutes
res3: Long = 1055521

scala>     duration.getStandardSeconds
res4: Long = 63331260

scala>     val period = duration.toPeriod()
period: org.joda.time.Period = PT17592H1M

scala>     period.getDays
res5: Int = 0

scala>     period.getHours
res6: Int = 17592

scala>     period.getMinutes
res7: Int = 1

scala>     period.getSeconds
res8: Int = 0

scala>     period.getMillis
res9: Int = 0

scala> val formatter = new PeriodFormatterBuilder()
            .appendMonths().appendSuffix(" month", " months").appendSeparator(" ")
            .appendDays().appendSuffix(" day", " days").appendSeparator(" ")
            .appendHours().appendSuffix("h")
            .appendMinutes().appendSuffix("m")
            .appendSeconds().appendSuffix("s")
            .toFormatter
formatter: org.joda.time.format.PeriodFormatter = org.joda.time.format.PeriodFormatter@3096d00d

scala>     formatter.print(duration.toPeriod())
res10: String = 17592h1m

scala> PeriodFormat.getDefault.print(duration.toPeriod)
res11: String = 17592 hours and 1 minute

Answer:

You're calling AbstractDuration.toPeriod:

Converts this duration to a Period instance using the standard period type and the ISO chronology.

Only precise fields in the period type will be used. Thus, only the hour, minute, second and millisecond fields on the period will be used. The year, month, week and day fields will not be populated.

A Duration doesn't "know" its start and end points, so you can't expect variable-length values like months to be populated.

To get the period between start and end, you should just use:

new Period(start, end)

... possibly with a PeriodType to say which units you want.

Question:

I am trying to calculate the differences between dates, like hours ago, minutes ago , months ago, ...

SimpleDateFormat simpleDateFormat = new SimpleDateFormat(
        "dd/M/yyyy HH:mm:ss");

Date now = null;
try {
    now = simpleDateFormat.parse(simpleDateFormat.format(new Date()));
} catch (ParseException e) {
    // TODO Auto-generated catch block
    e.printStackTrace();
}
Date time = null;
try {
    time = simpleDateFormat.parse(simpleDateFormat.format(timestamp));
} catch (ParseException e) {
    // TODO Auto-generated catch block
    e.printStackTrace();
}

Interval interval = new Interval(time.getTime(), now.getTime());
Period period = interval.toPeriod();

Integer s = period.getSeconds();
Integer m = period.getMinutes();
Integer h = period.getHours();
Integer days = period.getDays();

Integer mo = period.getMonths();
Integer y = period.getYears();

Integer mo = period.getMonths(); is always zero

i/p=> now =Tue Oct 18 12:15:50 IST 2016
      time=Mon Sep 26 14:38:36 IST 2016

o/p=> s=14
      m=37
      h=21
      days=0
      mo=0
      y=0

I also tried LocalDate and DateTime but had the same issue.


Answer:

With the way you are doing it now, you get a period that is expressed in weeks. The 'weeks' field of the period will be set, but the years / months / days field will not be set.

If you want a period with years, months, days and time, then you need to specify the period type when you call toPeriod on the interval:

Period period = interval.toPeriod(PeriodType.yearMonthDayTime());

Result:

i/p=> now =Tue Oct 18 12:15:50 IST 2016
      time=Mon Sep 26 14:38:36 IST 2016

o/p=> s=14
      m=37
      h=21
      days=21
      mo=0
      y=0

Ofcourse the months and years field are still 0 with this example because the two dates are less than a month apart.

Question:

I have a method which determines the period between two dateTime variables.

Period period = new Period(startTime, endTime);
PeriodFormatter runDurationFormatter = new PeriodFormatterBuilder().printZeroAlways().minimumPrintedDigits(2).appendDays().appendSeparator(":").appendHours().appendSeparator(":").appendMinutes().appendSeparator(":").appendSeconds().toFormatter();
return runDurationFormatter.print(period);

I would expect to see 00:01:00 for 1 minute, 23:00:00 for 23 hours, 30:00:00 for 30 hours, and 120:00:00 for 120 hours (5 days). I tried using

Period daystoHours = period.normalizedStandard(PeriodType.time());

But eclipse shows that normalizedStandard() method is undefined for type period.


Answer:

Be sure you are using the Period class from org.joda.time package, not from java.time. Following example may help you.

import org.joda.time.Period;
import org.joda.time.PeriodType;
import org.joda.time.format.PeriodFormatter;
import org.joda.time.format.PeriodFormatterBuilder;

import java.util.Calendar;
import java.util.GregorianCalendar;

public class Launcher
{
    public static void main(String[] args)
    {
        Calendar start = new GregorianCalendar(2016, 4, 12, 0, 0, 0);
        Calendar end = new GregorianCalendar(2016, 4, 17, 0, 0, 0);

        Period period = new Period(start.getTimeInMillis(), end.getTimeInMillis());

        PeriodFormatter runDurationFormatter = new PeriodFormatterBuilder().printZeroAlways()
            .minimumPrintedDigits(2)
            .appendHours().appendSeparator(":")    // <-- say formatter to emit hours
            .appendMinutes().appendSeparator(":")  // <-- say formatter to emit minutes
            .appendSeconds()                       // <-- say formatter to emit seconds
            .toFormatter();

        // here we are expecting the following result string 120:00:00
        System.out.println(
            runDurationFormatter.print(period.normalizedStandard(PeriodType.time()))
        );
    }
}

Question:

For experienced java developers, I guess this is a simple JodaTime question.

The problem

Given two DateTime d1, d2 and a Period p, I want to know the first DateTime after d2 that is d1 + k * p where k is an integer.

This is a simple question, and I can solve it using a loop. I would like to know if there is any contracted way in java. I mean can it be done in one line or two?

My idea so far

I think we have k = (d2 - d1) % p then we can get the correct date by adding k+1 * p to d1


Answer:

Period being a wrapper of long, you can do:

long between = d2.getMillis() - d1.getMillis();
long period = p.getMillis();
int k = between / period + 1;
DateTime firstAfter = d1.plusMillis(k * p);

Question:

I'm trying to make one of my classes parcellable, but while reading a Joda Time Period object as serializable, an NPE is thrown.

Code:

private Event(Parcel in) {
    this.id = in.readLong();
    this.scheduleId = in.readLong();
    this.entryId = in.readLong();
    this.time = (Period) in.readSerializable(); // NPE in this line.
}

@Override
public int describeContents() {
    return 0;
}

@Override
public void writeToParcel(Parcel out, int flags) {
    out.writeLong(id);
    out.writeLong(scheduleId);
    out.writeLong(entryId);
    out.writeSerializable(time);
}

public static final Parcelable.Creator<Event> CREATOR
        = new Parcelable.Creator<Event>() {
    public Event createFromParcel(Parcel in) {
        return new Event(in);
    }

    public Event[] newArray(int size) {
        return new Event[size];
    }
};

Logcat:

09-22 22:48:47.929  32708-32708/com.vibhinna.cubs E/AndroidRuntimeļ¹• FATAL EXCEPTION: main
    Process: com.vibhinna.cubs, PID: 32708
    java.lang.RuntimeException: Unable to start activity ComponentInfo{com.vibhinna.cubs/com.vibhinna.cubs.ui.EventActivity}: java.lang.NullPointerException
            at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2195)
            .....
     Caused by: java.lang.NullPointerException
            at java.io.ByteArrayInputStream.<init>(ByteArrayInputStream.java:60)
            at android.os.Parcel.readSerializable(Parcel.java:2210)
            at com.vibhinna.cubs.engine.Event.<init>(Event.java:110)
            at com.vibhinna.cubs.engine.Event.<init>(Event.java:10)
            at com.vibhinna.cubs.engine.Event$1.createFromParcel(Event.java:251)
            at com.vibhinna.cubs.engine.Event$1.createFromParcel(Event.java:249)
            at android.os.Parcel.readParcelable(Parcel.java:2104)
            at android.os.Parcel.readValue(Parcel.java:2013)
            ......

What causes this and how may I fix it?


Answer:

Seems that time object is null when it is written to the Parcel and you get when you are unmarshalling the object later. I can say it from the java.io.ByteArrayInputStream.<init>(ByteArrayInputStream.java:60) line, when the Parcel tries to initialize the ByteArrayInputStream.

Ensure that time object is not null when you write it to the parcel. Alternatively, use parcel.writeValue(time); and readValue(), respectively, documentation says that it accepts null and ? implements Serializable as object.

Question:

This problem is bothering me for a while. A couple months ago I started one project and just can't go one because of this stupid problem.

If you see a time line between 6 and 21 there is a daytime. My problem is that i need to calculate time between 2 dates or hours separately. For example. Let say that start time is at 5:00pm and end time is at 22pm. how can i calculate how many hours was at day and nigh separately between those 2 times ?

String enter = "2017-08-16 15:00:00";
String leave = "2017-08-17 12:00:00";
org.joda.time.format.DateTimeFormatter formatter = DateTimeFormat.forPattern("yyyy-MM-dd HH:mm:ss");
DateTime start = formatter.parseDateTime(enter);
DateTime end = formatter.parseDateTime(leave);
Period period = new Period(start,end);
int hours = period.toStandardHours().getHours();
System.out.println(hours);

Answer:

This solution uses the java.time API instead of Joda time, but you should be able to figure out how to rewrite it yourself if you think that's necessary.

String enter = "2017-08-14 15:00:00";
String leave = "2017-08-17 12:00:00";

DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
LocalDateTime start = LocalDateTime.parse(enter, formatter);
LocalDateTime end = LocalDateTime.parse(leave, formatter);
Duration duration = Duration.between(start, end);

int duringDay = 0;
int duringNight = 0;

// take care of all full days
long days;
if ((days = duration.toHours() / 24L) > 0) {
    duringDay += 15 * days;
    duringNight += 9 * days;
}

// take care of the remainder
for (int i = 1; i <= duration.toHours() % 24; i++) {
    LocalDateTime ldt = start.plusHours(i);
    if (ldt.getHour() <= 6 || ldt.getHour() > 21) {
        duringNight++;
    } else {
        duringDay++;
    }
}

System.out.println("Hours during day: " + duringDay);
System.out.println("Hours during night: " + duringNight);

With nanosecond precision it becomes a bit more complicated:

String enter = "2017-08-13 15:30:30";
String leave = "2017-08-17 22:00:00";

DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
LocalDateTime start = LocalDateTime.parse(enter, formatter);
LocalDateTime end = LocalDateTime.parse(leave, formatter);

Duration duration = Duration.between(LocalDateTime.of(start.toLocalDate().plusDays(1), LocalTime.of(0, 0)), LocalDateTime.of(end.toLocalDate(), LocalTime.of(0, 0)));

Duration timeDuringDay = Duration.ofDays(0);
Duration timeDuringNight = Duration.ofDays(0);

// take care of all full days
long days;
if ((days = duration.toHours() / 24L) > 0) {
    timeDuringDay = timeDuringDay.plusHours(15 * days);
    timeDuringNight = timeDuringNight.plusHours(9 * days);
}

// take care of the first day
if (start.isBefore(LocalDateTime.of(start.toLocalDate(), LocalTime.of(6, 0)))) {
    timeDuringNight = timeDuringNight.plus(Duration.between(start, LocalDateTime.of(start.toLocalDate(), LocalTime.of(6, 0))));
    timeDuringNight = timeDuringNight.plus(Duration.ofHours(3));
    timeDuringDay = timeDuringDay.plusHours(15);
} else if (start.isAfter(LocalDateTime.of(start.toLocalDate(), LocalTime.of(21, 0)))) {
    timeDuringNight = timeDuringNight.plus(Duration.between(start, LocalDateTime.of(start.toLocalDate().plusDays(1), LocalTime.of(0, 0))));
} else {
    timeDuringDay = timeDuringDay.plus(Duration.between(start, LocalDateTime.of(start.toLocalDate(), LocalTime.of(21, 0))));
    timeDuringNight = timeDuringNight.plusHours(3);
}   

// take care of the last day
if (end.isBefore(LocalDateTime.of(end.toLocalDate(), LocalTime.of(6, 0)))) {
    timeDuringNight = timeDuringNight.plus(Duration.between(LocalDateTime.of(end.toLocalDate(), LocalTime.of(0, 0)), end));
} else if (end.isAfter(LocalDateTime.of(end.toLocalDate(), LocalTime.of(21, 0)))) {
    timeDuringNight = timeDuringNight.plusHours(6);
    timeDuringNight = timeDuringNight.plus(Duration.between(LocalDateTime.of(end.toLocalDate(), LocalTime.of(21, 0)), end));
    timeDuringDay = timeDuringDay.plusHours(15);
} else {
    timeDuringNight = timeDuringNight.plusHours(6);
    timeDuringDay = timeDuringDay.plus(Duration.between(LocalDateTime.of(end.toLocalDate(), LocalTime.of(6, 0)), end));
}

System.out.println("Time during day: " + timeDuringDay);
System.out.println("Time during night: " + timeDuringNight);

Question:

in a joda-time based project I am trying to present the user with the period between 2 dates in a readable format. I do not succeed to retrieve this period without negative values. The following code fragment shows the issue:

LocalDateTime firstTime = new LocalDateTime( 2014, 10, 15, 4, 42 );
LocalDateTime secondTime = new LocalDateTime( 2014, 12, 3, 5, 5 );

Period period = Period.fieldDifference(firstTime, secondTime).normalizedStandard();

The output of of period.toString()is:

P2M-1W-4DT-23H-37M

I'm looking for the best method to get something the period in a format like:

P1M3W etc. instead of the negative values?

Thanks for any help.


Answer:

Replace Period.fieldDifference for new Period. try with this:

public static void main(String[] args) {
    LocalDateTime firstTime = new LocalDateTime(2014, 10, 15, 4, 42);
    LocalDateTime secondTime = new LocalDateTime(2014, 12, 3, 5, 5);

    System.out.println("period Type: "+Period.fieldDifference(firstTime, secondTime).getPeriodType());
    Period period = new Period(firstTime, secondTime).normalizedStandard();
    System.out.println(period);
}