Parse humanreadble time to milliseconds in Java - 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.

Related

Is there any possible failed edge case, for the following fast way to remove nanoseconds and seconds components from timestamp?

Currently, we are using the following way to remove nanoseconds and seconds components from timestamp.
public static long toMinuteResolution(long timestamp) {
return Instant.ofEpochMilli(timestamp).atZone(ZoneId.systemDefault()).withNano(0).withSecond(0).toInstant().toEpochMilli();
}
The above function shall work correctly for all cases.
However, we are seeking for a way faster function.
We plan to use the following function, which is way faster.
public static long toMinuteResolution(long timestamp) {
return timestamp / 1000 / 60 * 1000 * 60;
}
However, we are not sure the correctness of the function.
Is there any edge case where it will behave incorrect?
TL;DR
Is there any edge case where it will behave incorrect?
Yes, a couple.
The two are equivalent only for timestamps in 1970 and later; for 1969 and earlier they give different results.
The result of the former depends on time zone, that of the latter not, which gives differences in some cases.
The 1970 limit
Your current version, setting seconds and nanoseconds to 0, is rounding down (towards the beginning of time). The optimized version with division and multiplication is rounding towards zero. In this case “zero” it the epoch of the first moment of January 1, 1970 in UTC.
long exampleTimestamp = Instant.parse("1969-12-15T21:34:56.789Z").toEpochMilli();
long with0Seconds = Instant.ofEpochMilli(exampleTimestamp)
.atZone(ZoneId.systemDefault())
.withNano(0)
.withSecond(0)
.toInstant()
.toEpochMilli();
System.out.println("Set seconds to 0: " + with0Seconds);
long dividedAndMultiplied = exampleTimestamp / 1000 / 60 * 1000 * 60;
System.out.println("Divided and multiplied: " + dividedAndMultiplied);
Output from this snippet is (in my time zone and most time zones):
Set seconds to 0: -1391160000
Divided and multiplied: -1391100000
There is a difference of 60 000 milliseconds, a full minute, between the two outputs.
Dependency on time zone
You may have an issue with the definition of what it means to remove seconds. Seconds have not always been the same in all time zones. For example:
ZoneId zone = ZoneId.of("Asia/Kuala_Lumpur");
ZonedDateTime exampleTime = ZonedDateTime.of(1905, 5, 15, 10, 34, 56, 789_000_000, zone);
// Truncation in time zone
long longTzTimestamp = exampleTime.truncatedTo(ChronoUnit.MINUTES)
.toInstant()
.toEpochMilli();
System.out.println("After truncation in " + zone + ": " + longTzTimestamp);
// Truncation in UTC
long longUtcTimestamp = exampleTime.toInstant()
.truncatedTo(ChronoUnit.MINUTES)
.toEpochMilli();
System.out.println("After truncation in UTC: " + longUtcTimestamp);
After truncation in Asia/Kuala_Lumpur: -2039631685000
After truncation in UTC: -2039631660000
There is a difference of 25 seconds (25 000 milliseconds) between the two timestamps. The only difference I made was the order of two operations: truncation to whole minutes and conversion to UTC. How come the result is different? Until June 1, 1905 Malaysia was at offset +06:55:25 from GMT. So when the second of minute was 56 point something in Malaysia, it was 31 point something in GMT. So we are not removing the same number of seconds in both cases.
Again, I don’t think this will be a problem for timestamps after 1973. Nowadays time zones tend to use offsets that are a whole number of minutes from UTC.
Edit:
(Does this ever happen after 1970?)
A bit. For example Liberia was on offset -0:44:30 until January 6, 1972. And it’s anybody’s guess what politicians in some country decide next year or the year after that.
Checking for edge cases
One way to check whether you are hitting one of the cases mentioned in the foregoing is using assert:
public static long toMinuteResolution(long timestamp) {
assert timestamp >= 0 : "This optimized method doesn’t work for negative timestamps.";
assert Duration.ofSeconds(Instant.ofEpochMilli(timestamp).atZone(ZoneId.systemDefault()).getOffset().getTotalSeconds())
.toSecondsPart() == 0
: "This optimized method doesn’t work for an offset of "
+ Instant.ofEpochMilli(timestamp).atZone(ZoneId.systemDefault()).getOffset();
return TimeUnit.MINUTES.toMillis(TimeUnit.MILLISECONDS.toMinutes(timestamp));
}
Since you wanted to optimize, I expect these checks to be too expensive for your production environment. You know better then I whether enabling them in your test environments will give you some assurance.
Further suggestions
As Andreas said in the comments, the truncatedTo method makes the non-optimized version a bit simpler and clearer:
public static long toMinuteResolution(long timestamp) {
return Instant.ofEpochMilli(timestamp)
.atZone(ZoneId.systemDefault())
.truncatedTo(ChronoUnit.MINUTES)
.toInstant()
.toEpochMilli();
}
You can use truncatedTo directly on the Instant too if you want, as in Andreas’ comment.
If you want to go with your optimization anyway, for slightly better readability my optimized version would be:
private static final long MILLIS_PER_MINUTE = TimeUnit.MINUTES.toMillis(1);
public static long toMinuteResolution(long timestamp) {
return timestamp / MILLIS_PER_MINUTE * MILLIS_PER_MINUTE;
}
I might even try the following and see whether it is efficient enough. I expect no noticeable difference.
public static long toMinuteResolution(long timestamp) {
return TimeUnit.MINUTES.toMillis(TimeUnit.MILLISECONDS.toMinutes(timestamp));
}
Link
Time Changes in Monrovia Over the Years

