Hot questions for Using Joda-Time in periodformatter

Top Java Programmings / Joda-Time / periodformatter

Question:

I am using a PeriodFormatter to return a string showing the time until an event. The code below keeps creating strings like 1146 hours 39 minutes instead of x days y hours z minutes. Any ideas?

Thanks, Nathan

   PeriodFormatter formatter = new PeriodFormatterBuilder()
                .printZeroNever()
                .appendDays()
                .appendSuffix( "d " )
                .appendHours()
                .appendSuffix( "h " )
                .appendMinutes()
                .appendSuffix( "m " )
                .toFormatter();
        return formatter.print( duration.toPeriod() );

Answer:

This is because you convert Duration to Period with method Duratoin::toPeriod This is described in Joda-time documentation:

public Period 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.

Period insance cann't be calculated properly without start date (or end date), because some days can be 24h or 23h (due to DST)

You shoud use method Duration::toPeriodFrom method instead E.g.

Duration duration = new Duration(date1, date2);  
// ...  
formatter.print( duration.toPeriodFrom(date1));

Question:

I just tested the PeriodFormatterBuilder in Joda Time framework. When I am appending the weeks output to the builder, the calculated time is correct. But if weeks are not appended, what is actually what I want, the builder is just dropping 7 days:

public class JodaTest {
  public static void main(String[] args) {

    // builder 1 (weeks inc.)
    PeriodFormatterBuilder b1 = new PeriodFormatterBuilder();
    b1.appendYears().appendSuffix(" year", " years");
    b1.appendSeparator(" ");
    b1.appendMonths().appendSuffix(" month", " months");
    b1.appendSeparator(" ");
    // appends weeks ...
    b1.appendWeeks().appendSuffix(" week", " weeks");
    b1.appendSeparator(" ");
    b1.appendDays().appendSuffix(" day", " days");
    b1.appendSeparator(" ");
    b1.printZeroIfSupported().minimumPrintedDigits(2);
    b1.appendHours().appendSuffix(" hour", " hours");
    b1.appendSeparator(" ");
    b1.appendMinutes().appendSuffix(" minutes");
    b1.appendSeparator(" ");
    b1.appendSeconds().appendSuffix(" seconds");
    PeriodFormatter f1 = b1.toFormatter();

    // builder 2 (weeks not inc.)
    PeriodFormatterBuilder b2 = new PeriodFormatterBuilder();
    b2.appendYears().appendSuffix(" year", " years");
    b2.appendSeparator(" ");
    b2.appendMonths().appendSuffix(" month", " months");
    b2.appendSeparator(" ");
    // does not append weeks ...
    b2.appendDays().appendSuffix(" day", " days");
    b2.appendSeparator(" ");
    b2.printZeroIfSupported().minimumPrintedDigits(2);
    b2.appendHours().appendSuffix(" hour", " hours");
    b2.appendSeparator(" ");
    b2.appendMinutes().appendSuffix(" minutes");
    b2.appendSeparator(" ");
    b2.appendSeconds().appendSuffix(" seconds");
    PeriodFormatter f2 = b2.toFormatter();

    Period period = new Period(new Date().getTime(), new DateTime(2014, 12, 25, 0, 0).getMillis());

    System.out.println(f1.print(period));
    System.out.println(f2.print(period)); // 7 days missing?
   }
}

Prints out:

 1 month 1 week 2 days 09 hours 56 minutes 21 seconds 
 1 month 2 days 09 hours 56 minutes 21 seconds

In the second line the day value should be "9 days". How to make the builder summarize the correct day values?


Answer:

The standard Period object divides the period into years, months, weeks, days, and the time fields. A duration of more than a week will add to the weeks field, and the days field is, more or less, the remainder of dividing the duration by 7.

The PeriodFormatter merely prints the fields as they are inside the Period object. It doesn't make any calculations. If the days field is 2, it will stay 2 even if you haven't included the weeks.

To get the period with weeks represented in the days field instead of the weeks field, you should either create a period with a different type:

Period periodWithoutWeeks = new Period(
     Date().getTime(),
     new DateTime(2014, 12, 25, 0, 0).getMillis(),
     PeriodType.yearMonthDayTime());

Or convert your period to the type without weeks by assuming a week is a standard 7 days:

