Skip to content

Commit eefca59

Browse files
vyppkarwasz
andauthored
Rewrite date & time formatting (#3121)
Co-authored-by: Piotr P. Karwasz <piotr.github@karwasz.org>
1 parent 43e7552 commit eefca59

File tree

40 files changed

+3197
-1222
lines changed

40 files changed

+3197
-1222
lines changed

log4j-core-test/src/test/java/org/apache/logging/log4j/core/layout/HtmlLayoutTest.java

Lines changed: 0 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,6 @@
4343
import org.apache.logging.log4j.core.test.appender.ListAppender;
4444
import org.apache.logging.log4j.core.time.Instant;
4545
import org.apache.logging.log4j.core.time.MutableInstant;
46-
import org.apache.logging.log4j.core.util.datetime.FixedDateFormat;
4746
import org.apache.logging.log4j.message.Message;
4847
import org.apache.logging.log4j.message.SimpleMessage;
4948
import org.apache.logging.log4j.test.junit.UsingAnyThreadContext;
@@ -285,19 +284,6 @@ private void testLayoutWithDatePatternFixedFormat(final FixedFormat format, fina
285284
format.getPattern().replace('n', 'S').replace('X', 'x'), locale);
286285
String expected = zonedDateTime.format(dateTimeFormatter);
287286

288-
final String offset = zonedDateTime.getOffset().toString();
289-
290-
// Truncate minutes if timeZone format is HH and timeZone has minutes. This is required because according to
291-
// DateTimeFormatter,
292-
// One letter outputs just the hour, such as '+01', unless the minute is non-zero in which case the minute is
293-
// also output, such as '+0130'
294-
// ref : https://docs.oracle.com/javase/8/docs/api/java/time/format/DateTimeFormatter.html
295-
if (FixedDateFormat.FixedTimeZoneFormat.HH.equals(format.getFixedTimeZoneFormat())
296-
&& offset.contains(":")
297-
&& !"00".equals(offset.split(":")[1])) {
298-
expected = expected.substring(0, expected.length() - 2);
299-
}
300-
301287
assertEquals(
302288
"<td>" + expected + "</td>",
303289
actual,

log4j-core-test/src/test/java/org/apache/logging/log4j/core/pattern/DatePatternConverterTestBase.java

Lines changed: 53 additions & 147 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,9 @@
2020
import static org.junit.jupiter.api.Assertions.assertNull;
2121

2222
import java.text.SimpleDateFormat;
23+
import java.time.ZoneId;
24+
import java.time.format.DateTimeFormatter;
25+
import java.time.temporal.TemporalAccessor;
2326
import java.util.Calendar;
2427
import java.util.Date;
2528
import java.util.TimeZone;
@@ -28,8 +31,6 @@
2831
import org.apache.logging.log4j.core.time.Instant;
2932
import org.apache.logging.log4j.core.time.MutableInstant;
3033
import org.apache.logging.log4j.core.util.Constants;
31-
import org.apache.logging.log4j.core.util.datetime.FixedDateFormat;
32-
import org.apache.logging.log4j.core.util.datetime.FixedDateFormat.FixedTimeZoneFormat;
3334
import org.apache.logging.log4j.util.Strings;
3435
import org.junit.jupiter.api.Test;
3536

@@ -54,27 +55,13 @@ public long getTimeMillis() {
5455
}
5556
}
5657

57-
/**
58-
* SimpleTimePattern for DEFAULT.
59-
*/
60-
private static final String DEFAULT_PATTERN = FixedDateFormat.FixedFormat.DEFAULT.getPattern();
58+
private static final String DEFAULT_PATTERN = "yyyy-MM-dd HH:mm:ss,SSS";
6159

62-
/**
63-
* ISO8601 string literal.
64-
*/
65-
private static final String ISO8601 = FixedDateFormat.FixedFormat.ISO8601.name();
60+
private static final String ISO8601 = "ISO8601";
6661

67-
/**
68-
* ISO8601_OFFSET_DATE_TIME_XX string literal.
69-
*/
70-
private static final String ISO8601_OFFSET_DATE_TIME_HHMM =
71-
FixedDateFormat.FixedFormat.ISO8601_OFFSET_DATE_TIME_HHMM.name();
62+
private static final String ISO8601_OFFSET_DATE_TIME_HHMM = "ISO8601_OFFSET_DATE_TIME_HHMM";
7263

73-
/**
74-
* ISO8601_OFFSET_DATE_TIME_XXX string literal.
75-
*/
76-
private static final String ISO8601_OFFSET_DATE_TIME_HHCMM =
77-
FixedDateFormat.FixedFormat.ISO8601_OFFSET_DATE_TIME_HHCMM.name();
64+
private static final String ISO8601_OFFSET_DATE_TIME_HHCMM = "ISO8601_OFFSET_DATE_TIME_HHCMM";
7865

7966
private static final String[] ISO8601_FORMAT_OPTIONS = {ISO8601};
8067

@@ -91,14 +78,6 @@ private static Date date(final int year, final int month, final int date) {
9178
return cal.getTime();
9279
}
9380

94-
private String precisePattern(final String pattern, final int precision) {
95-
final String search = "SSS";
96-
final int foundIndex = pattern.indexOf(search);
97-
final String seconds = pattern.substring(0, foundIndex);
98-
final String remainder = pattern.substring(foundIndex + search.length());
99-
return seconds + "nnnnnnnnn".substring(0, precision) + remainder;
100-
}
101-
10281
@Test
10382
void testThreadLocalsConstant() {
10483
assertEquals(threadLocalsEnabled, Constants.ENABLE_THREADLOCALS);
@@ -109,6 +88,7 @@ public void testFormatDateStringBuilderDefaultPattern() {
10988
assertDatePattern(null, date(2001, 1, 1), "2001-02-01 14:15:16,123");
11089
}
11190

91+
@SuppressWarnings("deprecation")
11292
@Test
11393
public void testFormatDateStringBuilderIso8601() {
11494
final DatePatternConverter converter = DatePatternConverter.newInstance(ISO8601_FORMAT_OPTIONS);
@@ -121,19 +101,18 @@ public void testFormatDateStringBuilderIso8601() {
121101

122102
@Test
123103
public void testFormatDateStringBuilderIso8601BasicWithPeriod() {
124-
assertDatePattern(
125-
FixedDateFormat.FixedFormat.ISO8601_BASIC_PERIOD.name(), date(2001, 1, 1), "20010201T141516.123");
104+
assertDatePattern("ISO8601_BASIC_PERIOD", date(2001, 1, 1), "20010201T141516.123");
126105
}
127106

128107
@Test
129108
public void testFormatDateStringBuilderIso8601WithPeriod() {
130-
assertDatePattern(
131-
FixedDateFormat.FixedFormat.ISO8601_PERIOD.name(), date(2001, 1, 1), "2001-02-01T14:15:16.123");
109+
assertDatePattern("ISO8601_PERIOD", date(2001, 1, 1), "2001-02-01T14:15:16.123");
132110
}
133111

112+
@SuppressWarnings("deprecation")
134113
@Test
135114
public void testFormatDateStringBuilderIso8601WithPeriodMicroseconds() {
136-
final String[] pattern = {FixedDateFormat.FixedFormat.ISO8601_PERIOD_MICROS.name(), "Z"};
115+
final String[] pattern = {"ISO8601_PERIOD_MICROS", "Z"};
137116
final DatePatternConverter converter = DatePatternConverter.newInstance(pattern);
138117
final StringBuilder sb = new StringBuilder();
139118
final MutableInstant instant = new MutableInstant();
@@ -180,11 +159,12 @@ public void testFormatAmericanPatterns() {
180159
assertDatePattern("US_MONTH_DAY_YEAR4_TIME", date, "11/03/2011 14:15:16.123");
181160
assertDatePattern("US_MONTH_DAY_YEAR2_TIME", date, "11/03/11 14:15:16.123");
182161
assertDatePattern("dd/MM/yyyy HH:mm:ss.SSS", date, "11/03/2011 14:15:16.123");
183-
assertDatePattern("dd/MM/yyyy HH:mm:ss.nnnnnn", date, "11/03/2011 14:15:16.123000");
162+
assertDatePattern("dd/MM/yyyy HH:mm:ss.SSSSSS", date, "11/03/2011 14:15:16.123000");
184163
assertDatePattern("dd/MM/yy HH:mm:ss.SSS", date, "11/03/11 14:15:16.123");
185-
assertDatePattern("dd/MM/yy HH:mm:ss.nnnnnn", date, "11/03/11 14:15:16.123000");
164+
assertDatePattern("dd/MM/yy HH:mm:ss.SSSSSS", date, "11/03/11 14:15:16.123000");
186165
}
187166

167+
@SuppressWarnings("deprecation")
188168
private static void assertDatePattern(final String format, final Date date, final String expected) {
189169
final DatePatternConverter converter = DatePatternConverter.newInstance(new String[] {format});
190170
final StringBuilder sb = new StringBuilder();
@@ -219,9 +199,9 @@ public void testFormatLogEventStringBuilderIso8601TimezoneOffsetHHCMM() {
219199
final StringBuilder sb = new StringBuilder();
220200
converter.format(event, sb);
221201

222-
final SimpleDateFormat sdf = new SimpleDateFormat(converter.getPattern());
223-
final String format = sdf.format(new Date(event.getTimeMillis()));
224-
final String expected = format.endsWith("Z") ? format.substring(0, format.length() - 1) + "+00:00" : format;
202+
final String expected = DateTimeFormatter.ofPattern(converter.getPattern())
203+
.withZone(ZoneId.systemDefault())
204+
.format((TemporalAccessor) event.getInstant());
225205
assertEquals(expected, sb.toString());
226206
}
227207

@@ -233,9 +213,9 @@ public void testFormatLogEventStringBuilderIso8601TimezoneOffsetHHMM() {
233213
final StringBuilder sb = new StringBuilder();
234214
converter.format(event, sb);
235215

236-
final SimpleDateFormat sdf = new SimpleDateFormat(converter.getPattern());
237-
final String format = sdf.format(new Date(event.getTimeMillis()));
238-
final String expected = format.endsWith("Z") ? format.substring(0, format.length() - 1) + "+0000" : format;
216+
final String expected = DateTimeFormatter.ofPattern(converter.getPattern())
217+
.withZone(ZoneId.systemDefault())
218+
.format((TemporalAccessor) event.getInstant());
239219
assertEquals(expected, sb.toString());
240220
}
241221

@@ -311,7 +291,7 @@ public void testGetPatternReturnsDefaultForEmptyOptionsArray() {
311291

312292
@Test
313293
public void testGetPatternReturnsDefaultForInvalidPattern() {
314-
final String[] invalid = {"ABC I am not a valid date pattern"};
294+
final String[] invalid = {"A single `V` is not allow by `DateTimeFormatter` and should cause an exception"};
315295
assertEquals(DEFAULT_PATTERN, DatePatternConverter.newInstance(invalid).getPattern());
316296
}
317297

@@ -344,126 +324,52 @@ public void testGetPatternReturnsNullForUnixMillis() {
344324
assertNull(DatePatternConverter.newInstance(options).getPattern());
345325
}
346326

347-
@Test
348-
public void testInvalidLongPatternIgnoresExcessiveDigits() {
349-
final StringBuilder preciseBuilder = new StringBuilder();
350-
final StringBuilder milliBuilder = new StringBuilder();
351-
final LogEvent event = new MyLogEvent();
352-
353-
for (final FixedDateFormat.FixedFormat format : FixedDateFormat.FixedFormat.values()) {
354-
final String pattern = format.getPattern();
355-
final String search = "SSS";
356-
final int foundIndex = pattern.indexOf(search);
357-
if (pattern.endsWith("n") || pattern.matches(".+n+X*") || pattern.matches(".+n+Z*")) {
358-
// ignore patterns that already have precise time formats
359-
// ignore patterns that do not use seconds.
360-
continue;
361-
}
362-
preciseBuilder.setLength(0);
363-
milliBuilder.setLength(0);
364-
365-
final DatePatternConverter preciseConverter;
366-
final String precisePattern;
367-
if (foundIndex < 0) {
368-
precisePattern = pattern;
369-
} else {
370-
final String subPattern = pattern.substring(0, foundIndex);
371-
final String remainder = pattern.substring(foundIndex + search.length());
372-
precisePattern = subPattern + "nnnnnnnnn" + "n" + remainder; // nanos too long
373-
}
374-
preciseConverter = DatePatternConverter.newInstance(new String[] {precisePattern});
375-
preciseConverter.format(event, preciseBuilder);
376-
377-
final String[] milliOptions = {pattern};
378-
DatePatternConverter.newInstance(milliOptions).format(event, milliBuilder);
379-
final FixedTimeZoneFormat timeZoneFormat = format.getFixedTimeZoneFormat();
380-
final int truncateLen = 3 + (timeZoneFormat != null ? timeZoneFormat.getLength() : 0);
381-
final String tz = timeZoneFormat != null
382-
? milliBuilder.substring(milliBuilder.length() - timeZoneFormat.getLength(), milliBuilder.length())
383-
: Strings.EMPTY;
384-
milliBuilder.setLength(milliBuilder.length() - truncateLen); // truncate millis
385-
if (foundIndex >= 0) {
386-
milliBuilder.append("987123456");
387-
}
388-
final String expected = milliBuilder.append(tz).toString();
389-
390-
assertEquals(
391-
expected,
392-
preciseBuilder.toString(),
393-
"format = " + format + ", pattern = " + pattern + ", precisePattern = " + precisePattern);
394-
// System.out.println(preciseOptions[0] + ": " + precise);
395-
}
396-
}
397-
398327
@Test
399328
public void testNewInstanceAllowsNullParameter() {
400329
DatePatternConverter.newInstance(null); // no errors
401330
}
402331

403-
// test with all formats from one 'n' (100s of millis) to 'nnnnnnnnn' (nanosecond precision)
404-
@Test
405-
public void testPredefinedFormatWithAnyValidNanoPrecision() {
406-
final StringBuilder preciseBuilder = new StringBuilder();
407-
final StringBuilder milliBuilder = new StringBuilder();
408-
final LogEvent event = new MyLogEvent();
409-
410-
for (final String timeZone : new String[] {"PST", null}) { // Pacific Standard Time=UTC-8:00
411-
for (final FixedDateFormat.FixedFormat format : FixedDateFormat.FixedFormat.values()) {
412-
for (int i = 1; i <= 9; i++) {
413-
final String pattern = format.getPattern();
414-
if (pattern.endsWith("n")
415-
|| pattern.matches(".+n+X*")
416-
|| pattern.matches(".+n+Z*")
417-
|| !pattern.contains("SSS")) {
418-
// ignore patterns that already have precise time formats
419-
// ignore patterns that do not use seconds.
420-
continue;
421-
}
422-
preciseBuilder.setLength(0);
423-
milliBuilder.setLength(0);
424-
425-
final String precisePattern = precisePattern(pattern, i);
426-
final String[] preciseOptions = {precisePattern, timeZone};
427-
final DatePatternConverter preciseConverter = DatePatternConverter.newInstance(preciseOptions);
428-
preciseConverter.format(event, preciseBuilder);
429-
430-
final String[] milliOptions = {pattern, timeZone};
431-
DatePatternConverter.newInstance(milliOptions).format(event, milliBuilder);
432-
final FixedTimeZoneFormat timeZoneFormat = format.getFixedTimeZoneFormat();
433-
final int truncateLen = 3 + (timeZoneFormat != null ? timeZoneFormat.getLength() : 0);
434-
final String tz = timeZoneFormat != null
435-
? milliBuilder.substring(
436-
milliBuilder.length() - timeZoneFormat.getLength(), milliBuilder.length())
437-
: Strings.EMPTY;
438-
milliBuilder.setLength(milliBuilder.length() - truncateLen); // truncate millis
439-
final String expected =
440-
milliBuilder.append("987123456", 0, i).append(tz).toString();
441-
442-
assertEquals(
443-
expected,
444-
preciseBuilder.toString(),
445-
"format = " + format + ", pattern = " + pattern + ", precisePattern = " + precisePattern);
446-
// System.out.println(preciseOptions[0] + ": " + precise);
447-
}
448-
}
449-
}
450-
}
332+
private static final String[] PATTERN_NAMES = {
333+
"ABSOLUTE",
334+
"ABSOLUTE_MICROS",
335+
"ABSOLUTE_NANOS",
336+
"ABSOLUTE_PERIOD",
337+
"COMPACT",
338+
"DATE",
339+
"DATE_PERIOD",
340+
"DEFAULT",
341+
"DEFAULT_MICROS",
342+
"DEFAULT_NANOS",
343+
"DEFAULT_PERIOD",
344+
"ISO8601_BASIC",
345+
"ISO8601_BASIC_PERIOD",
346+
"ISO8601",
347+
"ISO8601_OFFSET_DATE_TIME_HH",
348+
"ISO8601_OFFSET_DATE_TIME_HHMM",
349+
"ISO8601_OFFSET_DATE_TIME_HHCMM",
350+
"ISO8601_PERIOD",
351+
"ISO8601_PERIOD_MICROS",
352+
"US_MONTH_DAY_YEAR2_TIME",
353+
"US_MONTH_DAY_YEAR4_TIME"
354+
};
451355

452356
@Test
453357
public void testPredefinedFormatWithoutTimezone() {
454-
for (final FixedDateFormat.FixedFormat format : FixedDateFormat.FixedFormat.values()) {
455-
final String[] options = {format.name()};
358+
for (final String patternName : PATTERN_NAMES) {
359+
final String[] options = {patternName};
456360
final DatePatternConverter converter = DatePatternConverter.newInstance(options);
457-
assertEquals(format.getPattern(), converter.getPattern());
361+
final String expectedPattern = DatePatternConverter.decodeNamedPattern(patternName);
362+
assertEquals(expectedPattern, converter.getPattern());
458363
}
459364
}
460365

461366
@Test
462367
public void testPredefinedFormatWithTimezone() {
463-
for (final FixedDateFormat.FixedFormat format : FixedDateFormat.FixedFormat.values()) {
464-
final String[] options = {format.name(), "PST"}; // Pacific Standard Time=UTC-8:00
368+
for (final String patternName : PATTERN_NAMES) {
369+
final String[] options = {patternName, "PST"}; // Pacific Standard Time=UTC-8:00
465370
final DatePatternConverter converter = DatePatternConverter.newInstance(options);
466-
assertEquals(format.getPattern(), converter.getPattern());
371+
final String expectedPattern = DatePatternConverter.decodeNamedPattern(patternName);
372+
assertEquals(expectedPattern, converter.getPattern());
467373
}
468374
}
469375
}

0 commit comments

Comments
 (0)