Java: Local Standard Time to Instant - java

A service like this has an explicit note that
All times are specified in Local Standard Time (LST). Add 1 hour to
adjust for Daylight Saving Time where and when it is observed.
Given a LocalDateTime object ldt, creating an instant is as simple as
Instant.of(ldt,zoneId). However, if I supply the ZoneId, then Instant will assume the daylight-savings (DST) adjustment has already been made, and ldt is with respect to DST, which is not the case.
What is the most Java-esque way to make the Instant's constructor apply the "standard" offset rather than taking into account the DST?
EDIT: I mean, short of hard-codedly checking, for the given year, the exact dates of DST transitions? Sure, I can get the offset as
ZoneOffset offset= ZoneOffset.of(zoneId) and then subtract offset.getTotalSeconds() from the given timestamp, to move to the "reference" time. But giving that reference-adjusted timestamp to Instant still
has the same problem of the DST.
EDIT: As has been suggested below, I tried this:
String str = String.format("%04d-%02d-%02dT%s:00", year, month, day, hourMinutes);
LocalDateTime ldt = LocalDateTime.parse(str);
Instant i1= ldt.toInstant(zoneId.getRules().getOffset(ldt));
Instant key= ldt.toInstant(zoneId.getRules().getStandardOffset(i1));

You can get the standard offset of a ZoneId by calling getRules() and then getStandardOffset().
However, you need an Instant. Because sometimes the standard offset changes too...
So one way is to first convert the LocalDateTime to Instant, using the offset at that LocalDateTime. Then we get the standard offset at that instant, and use that to get the actual Instant we want:
LocalDateTime ldt = ...;
Instant i = ldt.toInstant(someZoneId.getRules().getOffset(ldt));
Instant i2 = ldt.toInstant(someZoneId.getRules().getStandardOffset(i));
System.out.println(i2);
I think this wouldn't work for some specific local times if both the standard offset and the DST offset changes at the same time, because the first instant i is calculated with the offset before the transition. You can calculate i with the offset after the transition by doing this:
Instant i;
ZoneOffsetTransition transition = someZoneId.getRules().getTransition(ldt)
if (transition == null) {
i = ldt.toInstant(someZoneId.getRules().getOffset(ldt));
} else {
i = ldt.toInstant(transition.getOffsetAfter());
}
Update:
But even still, this will produce the wrong result for some really corner cases. The problem is that java.time does not provide a way of getting the transitions in standard offset.

The ZonedDateTime description states:
This class handles conversion from the local time-line of
LocalDateTime to the instant time-line of Instant. The difference
between the two time-lines is the offset from UTC/Greenwich,
represented by a ZoneOffset.
Convert the LocalDateTime object to ZonedDateTime with atZone using the zone identifier.
Then call the default toInstant method, which is inherited from the ChronoZonedDateTime class, to get an Instant object.

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 time not converted according to time zone?

I have LocalDateTime that keeps value in UTC.
I want to convert it to local date and time according to a time zone,
here how I do it:
public LocalDateTime convertUTC2LocalDateTimeZone(LocalDateTime dateTime){
System.out.println("dateTime:" + dateTime);
ZonedDateTime zonedDateTime = dateTime.atZone(ZoneId.of("Canada/Mountain"));
System.out.println("zonedDateTime:" + zonedDateTime);
LocalDateTime ldt = zonedDateTime.toLocalDateTime()
System.out.println("ldt:" + ldt);
return ldt;
}
Output:
dateTime:2018-07-15T10:00:46
zonedDateTime:2018-07-15T10:00:46-06:00[Canada/Mountain]
ldt:2018-07-15T10:00:46
As you can see the ldt value is the same as input, no time conversion occurred.
Any idea why time conversion not occurred?
A LocalDateTime represents a "local time and date". In other words: it's something abstract like "January 1st 2020, 10:00 AM" without any time zone information.
It does not represent anything in UTC. There simply is no time zone information contained in it.
So it doesn't represent a "physical" or exact point in time. To do this you need to convert it to a ZonedDateTime by adding some time zone. The way you do in your code basically says: "Give me a ZonedDateTime object that represents the local time provided by this LocalDateTime in the given time zone".
That means this ZonedDateTime does represent a fixed point in time (i.e. you can calculate the milliseconds since the epoch, basically).
Then you ask "given that ZonedDateTime, what would the local date/time be?", which will just return the value that you initially put in without any modification.
To actually convert from UTC to some other timezone, you need to explicitly create a ZonedDateTime in the UTC timezone first:
create ZonedDateTime representing UTC
calculate ZonedDateTime in the target timezone
get a LocalDateTime from the ZonedDateTime created in #2.
So in code:
ZonedDateTime utcDateTime = dateTime.atZone(ZoneOffset.UTC); // #1
ZonedDateTime mountainDateTime = utcDateTime.withZoneSameInstant(ZoneId.of("Canada/Mountain")); // #2
LocalDateTime localDateTimeAtMountain = mountainDateTime.toLocalDateTime(); // #3

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

