How to convert LocalDateTime to com.google.protobuf.Timestamp? - java

I have an instance of LocalDateTime, which I get from the repository layer, and I need to convert it to a Timestamp (Protocol Buffer) instance.
I have used to following approach for the conversion:
LocalDateTime localDateTime = LocalDateTime.now();//this can be any date
Instant instant = localDateTime.toInstant(ZoneOffset.UTC);
Timestamp timestamp = Timestamp.newBuilder()
.setSeconds(instant.getEpochSecond())
.setNanos(instant.getNano())
.build();
Is the ZoneOffset used here, to convert localDateTime to an instance of Instant, correct?
I have used the UTC offset because the comment on the "seconds" attribute in the Timestamp class says the following:
Represents seconds of UTC time since Unix epoch 1970-01-01T00:00:00Z.
Must be from 0001-01-01T00:00:00Z to 9999-12-31T23:59:59Z inclusive
Have I used the correct ZoneOffset and is my conversion approach correct?

In general, no, your approach is not correct. The reason is that a LocalDateTime does not have an associated timezone, so it is ambiguous by nature. To convert it to an actual timestamp (an absolute point in time, independent of timezones), you need to know what timezone it was measured in.
By calling localDateTime.toInstant(ZoneOffset.UTC), you are assuming that your localDateTime was actually measured in the UTC timezone. Instead, you should be using the timezone that the LocalDateTime is stored in. If you don't know, then your input data is inherently ambiguous and you'll need to fix that first.
Note that this has nothing to do with the fact that the Unix epoch is usually specified in UTC. We might as well say that the Unix epoch is 1970-01-01T08:00:00+08:00, and it would be the same instant in time.
The rest of it seems correct to me.

Here's a routine that pulls together the comments from the question and actual answer to this question:
protected Timestamp convertLocalDateTimeToGoogleTimestamp(LocalDateTime localDateTime) {
Instant instant = localDateTime.toInstant(ZoneOffset.UTC);
Timestamp result = Timestamp.newBuilder()
.setSeconds(instant.getEpochSecond())
.setNanos(instant.getNano())
.build();
return result;
}

LocalDateTime localDateTime = LocalDateTime.now();//this can be any date
Instant instant = localDateTime.atZone(ZoneId.systemDefault()).toInstant();
Timestamp timestamp = Timestamp.newBuilder()
.setSeconds(instant.getEpochSecond())
.setNanos(instant.getNano())
.build();

Related

Convert Time with offset to UTC and return localdatetime

I have a Method which parses a String according to Iso8601 and returns a LocalDateTime.
Now I accept possible offsets.
The Problem now is I have to convert the offset to UTC and return it as LocalDateTime.
So far I have tried working with Instant, OffsetDateTime, ZonedDateTime.
I always get the opposite. Instead of +06:00 my result will be shown as -06:00.
return LocalDateTime.ofInstant(Instant.from(OffsetDateTime.parse(date, buildIso8601Formatter())), ZoneOffset.UTC);
This is the solution which works the same as my other tried ones I mentioned above.
I know this is not the common way to do it and I did a lot of research because of that but the current architecture only allows me to do it that way.
EDIT example:
With an implementation like this:
OffsetDateTime offsetDateTime = OffsetDateTime.parse(date, buildIso8601Formatter());
Instant instant = offsetDateTime.toInstant();
return LocalDateTime.ofInstant(instant, ZoneOffset.UTC);
Let's say I get "2020-01-12T08:30+06:00" as input for my method. I HAVE to return LocalDateTime.
As a result I want to have "2020-01-12T14:30" instead my best solution was to get it the opposite way: "2020-01-12T02:30".
The behavior of java.time is correct. The string 2020-01-12T08:30+06:00 means that the datetime part of this string is a datetime local to some region, which exists in an area with an offset of +06:00 from UTC.
Your interpretation is different from the abovementioned case. In your case, you interpret 08:30 as a time in sync with UTC, and then concatenate the timezone offset string for the desired region.
So if you really want to do this – think again.
One way to achieve this, is simply by parsing the datetime as an offset datetime and negate the offset.

Strange results when converting XMLGregorianCalendar to LocalDateTime

