Java 8 - Trying to parse timestamp with textual TimeZone ID - java

Ugh... I can't figure this out for the life of me. I'm using Java 8 and trying do to something as simple as parsing a timestamp where the TimeZone ID is a textual value such as HST:
WORKS
ZonedDateTime timestamp = ZonedDateTime.parse("2018-10-29T12:00:12.456-10:00");
DOES NOT WORK
ZonedDateTime timestamp = ZonedDateTime.parse("2018-10-29T12:00:12.456HST");
And get this error:
java.time.format.DateTimeParseException: Text '2018-10-29T12:00:12.456HST' could not be parsed at index 23
Does anyone know how to parse a timestampe where the timezone ID comes in as a textual value?

There are two problems:
1) ZonedDateTime.parse method parse only strings that obey the ISO_ZONED_DATE_TIME format, description of how it looks you can find here:
https://docs.oracle.com/javase/8/docs/api/java/time/format/DateTimeFormatter.html#ISO_ZONED_DATE_TIME
In order to parse your format you have to create your own dateTimeFormatter.
This formatter can look like this
DateTimeFormatter formatter = new DateTimeFormatterBuilder()
.parseCaseInsensitive()
.append(DateTimeFormatter.ISO_LOCAL_DATE)
.appendLiteral('T')
.append(DateTimeFormatter.ISO_LOCAL_TIME)
.appendZoneId()
.toFormatter();
This formatter would work if you would use standart zones like GMT, UTC etc..
Problem is that HST is not standard format for Zone and is not supported. You can see supported time zones via:
System.out.println(ZoneId.getAvailableZoneIds());
If you still want to use HST zone you have to add ZoneRulesProvider for your custom zone like this:
ZoneRulesProvider.registerProvider(new ZoneRulesProvider() {
#Override
protected Set<String> provideZoneIds() {
return Collections.singleton("HST");
}
#Override
protected ZoneRules provideRules(String zoneId, boolean forCaching) {
return ZoneRules.of(ZoneOffset.ofHours(10));
}
#Override
protected NavigableMap<String, ZoneRules> provideVersions(String zoneId) {
TreeMap map = new TreeMap<>();
map.put("HST",ZoneRules.of(ZoneOffset.ofHours(10)));
return map;
}
});
DateTimeFormatter formatter = new DateTimeFormatterBuilder()
.parseCaseInsensitive()
.append(DateTimeFormatter.ISO_LOCAL_DATE)
.appendLiteral('T')
.append(DateTimeFormatter.ISO_LOCAL_TIME)
.appendZoneId()
.toFormatter();
ZonedDateTime timestamp = ZonedDateTime.parse("2018-10-29T12:00:12.456HST", formatter);
System.out.println(timestamp);
THis should work.

Related

error java.time.format.DateTimeParseException: Text '2020-10-16T18:04:59+0300' could not be parsed at index 24