How to compare two java.time.Period in java 8?

How do I compare two Periods in java 8?
E.g.
Period one = Period.of(10,0,0);
Period two = Period.of(8,0,0);
here in this case one is greater than two.
It is true that the comparison of two Period objects does not make sense in a general case, due to the undefined standard length of a month.
However, in many situations you can quite well live with an implementation similar to that which follows. The contract will be similar to the contract of compareTo():
public int comparePeriodsApproximately(Period p1, Period p2) {
return period2Days(p1) - period2Days(p2);
}
private int period2Days(Period p) {
if (p == null) {
return 0;
}
return (p.getYears() * 12 + p.getMonths()) * 30 + p.getDays();
}
Rightly said by JB Nizet.
You cannot compare Periods, as per java doc in Period class there is similar class to Period (Duration) available in java, you can use that depends on your business requirement.
"Durations and periods differ in their treatment of daylight savings time when added to ZonedDateTime. A Duration will add an exact number of seconds, thus a duration of one day is always exactly 24 hours. By contrast, a Period will add a conceptual day, trying to maintain the local time."
Period period = Period.of(10, 0, 0);
Period period2 = Period.of(10, 0, 0);
// No compareTo method in period
System.out.println(period.compareTo(period2));
Duration duration = Duration.ofDays(3);
Duration duration2 = Duration.ofDays(3);
// Can compare durations as it gives you the exact time
System.out.println(duration.compareTo(duration2));
In case you have a period of months and period of years you can do as follows:
Period periodOfMonths = Period.ofMonths(16);
Period periodOfYears = Period.ofYears(1);
// Check if period of months is ghreather that the period of years
System.out.println(periodOfMonths.toTotalMonths() > periodOfYears.toTotalMonths());
i like the answer of Martin Cassidy, but propose to use a hard coded LocalDate instead of LocalDate.now, for example LocalDate.MIN
public static int compareTo(final Period first, final Period second)
{
return LocalDate.MIN.plus(first).compareTo(LocalDate.MIN.plus(second));
}
This one should be bullet-proof. Whether it’s useful?
I claim that you get the smallest possible amount of days from a given number of years and months by counting forward from February 1, 2097 since you are first counting February (28 days) and it takes a long time until you get two months with 31 days in a row (July and August) and still longer until you hit a leap year (2104). Also 2200 and 2300 are non-leap years, should the counting reach that far. Conversely you get the greatest possible number of days when counting backward from the same date. You start by counting through 2 months # 31 days (January and December). The first February you encounter is in 2096, a leap year, so 29 days. And 2000 is a leap year.
So my trick is to count the difference between the two periods both ways from the mentioned date. If counts agree that the difference is positive (or negative), then two (or one) will always be longer no matter which day we count from.
In some cases the counts will not agree. Then I refuse to call a longer period.
Period one = Period.of(10, 0, 0);
Period two = Period.of(8, 0, 0);
if (one.normalized().equals(two.normalized())) {
System.out.println("They are the same.");
} else {
Period diff = two.minus(one);
LocalDate anchor = LocalDate.of(2097, Month.FEBRUARY, 1);
LocalDate plusDiff = anchor.plus(diff);
LocalDate minusDiff = anchor.minus(diff);
if (plusDiff.isAfter(anchor) && minusDiff.isBefore(anchor)) {
System.out.println("Two is greater (unambiguously)");
} else if (plusDiff.isBefore(anchor) && minusDiff.isAfter(anchor)) {
System.out.println("One is greater (unambiguously)");
} else {
System.out.println("Cannot decide");
}
}
Output in this case (using the periods from the question):
One is greater (unambiguously)
The normalized method I am using in the first comparison converts between years and months ensuring that the months are in the interval from -11 through +11 and have the same sign as the years (except if years are 0). This can be done unambiguously since there are always 12 months in a year.
You may be able to refine it to report that one period is longer than or equal to the other. For example if one is a month and two is 31 days, we know that two is at least as long as one, even though we can’t decide whether it’s also strictly longer.
This can be done with a proper compareTo() approach by borrowing from LocalDate.
public static int compareTo(final Period first, final Period second)
{
final LocalDate now = LocalDate.now();
return now.plus(first).compareTo(now.plus(second));
}

