Java 8 DateTimeFormatter parsing for optional fractional seconds of varying significance
Asked Answered
N

3

30

My MCVE (as a TestNG unit test):

public class MyDateTimeFormatterTest {

    private static final String BASE_PATTERN = "yyyy/MM/dd HH:mm:ss";
    private static final DateTimeFormatter FORMATTER =
            DateTimeFormatter.ofPattern(BASE_PATTERN + "[.SSSSSSSSS]");
    private static final LocalDateTime TEST_INPUT =
            LocalDateTime.of(2015, 5, 4, 12, 34, 56, 123456789);

    @DataProvider(name = "test-cases")
    public Iterator<Object[]> getTestCases() {
        return Arrays.asList(testFor("", ChronoUnit.SECONDS),
                testFor(".SSS", ChronoUnit.MILLIS),
                testFor(".SSSSSS", ChronoUnit.MICROS),
                testFor(".SSSSSSSSS", ChronoUnit.NANOS)).iterator();
    }

    @Test(dataProvider = "test-cases")
    public void testWithDefaultResolution(String input, LocalDateTime output) {
        assertThat(FORMATTER.parse(input, LocalDateTime::from), equalTo(output));
    }

    private Object[] testFor(String patternSuffix, TemporalUnit truncatedTo) {
        return new Object[] { DateTimeFormatter.ofPattern(BASE_PATTERN + patternSuffix)
                .format(TEST_INPUT), TEST_INPUT.truncatedTo(truncatedTo) };
    }
}

I am trying to test the parsing of a date-time String with optional fractional seconds of varying significance using DateTimeFormatter. The relevant part of the Javadoc reads:

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.

Based on my limited understanding, I used [...] to mark the fractional seconds as optional, and since I'm interested in varying significance, I thought I should stick to SSSSSSSSS.

However, the unit test fails at parsing up to milliseconds and microseconds, i.e. the second and third cases. Changing the ResolverStyle to LENIENT does not help here as it fails at the parsing stage, not the resolution.

May I know which approaches should I consider to resolve my problem? Should I be using DateTimeFormatterBuilder to optionally specify each fractional digit (9 times), or is there a 'smarter' way with my pattern?

edit I found my own answer in the end... will still leave this as unanswered for a day and see if there are other approaches or not.

Notochord answered 7/5, 2015 at 2:3 Comment(2)
Did you also see this question: #30103667 ? Looks like your answer is essentially the sameBelabor
@Belabor thanks for pointing that out... I've updated my own answer below to drop parseLenient() too, for future references...Notochord
N
48

Oh cool, another 15 minutes of troubleshooting yielded this:

private static final DateTimeFormatter FORMATTER = 
    new DateTimeFormatterBuilder().appendPattern(BASE_PATTERN) // .parseLenient()
        .appendFraction(ChronoField.NANO_OF_SECOND, 0, 9, true).toFormatter();

edit parseLenient() is optional.

Notochord answered 7/5, 2015 at 2:35 Comment(0)
S
3

You were close to the solution

Your answer is definitely an elegant way to solve this problem. However, what you attempted first was also close to the solution. You just had to specify the remaining numbers of fractional parts too while specifying the optional pattern.

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.

Sheen answered 15/10, 2022 at 18:21 Comment(0)
C
0

I tried the below pattern and it helped me parse the dates without milliseconds and with milliseconds(up to 3 decimal places)

final static DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss[.*][.SSS][.SS][.S]X");

e.g. 2023-10-25T10:22:28.280Z 2023-10-25T10:22:28.28Z 2023-10-25T10:22:28.2Z 2023-10-25T10:22:28Z

Cutoff answered 25/10, 2023 at 13:21 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.