Determine TimeZone from a JSON String? - java

I'm querying a JSON API that returns something like this:
{
"created_time": "2017-01-05T16:32:29+0100",
"updated_time": "2017-01-11T09:38:41+0100",
"id": "23842536731240607"
}
I need to store the times in UTC format, but in order to change the timezone, I first need to parse it as a ZonedDateTime.
To convert "+0100" to "+01:00" is easy enough. But how can I parse the (created/updated)_time into a ZonedDateTime so I can convert it to UTC?

There are some options.
First, as you say, inserting a colon in zone offset is not that difficult. After you’ve done that, getting a ZonedDateTime is straightforward:
ZonedDateTime zdt = ZonedDateTime.parse("2017-01-11T09:38:41+01:00");
System.out.println(zdt);
This prints:
2017-01-11T09:38:41+01:00
Alternatively, funnily, while ZonedDateTime.parse(String) needs a colon in the offset, ZoneId.of() does not, so you may split off the offset and do:
ZoneId zi = ZoneId.of("+0100");
LocalDateTime ldt = LocalDateTime.parse("2017-01-11T09:38:41");
ZonedDateTime zdt = ldt.atZone(zi);
The result is the same as before.
If you prefer not to modify your string prior to parsing it, there is also:
DateTimeFormatter dtf = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ssZ");
ZonedDateTime zdt = ZonedDateTime.parse("2017-01-11T09:38:41+0100", dtf);
Also this gives the same result.
Edit: Note: I am using ZonedDateTime since you asked for this in your question. You may consider it more correct to use OffsetDateTime. Most of the code is practically the same. The version that splits off the offset would go like this:
ZoneOffset zo = ZoneOffset.of("+0100");
LocalDateTime ldt = LocalDateTime.parse("2017-01-11T09:38:41");
OffsetDateTime odt = ldt.atOffset(zo);
To convert to UTC, as mentioned at end of Question, apply another ZoneOffset, the constant ZoneOffset.UTC.
OffsetDateTime odtUtc = odt.withOffsetSameInstant( ZoneOffset.UTC );

Well let me break down the problem statement.
First if you are querying an API then it can be assumed that they are following some standard Date-Time format (even if you are creating one). Looking over the given date it looks like they follow - ** ISO 8601 - Date and time format **
So the problem is how to parse ISO 8601 - Date and time format
What are best options available ?
Using Joda Time.
Using Date Time API Java-8
//Joda
String jtDate = "2010-01-01T12:00:00+01:00";
DateTimeFormatter yoda = ISODateTimeFormat.dateTimeNoMillis();
System.out.println(parser2.parseDateTime(jtDate));
//using Java 8 (As you specified - To convert "+0100" to "+01:00" is easy enough.)
String strDate = "2017-01-05T16:32:29+01:00";
DateTimeFormatter timeFormatter = DateTimeFormatter.ISO_DATE_TIME;
TemporalAccessor convertMe = timeFormatter.parse(strDate);
Date date = Date.from(Instant.from(convertMe));
System.out.println(date);
Hope it helps :)

Related

Kotlin date parsing is not working properly

