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.
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.
String is in this format - "2021-07-13 05:22:18.712"
I tried using this code to parse it.
OffsetDateTime parsedDateTime = OffsetDateTime.parse("2021-07-13 05:22:18.712");
But i keep getting this error -
org.threeten.bp.format.DateTimeParseException: Text '2021-07-13 05:22:18.712' could not be parsed at index 10.
How do i make it work? Any suggestions will be helpful. Thanks
You need to decide on a time zone (or at least on an offset, but time zone is usually the correct means). And then you need to use a formatter that defines the format that you are trying to parse:
private static final DateTimeFormatter FORMATTER = new DateTimeFormatterBuilder()
.append(DateTimeFormatter.ISO_LOCAL_DATE)
.appendLiteral(' ')
.append(DateTimeFormatter.ISO_LOCAL_TIME)
.toFormatter(Locale.ROOT);
With this formatter it’s a pretty simple operation:
ZoneId zone = ZoneId.of("Asia/Kolkata");
String input = "2021-07-13 05:22:18.712";
OffsetDateTime dateTime = LocalDateTime.parse(input, FORMATTER)
.atZone(zone)
.toOffsetDateTime();
System.out.println(dateTime);
Output from the example snippet is:
2021-07-13T05:22:18.712+05:30
Since as #Sweeper noted in a comment your string does not contain UTC offset nor time zone, parse it into a LocalDateTime first. The Local in some java.time class names means without time zone or UTC offset. Then convert into a ZonedDateTime in the intended time zone and further into the desired type, OffsetDateTime.
If you want to use the default time zone of the JVM, set zone to ZoneId.systemDefault(). Be aware that the default time zone may be changed at any time from another part of your program or another program running in the same JVM, so this is fragile.
Possible shortcuts
My formatter is wordy because I wanted to reuse as much as I could from built-in formatters. If you don’t mind building the formatter by hand from a pattern, you may use:
private static final DateTimeFormatter FORMATTER
= DateTimeFormatter.ofPattern("uuuu-MM-dd HH:mm:ss.SSS", Locale.ROOT);
Or even shorter, you may hand substitute the space in your string with a T to obtain ISO 8601 format and then parse into a LocalDateTime without specifying any formatter at all.
What went wrong in your code?
The exception message you got is trying to be helpful:
But i keep getting this error -
org.threeten.bp.format.DateTimeParseException: Text '2021-07-13
05:22:18.712' could not be parsed at index 10.
Index 10 in your string is where the space between date and time is. The one-arg OffsetDateTime.parse method expects ISO 8601 format, like 2021-07-13T05:22:18.712+05:30, so with a T to denote the start of the time part and with a UTC offset at the end. The absence of the T caused your exception. Had you fixed that, you would have got another exception because of the missing UTC offset.
Link
Wikipedia article: ISO 8601
You first need to check the document.
It indicates parse need to go with a date format such as 2007-12-03T10:15:30 +01:00.
You date is missing a "T", "2021-07-13 05:22:18.712". Hence it did not go well, counting it from index 0, it's character at 10.
If you need to parse 2021-07-13T05:22:18.712, you will still get error. An error Text '2021-07-13T05:22:18.712' could not be parsed at index 23. Which is the problem of miliseconds.
So a big round to go:
//Format to date
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS");
//Format to new string
SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssX");
java.util.Date date1=simpleDateFormat.parse("2021-07-13 05:22:18.712");
String newDate = formatter.format(date1);
//finally.
OffsetDateTime parsedDateTime = OffsetDateTime.parse(newDate);
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 :)
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...
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.