Search code examples
javajava-timedatetimeformatteroffsetdatetime

Java OffsetDateTime.parse() adds extra zeros to time


This following codes return an OffsetDateTime like this 2021-06-30T23:59:59.009966667Z, with 2 extra zeros added. I have 7 n's in the formatter, but it still returns 9 digits. Why?

import java.time.OffsetDateTime;
import java.time.format.DateTimeFormatter;

public class HelloWorld{

     public static void main(String []args){
         DateTimeFormatter MAIN_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.nnnnnnn XXX");
         String dbData = "2021-06-30 23:59:59.9966667 +00:00";
         OffsetDateTime time = OffsetDateTime.parse(dbData, MAIN_FORMATTER);
         System.out.println(time.toString());
     }
}

Solution

  • This following codes return an OffsetDateTime like this 2021-06-30T23:59:59.009966667Z, with 2 extra zeros added. I have 7 n's in the formatter, but it still returns 9 digits. Why?

    You will get the same result even for a single n or any number of ns less than or equal to 7. However, if you exceed 7, you will get an exception something like

    Exception in thread "main" java.time.format.DateTimeParseException:
                   Text '2021-06-30 23:59:59.9966667 +00:00' could not be parsed at index 20
    

    The reason for this is DateTimeFormatter counts the number of n in the pattern and if it is less than or equal to the number of digits after ., it will use additional n to compensate that but if the number of ns exceed the number of digits, it won't have any clue what that additional ns are for.

    It's not just for n but for most (if not all) of the symbols. You can understand it from the following example:

    import java.time.LocalDate;
    import java.time.format.DateTimeFormatter;
    import java.util.Locale;
    
    public class Main {
        public static void main(String[] args) {
            String strDate1 = "2020/2/2";
            String strDate2 = "2020/02/02";
    
            // Notice the single M and d
            DateTimeFormatter dtfCorrectForBothDateStrings = DateTimeFormatter.ofPattern("uuuu/M/d", Locale.ENGLISH);
            System.out.println(LocalDate.parse(strDate2, dtfCorrectForBothDateStrings));
            System.out.println(LocalDate.parse(strDate1, dtfCorrectForBothDateStrings));
    
            // Notice the two M's and d's
            DateTimeFormatter dtfCorrectOnlyForSecondDateString = DateTimeFormatter.ofPattern("uuuu/MM/dd", Locale.ENGLISH);
            System.out.println(LocalDate.parse(strDate2, dtfCorrectOnlyForSecondDateString));
            System.out.println(LocalDate.parse(strDate1, dtfCorrectOnlyForSecondDateString));
        }
    }
    

    Output:

    2020-02-02
    2020-02-02
    2020-02-02
    Exception in thread "main" java.time.format.DateTimeParseException: Text '2020/2/2' could not be parsed at index 5
        at java.base/java.time.format.DateTimeFormatter.parseResolved0(DateTimeFormatter.java:2051)
        at java.base/java.time.format.DateTimeFormatter.parse(DateTimeFormatter.java:1953)
        at java.base/java.time.LocalDate.parse(LocalDate.java:429)
        at Main.main(Main.java:16)
    

    Why does it give me .009966667 and not .9966667?

    The symbol, n stands for nano-of-second and 9966667 nanoseconds are equal to 0.009966667 second.

    public class Main {
        public static void main(String[] args) {
            int nanos = 9966667;
            System.out.println(nanos + " nanoseconds = " + ((double) nanos / 1_000_000_000) + " second");
        }
    }
    

    Output:

    9966667 nanoseconds = 0.009966667 second
    

    How does it work with S?

    S stands for fraction-of-second and .9966667 is parsed as 0.9966667 second.

    A note on OffsetDateTime#toString implementation:

    The OffsetDateTime#toString groups the digits of fraction-of-second in the nearest multiple of 3 (i.e. milli, micro and nano) e.g.

    • for .99, the output will be .990, and
    • for .996, the output will be .996, and
    • for .9966, the output will be .996600, and
    • for .9966667, the output will be.996666700.

    Given below is an excerpt from OffsetDateTime#toString:

    The output will be one of the following ISO-8601 formats:

    • uuuu-MM-dd'T'HH:mmXXXXX
    • uuuu-MM-dd'T'HH:mm:ssXXXXX
    • uuuu-MM-dd'T'HH:mm:ss.SSSXXXXX
    • uuuu-MM-dd'T'HH:mm:ss.SSSSSSXXXXX
    • uuuu-MM-dd'T'HH:mm:ss.SSSSSSSSSXXXXX

    The final demo (incorporating all that have been discussed above):

    import java.time.OffsetDateTime;
    import java.time.format.DateTimeFormatter;
    import java.util.Locale;
    
    public class Main {
        public static void main(String[] args) {
            String strDateTime = "2021-06-30 23:59:59.9966667 +00:00";
    
            System.out.println(OffsetDateTime.parse(strDateTime,
                    DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.nnnnnnn XXX", Locale.ENGLISH)));
    
            System.out.println(OffsetDateTime.parse(strDateTime,
                    DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.SSSSSSS XXX", Locale.ENGLISH)));
        }
    }
    

    Output:

    2021-06-30T23:59:59.009966667Z
    2021-06-30T23:59:59.996666700Z