Does having ZoneId and ZoneOffset at the same time make sense? - java

I'm a little bit confused of possibility in Java to have ZoneId and ZoneOffset in date at the same time
For example, does the date 2020-01-01'T'00:00:00+01:00[UTC-8] make sense? What time is it in UTC?
Is it +01:00 offset from UTC-8 timezone, so it's the same as 2020-01-01'T'00:00:00[UTC-7]?

No 2020-01-01'T'00:00:00+01:00[UTC-8] is a self contradiction. I doubt that there’s a way to construct a ZonedDateTime having this value. Or only by defining your own time zone that has an ID of UTC-8 and an offset of +01:00 at that point in time.
A ZonedDateTime has got both time zone and offset.
I am unsure whether it’s relevant to your question, but one way to study whether we can make a point in time out of a self-contradictory string could be:
System.out.println(ZonedDateTime.parse("2020-01-01T00:00:00+01:00[Pacific/Pitcairn]"));
Output on Java 11:
2019-12-31T15:00-08:00[Pacific/Pitcairn]
Pitcairn is at offset -08:00 all year. It seems that ZonedDateTime has chosen to trust the offset +01:00, so the time is the same as 2019-12-31T23:00 in UTC. It has then converted this time to the offset that it finds right for Pacific/Pitcairn time zone according to the time zone database, giving 15:00 in that time zone.
Edit:
What time is it in UTC? Is it +01:00 offset from UTC-8 timezone,
so it's the same as 2020-01-01'T'00:00:00[UTC-7]?
No, it is not the same at all. 2020-01-01'T'00:00:00[UTC-7] would be the same point in time as 2020-01-01T07:00:00 in UTC, but as I said, we got 2019-12-31T23:00 in UTC, which is 8 hours earlier than what you suggested. The -08:00 are not used to interpret the point in time, they are only used for conversion.
PS There aren’t any single quotes in 2020-01-01T00:00:00+01:00[UTC-8]. They are only in a format pattern that you would use for printing and/or parsing such a string.

Related

Instant vs ZoneDateTime. Converting to another timezone

I'm having a hard time understanding java.time between ZoneDateTime - Instant - LocalDateTime
, so far, the only thing I know of is:
Instant works in-between the two
Instant (in my understanding), is a Stamp of time from the moment of time (UTC), a stamp of time that is relevant to the flow of human time, but without a time zone
Zone Date time has TimeZone
Instant does not have Time Zone but can deal with it given that a Zone information is supplied
LocalDate time does not have time zone and cannot deal with zones, it's a Date Time without any relevance on the continuation of entire flow of time (global).
So I have this conversion below
val seoul = "Asia/Seoul"
val zoneId = ZoneId.of(seoul)
val now = ZonedDateTime.now()
val convertedZoneDateTIme = ZonedDateTime.of(now.toLocalDateTime(), zoneId).withZoneSameInstant(ZoneOffset.UTC)
val convertedInstant = now.toInstant().atZone(zoneId)
// expected output
println(convertedInstant.format(DateTimeFormatter.ofPattern(format)))
// not expected output
println(converted.format(DateTimeFormatter.ofPattern(format)))
Output
2021-05-02 03:15:13
2021-05-02 09:15:13
I'm trying to convert a given time to another Time Zone, a use-case where a user moved to a different timezone and I need to update any information about a stored date.
Why am I getting an incorrect value on the second one..? Why do I have to convert it to Instant first and proceed with conversion?
Thank you in advance
Most of your bullets are fully correct. Only you should not use Instant for working between LocalDateTime and ZonedDateTime as you said in your first bullet. Converting between Instant and LocalDateTime requires a time zone (or at least an offset from UTC), so should go through a ZonedDateTime. So ZonedDateTime is the one to use between the two others. As I said, the rest is correct.
You are not being perfectly clear about what you had expected from your code nor how more specifically observed result differs. Assuming you wanted to use the same point in time throughout, this line is where your surprise arises:
val convertedZoneDateTIme = ZonedDateTime.of(now.toLocalDateTime(), zoneId).withZoneSameInstant(ZoneOffset.UTC)
now is a ZonedDateTime in your own time zone (the default time zone of your JVM to be precise). By taking only the date and time of day from it and combining them with a different time zone you are keeping the time of day but in that way (probably) changing the point in the flow of time. Next you are converting to UTC keeping the point in time (the instant), thereby (probably) changing the time of day and possibly the date. You have got nothing left from the ZonedDateTime that was your starting point, and I can’t see that the operation makes sense. To convert now to UTC keeping the point on the timeline use the simpler:
val convertedZoneDateTIme = now.withZoneSameInstant(ZoneOffset.UTC)
With this change your two outputs agree about the point in time. Example output:
2021-05-07 02:30:16 +09:00 Korean Standard Time
2021-05-06 17:30:16 +00:00 Z
I used a format of uuuu-MM-dd HH:mm:ss xxx zzzz.
Also for you other conversion I would prefer to use withZoneSameInstant(). Then we don’t need to go through an Instant.
val convertedInstant = now.withZoneSameInstant(zoneId)
It gives the same result as your code.
A short overview of what is in each of the classes discussed:
Class
Date and time of day
Point in time
Time zone
ZonedDateTime
Yes
Yes
Yes
Instant
-
Yes
-
LocalDateTime
Yes
-
-
Basically you don’t have any use for LocalDateTime for your purpose, and also Instant, while useable, isn’t necessary. ZonedDateTime alone fulfils your needs.

