Can I switch between formats for DateTimeFormatterBuilder? - java

I'm using DateTimeFormatterBuilder() to turn the JSON data I take in (the commented code) and convert them into one of two formats. To decide which format to use, I'm using a REGEX to find any instances of a square, [] , bracket (with anything inside " .*? " ). After choosing the correct format, I would parse the new value into another JSON object.
The problem is, my program either does not correctly choose which format to use (either a REGEX and method error), or doesn't format it correctly (formatting error), not sure which, and sends an error back (bottom of code), instead.
However, this is only for data that has square brackets. Data without square brackets gets processed correctly. I'm wondering if there are any solutions/suggestions to fix this?
// 2018-11-28T13:09:00.2-04:00
def utcDateFormatter = new DateTimeFormatterBuilder()
.appendPattern("yyyy-MM-dd'T'HH:mm:ss")
.appendFraction(ChronoField.MILLI_OF_SECOND, 0, 3, true)
.appendPattern("xxx")
.toFormatter()
// 2018-11-28T13:09:00.528-08:00[America/New_York]
def utcDateFormatterWithZone = new DateTimeFormatterBuilder()
.appendPattern("yyyy-MM-dd'T'HH:mm:ss")
.appendFraction(ChronoField.MILLI_OF_SECOND, 0, 3, true)
.appendPattern("xxx'['VV']'")
.toFormatter()
if (json.beginDateTime.find("\\[.*?\\]") == true) {
object.setDate(LocalDateTime.parse("${json.beginDateTime}", utcDateFormatterWithZone).format(outFormatter))
} else {
object.setDate(LocalDateTime.parse("${json.beginDateTime}", utcDateFormatter).format(outFormatter))
}
Error: Text '2019-09-26T15:01:07.941-05:00[America/New_York]' could not be parsed, unparsed text found at index 29

