2

I spent all the morning trying to find out a way to achieve what I initially thought would be an relatively easy task: convert a duration of time expressed in a numeric way into a readable way. For example, for an input of 3.5, the output should be "3 years and 6 months".

According to what I was reading, it seems that Joda Time library is strongly recommended. Using that library and following this post I was trying things like:

    Period p = new Period(110451600000L); // 3 years and a half

    PeriodFormatter formatter = new PeriodFormatterBuilder()
        .appendYears()
        .appendSuffix(" year", " years")
        .appendSeparator(" and ")
        .appendMonths()
        .appendSuffix(" month", " months")
        .toFormatter();

    System.out.println(formatter.print(p));

But the output is nothing. No idea why it's not working.

I also tried with the Apache DurationFormatUtils, but doesn't work.

Does anybody have an idea?

Thanks in advance.

Community
  • 1
  • 1
  • Perhaps you'd be interested in this very old answer. http://stackoverflow.com/a/12/559299 – Hinton Sep 03 '12 at 13:56
  • Hinton, thanks very much for you prompt answer. Though the answer you suggested is for C#, I think I could easily adapt the algorithm. Nevertheless, it would be my last solution because I believe there should be an easier and cleaner way using Joda Time, for example. My priority is avoid reinventing the wheel :D Thanks again for your asnwer! – Alejandro García Seco Sep 03 '12 at 14:05

2 Answers2

4

After some research, tests, and the help of benjamin, I have a solution:

    DateTime dt = new DateTime(); // Now
    DateTime plusDuration = dt.plus(new Duration(110376000000L)); // Now plus three years and a half

    // Define and calculate the interval of time
    Interval interval = new Interval(dt.getMillis(), plusDuration.getMillis());

    // Parse the interval to period using the proper PeriodType
    Period period = interval.toPeriod(PeriodType.yearMonthDayTime());

    // Define the period formatter for pretty printing the period
    PeriodFormatter pf = new PeriodFormatterBuilder()
            .appendYears().appendSuffix("y ", "y ")
            .appendMonths().appendSuffix("m", "m ").appendDays()
            .appendSuffix("d ", "d ").appendHours()
            .appendSuffix("h ", "h ").appendMinutes()
            .appendSuffix("m ", "m ").appendSeconds()
            .appendSuffix("s ", "s ").toFormatter();

    // Print the period using the previously created period formatter
    System.out.println(pf.print(period).trim());

I found really useful the official documentation of Joda-Time and specially this post: Correctly defining a duration using JodaTime

Nevertheless, though it is working I'm not 100% happy because the output of the posted code above is "3y 6m 11h" and I don't understand the reason of those eleven hours :S Anyway, I only need precision of years and months so I believe is not a big problem. If anybody knows the reason and/or whether it could be a problem in certain scenarios, please make me know with a comment.

Community
  • 1
  • 1
2

The period p in your code does not contain years or moths, thats why the formatter does not output anything at all. Using the formatter PeriodFormat.getDefault(), you would see that it contains hours, namely exactly 30681 = 110451600000 / 1000 / 60 / 60.

And this is why: Milliseconds can be converted to seconds, minutes and hours in a defined way. But calculating the number of days, moths or years is ambiguous, since the number of hours of a day can be different (time zone shifting), as can the number of days in a month and number of days in a year. See the documenation: http://joda-time.sourceforge.net/apidocs/org/joda/time/Period.html#Period%28long%29

As found there:

For more control over the conversion process, you have two options:

  • convert the duration to an Interval, and from there obtain the period
  • specify a period type that contains precise definitions of the day and larger fields, such as UTC
benjamin
  • 1,066
  • 11
  • 22
  • Great explanation, now at least I understand the reason of my code is not working, and makes really sense. On the other hand, I don't have clear at all how to implement that in terms of doing anything that makes sense, taking into account that the time I need to convert is nothing but an amount of time or duration and nothing else. Any suggestion? Thanks a lot benjamin. – Alejandro García Seco Sep 03 '12 at 14:16
  • By the way, before asking I was trying with PeriodType.YearMonthDay, as the documentation says but I had the same result. – Alejandro García Seco Sep 03 '12 at 14:18
  • 1
    Well the actual solution to your problem depends on where your number of milliseconds come from. You could use the actual time like this: `new Duration(new DateTime(), new DateTime().plus(millis)).toPeriod();`, which could sometimes result in 3 years and 5 months, depending on when the code is executed. Or you say the milliseconds are always 'normal' days and years, then devide your milliseconds by (1000*60*60*24*365) and round to the next integer. But that's rather dirty. Specifiing a corresponding period type would be better, but I have no experience on that. – benjamin Sep 03 '12 at 14:28
  • Thanks benjamin. Well, my number of milliseconds come from a decimal number which represents an amount of time in years (for example, 3.5 years). What I'm trying to do is to convert the number of years in milliseconds (3.5 years = 110376000000 ms) for a further text formatting like "3 years and 6 months". Probably I'm facing a wrong approach... what do you think? – Alejandro García Seco Sep 03 '12 at 14:37
  • 1
    I don't know if you have influence on that, but you could change the way of inputting years such that you receive two integers for years and moths seperately rather than a decimal. If that's not possible, do something like `int allMoths = (int) (yearsDecimal * 12); int years = months / 12; int months = allMonths % 12;`. But notice that this may result in losing precision (think of yearsDecimal = 3.14159). I definatly would prefer the first suggestion. – benjamin Sep 03 '12 at 14:56
  • No, I don't have influence on that because the inputting years is a generated amount after several calculations. Honestly, the first idea I had was something similar to your last suggestion, by separating both integer and decimal parts of the number but I think is a little bit dirty and tricky hehehe This is why I tried Joda Time, thinking that it would have utils for solving my problem in a clean way. Well, I will think about a solution and I will post it when I have it working, thus it could be useful for others with the same problem. Thanks a lot bejamin for such a constructive chat! – Alejandro García Seco Sep 03 '12 at 15:09