DateTimeFormatter fails on milliseconds [duplicate] - java

With the first release of Java 8 (b132) on Mac OS X (Mavericks), this code using the new java.time package works:
String input = "20111203123456";
DateTimeFormatter formatter = DateTimeFormatter.ofPattern( "yyyyMMddHHmmss");
LocalDateTime localDateTime = LocalDateTime.parse( input, formatter );
Rendering:
2011-12-03T12:34:56
But when I add "SS" for fraction-of-second (and "55" as input), as specified in the DateTimeFormatter class doc, an exception is thrown:
java.time.format.DateTimeParseException: Text '2011120312345655' could not be parsed at index 0
The doc says Strict mode is used by default and requires the same number of format characters as input digits. So I'm confused why this code fails:
String input = "2011120312345655";
DateTimeFormatter formatter = DateTimeFormatter.ofPattern( "yyyyMMddHHmmssSS");
LocalDateTime localDateTime = LocalDateTime.parse( input, formatter );
Another example using example from documentation ("978") (fails):
String input = "20111203123456978";
DateTimeFormatter formatter = DateTimeFormatter.ofPattern( "yyyyMMddHHmmssSSS");
LocalDateTime localDateTime = LocalDateTime.parse( input, formatter );
This example works, adding a decimal point (but I find no such requirement in the doc):
String input = "20111203123456.978";
DateTimeFormatter formatter = DateTimeFormatter.ofPattern( "yyyyMMddHHmmss.SSS");
LocalDateTime localDateTime = LocalDateTime.parse( input, formatter );
Renders:
localDateTime: 2011-12-03T12:34:56.978
Omitting the period character from either the input string or the format cause a fail.
Fails:
String input = "20111203123456.978";
DateTimeFormatter formatter = DateTimeFormatter.ofPattern( "yyyyMMddHHmmssSSS");
LocalDateTime localDateTime = LocalDateTime.parse( input, formatter );
Fails:
String input = "20111203123456978";
DateTimeFormatter formatter = DateTimeFormatter.ofPattern( "yyyyMMddHHmmss.SSS");
LocalDateTime localDateTime = LocalDateTime.parse( input, formatter );

