1616 */
1717package org .apache .logging .log4j .core .pattern ;
1818
19+ import java .util .regex .Pattern ;
1920import org .jspecify .annotations .NullMarked ;
2021
2122/**
2829public enum NamedInstantPattern {
2930 ABSOLUTE ("HH:mm:ss,SSS" ),
3031
31- ABSOLUTE_MICROS ("HH:mm:ss,SSSSSS" , "HH:mm:ss,nnnnnn" ),
32+ ABSOLUTE_MICROS ("HH:mm:ss,SSSSSS" ),
3233
33- ABSOLUTE_NANOS ("HH:mm:ss,SSSSSSSSS" , "HH:mm:ss,nnnnnnnnn" ),
34+ ABSOLUTE_NANOS ("HH:mm:ss,SSSSSSSSS" ),
3435
3536 ABSOLUTE_PERIOD ("HH:mm:ss.SSS" ),
3637
@@ -42,9 +43,9 @@ public enum NamedInstantPattern {
4243
4344 DEFAULT ("yyyy-MM-dd HH:mm:ss,SSS" ),
4445
45- DEFAULT_MICROS ("yyyy-MM-dd HH:mm:ss,SSSSSS" , "yyyy-MM-dd HH:mm:ss,nnnnnn" ),
46+ DEFAULT_MICROS ("yyyy-MM-dd HH:mm:ss,SSSSSS" ),
4647
47- DEFAULT_NANOS ("yyyy-MM-dd HH:mm:ss,SSSSSSSSS" , "yyyy-MM-dd HH:mm:ss,nnnnnnnnn" ),
48+ DEFAULT_NANOS ("yyyy-MM-dd HH:mm:ss,SSSSSSSSS" ),
4849
4950 DEFAULT_PERIOD ("yyyy-MM-dd HH:mm:ss.SSS" ),
5051
@@ -54,30 +55,26 @@ public enum NamedInstantPattern {
5455
5556 ISO8601 ("yyyy-MM-dd'T'HH:mm:ss,SSS" ),
5657
57- ISO8601_OFFSET_DATE_TIME_HH ("yyyy-MM-dd'T'HH:mm:ss,SSSx" , "yyyy-MM-dd'T'HH:mm:ss,SSSX" ),
58+ ISO8601_OFFSET_DATE_TIME_HH ("yyyy-MM-dd'T'HH:mm:ss,SSSx" ),
5859
59- ISO8601_OFFSET_DATE_TIME_HHMM ("yyyy-MM-dd'T'HH:mm:ss,SSSxx" , "yyyy-MM-dd'T'HH:mm:ss,SSSXX" ),
60+ ISO8601_OFFSET_DATE_TIME_HHMM ("yyyy-MM-dd'T'HH:mm:ss,SSSxx" ),
6061
61- ISO8601_OFFSET_DATE_TIME_HHCMM ("yyyy-MM-dd'T'HH:mm:ss,SSSxxx" , "yyyy-MM-dd'T'HH:mm:ss,SSSXXX" ),
62+ ISO8601_OFFSET_DATE_TIME_HHCMM ("yyyy-MM-dd'T'HH:mm:ss,SSSxxx" ),
6263
6364 ISO8601_PERIOD ("yyyy-MM-dd'T'HH:mm:ss.SSS" ),
6465
65- ISO8601_PERIOD_MICROS ("yyyy-MM-dd'T'HH:mm:ss.SSSSSS" , "yyyy-MM-dd'T'HH:mm:ss.nnnnnn" ),
66+ ISO8601_PERIOD_MICROS ("yyyy-MM-dd'T'HH:mm:ss.SSSSSS" ),
6667
6768 US_MONTH_DAY_YEAR2_TIME ("dd/MM/yy HH:mm:ss.SSS" ),
6869
6970 US_MONTH_DAY_YEAR4_TIME ("dd/MM/yyyy HH:mm:ss.SSS" );
7071
72+ private static final Pattern NANO_PATTERN = Pattern .compile ("S{4,}" );
73+
7174 private final String pattern ;
72- private final String legacyPattern ;
7375
7476 NamedInstantPattern (String pattern ) {
75- this (pattern , pattern );
76- }
77-
78- NamedInstantPattern (String pattern , String legacyPattern ) {
7977 this .pattern = pattern ;
80- this .legacyPattern = legacyPattern ;
8178 }
8279
8380 /**
@@ -93,11 +90,74 @@ public String getPattern() {
9390 /**
9491 * Returns the legacy {@link org.apache.logging.log4j.core.util.datetime.FixedDateFormat} pattern
9592 * associated with this named pattern.
93+ * <p>
94+ * If legacy formatters are enabled, output is produced for
95+ * {@code FixedDateFormat} and {@code FastDateFormat}. To convert the {@code DateTimeFormatter}
96+ * to its legacy counterpart, the following transformations need to be applied:
97+ * </p>
98+ * <table>
99+ * <caption>Pattern Differences</caption>
100+ * <thead>
101+ * <tr>
102+ * <th></th>
103+ * <th>Microseconds</th>
104+ * <th>Nanoseconds</th>
105+ * <th>Time-zone</th>
106+ * </tr>
107+ * </thead>
108+ * <tbody>
109+ * <tr>
110+ * <td>Legacy formatter directive</td>
111+ * <td><code>nnnnnn</code></td>
112+ * <td><code>nnnnnnnnn</code></td>
113+ * <td><code>X</code>, <code>XX</code>, <code>XXX</code></td>
114+ * </tr>
115+ * <tr>
116+ * <td>{@code DateTimeFormatter} directive</td>
117+ * <td><code>SSSSSS</code></td>
118+ * <td><code>SSSSSSSSS</code></td>
119+ * <td><code>x</code>, <code>xx</code>, <code>xxx</code></td>
120+ * </tr>
121+ * </tbody>
122+ * </table>
123+ * <h2></h2>
124+ * <ul>
125+ * <li>
126+ * <p>
127+ * Legacy formatters are largely compatible with the {@code SimpleDateFormat} specification,
128+ * but introduce a custom {@code n} pattern letter, unique to Log4j, to represent sub-millisecond precision.
129+ * This {@code n} is not part of the standard {@code SimpleDateFormat}.
130+ * </p>
131+ * <p>
132+ * In legacy formatters, repeating {@code n} increases the precision, similar to how repeated {@code S}
133+ * is used for fractional seconds in {@code DateTimeFormatter}.
134+ * </p>
135+ * <p>
136+ * In contrast, {@code DateTimeFormatter} interprets {@code n} as nano-of-second.
137+ * In Java 17, both {@code n} and {@code N} always output nanosecond precision,
138+ * regardless of the number of pattern letters.
139+ * </p>
140+ * </li>
141+ * <li>
142+ * <p>
143+ * Legacy formatters use <code>X</code>, <code>XX</code>, and <code>XXX</code> to format time zones as
144+ * <code>+00</code>, <code>+0000</code>, or <code>+00:00</code>, following {@code SimpleDateFormat} conventions.
145+ * In contrast, {@code DateTimeFormatter} outputs <code>Z</code> for zero-offset when using <code>X</code>.
146+ * To ensure numeric output for zero-offset (e.g., <code>+00</code>),
147+ * we use <code>x</code>, <code>xx</code>, or <code>xxx</code> instead.
148+ * </p>
149+ * </li>
150+ * </ul>
96151 *
97152 * @return the legacy pattern string as used in
98153 * {@link org.apache.logging.log4j.core.util.datetime.FixedDateFormat.FixedFormat}
99154 */
100155 String getLegacyPattern () {
156+ String legacyPattern = pattern .replace ('x' , 'X' );
157+ if (NANO_PATTERN .matcher (pattern ).find ()) {
158+ // If the pattern contains sub-millis, replace 'S' with 'n' for legacy formatters
159+ return legacyPattern .replace ('S' , 'n' );
160+ }
101161 return legacyPattern ;
102162 }
103163}
0 commit comments