Why is the following date conversion in Java 8 not appropriate?

I have seen a lot of debates on the following date conversion:
timeStamp.toLocalDateTime().toLocalDate();
Some people say that it is not appropriate because the timezone has to be specified for proper conversion, otherwise the result may be unexpected. My requirement is that I have an object that contains Timestamp fields and another object that contains LocalDate fields. I have to take the date difference between both so I think that the best common type to use is LocalDate. I don't see why the timezone has to be specified as either timestamp or LocalDate just represent dates. The timezone is already implied. Can someone give an example when this conversion fails?.
It’s more complicated than that. While it’s true that a Timestamp is a point in time, it also tends to have a dual nature where it sometimes pretends to be a date and time of day instead.
BTW, you probably already know, the Timestamp class is poorly designed and long outdated. Best if you can avoid it completely. If you are getting a Timestamp from a legacy API, you are doing the right thing: immediately converting it to a type from java.time, the modern Java date and time API.
Timestamp is a point in time
To convert a point in time (however represented) to a date you need to decide on a time zone. It is never the same date in all time zones. So the choice of time zone will always make a difference. So one correct conversion would be:
ZoneId zone = ZoneId.of("Africa/Cairo");
LocalDate date = timestamp.toInstant().atZone(zone).toLocalDate();
The Timestamp class was designed for use with your SQL database. If your datatype in SQL is timestamp with time zone, then it unambiguously denotes a point in time, and you need to see it as a point in time as just described. Even when to most database engines timestamp with time zone really just means “timestamp in UTC”, it’s still a point in time.
And then again: sometimes to be thought of as date and time of day
From the documentation of Timestamp:
A Timestamp also provides formatting and parsing operations to support
the JDBC escape syntax for timestamp values.
The JDBC escape syntax is defined as
yyyy-mm-dd hh:mm:ss.fffffffff, where fffffffff indicates
nanoseconds.
This doesn’t define any point in time. It’s a mere date and time of day. What the documentation doesn’t even tell you is that the date and time of day is understood in the default time zone of the JVM.
I suppose that the reason for seeing a Timestamp in this way comes from the SQL Timestamp datatype. In most database engines this is a date and time without time zone. It’s not a timestamp, despite the name! It doesn’t define a point in time, which is the purpose of and is in the definition of timestamp.
I have seen a number of cases where the Timestamp prints the same date and time as in the database, but doesn’t represent the point in time implied in the database. For example, there may be a decision that “timestamps” in the database are in UTC, while the JVM uses the time zone of the place where it’s running. It’s a bad practice, but it is not one that will go away within a few years.
This must also have been the reason why Timestamp was fitted with the toLocalDateTime method that you used in the question. It gives you that date and time that were in the database, right? So in this case your conversion in the question ought to be correct, or…?
Where this can fail miserably without us having a chance to notice is, as others have mentioned already, when the default time zone of the JVM is changed. The JVM’s default time zone can be changed at any time from any place in your program or any other program running in the same JVM. When this happens, your Timestamp objects don’t change their point in time, but they do tacitly change their time of day, sometimes also their date. I’ve read horror stories — in Stack Overflow questions and elsewhere — about the wrong results and the confusion coming out of this.
Solution: don’t use Timestamp
Since JDBC 4.2 you can retrieve java.time types out of your SQL database. If your SQL datatype is timestamp with time zone (recommended for timestamps), fetch an OffsetDateTime. Some JDBC drivers also let you fetch an Instant, that’s fine too. In both cases no time zone change will play any trick on you. If the SQL type is timestamp without time zone (discouraged and all too common), fetch a LocalDateTime. Again you can be sure that your object doesn’t change its date and time no matter if the JVM time zone setting changes. Only your LocalDateTime never defined a point in time. Conversion to LocalDate is trivial, as you have already demonstrated in the question.
Links
java.sql.Timestamp documentation
Wikipedia article: Timestamp
Question: Getting the date from a ResultSet for use with java.time classes
Question: Java - Convert java.time.Instant to java.sql.Timestamp without Zone offset
As you can see here(taken from https://stackoverflow.com/a/32443004/1398418):
Timestamp represents a moment in UTC and is the equivalent of the modern Instant.
When you do:
timeStamp.toLocalDateTime().toLocalDate();
the timeStamp is converted from UTC to the system timezone. It's the same as doing:
timeStamp.toInstant().atZone(ZoneId.systemDefault()).toLocalDate()
For example:
Timestamp stamp = new Timestamp(TimeUnit.HOURS.toMillis(-1)); // UTC 1969-12-31
System.setProperty("user.timezone", "EET"); // Set system time zone to Eastern European EET - UTC+2
stamp.toLocalDateTime().toLocalDate(); // represents EET 1970-01-01
stamp.toInstant().atZone(ZoneId.systemDefault()).toLocalDate(); // represents EET 1970-01-01
That result (getting the date in the system time zone) is expected and if that's what you want, doing timeStamp.toLocalDateTime().toLocalDate() is appropriate and correct.
You're saying that you have a LocalDate field in some object and you want to get a period between it and a Timestamp, well that's just not possible without aditional information. LocalDate just represents a date, it has no time zone information, you need to know how it was created and what time zone was used.
If it represent a date in the system time zone then getting the period by using timeStamp.toLocalDateTime().toLocalDate() would be correct, if it represents a date in UTC or any other time zone then you might get a wrong result.
For example if the LocalDate field represents a date in UTC you will need to use:
timeStamp.toInstant().atZone(ZoneId.of("UTC")).toLocalDate();
Example: the 23rd of January becomes the 24th
You asked:
Can someone give an example when this conversion fails?.
Yes, I can.
Start with the 23rd of January.
LocalDate ld = LocalDate.of( 2020 , Month.JANUARY , 23 );
LocalTime lt = LocalTime.of( 23 , 0 );
ZoneId zMontreal = ZoneId.of( "America/Montreal" );
ZonedDateTime zdt = ZonedDateTime.of( ld , lt , zMontreal );
Instant instant = zdt.toInstant();
zdt.toString() = 2020-01-23T23:00-05:00[America/Montreal]
instant.toString() = 2020-01-24T04:00:00Z
The Instant class represents a moment as seen in UTC. Let's convert to the terribly legacy class java.sql.Timestamp using the new conversion method added to that old class.
// Convert from modern class to troubled legacy class `Timestamp`.
java.sql.Timestamp ts = Timestamp.from( instant );
ts.toString() = 2020-01-23 20:00:00.0
Unfortunately, the Timestamp::toString method dynamically applies the JVM’s current default time zone while generating text.
ZoneOffset defaultOffset = ZoneId.systemDefault().getRules().getOffset( ts.toInstant() );
System.out.println( "JVM’s current default time zone: " + ZoneId.systemDefault() + " had an offset then of: " + defaultOffset );
JVM’s current default time zone: America/Los_Angeles had an offset then of: -08:00
So Timestamp::toString misreports the object’s UTC value after adjusting back eight hours from 4 AM to 8 PM. This anti-feature is one of several severe problems with this poorly designed class. For more discussion of the screwy behavior of Timestamp, see the correct Answer by Ole V.V.
Let's run your code. Imagine at runtime the JVM’s current default time zone is Asia/Tokyo.
TimeZone.setDefault( TimeZone.getTimeZone( "Asia/Tokyo" ) );
LocalDate localDate = ts.toLocalDateTime().toLocalDate();
Test for equality. Oops! We ended up with the 24th rather than the 23rd.
boolean sameDate = ld.isEqual( localDate );
System.out.println( "sameDate = " + sameDate + " | ld: " + ld + " localDate: " + localDate );
sameDate = false | ld: 2020-01-23 localDate: 2020-01-24
See this code run live at IdeOne.com.
So what is wrong with your code?
Never use java.sql.Timestamp. It is one of several terrible date-time classes shipped with the earliest versions of Java. Never use these legacy classes. They have been supplanted entirely by the modern java.time classes defined in JSR 310.
You called toLocalDateTime which strips away vital information. Any time zone or offset-from-UTC is removed, leaving only a date and a time-of-day. So this class cannot be used to represent a moment, is not a point on the timeline. Ex: 2020-12-25 at noon — is that noon in Delhi, noon in Düsseldorf, or noon in Detroit, three different moments several hours apart? A LocalDateTime is inherently ambiguous.
You ignored the crucial issue of time zone in determining a date. For any given moment, the date varies around the globe. At one moment it may be “tomorrow” in Australia while simultaneously “yesterday” in Mexico.
The problem lies in what is being represented by these objects. Your question forgets a crucial aspect, which is: What is the type of timeStamp?
I'm guessing it's a java.sql.Timestamp object.
Timestamp, just like java.util.Date, is old API equivalent to Instant.
It represents an instant in time, in the sense that it is milliseconds since jan 1st 1970 UTC. The system has no idea which timezone that was supposed to be in. You're supposed to know; the error, if an error is going to occur here, already occurred before you get to this code. Here's a trivial explanation of how it COULD go wrong:
you start off with a user entering a date in a date field on a webform; it's 2020-04-01.
Your server, running in Amsterdam, saves it to a DB column that is internally represented as UTC, no zone. This is a mistake (you're not saving an instant in time, you're saving a date, these two are not the same thing). What is actually stored in the DB is the exact moment in time that it is midnight, 2020-04-01 in amsterdam (in UTC, that'd be 22:00 the previous day!).
Later, you query this moment in time back into a java.sql.Timestamp object, and you're doing this when the server's tz is elsewhere (say, London time). You then convert this to a localdatetime, and from there to a localdate, and.... you get 2020-03-31 out.
Whoops.
Dates should remain dates. Never convert LocalX (be it Time, Date, or DateTime) to Instant (or anything that effectively is an instant, including j.s.Timestamp, or j.u.Date - yes, j.u.Date does NOT represent a date, it is very badly named), or vice versa, or pain will ensue. If you must because of backward APIs take extreme care; it's hard to test that 'moving the server's timezone around' breaks stuff!

Java: get pacific timezone without DST

I need to use Pacific timezone in my code that doesn't incorporate DST. I know that America/Los_Angeles takes DST into account. When I try to use PST, the time API throws this exception:
java.time.zone.ZoneRulesException: Unknown time-zone ID: PST
Is there a solution for this?
"PST" isn't a time zone ID.
It sounds like you don't really want to use the Pacific time zone so much as a constant UTC offset of -8 hours. You can do that with:
ZoneOffset offset = ZoneOffset.ofHours(-8);
Options include:
Use a ZoneOffset as already explained in Jon Skeet’s answer. Since ZoneOffset is a subclass of ZoneId, you can use it everywhere a ZoneId is required.
Use ZoneId.of("Pacific/Pitcairn"). This time zone is at offset -08:00 all year. Pitcairn Standard Time is abbreviated PST too.
For the sake of completeness ZoneId.of("Etc/GMT+8") gives you a zone ID that is not a ZoneOffset but is equivalent to the ZoneOffset (+8 is no typo, the sign is intentionally inverted).
Link: List of tz database time zones

How to convert UTC date to UTC OffsetDateTime in java 8?

How can I convert the date object which is already in UTC to an OffsetDateTime Object in UTC itself in Java? This logic should be written on a microservice where the timezone can be entirely different. So .now() and other things are ruled out, I guess. Also, I don't want to pass Timezone as params anywhere.
Sample code:
public OffsetDateTime convertFrom(Date source) {
LOGGER.info("source: " + source.toString());
LOGGER.info("instant: " + source.toInstant().toString());
LOGGER.info("response: " + source.toInstant().atOffset(ZoneOffset.UTC).toString());
return source.toInstant().atOffset(ZoneOffset.UTC);
}
and the output I get is:
source: 2018-07-11 15:45:13.0
instant: 2018-07-11T19:45:13Z
response: 2018-07-11T19:45:13Z
I want my output return to be 2018-07-11 15:45:13Z for input 2018-07-11 15:45:13.0
tl;dr
A java.util.Date and a Instant both represent a moment in UTC. Other time zones and offsets are irrelevant.
Instant instant = myJavaUtilDate.toInstant()
How can I convert the date object which is already in UTC to an OffsetDateTime Object in UTC itself in Java?
You don’t need OffsetDateTime. Use Instant as shown above.
Use ZonedDateTime, not OffsetDateTime
You do not need OffsetDateTime. An offset-from-UTC is merely a number of hours and minutes. Nothing more, nothing less. In contrast, a time zone is a history of the past, present, and future changes to the offset used by the people of a particular region. So a time zone, if known, is always preferable to a mere offset. So use ZonedDateTime rather than OffsetDateTime wherever possible.
Use OffsetDateTime only when given an offset-from-UTC, such as +02:00, without the context of a specific time zone, such as Europe/Paris.
Convert Date to Instant
If given a java.util.Date, concert to the modern class (Instant) that replaced that troublesome old class. Both represent a moment in UTC as a count from the same epoch reference of first moment of 1970 in UTC. The modern class resolves to nanoseconds rather than milliseconds. To convert, call new methods added to the old class.
Instant instant = myJavaUtilDate.toInstant() ;
Remember that both java.util.Date and Instant always represent a moment in UTC.
Capture current moment, “now”
Capture the current moment in UTC.
Instant instant = Instant.now() ;
now() and other things are ruled out, I guess.
No, you can always capture the current moment by calling Instant.now() on any machine at any time. The JVM’s current default time zone is irrelevant as Instant is always in UTC.
Adjust from UTC into another time zone. Same moment, same point on the timeline, different wall-clock time. <— That is the most important concept to comprehend in this discussion!
ZoneId z = ZoneId.of( "Africa/Tunis" ) ;
ZonedDateTime zdt = instant.atZone() ;
As a shortcut, you can skip the Instant when capturing current moment.
ZonedDateTime zdt = ZonedDateTime.now( z ) ;
Move back to UTC by extracting a Instant object.
Instant instant = zdt.toInstant() ;
Tip: Focus on UTC
Usually best to have most of your work in UTC. When storing, logging, debugging, or exchanging moments, use UTC. Forget about your own parochial time zone while on the job as a programmer or sysadmin; learn to think in UTC. Keep a second click in your office set to UTC.
Avoid flipping between time zones all the time. Stick with UTC. Adjust to a time zone only when presenting to the user or when business logic demands.
It is already working as intended, the problem is that Date.toString is "helpfully" converting the internal timestamp to your local timezone. Using Date.toGMTString would result in the exact same timestamp for each of the values.
If the resulting timestamp is wrong then the problem lies in the creation of the Date instance. Using the constructor like new Date(2018, 7, 11, 15, 45, 11) would result in that date being calculated for the system timezone, not UTC. To create it for UTC there is Date.UTC but all these APIs have been deprecated since Java 1.1 because they are so confusing.
public static OffsetDateTime convertFrom(Date source) {
if (source instanceof Timestamp) {
return ((Timestamp) source).toLocalDateTime()
.atOffset(ZoneOffset.UTC);
}
return source.toInstant().atOffset(ZoneOffset.UTC);
}
The object that was passed to your method was a java.sql.Timestamp, not a Date. We can see this fact from the way it was printed: 2018-07-11 15:45:13.0 is the return value from Timestamp.toString(). The Timestamp class is implemented as a subclass of Date, but this doesn’t mean that we can nor should handle it as a Date. The documentation warns us:
Due to the differences between the Timestamp class and the
java.util.Date class mentioned above, it is recommended that code
not view Timestamp values generically as an instance of
java.util.Date. The inheritance relationship between Timestamp and
java.util.Date really denotes implementation inheritance, and not
type inheritance.
In the implementation above I have assumed that you cannot mitigate the possibility of getting a Timestamp argument, so I am handling the possibility the best I can. The code is still fragile, though, because sometimes a Timestamp denotes a point in time (I should say that this is the point), at other times it denotes a date and hour of day. Granted that the Timestamp does not hold a time zone in it, the two are not the same. I understand that your sample Timestamp denotes a date and time of 2018-07-11 15:45:13.0, and you want this interpreted in UTC. My code does that (your code in the question, on the other hand, correctly handles the situation where the Timestamp denotes a point in time). Also, even though no time zone is passed in my code, its behaviour still depends on the time zone setting of your JVM.
When I pass a Timestamp of 2018-07-11 15:45:13.0 to my method above, it returns an OffsetDateTime of 2018-07-11T15:45:13Z.
The double nature of Timestamp is unfortunate and confusing, and the only real solution would be if you could avoid that class completely. The Date class too is poorly designed, and both are outdated and replaced by java.time, the modern Java date and time API. If you cannot avoid the old classes in your code, I certainly understand your desire to convert to the modern OffsetDateTime first thing. If on the other hand I understand correctly that the date and time comes through JSON, you may be able to parse it on your side without any of the old date and time classes, which would be a good solution to your problem. And under all circumstances, if your real goal is to represent the point in time in a time zone neutral way, I agree with Basil Bourque in preferring an Instant over an OffsetDateTime in UTC.
Link: Documentation of java.sql.Timestamp

Datest, offsets and timezones in java

I'm trying to understand the underlying mechanic of temporal utilities.
So, I made the next example:
public class Test {
public static void main(String[] args) {
System.out.println(Instant.now().getEpochSecond());
System.out.println(new Date().getTime());
System.out.println(LocalDateTime.now().atZone(ZoneId.systemDefault()).toEpochSecond());
System.out.println(LocalDateTime.now().toEpochSecond(ZoneOffset.UTC));
System.out.println(ZoneId.systemDefault().toString());
}
}
Output is:
1460651620
1460651620182
1460651620
1460662420
Europe/Helsinki
My current systemDefault zoneId is Europe/Helsinki (+3 hours)
When we create new Date() it has the unix timestamp (UTC).
This is my base point to compare printed results.
1. In my third System.out I have the LocalDateTime with established timezone systemDefault but output is actually the same. I expected the bigger value (+3 hours).
2. In fourth output line I although have confusing result. I expected the same value with new Date().getTime()
Need some help to understand the output.
Any method of type getEpochSecond() and toEpochSecond() give you the amount of seconds since the epoch of 1970-01-01T00:00:00Z, the time is running the same way whatever the timezone so the result is the same whatever the time zone chosen.
Assuming that you spent 10 minutes to write this question in your time zone, it will be the same 10 minutes in my time zone.
I have the LocalDateTime with established timezone systemDefault
No, you don't. You started with a LocalDateTime, but then you converted it into a ZonedDateTime. A ZonedDateTime is effectively a LocalDateTime with a ZoneId and an offset from UTC to handle ambiguity.
LocalDateTime doesn't have a toEpochSecond() method, because it doesn't represent a fixed point in time. It has toEpochSecond(ZoneOffset) because that "anchors" the local date/time to a fixed point in time by applying a UTC offset.
Compare that with ZonedDateTime which does have a parameterless toEpochSecond() method, because it represents a fixed point in time, with the additional context of a time zone. (You can either view a ZonedDateTime as an Instant with a ZoneId, or a LocalDateTime with a ZoneId and a ZoneOffset; they're equivalent, but personally I prefer the latter notion.)
Note that your code could give you a different result - if you were in a period where LocalDateTime.now() was ambiguous due to the clocks going back (typically at the end of daylight saving). In that case, LocalDateTime.atZone picks the earlier occurrence of that LocalDateTime, whereas in reality you might be experiencing the later occurrence. That's the difference between LocalDateTime.now().atZone(zone) and ZonedDateTime.now(zone) - the latter will always "know" the offset correctly.

Categories