calculate an average time with joda time

I have many time stamps showing at which time a user entered the room. I want to calculate an average time. The problem occurs when some action happens at night.
I tried to calculate it with milis, but it is wrong.
ArrayList<String> times = new ArrayList<String>();
times.add("00:20:01");
times.add("00:00:01");
times.add("23:40:01");
times.add("23:20:01");
times.add("23:20:01");
times.add("00:20:01");
times.add("23:40:01");
times.add("23:40:01");
times.add("00:00:01");
long commonMillis=0;
for (String date:times){
LocalTime time = new LocalTime(date);
long dayMilis = time.getMillisOfDay();
commonMillis = commonMillis + dayMilis;
}
LocalTime average = new LocalTime(commonMillis/times.size());
This code, for example, returns the value 14:08:54.333. Because the hours 00:00 and 23:00 -- calculated in millis -- are too far from each other.
Please help me to find right way to calculate the average time?
Three things:
You have to define an offset time:
If you want an average of times of different days without knowing the day, you have to define an offset time by yourself. This time is used to decide whether a time is belonging to the next day or not.
This offset time may be derived depending on the values you get.
Without an offset time, you implicitely use 0 o'clock.
Avoid overflows:
If your times list gets longer, you may run into an overflow if a long field is not sufficient to store the accumulated value. You can use a data structure which is overflow resistant like BigInteger or use the (culmulative) moving average approach.
Wrong result constructor:
The constructor LocalTime(long instant) implicitely uses your local DateTimeZone to calculate a local time from an Instant. This causes different times when using the same code between different time zones.
The method you want to use is LocalTime#fromMillisOfDay.
Here is an approach considering the above points:
long movingAverage = 0;
// 1. define offset
LocalTime offset = new LocalTime("12:00:00");
long offsetMillis = offset.getMillisOfDay();
for (String date : times) {
long sampleMillis = new LocalTime(date).getMillisOfDay();
// align to offset
if (sampleMillis < offsetMillis)
sampleMillis += DateTimeConstants.MILLIS_PER_DAY;
long diff = sampleMillis - offsetMillis;
// 2. use moving average
movingAverage = movingAverage + diff / times.size();
}
// 3. avoid LocalTime(long) constructor
LocalTime result = offset.plusMillis((int) movingAverage);
System.out.println(result); // 23:48:54.329
A naive approach would be to gather the long millisecond values in all the dates, add them up and divide them by the number of dates, transforming them back into a LocalDate. You probably need a BigInteger to hold the sum, though.

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

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