So I have been looking up on how to properly parse an incoming datetime, problem is that this string contains the zone as well which apparently can't be parsed for some reason.
To give an example of the incoming date time string:
2021-10-05T10:00:00.0000000
Now I tried to the following:
var dateTimeString = "2021-10-05T10:00:00.0000000"
var formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss.SSSZ")
var date = LocalDate.parse(dateTimeString, formatter)
I tried replacing Z with nothing and with ZZZZ, but that didn't work I assume it doesn't work because of the plus or minus sign not being present. FYI, I receive the date in this format because of the Microsoft Graph API when retrieving the calendar events.
Any idea as to how this date should be formatted properly?
Edit: This comes from Microsoft Graph. Basically they give like a date as an object:
"start": {
"dateTime": "2021-10-05T10:00:00.0000000",
"timeZone": "UTC"
}
This is the page of the documentation that explains this date object: dateTimeTimeZone resource type.
Update:
I was finally able to solve this date issue, what I did was the following:
var inputDateTime = "2021-10-05T10:00:00.0000000"
var inputTimeZone = "UTC"
var zonedDateTime = ZonedDateTime.parse(
inputDateTime,
DateTimeFormatter.ISO_DATE_TIME.withZone(ZoneId.of(inputTimeZone))
).withZoneSameInstant(ZoneId.systemDefault()).toLocalDateTime()
This way, the date would be converted correctly to the right time zone and to the right Date/Time.
As I can see from docs you've provided https://learn.microsoft.com/en-us/graph/api/resources/datetimetimezone?view=graph-rest-1.0
dateTime String A single point of time in a combined date and time representation ({date}T{time}; for example, 2017-08-29T04:00:00.0000000).
timeZone String Represents a time zone, for example, "Pacific Standard Time". See below for more possible values.
dateTime object has no zone encoded. And all 7 zeroes represent fractions of a second. In such case it's regular ISO_DATE_TIME and you don't need to create your own formatter.
The following code should work
var dateTimeString = "2021-10-05T10:00:00.0000000"
var date = LocalDate.parse(dateTimeString, DateTimeFormatter.ISO_DATE_TIME)
Your Date-Time string does not have timezone information
Your Date-Time string, 2021-10-05T10:00:00.0000000 does not have timezone information. The .0000000 represents the fraction of a second and as of now, java.time is capable of handling up to 9 digits of precision (i.e. nanosecond of precision).
Since it does not have timezone information, it represents a local Date-Time and hence should be parsed into LocalDateTime.
You do not need a DateTimeFormatter for your Date-Time string
The modern Date-Time API is based on ISO 8601 and does not require using a DateTimeFormatter object explicitly as long as the Date-Time string conforms to the ISO 8601 standards. Your Date-Time string is already in the ISO 8601 format.
Demo:
import java.time.LocalDateTime;
public class Main {
public static void main(String args[]) {
var dateTimeString = "2021-10-05T10:00:00.0000000";
var ldt = LocalDateTime.parse(dateTimeString);
System.out.println(ldt);
}
}
Output:
2021-10-05T10:00
ONLINE DEMO
How to get ZonedDateTime out of the LocalDateTime instance?
You can use LocalDateTime#atZone to convert attach a ZoneId to a LocalDateTime resulting into a ZonedDateTime.
ZonedDateTime zdt = ldt.atZone(ZoneId.of("Etc/UTC"));
Note: If you need an Instant, you can get it from this instant of ZonedDateTime e.g.
Instant instant = zdt.toInstant();
An Instant represents an instantaneous point on the timeline, normally represented in UTC time. The Z in the output is the timezone designator for a zero-timezone offset. It stands for Zulu and specifies the Etc/UTC timezone (which has the timezone offset of +00:00 hours).
Learn more about the modern Date-Time API* from Trail: Date Time.
* If you are working for an Android project and your Android API level is still not compliant with Java-8, check Java 8+ APIs available through desugaring. Note that Android 8.0 Oreo already provides support for java.time.
For the case that the last 4 digits in your example String are not representing a time zone:
Parse it without a formatter (no need since it would be perfect ISO standard if the last 4 digits are just additional fractions of second), but also consider the time zone you get with the JSON:
import java.time.LocalDateTime
import java.time.ZoneId
import java.time.ZonedDateTime
import java.time.format.DateTimeFormatter
fun main() {
// the time String from JSON
var dateTimeString = "2021-10-05T10:00:00.0000000"
// the zone from JSON (may not always work, but does with UTC)
var timeZone = "UTC"
// create the zone from the timezone String
var zoneId = ZoneId.of(timeZone)
// then parse the time String using plain LocalDateTime and add the zone afterwards
var zdt: ZonedDateTime = LocalDateTime.parse(dateTimeString).atZone(zoneId)
// print some results
println("Full ZonedDateTime: ${zdt.format(DateTimeFormatter.ISO_ZONED_DATE_TIME)}")
println("(Local)Date only: ${zdt.toLocalDate()}")
}
Full ZonedDateTime: 2021-10-05T10:00:00Z[UTC]
(Local)Date only: 2021-10-05
Please note that parsing the time zones currently supported by Windows won't work this easy (except from UTC), but the time zones supported by the calendar API are (mostly) sufficient for the creation of a java.time.ZoneId.
As a supplement: Your documentation mentions Pacific Standard Time as a time zone string that may come as part of your MS Graph dateTimeTimeZone. I don’t think the other answers can handle that one, so I should like to show how you do handle it in Java.
private static final DateTimeFormatter ZONE_FORMATTER
= DateTimeFormatter.ofPattern("zzzz", Locale.ENGLISH);
static ZoneId parseZone(String timeZoneString) {
try {
return ZoneId.from(ZONE_FORMATTER.parse(timeZoneString));
} catch (DateTimeParseException dtpe) {
return ZoneId.of(timeZoneString);
}
}
I believe that this handles the strings mentioned in the documentation and also UTC from your question. I am demonstrating just three different ones:
String[] msTimeZoneStrings = { "UTC", "Pacific Standard Time", "Pacific/Honolulu" };
for (String zoneString : msTimeZoneStrings) {
ZoneId zid = parseZone(zoneString);
System.out.format("%-21s parsed to %s%n", zoneString, zid);
}
Output:
UTC parsed to UTC
Pacific Standard Time parsed to America/Los_Angeles
Pacific/Honolulu parsed to Pacific/Honolulu
In the formatter I use zzzz is for time zone name like Pacific Standard Time. My parse method tries this formatter first. It fails for UTC and Pacific/Honolulu. When the DateTimeParseException is caught, the method next tries the ZoneId.of method also used in the other answers. It handles UTC and all the time zone ID sin region/city format mentioned in the documentation.
Combine the ZoneId obtained from my method with the parsed LocalDateTime to get a ZonedDateTime the way that deHaar is already showing in their answer.

