Search code examples
datetimejava-8zoneddatetimedatetimeformatter

Database DateTime milli and nano seconds are truncated by default if they are 0s, while using it in Java 11 using ZonedDateTime


I am fetching datetime from an Oracle database and parsing in Java 11 using ZonedDateTime as below:

  1. Oracle --> 1/19/2020 06:09:46.038631 PM

    Java ZonedDateTime output --> 2020-01-19T18:09:46.038631Z[UTC]

  2. Oracle --> 1/19/2011 4:00:00.000000 AM

    Java ZonedDateTime output --> 2011-01-19T04:00Z[UTC] (So, here the 0s are truncated by default. However, my requirement is to have consistent fixed length output like #1.)

Expected Java ZonedDateTime output --> 2011-01-19T04:00:00.000000Z[UTC]

However, I didn’t find any date API methods to achieve above expected output. Instead of manipulating a string, is there a way to preserve the trailing 0s with fixed length?

We have consistent ZonedDateTime types in the application, so we do not prefer to change that.


Solution

  • We have consistent ZonedDateTime type in application, so we do not prefer to change that.

    Why do you think 2011-01-19T04:00Z[UTC] is inconsistent? A date-time object is supposed to hold (and provide methods/functions to operate with) only the date, time, and time-zone information. It is not supposed to store any formatting information; otherwise, it will violate the Single-responsibility principle. The formatting should be handled by a formating class e.g. DateTimeFormatter (for modern date-time API), DateFormat (for legacy java.util date-time API) etc.

    Every class is supposed to override the toString() function; otherwise, Object#toString will be returned when its object will be printed. A ZonedDateTime has date, time and time-zone information. Given below is how its toString() for time-part has been implemented:

    @Override
    public String toString() {
        StringBuilder buf = new StringBuilder(18);
        int hourValue = hour;
        int minuteValue = minute;
        int secondValue = second;
        int nanoValue = nano;
        buf.append(hourValue < 10 ? "0" : "").append(hourValue)
            .append(minuteValue < 10 ? ":0" : ":").append(minuteValue);
        if (secondValue > 0 || nanoValue > 0) {
            buf.append(secondValue < 10 ? ":0" : ":").append(secondValue);
            if (nanoValue > 0) {
                buf.append('.');
                if (nanoValue % 1000_000 == 0) {
                    buf.append(Integer.toString((nanoValue / 1000_000) + 1000).substring(1));
                } else if (nanoValue % 1000 == 0) {
                    buf.append(Integer.toString((nanoValue / 1000) + 1000_000).substring(1));
                } else {
                    buf.append(Integer.toString((nanoValue) + 1000_000_000).substring(1));
                }
            }
        }
        return buf.toString();
    }
    

    As you can see, the second and nano parts are included in the returned string only when they are greater than 0. It means that you need to use a formatting class if you want these (second and nano) zeros in the output string. Given below is an example:

        import java.time.LocalDateTime;
        import java.time.ZoneOffset;
        import java.time.ZonedDateTime;
        import java.time.format.DateTimeFormatter;
        import java.time.format.DateTimeFormatterBuilder;
        import java.util.Locale;
    
        public class Main {
            public static void main(String[] args) {
                String input = "1/19/2011 4:00:00.000000 AM";
    
                // Formatter for input string
                DateTimeFormatter inputFormatter = new DateTimeFormatterBuilder()
                                                    .parseCaseInsensitive()
                                                    .appendPattern("M/d/u H:m:s.n a")
                                                    .toFormatter(Locale.ENGLISH);
    
                ZonedDateTime zdt = LocalDateTime.parse(input, inputFormatter).atZone(ZoneOffset.UTC);
    
                // Print `zdt` in default format i.e. the string returned by `zdt.toString()`
                System.out.println(zdt);
    
                // Formatter for input string
                DateTimeFormatter outputFormatter = DateTimeFormatter.ofPattern("uuuu-MM-dd'T'HH:mm:ss.nnnnnnz");
                String output = zdt.format(outputFormatter);
                System.out.println(output);
            }
        }
    

    Output:

    2011-01-19T04:00Z
    2011-01-19T04:00:00.000000Z
    

    Food for thought:

    public class Main {
        public static void main(String[] args) {
            double d = 5.0000;
            System.out.println(d);
        }
    }
    

    What output do you expect from the code given above? Does 5.0 represent a value different from 5.0000? How will you print 5.0000? [Hint: Check String#format, NumberFormat, BigDecimal etc.]