How to check LocalDateTime instance is local or UTC in java?

I have want to find whether my LocalDateTime instance holds local date&time or UTC date&time (like DateTime.Kind property in C#)
LocalDateTime date1=LocalDateTime.now(); // it is local
LocalDateTime date2=LocalDateTime.now(ZoneId.of("UTC")); // it is UTC
Anything like (date1.getKind() == Kind.UTC || date1.getKind() == Kind.Local) in Java?
The LocalDateTime object itself doesn't store the timezone information - it has only the date and time related fields: day, month and year; hour, minute, seconds and nanoseconds. But the now method uses a timezone or an offset to get the correct values for those fields.
That's because the answer to the questions "What day is today?" and "What time is it?" is not as simple as we might think.
It's common to think that the answer is as simple as taking a look at our calendar/cell phone/whatever and seeing the current date/time. But the technically correct answer is: "It depends".
It depends, basically, on where you are. At this moment, each place in the world has its own local date and time. For example, in July 5th, 2017: while it was 14h (or 2 PM) in São Paulo, it was 6 PM in London and 5 PM in UTC, but in Tokyo it was 2 AM of the next day (July 6th).
Each region in the world has specific rules to determine what's their local time during history, and of course it affects their local date.
And the concept that maps a country/city/region to these rules is a timezone.
That's why the now method needs a timezone. The ZoneId object loads all the timezone data to check what's the current date and time in that zone and adjust the day/month/year/hour/minute/second/nanosecond values accordingly. The version that receives no parameters (LocalDateTime.now()) will use the system's default timezone, so the API always uses some timezone in the end.
The timezone (or the offset, such as ZoneOffset.UTC) is used to get the correct values for day, month, year, hour, minute, second and nanosecond, and then - in the case of LocalDateTime and any other classes that don't keep the zone - discarded.
So, the concept might be a little different from what you're thinking. If I do:
// ZoneOffset.UTC is equivalent to ZoneId.of("UTC")
LocalDateTime date = LocalDateTime.now(ZoneOffset.UTC);
What this code does is: "take the current date and time in UTC, and get just the date and time fields, discarding the timezone/offset information".
When I ran this code, the current date/time in UTC was 2017-09-25T12:15:43.570Z, so the LocalDateTime has the value equivalent to 2017-09-25T12:15:43.570 (without any timezone information, just the date and time fields). If I call now() without arguments, it'll use the JVM default timezone (in my case, it's America/Sao_Paulo), and the value will be 2017-09-25T09:15:43.570.
So, with a LocalDateTime you can get the values, but you can't know from which timezone those values came from, because it doesn't keep this information.
If you want a UTC date, you must use another classes, designed to keep this information:
Instant.now() - this will always get the current UTC instant
OffsetDateTime.now(ZoneOffset.UTC) - with this you can query for date and time fields (such as getDayOfMonth() or getHour())
ZonedDateTime.now(ZoneOffset.UTC) - for UTC, it's the same as OffsetDateTime, but if you use a different timezone, it handles all timezone specific data, such as Daylight Saving Time changes.
To check if such object is in UTC, one way is to use the getZone() method:
ZonedDateTime z = ZonedDateTime.now(ZoneOffset.UTC);
System.out.println(z.getZone().equals(ZoneOffset.UTC)); // true
But if you use equivalents like ZoneId.of("UTC"), the equals method return false. So you could also check if z.getZone().getId() is equals to Z or UTC. With OffsetDateTime, it's similar:
OffsetDateTime odt = OffsetDateTime.now(ZoneOffset.UTC);
System.out.println(odt.getOffset().equals(ZoneOffset.UTC)); // true
With Instant you don't need to check, because it's always in UTC.
You can check all the available types in Oracle's date/time tutorial.
Both ZonedDateTime and OffsetDateTime can be converted to a LocalDateTime using the toLocalDateTime() method:
// dt will have the current date and time in UTC
LocalDateTime dt = ZonedDateTime.now(ZoneOffset.UTC).toLocalDateTime();
// or
LocalDateTime dt = OffsetDateTime.now(ZoneOffset.UTC).toLocalDateTime();
With this, the dt variable will have all the date and time fields (day/month/year, hour/minute/second/nanosecond) that corresponds to the current date/time in UTC. But it won't keep any timezone/offset information, so the LocalDateTime object itself can't know from which timezone those values came from.
I realize this question is a bit old, but I am learning Java and found myself trying to do something very similar. After some reading I found I could do what you are asking with this:
public static void main (String args[]) {
LocalDateTime now = LocalDateTime.now(Clock.systemDefaultZone()); // The clock argument is not really needed here.
String pattern = "dd MMM yyyy HH:mm:ss"; // Setup your format for output
DateTimeFormatter dtf = DateTimeFormatter.ofPattern(pattern);
System.out.println("Local time is: " + dtf.format(now));
LocalDateTime utc = LocalDateTime.now(Clock.systemUTC()); // Define alternate timezone
System.out.println("GMT/UTC is: "+dtf.format(utc));
}