I'm trying to convert an XMLGregorianCalendarObject to LocalDateTime and I'm getting unusual results. I have already tried the solutions in this post and this post.
I'm making a few assumptions here that I could be wrong about:
1) the xmlDate argument is UTC
2) the return value is PST
private LocalDateTime convertDate(XMLGregorianCalendar xmlDate) {
GregorianCalendar gc = xmlDate.toGregorianCalendar();
ZonedDateTime zdt = gc.toZonedDateTime();
LocalDateTime localDate = zdt.withZoneSameInstant(ZoneId.of("America/Los_Angeles")).toLocalDateTime();
return localDate;
}
The output is exactly the same as the input:
XMLGregorianCalendar xmlDate: "2019-09-03T13:22:38.436-07:00"
LocalDateTime localDate: "2019-09-03T13:22:38"
Also, this does not work (same method, different syntax):
private LocalDateTime convertDate(XMLGregorianCalendar xmlDate) {
ZonedDateTime utcZoned = xmlDate.toGregorianCalendar().toZonedDateTime().withZoneSameInstant(ZoneId.of("America/Los_Angeles"));
LocalDateTime localDate = utcZoned.toLocalDateTime();
return localDate;
}
The result is the same as the first code snippet.
I think my issue is somewhere in the withZoneSameInstant() method. The strange thing is, when I feed a different timezone code into the parameter, conversion does occur. Try it with "Pacific/Auckland".
What am I doing wrong?
Your first assumption is wrong:
1) the xmlDate argument is UTC
The -07:00 at the end of 2019-09-03T13:22:38.436-07:00 is an offset from UTC. The offset agrees with America/Los_Angeles time zone (Pacific Daylight Time). Java recognizes this, so exactly when you convert to America/Los_Angeles, it doesn’t change the time. When you convert to Pacific/Auckland instead, it does.
I believe that your code is correct.

Convert LocalDateTime to Instant requires a ZoneOffset. Why?

I need to convert a LocalDateTime object to a new Instant object.
I've realized LocalDateTime has an toInstant method, but it's requesting me an ZoneOffset.
I don't quite figure out how to use it, or what does ZoneOffset meant for.
You can't convert a LocalDateTime directly to an Instant because a LocalDateTime can represent many instants. It does not represent one particular instant in time.
Here is a LocalDateTime:
23/10/2018 09:30:00
Can you figure out which instant of time exactly does the above refer to just by looking at it? No. Because that time in the UK is a different instant from that time in China.
To figure out what instant that time refers to, you also need to know how many hours is it offset from UTC, and that, is what ZoneOffset basically represents.
For example, for an offset of 8 hours, you would write this:
localDateTime.toInstant(ZoneOffset.ofHours(8))
Or, if you know you always want the zone offset in the current time zone for that local date time, you can replace ZoneOffset.ofHours(8) with:
ZoneId.systemDefault().getRules().getOffset(localDateTime)
You should think about what offset you want to use before converting it to an instant.
you can try this:
LocalDateTime dateTime = LocalDateTime.of(2018, Month.OCTOBER, 10, 31, 56);
Instant instant = dateTime.atZone(ZoneId.of("Europe/Rome")).toInstant();
System.out.println(instant);

How to create Java time instant from pattern?