How to parse JSR-310 date to Instant?

I'm trying to parse lets say "2020-01-12+01:00" with JSR-310 time.
I read it via DateTimeFormatter.ofPattern("yyyy-MM-ddVV"), however now if I want to transform that into a Instant via Instant.from(DateTimeFormatter.ofPattern("yyyy-MM-ddVV").parse("..."), it throws where it complains that time is null.
Which granted it is, but, I'd like to get Instant from that, i.e. epochMillis, so I can serialize the long into a database.
Is there a way around it? Basically I'd like to extend the "2020-01-12+01:00" to "2020-01-12T00:00.000+01:00" and parse that to Instant as usual
You need to use DateTimeFormatterBuilder, specifying ISO_DATE format and a default time-of-day (midnight1):
DateTimeFormatter formatter = new DateTimeFormatterBuilder()
.append(DateTimeFormatter.ISO_DATE)
.parseDefaulting(ChronoField.HOUR_OF_DAY, 0)
.toFormatter();
Instant instant = Instant.from(formatter.parse("2020-01-12+01:00"));
System.out.println(instant);
1) The ChronoField can be any time-of-day field, i.e. HOUR_OF_DAY, CLOCK_HOUR_OF_DAY, MINUTE_OF_DAY, SECOND_OF_DAY, MILLI_OF_DAY, MICRO_OF_DAY, or NANO_OF_DAY.
Output
2020-01-11T23:00:00Z
If you want to retain the time zone offset, you need to use OffsetDateTime (or ZonedDateTime) instead of Instant:
OffsetDateTime dateTime = OffsetDateTime.parse("2020-01-12+01:00", formatter);
System.out.println(dateTime);
System.out.println(dateTime.format(DateTimeFormatter.ISO_DATE));
Output (from both OffsetDateTime and ZonedDateTime)
2020-01-12T00:00+01:00
2020-01-12+01:00
You can use LocalDate.parse(dateString, formatter) using the Formatter you've made above to give you a LocalDate instance.
LocalDate can then give you a LocalDateTime at any time in that day, but (for example) you can get the start of day from it.
LocalDateTime has a toInstant method to give you an Instant.
Instant has a toEpochMilli method to get your long.
It’s easy enough when you know how. The formatter we need is built in. There’s a complication in the fact that there isn’t a type to parse the string into, no OffsetDate. I present two options for tackling this.
String s = "2020-01-12+01:00";
TemporalAccessor parsed = DateTimeFormatter.ISO_OFFSET_DATE.parse(s);
LocalDate date = LocalDate.from(parsed);
ZoneOffset offset = ZoneOffset.from(parsed);
Instant result = date.atStartOfDay(offset).toInstant();
System.out.println(result);
Output from this snippet is:
2020-01-11T23:00:00Z
We seldom need to use the TemporalAccessor interface directly, and it’s considered low-level. It also isn’t the only way to go here. The other good option is to define a default time of day so we can parse directly into an Instant:
DateTimeFormatter formatter = new DateTimeFormatterBuilder()
.append(DateTimeFormatter.ISO_OFFSET_DATE)
.parseDefaulting(ChronoField.HOUR_OF_DAY, 0)
.toFormatter();
Instant result = formatter.parse(s, Instant::from);
The result is the same as before.

Changing LocalDateTimeFormat to include seconds and milliseconds