Bug – Fixed in Java 9
This issue was already reported in JDK-bug-log. Stephen Colebourne mentions as work-around following solution:
DateTimeFormatter dtf =
new DateTimeFormatterBuilder()
.appendPattern("yyyyMMddHHmmss")
.appendValue(ChronoField.MILLI_OF_SECOND, 3)
.toFormatter();
Note: This workaround does not cover your use-case of only two pattern symbols SS. An adjustment might only be to use other fields like MICRO_OF_SECOND (6 times SSSSSS) or NANO_OF_SECOND (9 times SSSSSSSSS). For two fraction digits see my update below.
#PeterLawrey About the meaning of pattern symbol "S" see this documentation:
Fraction: Outputs the nano-of-second field as a fraction-of-second.
The nano-of-second value has nine digits, thus the count of pattern
letters is from 1 to 9. If it is less than 9, then the nano-of-second
value is truncated, with only the most significant digits being
output. When parsing in strict mode, the number of parsed digits must
match the count of pattern letters. When parsing in lenient mode, the
number of parsed digits must be at least the count of pattern letters,
up to 9 digits.
So we see that S stands for any fraction of second (including nanosecond), not just milliseconds. Furthermore, the fractional part does at the moment not take well in adjacent value parsing, unfortunately.
EDIT:
As background here some remarks about adjacent value parsing. As long as fields are separated by literals like a decimal point or time part separators (colon), the interpretation of fields in a text to be parsed is not difficult because the parser then knows easily when to stop i.e. when the field part is ended and when the next field starts. Therefore the JSR-310 parser can process the text sequence if you specify a decimal point.
But if you have a sequence of adjacent digits spanning over multiple fields then some implementation difficulties arise. In order to let the parser know when a field stops in text it is necessary to instruct the parser in advance that a given field is represented by a fixed-width of digit chars. This works with all appendValue(...)-methods which assume numerical representations.
Unfortunately JSR-310 has not managed well to do this also with the fractional part (appendFraction(...)). If you look for the keyword "adjacent" in the javadoc of class DateTimeFormatterBuilder then you find that this feature is ONLY realized by appendValue(...)-methods. Note that the spec for pattern letter S is slightly different but internally delegates to appendFraction()-method. I assume we will at least have to waint until Java 9 (as reported in JDK-bug-log, or later???) until fraction parts can manage adjacent value parsing as well.
Update from 2015-11-25:
The following code using two fraction digits only does not work and misinterpretes the millisecond part:
DateTimeFormatter dtf =
new DateTimeFormatterBuilder()
.appendPattern("yyyyMMddHHmmss")
.appendValue(ChronoField.MILLI_OF_SECOND, 2)
.toFormatter();
String input = "2011120312345655";
LocalDateTime ldt = LocalDateTime.parse(input, dtf);
System.out.println(ldt); // 2011-12-03T12:34:56.055
The workaround
String input = "2011120312345655";
SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMddHHmmssSS");
Date d = sdf.parse(input);
System.out.println(d.toInstant()); // 2011-12-03T12:34:56.055Z
does not work because SimpleDateFormat interpretes the fraction in a wrong way, too, similar to the modern example (see output, 55 ms instead of 550 ms).
What is left as solution is either waiting an undertermined long time until Java 9 (or later?) or writing your own hack or using 3rd-party libraries as solution.
Solution based on a dirty hack:
String input = "2011120312345655";
DateTimeFormatter dtf = DateTimeFormatter.ofPattern("yyyyMMddHHmmss");
int len = input.length();
LocalDateTime ldt = LocalDateTime.parse(input.substring(0, len - 2), dtf);
int millis = Integer.parseInt(input.substring(len - 2)) * 10;
ldt = ldt.plus(millis, ChronoUnit.MILLIS);
System.out.println(ldt); // 2011-12-03T12:34:56.550
Solution using Joda-Time:
String input = "2011120312345655";
DateTimeFormatter dtf = DateTimeFormat.forPattern("yyyyMMddHHmmssSS");
System.out.println(dtf.parseLocalDateTime(input)); // 2011-12-03T12:34:56.550
Solution using my library Time4J:
String input = "2011120312345655";
ChronoFormatter<PlainTimestamp> f =
ChronoFormatter.ofTimestampPattern("yyyyMMddHHmmssSS", PatternType.CLDR, Locale.ROOT);
System.out.println(f.parse(input)); // 2011-12-03T12:34:56.550
Update from 2016-04-29:
As people can see via the JDK-issue mentioned above, it is now marked as resolved - for Java 9.

DateTimeFormatterBuilder#appendFraction(ChronoField.MILLI_OF_SECOND, 0, 3, true)
Something like this helped me

Here's an algorithm which adjusts the order of the trailing zeros that are conventionally returned from the formatted date String.
/**
* Takes a Date and provides the format whilst compensating for the mistaken representation of sub-second values.
* i.e. 2017-04-03-22:46:19.000991 -> 2017-04-03-22:46:19.991000
* #param pDate Defines the Date object to format.
* #param pPrecision Defines number of valid subsecond characters contained in the system's response.
* */
private static final String subFormat(final Date pDate, final SimpleDateFormat pSimpleDateFormat, final int pPrecision) throws ParseException {
// Format as usual.
final String lString = pSimpleDateFormat.format(pDate);
// Count the number of characters.
final String lPattern = pSimpleDateFormat.toLocalizedPattern();
// Find where the SubSeconds are.
final int lStart = lPattern.indexOf('S');
final int lEnd = lPattern.lastIndexOf('S');
// Ensure they're in the expected format.
for(int i = lStart; i <= lEnd; i++) { if(lPattern.charAt(i) != 'S') {
// Throw an Exception; the date has been provided in the wrong format.
throw new ParseException("Unable to process subseconds in the provided form. (" + lPattern + ").", i);
} }
// Calculate the number of Subseconds. (Account for zero indexing.)
final int lNumSubSeconds = (lEnd - lStart) + 1;
// Fetch the original quantity.
String lReplaceString = lString.substring(lStart + (lNumSubSeconds - pPrecision), lStart + lNumSubSeconds);
// Append trailing zeros.
for(int i = 0; i < lNumSubSeconds - pPrecision; i++) { lReplaceString += "0"; }
// Return the String.
return lString.substring(0, lStart) + lReplaceString;
}

