3232import java .util .stream .IntStream ;
3333import java .util .stream .Stream ;
3434import org .apache .logging .log4j .core .time .MutableInstant ;
35- import org .apache .logging .log4j .core .util .internal .instant .InstantPatternDynamicFormatter .CompositePatternSequence ;
3635import org .apache .logging .log4j .core .util .internal .instant .InstantPatternDynamicFormatter .DynamicPatternSequence ;
3736import org .apache .logging .log4j .core .util .internal .instant .InstantPatternDynamicFormatter .PatternSequence ;
37+ import org .apache .logging .log4j .core .util .internal .instant .InstantPatternDynamicFormatter .SecondPatternSequence ;
3838import org .apache .logging .log4j .core .util .internal .instant .InstantPatternDynamicFormatter .StaticPatternSequence ;
3939import org .apache .logging .log4j .util .Constants ;
4040import org .junit .jupiter .params .ParameterizedTest ;
@@ -55,104 +55,74 @@ void sequencing_should_work(
5555 static List <Arguments > sequencingTestCases () {
5656 final List <Arguments > testCases = new ArrayList <>();
5757
58+ // Merged constants
59+ testCases .add (Arguments .of (":'foo'," , ChronoUnit .DAYS , singletonList (new StaticPatternSequence (":foo," ))));
60+
5861 // `SSSX` should be treated constant for daily updates
59- testCases .add (Arguments .of ("SSSX" , ChronoUnit .DAYS , singletonList ( pCom ( pDyn ( "SSS" ), pDyn ("X" ) ))));
62+ testCases .add (Arguments .of ("SSSX" , ChronoUnit .DAYS , asList ( pMilliSec ( ), pDyn ("X" ))));
6063
6164 // `yyyyMMddHHmmssSSSX` instant cache updated hourly
6265 testCases .add (Arguments .of (
6366 "yyyyMMddHHmmssSSSX" ,
6467 ChronoUnit .HOURS ,
65- asList (
66- pCom (pDyn ("yyyy" ), pDyn ("MM" ), pDyn ("dd" ), pDyn ("HH" )),
67- pCom (pDyn ("mm" ), pDyn ("ss" ), pDyn ("SSS" )),
68- pDyn ("X" ))));
68+ asList (pDyn ("yyyyMMddHH" , ChronoUnit .HOURS ), pDyn ("mm" ), pSec ("" , 3 ), pDyn ("X" ))));
6969
7070 // `yyyyMMddHHmmssSSSX` instant cache updated per minute
7171 testCases .add (Arguments .of (
7272 "yyyyMMddHHmmssSSSX" ,
7373 ChronoUnit .MINUTES ,
74- asList (
75- pCom (pDyn ("yyyy" ), pDyn ("MM" ), pDyn ("dd" ), pDyn ("HH" ), pDyn ("mm" )),
76- pCom (pDyn ("ss" ), pDyn ("SSS" )),
77- pDyn ("X" ))));
74+ asList (pDyn ("yyyyMMddHHmm" , ChronoUnit .MINUTES ), pSec ("" , 3 ), pDyn ("X" ))));
7875
7976 // ISO9601 instant cache updated daily
8077 final String iso8601InstantPattern = "yyyy-MM-dd'T'HH:mm:ss.SSSX" ;
8178 testCases .add (Arguments .of (
8279 iso8601InstantPattern ,
8380 ChronoUnit .DAYS ,
8481 asList (
85- pCom (pDyn ("yyyy" ), pSta ("-" ), pDyn ("MM" ), pSta ("-" ), pDyn ("dd" ), pSta ("T" )),
86- pCom (
87- pDyn ("HH" ),
88- pSta (":" ),
89- pDyn ("mm" ),
90- pSta (":" ),
91- pDyn ("ss" ),
92- pSta ("." ),
93- pDyn ("SSS" ),
94- pDyn ("X" )))));
82+ pDyn ("yyyy'-'MM'-'dd'T'" , ChronoUnit .DAYS ),
83+ pDyn ("HH':'mm':'" , ChronoUnit .MINUTES ),
84+ pSec ("." , 3 ),
85+ pDyn ("X" ))));
9586
9687 // ISO9601 instant cache updated per minute
9788 testCases .add (Arguments .of (
9889 iso8601InstantPattern ,
9990 ChronoUnit .MINUTES ,
100- asList (
101- pCom (
102- pDyn ("yyyy" ),
103- pSta ("-" ),
104- pDyn ("MM" ),
105- pSta ("-" ),
106- pDyn ("dd" ),
107- pSta ("T" ),
108- pDyn ("HH" ),
109- pSta (":" ),
110- pDyn ("mm" ),
111- pSta (":" )),
112- pCom (pDyn ("ss" ), pSta ("." ), pDyn ("SSS" )),
113- pDyn ("X" ))));
91+ asList (pDyn ("yyyy'-'MM'-'dd'T'HH':'mm':'" , ChronoUnit .MINUTES ), pSec ("." , 3 ), pDyn ("X" ))));
11492
11593 // ISO9601 instant cache updated per second
11694 testCases .add (Arguments .of (
11795 iso8601InstantPattern ,
11896 ChronoUnit .SECONDS ,
119- asList (
120- pCom (
121- pDyn ("yyyy" ),
122- pSta ("-" ),
123- pDyn ("MM" ),
124- pSta ("-" ),
125- pDyn ("dd" ),
126- pSta ("T" ),
127- pDyn ("HH" ),
128- pSta (":" ),
129- pDyn ("mm" ),
130- pSta (":" ),
131- pDyn ("ss" ),
132- pSta ("." )),
133- pDyn ("SSS" ),
134- pDyn ("X" ))));
97+ asList (pDyn ("yyyy'-'MM'-'dd'T'HH':'mm':'" , ChronoUnit .MINUTES ), pSec ("." , 3 ), pDyn ("X" ))));
98+
99+ // Seconds and micros
100+ testCases .add (Arguments .of (
101+ "HH:mm:ss.SSSSSS" , ChronoUnit .MINUTES , asList (pDyn ("HH':'mm':'" , ChronoUnit .MINUTES ), pSec ("." , 6 ))));
135102
136103 return testCases ;
137104 }
138105
139- private static CompositePatternSequence pCom (final PatternSequence ... sequences ) {
140- return new CompositePatternSequence (asList (sequences ));
106+ private static DynamicPatternSequence pDyn (final String singlePattern ) {
107+ return new DynamicPatternSequence (singlePattern );
108+ }
109+
110+ private static DynamicPatternSequence pDyn (final String pattern , final ChronoUnit precision ) {
111+ return new DynamicPatternSequence (pattern , precision );
141112 }
142113
143- private static DynamicPatternSequence pDyn ( final String pattern ) {
144- return new DynamicPatternSequence ( pattern );
114+ private static SecondPatternSequence pSec ( String separator , int fractionalDigits ) {
115+ return new SecondPatternSequence ( true , separator , fractionalDigits );
145116 }
146117
147- private static StaticPatternSequence pSta ( final String literal ) {
148- return new StaticPatternSequence ( literal );
118+ private static SecondPatternSequence pMilliSec ( ) {
119+ return new SecondPatternSequence ( false , "" , 3 );
149120 }
150121
151122 @ ParameterizedTest
152123 @ ValueSource (
153124 strings = {
154125 // Basics
155- "S" ,
156126 "SSSSSSS" ,
157127 "SSSSSSSSS" ,
158128 "n" ,
@@ -163,8 +133,7 @@ private static StaticPatternSequence pSta(final String literal) {
163133 "yyyy-MM-dd HH:mm:ss,SSSSSSS" ,
164134 "yyyy-MM-dd HH:mm:ss,SSSSSSSS" ,
165135 "yyyy-MM-dd HH:mm:ss,SSSSSSSSS" ,
166- "yyyy-MM-dd'T'HH:mm:ss.SSSSSSSSS" ,
167- "yyyy-MM-dd'T'HH:mm:ss.SXXX"
136+ "yyyy-MM-dd'T'HH:mm:ss.SSSSSSSSS"
168137 })
169138 void should_recognize_patterns_of_nano_precision (final String pattern ) {
170139 assertPatternPrecision (pattern , ChronoUnit .NANOS );
@@ -233,20 +202,31 @@ void should_recognize_patterns_of_second_precision(final String pattern) {
233202 assertPatternPrecision (pattern , ChronoUnit .SECONDS );
234203 }
235204
236- @ ParameterizedTest
237- @ ValueSource (
238- strings = {
205+ static Stream <String > should_recognize_patterns_of_minute_precision () {
206+ Stream <String > stream = Stream .of (
239207 // Basics
240208 "m" ,
241209 "mm" ,
210+ "Z" ,
211+ "x" ,
212+ "X" ,
213+ "O" ,
214+ "z" ,
215+ "VV" ,
242216 // Mixed with other stuff
243217 "yyyy-MM-dd HH:mm" ,
244218 "yyyy-MM-dd'T'HH:mm" ,
245219 "HH:mm" ,
220+ "yyyy-MM-dd HH x" ,
221+ "yyyy-MM-dd'T'HH XX" ,
246222 // Single-quoted text containing nanosecond and millisecond directives
247223 "yyyy-MM-dd'S'HH:mm" ,
248- "yyyy-MM-dd'n'HH:mm"
249- })
224+ "yyyy-MM-dd'n'HH:mm" );
225+ return Constants .JAVA_MAJOR_VERSION > 8 ? Stream .concat (stream , Stream .of ("v" )) : stream ;
226+ }
227+
228+ @ ParameterizedTest
229+ @ MethodSource
250230 void should_recognize_patterns_of_minute_precision (final String pattern ) {
251231 assertPatternPrecision (pattern , ChronoUnit .MINUTES );
252232 }
@@ -267,28 +247,71 @@ static List<String> hourPrecisionPatterns() {
267247 "K" ,
268248 "k" ,
269249 "H" ,
270- "Z" ,
271- "x" ,
272- "X" ,
273- "O" ,
274- "z" ,
275- "VV" ,
276250 // Mixed with other stuff
277251 "yyyy-MM-dd HH" ,
278252 "yyyy-MM-dd'T'HH" ,
279- "yyyy-MM-dd HH x" ,
280- "yyyy-MM-dd'T'HH XX" ,
281253 "ddHH" ,
282254 // Single-quoted text containing nanosecond and millisecond directives
283255 "yyyy-MM-dd'S'HH" ,
284256 "yyyy-MM-dd'n'HH" ));
285257 if (Constants .JAVA_MAJOR_VERSION > 8 ) {
286258 java8Patterns .add ("B" );
287- java8Patterns .add ("v" );
288259 }
289260 return java8Patterns ;
290261 }
291262
263+ static Stream <Arguments > dynamic_pattern_should_correctly_determine_precision () {
264+ // When no a precise unit is not available, uses the closest smaller unit.
265+ return Stream .of (
266+ Arguments .of ("G" , ChronoUnit .ERAS ),
267+ Arguments .of ("u" , ChronoUnit .YEARS ),
268+ Arguments .of ("D" , ChronoUnit .DAYS ),
269+ Arguments .of ("M" , ChronoUnit .MONTHS ),
270+ Arguments .of ("L" , ChronoUnit .MONTHS ),
271+ Arguments .of ("d" , ChronoUnit .DAYS ),
272+ Arguments .of ("Q" , ChronoUnit .MONTHS ),
273+ Arguments .of ("q" , ChronoUnit .MONTHS ),
274+ Arguments .of ("Y" , ChronoUnit .YEARS ),
275+ Arguments .of ("w" , ChronoUnit .WEEKS ),
276+ Arguments .of ("W" , ChronoUnit .DAYS ), // The month can change in the middle of the week
277+ Arguments .of ("F" , ChronoUnit .DAYS ), // The month can change in the middle of the week
278+ Arguments .of ("E" , ChronoUnit .DAYS ),
279+ Arguments .of ("e" , ChronoUnit .DAYS ),
280+ Arguments .of ("c" , ChronoUnit .DAYS ),
281+ Arguments .of ("a" , ChronoUnit .HOURS ), // Let us round it down
282+ Arguments .of ("h" , ChronoUnit .HOURS ),
283+ Arguments .of ("K" , ChronoUnit .HOURS ),
284+ Arguments .of ("k" , ChronoUnit .HOURS ),
285+ Arguments .of ("H" , ChronoUnit .HOURS ),
286+ Arguments .of ("m" , ChronoUnit .MINUTES ),
287+ Arguments .of ("s" , ChronoUnit .SECONDS ),
288+ Arguments .of ("S" , ChronoUnit .MILLIS ),
289+ Arguments .of ("SS" , ChronoUnit .MILLIS ),
290+ Arguments .of ("SSS" , ChronoUnit .MILLIS ),
291+ Arguments .of ("SSSS" , ChronoUnit .MICROS ),
292+ Arguments .of ("SSSSS" , ChronoUnit .MICROS ),
293+ Arguments .of ("SSSSSS" , ChronoUnit .MICROS ),
294+ Arguments .of ("SSSSSSS" , ChronoUnit .NANOS ),
295+ Arguments .of ("SSSSSSSS" , ChronoUnit .NANOS ),
296+ Arguments .of ("SSSSSSSSS" , ChronoUnit .NANOS ),
297+ Arguments .of ("A" , ChronoUnit .MILLIS ),
298+ Arguments .of ("n" , ChronoUnit .NANOS ),
299+ Arguments .of ("N" , ChronoUnit .NANOS ),
300+ // Time zones can change in the middle of a UTC hour (e.g. India)
301+ Arguments .of ("VV" , ChronoUnit .MINUTES ),
302+ Arguments .of ("z" , ChronoUnit .MINUTES ),
303+ Arguments .of ("O" , ChronoUnit .MINUTES ),
304+ Arguments .of ("X" , ChronoUnit .MINUTES ),
305+ Arguments .of ("x" , ChronoUnit .MINUTES ),
306+ Arguments .of ("Z" , ChronoUnit .MINUTES ));
307+ }
308+
309+ @ ParameterizedTest
310+ @ MethodSource
311+ void dynamic_pattern_should_correctly_determine_precision (String singlePattern , ChronoUnit expectedPrecision ) {
312+ assertThat (pDyn (singlePattern ).precision ).isEqualTo (expectedPrecision );
313+ }
314+
292315 private static void assertPatternPrecision (final String pattern , final ChronoUnit expectedPrecision ) {
293316 final InstantPatternFormatter formatter =
294317 new InstantPatternDynamicFormatter (pattern , Locale .getDefault (), TimeZone .getDefault ());
@@ -351,7 +374,11 @@ static Stream<Arguments> formatterInputs(final String pattern) {
351374
352375 private static MutableInstant randomInstant () {
353376 final MutableInstant instant = new MutableInstant ();
354- final long epochSecond = RANDOM .nextInt (1_621_280_470 ); // 2021-05-17 21:41:10
377+ // In the 1970's some time zones had sub-minute offsets to UTC, e.g., Africa/Monrovia.
378+ // We will exclude them for tests:
379+ final int minEpochSecond = 315_532_800 ; // 1980-01-01 01:00:00
380+ final int maxEpochSecond = 1_621_280_470 ; // 2021-05-17 21:41:10
381+ final long epochSecond = minEpochSecond + RANDOM .nextInt (maxEpochSecond - minEpochSecond );
355382 final int epochSecondNano = randomNanos ();
356383 instant .initFromEpochSecond (epochSecond , epochSecondNano );
357384 return instant ;
0 commit comments