This is built-in: DateTimeFormatter.ISO_ZONED_DATE_TIME
This can be done a lot more easily. The built-in DateTimeFormatter.ISO_ZONED_DATE_TIME matches both of your formats.
String stringWithoutZoneId = "2018-11-28T13:09:00.2-04:00";
String stringWithZoneId = "2018-11-28T13:09:00.528-08:00[America/New_York]";
LocalDateTime parsedWithoutZoneId = LocalDateTime.parse(
stringWithoutZoneId, DateTimeFormatter.ISO_ZONED_DATE_TIME);
System.out.println(parsedWithoutZoneId);
LocalDateTime parsedWithZoneId = LocalDateTime.parse(
stringWithZoneId, DateTimeFormatter.ISO_ZONED_DATE_TIME);
System.out.println(parsedWithZoneId);
Output from this snippet is:
2018-11-28T13:09:00.200
2018-11-28T13:09:00.528
Use the offset too
A word of warning, though: Are you sure you want to ignore the offsets in the strings? With those offsets the strings represent unambiguous points in time. What you get from parsing into LocalDateTime are datetimes belonging at different unknown offsets. I can’t see how you can reliably use them for anything useful.
Consider parsing into ZonedDateTime. The one-arg ZonedDateTIme.parse will even do this without any explicit formatter. Then either store these ZonedDateTime directly in your objects or convert to Instant and store those. An Instant represents a point in time. If you cannot change the type stored, you will probably want to convert your ZonedDateTime to UTC (or another agreed-upon time zone), then convert to LocalDateTime. All of this said without knowing your real requirements, so I could be wrong, only I think not.
What went wrong in your code?
#daggett is correct: CharSequence.find returns a string, so for you if statement to work you would have needed:
if (json.beginDateTime.find("\\[.*?\\]") != null) {
A String can never be equal to true, so the formatter without zone was always chosen.
Link
Documentation of DateTimeFormatter.ISO_ZONED_DATE_TIME

Related

LocalDateTime(or any other suitable class) with time as optional paramter

I am trying to parse an incoming string which might contain time or not. Both the following dates should be accepted
"2022-03-03" and "2022-03-03 15:10:05".
The DateTimeFormatter that I know will fail in any one of the cases. This is one answer I got, but I don't know if in any ways time part can be made optional here.
ISO_DATE_TIME.format() to LocalDateTime with optional offset
The idea is if the time part is not present I should set it to the end of the day, so the time part should be 23:59:59.
Any help is appreciated. Thanks!
Well, you could utilize a DateTimeFormatterBuilder to specify defaults for missing fields:
private static LocalDateTime parse(String str) {
DateTimeFormatter formatter = new DateTimeFormatterBuilder()
.appendPattern("uuuu-MM-dd[ HH:mm:ss]")
.parseDefaulting(ChronoField.HOUR_OF_DAY, 23)
.parseDefaulting(ChronoField.MINUTE_OF_HOUR, 59)
.parseDefaulting(ChronoField.SECOND_OF_MINUTE, 59)
.toFormatter();
return LocalDateTime.parse(str, formatter);
}
The pattern specifies the pattern it will try to parse. Note that the square brackets ([]) are optional parts. Everything between them will be either completely consumed, or entirely discarded.
With parseDefaulting you can specify the default values for when fields are missing. In your case, if the user provides only the date, the hour-of-day, minute-of-hour and second-of-minute fields are missing, that's why it is needed to provide defaults for them.
Example
System.out.println(parse("2022-03-03"));
System.out.println(parse("2022-03-03 15:10:05"));
System.out.println(parse("2025"));
Outputs the following:
2022-03-03T23:59:59
2022-03-03T15:10:05
Exception in thread "main" java.time.format.DateTimeParseException: Text '2025' could not be parsed at index 4

Remove time zone part from - ZonedDateTime : JAVA

I am converting milliseconds into ZonedDateTime
Long lEpochMilliSeconds = 1668415926445;
System.out.println(ZonedDateTime.ofInstant(Instant.ofEpochMilli(lEpochMilliSeconds),ZoneId.of("UTC"));
It gives output:
2022-10-28T12:59:34.939Z[UTC]
I don't want the time zone "[UTC]" part in my output.
I need my out to be like this in ZonedDateTime format:
2022-10-28T12:59:34.939Z
I need the format in ZonedDateTime only not string, as I will be returning the value & use it somewhere else
I need the format in ZonedDateTime only not string
This is like saying "I need an int that knows it's in hex rather than decimal". There's just no such concept.
If you need to format the value in a particular way, you should apply that format where you do the formatting.
It's possible that what you should actually do is return an Instant instead of a ZonedDateTime. That will format the way you want by default, although it's still not "part of the object" - just the default format for all instants.
It's important to understand the difference between "the value being represented" (and the type you're using to represent that value) and "a string representation of that value". You should try use the semantically-appropriate type for what you're trying to represent (e.g. ZonedDateTime, Instant etc) for as much of the time as possible, only converting to and from string representations at system boundaries. Those system boundaries need to be aware of the expected textual representation, and perform the appropriate conversion, rather than expecting a particular string representation to travel with the value itself through the system.
It is giving me the desired output when I gave ZoneOffset as an argument instead of ZoneId.
ZonedDateTime.ofInstant(Instant.ofEpochMilli(lEpochMilliSeconds), ZoneOffset.UTC);

Parsing PDF date using Java DateTimeFormatter

I'm trying to parse the date format used in PDFs. According to this page, the format looks as follows:
D:YYYYMMDDHHmmSSOHH'mm'
Where all components except the year are optional. I assume this means the string can be cut off at any point as i.e. specifying a year and an hour without specifying a month and a day seems kind of pointless to me. Also, it would make parsing pretty much impossible.
As far as I can tell, Java does not support zone offsets containing single quotes. Therefore, the first step would be to get rid of those:
D:YYYYMMDDHHmmSSOHHmm
The resulting Java date pattern should then look like this:
['D:']uuuu[MM[dd[HH[mm[ss[X]]]]]]
And my overall code looks like this:
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("['D:']uuuu[MM[dd[HH[mm[ss[X]]]]]]");
TemporalAccessor temporalAccessor = formatter.parseBest("D:20020101",
ZonedDateTime::from,
LocalDateTime::from,
LocalDate::from,
Month::from,
Year::from
);
I would expect that to result in a LocalDate object but what I get is java.time.format.DateTimeParseException: Text 'D:20020101' could not be parsed at index 2.
I've played around a bit with that and found out that everything works fine with the optional literal at the beginning but as soon as I add optional date components, I get an exception.
Can anybody tell me what I'm doing wrong?
Thanks in advance!
I've found a solution:
String dateString = "D:20020101120000+01'00'";
String normalized = dateString.replace("'", "");
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("['D:']ppppy[ppM[ppd[ppH[ppm[pps[X]]]]]]");
TemporalAccessor temporalAccessor = formatter.parseBest(normalized,
OffsetDateTime::from,
LocalDateTime::from,
LocalDate::from,
YearMonth::from,
Year::from
);
As it seems, the length of the components is ambiguous and parsing of the date without any separators thus failed.
When specifying a padding, the length of each component is clearly stated and the date can therefore be parsed.
At least that's my theory.

what is the actual return type for LocalDate.now().toString()

I am trying to save LocalDateTime to DB. So my scenario is I have consumer and producer. Producer generated some code and gives it to consumer, and consumer saves the entries to database.
Code on Producer Side
LocalDate.now().toString() // returns 2021-07-13T12:25:38.841775700 sometimes it returns 2021-07-13T12:25:38.841 so basically after the last decimal point the precision can be anything.
Code on Consumer side
On the consumer side i want to save entries from received from producer into the db for that i need to convert the str to LocalDateDime.
private static DateTimeFormatter timeFormatterInMs= DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss.SSS");
public static LocalDateTime unmarshalDateTime(final String dateTimeStr) {
return dateTimeStr == null ? null : LocalDateTime.parse(str, timeFormatterInMs);
}
Here the problem is what is the format for return type of LocalDateTime
i know it return data in the format yyyy-MM-dd'T'HH:mm:ss.SSS by in our case ms have sometimes 9, sometimes 6 decimal places? if i will give this string to the function unmarshalDateTime(..) the function will break and it wont work properly. because it expects the ms part to be 3 decimal places. what to do about it
The direct answer to your question is in the documentation of LocalDateTime.toString() (link at the bottom):
Outputs this date-time as a String, such as 2007-12-03T10:15:30.
The output will be one of the following ISO-8601 formats:
uuuu-MM-dd'T'HH:mm
uuuu-MM-dd'T'HH:mm:ss
uuuu-MM-dd'T'HH:mm:ss.SSS
uuuu-MM-dd'T'HH:mm:ss.SSSSSS
uuuu-MM-dd'T'HH:mm:ss.SSSSSSSSS
The format used will be the shortest that outputs the full value of
the time where the omitted parts are implied to be zero.
To parse such a string back into a LocalDateTime don’t use any formatter at all. The one-arg LocalDateTime.parse(CharSequence) parses every one of those formats.
Let me show you:
System.out.println(LocalDateTime.parse("2021-07-13T12:25:38.841775700"));
System.out.println(LocalDateTime.parse("2021-07-13T12:25:38.841"));
System.out.println(LocalDateTime.parse("2021-07-13T12:26"));
Output:
2021-07-13T12:25:38.841775700
2021-07-13T12:25:38.841
2021-07-13T12:26
This said Ali Behzadian Nejad is correct in his answer: assuming that those values are supposed to define points in time, LocalDateTime is the wrong type for them. Use Instant. If you can, use timestamp with time zone on the SQL side. Depending on your JDBC driver you may or may not need to convert via OffsetDateTime. If you need to store strings, Instant.toString() too can produce different formats and can parse each and every one of them back through the one-arg Instant.parse().
Documentation links
LocalDateTime.toString()
LocalDateTime.parse(CharSequence)
Storing local dates in database is not a good idea because they are not instants of time, they are just localized presentation of time.
Create an Instant object from LocalTime and its timezone and save that time Instant in database column with Timestamp type:
LocalDateTime localDateTime = ...
Instant instant = localDateTime.atZone(ZoneId.of("Europe/Paris")).toInstant();
From Java documentation:
This class [LocalDateTime] does not store or represent a time-zone.
Instead, it is a description of the date, as used for birthdays,
combined with the local time as seen on a wall clock. It cannot
represent an instant on the time-line without additional information
such as an offset or time-zone.
With jdk11: The format used will be the shortest that outputs the full value of the time where the omitted parts are implied to be zero.
Also there have LocalDate.format to generate a time string by format.
BTW, if you want save time to database I suggest use long or Timestamp instead of String
You can use a DateTimeFormatter that considers different patterns in order to parse Strings with different amounts of fractions-of-second (here: 6 or 9):
public static void main(String[] args) throws Exception {
String datetime = "2021-07-13T12:25:38.841775700";
String datetimeShort = "2021-07-13T12:25:38.841775";
// provide a formatter that "knows" two different patterns
DateTimeFormatter dtf = DateTimeFormatter.ofPattern(
"[uuuu-MM-dd'T'HH:mm:ss.nnnnnnnnn][uuuu-MM-dd'T'HH:mm:ss.SSSSSS]");
// parse the one with 9 fractions-of-second
LocalDateTime ldt = LocalDateTime.parse(datetime, dtf);
// and the one with 6 using the same formatter
LocalDateTime ldtShort = LocalDateTime.parse(datetimeShort, dtf);
// output the parsed LocalDateTimes
System.out.println(ldt + " and " + ldtShort + " are successfully parsed");
}
This outputs
2021-07-13T12:25:38.841775700 and 2021-07-13T12:25:38.841775 are successfully parsed

Validate correct date time format fetched from csv

I need to read one csv file which has different time format in one timestamp column. It can be anything from below mentioned 5 formats. I need to match the fetched date and parse accordingly on each row.
Please suggest how to validate ad parse it. thanks in advance.
public static final String DEFAULT_DATE_FORMAT_PATTERN = "yyyy-MM-dd";
public static final String DEFAULT_DATE_TIME_FORMAT_PATTERN = "yyyy-MM-dd HH:mm:ss.SSS";
public static final String DATE_TIME_MINUTES_ONLY_FORMAT_PATTERN = "yyyy-MM-dd HH:mm";
public static final String DATE_TIME_WITHOUT_MILLIS_FORMAT_PATTERN = "yyyy-MM-dd HH:mm:ss";
Epoch in milli
What you need is a formatter with optional parts. A pattern can contain square brackets to denote an optional part, for example HH:mm[:ss]. The formatter then is required to parse HH:mm, and tries to parse the following text as :ss, or skips it if that fails. yyyy-MM-dd[ HH:mm[:ss[.SSS]]] would then be the pattern.
There is only one issue here – when you try to parse a string with the pattern yyyy-MM-dd (so without time part) using LocalDateTime::parse, it will throw a DateTimeFormatException with the message Unable to obtain LocalDateTime from TemporalAccessor. Apparently, at least one time part must be available to succeed.
Luckily, we can use a DateTimeFormatterBuilder to build a pattern, instructing the formatter to use some defaults if information is missing from the parsed text. Here it is:
DateTimeFormatter formatter = new DateTimeFormatterBuilder()
.appendPattern("yyyy-MM-dd[ HH:mm[:ss[.SSS]]]")
.parseDefaulting(ChronoField.HOUR_OF_DAY, 0)
.parseDefaulting(ChronoField.MINUTE_OF_HOUR, 0)
.parseDefaulting(ChronoField.SECOND_OF_MINUTE, 0)
.toFormatter();
LocalDateTime dateTime = LocalDateTime.parse(input, formatter);
Tests:
String[] inputs = {
"2020-10-22", // OK
"2020-10-22 14:55", // OK
"2020-10-22T14:55", // Fails: incorrect format
"2020-10-22 14:55:23",
"2020-10-22 14:55:23.9", // Fails: incorrect fraction of second
"2020-10-22 14:55:23.91", // Fails: incorrect fraction of second
"2020-10-22 14:55:23.917", // OK
"2020-10-22 14:55:23.9174", // Fails: incorrect fraction of second
"2020-10-22 14:55:23.917428511" // Fails: incorrect fraction of second
};
And what about epoch in milli?
Well, this cannot be parsed directly by the DateTimeFormatter. But what's more: an epoch in milli has an implicit timezone: UTC. The other patterns lack a timezone. So an epoch is a fundamentally different piece of information. One thing you could do is assume a timezone for the inputs missing one.
However, if you nevertheless want to parse the instant, you could try to parse it as a long using Long::parseLong, and if it fails, then try to parse with the formatter. Alternatively, you could use a regular expression (like -?\d+ or something) to try to match the instant, and if it does, then parse as instant, and if it fails, then try to parse with the abovementioned formatter.
The brute force approach:
simply try your 4 formats, one after the other to parse the incoming string
if parsing throws an exception, try the next one
if parsing passes, well, that format just matched
Of course, if we are talking about larger tables, that is quite inefficient. Possible optimisations:
obviously, the different patterns have subtle differences, so you could use indexOf() checks first. Like: if the value to be parsed contains no ':' char, then it can only be the first pattern.
you can look at your data manually to figure the actual distribution of patterns that are used. then you adapt the order of patterns to try to the likelihood of the pattern being used in your data
Alternatively: you could define your own regex. The only thing that makes it slightly ugly is the fact that your input uses month names, not month number. But I think it shouldn't be too hard to write up a single regex that covers all your cases.

Categories