Related

Handling various timestamp with one formatter in java/scala?

I am currently getting two version of timestamp format eg '2017-04-17 20:33:45.223+05:30' and '2017-04-17 20:33:45+05:30'.My parsing is failing due to dynamic timestamp .Is it possible to handle both of these time stamp with one DateTimeFormatter Pattern .Below is the example code what i tried
val myDate=LocalDateTime.parse("2017-04-17 20:33:45.223+05:30", DateTimeFormat.forPattern("yyyy-MM-dd HH:mm:ss.SSSZ")).toDateTime(DateTimeZone.UTC)//this will fail if time stamp comes with '2017-04-17 20:33:45+05:30
I had seen one way to achieve the same using optional part however I canot make it work
val pattern = "MM/dd/yyyy HH:mm:ss[.SSS]Z"
val fmt = DateTimeFormatter.ofPattern(pattern)
val temporalAccessor = fmt.parse("2017-04-17 20:33:45.223+05:30")
Ant help on this or any suggestion how to handle such cases will be helpful .Thanks in advance .
uuuu-MM-dd
Edit: This fixes it. I am using java.time, the modern Java date and time API, and Java syntax.
private static final DateTimeFormatter FORMATTER
= DateTimeFormatter.ofPattern("uuuu-MM-dd HH:mm:ss[.SSS]xxx", Locale.ROOT);
Trying it out:
String[] variants = {
"2017-04-17 20:33:45.223+05:30",
"2017-04-17 20:33:45+05:30",
// Variants we don’t want to accept
"2017-04-17 20:00+05:30",
"2017-04-17 20:00:00.000000+05:30" };
for (String inputString : variants) {
try {
OffsetDateTime dateTime = OffsetDateTime.parse(inputString, FORMATTER);
System.out.println("Parsed: " + dateTime);
} catch (DateTimeParseException dtpe) {
System.out.println("Invalid: " + inputString);
}
}
Output:
Parsed: 2017-04-17T20:33:45.223+05:30
Parsed: 2017-04-17T20:33:45+05:30
Invalid: 2017-04-17 20:00+05:30
Invalid: 2017-04-17 20:00:00.000000+05:30
What went wrong in your code?
You had the right idea for your purpose.
You attempted using the outmoded Joda-Time library. Joda-Time can support optional parts when parsing, but not through the square bracket syntax. Instead its DateTimeFormatterBuilder has got an appendOptional method.
In your java.time code this part of your format pattern string doesn’t match any of your inputs: MM/dd/yyyy. Java parsed 20 as a 2 digit month number (postponing validation of the number) and threw the exception because no slash was found after 20.
Edit 2: why xxx works but Z doesn't:
With Joda-Time’s DateTimeFormat one Z is for offset without colon, for example +0530. ZZ should have worked for +05:30 with colon.
With java.time both x and Z (and also upper case X) are for zone offset. Here too Z is for offset without colon. Either xxx or ZZZZZ works for +05:30.
Use the built-in formatters
Original answer, likely useful for others: This one does it (using Java syntax):
private static final DateTimeFormatter FORMATTER = new DateTimeFormatterBuilder()
.append(DateTimeFormatter.ISO_LOCAL_DATE)
.appendLiteral(' ')
.append(DateTimeFormatter.ISO_LOCAL_TIME)
.appendOffsetId()
.toFormatter();
Let’s try it out:
String[] variants = {
"2017-04-17 20:33:45.223+05:30",
"2017-04-17 20:33:45+05:30",
"2017-04-17 20:00+05:30",
"2017-04-17 20:00:00.000000+05:30" };
for (String inputString : variants) {
OffsetDateTime dateTime = OffsetDateTime.parse(inputString, FORMATTER);
System.out.println(dateTime);
}
Output:
2017-04-17T20:33:45.223+05:30
2017-04-17T20:33:45+05:30
2017-04-17T20:00+05:30
2017-04-17T20:00+05:30
I am exploiting the fact that the built-in DateTimeFormatter.ISO_LOCAL_TIME accepts a time both with and without decimals on the seconds. We can reuse existing formatters in our own formatter through a DateTimeFormatterBuilder.
parseBest looks a good fit for this
public TemporalAccessor parseBest(CharSequence text,
TemporalQuery<?>... queries)
Fully parses the text producing an object of one of the specified
types.
This parse method is convenient for use when the parser can handle optional elements. For example, a pattern of 'uuuu-MM-dd HH.mm[ VV]'
can be fully parsed to a ZonedDateTime, or partially parsed to a
LocalDateTime. The queries must be specified in order, starting from
the best matching full-parse option and ending with the worst matching
minimal parse option. The query is typically a method reference to a
from(TemporalAccessor) method.
The result is associated with the first type that successfully parses

