You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@logging.apache.org by gg...@apache.org on 2018/12/05 00:22:22 UTC
[logging-log4j2] branch release-2.x updated: [LOG4J2-1246]
PatternLayout %date conversion pattern should render time zone designator
for ISO-ISO8601.
This is an automated email from the ASF dual-hosted git repository.
ggregory pushed a commit to branch release-2.x
in repository https://gitbox.apache.org/repos/asf/logging-log4j2.git
The following commit(s) were added to refs/heads/release-2.x by this push:
new 8e4f428 [LOG4J2-1246] PatternLayout %date conversion pattern should render time zone designator for ISO-ISO8601.
8e4f428 is described below
commit 8e4f4286940fe2199221a90e72d006563fce4083
Author: Gary Gregory <ga...@gmail.com>
AuthorDate: Tue Dec 4 17:22:19 2018 -0700
[LOG4J2-1246] PatternLayout %date conversion pattern should render time
zone designator for ISO-ISO8601.
- Add new format in pattern layout %d{ISO8601_OFFSET_DATE_TIME_X}
- Add new format in pattern layout %d{ISO8601_OFFSET_DATE_TIME_XX}
- Add new format in pattern layout %d{ISO8601_OFFSET_DATE_TIME_XXX}
- Add new format in pattern layout %d{ISO8601_OFFSET_DATE_TIME_Z}
---
.../log4j/core/pattern/DatePatternConverter.java | 2 +-
.../log4j/core/util/datetime/FixedDateFormat.java | 224 +++++++++++++++++----
.../rolling/OnStartupTriggeringPolicyTest.java | 1 +
.../core/pattern/DatePatternConverterTest.java | 173 ++++++++++++----
.../core/util/datetime/FixedDateFormatTest.java | 54 +++--
src/site/xdoc/manual/layouts.xml.vm | 16 ++
6 files changed, 373 insertions(+), 97 deletions(-)
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/DatePatternConverter.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/DatePatternConverter.java
index 703786b..fa5a8c3 100644
--- a/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/DatePatternConverter.java
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/DatePatternConverter.java
@@ -88,7 +88,7 @@ public final class DatePatternConverter extends LogEventPatternConverter impleme
private final FixedDateFormat fixedDateFormat;
// below fields are only used in ThreadLocal caching mode
- private final char[] cachedBuffer = new char[64]; // max length of formatted date-time in any format < 64
+ private final char[] cachedBuffer = new char[70]; // max length of formatted date-time in any format < 64
private int length = 0;
FixedFormatter(final FixedDateFormat fixedDateFormat) {
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/util/datetime/FixedDateFormat.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/util/datetime/FixedDateFormat.java
index f58a29b..c067314 100644
--- a/log4j-core/src/main/java/org/apache/logging/log4j/core/util/datetime/FixedDateFormat.java
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/util/datetime/FixedDateFormat.java
@@ -31,6 +31,7 @@ import java.util.concurrent.TimeUnit;
* <p>
* Related benchmarks: /log4j-perf/src/main/java/org/apache/logging/log4j/perf/jmh/TimeFormatBenchmark.java and
* /log4j-perf/src/main/java/org/apache/logging/log4j/perf/jmh/ThreadsafeDateFormatBenchmark.java
+ * </p>
*/
public class FixedDateFormat {
@@ -38,78 +39,107 @@ public class FixedDateFormat {
* Enumeration over the supported date/time format patterns.
* <p>
* Package protected for unit tests.
+ * </p>
*/
public enum FixedFormat {
+
/**
* ABSOLUTE time format: {@code "HH:mm:ss,SSS"}.
*/
- ABSOLUTE("HH:mm:ss,SSS", null, 0, ':', 1, ',', 1, 3),
+ ABSOLUTE("HH:mm:ss,SSS", null, 0, ':', 1, ',', 1, 3, null),
/**
* ABSOLUTE time format with microsecond precision: {@code "HH:mm:ss,nnnnnn"}.
*/
- ABSOLUTE_MICROS("HH:mm:ss,nnnnnn", null, 0, ':', 1, ',', 1, 6),
+ ABSOLUTE_MICROS("HH:mm:ss,nnnnnn", null, 0, ':', 1, ',', 1, 6, null),
/**
* ABSOLUTE time format with nanosecond precision: {@code "HH:mm:ss,nnnnnnnnn"}.
*/
- ABSOLUTE_NANOS("HH:mm:ss,nnnnnnnnn", null, 0, ':', 1, ',', 1, 9),
+ ABSOLUTE_NANOS("HH:mm:ss,nnnnnnnnn", null, 0, ':', 1, ',', 1, 9, null),
/**
* ABSOLUTE time format variation with period separator: {@code "HH:mm:ss.SSS"}.
*/
- ABSOLUTE_PERIOD("HH:mm:ss.SSS", null, 0, ':', 1, '.', 1, 3),
+ ABSOLUTE_PERIOD("HH:mm:ss.SSS", null, 0, ':', 1, '.', 1, 3, null),
/**
* COMPACT time format: {@code "yyyyMMddHHmmssSSS"}.
*/
- COMPACT("yyyyMMddHHmmssSSS", "yyyyMMdd", 0, ' ', 0, ' ', 0, 3),
+ COMPACT("yyyyMMddHHmmssSSS", "yyyyMMdd", 0, ' ', 0, ' ', 0, 3, null),
/**
* DATE_AND_TIME time format: {@code "dd MMM yyyy HH:mm:ss,SSS"}.
*/
- DATE("dd MMM yyyy HH:mm:ss,SSS", "dd MMM yyyy ", 0, ':', 1, ',', 1, 3),
+ DATE("dd MMM yyyy HH:mm:ss,SSS", "dd MMM yyyy ", 0, ':', 1, ',', 1, 3, null),
/**
* DATE_AND_TIME time format variation with period separator: {@code "dd MMM yyyy HH:mm:ss.SSS"}.
*/
- DATE_PERIOD("dd MMM yyyy HH:mm:ss.SSS", "dd MMM yyyy ", 0, ':', 1, '.', 1, 3),
+ DATE_PERIOD("dd MMM yyyy HH:mm:ss.SSS", "dd MMM yyyy ", 0, ':', 1, '.', 1, 3, null),
/**
* DEFAULT time format: {@code "yyyy-MM-dd HH:mm:ss,SSS"}.
*/
- DEFAULT("yyyy-MM-dd HH:mm:ss,SSS", "yyyy-MM-dd ", 0, ':', 1, ',', 1, 3),
+ DEFAULT("yyyy-MM-dd HH:mm:ss,SSS", "yyyy-MM-dd ", 0, ':', 1, ',', 1, 3, null),
/**
* DEFAULT time format with microsecond precision: {@code "yyyy-MM-dd HH:mm:ss,nnnnnn"}.
*/
- DEFAULT_MICROS("yyyy-MM-dd HH:mm:ss,nnnnnn", "yyyy-MM-dd ", 0, ':', 1, ',', 1, 6),
+ DEFAULT_MICROS("yyyy-MM-dd HH:mm:ss,nnnnnn", "yyyy-MM-dd ", 0, ':', 1, ',', 1, 6, null),
/**
* DEFAULT time format with nanosecond precision: {@code "yyyy-MM-dd HH:mm:ss,nnnnnnnnn"}.
*/
- DEFAULT_NANOS("yyyy-MM-dd HH:mm:ss,nnnnnnnnn", "yyyy-MM-dd ", 0, ':', 1, ',', 1, 9),
+ DEFAULT_NANOS("yyyy-MM-dd HH:mm:ss,nnnnnnnnn", "yyyy-MM-dd ", 0, ':', 1, ',', 1, 9, null),
/**
* DEFAULT time format variation with period separator: {@code "yyyy-MM-dd HH:mm:ss.SSS"}.
*/
- DEFAULT_PERIOD("yyyy-MM-dd HH:mm:ss.SSS", "yyyy-MM-dd ", 0, ':', 1, '.', 1, 3),
+ DEFAULT_PERIOD("yyyy-MM-dd HH:mm:ss.SSS", "yyyy-MM-dd ", 0, ':', 1, '.', 1, 3, null),
/**
* ISO8601_BASIC time format: {@code "yyyyMMdd'T'HHmmss,SSS"}.
*/
- ISO8601_BASIC("yyyyMMdd'T'HHmmss,SSS", "yyyyMMdd'T'", 2, ' ', 0, ',', 1, 3),
+ ISO8601_BASIC("yyyyMMdd'T'HHmmss,SSS", "yyyyMMdd'T'", 2, ' ', 0, ',', 1, 3, null),
/**
* ISO8601_BASIC time format: {@code "yyyyMMdd'T'HHmmss.SSS"}.
*/
- ISO8601_BASIC_PERIOD("yyyyMMdd'T'HHmmss.SSS", "yyyyMMdd'T'", 2, ' ', 0, '.', 1, 3),
+ ISO8601_BASIC_PERIOD("yyyyMMdd'T'HHmmss.SSS", "yyyyMMdd'T'", 2, ' ', 0, '.', 1, 3, null),
/**
* ISO8601 time format: {@code "yyyy-MM-dd'T'HH:mm:ss,SSS"}.
*/
- ISO8601("yyyy-MM-dd'T'HH:mm:ss,SSS", "yyyy-MM-dd'T'", 2, ':', 1, ',', 1, 3),
+ ISO8601("yyyy-MM-dd'T'HH:mm:ss,SSS", "yyyy-MM-dd'T'", 2, ':', 1, ',', 1, 3, null),
+
+// TODO Do we even want a format without seconds?
+// /**
+// * ISO8601_OFFSET_DATE_TIME time format: {@code "yyyy-MM-dd'T'HH:mmXXX"}.
+// */
+// // Would need work in org.apache.logging.log4j.core.util.datetime.FixedDateFormat.writeTime(int, char[], int)
+// ISO8601_OFFSET_DATE_TIME("yyyy-MM-dd'T'HH:mmXXX", "yyyy-MM-dd'T'", 2, ':', 1, ' ', 0, 0, FixedTimeZoneFormat.XXX),
+
+ /**
+ * ISO8601_OFFSET_DATE_TIME_X time format: {@code "yyyy-MM-dd'T'HH:mm:ss,SSSX"}.
+ */
+ ISO8601_OFFSET_DATE_TIME_X("yyyy-MM-dd'T'HH:mm:ss,SSSX", "yyyy-MM-dd'T'", 2, ':', 1, ',', 1, 3, FixedTimeZoneFormat.X),
+
+ /**
+ * ISO8601_OFFSET_DATE_TIME_XX time format: {@code "yyyy-MM-dd'T'HH:mm:ss,SSSXX"}.
+ */
+ ISO8601_OFFSET_DATE_TIME_XX("yyyy-MM-dd'T'HH:mm:ss,SSSXX", "yyyy-MM-dd'T'", 2, ':', 1, ',', 1, 3, FixedTimeZoneFormat.XX),
+
+ /**
+ * ISO8601_OFFSET_DATE_TIME_XXX time format: {@code "yyyy-MM-dd'T'HH:mm:ss,SSSXXX"}.
+ */
+ ISO8601_OFFSET_DATE_TIME_XXX("yyyy-MM-dd'T'HH:mm:ss,SSSXXX", "yyyy-MM-dd'T'", 2, ':', 1, ',', 1, 3, FixedTimeZoneFormat.XXX),
+
+ /**
+ * ISO8601_OFFSET_DATE_TIME_Z time format: {@code "yyyy-MM-dd'T'HH:mm:ss,SSSZ"}.
+ */
+ ISO8601_OFFSET_DATE_TIME_Z("yyyy-MM-dd'T'HH:mm:ss,SSSZ", "yyyy-MM-dd'T'", 2, ':', 1, ',', 1, 3, FixedTimeZoneFormat.Z),
/**
* ISO8601 time format: {@code "yyyy-MM-dd'T'HH:mm:ss.SSS"}.
*/
- ISO8601_PERIOD("yyyy-MM-dd'T'HH:mm:ss.SSS", "yyyy-MM-dd'T'", 2, ':', 1, '.', 1, 3);
+ ISO8601_PERIOD("yyyy-MM-dd'T'HH:mm:ss.SSS", "yyyy-MM-dd'T'", 2, ':', 1, '.', 1, 3, null);
private static final String DEFAULT_SECOND_FRACTION_PATTERN = "SSS";
private static final int MILLI_FRACTION_DIGITS = DEFAULT_SECOND_FRACTION_PATTERN.length();
@@ -123,10 +153,11 @@ public class FixedDateFormat {
private final char millisSeparatorChar;
private final int millisSeparatorLength;
private final int secondFractionDigits;
+ private final FixedTimeZoneFormat timeZoneFormat;
FixedFormat(final String pattern, final String datePattern, final int escapeCount, final char timeSeparator,
final int timeSepLength, final char millisSeparator, final int millisSepLength,
- final int secondFractionDigits) {
+ final int secondFractionDigits, final FixedTimeZoneFormat timeZoneFormat) {
this.timeSeparatorChar = timeSeparator;
this.timeSeparatorLength = timeSepLength;
this.millisSeparatorChar = millisSeparator;
@@ -135,6 +166,7 @@ public class FixedDateFormat {
this.datePattern = datePattern; // may be null
this.escapeCount = escapeCount;
this.secondFractionDigits = secondFractionDigits;
+ this.timeZoneFormat = timeZoneFormat;
}
/**
@@ -171,9 +203,12 @@ public class FixedDateFormat {
}
static FixedFormat lookupIgnoringNanos(final String pattern) {
- final int nanoStart = nanoStart(pattern);
+ int[] nanoRange = nanoRange(pattern);
+ final int nanoStart = nanoRange[0];
+ final int nanoEnd = nanoRange[1];
if (nanoStart > 0) {
- final String subPattern = pattern.substring(0, nanoStart) + DEFAULT_SECOND_FRACTION_PATTERN;
+ final String subPattern = pattern.substring(0, nanoStart) + DEFAULT_SECOND_FRACTION_PATTERN
+ + pattern.substring(nanoEnd, pattern.length());
for (final FixedFormat type : FixedFormat.values()) {
if (type.getPattern().equals(subPattern)) {
return type;
@@ -183,16 +218,25 @@ public class FixedDateFormat {
return null;
}
- private static int nanoStart(final String pattern) {
- final int index = pattern.indexOf(SECOND_FRACTION_PATTERN);
- if (index >= 0) {
- for (int i = index + 1; i < pattern.length(); i++) {
+ private final static int[] EMPTY_RANGE = { -1, -1 };
+
+ /**
+ * @return int[0] start index inclusive; int[1] end index exclusive
+ */
+ private static int[] nanoRange(final String pattern) {
+ final int indexStart = pattern.indexOf(SECOND_FRACTION_PATTERN);
+ int indexEnd = -1;
+ if (indexStart >= 0) {
+ indexEnd = pattern.indexOf('Z', indexStart);
+ indexEnd = indexEnd < 0 ? pattern.indexOf('X', indexStart) : indexEnd;
+ indexEnd = indexEnd < 0 ? pattern.length() : indexEnd;
+ for (int i = indexStart + 1; i < indexEnd; i++) {
if (pattern.charAt(i) != SECOND_FRACTION_PATTERN) {
- return -1;
+ return EMPTY_RANGE;
}
}
}
- return index;
+ return new int [] {indexStart, indexEnd};
}
/**
@@ -241,6 +285,100 @@ public class FixedDateFormat {
public int getSecondFractionDigits() {
return secondFractionDigits;
}
+
+ /**
+ * Returns the optional time zone format.
+ * @return the optional time zone format, may be null.
+ */
+ public FixedTimeZoneFormat getTimeZoneFormat() {
+ return timeZoneFormat;
+ }
+ }
+
+ private static final char NONE = (char) 0;
+
+ /**
+ * Fixed time zone formats. The enum names are symbols from
+ * https://docs.oracle.com/javase/8/docs/api/java/time/format/DateTimeFormatter.html.
+ */
+ public enum FixedTimeZoneFormat {
+
+ /**
+ * Offset like {@code -07}
+ */
+ X(NONE, false, 3),
+
+ /**
+ * Offset like {@code -0700}.
+ * Same as Z.
+ */
+ XX(NONE, true, 5),
+
+ /**
+ * Offset like {@code -07:00}
+ */
+ XXX(':', true, 6),
+
+ /**
+ * Offset like {@code -0700}.
+ * Same as XX.
+ */
+ Z(NONE, true, 5);
+
+ private FixedTimeZoneFormat() {
+ this(NONE, true, 4);
+ }
+
+ private FixedTimeZoneFormat(char timeSeparatorChar, boolean minutes, int length) {
+ this.timeSeparatorChar = timeSeparatorChar;
+ this.timeSeparatorCharLen = timeSeparatorChar != NONE ? 1 : 0;
+ this.useMinutes = minutes;
+ this.length = length;
+ }
+
+ private final char timeSeparatorChar;
+ private final int timeSeparatorCharLen;
+ private final boolean useMinutes;
+ // The length includes 1 for the leading sign
+ private final int length;
+
+ public int getLength() {
+ return length;
+ }
+
+ // Profiling showed this method is important to log4j performance. Modify with care!
+ // 262 bytes (will be inlined when hot enough: <= -XX:FreqInlineSize=325 bytes on Linux)
+ private int write(int offset, final char[] buffer, int pos) {
+ // This method duplicates part of writeTime()
+
+ buffer[pos++] = offset < 0 ? '-' : '+';
+ final int absOffset = Math.abs(offset);
+ final int hours = absOffset / 3600000;
+ int ms = absOffset - (3600000 * hours);
+
+ // Hour
+ int temp = hours / 10;
+ buffer[pos++] = ((char) (temp + '0'));
+
+ // Do subtract to get remainder instead of doing % 10
+ buffer[pos++] = ((char) (hours - 10 * temp + '0'));
+
+ // Minute
+ if (useMinutes) {
+ buffer[pos] = timeSeparatorChar;
+ pos += timeSeparatorCharLen;
+ final int minutes = ms / 60000;
+ ms -= 60000 * minutes;
+
+ temp = minutes / 10;
+ buffer[pos++] = ((char) (temp + '0'));
+
+ // Do subtract to get remainder instead of doing % 10
+ buffer[pos++] = ((char) (minutes - 10 * temp + '0'));
+ }
+ return pos;
+ }
+
}
private final FixedFormat fixedFormat;
@@ -252,6 +390,7 @@ public class FixedDateFormat {
private final char millisSeparatorChar;
private final int timeSeparatorLength;
private final int millisSeparatorLength;
+ private final FixedTimeZoneFormat timeZoneFormat;
private volatile long midnightToday = 0;
private volatile long midnightTomorrow = 0;
@@ -282,6 +421,7 @@ public class FixedDateFormat {
* Constructs a FixedDateFormat for the specified fixed format.
* <p>
* Package protected for unit tests.
+ * </p>
*
* @param fixedFormat the fixed format
* @param tz time zone
@@ -294,6 +434,7 @@ public class FixedDateFormat {
this.timeSeparatorLength = fixedFormat.timeSeparatorLength;
this.millisSeparatorChar = fixedFormat.millisSeparatorChar;
this.millisSeparatorLength = fixedFormat.millisSeparatorLength;
+ this.timeZoneFormat = fixedFormat.timeZoneFormat; // may be null
this.length = fixedFormat.getLength();
this.secondFractionDigits = Math.max(1, Math.min(9, secondFractionDigits));
this.fastDateFormat = fixedFormat.getFastDateFormat(tz);
@@ -314,12 +455,16 @@ public class FixedDateFormat {
tz = TimeZone.getDefault();
}
- final FixedFormat withNanos = FixedFormat.lookupIgnoringNanos(options[0]);
+ final String option0 = options[0];
+ final FixedFormat withNanos = FixedFormat.lookupIgnoringNanos(option0);
if (withNanos != null) {
- final int secondFractionDigits = options[0].length() - FixedFormat.nanoStart(options[0]);
+ final int[] nanoRange = FixedFormat.nanoRange(option0);
+ final int nanoStart = nanoRange[0];
+ final int nanoEnd = nanoRange[1];
+ final int secondFractionDigits = nanoEnd - nanoStart;
return new FixedDateFormat(withNanos, tz, secondFractionDigits);
}
- final FixedFormat type = FixedFormat.lookup(options[0]);
+ final FixedFormat type = FixedFormat.lookup(option0);
return type == null ? null : new FixedDateFormat(type, tz);
}
@@ -358,7 +503,6 @@ public class FixedDateFormat {
*
* @return the time zone
*/
-
public TimeZone getTimeZone() {
return timeZone;
}
@@ -436,20 +580,17 @@ public class FixedDateFormat {
}
public int formatInstant(final Instant instant, final char[] buffer, final int startPos) {
- int result = format(instant.getEpochMillisecond(), buffer, startPos);
+ final long epochMillisecond = instant.getEpochMillisecond();
+ int result = format(epochMillisecond, buffer, startPos);
result -= digitsLessThanThree();
- formatNanoOfMillisecond(instant.getNanoOfMillisecond(), buffer, startPos + result);
- return result + digitsMorePreciseThanMillis();
+ int pos = formatNanoOfMillisecond(instant.getNanoOfMillisecond(), buffer, startPos + result);
+ return writeTimeZone(epochMillisecond, buffer, pos);
}
private int digitsLessThanThree() { // in case user specified only 1 or 2 'n' format characters
return Math.max(0, FixedFormat.MILLI_FRACTION_DIGITS - secondFractionDigits);
}
- private int digitsMorePreciseThanMillis() {
- return Math.max(0, secondFractionDigits - FixedFormat.MILLI_FRACTION_DIGITS);
- }
-
// Profiling showed this method is important to log4j performance. Modify with care!
// 28 bytes (allows immediate JVM inlining: <= -XX:MaxInlineSize=35 bytes)
public String format(final long epochMillis) {
@@ -468,7 +609,8 @@ public class FixedDateFormat {
// int ms = (int) (time % 86400000);
final int ms = (int) (millisSinceMidnight(epochMillis));
writeDate(buffer, startPos);
- return writeTime(ms, buffer, startPos + dateLength) - startPos;
+ int pos = writeTime(ms, buffer, startPos + dateLength);
+ return pos - startPos;
}
// Profiling showed this method is important to log4j performance. Modify with care!
@@ -530,6 +672,13 @@ public class FixedDateFormat {
return pos;
}
+ private int writeTimeZone(final long epochMillis, final char[] buffer, int pos) {
+ if (timeZoneFormat != null) {
+ pos = timeZoneFormat.write(timeZone.getOffset(epochMillis), buffer, pos);
+ }
+ return pos;
+ }
+
static int[] TABLE = {
100000, // 0
10000, // 1
@@ -539,7 +688,7 @@ public class FixedDateFormat {
1, // 5
};
- private void formatNanoOfMillisecond(final int nanoOfMillisecond, final char[] buffer, int pos) {
+ private int formatNanoOfMillisecond(final int nanoOfMillisecond, final char[] buffer, int pos) {
int temp;
int remain = nanoOfMillisecond;
for (int i = 0; i < secondFractionDigits - FixedFormat.MILLI_FRACTION_DIGITS; i++) {
@@ -548,6 +697,7 @@ public class FixedDateFormat {
buffer[pos++] = ((char) (temp + '0'));
remain -= divisor * temp; // equivalent of remain % 10
}
+ return pos;
}
private int daylightSavingTime(final int hourOfDay) {
diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/rolling/OnStartupTriggeringPolicyTest.java b/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/rolling/OnStartupTriggeringPolicyTest.java
index 5f26338..1ea3aa2 100644
--- a/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/rolling/OnStartupTriggeringPolicyTest.java
+++ b/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/rolling/OnStartupTriggeringPolicyTest.java
@@ -79,6 +79,7 @@ public class OnStartupTriggeringPolicyTest {
final FileTime fileTime = FileTime.fromMillis(timeStamp);
final BasicFileAttributeView attrs = Files.getFileAttributeView(target, BasicFileAttributeView.class);
attrs.setTimes(fileTime, fileTime, fileTime);
+
final PatternLayout layout = PatternLayout.newBuilder().withPattern("%msg").withConfiguration(configuration)
.build();
final RolloverStrategy strategy = DefaultRolloverStrategy.createStrategy(null, null, null, "0", null, true,
diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/pattern/DatePatternConverterTest.java b/log4j-core/src/test/java/org/apache/logging/log4j/core/pattern/DatePatternConverterTest.java
index affb5b2..e391917 100644
--- a/log4j-core/src/test/java/org/apache/logging/log4j/core/pattern/DatePatternConverterTest.java
+++ b/log4j-core/src/test/java/org/apache/logging/log4j/core/pattern/DatePatternConverterTest.java
@@ -31,6 +31,8 @@ import org.apache.logging.log4j.core.util.Constants;
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.core.util.datetime.FixedDateFormat.FixedTimeZoneFormat;
+import org.apache.logging.log4j.util.Strings;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
@@ -50,9 +52,19 @@ public class DatePatternConverterTest {
*/
private static final String ISO8601_FORMAT = FixedDateFormat.FixedFormat.ISO8601.name();
+ /**
+ * ISO8601_OFFSET string literal.
+ */
+ private static final String ISO8601_OFFSET_DATE_TIME_XXX = FixedDateFormat.FixedFormat.ISO8601_OFFSET_DATE_TIME_XXX.name();
+
+ /**
+ * ISO8601_OFFSET_Z string literal.
+ */
+ private static final String ISO8601_OFFSET_Z_FORMAT = FixedDateFormat.FixedFormat.ISO8601_OFFSET_DATE_TIME_Z.name();
+
private static final String[] ISO8601_FORMAT_OPTIONS = {ISO8601_FORMAT};
- @Parameterized.Parameters
+ @Parameterized.Parameters(name = "threadLocalEnabled={0}")
public static Collection<Object[]> data() {
return Arrays.asList(new Object[][]{{Boolean.TRUE}, {Boolean.FALSE}});
}
@@ -133,6 +145,49 @@ public class DatePatternConverterTest {
}
@Test
+ public void testFormatLogEventStringBuilderIso8601TimezoneZ() {
+ final LogEvent event = new MyLogEvent();
+ final String[] optionsWithTimezone = {ISO8601_FORMAT, "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 testFormatLogEventStringBuilderIso8601TimezoneOffset() {
+ final LogEvent event = new MyLogEvent();
+ final String[] optionsWithTimezone = {ISO8601_OFFSET_DATE_TIME_XXX};
+ final DatePatternConverter converter = DatePatternConverter.newInstance(optionsWithTimezone);
+ final StringBuilder sb = new StringBuilder();
+ converter.format(event, sb);
+
+ final SimpleDateFormat sdf = new SimpleDateFormat(converter.getPattern());
+ final String expected = sdf.format(new Date(event.getTimeMillis()));
+ assertEquals(expected, sb.toString());
+ }
+
+ @Test
+ public void testFormatLogEventStringBuilderIso8601TimezoneOffsetZ() {
+ final LogEvent event = new MyLogEvent();
+ final String[] optionsWithTimezone = {ISO8601_OFFSET_Z_FORMAT};
+ final DatePatternConverter converter = DatePatternConverter.newInstance(optionsWithTimezone);
+ final StringBuilder sb = new StringBuilder();
+ converter.format(event, sb);
+
+ final SimpleDateFormat sdf = new SimpleDateFormat(converter.getPattern());
+ final String expected = sdf.format(new Date(event.getTimeMillis()));
+ assertEquals(expected, sb.toString());
+ }
+
+ @Test
public void testPredefinedFormatWithTimezone() {
for (final FixedDateFormat.FixedFormat format : FixedDateFormat.FixedFormat.values()) {
final String[] options = {format.name(), "PDT"}; // Pacific Daylight Time=UTC-8:00
@@ -151,37 +206,53 @@ public class DatePatternConverterTest {
}
private String precisePattern(final String pattern, final int precision) {
- final String seconds = pattern.substring(0, pattern.indexOf("SSS"));
- return seconds + "nnnnnnnnn".substring(0, 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 with all formats from one 'n' (100s of millis) to 'nnnnnnnnn' (nanosecond precision)
@Test
public void testPredefinedFormatWithAnyValidNanoPrecision() {
- final StringBuilder precise = new StringBuilder();
- final StringBuilder milli = new StringBuilder();
+ final StringBuilder preciseBuilder = new StringBuilder();
+ final StringBuilder milliBuilder = new StringBuilder();
final LogEvent event = new MyLogEvent();
for (final String timeZone : new String[]{"PDT", null}) { // Pacific Daylight Time=UTC-8:00
for (final FixedDateFormat.FixedFormat format : FixedDateFormat.FixedFormat.values()) {
for (int i = 1; i <= 9; i++) {
- if (format.getPattern().endsWith("n")) {
- continue; // ignore patterns that already have precise time formats
+ 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;
}
- precise.setLength(0);
- milli.setLength(0);
+ preciseBuilder.setLength(0);
+ milliBuilder.setLength(0);
- final String[] preciseOptions = {precisePattern(format.getPattern(), i), timeZone};
+ final String precisePattern = precisePattern(pattern, i);
+ final String[] preciseOptions = { precisePattern, timeZone };
final DatePatternConverter preciseConverter = DatePatternConverter.newInstance(preciseOptions);
- preciseConverter.format(event, precise);
-
- final String[] milliOptions = {format.getPattern(), timeZone};
- DatePatternConverter.newInstance(milliOptions).format(event, milli);
- milli.setLength(milli.length() - 3); // truncate millis
- final String expected = milli.append("987123456".substring(0, i)).toString();
-
- assertEquals(expected, precise.toString());
- //System.out.println(preciseOptions[0] + ": " + precise);
+ 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);
}
}
}
@@ -189,30 +260,52 @@ public class DatePatternConverterTest {
@Test
public void testInvalidLongPatternIgnoresExcessiveDigits() {
- final StringBuilder precise = new StringBuilder();
- final StringBuilder milli = new StringBuilder();
+ final StringBuilder preciseBuilder = new StringBuilder();
+ final StringBuilder milliBuilder = new StringBuilder();
final LogEvent event = new MyLogEvent();
- for (final FixedDateFormat.FixedFormat format : FixedDateFormat.FixedFormat.values()) {
- if (format.getPattern().endsWith("n")) {
- continue; // ignore patterns that already have precise time formats
- }
- precise.setLength(0);
- milli.setLength(0);
-
- final String pattern = format.getPattern().substring(0, format.getPattern().indexOf("SSS"));
- final String[] preciseOptions = {pattern + "nnnnnnnnn" + "n"}; // too long
- final DatePatternConverter preciseConverter = DatePatternConverter.newInstance(preciseOptions);
- preciseConverter.format(event, precise);
-
- final String[] milliOptions = {format.getPattern()};
- DatePatternConverter.newInstance(milliOptions).format(event, milli);
- milli.setLength(milli.length() - 3); // truncate millis
- final String expected = milli.append("987123456").toString();
-
- assertEquals(expected, precise.toString());
- //System.out.println(preciseOptions[0] + ": " + precise);
+ 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);
+ }
}
private class MyLogEvent extends AbstractLogEvent {
diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/util/datetime/FixedDateFormatTest.java b/log4j-core/src/test/java/org/apache/logging/log4j/core/util/datetime/FixedDateFormatTest.java
index e41c4e1..b36c977 100644
--- a/log4j-core/src/test/java/org/apache/logging/log4j/core/util/datetime/FixedDateFormatTest.java
+++ b/log4j-core/src/test/java/org/apache/logging/log4j/core/util/datetime/FixedDateFormatTest.java
@@ -17,6 +17,12 @@
package org.apache.logging.log4j.core.util.datetime;
+import static org.apache.logging.log4j.core.util.datetime.FixedDateFormat.FixedFormat.DEFAULT;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertSame;
+
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;
@@ -27,9 +33,6 @@ import java.util.concurrent.TimeUnit;
import org.apache.logging.log4j.core.util.datetime.FixedDateFormat.FixedFormat;
import org.junit.Test;
-import static org.apache.logging.log4j.core.util.datetime.FixedDateFormat.FixedFormat.*;
-import static org.junit.Assert.*;
-
/**
* Tests {@link FixedDateFormat}.
*/
@@ -156,15 +159,16 @@ public class FixedDateFormatTest {
final long start = now - TimeUnit.HOURS.toMillis(25);
final long end = now + TimeUnit.HOURS.toMillis(25);
for (final FixedFormat format : FixedFormat.values()) {
- if (format.getPattern().endsWith("n")) {
+ String pattern = format.getPattern();
+ if (containsNanos(format) || format.getTimeZoneFormat() != null) {
continue; // cannot compile precise timestamp formats with SimpleDateFormat
}
- final SimpleDateFormat simpleDF = new SimpleDateFormat(format.getPattern(), Locale.getDefault());
+ final SimpleDateFormat simpleDF = new SimpleDateFormat(pattern, Locale.getDefault());
final FixedDateFormat customTF = new FixedDateFormat(format, TimeZone.getDefault());
for (long time = start; time < end; time += 12345) {
final String actual = customTF.format(time);
final String expected = simpleDF.format(new Date(time));
- assertEquals(format + "(" + format.getPattern() + ")" + "/" + time, expected, actual);
+ assertEquals(format + "(" + pattern + ")" + "/" + time, expected, actual);
}
}
}
@@ -175,15 +179,16 @@ public class FixedDateFormatTest {
final long start = now - TimeUnit.HOURS.toMillis(25);
final long end = now + TimeUnit.HOURS.toMillis(25);
for (final FixedFormat format : FixedFormat.values()) {
- if (format.getPattern().endsWith("n")) {
+ String pattern = format.getPattern();
+ if (containsNanos(format) || format.getTimeZoneFormat() != null) {
continue; // cannot compile precise timestamp formats with SimpleDateFormat
}
- final SimpleDateFormat simpleDF = new SimpleDateFormat(format.getPattern(), Locale.getDefault());
+ final SimpleDateFormat simpleDF = new SimpleDateFormat(pattern, Locale.getDefault());
final FixedDateFormat customTF = new FixedDateFormat(format, TimeZone.getDefault());
for (long time = end; time > start; time -= 12345) {
final String actual = customTF.format(time);
final String expected = simpleDF.format(new Date(time));
- assertEquals(format + "(" + format.getPattern() + ")" + "/" + time, expected, actual);
+ assertEquals(format + "(" + pattern + ")" + "/" + time, expected, actual);
}
}
}
@@ -195,16 +200,19 @@ public class FixedDateFormatTest {
final long end = now + TimeUnit.HOURS.toMillis(25);
final char[] buffer = new char[128];
for (final FixedFormat format : FixedFormat.values()) {
- if (format.getPattern().endsWith("n")) {
- continue; // cannot compile precise timestamp formats with SimpleDateFormat
+ String pattern = format.getPattern();
+ if (containsNanos(format) || format.getTimeZoneFormat() != null) {
+ // cannot compile precise timestamp formats with SimpleDateFormat
+ // This format() API not include the TZ
+ continue;
}
- final SimpleDateFormat simpleDF = new SimpleDateFormat(format.getPattern(), Locale.getDefault());
+ final SimpleDateFormat simpleDF = new SimpleDateFormat(pattern, Locale.getDefault());
final FixedDateFormat customTF = new FixedDateFormat(format, TimeZone.getDefault());
for (long time = start; time < end; time += 12345) {
final int length = customTF.format(time, buffer, 23);
final String actual = new String(buffer, 23, length);
final String expected = simpleDF.format(new Date(time));
- assertEquals(format + "(" + format.getPattern() + ")" + "/" + time, expected, actual);
+ assertEquals(format + "(" + pattern + ")" + "/" + time, expected, actual);
}
}
}
@@ -216,16 +224,17 @@ public class FixedDateFormatTest {
final long end = now + TimeUnit.HOURS.toMillis(25);
final char[] buffer = new char[128];
for (final FixedFormat format : FixedFormat.values()) {
- if (format.getPattern().endsWith("n")) {
+ String pattern = format.getPattern();
+ if (pattern.endsWith("n") || pattern.matches(".+n+X*") || pattern.matches(".+n+Z*") || format.getTimeZoneFormat() != null) {
continue; // cannot compile precise timestamp formats with SimpleDateFormat
}
- final SimpleDateFormat simpleDF = new SimpleDateFormat(format.getPattern(), Locale.getDefault());
+ final SimpleDateFormat simpleDF = new SimpleDateFormat(pattern, Locale.getDefault());
final FixedDateFormat customTF = new FixedDateFormat(format, TimeZone.getDefault());
for (long time = end; time > start; time -= 12345) {
final int length = customTF.format(time, buffer, 23);
final String actual = new String(buffer, 23, length);
final String expected = simpleDF.format(new Date(time));
- assertEquals(format + "(" + format.getPattern() + ")" + "/" + time, expected, actual);
+ assertEquals(format + "(" + pattern + ")" + "/" + time, expected, actual);
}
}
}
@@ -360,6 +369,12 @@ public class FixedDateFormatTest {
calendar.add(Calendar.HOUR_OF_DAY, 1);
}
}
+
+ private boolean containsNanos(FixedFormat fixedFormat) {
+ final String pattern = fixedFormat.getPattern();
+ return pattern.endsWith("n") || pattern.matches(".+n+X*") || pattern.matches(".+n+Z*");
+ }
+
/**
* This test case validates date pattern before and after DST
* Base Date : 12 Mar 2017
@@ -374,15 +389,16 @@ public class FixedDateFormatTest {
final long end = now + TimeUnit.HOURS.toMillis(1);
for (final FixedFormat format : FixedFormat.values()) {
- if (format.getPattern().endsWith("n")) {
+ String pattern = format.getPattern();
+ if (containsNanos(format) || format.getTimeZoneFormat() != null) {
continue; // cannot compile precise timestamp formats with SimpleDateFormat
}
- final SimpleDateFormat simpleDF = new SimpleDateFormat(format.getPattern(), Locale.getDefault());
+ final SimpleDateFormat simpleDF = new SimpleDateFormat(pattern, Locale.getDefault());
final FixedDateFormat customTF = new FixedDateFormat(format, TimeZone.getDefault());
for (long time = end; time > start; time -= 12345) {
final String actual = customTF.format(time);
final String expected = simpleDF.format(new Date(time));
- assertEquals(format + "(" + format.getPattern() + ")" + "/" + time, expected, actual);
+ assertEquals(format + "(" + pattern + ")" + "/" + time, expected, actual);
}
}
}
diff --git a/src/site/xdoc/manual/layouts.xml.vm b/src/site/xdoc/manual/layouts.xml.vm
index fe72b0d..76de62f 100644
--- a/src/site/xdoc/manual/layouts.xml.vm
+++ b/src/site/xdoc/manual/layouts.xml.vm
@@ -747,6 +747,22 @@ WARN [main]: Message 2</pre>
<td>20121102T143402,781</td>
</tr>
<tr>
+ <td>%d{ISO8601_OFFSET_DATE_TIME_X}</td>
+ <td>2012-11-02'T'14:34:02,781-07</td>
+ </tr>
+ <tr>
+ <td>%d{ISO8601_OFFSET_DATE_TIME_XX}</td>
+ <td>2012-11-02'T'14:34:02,781-0700</td>
+ </tr>
+ <tr>
+ <td>%d{ISO8601_OFFSET_DATE_TIME_XXX}</td>
+ <td>2012-11-02'T'14:34:02,781-07:00</td>
+ </tr>
+ <tr>
+ <td>%d{ISO8601_OFFSET_DATE_TIME_Z}</td>
+ <td>2012-11-02'T'14:34:02,781-0700</td>
+ </tr>
+ <tr>
<td>%d{ABSOLUTE}</td>
<td>14:34:02,781</td>
</tr>