As per the requirement, my code is supposed to append date from a ZonedDateTime parameter, and Time from OffSetTime parameter into this format, "yyyy-MM-dd HH:mm:ss.SSSz". However, i was not been able to achieve this
I have tried various ways , including the one below, using DateTimeFormatter.
ZonedDateTime zonedDateTime = ZonedDateTime.parse("2019-05-23T09:00:00-05:00");
OffsetTime offsetTime = OffsetTime.parse("08:59:00-05:00");
LocalDateTime localDateTime = LocalDateTime.of(zonedDateTime.toLocalDate(), offsetTime.toLocalTime());
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.SSSz");
String finalDate = localDateTime.format(formatter);
I notice that :
- code is throwing "java.time.DateTimeException: Unable to extract value: class java.time.LocalDateTime" at localDateTime.format(formatter)
The expectation is to get the DateTime in String like so - "2019-05-23T08:59:00.000Z"
Any help appreciated, Thank you for your time.
Four issues:
LocalDateTime doesn't have any time zone information, so don't use it.
Even if it did, your inputs only have a time zone offset, not a full time zone, so you format string cannot use z, as that requires a time zone name. Use XXX instead.
Since the inputs are offset -05:00 you shouldn't expect output with Z (Zulu), as that means +00:00, unless you also expect the time to be adjusted by 5 hours.
The format pattern for year should use uuuu, not yyyy. See uuuu versus yyyy in DateTimeFormatter formatting pattern codes in Java?
Assuming you want to keep the time zone, change code to:
ZonedDateTime zonedDateTime = ZonedDateTime.parse("2019-05-23T09:00:00-05:00");
OffsetTime offsetTime = OffsetTime.parse("08:59:00-05:00");
OffsetDateTime offsetDateTime = zonedDateTime.toLocalDate().atTime(offsetTime);
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("uuuu-MM-dd HH:mm:ss.SSSXXX");
System.out.println(offsetDateTime.format(formatter));
2019-05-23 08:59:00.000-05:00
If you instead want Zulu time, i.e. UTC, with the time adjusted, add the line to make the adjustment:
OffsetDateTime offsetDateTime = zonedDateTime.toLocalDate().atTime(offsetTime)
.withOffsetSameInstant(ZoneOffset.UTC);
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("uuuu-MM-dd HH:mm:ss.SSSXXX");
System.out.println(offsetDateTime.format(formatter));
2019-05-23 13:59:00.000Z

how to get timezone offset for current date in java

correct date format i am looking for:- "2017-07-06T18:03:39.195+0530"
how to get the +0530 for the current date in java ?
when i am using SimpleDateFormat it is giving +0000 , while it should give +0530 .
=================================================
i have tried using below :-
SimpleDateFormat format = new SimpleDateFormat("enter code hereyyy-MM-dd HH:mm:ss.SSS Z");
String dateString = format.format(new Date());
System.out.println("value of dateString is :"+dateString);
the above code will output :-
value of dateString is :2017-07-10 06:51:27.250 +0000
while it should output : +0530
can you please tell me how can i get the +0530 offset for the above date or current date ?
tl;dr
ZonedDateTime.now( ZoneId.of( "Asia/Kolkata" ) ).toOffsetDateTime().toString()
OffsetDateTime
To directly address the Question, use the modern OffsetDateTime class.
OffsetDateTime.now().toString()
See this code run live at IdeOne.com.
odt.toString(): 2019-08-22T17:44:00.219684Z
That will generate a string representing the current moment in the offset used by the JVM’s current default time zone using standard ISO 8601 format. One difference: This method will include the optional COLON : character between hours and minutes of the offset. I suggest never omitting that character as I have seen multiple libraries break on such input, expecting the COLON to be there.
When capturing the current moment, the result may resolve to microseconds. If you prefer milliseconds, truncate, lopping off the micros.
OffsetDateTime.now().truncatedTo( ChronoUnit.MILLIS )
odtTruncated.toString(): 2019-08-22T17:44:00.219Z
That code running at IdeOne.com is in a JVM where the offset is set to zero, for UTC itself. So we see a Z at the end, a common and standard alternative to +00:00, pronounced “Zulu”.
You may want to specify your offset explicitly.
Instant instant = Instant.now() ; // Current moment in UTC.
ZoneOffset offset = ZoneId.of( "Asia/Kolkata" ).getRules().getOffset( instant ) ; // We must pass a moment. India is currently at five and a half hours ahead of UTC. But it has not always been so in the past, and may not always be so in the future.
OffsetDateTime odt = instant.atOffset( offset ) ;
odt.toString(): 2019-08-22T23:00:06.925139+05:30
I think a cleaner syntax is to use ZonedDateTime.
String output = ZonedDateTime.now( ZoneId.of( "Asia/Kolkata" ) ).toOffsetDateTime().toString() ;
2019-08-22T23:03:19.072681+05:30
ZonedDateTime
More likely you should be getting the current moment in a time zone rather than a mere offset. If so, see the Answer by RaT.
Be clear on the difference between offset and zone:
An offset-from-UTC is merely a number of hours-minutes-seconds ahead of UTC or behind UTC.
A time zone is much more. A time zone is a history of past, present, and future changes to the offset used by the people of a particular region. A zone has a name in Continent/Region format, such as America/Montreal or Africa/Tunis.
Converting
Normally, you should avoid using the terrible date-time classes such as java.util.Date and SimpleDateFormat that are now legacy. Always use Instant, OffsetDateTime, and ZonedDateTime. Never use Date & Calendar.
But if you must you must use the legacy classes when interoperating with old code not yet updated for the java.time classes, you can convert back-and-forth. This is shown in the Answer by RaT.
You can Use Java 8 DateTime API for another solution
ZoneId zone = ZoneId.systemDefault(); //It will give current System's timezone (IST for you)
Date date = new Date(); // Any date
DateTimeFormatter formatter= DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.SSS Z", Locale.ENGLISH);
ZonedDateTime zonedTime = ZonedDateTime.ofInstant(date.toInstant(), zone);
System.out.println(zonedTime.format(formatter));
Above code will give you Date with correct Zone Offset
After a lot of searching I found that the timezone problem can be solved with Joda time API. You can use below code to achieve what I want in my question:
DateTime dateTime = DateTime.now();
System.out.println(dateTime.toString("yyyy-MM-dd'T'HH:mm:ss.SSSZ"));
It outputs the current date with proper timezone:
2017-07-10T15:01:48.319+0530
Set the timezone you need. Your code would looke like:
SimpleDateFormat format = new SimpleDateFormat("yyy-MM-dd HH:mm:ss.SSS Z");
format.setTimeZone(TimeZone.getTimeZone("IST"));
String dateString = format.format(new Date());
System.out.println("value of dateString is :"+dateString);