Date and time to UTC given timezone

i have the following string: 2019120610000100 which corresponds to 2019/12/06 at 10:00 +1.
How can I convert this to utc time, in this case 2019/12/06 09:00?
This string could also have a +2, +3 ... -1, -2 ... timezone so I must be able to convert other strings too.
The + or - sign is given in another instance however, if it can be useful, it can be added to the time and date string.
(The string could become 201912061000 +0100)
Right now I'm converting it manually splitting the string but I'm trying to find a way to make this safe as it gets tricky with hours and minutes like 00 that have to change the day, possibly the month and year.
This is what I have made so far:
hour -= hourOffset;
if(hour<0){
hour += 24
}
minutes -= minutesOffset;
if(minutes<0){
minutes += 60
}
When dealing with dates and times, it is usually better to not do string operations but use one of the many classes that extend java.time.temporal.Temporal from the java.time package - introduced with Java 8.
In your case, you want to use an OffsetDateTime, as your string represents exactly that: A date-time with an offset. Note, that a ZonedDateTime is not really appropriate here, because the offset information (e.g. "+01:00") is not enough to represent a whole timezone. Look at this SO question for more information.
To get an OffsetDateTime from a string, you must simply parse it.
Let's do it.
Step 1: Adjust your string to contain the offset sign (plus or minus).
String offsetSign = "+";
String datetimeString = "2019120610000100";
datetimeString = new StringBuilder(datetimeString).insert(datetimeString.length() - 4, offsetSign).toString();
Step 2: Parse that string to an OffsetDateTime object.
DateTimeFormatter dtf = DateTimeFormatter.ofPattern("yyyyMMddHHmmZ");
OffsetDateTime odt = OffsetDateTime.parse(datetimeString, dtf);
Step 3: Convert that OffsetDateTime to UTC.
OffsetDateTime odtUTC = odt.withOffsetSameInstant(ZoneOffset.UTC);
Printing out those variables
System.out.println(datetimeString);
System.out.println(odt);
System.out.println(odtUTC);
will get you the following output:
201912061000+0100
2019-12-06T10:00+01:00
2019-12-06T09:00Z
You can directly convert the time to UTC by the following code
String dateStr = "201912061000+0100";
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyyMMddHHmm");
final LocalDateTime parse = LocalDateTime.parse(dateStr.substring(0, dateStr.length()-5), formatter);
final ZoneId zone = ZoneId.of("GMT"+dateStr.substring(12,15)+":"+dateStr.substring(15));
final ZonedDateTime given = ZonedDateTime.of(parse, zone);
final String toUTC = given.withZoneSameInstant(ZoneId.of("UTC"))
.format(DateTimeFormatter.ofPattern("yyyy/MM/dd HH:mm"));
String dateStr = "2019120610000100";
DateTimeFormatter dtfInput = DateTimeFormatter.ofPattern("yyyyMMddHHmm Z");
DateTimeFormatter dtfOutput = DateTimeFormatter.ofPattern("yyyy/MM/dd hh:mm");
String adjustedDateStr = new StringBuilder(dateStr).insert(dateStr.length() - 4, " +").toString();
ZonedDateTime givenDate = ZonedDateTime.parse(adjustedDateStr, dtfInput);
ZonedDateTime timezoneAdjustedDate = ZonedDateTime.ofInstant(givenDate.toInstant(), ZoneId.of("UTC"));
System.out.println(dtfOutput.format(timezoneAdjustedDate));
Since you handle the plus or minus for the timezone offset externally, you can just insert it into the exsample above instead of the plus if need be.

