Skip to content

Rewrite date & time formatting #3121

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 26 commits into from
Oct 31, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
d9eb350
Move `InstantFormatter` to `log4j-core`
vy Oct 18, 2024
1cf4ac9
Update `com.github.jnr:jnr-ffi` to version `2.2.17` (#3082)
asf-rm Oct 14, 2024
062b945
Correct example property syntax for PatternMatch under ScriptPatternS…
martin-dorey-hv Oct 16, 2024
dfe0ada
Fixes property names in release notes (#3089)
ppkarwasz Oct 16, 2024
9ef736f
Remove JANSI dependency in `2.x` (#3070)
ppkarwasz Oct 16, 2024
c45f456
Update `co.elastic.clients:elasticsearch-java` to version `8.15.3` (#…
asf-rm Oct 18, 2024
b3a6fcc
Rewrite `InstantPatternDynamicFormatter`
vy Oct 25, 2024
f255f0b
Merge remote-tracking branch 'origin/2.x' into feature/2.x/cached-ins…
vy Oct 25, 2024
3e39405
Fix Javadoc
vy Oct 25, 2024
8d3ba30
More Javadoc fixes
vy Oct 25, 2024
80a7c27
Fix Spotless failures
vy Oct 25, 2024
4f431d6
Add #2943 to the changelog
vy Oct 25, 2024
2f74ffc
Fix Java 8 errors
vy Oct 25, 2024
ff4f688
Remove redundant `DatePatternConverter.Formatter`
vy Oct 25, 2024
9e7f4ce
Merge remote-tracking branch 'origin/2.x' into feature/2.x/cached-ins…
vy Oct 27, 2024
12ec63a
Decrease the visibility of `InstantPatternDynamicFormatter`
vy Oct 27, 2024
8281082
Remove deprecated `TimeFormatBenchmark`
vy Oct 27, 2024
4d3b5db
Merge remote-tracking branch 'origin/2.x' into feature/2.x/cached-ins…
vy Oct 28, 2024
1f84806
Export `o.a.l.l.c.util.internal.instant` package
vy Oct 28, 2024
612adf8
Add `@ExportTo` for the `o.a.l.l.c.util.internal.instant` package
vy Oct 29, 2024
9c64a39
Improve docs
vy Oct 29, 2024
cce4fa9
Fix `site` failure
vy Oct 29, 2024
33218ce
Fix date pattern in `log4j-rolling-size-with-time.xml`
vy Oct 29, 2024
523251d
Merge remote-tracking branch 'origin/2.x' into feature/2.x/cached-ins…
vy Oct 29, 2024
02f7bee
Document potential optimization directions
vy Oct 30, 2024
4bf4232
Merge remote-tracking branch 'origin/2.x' into feature/2.x/cached-ins…
vy Oct 31, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,6 @@
import org.apache.logging.log4j.core.test.appender.ListAppender;
import org.apache.logging.log4j.core.time.Instant;
import org.apache.logging.log4j.core.time.MutableInstant;
import org.apache.logging.log4j.core.util.datetime.FixedDateFormat;
import org.apache.logging.log4j.message.Message;
import org.apache.logging.log4j.message.SimpleMessage;
import org.apache.logging.log4j.test.junit.UsingAnyThreadContext;
Expand Down Expand Up @@ -285,19 +284,6 @@ private void testLayoutWithDatePatternFixedFormat(final FixedFormat format, fina
format.getPattern().replace('n', 'S').replace('X', 'x'), locale);
String expected = zonedDateTime.format(dateTimeFormatter);

final String offset = zonedDateTime.getOffset().toString();

// Truncate minutes if timeZone format is HH and timeZone has minutes. This is required because according to
// DateTimeFormatter,
// One letter outputs just the hour, such as '+01', unless the minute is non-zero in which case the minute is
// also output, such as '+0130'
// ref : https://docs.oracle.com/javase/8/docs/api/java/time/format/DateTimeFormatter.html
if (FixedDateFormat.FixedTimeZoneFormat.HH.equals(format.getFixedTimeZoneFormat())
&& offset.contains(":")
&& !"00".equals(offset.split(":")[1])) {
expected = expected.substring(0, expected.length() - 2);
}

assertEquals(
"<td>" + expected + "</td>",
actual,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,9 @@
import static org.junit.jupiter.api.Assertions.assertNull;

import java.text.SimpleDateFormat;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
import java.time.temporal.TemporalAccessor;
import java.util.Calendar;
import java.util.Date;
import java.util.TimeZone;
Expand All @@ -28,8 +31,6 @@
import org.apache.logging.log4j.core.time.Instant;
import org.apache.logging.log4j.core.time.MutableInstant;
import org.apache.logging.log4j.core.util.Constants;
import org.apache.logging.log4j.core.util.datetime.FixedDateFormat;
import org.apache.logging.log4j.core.util.datetime.FixedDateFormat.FixedTimeZoneFormat;
import org.apache.logging.log4j.util.Strings;
import org.junit.jupiter.api.Test;

Expand All @@ -54,27 +55,13 @@ public long getTimeMillis() {
}
}

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

/**
* ISO8601 string literal.
*/
private static final String ISO8601 = FixedDateFormat.FixedFormat.ISO8601.name();
private static final String ISO8601 = "ISO8601";

/**
* ISO8601_OFFSET_DATE_TIME_XX string literal.
*/
private static final String ISO8601_OFFSET_DATE_TIME_HHMM =
FixedDateFormat.FixedFormat.ISO8601_OFFSET_DATE_TIME_HHMM.name();
private static final String ISO8601_OFFSET_DATE_TIME_HHMM = "ISO8601_OFFSET_DATE_TIME_HHMM";

/**
* ISO8601_OFFSET_DATE_TIME_XXX string literal.
*/
private static final String ISO8601_OFFSET_DATE_TIME_HHCMM =
FixedDateFormat.FixedFormat.ISO8601_OFFSET_DATE_TIME_HHCMM.name();
private static final String ISO8601_OFFSET_DATE_TIME_HHCMM = "ISO8601_OFFSET_DATE_TIME_HHCMM";

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

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

private String precisePattern(final String pattern, final int precision) {
final String search = "SSS";
final int foundIndex = pattern.indexOf(search);
final String seconds = pattern.substring(0, foundIndex);
final String remainder = pattern.substring(foundIndex + search.length());
return seconds + "nnnnnnnnn".substring(0, precision) + remainder;
}

@Test
void testThreadLocalsConstant() {
assertEquals(threadLocalsEnabled, Constants.ENABLE_THREADLOCALS);
Expand All @@ -109,6 +88,7 @@ public void testFormatDateStringBuilderDefaultPattern() {
assertDatePattern(null, date(2001, 1, 1), "2001-02-01 14:15:16,123");
}

@SuppressWarnings("deprecation")
@Test
public void testFormatDateStringBuilderIso8601() {
final DatePatternConverter converter = DatePatternConverter.newInstance(ISO8601_FORMAT_OPTIONS);
Expand All @@ -121,19 +101,18 @@ public void testFormatDateStringBuilderIso8601() {

@Test
public void testFormatDateStringBuilderIso8601BasicWithPeriod() {
assertDatePattern(
FixedDateFormat.FixedFormat.ISO8601_BASIC_PERIOD.name(), date(2001, 1, 1), "20010201T141516.123");
assertDatePattern("ISO8601_BASIC_PERIOD", date(2001, 1, 1), "20010201T141516.123");
}

@Test
public void testFormatDateStringBuilderIso8601WithPeriod() {
assertDatePattern(
FixedDateFormat.FixedFormat.ISO8601_PERIOD.name(), date(2001, 1, 1), "2001-02-01T14:15:16.123");
assertDatePattern("ISO8601_PERIOD", date(2001, 1, 1), "2001-02-01T14:15:16.123");
}

@SuppressWarnings("deprecation")
@Test
public void testFormatDateStringBuilderIso8601WithPeriodMicroseconds() {
final String[] pattern = {FixedDateFormat.FixedFormat.ISO8601_PERIOD_MICROS.name(), "Z"};
final String[] pattern = {"ISO8601_PERIOD_MICROS", "Z"};
final DatePatternConverter converter = DatePatternConverter.newInstance(pattern);
final StringBuilder sb = new StringBuilder();
final MutableInstant instant = new MutableInstant();
Expand Down Expand Up @@ -180,11 +159,12 @@ public void testFormatAmericanPatterns() {
assertDatePattern("US_MONTH_DAY_YEAR4_TIME", date, "11/03/2011 14:15:16.123");
assertDatePattern("US_MONTH_DAY_YEAR2_TIME", date, "11/03/11 14:15:16.123");
assertDatePattern("dd/MM/yyyy HH:mm:ss.SSS", date, "11/03/2011 14:15:16.123");
assertDatePattern("dd/MM/yyyy HH:mm:ss.nnnnnn", date, "11/03/2011 14:15:16.123000");
assertDatePattern("dd/MM/yyyy HH:mm:ss.SSSSSS", date, "11/03/2011 14:15:16.123000");
assertDatePattern("dd/MM/yy HH:mm:ss.SSS", date, "11/03/11 14:15:16.123");
assertDatePattern("dd/MM/yy HH:mm:ss.nnnnnn", date, "11/03/11 14:15:16.123000");
assertDatePattern("dd/MM/yy HH:mm:ss.SSSSSS", date, "11/03/11 14:15:16.123000");
}

@SuppressWarnings("deprecation")
private static void assertDatePattern(final String format, final Date date, final String expected) {
final DatePatternConverter converter = DatePatternConverter.newInstance(new String[] {format});
final StringBuilder sb = new StringBuilder();
Expand Down Expand Up @@ -219,9 +199,9 @@ public void testFormatLogEventStringBuilderIso8601TimezoneOffsetHHCMM() {
final StringBuilder sb = new StringBuilder();
converter.format(event, sb);

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

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

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

Expand Down Expand Up @@ -311,7 +291,7 @@ public void testGetPatternReturnsDefaultForEmptyOptionsArray() {

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

Expand Down Expand Up @@ -344,126 +324,52 @@ public void testGetPatternReturnsNullForUnixMillis() {
assertNull(DatePatternConverter.newInstance(options).getPattern());
}

@Test
public void testInvalidLongPatternIgnoresExcessiveDigits() {
final StringBuilder preciseBuilder = new StringBuilder();
final StringBuilder milliBuilder = new StringBuilder();
final LogEvent event = new MyLogEvent();

for (final FixedDateFormat.FixedFormat format : FixedDateFormat.FixedFormat.values()) {
final String pattern = format.getPattern();
final String search = "SSS";
final int foundIndex = pattern.indexOf(search);
if (pattern.endsWith("n") || pattern.matches(".+n+X*") || pattern.matches(".+n+Z*")) {
// ignore patterns that already have precise time formats
// ignore patterns that do not use seconds.
continue;
}
preciseBuilder.setLength(0);
milliBuilder.setLength(0);

final DatePatternConverter preciseConverter;
final String precisePattern;
if (foundIndex < 0) {
precisePattern = pattern;
} else {
final String subPattern = pattern.substring(0, foundIndex);
final String remainder = pattern.substring(foundIndex + search.length());
precisePattern = subPattern + "nnnnnnnnn" + "n" + remainder; // nanos too long
}
preciseConverter = DatePatternConverter.newInstance(new String[] {precisePattern});
preciseConverter.format(event, preciseBuilder);

final String[] milliOptions = {pattern};
DatePatternConverter.newInstance(milliOptions).format(event, milliBuilder);
final FixedTimeZoneFormat timeZoneFormat = format.getFixedTimeZoneFormat();
final int truncateLen = 3 + (timeZoneFormat != null ? timeZoneFormat.getLength() : 0);
final String tz = timeZoneFormat != null
? milliBuilder.substring(milliBuilder.length() - timeZoneFormat.getLength(), milliBuilder.length())
: Strings.EMPTY;
milliBuilder.setLength(milliBuilder.length() - truncateLen); // truncate millis
if (foundIndex >= 0) {
milliBuilder.append("987123456");
}
final String expected = milliBuilder.append(tz).toString();

assertEquals(
expected,
preciseBuilder.toString(),
"format = " + format + ", pattern = " + pattern + ", precisePattern = " + precisePattern);
// System.out.println(preciseOptions[0] + ": " + precise);
}
}

@Test
public void testNewInstanceAllowsNullParameter() {
DatePatternConverter.newInstance(null); // no errors
}

// test with all formats from one 'n' (100s of millis) to 'nnnnnnnnn' (nanosecond precision)
@Test
public void testPredefinedFormatWithAnyValidNanoPrecision() {
final StringBuilder preciseBuilder = new StringBuilder();
final StringBuilder milliBuilder = new StringBuilder();
final LogEvent event = new MyLogEvent();

for (final String timeZone : new String[] {"PST", null}) { // Pacific Standard Time=UTC-8:00
for (final FixedDateFormat.FixedFormat format : FixedDateFormat.FixedFormat.values()) {
for (int i = 1; i <= 9; i++) {
final String pattern = format.getPattern();
if (pattern.endsWith("n")
|| pattern.matches(".+n+X*")
|| pattern.matches(".+n+Z*")
|| !pattern.contains("SSS")) {
// ignore patterns that already have precise time formats
// ignore patterns that do not use seconds.
continue;
}
preciseBuilder.setLength(0);
milliBuilder.setLength(0);

final String precisePattern = precisePattern(pattern, i);
final String[] preciseOptions = {precisePattern, timeZone};
final DatePatternConverter preciseConverter = DatePatternConverter.newInstance(preciseOptions);
preciseConverter.format(event, preciseBuilder);

final String[] milliOptions = {pattern, timeZone};
DatePatternConverter.newInstance(milliOptions).format(event, milliBuilder);
final FixedTimeZoneFormat timeZoneFormat = format.getFixedTimeZoneFormat();
final int truncateLen = 3 + (timeZoneFormat != null ? timeZoneFormat.getLength() : 0);
final String tz = timeZoneFormat != null
? milliBuilder.substring(
milliBuilder.length() - timeZoneFormat.getLength(), milliBuilder.length())
: Strings.EMPTY;
milliBuilder.setLength(milliBuilder.length() - truncateLen); // truncate millis
final String expected =
milliBuilder.append("987123456", 0, i).append(tz).toString();

assertEquals(
expected,
preciseBuilder.toString(),
"format = " + format + ", pattern = " + pattern + ", precisePattern = " + precisePattern);
// System.out.println(preciseOptions[0] + ": " + precise);
}
}
}
}
private static final String[] PATTERN_NAMES = {
"ABSOLUTE",
"ABSOLUTE_MICROS",
"ABSOLUTE_NANOS",
"ABSOLUTE_PERIOD",
"COMPACT",
"DATE",
"DATE_PERIOD",
"DEFAULT",
"DEFAULT_MICROS",
"DEFAULT_NANOS",
"DEFAULT_PERIOD",
"ISO8601_BASIC",
"ISO8601_BASIC_PERIOD",
"ISO8601",
"ISO8601_OFFSET_DATE_TIME_HH",
"ISO8601_OFFSET_DATE_TIME_HHMM",
"ISO8601_OFFSET_DATE_TIME_HHCMM",
"ISO8601_PERIOD",
"ISO8601_PERIOD_MICROS",
"US_MONTH_DAY_YEAR2_TIME",
"US_MONTH_DAY_YEAR4_TIME"
};

@Test
public void testPredefinedFormatWithoutTimezone() {
for (final FixedDateFormat.FixedFormat format : FixedDateFormat.FixedFormat.values()) {
final String[] options = {format.name()};
for (final String patternName : PATTERN_NAMES) {
final String[] options = {patternName};
final DatePatternConverter converter = DatePatternConverter.newInstance(options);
assertEquals(format.getPattern(), converter.getPattern());
final String expectedPattern = DatePatternConverter.decodeNamedPattern(patternName);
assertEquals(expectedPattern, converter.getPattern());
}
}

@Test
public void testPredefinedFormatWithTimezone() {
for (final FixedDateFormat.FixedFormat format : FixedDateFormat.FixedFormat.values()) {
final String[] options = {format.name(), "PST"}; // Pacific Standard Time=UTC-8:00
for (final String patternName : PATTERN_NAMES) {
final String[] options = {patternName, "PST"}; // Pacific Standard Time=UTC-8:00
final DatePatternConverter converter = DatePatternConverter.newInstance(options);
assertEquals(format.getPattern(), converter.getPattern());
final String expectedPattern = DatePatternConverter.decodeNamedPattern(patternName);
assertEquals(expectedPattern, converter.getPattern());
}
}
}
Loading