@@ -63,7 +63,7 @@ private static InstantFormatter createFormatter(@Nullable final String[] options
6363 logOptionReadFailure (options , error , "failed for options: {}, falling back to the default instance" );
6464 }
6565 return InstantPatternFormatter .newBuilder ()
66- .setPattern (NamedDatePattern .DEFAULT .getPattern ())
66+ .setPattern (NamedInstantPattern .DEFAULT .getPattern ())
6767 .build ();
6868 }
6969
@@ -94,7 +94,7 @@ private static InstantFormatter createFormatterUnsafely(@Nullable final String[]
9494 private static String readPattern (@ Nullable final String [] options ) {
9595 return options != null && options .length > 0 && options [0 ] != null
9696 ? decodeNamedPattern (options [0 ])
97- : NamedDatePattern .DEFAULT .getPattern ();
97+ : NamedInstantPattern .DEFAULT .getPattern ();
9898 }
9999
100100 /**
@@ -109,8 +109,40 @@ private static String readPattern(@Nullable final String[] options) {
109109 * @since 2.25.0
110110 */
111111 static String decodeNamedPattern (final String pattern ) {
112+ // If legacy formatters are enabled, we need to produce output aimed for `FixedDateFormat` and `FastDateFormat`.
113+ // Otherwise, we need to produce output aimed for `DateTimeFormatter`.
114+ // In conclusion, we need to check if legacy formatters enabled and apply following transformations.
115+ //
116+ // | Microseconds | Nanoseconds | Time-zone
117+ // ------------------------------+--------------+-------------+-----------
118+ // Legacy formatter directive | nnnnnn | nnnnnnnnn | X, XX, XXX
119+ // `DateTimeFormatter` directive | SSSSSS | SSSSSSSSS | x, xx, xxx
120+ //
121+ // Enabling legacy formatters mean that user requests the pattern to be formatted using deprecated
122+ // `FixedDateFormat` and `FastDateFormat`.
123+ // These two have, let's not say _bogus_, but an _interesting_ way of handling certain pattern directives:
124+ //
125+ // - They say they adhere to `SimpleDateFormat` specification, but use `n` directive.
126+ // `n` is neither defined by `SimpleDateFormat`, nor `SimpleDateFormat` supports sub-millisecond precisions.
127+ // `n` is probably manually introduced by Log4j to support sub-millisecond precisions.
128+ //
129+ // - `n` denotes nano-of-second for `DateTimeFormatter`.
130+ // In Java 17, `n` and `N` (nano-of-day) always output nanosecond precision.
131+ // This is independent of how many times they occur consequently.
132+ // Yet legacy formatters use repeated `n` to denote sub-milliseconds precision of certain length.
133+ // This doesn't work for `DateTimeFormatter`, which needs
134+ //
135+ // - `SSSSSS` for 6-digit microsecond precision
136+ // - `SSSSSSSSS` for 9-digit nanosecond precision
137+ //
138+ // - Legacy formatters use `X`, `XX,` and `XXX` to choose between `+00`, `+0000`, or `+00:00`.
139+ // This is the correct behaviour for `SimpleDateFormat`.
140+ // Though `X` in `DateTimeFormatter` produces `Z` for zero-offset.
141+ // To avoid the `Z` output, one needs to use `x` with `DateTimeFormatter`.
112142 try {
113- return NamedDatePattern .valueOf (pattern ).getPattern ();
143+ return InstantPatternFormatter .LEGACY_FORMATTERS_ENABLED
144+ ? FixedDateFormat .FixedFormat .valueOf (pattern ).getPattern ()
145+ : NamedInstantPattern .valueOf (pattern ).getPattern ();
114146 } catch (IllegalArgumentException ignored ) {
115147 return pattern ;
116148 }
0 commit comments