I have two times in hours and minutes.
time[0]: hour1
time[1]: minutes1
time[2]: hour2
time[3]: minutes2
I've created this formula to calculate the difference in time in minutes:
((time[2] % 12 - time[0] % 12) * 60) + (time[3] - time[1])
I was wondering if there are any edge cases to this. In addition, what is the paradigm you would follow to create this formula (although it is very basic)?
You could express your times with the Date class instead, then calculate the difference and then express it in the time unit of your choice.
With this method, you will avoid a lot of tricky cases (difference between two times on two different days, time change, etc.).
I recommend you the reading of this post and this post but there are many answers to this same exact question on StackOverflow ;)
Note: before using Date, have a look to this excellent post: What's wrong with Java Date & Time API?
Your code assumes days are 24 hours long. Not all days are 24-hours long. Anomalies such as Daylight Saving Time (DST) mean days vary in length.
Also, we have classes already built for this. No need to roll your own. The LocalTime class represents a time-of-day without a date and without a time zone. A Duration represents a span of time not attached to the timeline.
LocalTime start = LocalTime.of( 8 , 0 ) ;
LocalTime stop = LocalTime.of( 14 , 0 ) ;
Duration d = Duration.between( start , stop );
long minutes = d.toMinutes() ; // Entire duration as a total number of minutes.
That code too pretends that days are 24 hours long.
For realistic spans of time, use the ZonedDateTime class to include a date and time zone along with your time-of-day.
Related
I'm having an issue working with time in Java. I don't really understand how to efficiently solve comparing the time of now and 12 hours before and after
I get a set of starting times for a show from an API and then compare that starting time with LocalTime.now(). It looks something like this:
SimpleDateFormat sdt = new SimpleDateFormat("HH:mm:ss");
String temp = sdt.format(Local.time(now));
LocalTime secondTime = LocalTime.parse(parts1[0]);
LocalTime firstTime = LocalTime.parse(temp);
int diff = (int) ((MINUTES.between(firstDay, secondDay) + 1440) % 1440);
if(diff <= 720){
return true;
}
Where my idea is that if the difference between the two times is smaller than 720 minutes (12 hours) I should get the correct output. And this works for the 12 hours before now. I thought I might need to swap the parameters of .between, to get the other side of the day. That counts it completely wrong (If the time now is 15:00:00 it would accept all the times until 22:00:00 the same day). Is this just a really bad way of comparing two times? Or is it just my math that lacks understanding of what I'm trying to do?
Thanks
Using the 'new' (not that new) Java 8 time API:
Instant now = Instant.now();
Instant hoursAfter = now.plus(12, ChronoUnit.HOURS);
Instant hoursBefore = now.minus(12, ChronoUnit.HOURS);
First, doing this kind of operations on java.time.LocalTime won't work! Or at least only if the time is "12:00:00" …
That is because you will have an over-/underflow when you add/substract 12 hours from any other time.
So your starting point should be to go for java.time.LocalDateTime (at least, although I would go for java.time.Instant). Now you can handle the over-/underflow, as you will get another day when adding or subtracting 12 hours.
How this works is shown in this anwswer: LocalDateTime allows nearly the same operations as Instant.
I am trying to use Java to sum two times.
Lets say i have these LocalTimes:
LocalTime L1 = LocalTime.parse("2:10");
LocalTime L2 = LocalTime.parse("13:20");
Is there any fancy LocalTime method which makes it possible to sum hours and get 15:30 (L1+L2)
(Note: I was looking for something in the time or date package), without String butchering.
Basically, LocalTime marks a point in time, not a duration. You cannot add two points in time - it doesn't make sense.
LocalTime does have a plus() method, but it requires a TemporalAmount (a Period or Duration) and not a LocalTime. You can do what you want by converting a LocalTime to a Duration by using Duration.ofNanos(localTime.toNanoOfDay()), so you'd do something like l1.plus(Duration.ofNanos(l2.toNanoOfDay())) (this basically means you're treating the second LocalTime not as a point in time, but as the amount of time that elapsed since the start of the day)
The Answer by Wilkin is correct. I'll elaborate.
You have conflated two key concepts:
Point in time, a moment, a point on the timeline
Span of time, an amount of time not attached to the timeline.
An example of the first is agreeing to meet you for lunch next Wednesday at 12:30 PM in Africa/Casablanca time zone. An example of the second is a school’s policy that the students’ lunch period lasts 50 minutes.
For the first, use the Instant (always in UTC), OffsetDateTime (offset from UTC as a count of hours and minutes), or ZonedDateTime (a time zone such as Pacific/Auckland assigned) classes.
For the second, use the Period (a count of years, months, and days) or Duration (a total number of whole seconds plus a fractional second) classes.
For a date-only value without time of day and without time zone, use LocalDate. For a time-of-day without a date and without a time zone, use LocalTime.
Your examples suggest that you had two events occur, one elapsing a couple minutes and one taking thirteen minutes. So we need the Duration class. The LocalTime class does not apply to your problem.
You confusingly use time-of-day notation to represent such spans of time. Better to use standard ISO 8601 formats for such strings. The standard format is PnYnMnDTnHnMnS where the P marks the beginning and the T separates the years-months-days from the hours-minutes-seconds.
The java.time classes use the standard formats by default when parsing or generating strings.
Duration d1 = Duration.parse( "PT2M10S" ) ;
Duration d2 = Duration.parse( "PT13M20S" ) ;
Duration d3 = d1.plus( d2 ) ;
d3.toString(): PT15M30S
I'm not sure I'm getting the subtleties between Java Period and Duration.
When I read Oracle's explanation, it says that I can find out how many days since a birthday like this (using the example dates they used):
LocalDate today = LocalDate.now();
LocalDate birthday = LocalDate.of(1960, Month.JANUARY, 1);
Period birthdayPeriod = Period.between(birthday, today);
int daysOld = birthdayPeriod.getDays();
But as even they point out, this doesn't take into account the time zone you were born in and the time zone you are in now. But this is a computer and we can be precise, right? So would I use a Duration?
ZoneId bornIn = ZoneId.of("America/New_York");
ZonedDateTime born = ZonedDateTime.of(1960, Month.JANUARY.getValue(), 1, 2, 34, 56, 0, bornIn);
ZonedDateTime now = ZonedDateTime.now();
Duration duration = Duration.between(born, now);
long daysPassed = duration.toDays();
Now the actual times are accurate, but if I understand this correctly, the days might not correctly represent calendar days, e.g. with DST and such.
So what am I do to to get a precise answer based upon my time zone? The only thing I can think of is to go back to using LocalDate, but normalize the time zones first from the ZonedDateTime values, and then use a Duration.
ZoneId bornIn = ZoneId.of("America/New_York");
ZonedDateTime born = ZonedDateTime.of(1960, Month.JANUARY.getValue(), 1, 2, 34, 56, 0, bornIn);
ZonedDateTime now = ZonedDateTime.now();
ZonedDateTime nowNormalized=now.withZoneSameInstant(born.getZone());
Period preciseBirthdayPeriod = Period.between(born.toLocalDate(), nowNormalized.toLocalDate());
int preciseDaysOld = preciseBirthdayPeriod.getDays();
But that seems really complicated just to get a precise answer.
Your analysis regarding the Java-8-classes Period and Duration is more or less correct.
The class java.time.Period is limited to calendar date precision.
The class java.time.Duration only handles second (and nanosecond) precision but treats days always as equivalent to 24 hours = 86400 seconds.
Normally it is completely sufficient to ignore clock precision or timezones when calculating the age of a person because personal documents like passports don't document the exact time of day when someone was born. If so then the Period-class does its job (but please handle its methods like getDays() with care - see below).
But you want more precision and describe the result in terms of local fields taking into account timezones. Well, the first part (precision) is supported by Duration, but not the second part.
It is also not helpful to use Period because the exact time difference (which is ignored by Period) can impact the delta in days. And furthermore (just printing the output of your code):
Period preciseBirthdayPeriod =
Period.between(born.toLocalDate(), nowNormalized.toLocalDate());
int preciseDaysOld = preciseBirthdayPeriod.getDays();
System.out.println(preciseDaysOld); // 13
System.out.println(preciseBirthdayPeriod); // P56Y11M13D
As you can see, it is quite dangerous to use the method preciseBirthdayPeriod.getDays() in order to get the total delta in days. No, it is only a partial amount of the total delta. There are also 11 months and 56 years. I think it is wise to also print the delta not only in days because then people can easier imagine how big the delta is (see the often seen use-case of printed durations in social media like "3 years, 2 months, and 4 days").
Obviously, you rather need a way to determine a duration including calendar units as well as clock units in a special timezone (in your example: the timezone where someone has been born). The bad thing about Java-8-time-library is: It does not support any combination of Period AND Duration. And importing the external library Threeten-Extra-class Interval will also not help because long daysPassed = interval.toDuration().toDays(); will still ignore timezone effects (1 day == 24 hours) and is also not capable of printing the delta in other units like months etc.
Summary:
You have tried the Period-solution. The answer given by #swiedsw tried the Duration-based solution. Both approaches have disadvantages with respect to precision. You could try to combine both classes in a new class which implements TemporalAmount and realize the necessary time arithmetic yourself (not so trivial).
Side note:
I have myself already implemented in my time library Time4J what you look for, so it might be useful as inspiration for your own implementation. Example:
Timezone bornZone = Timezone.of(AMERICA.NEW_YORK);
Moment bornTime =
PlainTimestamp.of(1960, net.time4j.Month.JANUARY.getValue(), 1, 22, 34, 56).in(
bornZone
);
Moment currentTime = Moment.nowInSystemTime();
MomentInterval interval = MomentInterval.between(bornTime, currentTime);
MachineTime<TimeUnit> mt = interval.getSimpleDuration();
System.out.println(mt); // 1797324427.356000000s [POSIX]
net.time4j.Duration<?> duration =
interval.getNominalDuration(
bornZone, // relevant if the moments are crossing a DST-boundary
CalendarUnit.YEARS,
CalendarUnit.MONTHS,
CalendarUnit.DAYS,
ClockUnit.HOURS,
ClockUnit.MINUTES
);
// P56Y11M12DT12H52M (12 days if the birth-time-of-day is after current clock time)
// If only days were specified above then the output would be: P20801D
System.out.println(duration);
System.out.println(duration.getPartialAmount(CalendarUnit.DAYS)); // 12
This example also demonstrates my general attitude that using units like months, days, hours etc. is not really exact in strict sense. The only strictly exact approach (from a scientific point of view) would be using the machine time in decimal seconds (best in SI-seconds, also possible in Time4J after the year 1972).
The JavaDoc of Period states that it models:
A date-based amount of time in the ISO-8601 calendar system, such as '2 years, 3 months and 4 days'.
I understand it has no reference to points in time.
You might want to check Interval from project ThreeTen-Extra which models:
an immutable interval of time between two instants.
The project website states the project “[...] is curated by the primary author of the Java 8 date and time library, Stephen Colebourne”.
You can retrieve a Duration from an Interval by invoking toDuration() on it.
I shall transform your code to give an example:
ZoneId bornIn = ZoneId.of("America/New_York");
ZonedDateTime born = ZonedDateTime.of(1960, Month.JANUARY.getValue(), 1, 2, 34, 56, 0, bornIn);
ZonedDateTime now = ZonedDateTime.now();
Interval interval = Interval.of(born.toInstant(), now.toInstant());
long daysPassed = interval.toDuration().toDays();
The main distinction between the two classes is :
that java.time.Period uses date-based values ( May 31, 2018)
while java.time.Duration is more precise, it uses time-based values ( "2018-05-31T11:45:20.223Z" )
java.time.Period is more friendly for human reading
for example Period between A and B is 2 years 3 months 3 days
java.time.Duration is for a machine.
I have found how to get the hours between two dates and the minutes . What I want is the exact difference between both of them, this is the code that I'm using:
private String getHours(Message punch) {
LocalTime out = Instant.ofEpochMilli(message.getOut().getTime()).atZone(ZoneId.of(message.getTimezone()))
.toLocalTime();
LocalTime in = Instant.ofEpochMilli(message.getIn().getTime()).atZone(ZoneId.of(message.getTimezone()))
.toLocalTime();
Duration duration = Duration.between(in, out);
Long hours = duration.toHours();
Long minutes = duration.toMinutes() - (hours * Constants.MINUTES_IN_AN_HOUR);
return String.format("%d:%d", hours, minutes);
}
It works fine for the major of the cases but I'm having an error in the following case:
message.getIn() returns: 12:59
message.getOut() returns: 22:00
Both are the same day, the difference that I'm expecting is 9:01, but I'm getting -14:-59
Debugging the code I realize that out is getting 04:00 and in is getting 18:59.
For almost all the cases it works well but It happens in some scenarios.
I believe your problem is that you are using LocalTime class but you should be using LocalDateTime class. It apears that your timezone is GMT+6, so in your example your in time and out time fall in different days - your in time in the evening of a previous day and out time on the morning of the next day. But because you are using LocalTime you are loosing the fact that those are times in different days. Change your LocalTime to LocalDateTime and see if this helps
You are working too hard, going through too many gyrations in your code.
Use Instant
You have a pair of Instant objects but throw them away. Use them. An instant is a specific moment on the timeline in UTC with a resolution of nanoseconds.
Instant start = … ;
Instant stop = … ;
Duration d = Duration.between( start , stop );
How you get your instants is a mystery. If you revise your Question to explain the exact nature of your inputs, I will provide more code here.
If you are being passed a pair of java.util.Date objects, convert them to Instant. Use new conversion methods added to the old date-time classes. No need for time zones at all for calculating elapsed time.
Instant start = utilDateX.toInstant() ;
Instant stop = utilDateY.toInstant() ;
Duration d = Duration.between( start , stop );
Calculating elapsed time with LocalTime is rarely appropriate because of crossing over into the next or previous days.
// someTime is epoch in millis (UTC)
final long timeNow = new Date().getTime();
final long midnight = timeNow - timeNow % (3600 * 24 * 1000L);
final long yesterdayMidnight = midnight - (3600 * 24 * 1000L);
// check if same day.
if (someTime >= midnight)
// do something
// check if yesterday
if (someTime >= yesterdayMidnight)
Edited: My purpose is to check whether someTime is in the same day or in the previous day without doing too much heavyweight stuff.
Does this account for day light savings and why? If not, what's the simplest logic?
Your current code doesn't do anything with the local time zone - everything is in UTC, effectively (certainly in terms of your code, which is dealing in "milliseconds since the Unix epoch").
If you want to make your code time-zone-sensitive, you should use (in order of preference):
Java 8's java.time (look at ZonedDateTime and Clock for example)
Joda Time
java.util.Calendar with java.util.TimeZone
Use higher-level abstractions where possible - your code should do as little low-level manipulation of time as possible.
EDIT: Now that we know the purpose, here's an example implementation in Joda Time:
public void calculate(Instant now, Instant then, DateTimeZone zone) {
LocalDate today = new LocalDate(now, zone);
LocalDate otherDay = new LocalDate(then, zone);
if (otherDay.equals(today)) {
// Today day
} else if (otherDay.equals(today.minusDays(1)) {
// Yesterday
} else {
// Neither today nor yesterday
}
}
Note how there's nothing low level here - we're just working out which date each value (now and then) falls in within the given time zone, and then comparing those.
Your check will fail in some cases having in mind daylight savings. Let's assume it is now 5 o' clock on the day when daylight saving happens and at 3 o'clock we've switched the clock forward. Therefor only 4 hours have passed since midnight but it is in fact 5. So midnight in your code will be a time 5 hours ago. This means that if someTime is between 5 hours ago and 4 hours ago(e.g. 4 hours and a half ago) when it's in fact been yesterday your algorithm will report it has been today.
That does not seem to be correct from daylight savings point. First, what's the timezone of someTime date? What about the days/nights(in fact) when the daylight savings take place (one hour +/-)?
If you use Joda lib there is a convenient method DateTime.isBefore() that will return whether one date is before another.