Convert length of time to appropriate unit - java

I have a time, in seconds, which has the possibility to be very large. I wish to convert said time into the "appropriate" rounded, readable format.
I already have code which achieves this, however it's not very efficient (and contains a whole bunch of magic numbers):
String readable = decayTime + " minutes";
if(decayTime > 60)
{
decayTime /= 60;
readable = decayTime + " hours";
if(decayTime > 24)
{
decayTime /= 24;
readable = decayTime + " days";
if(decayTime > 365)
{
decayTime /= 365;
readable = decayTime + " years";
if(decayTime > 1000000)
{
decayTime /= 1000000;
readable = decayTime + "mn years";
if(decayTime > 1000)
{
decayTime /= 1000;
readable = decayTime + "bn years";
}
}
}
}
Apart from switching out the magic numbers, I can't personally think how to make it better.
What I am asking is essentially what would be a better approach to this, or is there something in-built which could help?
Edit: moved to here https://codereview.stackexchange.com/questions/139970/convert-length-of-time-to-appropriate-unit

You can use Duration but it doesn't convert to years.
Duration.ofSeconds(decayTime).toNanos();
Duration.ofSeconds(decayTime).toMillis();
Duration.ofSeconds(decayTime).toMinutes();
Duration.ofSeconds(decayTime).toHours();
Duration.ofSeconds(decayTime).toDays();
See https://docs.oracle.com/javase/tutorial/datetime/iso/period.html and https://docs.oracle.com/javase/8/docs/api/java/time/Duration.html
Another option is:
TimeUnit.SECONDS.toNanos(decayTime);
TimeUnit.SECONDS.toMicros(decayTime);
TimeUnit.SECONDS.toMillis(decayTime);
TimeUnit.SECONDS.toMinutes(decayTime);
TimeUnit.SECONDS.toHours(decayTime);
TimeUnit.SECONDS.toDays(decayTime);
For the year you could use some hack like:
Long.parseLong(new SimpleDateFormat("YYYY").format(new Date(Duration.ofSeconds(decayTime).toMillis())));
But I strongly don't recomend it.

ISO 8601
FYI, the ISO 8601 standard defines sensible non-ambiguous formats for date-time values. This includes a compact way to represent a span of time: PnYnMnDTnHnMnS
The P marks the beginning, the T separates the years-months-days from hours-minutes-seconds.
Examples:
PT1H30M = An hour and a half.
P3Y6M4DT12H30M5S = three years, six months, four days, twelve hours, thirty minutes, and five seconds
java.time
The java.time classes in Java 8 and later use ISO 8601 formats by default when parsing or generating strings to represent date-time values.
The java.time classes include a pair of classes for spans of time. Both can parse/generate this ISO 8601 format.
Period for years, months, and days.
Duration for days, hours, minutes, seconds, and fraction of second.
Your scale of minutes to billions of years is too extreme for this obviously, but this does suit the more mundane business world outside of science.

The other answers are useful for shorter lengths of time, but without some ugly hacks and likely a block of code less useful for the longer periods I require. #David Wallace over on Code Review gave an answer which used a TreeMap:
import java.util.Map;
import java.util.NavigableMap;
import java.util.TreeMap;
import java.util.concurrent.TimeUnit;
public class TimeFormatter {
private NavigableMap<Long,String> timeUnits = new TreeMap<>();
public TimeFormatter() {
timeUnits.put(Long.MIN_VALUE, " is not a valid argument");
timeUnits.put(TimeUnit.SECONDS.toSeconds(1), " seconds");
timeUnits.put(TimeUnit.MINUTES.toSeconds(1), " minutes");
timeUnits.put(TimeUnit.HOURS.toSeconds(1), " hours");
timeUnits.put(TimeUnit.DAYS.toSeconds(1), " days");
timeUnits.put(TimeUnit.DAYS.toSeconds(365), " years");
timeUnits.put(TimeUnit.DAYS.toSeconds(365 * 1000000L), " million years");
timeUnits.put(TimeUnit.DAYS.toSeconds(365 * 1000000L * 1000), " billion years");
}
public String format(long milliseconds) {
Map.Entry<Long,String> unitBelow = timeUnits.floorEntry(milliseconds);
int time = (int) (milliseconds / unitBelow.getKey());
String formatted = time + unitBelow.getValue();
if(time == 1 && unitBelow.getKey() < 1000000L) formatted = formatted.substring(0, formatted.length()-1); //Remove plural
return formatted;
}
}
I have changed David's answer to use TimeUnit to cut down on the number of magic numbers, as well as dealing with plurals.
EDIT: Some magic numbers do obviously still exist, and actually the code required to deal with larger units are not as bad as I expected.

Related

Calculate the difference between two dates: not working as expected

This is my code:
public enum MILLI_CONVERSIONS {
//values in milliseconds
SECOND(1000),
MINUTE(1000*60),
HOUR(1000*60*60),
DAY(1000*60*60*24),
WEEK(1000*60*60*24*7),
MONTH(1000*60*60*24*30),
YEAR(1000*60*60*24*31*12);
private final long millis;
private MILLI_CONVERSIONS(final long millis) {
this.millis = millis;
}
public long getMillis() {
return millis;
}
}
public static String getDateDifferenceFromToday(Date startDate){
Date today = new Date();
long delta = today.getTime() - startDate.getTime();
Integer diff;
String diffText = "";
//seconds
if (delta < MILLI_CONVERSIONS.MINUTE.getMillis()){
diff = Integer.valueOf((int) (delta/MILLI_CONVERSIONS.SECOND.getMillis()));
diffText = diff.toString() + " seconds ago";
}
//minutes
else if (delta < MILLI_CONVERSIONS.HOUR.getMillis()){
diff = Integer.valueOf((int) (delta/MILLI_CONVERSIONS.MINUTE.getMillis()));
diffText = diff.toString() + " minutes ago";
}
//hours
else if (delta < MILLI_CONVERSIONS.DAY.getMillis()){
diff = Integer.valueOf((int) (delta/MILLI_CONVERSIONS.HOUR.getMillis()));
diffText = diff.toString() + " hours ago";
}
//days
else if (delta < MILLI_CONVERSIONS.WEEK.getMillis()){
diff = Integer.valueOf((int) (delta/MILLI_CONVERSIONS.DAY.getMillis()));
diffText = diff.toString() + " days ago";
}
// weeks
else if(delta < MILLI_CONVERSIONS.MONTH.getMillis()){
diff = Integer.valueOf((int) (delta/MILLI_CONVERSIONS.WEEK.getMillis()));
diffText = diff.toString() + " weeks ago";
}
// months
else if(delta < MILLI_CONVERSIONS.YEAR.getMillis()){
diff = Integer.valueOf((int) (delta/MILLI_CONVERSIONS.MONTH.getMillis()));
diffText = diff.toString() + " months ago";
}
// years
else{
diff = Integer.valueOf((int) (delta/MILLI_CONVERSIONS.YEAR.getMillis()));
diffText = diff.toString() + " years ago";
}
return diffText;
}
It seems to work fine untill I hit weeks. When it is even a week away, it shows 0 months. And then when it is month, it seemingly gets the right amount of months but it shows "years ago" (ex. if it was two months ago, instead of "2 months ago", it says "2 years ago". And it never hits years, it just keeps going with months (ex. if it was 16 months ago, it should say "1 years ago", but it says "16 years ago").
I am using this with my Android app, so if there are any libraries that do this, or I am missing some built in way to calculate this, please let me know.
Thanks.
When you define your enum constants, you need to do the calculations with long integers since you overflow the integer range. So for example use
YEAR(1000L*60*60*24*31*12)
instead of
YEAR(1000*60*60*24*31*12)
Specifying the first number as a long is enough that the calculations are carried out with data type long.
You are working way too hard. Leverage either the Joda-Time library or the new java.time package in Java 8.
In general, you should forget about milliseconds. Work with date-time and time zone values so as to let Joda-Time help you with Daylight Saving Time (DST) and other anomalies. But if you really are starting from milliseconds, you can use a long with Joda-Time.
Period period = new Period( myMillis );
When you care about a span of time in terms of a number of months, days, hours, and such, use the Period class. The PeriodFormatterBuilder class will help you generate a representation as descriptive words such as "15 years and 8 months".
PeriodFormatter yearsAndMonths = new PeriodFormatterBuilder()
.printZeroAlways()
.appendYears()
.appendSuffix(" year", " years")
.appendSeparator(" and ")
.printZeroRarelyLast()
.appendMonths()
.appendSuffix(" month", " months")
.toFormatter();
When you have points on the timeline of the Universe (date-time values), use the Interval class.
Interval interval = new Interval( startDateTime, stopDateTime );
You can capture the span of time inside that interval as a Period.
Period period = interval.toPeriod();
You might find handy the ISO 8601 standard’s definition of a compact and standard way to represent a number of months, days, etc. as a string. The standard calls this a "duration" (not the same term in Joda-Time). The format is PnYnMnDTnHnMnS where P (for Period) marks the beginning, each n is a number, and T separates date portion from time portion. Example: P3Y6Mis three and a half years. In Joda-Time, the Period class knows how to parse and generate such strings.
Search StackOverflow to find many more examples. You are not the first to ask.

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.

What is the most efficient way to convert long to HH:mm:ss and display it in a label?

I'm wanting to have my JLabel display values in the format of HH:mm:ss without making use of any external libraries. (the label will update every second)
So for example, the following input in seconds and the desired output are below:
Seconds: Output:
--------------------------------------------------
long seconds = 0 00:00:00
long seconds = 5 00:00:05
long seconds = 500 00:08:20
long seconds = 5000 01:23:20
Note: the seconds value is of type long
I'm aware that typically one would just do the following conversions to get the desired numbers:
long s = 5000; //total seconds
long hrs = (s / 3600) //hours
long mins = ((s%3600)/60) //minutes
long secs = (s%60) //seconds
However, this leaves decimals on the values. Perhaps there is some sort of formatting that will allow me to toss the un-needed decimals.
Options I have come across were String.format(), SimpleDateFormat(), or concatenating a string myself.
The thing is, I will be updating this JLabel every second and sometimes it can count to the equivalent of 5-6 days if not longer.
So I'm looking for someone who has more experience in the area than I, and knows the most efficient way to tackle this issue.
I would use SimpleDateFormat if I were you.
If SDF is too slow for you, profile all your options and pick the fastest one, then refactor the rest of your code until it's fast enough.
Remember that premature optimization is the root of all evil, and that you should only really do any optimizing after you've profiled your code and missed your target execution time.
SimpleDateFormat() is really quite appropriate for your needs.
Use the TimeUnit class, as shown here in combination with the javax.swing.Timer class set to execute at 1 second intervals.
If you don't mind values wrapping then use SimpleDateFormat as follows. Remember x1000 to convert to milliseconds and to manually override the timezone.
long value = 5 * 24 * 3600 + 5000;
// wrapping solution
SimpleDateFormat sdf = new SimpleDateFormat("HH:mm:ss");
// ensure no daylight saving +1 hour
sdf.setTimeZone(TimeZone.getTimeZone("GMT"));
System.out.println(sdf.format(value * 1000));
Output
01:23:20
If you want the hours to go past 23.59.59 then this is the simplest I could come up with. I used DecimalFormat to force at least 2 digits for the hours.
long value = 5 * 24 * 3600 + 5000;
long hours = value / 3600; // whole hours
long mins = value / 60 - hours * 60;
long secs = value % 60;
System.out.println(String.format("%s:%2d:%2d",
new DecimalFormat("00").format(hours), mins, secs));
Output
121:23:20
I've found this to be extremely fast. Try it out. Seconds go from 0 - 59, minutes go from 0 - 59, hours go from 0 - 2,562,047,788,015. Afterwards the hours become negative and begin going towards that maximum.
performing the "+" operator on Strings is very slow. A StringBuilder performs grouping strings together the fastest from what I've seen. You should also be using "chars" not "String/Byte" Bytes are very slow as well. I'd prefer doing only multiplication however dividing by 36 and 6 give decimals that are to large for holding.
StringBuilder sb = new StringBuilder(8);
long hours = time / 3600000;
long minutes = (time - hours * 3600000) / 60000;
long seconds = (time - hours * 3600000 - minutes * 60000) / 1000;
if (hours < 10)
sb.append('0');
sb.append(hours);
sb.append(':');
if (minutes < 10)
sb.append('0');
sb.append(minutes);
sb.append(':');
if (seconds < 10)
sb.append('0');
sb.append(seconds);
String formattedTime = sb.toString();
.....
If you don't want to use a formatter class, you can get your work done by using basic operations like conversion among wrapper classes and String operations. Take a look at this code:
long h, m, s; // Initialize them after calculation.
String h1, m1, s1;
h1 = Long.toString( h );
m1 = Long.toString( m );
s1 = Long.toString( s );
if ( s1.length() < 2 )
s1 = "0" + s1;
if ( m1.length() < 2 )
m1 = "0" + m1;
if ( h1.length() < 2 )
h1 = "0" + h1;
String output = h1+":"+m1+":"+s1;
Supposing you have correctly calculated values of seconds, minutes and hours, you can gather String versions of these variables, then format them with a simple length check and finally concatenate these time unit parts.
i think you want to do the math you indicated, but take the floor of each value. then concatenate..
public class Test{
public static void main(String args[]){
double d = -100.675;
float f = -90;
System.out.println(Math.floor(d));
System.out.println(Math.floor(f));
System.out.println(Math.ceil(d));
System.out.println(Math.ceil(f));
}
}

Calculate elapsed time in Java / Groovy

I have...
Date start = new Date()
...
...
...
Date stop = new Date()
I'd like to get the years, months, days, hours, minutes and seconds ellapsed between these two dates.
--
I'll refine the question.
I just want to get the elapsed time, as an absolute measure, that is without taking into account leap years, the days of each month, etc.
Thus I think it's impossible to get the years and months elapsed, all I can get is days, hours, minutes and seconds.
More specifically I want to tell that a certain task lasted for e.g.
20 sec
13 min, 4 sec
2 h, 10 min, 2 sec
4 d, 4 h, 2 min, 2 sec
So please forgive my lack of precision.
I've just discovered this quick Groovy-sourced solution:
import groovy.time.TimeCategory
import groovy.time.TimeDuration
Date start = new Date()
// do something here
Date stop = new Date()
TimeDuration td = TimeCategory.minus( stop, start )
println td
You can do all of this with division and mod.
long l1 = start.getTime();
long l2 = stop.getTime();
long diff = l2 - l1;
long secondInMillis = 1000;
long minuteInMillis = secondInMillis * 60;
long hourInMillis = minuteInMillis * 60;
long dayInMillis = hourInMillis * 24;
long elapsedDays = diff / dayInMillis;
diff = diff % dayInMillis;
long elapsedHours = diff / hourInMillis;
diff = diff % hourInMillis;
long elapsedMinutes = diff / minuteInMillis;
diff = diff % minuteInMillis;
long elapsedSeconds = diff / secondInMillis;
That should give you all of the information you requested.
EDIT: Since people seem to be confused, no, this does not take things like leap years or daylight savings time switches into account. It is pure elapsed time, which is what opensas asked for.
Not so easy with the standard Date API.
You might want to look at Joda Time, or JSR-310 instead.
I'm not an expert in Joda, but I think the code would be:
Interval interval = new Interval(d1.getTime(), d2.getTime());
Period period = interval.toPeriod();
System.out.printf(
"%d years, %d months, %d days, %d hours, %d minutes, %d seconds%n",
period.getYears(), period.getMonths(), period.getDays(),
period.getHours(), period.getMinutes(), period.getSeconds());
Regarding JodaTime I just got it going; thanks to the responder who suggested it. Here's a more condensed version of the Joda code suggested:
Period period = new Period(d1.getTime(), d2.getTime());
System.out.printf(
"%d years, %d months, %d days, %d hours, %d minutes, %d seconds%n",
period.getYears(), period.getMonths(), period.getDays(),
period.getHours(), period.getMinutes(), period.getSeconds());
(not sure if this is helping the original question but certainly searchers).
tl;dr
Duration.between( then , Instant.now() )
Using java.time
The modern way uses the java.time classes that supplant the troublesome old date-time classes.
Instead of Date, use Instant. The Instant class represents a moment on the timeline in UTC with a resolution of nanoseconds (up to nine (9) digits of a decimal fraction).
Instant then = Instant.now();
…
Instant now = Instant.now();
Use the Duration class for a span of time unattached to the timeline, with resolution of day-hours-minutes-seconds-nanos.
Duration d = Duration.between( then , now );
For a span of time with resolution of years-months-days, use the Period class.
Generate a string is standard ISO 8601 format for durations: PnYnMnDTnHnMnS. The P marks the beginning. The T separates any years-months-days from hours-minutes-seconds. So two and a half hours is PT2H30M.
String output = d.toString();
In Java 9 and later, you can access the individual parts with methods toDaysPart, toHoursPart, and so on.
Also in Java 9 and later is an improvement to Instant.now where it captures the current moment in microseconds rather than the milliseconds seen in Java 8. Of course in all versions of Java the Instant class can hold a value in nanoseconds.
About java.time
The java.time framework is built into Java 8 and later. These classes supplant the troublesome old legacy date-time classes such as java.util.Date, Calendar, & SimpleDateFormat.
The Joda-Time project, now in maintenance mode, advises migration to the java.time classes.
To learn more, see the Oracle Tutorial. And search Stack Overflow for many examples and explanations. Specification is JSR 310.
Where to obtain the java.time classes?
Java SE 8 and SE 9 and later
Built-in.
Part of the standard Java API with a bundled implementation.
Java 9 adds some minor features and fixes.
Java SE 6 and SE 7
Much of the java.time functionality is back-ported to Java 6 & 7 in ThreeTen-Backport.
Android
The ThreeTenABP project adapts ThreeTen-Backport (mentioned above) for Android specifically.
See How to use ThreeTenABP….
The ThreeTen-Extra project extends java.time with additional classes. This project is a proving ground for possible future additions to java.time. You may find some useful classes here such as Interval, YearWeek, YearQuarter, and more.
Well since Java 1.5 you should use TimeUnit.
Here is a simple & plain example for this. I think in groovy it might get shorter(as always).
/**
* Formats a given {#link Date} to display a since-then timespan.
* #param created
* #return String with a format like "3 minutes ago"
*/
public static String getElapsedTime(Date created) {
long duration = System.currentTimeMillis() - created.getTime();
long seconds = TimeUnit.MILLISECONDS.toSeconds(duration);
long days = TimeUnit.MILLISECONDS.toDays(duration);
long hours = TimeUnit.MILLISECONDS.toHours(duration);
long minutes = TimeUnit.MILLISECONDS.toMinutes(duration);
if (days > 0) {
return days + " days";
}
if (hours > 0) {
return hours + " hrs";
}
if (minutes > 0) {
return minutes + " minutes";
}
return seconds + " seconds";
}
Oh and avoid multiple returns please ;)
Actually, regarding the above answers about Joda-Time.
There's an easier way to do this with Joda-Time’s Period class:
Period period = new Period(startDate, endDate);
System.out.println(PeriodFormat.getDefault().print(period));
To customize the output, look into the PeriodFormat, PeriodFormatter, and PeriodFormatterBuilder classes.
Hmm, if I get what you're asking, you want to know that if:
start = Jan 1, 2001, 10:00:00.000 am and
stop = Jan 6, 2003, 12:01:00.000 pm
you want an answer of 2 years, 0 months, 5 days, 2 hours, 1 minute
Unfortunately, this is a specious answer. What if the dates were Jan 2, and Jan 31? Would that be 29 days? Ok, but Feb 2 to Mar 2 is 28 (29) days, but would be listed as 1 month?
The length of time in anything other than seconds or possibly days is variable without knowing the context since months and years are of different lengths. The difference between 2 dates should be in static units, which are easily computable from stop.getTime() - start.getTime() (which is the difference in millisecs)
Apart from the aforementioned great JodaTime API which I do recommend, the best standard Java alternative you can have is the java.util.Calendar. It is cumbersome to work with it (this is an understatement .. look at the single-line JodaTime examples here), but you can calculate the elapsed time with it as well. Important key is that you should use the Calendar#add() in a loop to get the elapsed value for years, months and days to take leap days, years and centuries into account. You should not calculate them back from the (milli)seconds.
Here's a basic example:
import java.util.Calendar;
public class Test {
public static void main(String[] args) throws Exception {
Calendar start = Calendar.getInstance();
start.set(1978, 2, 26, 12, 35, 0); // Just an example.
Calendar end = Calendar.getInstance();
Integer[] elapsed = new Integer[6];
Calendar clone = (Calendar) start.clone(); // Otherwise changes are been reflected.
elapsed[0] = elapsed(clone, end, Calendar.YEAR);
clone.add(Calendar.YEAR, elapsed[0]);
elapsed[1] = elapsed(clone, end, Calendar.MONTH);
clone.add(Calendar.MONTH, elapsed[1]);
elapsed[2] = elapsed(clone, end, Calendar.DATE);
clone.add(Calendar.DATE, elapsed[2]);
elapsed[3] = (int) (end.getTimeInMillis() - clone.getTimeInMillis()) / 3600000;
clone.add(Calendar.HOUR, elapsed[3]);
elapsed[4] = (int) (end.getTimeInMillis() - clone.getTimeInMillis()) / 60000;
clone.add(Calendar.MINUTE, elapsed[4]);
elapsed[5] = (int) (end.getTimeInMillis() - clone.getTimeInMillis()) / 1000;
System.out.format("%d years, %d months, %d days, %d hours, %d minutes, %d seconds", elapsed);
}
private static int elapsed(Calendar before, Calendar after, int field) {
Calendar clone = (Calendar) before.clone(); // Otherwise changes are been reflected.
int elapsed = -1;
while (!clone.after(after)) {
clone.add(field, 1);
elapsed++;
}
return elapsed;
}
}
It should print my age as of now =)
Oh, I should add, you can "convert" Date to Calendar using Calendar#setTime().
import groovy.time.TimeCategory
import groovy.time.TimeDuration
time = { closure ->
use(TimeCategory) {
def started = new Date()
def res = closure()
TimeDuration duration = new Date() - started
logger.info("Execution duration: " + duration.toMilliseconds() + "ms")
res
}
}
time {
println 'A realy heavy operation here that you want to measure haha'
}
It's easy; You should set the right timezone
import java.util.TimeZone;
import java.util.logging.Logger;
import org.joda.time.DateTimeZone;
import org.joda.time.format.DateTimeFormat;
import org.joda.time.format.DateTimeFormatter;
public class ImportData {
private final static Logger log = Logger.getLogger(ImportData.class.getName());
private final static DateTimeFormatter dtf = DateTimeFormat.forPattern("yyyy-MM-dd HH:mm:ss.SSS");
private final static DateTimeFormatter dtfh = DateTimeFormat.forPattern("HH:mm:ss.SSS");
/**
* #param args the command line arguments
*/
public static void main(String[] args) throws Exception {
TimeZone.setDefault(TimeZone.getTimeZone("Europe/Berlin"));
DateTimeZone.setDefault(DateTimeZone.forID("Europe/Berlin"));
// Quotes connection=Quotes.getInstance();
final long start = System.currentTimeMillis();
// do something here ...
// connection.importTickdata();
Thread.currentThread().sleep(2000);
final long end = System.currentTimeMillis();
log.info("[start] " + dtf.print(start));
log.info("[end] " + dtf.print(end));
TimeZone.setDefault(TimeZone.getTimeZone("UTC"));
DateTimeZone.setDefault(DateTimeZone.forID("UTC"));
log.info("[duration] " + dtfh.print(end - start));
// connection.logOff();
// System.exit(0);
}
returns:
10.11.2010 00:08:12 ImportData main
INFO: [start] 2010-11-10 00:08:10.306
10.11.2010 00:08:12 ImportData main
INFO: [end] 2010-11-10 00:08:12.318
10.11.2010 00:08:12 ImportData main
INFO: [duration] 00:00:02.012
I do this in general:
def start_time = System.currentTimeMillis()
...
def end_time = System.currentTimeMillis()
println (end_time - start_time) +' ms'
Then you can break that into any time unit that you want using the Duration Groovy class http://docs.groovy-lang.org/latest/html/api/groovy/time/Duration.html.
There is no point creating a Date object as all this does is wrap System.currentTimeMillis(). The getTime() function just unwraps the Date object. I suggest you just use this function to obtain a long.
If you only need second accuracy, this is fine. However if you want sub-millisecond accuracy use System.nanoTime() to get the elapse time.
This is a complete function I implemented based on Sebastian Celis answer. And again, from his post - this does not take things like leap years or daylight savings time switches into account. It is pure elapsed time.
The output is tailored towards my need. This only outputs three significant sections. Instead of returning
4 months, 2 weeks, 3 days, 7 hours, 28 minutes, 43 seconds
It just returns first 3 block (see more sample run at the end of post):
4 months, 2 weeks, 3 days
Here is the complete method source code:
/**
* Format milliseconds to elapsed time format
*
* #param time difference in milliseconds
* #return Human readable string representation - eg. 2 days, 14 hours, 5 minutes
*/
public static String formatTimeElapsedSinceMillisecond(long milisDiff) {
if(milisDiff<1000){ return "0 second";}
String formattedTime = "";
long secondInMillis = 1000;
long minuteInMillis = secondInMillis * 60;
long hourInMillis = minuteInMillis * 60;
long dayInMillis = hourInMillis * 24;
long weekInMillis = dayInMillis * 7;
long monthInMillis = dayInMillis * 30;
int timeElapsed[] = new int[6];
// Define time units - plural cases are handled inside loop
String timeElapsedText[] = {"second", "minute", "hour", "day", "week", "month"};
timeElapsed[5] = (int) (milisDiff / monthInMillis); // months
milisDiff = milisDiff % monthInMillis;
timeElapsed[4] = (int) (milisDiff / weekInMillis); // weeks
milisDiff = milisDiff % weekInMillis;
timeElapsed[3] = (int) (milisDiff / dayInMillis); // days
milisDiff = milisDiff % dayInMillis;
timeElapsed[2] = (int) (milisDiff / hourInMillis); // hours
milisDiff = milisDiff % hourInMillis;
timeElapsed[1] = (int) (milisDiff / minuteInMillis); // minutes
milisDiff = milisDiff % minuteInMillis;
timeElapsed[0] = (int) (milisDiff / secondInMillis); // seconds
// Only adds 3 significant high valued units
for(int i=(timeElapsed.length-1), j=0; i>=0 && j<3; i--){
// loop from high to low time unit
if(timeElapsed[i] > 0){
formattedTime += ((j>0)? ", " :"")
+ timeElapsed[i]
+ " " + timeElapsedText[i]
+ ( (timeElapsed[i]>1)? "s" : "" );
++j;
}
} // end for - build string
return formattedTime;
} // end of formatTimeElapsedSinceMillisecond utility method
Here are some sample test statement:
System.out.println(formatTimeElapsedSinceMillisecond(21432424234L));
// Output: 8 months, 1 week, 1 day
System.out.println(formatTimeElapsedSinceMillisecond(87724294L));
// Output: 1 day, 22 minutes, 4 seconds
System.out.println(formatTimeElapsedSinceMillisecond(123434L));
// Output: 2 minutes, 3 seconds
This is another similar function, It won't show up days, hours, minutes, etc. if its not needed change its literals if needed.
public class ElapsedTime {
public static void main(String args[]) {
long start = System.currentTimeMillis();
start -= (24 * 60 * 60 * 1000 * 2);
start -= (60 * 60 * 1000 * 2);
start -= (60 * 1000 * 3);
start -= (1000 * 55);
start -= 666;
long end = System.currentTimeMillis();
System.out.println(elapsedTime(start, end));
}
public static String elapsedTime(long start, long end) {
String auxRet = "";
long aux = end - start;
long days = 0, hours = 0, minutes = 0, seconds = 0, milliseconds = 0;
// days
if (aux > 24 * 60 * 60 * 1000) {
days = aux / (24 * 60 * 60 * 1000);
}
aux = aux % (24 * 60 * 60 * 1000);
// hours
if (aux > 60 * 60 * 1000) {
hours = aux / (60 * 60 * 1000);
}
aux = aux % (60 * 60 * 1000);
// minutes
if (aux > 60 * 1000) {
minutes = aux / (60 * 1000);
}
aux = aux % (60 * 1000);
// seconds
if (aux > 1000) {
seconds = aux / (1000);
}
milliseconds = aux % 1000;
if (days > 0) {
auxRet = days + " days ";
}
if (days != 0 || hours > 0) {
auxRet += hours + " hours ";
}
if (days != 0 || hours != 0 || minutes > 0) {
auxRet += minutes + " minutes ";
}
if (days != 0 || hours != 0 || minutes != 0 || seconds > 0) {
auxRet += seconds + " seconds ";
}
auxRet += milliseconds + " milliseconds ";
return auxRet;
}
}
This is a problem and an algorithm needs to be made to account for leap years and exact amount of months and days beside years. Interesting how it is simple if only one unit of measure is to be used. For example, total number of days between two days is correct as apposed to reminder number of days after number of months and years is calculate within let's say two decades.
I am currently working on this to provide it from my PML implementation, for example, in the form of:
unemployed <- date.difference[
From = 2009-07-01,
Till = now,
YEARS, MONTHS, DAYS
]: yyyy-MM-dd
$$-> *unemployed -> date.translate[ YEARS, MONTHS, DAYS ] -> print["Unemployed for:", .]
Of course, this would also be useful and required for exact interest rate calculations.

Convert x hour(s) y min(s) into z minutes using Java

I have fetched google maps ETAs (that is, durations) for some routes in the format of x hour(s) y min(s), and also if x > 24 then this format changes into u day(s) v hour(s).
Now I want to compare these values to some other ETAs so all I need to do is convert these format value into a minutes-only value.
Such as I have a value as 4 hours 34 mins, I want to change it into minutes using Java or such as 1 hour 1 min to minutes, there are records where hour indicated as 1 hour and 3 hours and same for mins and days.
Duration lessThanADay = Duration.ofHours(4).plusMinutes(34);
long minutes = lessThanADay.toMinutes();
This yields 274 minutes. The case for more than 24 hours is similar:
Duration moreThanADay = Duration.ofDays(1).plusHours(3);
This time toMinutes() returns 1620.
You can apply and mix plusHours() and plusMinutes()freely depending on which input numbers you’ve got.
EDIT: Your input strings are a bit more complicated. The Duration class can parse strings in ISO 8601 format, it goes like PT3H23M for a period of time of 3 hours 23 minutes. It may feel a little odd at first. However, we can fix your strings into this format:
private static Duration toDuration(String durationString) {
durationString = durationString.replaceAll(" days?", "D");
durationString = durationString.replaceAll(" hours?", "H");
durationString = durationString.replaceAll(" mins?", "M");
durationString = durationString.replace(" ", "");
if (durationString.contains("D")) {
durationString = durationString.replaceFirst("\\d+D", "P$0T");
} else {
durationString = "PT" + durationString;
}
return Duration.parse(durationString);
}
Let’s try this method on your example strings from the comment:
System.out.println(toDuration("3 hours 23 mins"));
System.out.println(toDuration("2 hours 56 mins"));
System.out.println(toDuration("1 hour 1 min"));
System.out.println(toDuration("1 day 18 hours"));
This prints:
PT3H23M
PT2H56M
PT1H1M
PT42H
So all of your strings have been recognized and parsed.
For the comparison, you don’t need to convert to minutes since Duration objects have a natural ordering and can be compared using compareTo, for example:
if (lessThanADay.compareTo(moreThanADay) < 0) {
System.out.println("Less");
}
(This prints Less.) You may find it more natural to compare the long minutes values using < and >, though.
You could split it on space...
String s[]="3 hours 23 mins".split(" ")
Then I'd just normalize everything to minutes
int minutes=0;
for(int i=0;i<=2;i+=2) {
if(s[i+1].startsWith("min"))
minutes+=s[i]
if(s[i+1].startsWith("hour"))
minutes+=s[i]*60
if(s[i+1].startsWith("day"))
minutes+=s[i]*60*24
}
If you were to put the lookup values into a map you could cut 2/3 from that loop, I'd do it that way in Groovy where the additional syntax would be minimal but in java it's a 50/50 if the added awkwardness loweres the redundancy enough to be worth it.

Categories