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.
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.
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
Related
I'm using this library I just discovered which is supposedly less heavier than Joda time for android and I said what the heck, let's use it. But now I'm struggling to find any good examples on the web about how to use it, besides these two methods I have:
// ZonedDateTime contains timezone information at the end
// For example, 2011-12-03T10:15:30+01:00[Europe/Paris]
public static ZonedDateTime getDate(String dateString) {
return ZonedDateTime.parse(dateString).withZoneSameInstant(ZoneId.of("UTC"));
}
public static String formatDate(String format, String dateString) {
return DateTimeFormatter.ofPattern(format).format(getDate(dateString));
}
So how can I get the difference between two dates with this library?
There are several options depending on what you require from the difference you obtain.
It’s easiest to find the difference measured in some time unit. Use ChronoUnit.between. For example:
ZonedDateTime zdt1 = getDate("2011-12-03T10:15:30+01:00[Europe/Paris]");
ZonedDateTime zdt2 = getDate("2017-11-23T23:43:45-05:00[America/New_York]");
long diffYears = ChronoUnit.YEARS.between(zdt1, zdt2);
System.out.println("Difference is " + diffYears + " years");
long diffMilliseconds = ChronoUnit.MILLIS.between(zdt1, zdt2);
System.out.println("Difference is " + diffMilliseconds + " ms");
This prints:
Difference is 5 years
Difference is 188594895000 ms
I am using your getDate method, so the format required is that of ZonedDateTime (modified from ISO 8601), for example 2011-12-03T10:15:30+01:00[Europe/Paris]. Seconds and fraction of second are optional, as is time zone ID in square brackets.
BTW you don’t need to convert to UTC before finding the difference. You will get the same result even if you leave out that conversion.
You may also get the difference in years, months and days. The Period class can give you this, but it cannot handle time of day, so convert to LocalDate first:
Period diff = Period.between(zdt1.toLocalDate(), zdt2.toLocalDate());
System.out.println("Difference is " + diff);
Difference is P5Y11M21D
The output means a period of 5 years 11 months 21 days. The syntax may feel a little strange at first, but is straightforward. It is defined by the ISO 8601 standard. In this case the time zone matters since it is never the same date in all time zones.
To get the difference in hours, minutes and seconds use the Duration class (I am introducing a new time since using Duration for nearly 6 years would be too atypical (though possible)).
ZonedDateTime zdt3 = getDate("2017-11-24T18:45:00+01:00[Europe/Copenhagen]");
Duration diff = Duration.between(zdt2, zdt3);
System.out.println("Difference is " + diff);
Difference is PT13H1M15S
A period of 13 hours 1 minute 15 seconds. The T that you already know from 2011-12-03T10:15:30+01:00[Europe/Paris] here too separates the date part from the time part so you know that in this case 1M means 1 minute, not 1 month.
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;
}
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).
I'm trying to convert a no of months into milliseconds
For example:
6 months = X milliseconds
There's no fixed answer to that, because it depends on which months those are - and indeed which year it is. Also potentially which time zone you're in, if you want to take account of that. (I'm assuming you mean the Gregorian calendar, by the way - different calendar systems have different month lengths.)
You could get some sort of "reasonable approximation" by assuming 365.25 days in a year, and saying that 6 months is half of that, then find out that many days in milliseconds. But it would only be an approximation.
For "how many milliseconds does it take to get from date/time X to 6 months later" you'd use an API (even Calendar would work for this particular case, although I'd recommend Joda Time or java.time in Java 8):
Set your start date/time, in the appropriate calendar and time zone
Fetch the "milliseconds since the Unix epoch" (which is easy enough to retrieve in any API) and remember it
Add 6 months
Fetch the "milliseconds since the Unix epoch" again, and subtract the earlier value from it
If you know exactly from when to when those 6 months reach, you can use a variety of ways to calculate the duration, using java.util.Calendar, JodaTime, or the JDK1.8 time API.
But if you don't have particular dates in mind, you can take an average duration for your month.
No API in the world can change that fact.
For example, the JDK1.8 time API uses this for the duration of a month in seconds: (from java.time.temporal.ChronoUnit)
MONTHS("Months", Duration.ofSeconds(31556952L / 12)),
31,556,952 is the number of a seconds in a year, based on a year that lasts 365.2425 days.
You can use the same number directly and get the same result as with the time API:
long months = 6;
long seconds = months * 31556952L / 12;
long milliseconds = seconds * 1000;
Result:
15,778,476,000
Calendar today = Calendar.getInstance();
Calendar sixMonthsAhead = Calendar.getInstance();
sixMonthsAhead.add(Calendar.MONTH, 6);
long differenceInMilis = sixMonthsAhead.getTimeInMillis() - today.getTimeInMillis();
You could also use...
sixMonthsAhead.add(Calendar.DATE, 180);
// or 183 days because 365 / 2 is approximately 183.
instead of...
sixMonthsAhead.add(Calendar.MONTH, 6);
for a more accurate result. But like Jon has mentioned, it will always vary depending on what day of the year it is.
The answer by Jon Skeet is correct.
Joda-Time
Assuming you could specify a pair of beginning and ending points on a time line, here is some example code using the Joda-Time 2.3 library.
This code grabs the current moment, adjusts to first of the month, and adjusts to first moment of that day. Then it adds 6 months. Joda-Time is smart about adding the months, taking into account leap year and various lengths of months. This span of 6 months is then represented as an Interval instance. From that we calculate the number of milliseconds. Note that count of milliseconds needs to be a long (64-bit) rather than an int (32-bit) we Java programmers more commonly use. Lastly, for fun, we see what this span of time looks like when formatted in the ISO 8601 standard’s "Duration" format.
DateTimeZone dateTimeZone = DateTimeZone.forID( "Europe/Paris" ); // Better to specify a time zone than rely on JVM’s default.
DateTime start = new DateTime( dateTimeZone ).withDayOfMonth( 1 ).withTimeAtStartOfDay();
DateTime stop = start.plusMonths( 6 );
Interval interval = new Interval( start, stop );
long milliseconds = interval.toDurationMillis(); // A long, not an int.
Period period = interval.toPeriod(); // For fun… ISO 8601 standard's Duration format.
Dump to console…
System.out.println("start: " + start );
System.out.println("stop: " + stop );
System.out.println("interval: " + interval );
System.out.println("milliseconds: " + milliseconds );
System.out.println("period: " + period );
When run…
start: 2014-04-01T00:00:00.000+02:00
stop: 2014-10-01T00:00:00.000+02:00
interval: 2014-04-01T00:00:00.000+02:00/2014-10-01T00:00:00.000+02:00
milliseconds: 15811200000
period: P6M
We have intervals (elapsed time between two oracle timestamps) stored in our database as seconds and we format them at front end with Java.
What we would to achieve on the reports is a format of the form "HH:MM" or "HH:MM:SS", with the time separator ":" localized as it happens with dates and time information, i.e '.' for Italian and ':' for English.
Unfortunately the date-related formatting classes, like SimpleDateFormat, do not work** because we can expect durations above the 24 hours.
We don't want to employ 3rdy party library as well.
Do you know how we can solve this problem?
TIA
If you want hours of more than 24 you can print this separately.
int hour = time / 3600000;
String duration = time + new SimpleDateFormat(":mm:ss").format(new Date(time));
To support other locales you could do this more complicated example.
int hour = time / 3600000;
String duration = hour
+ DateFormat.getDateInstance(DateFormat.MEDIUM, locale)
.format(new Date(time % 3600000).substring(1);
What this will do is use the locale specific format for the last digit of the hour + mins + secs and prepend the additional digits of the hours. Note: this will not work for negative times.
You can construct a date object, say "2000-01-01", then add your seconds to it. Then you can use this to extract the localized version of the time:
DateFormat format = DateFormat.getTimeInstance(DateFormat.MEDIUM, locale);
String formatted_time_part = format.format(some_date_object);
Finally you still need to add elapsed days! I'm affraid that localization of long intervals (days, months, years, centuries) has no corresponding API in Java but I might be wrong. So you will have to figure out that for yourself.
I was just wondering if there is a need of TimeSpan in java.util so that I can define how much hours,minutes and seconds are there in between these two times.
From this TimeSpan we can have a time interval between two times. like
TimeSpan getTimeSpan( Date before, Date after ){...}
or
long timeSpan = System.currentTimeMillis();
// ... long job
timeSpan = System.currentTimeMillis() - timeSpan;
TimeSpan ts = new TimeSpan(timeSpan);
and with this TimeSpan we can use it in SimpleDateFormat.
SimpleDateFormat format = new SimpleDateFormat("HH:mm:ss");
format.format( timsSpan );
I am not sure if this is already been implemented in Java but yet undiscovered by me.
With JDK 8 date-time libraries in SDK has been enriched and you can use
Duration or Period
Interval from JodaTime will do..
A time interval represents a period of
time between two instants. Intervals
are inclusive of the start instant and
exclusive of the end. The end instant
is always greater than or equal to the
start instant.
Intervals have a fixed millisecond
duration. This is the difference
between the start and end instants.
The duration is represented separately
by ReadableDuration. As a result,
intervals are not comparable. To
compare the length of two intervals,
you should compare their durations.
An interval can also be converted to a
ReadablePeriod. This represents the
difference between the start and end
points in terms of fields such as
years and days.
Interval is thread-safe and immutable.
In Java 8 a proper time library has been added to the standard API (this is based heavily on JodaTime).
In it there are two classes that you can use to indicate a period:
Duration which indicates a seconds or nanoseconds length of a timespan.
Period which indicates a more user-friendly difference, stored as 'x years and y months etc'.
A detailed explanation of the difference between them can be found in the Java tutorial
If you're on on Java 8 (or higher) or simply don't want to import JodaTime (the Author of JodaTime himself suggest migrating to java.time): Java 8 offers that functionality with Periods, see here for a tutorial: https://docs.oracle.com/javase/tutorial/datetime/iso/period.html
Let me quote the Oracle tutorial here:
LocalDate today = LocalDate.now();
LocalDate birthday = LocalDate.of(1960, Month.JANUARY, 1);
Period p = Period.between(birthday, today);
long p2 = ChronoUnit.DAYS.between(birthday, today);
System.out.println("You are " + p.getYears() + " years, " + p.getMonths() +
" months, and " + p.getDays() +
" days old. (" + p2 + " days total)");
The code produces output similar to the following:
You are 53 years, 4 months, and 29 days old. (19508 days total)
If you are looking for an alternative lighter version, have a look at this library that I wrote to use in my own Android app. https://github.com/ashokgelal/samaya
Sorry, I don't have any documentation on its usage, but it is very similar to the counterpart .net Timespan class. Also, there are some unit tests which contains many examples on how to use it.