001/* 002 * Licensed to the Apache Software Foundation (ASF) under one or more 003 * contributor license agreements. See the NOTICE file distributed with 004 * this work for additional information regarding copyright ownership. 005 * The ASF licenses this file to You under the Apache license, Version 2.0 006 * (the "License"); you may not use this file except in compliance with 007 * the License. You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the license for the specific language governing permissions and 015 * limitations under the license. 016 */ 017 018package org.apache.logging.log4j.core.util.datetime; 019 020import java.util.Calendar; 021import java.util.Objects; 022 023/** 024 * Custom time formatter that trades flexibility for performance. This formatter only supports the date patterns defined 025 * in {@link FixedFormat}. For any other date patterns use {@link FastDateFormat}. 026 * <p> 027 * Related benchmarks: /log4j-perf/src/main/java/org/apache/logging/log4j/perf/jmh/TimeFormatBenchmark.java and 028 * /log4j-perf/src/main/java/org/apache/logging/log4j/perf/jmh/ThreadsafeDateFormatBenchmark.java 029 */ 030public class FixedDateFormat { 031 /** 032 * Enumeration over the supported date/time format patterns. 033 * <p> 034 * Package protected for unit tests. 035 */ 036 public static enum FixedFormat { 037 /** 038 * ABSOLUTE time format: {@code "HH:mm:ss,SSS"}. 039 */ 040 ABSOLUTE("HH:mm:ss,SSS", null, 0, ':', 1, ',', 1), 041 042 /** 043 * ABSOLUTE time format variation with period separator: {@code "HH:mm:ss.SSS"}. 044 */ 045 ABSOLUTE_PERIOD("HH:mm:ss.SSS", null, 0, ':', 1, '.', 1), 046 047 /** 048 * COMPACT time format: {@code "yyyyMMddHHmmssSSS"}. 049 */ 050 COMPACT("yyyyMMddHHmmssSSS", "yyyyMMdd", 0, ' ', 0, ' ', 0), 051 052 /** 053 * DATE_AND_TIME time format: {@code "dd MMM yyyy HH:mm:ss,SSS"}. 054 */ 055 DATE("dd MMM yyyy HH:mm:ss,SSS", "dd MMM yyyy ", 0, ':', 1, ',', 1), 056 057 /** 058 * DATE_AND_TIME time format variation with period separator: {@code "dd MMM yyyy HH:mm:ss.SSS"}. 059 */ 060 DATE_PERIOD("dd MMM yyyy HH:mm:ss.SSS", "dd MMM yyyy ", 0, ':', 1, '.', 1), 061 062 /** 063 * DEFAULT time format: {@code "yyyy-MM-dd HH:mm:ss,SSS"}. 064 */ 065 DEFAULT("yyyy-MM-dd HH:mm:ss,SSS", "yyyy-MM-dd ", 0, ':', 1, ',', 1), 066 067 /** 068 * DEFAULT time format variation with period separator: {@code "yyyy-MM-dd HH:mm:ss.SSS"}. 069 */ 070 DEFAULT_PERIOD("yyyy-MM-dd HH:mm:ss.SSS", "yyyy-MM-dd ", 0, ':', 1, '.', 1), 071 072 /** 073 * ISO8601_BASIC time format: {@code "yyyyMMdd'T'HHmmss,SSS"}. 074 */ 075 ISO8601_BASIC("yyyyMMdd'T'HHmmss,SSS", "yyyyMMdd'T'", 2, ' ', 0, ',', 1), 076 077 /** 078 * ISO8601 time format: {@code "yyyy-MM-dd'T'HH:mm:ss,SSS"}. 079 */ 080 ISO8601("yyyy-MM-dd'T'HH:mm:ss,SSS", "yyyy-MM-dd'T'", 2, ':', 1, ',', 1), ; 081 082 private final String pattern; 083 private final String datePattern; 084 private final int escapeCount; 085 private final char timeSeparatorChar; 086 private final int timeSeparatorLength; 087 private final char millisSeparatorChar; 088 private final int millisSeparatorLength; 089 090 private FixedFormat(final String pattern, final String datePattern, final int escapeCount, 091 final char timeSeparator, final int timeSepLength, final char millisSeparator, final int millisSepLength) { 092 this.timeSeparatorChar = timeSeparator; 093 this.timeSeparatorLength = timeSepLength; 094 this.millisSeparatorChar = millisSeparator; 095 this.millisSeparatorLength = millisSepLength; 096 this.pattern = Objects.requireNonNull(pattern); 097 this.datePattern = datePattern; // may be null 098 this.escapeCount = escapeCount; 099 } 100 101 public String getPattern() { 102 return pattern; 103 } 104 105 public String getDatePattern() { 106 return datePattern; 107 } 108 109 /** 110 * Returns the FixedFormat with the name or pattern matching the specified string or {@code null} if not found. 111 */ 112 public static FixedFormat lookup(final String nameOrPattern) { 113 for (final FixedFormat type : FixedFormat.values()) { 114 if (type.name().equals(nameOrPattern) || type.getPattern().equals(nameOrPattern)) { 115 return type; 116 } 117 } 118 return null; 119 } 120 121 public int getLength() { 122 return pattern.length() - escapeCount; 123 } 124 125 public int getDatePatternLength() { 126 return getDatePattern() == null ? 0 : getDatePattern().length() - escapeCount; 127 } 128 129 public FastDateFormat getFastDateFormat() { 130 return getDatePattern() == null ? null : FastDateFormat.getInstance(getDatePattern()); 131 } 132 } 133 134 public static FixedDateFormat createIfSupported(final String... options) { 135 if (options == null || options.length == 0 || options[0] == null) { 136 return new FixedDateFormat(FixedFormat.DEFAULT); 137 } 138 if (options.length > 1) { 139 return null; // time zone not supported 140 } 141 final FixedFormat type = FixedFormat.lookup(options[0]); 142 return type == null ? null : new FixedDateFormat(type); 143 } 144 145 public static FixedDateFormat create(FixedFormat format) { 146 return new FixedDateFormat(format); 147 } 148 149 private final FixedFormat fixedFormat; 150 private final int length; 151 private final int dateLength; 152 private final FastDateFormat fastDateFormat; // may be null 153 private final char timeSeparatorChar; 154 private final char millisSeparatorChar; 155 private final int timeSeparatorLength; 156 private final int millisSeparatorLength; 157 158 private volatile long midnightToday = 0; 159 private volatile long midnightTomorrow = 0; 160 // cachedDate does not need to be volatile because 161 // there is a write to a volatile field *after* cachedDate is modified, 162 // and there is a read from a volatile field *before* cachedDate is read. 163 // The Java memory model guarantees that because of the above, 164 // changes to cachedDate in one thread are visible to other threads. 165 // See http://g.oswego.edu/dl/jmm/cookbook.html 166 private char[] cachedDate; // may be null 167 168 /** 169 * Constructs a FixedDateFormat for the specified fixed format. 170 * <p> 171 * Package protected for unit tests. 172 * 173 * @param fixedFormat the fixed format 174 */ 175 FixedDateFormat(final FixedFormat fixedFormat) { 176 this.fixedFormat = Objects.requireNonNull(fixedFormat); 177 this.timeSeparatorChar = fixedFormat.timeSeparatorChar; 178 this.timeSeparatorLength = fixedFormat.timeSeparatorLength; 179 this.millisSeparatorChar = fixedFormat.millisSeparatorChar; 180 this.millisSeparatorLength = fixedFormat.millisSeparatorLength; 181 this.length = fixedFormat.getLength(); 182 this.dateLength = fixedFormat.getDatePatternLength(); 183 this.fastDateFormat = fixedFormat.getFastDateFormat(); 184 } 185 186 public String getFormat() { 187 return fixedFormat.getPattern(); 188 } 189 190 // Profiling showed this method is important to log4j performance. Modify with care! 191 // 30 bytes (allows immediate JVM inlining: <= -XX:MaxInlineSize=35 bytes) 192 private long millisSinceMidnight(final long now) { 193 if (now >= midnightTomorrow || now < midnightToday) { 194 updateMidnightMillis(now); 195 } 196 return now - midnightToday; 197 } 198 199 private void updateMidnightMillis(final long now) { 200 201 updateCachedDate(now); 202 203 midnightToday = calcMidnightMillis(now, 0); 204 midnightTomorrow = calcMidnightMillis(now, 1); 205 } 206 207 static long calcMidnightMillis(final long time, final int addDays) { 208 final Calendar cal = Calendar.getInstance(); 209 cal.setTimeInMillis(time); 210 cal.set(Calendar.HOUR_OF_DAY, 0); 211 cal.set(Calendar.MINUTE, 0); 212 cal.set(Calendar.SECOND, 0); 213 cal.set(Calendar.MILLISECOND, 0); 214 cal.add(Calendar.DATE, addDays); 215 return cal.getTimeInMillis(); 216 } 217 218 private void updateCachedDate(final long now) { 219 if (fastDateFormat != null) { 220 final StringBuilder result = fastDateFormat.format(now, new StringBuilder()); 221 cachedDate = result.toString().toCharArray(); 222 } 223 } 224 225 // Profiling showed this method is important to log4j performance. Modify with care! 226 // 28 bytes (allows immediate JVM inlining: <= -XX:MaxInlineSize=35 bytes) 227 public String format(final long time) { 228 final char[] result = new char[length]; 229 int written = format(time, result, 0); 230 return new String(result, 0, written); 231 } 232 233 // Profiling showed this method is important to log4j performance. Modify with care! 234 // 31 bytes (allows immediate JVM inlining: <= -XX:MaxInlineSize=35 bytes) 235 public int format(final long time, final char[] buffer, final int startPos) { 236 // Calculate values by getting the ms values first and do then 237 // calculate the hour minute and second values divisions. 238 239 // Get daytime in ms: this does fit into an int 240 // int ms = (int) (time % 86400000); 241 final int ms = (int) (millisSinceMidnight(time)); 242 writeDate(buffer, startPos); 243 return writeTime(ms, buffer, startPos + dateLength) - startPos; 244 } 245 246 // Profiling showed this method is important to log4j performance. Modify with care! 247 // 22 bytes (allows immediate JVM inlining: <= -XX:MaxInlineSize=35 bytes) 248 private void writeDate(final char[] buffer, final int startPos) { 249 if (cachedDate != null) { 250 System.arraycopy(cachedDate, 0, buffer, startPos, dateLength); 251 } 252 } 253 254 // Profiling showed this method is important to log4j performance. Modify with care! 255 // 262 bytes (will be inlined when hot enough: <= -XX:FreqInlineSize=325 bytes on Linux) 256 private int writeTime(int ms, final char[] buffer, int pos) { 257 final int hours = ms / 3600000; 258 ms -= 3600000 * hours; 259 260 final int minutes = ms / 60000; 261 ms -= 60000 * minutes; 262 263 final int seconds = ms / 1000; 264 ms -= 1000 * seconds; 265 266 // Hour 267 int temp = hours / 10; 268 buffer[pos++] = ((char) (temp + '0')); 269 270 // Do subtract to get remainder instead of doing % 10 271 buffer[pos++] = ((char) (hours - 10 * temp + '0')); 272 buffer[pos] = timeSeparatorChar; 273 pos += timeSeparatorLength; 274 275 // Minute 276 temp = minutes / 10; 277 buffer[pos++] = ((char) (temp + '0')); 278 279 // Do subtract to get remainder instead of doing % 10 280 buffer[pos++] = ((char) (minutes - 10 * temp + '0')); 281 buffer[pos] = timeSeparatorChar; 282 pos += timeSeparatorLength; 283 284 // Second 285 temp = seconds / 10; 286 buffer[pos++] = ((char) (temp + '0')); 287 buffer[pos++] = ((char) (seconds - 10 * temp + '0')); 288 buffer[pos] = millisSeparatorChar; 289 pos += millisSeparatorLength; 290 291 // Millisecond 292 temp = ms / 100; 293 buffer[pos++] = ((char) (temp + '0')); 294 295 ms -= 100 * temp; 296 temp = ms / 10; 297 buffer[pos++] = ((char) (temp + '0')); 298 299 ms -= 10 * temp; 300 buffer[pos++] = ((char) (ms + '0')); 301 return pos; 302 } 303}