Skip to content

Commit 2279c11

Browse files
committed
Fix precision detection
We assume that timezones can change in the middle of a (UTC) hour, so they have a precision of minutes.
1 parent 0e8ddb3 commit 2279c11

File tree

2 files changed

+85
-31
lines changed

2 files changed

+85
-31
lines changed

log4j-core-test/src/test/java/org/apache/logging/log4j/core/util/internal/instant/InstantPatternDynamicFormatterTest.java

Lines changed: 68 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -202,20 +202,31 @@ void should_recognize_patterns_of_second_precision(final String pattern) {
202202
assertPatternPrecision(pattern, ChronoUnit.SECONDS);
203203
}
204204

205-
@ParameterizedTest
206-
@ValueSource(
207-
strings = {
205+
static Stream<String> should_recognize_patterns_of_minute_precision() {
206+
Stream<String> stream = Stream.of(
208207
// Basics
209208
"m",
210209
"mm",
210+
"Z",
211+
"x",
212+
"X",
213+
"O",
214+
"z",
215+
"VV",
211216
// Mixed with other stuff
212217
"yyyy-MM-dd HH:mm",
213218
"yyyy-MM-dd'T'HH:mm",
214219
"HH:mm",
220+
"yyyy-MM-dd HH x",
221+
"yyyy-MM-dd'T'HH XX",
215222
// Single-quoted text containing nanosecond and millisecond directives
216223
"yyyy-MM-dd'S'HH:mm",
217-
"yyyy-MM-dd'n'HH:mm"
218-
})
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
219230
void should_recognize_patterns_of_minute_precision(final String pattern) {
220231
assertPatternPrecision(pattern, ChronoUnit.MINUTES);
221232
}
@@ -236,28 +247,71 @@ static List<String> hourPrecisionPatterns() {
236247
"K",
237248
"k",
238249
"H",
239-
"Z",
240-
"x",
241-
"X",
242-
"O",
243-
"z",
244-
"VV",
245250
// Mixed with other stuff
246251
"yyyy-MM-dd HH",
247252
"yyyy-MM-dd'T'HH",
248-
"yyyy-MM-dd HH x",
249-
"yyyy-MM-dd'T'HH XX",
250253
"ddHH",
251254
// Single-quoted text containing nanosecond and millisecond directives
252255
"yyyy-MM-dd'S'HH",
253256
"yyyy-MM-dd'n'HH"));
254257
if (Constants.JAVA_MAJOR_VERSION > 8) {
255258
java8Patterns.add("B");
256-
java8Patterns.add("v");
257259
}
258260
return java8Patterns;
259261
}
260262

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+
261315
private static void assertPatternPrecision(final String pattern, final ChronoUnit expectedPrecision) {
262316
final InstantPatternFormatter formatter =
263317
new InstantPatternDynamicFormatter(pattern, Locale.getDefault(), TimeZone.getDefault());

log4j-core/src/main/java/org/apache/logging/log4j/core/util/internal/instant/InstantPatternDynamicFormatter.java

Lines changed: 17 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -561,34 +561,36 @@ PatternSequence tryMerge(PatternSequence other, ChronoUnit thresholdPrecision) {
561561
}
562562

563563
/**
564-
* @param simplePattern a single-letter directive simplePattern complying (e.g., {@code H}, {@code HH}, or {@code pHH})
564+
* @param singlePattern a single-letter directive singlePattern complying (e.g., {@code H}, {@code HH}, or {@code pHH})
565565
* @return the time precision of the directive
566566
*/
567-
private static ChronoUnit patternPrecision(final String simplePattern) {
567+
private static ChronoUnit patternPrecision(final String singlePattern) {
568568

569-
validateContent(simplePattern);
570-
final String paddingRemovedContent = removePadding(simplePattern);
569+
validateContent(singlePattern);
570+
final String paddingRemovedContent = removePadding(singlePattern);
571571

572-
if (paddingRemovedContent.matches("[GuyY]+")) {
572+
if (paddingRemovedContent.matches("G+")) {
573+
return ChronoUnit.ERAS;
574+
} else if (paddingRemovedContent.matches("[uyY]+")) {
573575
return ChronoUnit.YEARS;
574576
} else if (paddingRemovedContent.matches("[MLQq]+")) {
575577
return ChronoUnit.MONTHS;
576-
} else if (paddingRemovedContent.matches("[wW]+")) {
578+
} else if (paddingRemovedContent.matches("w+")) {
577579
return ChronoUnit.WEEKS;
578-
} else if (paddingRemovedContent.matches("[DdgEecF]+")) {
580+
} else if (paddingRemovedContent.matches("[DdgEecFW]+")) {
579581
return ChronoUnit.DAYS;
580-
} else if (paddingRemovedContent.matches("[aBhKkH]+")
581-
// Time-zone directives
582-
|| paddingRemovedContent.matches("[ZxXOzvV]+")) {
582+
} else if (paddingRemovedContent.matches("[aBhKkH]+")) {
583583
return ChronoUnit.HOURS;
584-
} else if (paddingRemovedContent.contains("m")) {
584+
} else if (paddingRemovedContent.contains("m")
585+
// Time-zone directives
586+
|| paddingRemovedContent.matches("[ZxXOzVv]+")) {
585587
return ChronoUnit.MINUTES;
586588
} else if (paddingRemovedContent.contains("s")) {
587589
return ChronoUnit.SECONDS;
588590
}
589591

590592
// 2 to 3 consequent `S` characters output millisecond precision
591-
else if (paddingRemovedContent.matches("S{2,3}")
593+
else if (paddingRemovedContent.matches("S{1,3}")
592594
// `A` (milli-of-day) outputs millisecond precision.
593595
|| paddingRemovedContent.contains("A")) {
594596
return ChronoUnit.MILLIS;
@@ -599,17 +601,15 @@ else if (paddingRemovedContent.matches("S{4,6}")) {
599601
return ChronoUnit.MICROS;
600602
}
601603

602-
// A single `S` (fraction-of-second) outputs nanosecond precision
603-
else if (paddingRemovedContent.equals("S")
604-
// 7 to 9 consequent `S` characters output nanosecond precision
605-
|| paddingRemovedContent.matches("S{7,9}")
604+
// 7 to 9 consequent `S` characters output nanosecond precision
605+
else if (paddingRemovedContent.matches("S{7,9}")
606606
// `n` (nano-of-second) and `N` (nano-of-day) always output nanosecond precision.
607607
// This is independent of how many times they occur sequentially.
608608
|| paddingRemovedContent.matches("[nN]+")) {
609609
return ChronoUnit.NANOS;
610610
}
611611

612-
final String message = String.format("unrecognized pattern: `%s`", simplePattern);
612+
final String message = String.format("unrecognized pattern: `%s`", singlePattern);
613613
throw new IllegalArgumentException(message);
614614
}
615615

0 commit comments

Comments
 (0)