Period periodWithoutWeeks =  period.normalizedStandard(PeriodType.yearMonthDayTime());

Now you can print it with either of the formatters:

System.out.println( f2.print(periodWithoutWeeks) );

Question:

I need only to show suffix for day/days, how can I achieve that? It doesn't work:

java.lang.IllegalStateException: No field to apply suffix to..

    private PeriodFormatter getDayTextFormatter() {
        return new PeriodFormatterBuilder()
                .printZeroNever()
                .appendSuffix("day", "days")
                .toFormatter();
    }

Answer:

I don't think it's possible. According to JodaTime's javadoc, the appendSuffix method will throw an exception if there's no field to append the suffix:

Throws: IllegalStateException - if no field exists to append to

So I believe JodaTime can't help you this time. Although, you could do something like this:

private String suffix(Period p) {
    int days = p.getDays();
    if (days <= 0) {
        return "";
    }

    return days == 1 ? "day" : "days";
}

With this code, the following:

System.out.println(suffix(Period.days(1)));
System.out.println(suffix(Period.days(2)));
System.out.println(suffix(new Period()));

produces the output:

day
days
// and a line with an empty string

Question:

I try to call the parsePeriod() with the parameter "12:00:00", and it runs an IllegalArgumentException. I try the decompile the PeriodFormatter class, and getParser().parseInto(localMutablePeriod, paramString, 0, iLocale); this line turns wrong. Can anybody tells me the reason? Thanks.


Answer:

"12:00:00" is not the correct ISO 8601 duration format. See the description of the format here: http://en.wikipedia.org/wiki/ISO_8601#Durations

In your case, if you mean a 12-hour duration, the parameter should be "PT12H0M0S": ISOPeriodFormat.standard().parsePeriod("PT12H0M0S")

Question:

Am using joda-time-2.5 in Android Studio project.

I am not able to work out what I missing to be able to correctly format a String with years and/or months.

The Period calculates correctly - but will not go beyond "Weeks" eg. 1000000 minutes is correctly formatted to "99wks, 1day, 10hrs + 40mins". But not as months/years format eg. "1year, 10months, 3weeks, 1day, 10hrs + 40mins" etc

I have tried all kinds of variations of

Period pA = new Period(mA);

Period pA = new Period(mA, PeriodType.standard());

Period pA = new Period(mA, PeriodType.yearMonthDay());

etc but these made no difference.

I have tried adding/removing various .appends/years/months/printZero - this made no difference.

I have tried changing the period units : if I use Months or Years it will work eg

    Months mA = Months.months(15);

    Period pA = new Period(mA, PeriodType.standard());

Correctly produces "1year, 3months".

I understand that 'years' and 'months' are not precise (and approximate would actually be fine in this case), but I thought that's what the PeriodTypes/yearMonthDay or standard took care of?

I also tried PeriodFormat.getDefault().print(period) without success.

Please find code below:

private String formatTimeStr(int minutes){

    Minutes mA = Minutes.minutes(minutes);

    Period pA = new Period(mA);

    PeriodFormatter dhm = new PeriodFormatterBuilder()
            .printZeroNever()
            .appendYears()
            .appendSuffix("year","years")
            .appendSeparator(", ")
            .appendMonths()
            .appendSuffix("mnth", "mnths")
            .appendSeparator(", ")
            .appendWeeks()
            .appendSuffix("wk", "wks")
            .appendSeparator(", ")
            .appendDays()
            .appendSuffix("day", "days")
            .appendSeparator(", ")
            .appendHours()
            .appendSuffix("hr", "hrs")
            .appendSeparator(" & ")
            .appendMinutes()
            .appendSuffix("min", "mins")
            .toFormatter();

    String formattedTimeStr = dhm.print(pA.normalizedStandard());

    return formattedTimeStr;
}

Answer:

Fact is as you already have recognized that minutes are not convertible to months in a strict sense. The Joda-Time-documentation speaks it out, too.

If the period contains years or months, then the months will be normalized to be between 0 and 11. The days field and below will be normalized as necessary, however this will not overflow into the months field. Thus a period of 1 year 15 months will normalize to 2 years 3 months. But a period of 1 month 40 days will remain as 1 month 40 days.

Technically, there are two options for conversion.

a) You define a reference timestamp. Then you can do following:

