blob: a9362e37d5b67ed0ff28fa802a6626af8425e037 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache license, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the license for the specific language governing permissions and
* limitations under the license.
*/
package org.apache.logging.log4j.core.pattern;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNull;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.text.SimpleDateFormat;
import java.util.Arrays;
import java.util.Calendar;
import java.util.Collection;
import java.util.Date;
import java.util.TimeZone;
import org.apache.logging.log4j.core.AbstractLogEvent;
import org.apache.logging.log4j.core.LogEvent;
import org.apache.logging.log4j.core.time.Instant;
import org.apache.logging.log4j.core.time.MutableInstant;
import org.apache.logging.log4j.core.time.internal.format.FixedDateFormat;
import org.apache.logging.log4j.core.time.internal.format.FixedDateFormat.FixedTimeZoneFormat;
import org.apache.logging.log4j.core.util.Constants;
import org.apache.logging.log4j.util.Strings;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
@RunWith(Parameterized.class)
public class DatePatternConverterTest {
private class MyLogEvent extends AbstractLogEvent {
private static final long serialVersionUID = 0;
@Override
public Instant getInstant() {
MutableInstant result = new MutableInstant();
result.initFromEpochMilli(getTimeMillis(), 123456);
return result;
}
@Override
public long getTimeMillis() {
final Calendar cal = Calendar.getInstance();
cal.set(2011, Calendar.DECEMBER, 30, 10, 56, 35);
cal.set(Calendar.MILLISECOND, 987);
return cal.getTimeInMillis();
}
}
/**
* SimpleTimePattern for DEFAULT.
*/
private static final String DEFAULT_PATTERN = FixedDateFormat.FixedFormat.DEFAULT.getPattern();
/**
* ISO8601 string literal.
*/
private static final String ISO8601 = FixedDateFormat.FixedFormat.ISO8601.name();
/**
* ISO8601_OFFSE_DATE_TIME_XX string literal.
*/
private static final String ISO8601_OFFSE_DATE_TIME_HHMM = FixedDateFormat.FixedFormat.ISO8601_OFFSET_DATE_TIME_HHMM
.name();
/**
* 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_FORMAT_OPTIONS = { ISO8601 };
@Parameterized.Parameters(name = "threadLocalEnabled={0}")
public static Collection<Object[]> data() {
return Arrays.asList(new Object[][]{{Boolean.TRUE}, {Boolean.FALSE}});
}
public DatePatternConverterTest(final Boolean threadLocalEnabled) throws Exception {
// Setting the system property does not work: the Constant field has already been initialized...
//System.setProperty("log4j2.enable.threadlocals", threadLocalEnabled.toString());
final Field field = Constants.class.getDeclaredField("ENABLE_THREADLOCALS");
field.setAccessible(true); // make non-private
final Field modifiersField = Field.class.getDeclaredField("modifiers");
modifiersField.setAccessible(true);
modifiersField.setInt(field, field.getModifiers() & ~Modifier.FINAL); // make non-final
field.setBoolean(null, threadLocalEnabled.booleanValue());
}
private Date date(final int year, final int month, final int date) {
final Calendar cal = Calendar.getInstance();
cal.set(year, month, date, 14, 15, 16);
cal.set(Calendar.MILLISECOND, 123);
return cal.getTime();
}
private String precisePattern(final String pattern, int precision) {
String search = "SSS";
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
public void testFormatDateStringBuilderDefaultPattern() {
final DatePatternConverter converter = DatePatternConverter.newInstance(null);
final StringBuilder sb = new StringBuilder();
converter.format(date(2001, 1, 1), sb);
final String expected = "2001-02-01 14:15:16,123";
assertEquals(expected, sb.toString());
}
@Test
public void testFormatDateStringBuilderIso8601() {
final DatePatternConverter converter = DatePatternConverter.newInstance(ISO8601_FORMAT_OPTIONS);
final StringBuilder sb = new StringBuilder();
converter.format(date(2001, 1, 1), sb);
final String expected = "2001-02-01T14:15:16,123";
assertEquals(expected, sb.toString());
}
@Test
public void testFormatDateStringBuilderIso8601BasicWithPeriod() {
final String[] pattern = {FixedDateFormat.FixedFormat.ISO8601_BASIC_PERIOD.name()};
final DatePatternConverter converter = DatePatternConverter.newInstance(pattern);
final StringBuilder sb = new StringBuilder();
converter.format(date(2001, 1, 1), sb);
final String expected = "20010201T141516.123";
assertEquals(expected, sb.toString());
}
@Test
public void testFormatDateStringBuilderIso8601WithPeriod() {
final String[] pattern = {FixedDateFormat.FixedFormat.ISO8601_PERIOD.name()};
final DatePatternConverter converter = DatePatternConverter.newInstance(pattern);
final StringBuilder sb = new StringBuilder();
converter.format(date(2001, 1, 1), sb);
final String expected = "2001-02-01T14:15:16.123";
assertEquals(expected, sb.toString());
}
@Test
public void testFormatDateStringBuilderIso8601WithPeriodMicroseconds() {
final String[] pattern = {FixedDateFormat.FixedFormat.ISO8601_PERIOD_MICROS.name(), "Z"};
final DatePatternConverter converter = DatePatternConverter.newInstance(pattern);
final StringBuilder sb = new StringBuilder();
MutableInstant instant = new MutableInstant();
instant.initFromEpochMilli(
1577225134559L,
// One microsecond
1000);
converter.format(instant, sb);
final String expected = "2019-12-24T22:05:34.559001";
assertEquals(expected, sb.toString());
}
@Test
public void testFormatDateStringBuilderOriginalPattern() {
final String[] pattern = {"yyyy/MM/dd HH-mm-ss.SSS"};
final DatePatternConverter converter = DatePatternConverter.newInstance(pattern);
final StringBuilder sb = new StringBuilder();
converter.format(date(2001, 1, 1), sb);
final String expected = "2001/02/01 14-15-16.123";
assertEquals(expected, sb.toString());
}
@Test
public void testFormatLogEventStringBuilderDefaultPattern() {
final LogEvent event = new MyLogEvent();
final DatePatternConverter converter = DatePatternConverter.newInstance(null);
final StringBuilder sb = new StringBuilder();
converter.format(event, sb);
final String expected = "2011-12-30 10:56:35,987";
assertEquals(expected, sb.toString());
}
@Test
public void testFormatLogEventStringBuilderIso8601() {
final LogEvent event = new MyLogEvent();
final DatePatternConverter converter = DatePatternConverter.newInstance(ISO8601_FORMAT_OPTIONS);
final StringBuilder sb = new StringBuilder();
converter.format(event, sb);
final String expected = "2011-12-30T10:56:35,987";
assertEquals(expected, sb.toString());
}
@Test
public void testFormatLogEventStringBuilderIso8601TimezoneJST() {
final LogEvent event = new MyLogEvent();
final String[] optionsWithTimezone = {ISO8601, "JST"};
final DatePatternConverter converter = DatePatternConverter.newInstance(optionsWithTimezone);
final StringBuilder sb = new StringBuilder();
converter.format(event, sb);
// JST=Japan Standard Time: UTC+9:00
final TimeZone tz = TimeZone.getTimeZone("JST");
final SimpleDateFormat sdf = new SimpleDateFormat(converter.getPattern());
sdf.setTimeZone(tz);
final long adjusted = event.getTimeMillis() + tz.getDSTSavings();
final String expected = sdf.format(new Date(adjusted));
// final String expected = "2011-12-30T18:56:35,987"; // in CET (Central Eastern Time: Amsterdam)
assertEquals(expected, sb.toString());
}
@Test
public void testFormatLogEventStringBuilderIso8601TimezoneOffsetHHCMM() {
final LogEvent event = new MyLogEvent();
final String[] optionsWithTimezone = { ISO8601_OFFSET_DATE_TIME_HHCMM };
final DatePatternConverter converter = DatePatternConverter.newInstance(optionsWithTimezone);
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;
assertEquals(expected, sb.toString());
}
@Test
public void testFormatLogEventStringBuilderIso8601TimezoneOffsetHHMM() {
final LogEvent event = new MyLogEvent();
final String[] optionsWithTimezone = { ISO8601_OFFSE_DATE_TIME_HHMM };
final DatePatternConverter converter = DatePatternConverter.newInstance(optionsWithTimezone);
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;
assertEquals(expected, sb.toString());
}
@Test
public void testFormatLogEventStringBuilderIso8601TimezoneUTC() {
final LogEvent event = new MyLogEvent();
final DatePatternConverter converter = DatePatternConverter.newInstance(new String[] {"ISO8601", "UTC"});
final StringBuilder sb = new StringBuilder();
converter.format(event, sb);
final TimeZone tz = TimeZone.getTimeZone("UTC");
final SimpleDateFormat sdf = new SimpleDateFormat(converter.getPattern());
sdf.setTimeZone(tz);
final long adjusted = event.getTimeMillis() + tz.getDSTSavings();
final String expected = sdf.format(new Date(adjusted));
// final String expected = "2011-12-30T09:56:35,987";
assertEquals(expected, sb.toString());
}
@Test
public void testFormatLogEventStringBuilderIso8601TimezoneZ() {
final LogEvent event = new MyLogEvent();
final String[] optionsWithTimezone = { ISO8601, "Z" };
final DatePatternConverter converter = DatePatternConverter.newInstance(optionsWithTimezone);
final StringBuilder sb = new StringBuilder();
converter.format(event, sb);
final TimeZone tz = TimeZone.getTimeZone("UTC");
final SimpleDateFormat sdf = new SimpleDateFormat(converter.getPattern());
sdf.setTimeZone(tz);
final long adjusted = event.getTimeMillis() + tz.getDSTSavings();
final String expected = sdf.format(new Date(adjusted));
// final String expected = "2011-12-30T17:56:35,987"; // in UTC
assertEquals(expected, sb.toString());
}
@Test
public void testFormatObjectStringBuilderDefaultPattern() {
final DatePatternConverter converter = DatePatternConverter.newInstance(null);
final StringBuilder sb = new StringBuilder();
converter.format("nondate", sb);
final String expected = ""; // only process dates
assertEquals(expected, sb.toString());
}
@Test
public void testFormatStringBuilderObjectArrayDefaultPattern() {
final DatePatternConverter converter = DatePatternConverter.newInstance(null);
final StringBuilder sb = new StringBuilder();
converter.format(sb, date(2001, 1, 1), date(2002, 2, 2), date(2003, 3, 3));
final String expected = "2001-02-01 14:15:16,123"; // only process first date
assertEquals(expected, sb.toString());
}
@Test
public void testFormatStringBuilderObjectArrayIso8601() {
final DatePatternConverter converter = DatePatternConverter.newInstance(ISO8601_FORMAT_OPTIONS);
final StringBuilder sb = new StringBuilder();
converter.format(sb, date(2001, 1, 1), date(2002, 2, 2), date(2003, 3, 3));
final String expected = "2001-02-01T14:15:16,123"; // only process first date
assertEquals(expected, sb.toString());
}
@Test
public void testGetPatternReturnsDefaultForEmptyOptionsArray() {
assertEquals(DEFAULT_PATTERN, DatePatternConverter.newInstance(new String[0]).getPattern());
}
@Test
public void testGetPatternReturnsDefaultForInvalidPattern() {
final String[] invalid = {"ABC I am not a valid date pattern"};
assertEquals(DEFAULT_PATTERN, DatePatternConverter.newInstance(invalid).getPattern());
}
@Test
public void testGetPatternReturnsDefaultForNullOptions() {
assertEquals(DEFAULT_PATTERN, DatePatternConverter.newInstance(null).getPattern());
}
@Test
public void testGetPatternReturnsDefaultForSingleNullElementOptionsArray() {
assertEquals(DEFAULT_PATTERN, DatePatternConverter.newInstance(new String[1]).getPattern());
}
@Test
public void testGetPatternReturnsDefaultForTwoNullElementsOptionsArray() {
assertEquals(DEFAULT_PATTERN, DatePatternConverter.newInstance(new String[2]).getPattern());
}
@Test
public void testGetPatternReturnsNullForUnix() {
final String[] options = {"UNIX"};
assertNull(DatePatternConverter.newInstance(options).getPattern());
}
@Test
public void testGetPatternReturnsNullForUnixMillis() {
final String[] options = {"UNIX_MILLIS"};
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()) {
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;
preciseConverter = DatePatternConverter.newInstance(new String[] { precisePattern });
} 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);
FixedTimeZoneFormat timeZoneFormat = format.getTimeZoneFormat();
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("format = " + format + ", pattern = " + pattern + ", precisePattern = " + precisePattern,
expected, preciseBuilder.toString());
// 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.indexOf("SSS") < 0) {
// 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);
FixedTimeZoneFormat timeZoneFormat = format.getTimeZoneFormat();
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".substring(0, i)).append(tz).toString();
assertEquals(
"format = " + format + ", pattern = " + pattern + ", precisePattern = " + precisePattern,
expected, preciseBuilder.toString());
// System.out.println(preciseOptions[0] + ": " + precise);
}
}
}
}
@Test
public void testPredefinedFormatWithoutTimezone() {
for (final FixedDateFormat.FixedFormat format : FixedDateFormat.FixedFormat.values()) {
final String[] options = {format.name()};
final DatePatternConverter converter = DatePatternConverter.newInstance(options);
assertEquals(format.getPattern(), 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
final DatePatternConverter converter = DatePatternConverter.newInstance(options);
assertEquals(format.getPattern(), converter.getPattern());
}
}
}