Single pattern for java 8 date time [duplicate]

This question already has answers here:
JSR-310 - parsing seconds fraction with variable length
(4 answers)
Java 8 Date equivalent to Joda's DateTimeFormatterBuilder with multiple parser formats?
(5 answers)
Closed 4 years ago.
public static long convertDateTimeToEpochMillis(String eventDate,String eventTime) {
String patternMills = "yyyy-MM-dd HH:mm:ss.SSS";
DateTimeFormatter dtf = DateTimeFormatter.ofPattern(pattern);
LocalDateTime localDateTime = LocalDateTime.parse(eventDate + " " +
eventTime, dtf);
}
This is giving a parsing exception when I am passing 2018-07-19 23:11:52.3 but parses successfully for 2018-07-19 23:11:52.312. I don't want to specify 3 different patterns for different times like yyyy-MM-dd HH:mm:ss.S,yyyy-MM-dd HH:mm:ss.SS and yyyy-MM-dd HH:mm:ss.SSS.
Can I provide a single pattern which will take up to 1/10th of a sec, 100th of a sec and millisec?
ISO 8601
Actually your pattern is not far from accepted by LocalDateTime.parse default pattern, using standard ISO 8601 format.
So, maybe put not a space but letter T instead will be enough for you?
Then you do not need DateTimeFormatter at all.
LocalDateTime localDateTime = LocalDateTime.parse(eventDate + "T" +
eventTime);
It accepts any number of digits in milliseconds part, from 0 up to 9.
You can just choose a pattern with three SSS as default
String patternMills = "yyyy-MM-dd HH:mm:ss.SSS";
and fulfill right side of your date with zeros when necessary
String eventDate = "2018-07-19 23:11:52.3";
String millis = eventDate.substring(eventDate.lastIndexOf('.')+1);
if(millis.length() < 3)
eventDate += Stream.generate(() -> "0").limit(3 - millis.length()).collect(Collectors.joining(""));
or use nice apache method:
StringUtils.rightPad(eventDate, 23, '0'); // 23 is string length for "yyyy-MM-dd HH:mm:ss.SSS" pattern
Anyway you can also add some validation to check the rest part of the date string

Java: DateTimeFormatter and parsing sub-seconds [duplicate]

