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.
Related
A server is storing the date/time of a client action with System.currentTimeMillis(). Let's say this server is in the EST time zone.
A client in France wants to see what time that action was made. That long value stored via System.currentTimeMillis() (on the US server) is returned to the client in France.
I'm having problems understanding how to make that conversion on the client side to accurately describe their action time. My understanding is that on the server the System.currentTimeMillis() is a zoneless UTC time. Things seem strange when I instantiate a Calendar object with a "UTC" timezone, so is the server storing a time zoned epoch time when it saves with currentTimeMillis()
First attempt:
String timezone = clientTimezone;//Lets say france
Calendar cal = Calendar.getInstance();
cal.setTimeInMillis(time); //This time var is the server stored value (currentTimeMillis)
if(!timezone.equals("")) {
long timezoneAlteredTime = time + TimeZone.getTimeZone(timezone).getRawOffset();
cal = Calendar.getInstance(TimeZone.getTimeZone(timezone));
cal.setTimeInMillis(timezoneAlteredTime);
}
Other attempt:
String timezone = clientTimezone;//Lets say france
TimeZone utc = TimeZone.getTimeZone("UTC");
Calendar cal = Calendar.getInstance(utc);
cal.setTimeInMillis(time); //This time var is the server stored value
if(!timezone.equals("")) {
long timezoneAlteredTime = time + TimeZone.getTimeZone(timezone).getRawOffset();
cal = Calendar.getInstance(TimeZone.getTimeZone(timezone));
cal.setTimeInMillis(timezoneAlteredTime);
}
Am I misinterpreting this issue altogether? How would you go about this conversion
The Answer by Ole V.V. is correct: Call Instant::atZone to adjust a moment from UTC to a particular time zone. Same point on the timeline, different wall-clock time.
Here are a few more thoughts.
Let's say this server is in the EST time zone.
FYI, generally best to set UTC as the time zone on servers.
the System.currentTimeMillis() is a zoneless UTC time
Not zoneless. That method returns the number of milliseconds since the epoch reference of the first moment of 1970 as seen in UTC. (An offset-from-UTC of zero hours-minutes-seconds)
Without the context of an offset-from-UTC or a time zone, you cannot represent a moment, a specific point on the timeline.
Things seem strange when I instantiate a Calendar object
Everything about the Calendar class is strange.
That is why we stopped using that terrible class years ago. Use only the java.time classes.
A server is storing the date/time of a client action with System.currentTimeMillis().
We have a class for that: Instant. So no need to use a mere integer number, now you can use a type-safe and handy class.
The Instant class represents a moment as seen in UTC. Its objects use a resolution of nanoseconds, though most computer clocks nowadays can capture a moment as microseconds.
Instant instant = Instant.now() ;
You can convert between your count-of-milliseconds and Instant.
Instant instant = Instant.ofEpochMilli( yourCountOfMillis ) ;
An Instant is in UTC, by definition. To see the same moment from an on other offset, use OffsetDateTime. To see the same moment in a particular time zone, use ZonedDateTime.
Understand that a time zone is a history of past, present, and future changes to the offset used by the people of a particular region. Politicians frequently change the offset of their jurisdiction(s) for various reasons.
Search Stack Overflow to learn more. These issues have been covered many many times already.
java.time
This is pretty straightforward when you know how. And I recommend that you leave the work to java.time, the modern Java date and time API.
String clientTimezone = "Europe/Paris";
long time = 1_607_708_578_154L;
ZonedDateTime timeInClientTimeZone = Instant.ofEpochMilli(time)
.atZone(ZoneId.of(clientTimezone));
System.out.println(timeInClientTimeZone);
Output from this example code snippet is:
2020-12-11T18:42:58.154+01:00[Europe/Paris]
What went wrong in your code?
My understanding is that on the server the System.currentTimeMillis()
is a zoneless UTC time.
This far your understanding is correct.
Even though the epoch is usually defined in UTC, the clue here is zoneless. So you don’t need any date-time object in UTC. You also don’t need to make any adjustments to the millisecond count, so by doing so, you are turning a correct time into an incorrect one.
Link
Oracle tutorial: Date Time explaining how to use java.time.
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
I have written below code which is running, and giving output. But I'm not sure It's a right one.
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss");
Date date = new Date();
sdf.setTimeZone(TimeZone.getTimeZone("GMT-7"));
String value = sdf.format(date);
System.out.println(value);
Date date2 = sdf.parse(value);
long result = date2.getTime();
System.out.println(result);
return result;
The above code what I'm trying is, I just need to get the current time of GMT time zone and convert it as epoch format which is gonna used in Oracle db.
Can someone tell me that format, and the above code is right?
First, you should not store time since the epoch as a timestamp in your database. Look into the date-time datatypes that your DMBS offers. In Oracle I think that a date column will be OK. For most other DBMS you would need a datetime column. timestamp and timestamp with timezone may be other and possibly even sounder options depending on your exact requirements.
However, taking your word for it: Getting the number of milliseconds since the epoch is simple when you know how:
long millisecondsSinceEpoch = System.currentTimeMillis();
System.out.println(millisecondsSinceEpoch);
This just printed:
1533458641714
The epoch is defined in UTC, so in this case we need to concern ourselves with no other time zones.
If you needed seconds rather than milliseconds, it’s tempting to divide by 1000. However, doing your own time conversions is a bad habit since the libraries already offers them, and using the appropriate library methods gives clearer, more explanatory and less error-prone code:
long secondsSinceEpoch = Instant.now().getEpochSecond();
System.out.println(secondsSinceEpoch);
1533458641
You said:
I just need to get the current time of GMT time zone…
Again taking your word:
OffsetDateTime currentTimeInUtc = OffsetDateTime.now(ZoneOffset.UTC);
System.out.println(currentTimeInUtc);
long millisecondsSinceEpoch = currentTimeInUtc.toInstant().toEpochMilli();
System.out.println(millisecondsSinceEpoch);
2018-08-05T08:44:01.719265Z
1533458641719
I know that GMT and UTC are not exactly the same, but for most applications they can be (and are) used interchangeably.
Can someone tell me (if) the above code is right?
When I ran your code just now, its output agreed with mine except the milliseconds were rounded down to whole thousands (whole seconds):
1533458641000
Your code has some issues, though:
You are using the old, long out-dated and poorly designed classes SimpleDateFormat, Date and TimeZone. The first in particular has a reputation for being troublesome. Instead we should use java.time, the modern Java date and time API.
Bug: In your format pattern string you are using lowercase hh for hour of day. hh is for hour within AM or PM, from 1 through 12, so will give you incorrect results at least half of the day. Uppercase HH is for hour of day.
Don’t use GMT-7 as a time zone. Use for example America/Los_Angeles. Of course select the time zone that makes sense for your situation. Edit: You said:
I just want to specify the timezone for sanjose. GMT-7 is refer to
sanjose current time.
I believe many places are called San Jose. If you mean San Jose, California, USA, you are going to modify your program to use GMT-8 every time California goes back to standard time and opposite when summer time (DST) begins?? Miserable idea. Use America/Los_Angeles and your program will work all year.
Since you ask for time in the GMT time zone, what are you using GMT-7 for at all?
There is no point that I can see in formatting your Date into a string and parsing it back. Even if you did it correctly, the only result you would get would be to lose your milliseconds since there are no milliseconds in your format (it only has second precision; this also explained the rounding down I observed).
Links
Oracle tutorial: Date Time explaining how to use java.time, the modern Java date and time API.
San Jose, California on Wikipedia
Why not use Calendar class?
public long getEpochTime(){
return Calendar.getInstance(TimeZone.getTimeZone("GMT-7")).getTime().getTime()/1000; //( milliseconds to seconds)
}
It'll return the current Date's Epoch/Unix Timestamp.
Based on Harald's Comment:
public static long getEpochTime(){
return Clock.system(TimeZone.getTimeZone("GMT-7").toZoneId() ).millis()/1000;
}
Here is a solution using the java.time API
ZonedDateTime zdt = LocalDateTime.now().atZone(ZoneId.of("GMT-7"));
long millis = zdt.toInstant().toEpochMilli();
I want to format time like 19:19:00 to different time zones. If I use SimpleDateFormat it always takes into account the start of the epoch: 1970.01.01.
Some timezones have different offsets on the start of the epoch and now. For example, the default offset from Europe/Kiev now is UTC+0200 but in 1970 it was UTC+0300. That means if I run my server under Europe/Kiev the client which login under Europe/Berlin(UTC+0100) will see three hours different instead of two.
I can solve this problem by writing a custom formatter for java.sql.Time. But I want to ask maybe there are some common approach or Java tools/libraries which can solve it.
Another solution can be using joda-time:
TimeZone.setDefault(TimeZone.getTimeZone("Europe/Kiev"));
DateTimeZone.setDefault(DateTimeZone.forID("Europe/Kiev"));
DateTimeFormat.forPattern("HH:mm:ss.SSS")
.withZone(DateTimeZone.forID("Europe/Berlin"))
.print(Time.valueOf("19:00:00").getTime());
You can't format just a time to different time zones. You need a date.
If you want to assume that the date of that time is today, you can try this code:
ZoneId originalZone = ZoneId.of("Europe/Kiev");
ZoneId targetZone = ZoneId.of("Europe/Berlin");
LocalTime originalTime = LocalTime.parse("19:19:00");
LocalTime convertedTime = LocalDate.now(originalZone)
.atTime(originalTime)
.atZone(originalZone)
.withZoneSameInstant(targetZone)
.toLocalTime();
System.out.println(convertedTime);
Is java.time.instant an alternative for you? It handles all Timestamps internally as UTC-Time.
One way to parse it from a string is Instant.parse("2018-05-30T19:00:00")
If you want to have the time for a specific timezone you can get it with myInstant.atZone("Zone")
ZoneId originalZone = ZoneId.of("Europe/Kiev");
ZoneId targetZone = ZoneId.of("Europe/Berlin");
LocalDate assumedDate = LocalDate.now(originalZone);
String formattedTime = assumedDate.atTime(LocalTime.parse("19:19:00"))
.atZone(originalZone)
.withZoneSameInstant(targetZone)
.format(DateTimeFormatter.ofPattern("HH:mm:ss"));
System.out.println(formattedTime);
Today this printed:
18:19:00
When you know the date, you should of course use that instead of just today. In the case of Kyiv and Berlin I think they follow the same rules for summer time (DST), so the precise date may not be important. If converting between zones that don’t use the same transitions, or between a time zone that uses summer time and one that doesn’t, it’s suddenly crucial. And who knows in which of those two countries the politicians will change the rules next year? Better be safe.
Link: Oracle tutorial: Date Time explaining how to use java.time.
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.