Java unparseable date

There is the following code:
Date st = new SimpleDateFormat("HH:mm").parse(date);
And I get the following exception "Unparseable date: "2000-01-01T01:00:00Z" (at offset 4)". How can I fix it?
tl;dr
Instant.parse( "2000-01-01T01:00:00Z" )
Wrong Parsing Pattern
You defined a formatting pattern that says you expect the input string to be hours and minutes. But your input string has much more, years, months, and so on.
java.time
As of Java 8 and later, the old java.util.Date/.Calendar and java.text.SimpleDateFormat have been supplanted by the new java.time package. Avoid the old classes whenever possible as they have proven to be confusing, troublesome, and flawed.
Your input string is using one of the standard date-time formats defined by ISO 8601. Fortunately, java.time uses that format by default.
String input = "2000-01-01T01:00:00Z" ;
Instant instant = Instant.parse( input ) ;
An Instant is a moment on the timeline basically in UTC. You may adjust the value to a particular time zone (a ZoneId), producing a ZonedDateTime object.
ZonedId zoneId = ZoneId.of( "America/Montreal" ) ;
ZonedDateTime zdt = ZonedDateTime.of( instant , zoneId ) ;
If you call toString on a ZonedDateTime, you will get a string like:
1999-12-31T20:00:00-05:00[America/Montreal]
The ZonedDateTime class extends the ISO 8601 format by appending the name of the time zone in brackets.
Date st = new SimpleDateFormat("HH:mm").parse(date.substring(11,16));
Use this format instead:
Date st = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'").parse(date);
SimpleDateFormat is very unforgiving. It expects you to pass a String that exactly matches the format string you initialized it with. If you pass "HH:mm" as its format string, it will not be able to handle any other format - it cannot handle a date with a year in it, etc. It will handle "3:56" (in which case you'll get Jan 1, 1970 at 03:56AM in your Date).
SimpleDateFormat dtf = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'");
Date date = dtf.parse(date);
dtf = new SimpleDateFormat("HH:mm");
String dateStr = dtf.format(date);
Date finalDate = dtf.parse(date);
new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssX")
The date in the exception is an xml schema dateTime. Note that simply creating
SimpleDateFormat dtf = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'");
Will not work entirely as you might think because the Z indicates "UTC" timezone and the parser is by default initialized as local time.
Also note that the schema dateTime has a variable definition, it can (optionally) have millisecond precision (0 or more milliseconds) and the timezone (if something other than Z) is not compatible with the format of SimpleDateFormat.
In short: xml date times are tricky with the default libraries. I have written a custom solution for handling them in our environment but you could also look at the joda time library which I believe handles them well. Or you could wait for the next java version which will have a new date API.

Categories

Resources