With the first release of Java 8 (b132) on Mac OS X (Mavericks), this code using the new java.time package works:
String input = "20111203123456";
DateTimeFormatter formatter = DateTimeFormatter.ofPattern( "yyyyMMddHHmmss");
LocalDateTime localDateTime = LocalDateTime.parse( input, formatter );
Rendering:
2011-12-03T12:34:56
But when I add "SS" for fraction-of-second (and "55" as input), as specified in the DateTimeFormatter class doc, an exception is thrown:
java.time.format.DateTimeParseException: Text '2011120312345655' could not be parsed at index 0
The doc says Strict mode is used by default and requires the same number of format characters as input digits. So I'm confused why this code fails:
String input = "2011120312345655";
DateTimeFormatter formatter = DateTimeFormatter.ofPattern( "yyyyMMddHHmmssSS");
LocalDateTime localDateTime = LocalDateTime.parse( input, formatter );
Another example using example from documentation ("978") (fails):
String input = "20111203123456978";
DateTimeFormatter formatter = DateTimeFormatter.ofPattern( "yyyyMMddHHmmssSSS");
LocalDateTime localDateTime = LocalDateTime.parse( input, formatter );
This example works, adding a decimal point (but I find no such requirement in the doc):
String input = "20111203123456.978";
DateTimeFormatter formatter = DateTimeFormatter.ofPattern( "yyyyMMddHHmmss.SSS");
LocalDateTime localDateTime = LocalDateTime.parse( input, formatter );
Renders:
localDateTime: 2011-12-03T12:34:56.978
Omitting the period character from either the input string or the format cause a fail.
Fails:
String input = "20111203123456.978";
DateTimeFormatter formatter = DateTimeFormatter.ofPattern( "yyyyMMddHHmmssSSS");
LocalDateTime localDateTime = LocalDateTime.parse( input, formatter );
Fails:
String input = "20111203123456978";
DateTimeFormatter formatter = DateTimeFormatter.ofPattern( "yyyyMMddHHmmss.SSS");
LocalDateTime localDateTime = LocalDateTime.parse( input, formatter );
Bug – Fixed in Java 9
This issue was already reported in JDK-bug-log. Stephen Colebourne mentions as work-around following solution:
DateTimeFormatter dtf =
new DateTimeFormatterBuilder()
.appendPattern("yyyyMMddHHmmss")
.appendValue(ChronoField.MILLI_OF_SECOND, 3)
.toFormatter();
Note: This workaround does not cover your use-case of only two pattern symbols SS. An adjustment might only be to use other fields like MICRO_OF_SECOND (6 times SSSSSS) or NANO_OF_SECOND (9 times SSSSSSSSS). For two fraction digits see my update below.
#PeterLawrey About the meaning of pattern symbol "S" see this documentation:
Fraction: Outputs the nano-of-second field as a fraction-of-second.
The nano-of-second value has nine digits, thus the count of pattern
letters is from 1 to 9. If it is less than 9, then the nano-of-second
value is truncated, with only the most significant digits being
output. When parsing in strict mode, the number of parsed digits must
match the count of pattern letters. When parsing in lenient mode, the
number of parsed digits must be at least the count of pattern letters,
up to 9 digits.
So we see that S stands for any fraction of second (including nanosecond), not just milliseconds. Furthermore, the fractional part does at the moment not take well in adjacent value parsing, unfortunately.
EDIT:
As background here some remarks about adjacent value parsing. As long as fields are separated by literals like a decimal point or time part separators (colon), the interpretation of fields in a text to be parsed is not difficult because the parser then knows easily when to stop i.e. when the field part is ended and when the next field starts. Therefore the JSR-310 parser can process the text sequence if you specify a decimal point.
But if you have a sequence of adjacent digits spanning over multiple fields then some implementation difficulties arise. In order to let the parser know when a field stops in text it is necessary to instruct the parser in advance that a given field is represented by a fixed-width of digit chars. This works with all appendValue(...)-methods which assume numerical representations.
Unfortunately JSR-310 has not managed well to do this also with the fractional part (appendFraction(...)). If you look for the keyword "adjacent" in the javadoc of class DateTimeFormatterBuilder then you find that this feature is ONLY realized by appendValue(...)-methods. Note that the spec for pattern letter S is slightly different but internally delegates to appendFraction()-method. I assume we will at least have to waint until Java 9 (as reported in JDK-bug-log, or later???) until fraction parts can manage adjacent value parsing as well.
Update from 2015-11-25:
The following code using two fraction digits only does not work and misinterpretes the millisecond part:
DateTimeFormatter dtf =
new DateTimeFormatterBuilder()
.appendPattern("yyyyMMddHHmmss")
.appendValue(ChronoField.MILLI_OF_SECOND, 2)
.toFormatter();
String input = "2011120312345655";
LocalDateTime ldt = LocalDateTime.parse(input, dtf);
System.out.println(ldt); // 2011-12-03T12:34:56.055
The workaround
String input = "2011120312345655";
SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMddHHmmssSS");
Date d = sdf.parse(input);
System.out.println(d.toInstant()); // 2011-12-03T12:34:56.055Z
does not work because SimpleDateFormat interpretes the fraction in a wrong way, too, similar to the modern example (see output, 55 ms instead of 550 ms).
What is left as solution is either waiting an undertermined long time until Java 9 (or later?) or writing your own hack or using 3rd-party libraries as solution.
Solution based on a dirty hack:
String input = "2011120312345655";
DateTimeFormatter dtf = DateTimeFormatter.ofPattern("yyyyMMddHHmmss");
int len = input.length();
LocalDateTime ldt = LocalDateTime.parse(input.substring(0, len - 2), dtf);
int millis = Integer.parseInt(input.substring(len - 2)) * 10;
ldt = ldt.plus(millis, ChronoUnit.MILLIS);
System.out.println(ldt); // 2011-12-03T12:34:56.550
Solution using Joda-Time:
String input = "2011120312345655";
DateTimeFormatter dtf = DateTimeFormat.forPattern("yyyyMMddHHmmssSS");
System.out.println(dtf.parseLocalDateTime(input)); // 2011-12-03T12:34:56.550
Solution using my library Time4J:
String input = "2011120312345655";
ChronoFormatter<PlainTimestamp> f =
ChronoFormatter.ofTimestampPattern("yyyyMMddHHmmssSS", PatternType.CLDR, Locale.ROOT);
System.out.println(f.parse(input)); // 2011-12-03T12:34:56.550
Update from 2016-04-29:
As people can see via the JDK-issue mentioned above, it is now marked as resolved - for Java 9.
DateTimeFormatterBuilder#appendFraction(ChronoField.MILLI_OF_SECOND, 0, 3, true)
Something like this helped me
Here's an algorithm which adjusts the order of the trailing zeros that are conventionally returned from the formatted date String.
/**
* Takes a Date and provides the format whilst compensating for the mistaken representation of sub-second values.
* i.e. 2017-04-03-22:46:19.000991 -> 2017-04-03-22:46:19.991000
* #param pDate Defines the Date object to format.
* #param pPrecision Defines number of valid subsecond characters contained in the system's response.
* */
private static final String subFormat(final Date pDate, final SimpleDateFormat pSimpleDateFormat, final int pPrecision) throws ParseException {
// Format as usual.
final String lString = pSimpleDateFormat.format(pDate);
// Count the number of characters.
final String lPattern = pSimpleDateFormat.toLocalizedPattern();
// Find where the SubSeconds are.
final int lStart = lPattern.indexOf('S');
final int lEnd = lPattern.lastIndexOf('S');
// Ensure they're in the expected format.
for(int i = lStart; i <= lEnd; i++) { if(lPattern.charAt(i) != 'S') {
// Throw an Exception; the date has been provided in the wrong format.
throw new ParseException("Unable to process subseconds in the provided form. (" + lPattern + ").", i);
} }
// Calculate the number of Subseconds. (Account for zero indexing.)
final int lNumSubSeconds = (lEnd - lStart) + 1;
// Fetch the original quantity.
String lReplaceString = lString.substring(lStart + (lNumSubSeconds - pPrecision), lStart + lNumSubSeconds);
// Append trailing zeros.
for(int i = 0; i < lNumSubSeconds - pPrecision; i++) { lReplaceString += "0"; }
// Return the String.
return lString.substring(0, lStart) + lReplaceString;
}

