More precision in comparing two dates, that are two hours appart - java

Can this be tested with more precision? This is a simple function that calculates executionStartTime as two hours behind currentTime.
Function below works, but I would like a function that is more precise.
#Test
public void testCalculateExecutionStartTime() {
Date currentTime = new Date(ZonedDateTime.now(ZoneOffset.UTC).toEpochSecond() * 1000);
Date executionStartTime = proxy.calculateExecutionStartTime(properties.getMessageConfiguration().getExecutionStartTime());
Assert.assertTrue(currentTime.after(executionStartTime));
}

I am assuming that calculateExecutionStartTime is returning a time that is two hours ago compared to the time you call the method, and this is what you want to test. In my first suggestion I am further assuming that you can change the method to return a modern Instant instead of an outdated Date. Since you can use java.time, the modern Java date and time API, this would immediately look like an easy improvement.
The challenge is that the call may take some milliseconds, even some seconds, and we don’t know at what point in time during the call it is reading the clock. So there is no testing using assertEquals with the time that we expect. Instead we read the clock before and after the call. Then our test can rely on the method reading the clock at some point between our two calls. In most cases this will allow us to test the returned time with just a small margin. The idea was already presented by Dawood ibn Kareem in a comment.
#Test
public void testCalculateExecutionStartTime() {
Instant timeBefore = Instant.now();
Instant executionStartTime = proxy.calculateExecutionStartTime(
properties.getMessageConfiguration().getExecutionStartTime());
Instant timeAfter = Instant.now();
Duration expectedTimeAgo = Duration.ofHours(2);
Assert.assertFalse(executionStartTime.isBefore(timeBefore.minus(expectedTimeAgo)));
Assert.assertFalse(executionStartTime.isAfter(timeAfter.minus(expectedTimeAgo)));
}
Note the use of Instant.now() for reading the clock. You don’t need ZonedDateTime nor ZoneOffset.
If you cannot change the return type of calculateExecutionStartTime, just convert the Date you get from it:
Instant executionStartTime = proxy.calculateExecutionStartTime(
properties.getMessageConfiguration().getExecutionStartTime())
.toInstant();
The rest is exactly as before.

Related

One second difference when comparing two Date() with difference of one day