LocalDateTime tsp1 = new LocalDateTime(); // now as example
LocalDateTime tsp2 = tsp1.plusMinutes(minutes);
Period p = new Period(tsp1, tsp2, PeriodType.standard()); // or other period type?

b) You find and develop yourself a rounding algorithm based on the estimated length of time units. For example you might be willing to accept a rounded month length of 30.5 days or similar. This can be considered as fair solution if the use-case does not require absolute precision as this is often true in social media scenarios. As far as I know, Joda-Time does not support such a rounding feature out of the box (in contrast to other libraries).

Question:

I have a formatter as below:

 private static PeriodFormatter formatter = new PeriodFormatterBuilder()
            .printZeroNever()
            .appendYears().appendSuffix(" years ")
            .appendMonths().appendSuffix(" months ")
            .appendWeeks().appendSuffix(" weeks ")
            .appendDays().appendSuffix(" days ")
            .appendHours().appendSuffix(" hours ")
            .appendMinutes().appendSuffix(" minutes ")
            .appendSeconds().appendSuffix(" seconds")
            .toFormatter();

and use it as below:

        DateTime dt = DateTime.parse("2010-06-30T01:20");
        Duration duration = new Duration(dt.toInstant().getMillis(), System.currentTimeMillis());

        Period period = duration.toPeriod().normalizedStandard(PeriodType.yearMonthDayTime());
        formatter.print(period);

the output is:

2274 days 13 hours 59 minutes 39 seconds

So where is the years?


Answer:

The underlying problem here is your use of Duration to start with, IMO. A Duration is just a number of milliseconds... it's somewhat troublesome to consider the number of years in that, as a year is either 365 or 366 days (and even that depends on the calendar system). That's why the toPeriod method you're calling explicitly says:

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.

Then you're calling normalizedStandard(PeriodType) which includes:

The days field and below will be normalized as necessary, however this will not overflow into the months field. Thus a period of 1 year 15 months will normalize to 2 years 3 months. But a period of 1 month 40 days will remain as 1 month 40 days.

Rather than create the period from a Duration, create it directly from the DateTime and "now", e.g.

DateTime dt = DateTime.parse("2010-06-30T01:20");
DateTime now = DateTime.now(); // Ideally use a clock abstraction for testability
Period period = new Period(dt, now, PeriodType.yearMonthDayTime());

Question:

I have this PeriodFormatter:

PeriodFormatterBuilder()
            .appendDays()
            .appendSuffix(
                " day",
                " days")
            )
            .appendSeparator(", ")
            .printZeroRarelyLast()
            .appendHours()
            .appendSuffix(
                " hours",
                " hours"
            )
            .appendSeparator(" ")
            .appendMinutes()
            .appendSuffix(" minute")
            .toFormatter()
            .let {
                Period(Seconds.seconds(seconds.toInt())).toString(it)
            }

I want to give seconds as input and get x DAYS, x HOURS, x MINUTES back.... I get an empty String back when doing this. If i add "appendSeconds()" to the formatter creation I get the same amount of seconds back as a return value as I sent in..

I need the conversion, not just the amount, and I'm not interested in number of seconds, but how many minutes, hours and days it accounts to.. Anyone who can help?


Answer:

You need to use Period.normalizedStandard() to persuade your period to convert your seconds into minutes, hours and days.

            Period(Seconds.seconds(seconds.toInt())).normalizedStandard().toString(it)

(Not sure whether the empty round brackets () are needed in Kotlin. They are in Java, where I tested.)

Question:

I have seen this, but is it possible to use only month and days, not weeks?

For example, instead of "1 month 2 weeks 2 days", I want "1 month 16 days".


Answer:

You can use a solution similar to the answer you linked, but you'll also need to use a org.joda.time.PeriodType to normalized the period:

// 1 month, 2 weeks and 2 days
Period p = new Period(0, 1, 2, 2, 0, 0, 0, 0);

PeriodFormatter fmt = new PeriodFormatterBuilder()
    // months
    .appendMonths().appendSuffix(" month", " months").appendSeparator(" and ")
    // days
    .appendDays().appendSuffix(" day", " days")
    .toFormatter();
System.out.println(fmt.print(p.normalizedStandard(PeriodType.yearMonthDayTime())));

This will print:

1 month and 16 days