How to parse date-time with two or three milliseconds digits in java?

Here is my method to parse String into LocalDateTime.
public static String formatDate(final String date) {
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.SS");
LocalDateTime formatDateTime = LocalDateTime.parse(date, formatter);
return formatDateTime.atZone(ZoneId.of("UTC")).toOffsetDateTime().toString();
}
but this only works for input String like
2017-11-21 18:11:14.05
but fails for 2017-11-21 18:11:14.057
with DateTimeParseException.
How can I define a formatter that works for both .SS and .SSS?
You would need to build a formatter with a specified fraction
DateTimeFormatter formatter = new DateTimeFormatterBuilder()
.appendPattern("yyyy-MM-dd HH:mm:ss")
.appendFraction(ChronoField.MILLI_OF_SECOND, 2, 3, true) // min 2 max 3
.toFormatter();
LocalDateTime formatDateTime = LocalDateTime.parse(date, formatter);
The answers by Basil Bourque and Sleiman Jneidi are excellent. I just wanted to point out that the answer by EMH333 has a point in it too: the following very simple modification of the code in the question solves your problem.
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.[SSS][SS]");
The square bracket in the format pattern string enclose optional parts, so this accepts 3 or 2 decimals in the fraction of seconds.
Potential advantage over Basil Bourque’s answer: gives better input validation, will object if there is only 1 or there are four decimals on the seconds (whether this is an advantage depends entirely on your situation).
Advantage over Sleiman Jneidi’s answer: You don’t need the builder.
Possible downside: it accepts no decimals at all (as long as the decimal point is there).
As I said, the other solutions are very good too. Which one you prefer is mostly a matter of taste.
tl;dr
No need to define a formatter at all.
LocalDateTime.parse(
"2017-11-21 18:11:14.05".replace( " " , "T" )
)
ISO 8601
The Answer by Sleiman Jneidi is especially clever and high-tech, but there is a simpler way.
Adjust your input string to comply with ISO 8601 format, the format used by default in the java.time classes. So no need to specify a formatting pattern at all. The default formatter can handle any number of decimal digits between zero (whole seconds) and nine (nanoseconds) for the fractional second.
Your input is nearly compliant. Just replace the SPACE in the middle with aT.
String input = "2017-11-21 18:11:14.05".replace( " " , "T" );
LocalDateTime ldt = LocalDateTime.parse( input );
ldt.toString(): 2017-11-21T18:11:14.050
Using Java 8 you can use the DateTimeFormatterBuilder and a Pattern. See this answer for a little more information
public static String formatDate(final String date) {
DateTimeFormatterBuilder dateTimeFormatterBuilder = new DateTimeFormatterBuilder()
.append(DateTimeFormatter.ofPattern("" + "[yyyy-MM-dd HH:mm:ss.SSS]"
+ "[yyyy-MM-dd HH:mm:ss.SS]"));
DateTimeFormatter formatter = dateTimeFormatterBuilder.toFormatter();
try {
LocalDateTime formatDateTime = LocalDateTime.parse(date, formatter);
return formatDateTime.atZone(ZoneId.of("UTC")).toOffsetDateTime().toString();
} catch (DateTimeParseException e) {
return "";
}
}
Ideally, you would account for a time that has 0 nanoseconds as well. If the time so happens to land perfectly on 2021-02-28T12:00:15.000Z, it may actually be serialised to 2021-02-28T12:00:15Z (at least, for something like java.time.OffsetDateTime it would be). It would therefore be more appropriate to use the following:
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss[.SSS][.SS][.S]");
... and if you require time zone, like I did, then it would look this:
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss[.SSS][.SS][.S]z");
DateTimeFormatter allows specifying optional units using square brackets.
Demo:
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.Locale;
import java.util.stream.Stream;
public class Main {
public static void main(String[] args) {
DateTimeFormatter formatter = DateTimeFormatter.ofPattern(
"yyyy-MM-dd HH:mm:ss[.[SSSSSSSSS][SSSSSSSS][SSSSSSS][SSSSSS][SSSSS][SSSS][SSS][SS][S]]",
Locale.ENGLISH);
// Test
Stream.of(
"2015-05-04 12:34:56.123456789",
"2015-05-04 12:34:56.123456",
"2015-05-04 12:34:56.123",
"2015-05-04 12:34:56"
).forEach(s -> System.out.println(LocalDateTime.parse(s, formatter)));
}
}
Output:
2015-05-04T12:34:56.123456789
2015-05-04T12:34:56.123456
2015-05-04T12:34:56.123
2015-05-04T12:34:56
Learn more about the modern date-time API from Trail: Date Time.

Categories

Resources