I wrote simple TimeService with method getDateAfter(int days) and test for it:
#Test
#Order(7)
public void getDateAfterCorrect() throws InterruptedException {
waitIfNeeded();
LocalDateTime today = LocalDateTime.now();
LocalDateTime tomorrow = timeService.getDateAfter(1).toInstant()
.atZone(ZoneId.systemDefault()).toLocalDateTime();
long diff = ChronoUnit.SECONDS.between(today, tomorrow);
long secondsAtDay = 86400;
Assertions.assertEquals(secondsAtDay, diff);
}
It should be 86400 seconds at day, but diff is 86399.
I tried to take that one part of code could be executed in another time than other into account by implementing waitIfNeeded() method
private void waitIfNeeded() throws InterruptedException {
var currentMillis = Instant.now().get(ChronoField.MILLI_OF_SECOND);
if (currentMillis > 500) {
Thread.sleep(1000 - currentMillis);
}
}
Do You have any idea why I am not able to make this test and other possible things that can be wrong here (I assume things like how programming languages are dealing with step year, etc...)
I managed to get test simplified and working, now it is OK:
#Test
#Order(7)
public void getDateAfterCorrect() throws InterruptedException {
waitIfNeeded();
long today = timeService.getDate().toInstant().getEpochSecond();
long tommorow = timeService.getDateAfter(1).toInstant().getEpochSecond();
Assertions.assertEquals(86400, tommorow - today);
}
but It is still interesting why using other method of comparing that two dates produced such results, if someone with deep level knowledge can answer it, probably few people will be interested.
The java.util Date-Time API is outdated and error-prone. It is recommended to stop using it completely and switch to the modern Date-Time API*.
Apart from that, instead of performing the calculation (subtraction) yourself, you use Instant#until which returns the duration in the specified ChronoUnit.
import java.time.Instant;
import java.time.temporal.ChronoUnit;
public class Main {
public static void main(String[] args) {
// This is a sample Instant. In your case, it will be returned by
// timeService.getDate().toInstant()
Instant today = Instant.now();
// This is a sample Instant after one day. In your case, it will be returned by
// timeService.getDateAfter(1).toInstant()
Instant tomorrow = today.plus(1, ChronoUnit.DAYS);
long seconds = today.until(tomorrow, ChronoUnit.SECONDS);
// In your case, you will use Assertions.assertEquals(86400, seconds);
System.out.println(seconds);
}
}
Output:
86400
ONLINE DEMO
Learn more about the modern Date-Time API from Trail: Date Time.
* If you are working for an Android project and your Android API level is still not compliant with Java-8, check Java 8+ APIs available through desugaring. Note that Android 8.0 Oreo already provides support for java.time.
The explanation: why 86399? Lack of precision
I am assuming that timeService.getDateAfter(1) returns an old-fashioned Date object. You shouldn’t be using Date in your code anymore, it’s been replaced by java.time, the modern Java date and time API that you are also using, many years ago. But we’re still curious why your code using Date didn’t give the expected result of 86 400 seconds in a day.
Date has milliseconds precision. java.time has nanosecond precision, and since Java 9 the now methods of many of the classes have microsecond precision on most platforms. So for example LocalDateTime.now() returns 0.001234 seconds past a whole second. Some microseconds later, maybe at 0.001432 past the whole second, your time service returns a Date worth 0.001 seconds past the same whole second. Between today at 0.001234 and tomorrow at 0.001 is not a whole day, not fully 24 hours, so the difference in seconds is truncated to 86 399.
When I ran your code in a loop, the first time I got 86 400. It must be because I passed a full millisecond of the second between the two calls. Maybe because of JVM warm-up. The following times I got 86 399.
A possible fix
One way to obtain consistent precision is to truncate everything to milliseconds. Or even to seconds. With the following change to your code I got 86 400 consistently.
LocalDateTime today = LocalDateTime.now().truncatedTo(ChronoUnit.MILLIS);
I got the same result when using ChronoUnit.SECONDS instead of .MILLIS. I believe that this also somewhat explains why the change in your own answer worked. I think you need to be aware, though, that some time does elapse between your two calls, and you cannot control how much. So you may get 86 401 or even a still higher number on rare occasions. Even if I didn’t observe it in my few runs.
I did once work in a place where some of the very many unit tests failed sporadically. It was quite annoying and a source of distrust in the code even when I made it a habit to type comments into the unit tests in question about their sporadic failures. Please do not put yourself and your co-workers in the same situation.

How can we decide whether to use ZonedDateTime or LocalDateTime?

Sometimes, we find it is difficult to make judgement, whether to use ZonedDateTime or LocalDateTime, when we want to solve certain date/ time problem.
For instance, given an epoch, we would like to know the day of the week.
We find we can accomplish this task, with either ZonedDateTime or LocalDateTime. Here's the code example
import java.time.*;
public class Main {
public static void main(String[] args) {
long currentTimeMillis = System.currentTimeMillis();
// Yield correct result.
System.out.println("useLocalDateTime -> " + useLocalDateTime(currentTimeMillis));
// Also yield correct result.
System.out.println("useZonedDateTime -> " + useZonedDateTime(currentTimeMillis));
}
public static DayOfWeek useLocalDateTime(long currentTimeMillis) {
LocalDateTime localDateTime = LocalDateTime.ofInstant(
Instant.ofEpochMilli(currentTimeMillis),
ZoneId.systemDefault()
);
DayOfWeek dayOfWeek = localDateTime.getDayOfWeek();
return dayOfWeek;
}
public static DayOfWeek useZonedDateTime(long currentTimeMillis) {
ZonedDateTime zonedDateTime = Instant.ofEpochMilli(currentTimeMillis).atZone(ZoneId.systemDefault());
DayOfWeek dayOfWeek = zonedDateTime.getDayOfWeek();
return dayOfWeek;
}
}
In the above case, is it better to use ZonedDateTime or LocalDateTime? Is there any guideline, so that we can pick up the correct class as tool?
I always have the impression that ZonedDateTime is more "feature rich" than LocalDateTime. Whatever can be accomplished by LocalDateTime, it can be accomplished by ZonedDateTime too, but not vice-versa. Hence, if I get stuck on which to choose, I will go to ZonedDateTime as default. Is that a correct concept?
Do you need to store time data that is attached to a specific time zone, or do you need to process time data that has an associated offset?
If you do, use ZonedDateTime.
If you don't, use LocalDateTime.
Some examples of when I would want to use ZonedDateTime:
I'm parsing an ISO 8601 timestamp with zone information.
I'm looking at data from two different sources located in two physically different locations.
I'm trying to calculate what the day of the week is given a timestamp.
Some examples of when I would want to use LocalDateTime:
I'm assured that my system only needs to care about one time zone - mine.
The data that I'm parsing does not have time stamp information.
I want to know how many seconds have passed between two time stamps. This may get converted to a ZonedDateTime first before it eventualy decants into a Duration if the time stamps are in ISO 8601 format.
Definitely be careful about days of the week across time zones, since the International Date Line can offset the day of the week depending on where you are physically located.
Instead of using System.currentTimeMillis() use ZonedDateTime.now(ZoneId) or Instant.now(). You should almost never need currentTimeMillis() in modern Java. Use the dedicated java.time APIs throughout your application, so that you're working with well-typed data structures instead of primitives like long currentTimeMillis.
given an epoch, we would like to know the day of the week
It's worth recognizing that this isn't a meaningful question without a time zone; at any moment in time there are two (or more?) days of the week in different places on earth. So before we go further we need to ask which time zone(s) do you care about?
Generally speaking, the systemDefault() time zone is not what you want. Instead the caller should provide the time zone they expect. If your program is running locally and only ever needs your machine's clock it may be fine, but the very reason for the split between LocalDateTime and ZonedDateTime is because the system is very often not the correct time zone to be using.
For trivial cases, e.g. a Java process running on your local machine that doesn't care about time zone changes over time, you might correctly use the system time zone. But in such cases it's a good idea to query the system near your main() method and then pass that zone through your application. This makes the application more scalable and testable, if the system zone stops being the right approach down the road.