I´m trying to pase the next String using LocalDateTime, but I always get de unparsed text found error:
java.time.format.DateTimeParseException: Text '2020-10-16T18:04:59+0300' could not be parsed at index 24
Need: from 2020-10-16T18:04:59+0300 to 2020-10-16 18:04.
My code:
public String getFormattingData(String sourceData) {
DateTimeFormatter sourceFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ssZ''e", Locale.ENGLISH);
DateTimeFormatter newFormatter = DateTimeFormatter.ofPattern("dd-MM-yyy HH:mm", Locale.ENGLISH);
LocalDate date = LocalDate.parse("2020-10-16T18:04:59+0300", sourceFormatter);
return newFormatter.format(date);
}
What am I doing wrong?
See the related question: Format a date using the new date time API
The source format you are looking for is: "yyyy-MM-dd'T'HH:mm:ssZ" (as mentioned by #Sweeper in his comment)
If you want the HH:mm in the output format, you need to use a LocalDateTime rather than a LocalDate
The code below works for me:
public String getFormattingData(String sourceData) {
DateTimeFormatter sourceFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ssZ", Locale.ENGLISH);
DateTimeFormatter newFormatter = DateTimeFormatter.ofPattern("dd-MM-yyyy HH:mm", Locale.ENGLISH);
LocalDateTime date = LocalDateTime.parse("2020-10-16T18:04:59+0300", sourceFormatter);
return newFormatter.format(date);
result:
16-10-2020 18:04
You just need to correct the date pattern of source date like this:
public static String formatDate(String strDate, String srcPattern, String tgtPattern) {
DateTimeFormatter srcFormatter = DateTimeFormatter.ofPattern(srcPattern, Locale.ENGLISH);
DateTimeFormatter tgtFormatter = DateTimeFormatter.ofPattern(tgtPattern, Locale.ENGLISH);
return tgtFormatter.format(LocalDateTime.parse(strDate, srcFormatter));
}
You can also use SimpleDateFormat:
public static String formatDate(String strDate, String srcPattern,
String tgtPattern) throws ParseException {
SimpleDateFormat srcFormatter = new SimpleDateFormat(srcPattern, Locale.ENGLISH);
SimpleDateFormat tgtFormatter = new SimpleDateFormat(tgtPattern, Locale.ENGLISH);
return tgtFormatter.format(srcFormatter.parse(strDate));
}
And then call it with any pattern that you want:
System.out.println(formatDate("2020-10-16T18:04:59+0300", "yyyy-MM-dd'T'HH:mm:ssZ", "dd-MM-yyy HH:mm"));
Don’t write a method that converts a date and time from a string in one format to a string in a different format. In your program keep dates and times as proper date-time objects. Just like you don’t keep numbers and Boolean values in strings (I hope!) When you receive string input, parse into a date-time object at once. Only when you need to give string output, format into an appropriate string.
When I receive a string containing date, time and UTC offset, like yours does, I prefer to parse it into a OffsetDateTime so I get all the information. It’s easier to throw unneeded information away later than to invent the information that we neglected to parse. Also a LocalDate will not work for your purpose since it doesn’t contain time of day. So you cannot format one into 2020-10-16 18:04 format.
For parsing your string I would use:
DateTimeFormatter formatter = new DateTimeFormatterBuilder()
.append(DateTimeFormatter.ISO_LOCAL_DATE)
.appendLiteral('T')
.append(DateTimeFormatter.ISO_LOCAL_TIME)
.appendOffset("+HHmm", "Z")
.toFormatter();
String sourceData = "2020-10-16T18:04:59+0300";
OffsetDateTime dateTime = OffsetDateTime.parse(sourceData, formatter);
System.out.println(dateTime);
Output is:
2020-10-16T18:04:59+03:00
The definition of the formatter is longish but has the advantage of reusing predefined formatters for date and time.
For displaying a formatted date and time to the user, don’t you want to use the user’s time zone rather then the offset that happened to be in the string (+03:00 in your case)?
ZoneId zone = ZoneId.of("Antarctica/South_Pole");
DateTimeFormatter newFormatter = DateTimeFormatter.ofPattern("dd-MM-yyyy HH:mm", Locale.ENGLISH);
String formatted = dateTime.atZoneSameInstant(zone).format(newFormatter);
System.out.println(formatted);
17-10-2020 04:04
What went wrong in your code?
As others have said, yyyy-MM-dd'T'HH:mm:ssZ in your format pattern string for parsing parses your entire date-time string of 2020-10-16T18:04:59+0300 nicely. The ''e at the end of the format pattern is the culprit. This would require an additional single quote (apostrophe) and the number of the day of the week to be present (pattern letter e is for localized day of week). Since Java had successfully parsed 24 chars and then failed to parse an apostrophe, it threw the exception mentioning thst the string could not be parsed at index 24.

Parsing date with timezone from a string

Quick (I suppose) question. How to parse string like that "2018-07-22 +3:00" to OffsetDateTime (setting time to 0:0:0.0)?
DateTimeFormatter formatter =cDateTimeFormatter.ofPattern("yyyy-MM-dd xxx");
OffsetDateTime dt = OffsetDateTime.parse("2007-07-21 +00:00", formatter);
java.time.format.DateTimeParseException: Text '2007-07-21 +00:00'
could not be parsed: Unable to obtain OffsetDateTime from
TemporalAccessor: {OffsetSeconds=0},ISO resolved to 2007-07-21 of type
java.time.format.Parsed
The trick here is to start by getting the TemporalAccessor:
TemporalAccessor ta = DateTimeFormatter.ofPattern("yyyy-MM-dd XXX").parse("2018-07-22 +03:00");
From there, you can extract a LocalDate and a ZoneOffset:
LocalDate date = LocalDate.from(ta);
ZoneOffset tz = ZoneOffset.from(ta);
And combine them like so:
ZonedDateTime zdt = date.atStartOfDay(tz);
An OffsetDateTime requires a time-of-day, but your format string doesn't supply that, so you need to tell the DateTimeFormatter to default time-of-day to midnight.
Also, offset +3:00 is invalid, since hour must be 2-digit, which means you need to fix that first.
This will do both:
public static OffsetDateTime parse(String text) {
// Fix 1-digit offset hour
String s = text.replaceFirst("( [+-])(\\d:\\d\\d)$", "$10$2");
DateTimeFormatter formatter = new DateTimeFormatterBuilder()
.appendPattern("uuuu-MM-dd xxx")
.parseDefaulting(ChronoField.HOUR_OF_DAY, 0)
.toFormatter();
return OffsetDateTime.parse(s, formatter);
}
Test
System.out.println(parse("2018-07-22 +3:00"));
System.out.println(parse("2018-07-22 +03:00"));
System.out.println(parse("2007-07-21 +00:00"));
Output
2018-07-22T00:00+03:00
2018-07-22T00:00+03:00
2007-07-21T00:00Z

Convert String without ZonedId to OffsetDateTime

I would like to convert an String to OffsetDateTime datatype. The string has the following shape:
2017-11-27T19:06:03
I've tried two approaches:
Approach 1
public static OffsetDateTime convertString(String timestamp) {
java.time.format.DateTimeFormatter formatter = new java.time.format.DateTimeFormatterBuilder()
.parseCaseInsensitive()
.append(java.time.format.DateTimeFormatter.ISO_LOCAL_DATE)
.appendLiteral('T')
.appendValue(HOUR_OF_DAY, 2)
.appendLiteral(':')
.appendValue(MINUTE_OF_HOUR, 2)
.optionalStart()
.appendLiteral(':')
.appendValue(SECOND_OF_MINUTE, 2)
.toFormatter();
return OffsetDateTime.parse(timestamp, formatter);
}
Approach 2:
public static OffsetDateTime convertString(String timestamp) {
java.time.format.DateTimeFormatter parser = java.time.format.DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss");
java.time.LocalDateTime dt = java.time.LocalDateTime.parse(timestamp, parser);
ZonedDateTime zdt = ZonedDateTime.of(dt, java.time.ZoneId.of("UTC"));
return OffsetDateTime.from(zdt);
}
First approach does not work since it complains the following:
java.time.format.DateTimeParseException: Text '2017-11-27T19:02:42' could not be parsed: Unable to obtain OffsetDateTime from TemporalAccessor: {},ISO resolved to 2017-11-27T19:02:42 of type java.time.format.Parsed
For my understanding it comes from the fact that the string does not have ZoneId. How can I overwrite, on the formatter, the ZoneId so to ignore it?
The second approach comes from this question and works but it requieres 2 additional conversions, I would like to avoid those additional conversions.
Any help is going to be appreciated.
To get an OffsetDateTime from a string with no offset or time zone, you need to decide an offset or a way to get one. The most obvious way is if you know in which time zone the date-time is to be interpreted. For example:
OffsetDateTime dateTime = LocalDateTime.parse(timestamp)
.atZone(ZoneId.of("Asia/Chongqing"))
.toOffsetDateTime();
System.out.println(dateTime);
Output using the string from your question:
2017-11-27T19:06:03+08:00
Don’t be afraid of the two conversions from LocalDateTime to ZonedDateTime and then to OffsetDateTime. With java.time they are not only easy to do but also clear to read.
If you know the offset, just use that, then you need only one conversion (I am using a nonsensical offset for the example, so you will have to pick your own):
OffsetDateTime dateTime = LocalDateTime.parse(timestamp)
.atOffset(ZoneOffset.ofTotalSeconds(-36953));
System.out.println(dateTime);
Output:
2017-11-27T19:06:03-10:15:53
You may specify your desired offset as for example ZoneOffset.of("+08:00") or ZoneOffset.ofHours(8).
But to answer your question (it’s about time now).
How can I overwrite, on the formatter, the ZoneId so to ignore it?
DateTimeFormatter formatter = new DateTimeFormatterBuilder()
.append(DateTimeFormatter.ISO_LOCAL_DATE_TIME)
.parseDefaulting(ChronoField.OFFSET_SECONDS, -36953)
.toFormatter();
OffsetDateTime dateTime = OffsetDateTime.parse(timestamp, formatter);
System.out.println(dateTime);
Output is the same as the previous one (and again you will need to pick an offset that makes sense in your situation).
Finally, I've solved this issue by the following code:
public static OffsetDateTime convertString(String timestamp) {
java.time.format.DateTimeFormatter formatter = new java.time.format.DateTimeFormatterBuilder()
.parseCaseInsensitive()
.append(java.time.format.DateTimeFormatter.ISO_LOCAL_DATE)
.appendLiteral('T')
.appendValue(HOUR_OF_DAY, 2)
.appendLiteral(':')
.appendValue(MINUTE_OF_HOUR, 2)
.optionalStart()
.appendLiteral(':')
.appendValue(SECOND_OF_MINUTE, 2)
.toFormatter();
return LocalDateTime.parse(timestamp, formatter)
.atOffset(java.time.ZoneOffset.UTC);
}

UnsupportedTemporalTypeException: Unsupported field: InstantSeconds

I have this code which is producing a timestamp and then parsing.
DateTimeFormatter formatter =
DateTimeFormatter
.ofPattern("yyyyMMdd kk:HH:ss.SSSZ")
.withLocale(Locale.getDefault())
.withZone(ZoneId.systemDefault());
Instant now = Instant.now();
String formatted = formatter.format(now);
Instant parsed = formatter.parse(formatted, Instant::from);
When it runs, the last line produces an exception:
java.time.format.DateTimeParseException: Text '20180123 12:12:45.648-0500' could not be parsed: Unable to obtain Instant from TemporalAccessor: {SecondOfMinute=45, NanoOfSecond=648000000, OffsetSeconds=-18000, MilliOfSecond=648, MicroOfSecond=648000, HourOfDay=12},ISO,America/New_York resolved to 2018-01-23 of type java.time.format.Parsed
Caused by: java.time.DateTimeException: Unable to obtain Instant from TemporalAccessor: {SecondOfMinute=45, NanoOfSecond=648000000, OffsetSeconds=-18000, MilliOfSecond=648, MicroOfSecond=648000, HourOfDay=12},ISO,America/New_York resolved to 2018-01-23 of type java.time.format.Parsed
Caused by: java.time.temporal.UnsupportedTemporalTypeException: **Unsupported field: InstantSeconds**
I replace the formatter with DateTimeFormatter.ISO_INSTANT, it works correctly. The actual data produced are nearly identical. What is the disconnect?
ISO_INSTANT: 2018-01-23T16:51:25.516Z
My Format: 20180119 23:59:59.999-0800
I am required to use my format. What is the problem here?
The problem is that your format does not completely represent an Instant because your format does not have a representation for minutes at all. The formatter can correctly go from Instant and output the result in your format because an Instant has all of the data that your format requires, but your format does not have everything that an Instant requires.
Try changing your pattern to yyyyMMdd kk:HH:mm:ss.SSS, and you will see that your code now works. Note the addition of mm.
If you absolutely require a minuteless pattern, you should make your own TemporalQuery to extract the information you require from the TemporalAccessor
In this case, I simply set minutes to 0:
public class MyQuery implements TemporalQuery<Instant> {
#Override
public Instant queryFrom(TemporalAccessor temporal) {
LocalDate ld = LocalDate.from(temporal);
LocalTime lt = LocalTime.of(temporal.get(ChronoField.HOUR_OF_DAY), 0, temporal.get(ChronoField.SECOND_OF_MINUTE), temporal.get(ChronoField.NANO_OF_SECOND));
return ZonedDateTime.of(ld, lt, ZoneId.systemDefault()).toInstant();
}
}
We can then use this TemporalQuery like this:
public class Test {
public static void main(String[] args) {
DateTimeFormatter formatter = DateTimeFormatter
.ofPattern("yyyyMMdd kk:HH:mm:ss.SSS")
.withLocale(Locale.getDefault())
.withZone(ZoneId.systemDefault());
Instant now = Instant.now();
String formatted = formatter.format(now);
System.out.println(formatted);
Instant ld = formatter.parse(formatted, new MyQuery());
}
}
I know that this is an old question but if you're looking for a short answer just add a locale and zone to the DateTimeFormatter, you may also use the default ones: .withLocale(Locale.getDefault()).withZone(ZoneId.systemDefault())
Here is an example of code:
Instant now = Instant.now();
System.out.println(now.toString());
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("dd/MM/yyyy HH:mm:ss").withLocale(Locale.getDefault()).withZone(ZoneId.systemDefault());
System.out.println(formatter.format(now));
This code will use the current instant, output the sample, format it with the date time formatter and output the formatted instant.

LocalDate - How to remove character 'T' in LocalDate

How to remove T in my localDate?
I need to remove the 'T' to match data in my database.
This is my code
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss", Locale.US);
String strLocalDate = patientDiagnosisByDoctor.getDiagnosisDateTime().toLocalDateTime().toString();
LocalDateTime localDate = LocalDateTime.parse(strLocalDate, formatter);
System.out.println(localDate);
I got this output:
2015-10-23T03:34:40
What is the best way to remove the 'T' character? Any idea guys?
What is the best way to remove the 'T' character? Any idea guys?
Use a DateTimeFormatter to format the value of LocalDateTime the way you want it...
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss", Locale.US);
String strLocalDate = "2015-10-23T03:34:40";
LocalDateTime localDate = LocalDateTime.parse(strLocalDate, formatter);
System.out.println(localDate);
System.out.println(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss").format(localDate));
System.out.println(DateTimeFormatter.ofPattern("HH:mm:ss yyyy-MM-dd ").format(localDate));
Which prints...
2015-10-23T03:34:40
2015-10-23 03:34:40
03:34:40 2015-10-23
Remember, date/time objects are just a container for amount of time which has passed since a fixed point in time (like the Unix epoch), they don't have a internal/configurable format of their own, they tend to use the current locale's format.
Instead, when you want to present the date/time value, you should first use a DateTimeFormatter to format the date/time value to what ever format you want and display that
I need to remove the 'T' to match data in my database.
Opps, missed that part.
In this case, you should be converting your Date/Time values to use java.sql.Timestamp and using a PreparedStatement to insert/update them
String localTime = "2018-09-13 00:00:00";
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss", Locale.ENGLISH);
LocalDateTime date = LocalDateTime.parse(localTime, formatter);
String replace = date.toString().replace("T", " ");
System.out.println(replace);
2018-09-13 00:00
Simple option using the Joda-Time library.
new LocalDateTime(DateTimeZone.forID("Europe/London")).toString().replace("T", " ");

Categories