Consider a code:
TemporalAccessor date = DateTimeFormatter.ofPattern("yyyy-MM-dd").parse("9999-12-31");
Instant.from(date);
The last line throws an exception:
Unable to obtain Instant from TemporalAccessor: {},ISO resolved to 9999-12-31 of type java.time.format.Parsed
How to create Instant from yyyy-MM-dd pattern?
The string "9999-12-31" only contains information about a date. It does not contain any information about the time-of-day or offset. As such, there is insufficient information to create an Instant. (Other date and time libraries are more lenient, but java.time avoids defaulting these values)
Your first choice is to use a LocalDate instead of an `Instant:
LocalDate date = LocalDate.parse("9999-12-31");
Your second choice is to post process the date to convert it to an instant, which requires a time-zone, here chosen to be Paris:
LocalDate date = LocalDate.parse("9999-12-31");
Instant instant = date.atStartOfDay(ZoneId.of("Europe/Paris")).toInstant();
Your third choice is to add the time-zone to the formatter, and default the time-of-day:
static final DateTimeFormatter FMT = new DateTimeFormatterBuilder()
.appendPattern("yyyy-MM-dd")
.parseDefaulting(ChronoField.NANO_OF_DAY, 0)
.toFormatter()
.withZone(ZoneId.of("Europe/Paris"));
Instant instant = FMT.parse("9999-31-12", Instant::from);
(If this doesn't work, ensure you have the latest JDK 8 release as a bug was fixed in this area).
It is worth noting that none of these possibilities use TemporalAccessor directly, because that type is a low-level framework interface, not one for most application developers to use.
The problem isn't the fact that you are using the year 9999. The Instant.MAX field evaluates to the timestamp 1000000000-12-31T23:59:59.999999999Z, so 9999 as a year is fine.
Dealing with TemporalAccessors instead of the more semantically rich types like LocalDateTime or ZonedDateTime is like using a Map to model an object and its properties instead of writing a class -- you have to assure that the value has the fields (like seconds, nanoseconds, etc) that are expected by something receiving it, rather than depending on formally declared operations in a higher level class to prevent dependencies from going unmet.
In your case it is likely that the temporal accessor contained the parsed date fields it was given, but didn't have a "seconds" field that the Instant needed. It is best to use the more semantically rich types like LocalDateTime in most instances.
Since you only have date fields, you should parse it as a date, then add the time fields before converting it to an Instant. Here is one way, using LocalDate to parse the date:
LocalDate localDate = LocalDate.parse("2016-04-17");
LocalDateTime localDateTime = localDate.atStartOfDay();
Instant instant = localDateTime.toInstant(ZoneOffset.UTC);
Either you are only interested in the date itself (31st of December 9999), in which case the appropriate type would be a LocalDate:
LocalDate date = LocalDate.parse("9999-12-31");
Or you do want an Instant, in which case you need to set a time and time zone, for example, 00:00 in Tokyo:
Instant instant = date.atStartOfDay(ZoneId.of("Asia/Tokyo")).toInstant();
public static void main(String[] args) throws ParseException {
System.out.println(new SimpleDateFormat("yyyy-MM-dd").parse("2016-12-31").toInstant());
}
the above code gives the following output:
2016-12-31T00:00:00Z
i have answered this question using features('toInstant' method) of java 8. hope this answers your question...

Easy way to convert Java 8's LocalDateTime to Joda's LocalDateTime

Is there any easy way to convert Java 8's LocalDateTime to Joda's LocalDateTime?
One of the ways is to convert it to String and then create Joda's LocalDateTime from that String.
Convert through epoch millis (essentially a java.util.Date()):
java.time.LocalDateTime java8LocalDateTime = java.time.LocalDateTime.now();
// Separate steps, showing intermediate types
java.time.ZonedDateTime java8ZonedDateTime = java8LocalDateTime.atZone(ZoneId.systemDefault());
java.time.Instant java8Instant = java8ZonedDateTime.toInstant();
long millis = java8Instant.toEpochMilli();
org.joda.time.LocalDateTime jodaLocalDateTime = new org.joda.time.LocalDateTime(millis);
// Chained
org.joda.time.LocalDateTime jodaLocalDateTime =
new org.joda.time.LocalDateTime(
java8LocalDateTime.atZone(ZoneId.systemDefault())
.toInstant()
.toEpochMilli()
);
// One-liner
org.joda.time.LocalDateTime jodaLocalDateTime = new org.joda.time.LocalDateTime(java8LocalDateTime.atZone(ZoneId.systemDefault()).toInstant().toEpochMilli());
Single line, but long, so "easy"? It's all relative.
Both localDate types consist of (year, month, date), so just copy those values:
public static org.joda.time.LocalDate toJoda(java.time.LocalDate input) {
return new org.joda.time.LocalDate(input.getYear(),
input.getMonthValue(),
input.getDayOfMonth());
}
A slightly shorter method:
If you can get away with using a ZoneOffset instead of a ZoneId then the code may be shortened slightly.
java.time.LocalDateTime java8LDT = java.time.LocalDateTime.now();
org.joda.time.LocalDateTime jodaLDT = new org.joda.time.LocalDateTime(
java8LDT.toInstant(ZoneOffset.UTC).toEpochMilli()
);
The call to the atZone() method can be dropped by providing the timezone offset to the toInstant() method.
More detailed explanation
Just to clarify, the step I have removed is with creating the intermediate ZonedDateTime object. This part of the sequence is still with the Java 8 API before anything to do with Joda.
The process of conversion first involves converting the Java 8 LocalDateTime to the number of millis since the epoch. This can be achieved in a couple of different ways. For example in Andreas' answer:
LocalDateTime ldt = LocalDateTime.now();
ZonedDateTime zdt = ldt.atZone(ZoneId.systemDefault());
Instant instant = zdt.toInstant();
long millis = instant.toEpochMilli();
Or my alternative:
LocalDateTime ldt = LocalDateTime.now();
Instant instant = ldt.toInstant(ZoneOffset.UTC);
long millis = instant.toEpochMilli();
The difference is that I am skipping creating a Java 8 ZonedDateTime and instead passing the timezone offset to the toInstant() method.
From this point on the two answers are the same.
org.joda.time.LocalDateTime jodaLocalDateTime = new org.joda.time.LocalDateTime(millis);
Caveats
This works well when converting using a consistent offset where no daylight savings changes apply. For example UTC is always +0:00. If you need to convert to a local timezone where the offset can change then you'll need the slightly longer answer using atZone()
A LocalDateTime with both API's (Java 8 and Joda) is a DateTime without a timezone. However, if you have the number of millis since the epoch then you need an offset to derive a date and time. The Java 8 API requires either an offset or timezone to be explicitly passed in, whereas the Joda API will use the system default if none is passed.
A more precise way to construct the Joda LocalDateTime would be:
org.joda.time.LocalDateTime jodaLocalDateTime = new org.joda.time.LocalDateTime(millis, DateTimeZone.UTC);
This will only use the timezone during construction to get the correct date and time. Once the constructor has completed the timezone will not form part of the object since LocalDateTime has no timezone.

Categories

Resources