| /* |
| * 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()); |
| } |
| } |
| |
| } |