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.
Related
I'm receiving a query parameter date, as yyyy-MM-DD (2022-03-08).
I want to conver this to java.util.Calendar / java.util.GregorianCalendar formmat.
So my idea is converto:
String -> ZonedDateTime -> Calendar.
What I did:
ZonedDateTime parsedDate = ZonedDateTime.parse(date, DateTimeFormatter.ofPattern("yyyy-MM-dd"));
//date = 2022-03-08
even with the correct format, I'm getting:
Text '2022-03-08' could not be parsed: Unable to obtain ZonedDateTime from TemporalAccessor: {},ISO resolved to 2022-03-08 of type java.time.format.Parsed
I found out that this error occurs because my string does not have a TimeZone.
One suggestion was to use LocalDate
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("dd/MM/yyyy");
LocalDate date = LocalDate.parse(fecha, formatter);
but I can't use localDate as an argument for ZonedDateTime.parse().
What else could I try?
I want to conver this to java.util.Calendar / java.util.GregorianCalendar formmat.
That seems silly; Calendar/GregorianCalendar is obsolete, and the API is horrendous. Why use a broken screwdriver when there's a shiny new one right there in the toolbox? Don't do this.
So my idea is converto: String -> ZonedDateTime -> Calendar.
That seems silly. The string does not contain a ZonedDateTime. It doesn't even contain a LocalDateTime. It is clearly a LocalDate. So, convert it to a localdate, and you go from there.
The power of the java.time package is that each different concept in time has a matching type in the j.t package that is properly named. For example, java.util.Date is a lie: It is a timestamp, and it has nothing whatsoever to do with dates; asking a Date object for 'what year is it', is broken (try it, you get a warning).
Calendar, similarly, is an utter falsehood. It does not represent a calendar at all; it, too, represents a timestamp.
LocalDate on the other hand is perfect truth: It represents a date (not a time), and it does not include timezone or other localizing information: It makes sense only as 'locally'.
Each stop should just make sense, on its own:
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("uuuu-MM-dd");
LocalDate date = LocalDate.parse("2022-10-01", formatter);
So far, so good. I'd just stop there - why lie? Why return a Calendar which is both API wise a lie (that class does not represent calendars), and even if someone knows exactly what Calendar is, it's still a lie: A calendar implies it has exact time and a timezone. You do not have a time, and also don't have a timezone. Why return something that suggests stuff that isn't there?
But, if you MUST, then explicitly add a timezone and a time, and THEN go for it:
ZonedDateTime zdt = someLocalDate.atStartOfDay().atZone(ZoneId.of("Europe/Amsterdam"));
GregorianCalendar gc = GregorianCalendar.from(zdt);
This code is clear and legible: It makes crystal clear that the code picks a time, and picks a zone.
But, again, now you ended up with a horrible, horrible object you should not be using, for anything.
There are other ways of getting a ZonedDateTime than just its static parse() method. Here's how to turn a LocalDateTime into a ZonedDateTime:
ZonedDateTime zoned = dateTime.atZone(ZoneId.of( "America/New_York"));
or if you have a LocalDate:
ZonedDateTime zoned =
date.atStartOfDay( ZoneId.of( "America/New_York" ));
I would not use java.util.Calendar or Date. They're junk. I'd either stick with LocalDate or use ZonedDateTime depending on if you need to keep track of time zones or not. This should get you where you want to go either way, I guess, as it sounds like you know what you want to do once you have a ZonedDateTime.
UPDATE: I looked up how to convert a ZoneDateTime to a Calendar:
Calendar calendar = GregorianCalendar.from(zoned);
just in case you hadn't gotten that far and really want to go that way.
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();
I have a datetime string "2018-01-15 01:16:00" which is in EST timezone. I want to convert this into another timezone dynamically using the UTC offset. My javascript code passes this UTC offset as a parameter and the servlet has to convert/format this datetime string to the timezone identified by the provided offset.
I have tried many approaches including the one documented in the oracle tutorials but unable to arrive at a solution.
Below is my code that I am trying, any help is greatly appreciated.
private static final String DATE_FORMAT = "yyyy-MM-dd HH:mm:ss";
private static final String DEFAULT_TIME_ZONE = ZoneId.SHORT_IDS.get("EST");
public static void main(String[] args) throws Exception {
String dateTime = "2018-01-15 02:35:00";
//parse the datetime using LocalDateTime
LocalDateTime defaultDateTime = LocalDateTime.parse(dateTime, DateTimeFormatter.ofPattern(DATE_FORMAT));
//get the datetime in default timezone
ZoneId defaultZoneId = ZoneId.of(DEFAULT_TIME_ZONE);
ZonedDateTime defaultZoneDateTime = defaultDateTime.atZone(defaultZoneId);
System.out.println("EST time: "+defaultZoneDateTime.format(DateTimeFormatter.ofPattern(DATE_FORMAT)));
ZonedDateTime utcZonedDateTime = defaultZoneDateTime.withZoneSameInstant(ZoneId.of("UTC"));
String utcTime = defaultZoneDateTime.withZoneSameInstant(ZoneId.of("UTC")).format(DateTimeFormatter.ofPattern(DATE_FORMAT));
System.out.println("UTC : "+utcTime);
//IST timezone
ZoneOffset offset = ZoneOffset.of("+05:30");
OffsetDateTime offsetDate = OffsetDateTime.of(utcZonedDateTime.toLocalDateTime(), offset);
String targetTimeZone = offsetDate.format(DateTimeFormatter.ofPattern(DATE_FORMAT));
System.out.printf("target time : "+targetTimeZone);
}
OUTPUT
EST time: 2018-01-15 02:35:00
UTC : 2018-01-15 07:37:00
target time : 2018-01-15 07:37:00
Expected target time : 2018-01-15 13:05:00
The immediate problem is this line:
OffsetDateTime offsetDate = OffsetDateTime.of(utcZonedDateTime.toLocalDateTime(), offset);
That's saying you want the same local date/time, but with the specified offset. That changes which instant in time is being represented.
Instead, you really want to represent the same instant in time, but at a particular offset. So the shortest fix is:
OffsetDateTime offsetDate = utcZonedDateTime.toInstant().atOffset(offset);
However, there are a number of other aspects which could do with changing:
Prefer ZoneOffset.UTC to ZoneId.of("UTC")
Using EST as a time zone is confusing - it's not clear whether you expect it to mean "Eastern Time" (changing between EST and EDT) or pure standard time of UTC-5. Assuming you actually mean "Eastern Time" it would be better to use America/New_York as a zone ID.
It's unclear what you want to happen if the input string represents a skipped or ambiguous value in Eastern time. These happen around DST transitions.
Next, you don't need to convert the ZonedDateTime in Eastern time into a ZonedDateTime in UTC at all. Either convert it directly to an instant:
OffsetDateTime target = defaultZoneDateTime.toInstant().at(offset);
Or create a ZonedDateTime for the target instead:
ZonedDateTime target = defaultZoneDateTime.withZoneSameInstant(offset);
Given that an offset isn't really a time zone, I'd probably go with the first of these.
You're using
OffsetDateTime.of(utcZonedDateTime.toLocalDateTime(), offset)
to create your target. You're thus constructing an OffsetDateTime in the target offset, having a LocalDateTime equal to the LocalDateTime in the UTC zone.
What you want is the exact same transformation as the one you're using to go from the EST time to UTC: keep the same instant, but go to a different timezone:
defaultZoneDateTime.withZoneSameInstant(offset);
or, if you really want an OffsetDateTime and not a ZonedDateTime:
OffsetDateTime.ofInstant(defaultZoneDateTime.toInstant(), offset);
I'm using the below function to convert a microsecond format time string to ZoneDateTime so I can do comparisons later.
public static ZonedDateTime createTimeFromString(String inputTime) {
ZonedDateTime time;
try {
System.out.printf("Input time %s%n", inputTime);
DateTimeFormatter formatter =
DateTimeFormatter.ofPattern("yyyyMMdd-HH:mm:ss.SSSSSS");
LocalDate date = LocalDate.parse(inputTime, formatter);
time = date.atStartOfDay(ZoneId.systemDefault());
System.out.printf("Formated time %s%n", time);
return time;
}
catch (DateTimeParseException exc) {
System.out.printf("%s is not parsable!%n", inputTime);
throw exc; // Rethrow the exception.
}
}
However, whatever time string I pass into the function I get the same output.
eg:
Input time 20171025-10:58:24.062151
Formated time 2017-10-25T00:00+05:30[Asia/Colombo]
Input time 20171025-10:58:25.446862
Formated time 2017-10-25T00:00+05:30[Asia/Colombo]
I'm using Java 8.
Can you please clarify what I'm doing wrong?
When you call LocalDate.parse, you're getting just the date part (day, month and year) and discarding the rest. A LocalDate doesn't have the time fields (hour, minute, seconds and fraction of second), so they are simply discarded and lost.
Then you call atStartOfDay(ZoneId.systemDefault()), which sets the time to midnight at the JVM default timezone.
If you want to keep everything (date and time), parse it to a LocalDateTime, which is a class that contains all the date and time fields. Then you call the atZone method to convert it to a ZonedDateTime:
String inputTime = "20171025-10:58:24.062151";
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyyMMdd-HH:mm:ss.SSSSSS");
// parse to a LocalDateTime (keeping all date and time fields)
LocalDateTime date = LocalDateTime.parse(inputTime, formatter);
// convert to ZonedDateTime
ZonedDateTime z = date.atZone(ZoneId.systemDefault());
PS: ZoneId.systemDefault() returns the JVM default timezone, but keep in mind that this can be changed without notice, even at runtime, so it's better to always make it explicit which one you're using.
The API uses IANA timezones names (always in the format Region/City, like Asia/Colombo or Europe/Berlin).
Avoid using the 3-letter abbreviations (like IST or CET) because they are ambiguous and not standard.
You can get a list of available timezones (and choose the one that fits best your system) by calling ZoneId.getAvailableZoneIds(). Then you call the ZoneId.of() method with the zone name, like this:
// using specific timezone instead of JVM's default
ZonedDateTime z = date.atZone(ZoneId.of("Asia/Colombo"));
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.