Approximate Time Period with Joda-Time - java

I am using Joda library to get time period passed since a given timestamp:
public static String getTimePassedSince(Date initialTimestamp){
DateTime initDT = new DateTime(initialTimestamp.getTime());
DateTime now = new DateTime();
Period p = new Period(initDT, now);
PeriodFormatter formatter = new PeriodFormatterBuilder()
.appendYears().appendSuffix(" year, ", " years, ")
.appendMonths().appendSuffix(" month, ", " months, ")
.appendDays().appendSuffix(" day, ", " days, ")
.appendHours().appendSuffix(" hour, ", " hours, ")
.appendMinutes().appendSuffix(" minute, ", " minutes, ")
.appendSeconds().appendSuffix(" second, ", " seconds")
.printZeroNever()
.toFormatter();
return formatter.print(p);
}
The function returns exact time period strings for given timestamps. For example:
3 minutes, 23 seconds
1 hour, 30 minutes, 57 seconds
1 day, 23 hours, 21 minutes, 19 seconds
Is there any way that I can get approximate time instead of exact? For example, if one minute and 30 seconds have passed since the initialTimestamp, it only returns 1.5 minutes. Similarly, if an hour and 35 minutes have passed, it returns about 1.5 hours instead of 1 hour, 35 minutes, xy seconds.
I know the string returned can be parsed and manipulated but I am looking for something more sophisticated.

Take a look on PrettyTime.

I think you'll need to create your own formatter for this, which looks at the period and determines what granularity you want to format it in, say for 63 seconds "1 minute", or for 3 hours 48 minutes: "3 hours". Sounds to me like you want to report only the one largest unit of time, and ignore the more granular ones. You'll need to define the rounding behavior and how to render times in days: "44 days ago" or "one month ago" or "1 month and 2 weeks ago".
I am not aware of any generic utility which does this, but I haven't looked for one either.

Related

jodaTime PeriodFormatter not formatting correctly

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

How to handle LocalDate Period.between results?

I use LocalDate (ThreeTenABP) to calculate the period between to given dates. When I use 01/01/2018 as the first and 12/31/2018 as the second date, Period.between gives the following result:
00 years
11 months
30 days
IMHO this is wrong, since a full year has 12 months.
I tried to add one day. Then I get:
00 years
11 months
31 days
I would need a reliable method to get the real amount of full months within a given period.
If you look at the Javadoc for Period.between(), it tells you that the end date is exclusive (meaning: not counted).
The start date is included, but the end date is not. The period is calculated by removing complete months, then calculating the remaining number of days, adjusting to ensure that both have the same sign.
I wrote this code, and it appears to function as I would expect...
LocalDate d1 = LocalDate.of(2018, 1, 1);
LocalDate d2 = LocalDate.of(2018, 12, 31);
Period p1 = Period.between(d1, d2);
System.out.println(p1.getYears() + " years, " + p1.getMonths() + " months, " + p1.getDays() + " days");
// Prints: 0 years, 11 months, 30 days
Adding one day, making it 2019-01-01, gives me one year:
LocalDate d3 = LocalDate.of(2019, 1, 1);
Period p2 = Period.between(d1, d3);
System.out.println(p2.getYears() + " years, " + p2.getMonths() + " months, " + p2.getDays() + " days");
// Prints: 1 years, 0 months, 0 days
Edit:
If you just add one day to the calculated Period object, you are really just adding a day, not recalculating the period as the between method would. Here's the code from Period which does plusDays():
public Period plusDays(long daysToAdd) {
if (daysToAdd == 0) {
return this;
}
return create(years, months, Math.toIntExact(Math.addExact(days, daysToAdd)));
}
If you follow the create call, it really just adds one day to the days counter, it doesn't recalculate anything. To properly add a day to a period, recalculate it with different endpoints as I have above.
The reliable method is:
LocalDate begin = LocalDate.of(2018, Month.JANUARY, 1);
LocalDate end = LocalDate.of(2018, Month.DECEMBER, 31);
Period inclusive = Period.between(begin, end.plusDays(1));
System.out.println(inclusive);
This prints
P1Y
Voilà, a period of one year. Adding 1 day to the end date makes the end date inclusive (since now it’s the day after the end date that is exclusive).

joda time PeriodFormatter does not print years

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?
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());

Joda Period not converting all minutes to hours "8h, 132m"

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

Effectively getting time unit with Joda

I am looking for a neat solution to get the time units in Java 7 ( or using Joda date time)
Like, to 65 minutes, it should say 1 hour 5 minutes
To 30 minutes, it should just say 30 minutes
Thanks.
You can use joda time's normalizedStandard to print your output too.
Per the doc,
Normalizes this period using standard rules, assuming a 12 month year,
7 day week, 24 hour day, 60 minute hour and 60 second minute.
An example for 65 minutes would be:
System.out.println(PeriodFormat.getDefault().print(Period.hours(0).plusMinutes(65).plusSeconds(0).normalizedStandard()));
Output:
1 hour and 5 minutes
Short answer, use org.joda.time.Period.
For example, a general purpose solution might be to have a method that takes the number of milliseconds and returns a String of the form:
X hours, X minutes, X seconds, X milliseconds
public class DateTimeUtils {
public static String toNicePeriodValue(Period period) {
return period.getHours() + "hours " +
period.getMinutes() + "minutes " +
period.getSeconds() + "seconds " +
period.getMillis() + "milliseconds";
}
}
An easy way to create a Period object is like this:
public String nicePeriodValueFromMillis(long timeInMillis) {
Period period = new Period(timeInMillis);
String ret = DateTimeUtils.toNicePeriodValue(period);
return ret;
}
And invoke it like this:
long timeInMillis = /* obtain somehow */
String nicePeriodValue = nicePeriodValue(timeInMillis);
System.out.println("Nice Period Value: " + nicePeriodValue);
This is not, of course, a complete solution, but it should get you started.
If your input is always minutes use the modulus operator % 60 to find remaining minutes and / 60 to find hours.

Categories