What is the easiest way to print a DateTime using an hour adjustment instead of a locale?

I am working on a system where each user can specify a setting of their adjustment from UTC in hours.
So when a date/time is input from this user, the value is adjusted by their setting and saved as UTC. Similarly, on the way out, the date/time is retrieved from the database, adjusted per their setting and displayed.
I might be thinking about this too much, but does this mean to show the correct date/time for each person, I have to effectively adjust the hours and tell the instance of my SimpleDateFormat that this is "UTC"? Right now I am in the UK where the current time zone is UTC+1 and if I don't specify to print in UTC then the time is off by one hour!
DateTime dateWithOffset = statusDate.plusMinutes(currentTimezoneOffsetInMinutes);
SimpleDateFormat sdf = new SimpleDateFormat("dd MMM yyyy HH:mm");
sdf.setTimeZone(TimeZone.getTimeZone("UTC"));
return sdf.format(dateTime.toDate());
Am I thinking about this correctly? Or is there an easier way to print a date in a format given that I just want an hours adjustment from UTC?
You are working too hard. Never do manual adjustments for offsets and time zones, never be adding or subtracting minutes to a date-time value for that purpose. Let a decent date-time library do that work.
java.time
The Joda-Time team advises us to migrate to the java.time framework built into Java 8 and later.
The ZoneOffset class represents an offset-from-UTC. Keep in mind that in some areas an offset may involve not only a number of hours but also minutes and even seconds.
The OffsetDateTime class represents a a moment in the timeline with an assigned offset.
int hours = 3; // input by user
ZoneOffset offset = ZoneOffset.ofHours( hours );
OffsetDateTime odt = OffsetDateTime.now( offset );
The standard ISO 8601 formats are used by the toString methods in java.time.
String output = odt.toString();
Generally the best practice is to do your business logic and data storage in UTC. Convert to/from an offset or zoned value only for interaction with user.
In java.time a moment on the timeline in UTC is represented by the Instant class. You can extract an Instant object from the OffsetDateTime.
Instant instant = odt.toString();
Both this Instant and this OffsetDateTime represent the same simultaneous moment on the timeline. They present different wall-clock times.
It may be more clear to skip the use of the OffsetDateTime.now convenience method and start with Instant.
Instant instant = Instant.now(); // Always in UTC, by definition.
ZoneOffset offset = ZoneOffset.ofHours( hours );
OffsetDateTime odt = OffsetDateTime.ofInstant( instant , offset ); // Same moment but presenting alternate wall-clock time.
Handling input
If the user is inputting date-time values as strings, we need to parse. In java.time that means the DateTimeFormatter class. The formatting codes are similar to the outmoded java.text.SimpleDateFormat but not exactly identical, so be sure to study the doc.
DateTimeFormatter formatter = DateTimeFormatter.ofPattern( "dd MMM uuuu HH:mm";
Since the offset-from-UTC is given separately, we parse this input string as a LocalDateTime devoid of time zone information.
LocalDateTime ldt = LocalDateTime.parse( inputString , formatter );
To view, create a String object formatted in ISO 8601 format by calling ldt.toString().
2016-01-02T12:34:45
Apply the pre-determined ZoneOffset object to yield a OffsetDateTime object.
OffsetDateTime odt = ldt.atOffset( offset );
2016-01-12T12:34:45+03:00
Think in UTC
Handling date-time values is a headache. UTC is your aspirin.
When a programmer arrives at the office, she should take off her “UK citizen / London resident” hat, and put on her “UTC” hat. Forget all about your own local time zone. Learn to think in UTC (and 24-hour clock). Add another clock to your desk or computer, set to UTC (or Reykjavík Iceland), or at least bookmark a page like time.is/UTC. Do all your logging, business logic, data serialization, data-exchange, and debugging in UTC.
Make Instant your first-thought, your go-to class. It's value is always in UTC by definition.
Instant instant = Instant.now();
Look at the Instant extracted from the OffsetDateTime value we saw above whose String representation was 2016-01-12T12:34:45+03:00. Being in UTC means 9 AM rather than noon, same moment but three hours difference in wall-clock time. The Z is short for Zulu and means UTC.
String output = odt.toInstant().toString();
2016-01-12T09:34:45Z
Adjust into an offset or time zone only as needed, when expected by a user or data sink. FYI, a time zone is an offset-from-UTC plus a set of rules for handling anomalies such as Daylight Saving Time (DST). Use a time zone in preference to a mere offset wherever possible.
The Europe/London time zone is the same as UTC in the summer, but in winter uses Daylight Saving Time nonsense, and is one hour ahead of UTC. So using the same Instant seen just above, the London wall-clock time is 10 AM rather than 9 AM in UTC, and different from the noon we saw with an offset of +03:00.
ZoneId zoneId = ZoneId.of( "Europe/London" );
ZonedDateTime zdt = ZonedDateTime.ofInstant( instant , zoneId );
2016-01-12T10:34:45+01:00[Europe/London]
Always specify the desired/required offset or time zone; never rely on the implicit current default by omitting this optional argument. (Ditto for Locale by the way.) Note how in all the code of this answer the fact that your JVM has a current default time zone (ZoneId.systemDefault) of Europe/London and the fact that my JVM has a current default time zone of America/Los_Angeles is completely irrelevant. The code runs the same, gets the same results, regardless of whatever machine you use to develop, test, and deploy.
Locale
Specify a Locale object when generating a textual representation of a date-time value that involves a name of month or day, commas or periods and so on. The Locale determines (a) the human language to use when translating such names, and (b) the cultural norms to follow in deciding issues such as punctuation marks.
The Locale has nothing to do with time zones and offset-from-UTC. For example, you could use Locale.CANADA_FRENCH with a date-time zoned for Asia/Kolkata if you had a Québécois user in India.

Categories