Using instances of GregorianCalendar to determine time passed?

I am working on a project in my CIS 163 class that is effectively a campsite reservation system. The bulk of the code was provided and I just have to add certain functionalities to it. Currently I need to be able to determine how much time has passed between 2 different GregorianCalendar instances (one being the current date, the other being a predetermined "check out") represented by days. I haven't been able to figure out quite how to do this, and was hoping someone here might be able to help me out.
The GregorianCalendar is old and you shouldn't really use it anymore. It was cumbersome and was replaced by the "new" java.time module since Java 8.
Still, if you need to compare using GC instances, you could easily calculate time using milliseconds between dates, like this:
GregorianCalendar date1 = new GregorianCalendar();
GregorianCalendar date2 = new GregorianCalendar();
// Adding 15 days after the first date
date2.add(GregorianCalendar.DAY_OF_MONTH, 15);
long duration = (date2.getTimeInMillis() - date1.getTimeInMillis() )
/ ( 1000 * 60 * 60 * 24) ;
System.out.println(duration);
If you want to use the new Time API, the following code would work.
LocalDate date1 = LocalDate.now();
LocalDate date2 = date1.plusDays(15);
Period period = Period.between(date1, date2);
int diff = period.getDays();
System.out.println(diff);
If you need to convert between the types (e.g. you're working with legacy code), you can do it like this:
LocalDate date3 = gcDate1.toInstant().atZone(ZoneId.systemDefault()).toLocalDate();
LocalDate date4 = gcDate2.toInstant().atZone(ZoneId.systemDefault()).toLocalDate();
Also I'm pretty sure this question must've been asked over and over again, so make sure you search properly before asking.
Since you have been forced to use the old and poorly designed GregorianCalendar class, the first thing you should do is convert each of the two GregorianCalendar objects to a modern type. Since Java 8 GregorianCalendar has a method that converts it to ZonedDateTime. Check the documentation, I include a link below.
Now that you’ve got two ZonedDateTime objects, there are different paths depending on your exact requirements. Often one will use Duration.between() for finding the duration, the amount of time between them in hours, minutes, seconds and fraction of second. If you know that you will always need just one of those time units, you may instead use for example ChronoUnit.HOURS.between() or ChronoUnit.MILLISECONDS.between(). If you need to count days, use ChronoUnit.DAYS.between().
If instead you need the time in months and days, you should instead use Period.between().
Links
Documentation:
GregorianCalendar (long outdated, don’t use unless forced to)
Duration
ChronoUnit
Period
Oracle tutorial: Date Time explaining how to use java.time, the modern Java date and time API to which ZonedDateTIme, Duration, ChronoUnit and Period belong.

Creating specific time stamps for objects?

I'm not trying to use the current time at all. Im aware of the calendar and date java utils but I'm not sure how to create my OWN timestamps not dependent on the current time. For instance each object would have an "arrival time" variable in hh:mm:ss format. But I have no clue how to do this. Do I have to create my own class?
It’s not perfectly clear, but I would think you’re after something like this:
public class MyObject {
private static final DateTimeFormatter ARRIVAL_TIME_FORMATTER
= DateTimeFormatter.ofPattern("HH:mm:ss");
// Some instance variables
private LocalTime arrivalTime;
public MyObject(LocalTime arrivalTime) {
this.arrivalTime = arrivalTime;
}
public String getFormattedArrivalTime() {
return arrivalTime.format(ARRIVAL_TIME_FORMATTER);
}
}
As has been said in the comments, use a LocalTime for the time of day. Don’t worry about format when storing the arrival time in your object. Only format it when you need to display it. LocalTime.of creates a LocalTime not related to the current time. There are overloaded versions that also take seconds.
So no, don’t create your own timestamp class. Use LocalTime (or depending on requirements some other class) from java.time, the modern Java date and time API.
Let’s see a brief example of using the above MyObject class:
MyObject obj = new MyObject(LocalTime.of(23, 45));
System.out.println("Object arrived at " + obj.getFormattedArrivalTime());
Output:
Object arrived at 23:45:00
In the output (and there only) the time has been formatted to hh:mm:ss as you requested.
Edit: LocalTime has a precision of nanoseconds. Basil Bourque is correct in his comment that if you know that you don’t want to store any finer precision than whole seconds, it’s easy to truncate the incoming value:
public MyObject(LocalTime arrivalTime) {
this.arrivalTime = arrivalTime.truncatedTo(ChronoUnit.SECONDS);
}
On one hand I’d hesitate to throw information away like this, and time stamps tend to benefit from precision. On the other hand it may also be confusing if your object stores a fraction of second that no one ever sees. You will have to decide yourself.
Links
Oracle tutorial: Date Time explaining how to use java.time.
Documentation of LocalTime

Passing TimeUnits and converting to milliseconds

How can I make an API for a class that the user passes a TimeUnit e.g. minutes, seconds, hours and a number and keep the millisec value internally in the class.
The following seems the only way?
void someMethodDays(int numOfDays) {
this.longValue = TimeUnit.DAYS.toMillis(numOfDays);
}
void someMethodHour(int numOfHours) {
this.longValue = TimeUnit.HOURS.toMillis(numOfDays);
} etc
Is this the only way? A method per value with a descriptive name for argument?
You could model your class after a well-known and tested class: java.time.LocalDate, which provides the plus(long, TemporalUnit) method.
Similarly, you could create a someMethod(long, TimeUnit) method that allows callers to pass in arbitrary amounts of any TimeUnit.
void someMethod(long amount, TimeUnit unit) {
this.longValue = unit.toMillis(amount);
}
Note that LocalDate also provides specialized methods for adding certain common units of time, like plusDays(). That gives the caller the ability to decide which is clearer for the code they're writing:
LocalDate tomorrow = today.plusDays(1);
LocalDate tomorrow = today.plus(1, TimeUnit.DAYS);
It seems to me that you don’t need to develop your own class; that you’ll be reinventing the wheel. I suggest you use the Duration class.
To convert from some time unit to a Duration:
System.out.println(Duration.of(3, ChronoUnit.HOURS));
Or alternatively:
System.out.println(Duration.ofHours(3));
Output is the same in both cases:
PT3H
It prints a little funny; this means a span of time of 3 hours. The format is ISO 8601.
System.out.println(Duration.of(2, ChronoUnit.DAYS));
PT48H
48 hours; that’s correct.
System.out.println(Duration.of(327864523, ChronoUnit.MICROS));
PT5M27.864523S
A span of 5 minutes 27.864523 seconds.
If you need to convert to milliseconds, the method is built in:
Duration dur = Duration.of(284, ChronoUnit.MINUTES);
System.out.println("" + dur.toMillis() + " milliseconds");
17040000 milliseconds
Duration is part of java.time, the modern Java date and time API, and of course works well with the other classes from that API.
Links
Oracle tutorial: Date Time explaining how to use java.time.
Documentation: the Duration class
Wikipedia article: ISO 8601; section on durations

Categories

Resources