Convert x hour(s) y min(s) into z minutes using Java - 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.

Related

Duration of minutes not working correctly in Java/Android

I am trying to display a rest time value in hours and minutes to my UI on an application I am finishing up.. I made use of another StackOverflow forum to figure this out and so far it is working okay, the time is displaying, but I think the maths is wrong somewhere..
I have two TextViews, one which displays active time in minutes, and another which displays rest time in hours and minutes (as the rest time will normally be a significantly longer period). The rest time is essentially 24hrs minus the active time.
So far, I have converted my stored active time string to an Int, subtracted that from the minute value for 24 hours (1440 minutes), then used the Duration method to convert that value to hours and minutes (hh:MM) which worked fine, the UI showed the correct subtracted value in hh:MM.
My problem is when I tried to split this into parts using a string, where my string is (restHours + " h" + restMinutes + " minutes") I get the hours and minutes value for the entire restTimeInt value (for example: 23h 1430minutes).
How can I change this so it displays the correct minutes value (for example: 23hr 40min)?
int activeTimeValue = Integer.parseInt(activityTime); // ex: 10 mins
int day = 1440;
int restTimeInt = day - activeTimeValue; // 1430 mins
Duration d = Duration.ofMinutes(restTimeInt);
int restHours = (int) d.toHours();
int restMinutes = (int) d.toMinutes();
restTimeTV.setText(restHours + " hr" + restMinutes + " mins");
Duration.minus() and Duration.toMinutesPart()
Now you’re at it, why not go all in on Duration and let it handle all math for you? With a little help it can even parse your string of minutes.
String activityTimeStr = "10"; // Minutes
Duration activityTime = Duration.parse("PT" + activityTimeStr + "M");
Duration restTime = Duration.ofDays(1).minus(activityTime);
System.out.format("Rest time: %s%n", restTime);
Output is:
Rest time: PT23H50M
So 23 hours 50 minutes, as expected. If you need those numbers, 23 and 50, for example for formatting for the user, as deHaar said, use the toMinutesPart method of Duration:
int restHours = Math.toIntExact(restTime.toHours());
int restMinutes = restTime.toMinutesPart();
System.out.format("Rest time: %d hours %d minutes%n", restHours, restMinutes);
Rest time: 23 hours 50 minutes

Parse humanreadble time to milliseconds in Java

I am trying to convert a string like, 4hours or 1day or 2months to milliseconds. But how would I do this efficiently without having to check if string.contains("h") etc....?
Node has ms, would Java happen to have something similar?
Edit based on some responses: Yes the actual number can change, I gave 4 hours as an example but that can be 1, 2, 3, 5 etc. Same for the days and months. And yes they are directly next to each other. No spaces.
Example string: remind 1h Go to school
The output should be: 3 600 000 (milliseconds)
Well, you can break this down into smaller pieces.
Find such occurrences within the string;
Take both the number part and the unit part and multiply their values in order to get the milliseconds.
To find the occurrences, you could use regular expressions. A regular expression like (-?\d+)(h|days|min|sec) would match such strings. Using Pattern and Matcher would help.
To convert to number to milliseconds, the units need to be mapped to a value matching the number of milliseconds for that unit. For example, days would map to 86400000.
While it’s not built in, I don’t find it that bad.
String[] exampleStrings = { "4hours", "1day", "2months" };
for (String example : exampleStrings) {
long millis;
if (example.contains("hour") || example.contains("minute")
|| example.contains("second")) {
String durationString = "PT" + unitToAbbreviation(example);
Duration dur = Duration.parse(durationString);
millis = dur.toMillis();
} else {
String periodString = "P" + unitToAbbreviation(example);
Period per = Period.parse(periodString);
Duration estimatedDuration = Duration.ZERO;
for (TemporalUnit unit : per.getUnits()) {
estimatedDuration = estimatedDuration.plus(
unit.getDuration().multipliedBy(per.get(unit)));
}
millis = estimatedDuration.toMillis();
}
System.out.format("%7s -> %10d milliseconds%n", example, millis);
}
Output from this sample is:
4hours -> 14400000 milliseconds
1day -> 86400000 milliseconds
2months -> 5259492000 milliseconds
I have assumed that you have got only one number and one unit in the string. It may be 2months or 1day but not 2months1day.
The length of days, months and years are inaccurate estimates since their real length vary. A month may be from 28 to 31 days, and a day may be from 23 to 25 hours.
I am using the following little auxiliary method for converting 4hours into just 4h as required by Duration.parse():
String unitToAbbreviation(String withFullUnit) {
return withFullUnit.replaceFirst("(\\d+[a-z])[a-z]*", "$1");
}
In case of a Period (a day-based rather than a time-based interval) I am iterating over the units that a Period supports — years, months and days. For each I am using TemporalUnit.getDuration() to get its estimated duration and Period.get() for getting the corresponing value. The call to mulitpliedBy() (don’t be surprised) multiplies the two.

JAVA Hours to duration

I have an app where i need to check if travel is in duration between 4 hours and 30 hours, I store it as a strings "04:00" and "30:00", then i try to parse it using LocalTime.parse(), "04:00" is parsed successfully but "30:00" gets an error for invalid format, what could be best way to parse duration hours from a string ?
First of all, you're storing it somehow wrong. I suggest to store it in way Duration.parse can handle it, in standard ISO 8601 format.
Examples:
"PT20.345S" -- parses as "20.345 seconds"
"PT15M" -- parses as "15 minutes" (where a minute is 60 seconds)
"PT10H" -- parses as "10 hours" (where an hour is 3600 seconds)
"P2D" -- parses as "2 days" (where a day is 24 hours or 86400 seconds)
"P2DT3H4M" -- parses as "2 days, 3 hours and 4 minutes"
"P-6H3M" -- parses as "-6 hours and +3 minutes"
"-P6H3M" -- parses as "-6 hours and -3 minutes"
"-P-6H+3M" -- parses as "+6 hours and -3 minutes"
So then you can just do:
Duration dur30H = Duration.parse("PT30H"); // 30h
Duration dur4H = Duration.parse("PT4H"); // 4h
Duration travelTime = Duration.parse("P1D"); // 1D
boolean result = travelTime.compareTo(dur30H) <= 0 && travelTime.compareTo(dur4H) >= 0; // true

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));
}
}

Categories