I am running my code in EST timezone.
Using Instant.now() in my code and it returns time in UTC.
But, I am trying to test a method which gets data from DB as Date not Instant and hence trying to convert this to Date using
Date.from(Instant.now())
Since, I am running this in EST, this Date gives me time in EST.
Actual code,
final Optional<Date> dbTime = dbService.getUpdatedTime();
final Instant lastInstant = dbTime.orElseGet(() -> Date.from(Instant.now())).toInstant();
Test Code,
final Date dbTime = Date.from(Instant.now().minusSeconds(36000));
when(dbService.getUpdatedTime().thenReturn(Optional.of(dbTime));
Here, the dbTime gets converted to EST time. I can make that to return UTC time by setting TimeZone.setDefault(TimeZone.getTimeZone("UTC"));
Is there any other better way? Is this ok to set TimeZone.setDefault(TimeZone.getTimeZone("UTC")); in the main Application class so that it will always be treated as UTC?
First recommendation, since you can use the modern Java date and time API, use it as much as you can and minimize the use of the outdated Date class. Best will be if you can modify getUpdatedTime() to return an Optional<Instant> rather than an Optional<Date> (a modern JDBC driver can give you the datetime from your database as an Instant directly). Since an Instant prints in UTC, this should wipe away all of your issue and your question.
In this answer I am assuming that you either cannot do that or don’t want to do it just yet. You can still get close, though:
final Optional<Instant> dbTime = dbService.getUpdatedTime().map(Date::toInstant);
final Instant lastReconInstant = dbTime.orElseGet(Instant::now);
Avoid TimeZone.setDefault(). Since the JVM only has one global time zone setting, this may unintentionally change the behaviour of other parts of your program or other programs running in the same JVM.
A detail, in your stub code I recommend to make it explicit that you subtract 10 hours. Two options are
final Date dbTime = Date.from(Instant.now().minus(10, ChronoUnit.HOURS));
final Date dbTime = Date.from(Instant.now().minus(Duration.ofHours(10)));
All of this said, it still seems to me that you didn’t have a problem in the first place. A Date does not have a time zone in it. Its toString method just grabs the JVM’s default time zone and uses it for rendering the date and time. This has fooled many and is just one of the reasons to avoid that class when you can.
Related
I have a program that stores a local copy of a file hosted in an FTP server. The program automatically checks every day if the file has been updated on the server using the following code:
FTPFile remoteFile = ftpClient.mlistFile(remotePath);
Date remoteDate = remoteFile.getTimestamp().getTime();
BasicFileAttributes localFile = Files.readAttributes(Paths.get(localPath), BasicFileAttributes.class);
Date localDate = new Date(localFile.lastModifiedTime().toMillis());
isUpToDate = localDate.compareTo(remoteDate) > 0;
My coworker and I now have a disagreement about this code. He says that this might not work if the program is executed in a different time zone, and I say it will work because Java Date objects are not affected by time zones, only the instances of Calendar are. Am I right ? Is he right ?
Can the time zone affect a java.util.Date.compareTo() result?
No. The only thing compared by Date is the milliseconds since epoch.
This would be easy to write a test for: run the same code, setting the JVM's default time zone to different values.
Nope, java.util.Date doesn't matter on the timezone, it is always a milliseconds-since-Unix-epoch value. If you want the time in a different timezone, then you need to do the following --
public static void main(String[] args) {
Date date = new Date();
// Display the instant in three different time zones
TimeZone.setDefault(TimeZone.getTimeZone("America/Chicago"));
System.out.println(date);
TimeZone.setDefault(TimeZone.getTimeZone("Europe/London"));
System.out.println(date);
TimeZone.setDefault(TimeZone.getTimeZone("Asia/Riyadh"));
System.out.println(date);
// Prove that the instant hasn't changed...
System.out.println(date.getTime());
}
java.time
It’s not what you asked, but I thought it would be interesting to you and not least a lot of other people interested in this and similar questions. The doubt will probably go away if instead of the old-fashioned Date class you use Instant from java.time, the modern Java date and time API.
FTPFile remoteFile = ftpClient.mlistFile(remotePath);
Instant remoteInstant = remoteFile.getTimestamp().toInstant();
BasicFileAttributes localFile = Files.readAttributes(Paths.get(localPath), BasicFileAttributes.class);
Instant localInstant = localFile.lastModifiedTime().toInstant();
isUpToDate = ! localInstant.isBefore(remoteInstant);
(The code is not tested, please forgive any typo.) While Date sometimes pretends to be a date and time in a time zone (in particular its confusing toString method gives this impression), I can’t see any doubt that an Instant is just what the name says, a point in time, no more, no less. Absolutely independent of time zone.
In my comparison I have allowed the instants to be equal. I am using not before to mean same time or after. You can just use isAfter() if you require the local instant to be strictly after as in your own code.
Link
Oracle tutorial: Date Time explaining how to use java.time.
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.
An external API returns an object with a date.
According to their API specification, all dates are always reported in GMT.
However, the generated client classes (which I can't edit) doesn't set the timezone correctly. Instead, it uses the local timezone without converting the date to that timezone.
So, long story short, I have an object with a date that I know to be GMT but it says CET. How can I adjust for this mistake withouth changing my local timezone on the computer or doing something like this:
LocalDateTime.ofInstant(someObject.getDate().toInstant().plus(1, ChronoUnit.HOURS),
ZoneId.of("CET"));
Thank you.
tl;dr ⇒ use ZonedDateTime for conversion
public static void main(String[] args) {
// use your date here, this is just "now"
Date date = new Date();
// parse it to an object that is aware of the (currently wrong) time zone
ZonedDateTime wrongZoneZdt = ZonedDateTime.ofInstant(date.toInstant(), ZoneId.of("CET"));
// print it to see the result
System.out.println(wrongZoneZdt.format(DateTimeFormatter.ISO_ZONED_DATE_TIME));
// extract the information that should stay (only date and time, NOT zone or offset)
LocalDateTime ldt = wrongZoneZdt.toLocalDateTime();
// print it, too
System.out.println(ldt.format(DateTimeFormatter.ISO_LOCAL_DATE_TIME));
// then take the object without zone information and simply add a zone
ZonedDateTime correctZoneZdt = ldt.atZone(ZoneId.of("GMT"));
// print the result
System.out.println(correctZoneZdt.format(DateTimeFormatter.ISO_ZONED_DATE_TIME));
}
Output:
2020-01-24T09:21:37.167+01:00[CET]
2020-01-24T09:21:37.167
2020-01-24T09:21:37.167Z[GMT]
Explanation:
The reason why your approach did not just correct the zone but also adjusted the time accordingly (which is good when desired) is your use of a LocalDateTime created from an Instant. An Instant represents a moment in time which could have different representations in different zones but it stays the same moment. If you create a LocalDateTime from it and put another zone, the date and time are getting converted to the target zone's. This is not just replacing the zone while keeping the date and time as they are.
If you use a LocalDateTime from a ZonedDateTime, you extract the date and time representation ignoring the zone, which enables you to add a different zone afterwards and keep the date and time as it was.
Edit: If the code is running in the same JVM as the faulty code, you can use ZoneId.systemDefault() to get the same time zone as the faulty code is using. And depending on taste you may use ZoneOffset.UTC instead of ZoneId.of("GMT").
I am afraid you will not get around some calculations here. I'd strongly suggest to follow an approach based on java.time classes, but alternatively you might use the java.util.Calendar class and myCalendar.get(Calendar.ZONE_OFFSET) for those calculations:
https://docs.oracle.com/javase/8/docs/api/java/util/Calendar.html#ZONE_OFFSET
What is the best way to write the following code snippet in Java 8?
private Timestamp resetTime(Timestamp ts) {
ts.setHours(0);
ts.setMinutes(0);
ts.setSeconds(0);
return ts;
}
I was going to use the Calendar class but then read that it is advisable not to do so in Java 8. Any help would be greatly appreciated. Thanks.
Your code seems to try to adjust the Timestamp object to the start of the day in the default time zone (it doesn’t in all cases do that to perfection).
In the old days we used Timestamp to transfer a value to an SQL timestamp with or without time zone. The latter is somewhat self-contradictory: a time stamp is supposed to define a point in time, but a date and time of day without time zone or UTC offset doesn’t do that. So let’s first assume that you want a value to transfer to an SQL database that needs a timestamp with time zone. The type to use for that in Java 8 (assuming JDBC 4.2 or later driver) is OffsetDateTime (some JDBC drivers also accept Instant). Since the databases I know of always use UTC as time zone, I find it most natural and least confusing to transfer an OffsetDateTime in UTC.
private OffsetDateTime resetTime(LocalDate date) {
return date.atStartOfDay(ZoneId.systemDefault())
.toOffsetDateTime()
.withOffsetSameInstant(ZoneOffset.UTC);
}
Example use:
OffsetDateTime ts = resetTime(LocalDate.of(2019, Month.NOVEMBER, 30));
System.out.println(ts);
Output when running in the Africa/Blantyre time zone (just to pick a time zone at random):
2019-11-29T22:00Z
My method accepts a LocalDate argument. A LocalDate is a date without time of day and all that the method needs since it is setting the time of day to 00:00:00 anyway.
Should your database require a timestamp without time zone (not recommended), you will need a LocalDateTime instead:
private LocalDateTime resetTime(LocalDate date) {
return date.atStartOfDay();
}
I was going to use the Calendar class but then read that it is
advisable not to do so in Java 8.
Your are completely correct. Before the advent of java.time in 2014 the Timestamp class was used with SQL databases and the Calendar class would have been the correct means for you task (with the Joda-Time library as a probably better alternative). Even though both Timestamp and Calendar were poorly designed. Now they are long outdated, we should not use any of them anymore.
Link: Oracle tutorial: Date Time explaining how to use java.time.
You can user java.time.ZonedDateTime and java.sql.Timestamp together
Timestamp.valueOf(ZonedDateTime.now().truncatedTo(ChronoUnit.DAYS).toLocalDateTime());
I'm trying to get the epoc time adjusted for the local timezone (i.e. GMT-7, but it displays GMT). I'm fairly sure this should work, but it's not...
Calendar localTime = new GregorianCalendar(TimeZone.getDefault());
Date dd = localTime.getTime();
long t = dd.getTime()/1000;
System.out.printf("%d\n",t);
But it still outputs the epoc time based on GMT, not GMT-7 (my timezone). After playing around for some time I did get this to work...
Date ddd = new Date();
long t = ddd.getTime() + TimeZone.getDefault().getOffset( ddd.getTime() );
t = t/1000;
But why isn't the first block working?
A Date object simply wraps the UTC time in milliseconds since the epoch. This is how all time is represented 'under the hood' in Java. This also makes it consistent to work with. Whenever you want to print something out, apply the TimeZone offset and DST offset to it.
This is why the SimpleDateFormat class accepts a TimeZone but there is no TimeZone setter on the Date class.
Obligatory: I have heard Joda Time is a much easier to use datetime API.
Also, have a look at this post on the standard date and time libraries in Java.