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 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 FixedFormat(final String pattern, final String datePattern, final int escapeCount, final char timeSeparator, 091 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 /** 102 * Returns the full pattern. 103 * 104 * @return the full pattern 105 */ 106 public String getPattern() { 107 return pattern; 108 } 109 110 /** 111 * Returns the date part of the pattern. 112 * 113 * @return the date part of the pattern 114 */ 115 public String getDatePattern() { 116 return datePattern; 117 } 118 119 /** 120 * Returns the FixedFormat with the name or pattern matching the specified string or {@code null} if not found. 121 * 122 * @param nameOrPattern the name or pattern to find a FixedFormat for 123 * @return the FixedFormat with the name or pattern matching the specified string 124 */ 125 public static FixedFormat lookup(final String nameOrPattern) { 126 for (final FixedFormat type : FixedFormat.values()) { 127 if (type.name().equals(nameOrPattern) || type.getPattern().equals(nameOrPattern)) { 128 return type; 129 } 130 } 131 return null; 132 } 133 134 /** 135 * Returns the length of the resulting formatted date and time strings. 136 * 137 * @return the length of the resulting formatted date and time strings 138 */ 139 public int getLength() { 140 return pattern.length() - escapeCount; 141 } 142 143 /** 144 * Returns the length of the date part of the resulting formatted string. 145 * 146 * @return the length of the date part of the resulting formatted string 147 */ 148 public int getDatePatternLength() { 149 return getDatePattern() == null ? 0 : getDatePattern().length() - escapeCount; 150 } 151 152 /** 153 * Returns the {@code FastDateFormat} object for formatting the date part of the pattern or {@code null} if the 154 * pattern does not have a date part. 155 * 156 * @return the {@code FastDateFormat} object for formatting the date part of the pattern or {@code null} 157 */ 158 public FastDateFormat getFastDateFormat() { 159 return getDatePattern() == null ? null : FastDateFormat.getInstance(getDatePattern()); 160 } 161 } 162 163 private final FixedFormat fixedFormat; 164 private final int length; 165 private final FastDateFormat fastDateFormat; // may be null 166 private final char timeSeparatorChar; 167 private final char millisSeparatorChar; 168 private final int timeSeparatorLength; 169 private final int millisSeparatorLength; 170 171 private volatile long midnightToday = 0; 172 private volatile long midnightTomorrow = 0; 173 // cachedDate does not need to be volatile because 174 // there is a write to a volatile field *after* cachedDate is modified, 175 // and there is a read from a volatile field *before* cachedDate is read. 176 // The Java memory model guarantees that because of the above, 177 // changes to cachedDate in one thread are visible to other threads. 178 // See http://g.oswego.edu/dl/jmm/cookbook.html 179 private char[] cachedDate; // may be null 180 private int dateLength; 181 182 /** 183 * Constructs a FixedDateFormat for the specified fixed format. 184 * <p> 185 * Package protected for unit tests. 186 * 187 * @param fixedFormat the fixed format 188 */ 189 FixedDateFormat(final FixedFormat fixedFormat) { 190 this.fixedFormat = Objects.requireNonNull(fixedFormat); 191 this.timeSeparatorChar = fixedFormat.timeSeparatorChar; 192 this.timeSeparatorLength = fixedFormat.timeSeparatorLength; 193 this.millisSeparatorChar = fixedFormat.millisSeparatorChar; 194 this.millisSeparatorLength = fixedFormat.millisSeparatorLength; 195 this.length = fixedFormat.getLength(); 196 this.fastDateFormat = fixedFormat.getFastDateFormat(); 197 } 198 199 public static FixedDateFormat createIfSupported(final String... options) { 200 if (options == null || options.length == 0 || options[0] == null) { 201 return new FixedDateFormat(FixedFormat.DEFAULT); 202 } 203 if (options.length > 1) { 204 return null; // time zone not supported 205 } 206 final FixedFormat type = FixedFormat.lookup(options[0]); 207 return type == null ? null : new FixedDateFormat(type); 208 } 209 210 /** 211 * Returns a new {@code FixedDateFormat} object for the specified {@code FixedFormat} and a {@code null} TimeZone. 212 * 213 * @param format the format to use 214 * @return a new {@code FixedDateFormat} object 215 */ 216 public static FixedDateFormat create(final FixedFormat format) { 217 return new FixedDateFormat(format); 218 } 219 220 /** 221 * Returns the full pattern of the selected fixed format. 222 * 223 * @return the full date-time pattern 224 */ 225 public String getFormat() { 226 return fixedFormat.getPattern(); 227 } 228 229 // Profiling showed this method is important to log4j performance. Modify with care! 230 // 30 bytes (allows immediate JVM inlining: <= -XX:MaxInlineSize=35 bytes) 231 private long millisSinceMidnight(final long now) { 232 if (now >= midnightTomorrow || now < midnightToday) { 233 updateMidnightMillis(now); 234 } 235 return now - midnightToday; 236 } 237 238 private void updateMidnightMillis(final long now) { 239 240 updateCachedDate(now); 241 242 midnightToday = calcMidnightMillis(now, 0); 243 midnightTomorrow = calcMidnightMillis(now, 1); 244 } 245 246 static long calcMidnightMillis(final long time, final int addDays) { 247 final Calendar cal = Calendar.getInstance(); 248 cal.setTimeInMillis(time); 249 cal.set(Calendar.HOUR_OF_DAY, 0); 250 cal.set(Calendar.MINUTE, 0); 251 cal.set(Calendar.SECOND, 0); 252 cal.set(Calendar.MILLISECOND, 0); 253 cal.add(Calendar.DATE, addDays); 254 return cal.getTimeInMillis(); 255 } 256 257 private void updateCachedDate(final long now) { 258 if (fastDateFormat != null) { 259 final StringBuilder result = fastDateFormat.format(now, new StringBuilder()); 260 cachedDate = result.toString().toCharArray(); 261 dateLength = result.length(); 262 } 263 } 264 265 // Profiling showed this method is important to log4j performance. Modify with care! 266 // 28 bytes (allows immediate JVM inlining: <= -XX:MaxInlineSize=35 bytes) 267 public String format(final long time) { 268 final char[] result = new char[length << 1]; // double size for locales with lengthy DateFormatSymbols 269 final int written = format(time, result, 0); 270 return new String(result, 0, written); 271 } 272 273 // Profiling showed this method is important to log4j performance. Modify with care! 274 // 31 bytes (allows immediate JVM inlining: <= -XX:MaxInlineSize=35 bytes) 275 public int format(final long time, final char[] buffer, final int startPos) { 276 // Calculate values by getting the ms values first and do then 277 // calculate the hour minute and second values divisions. 278 279 // Get daytime in ms: this does fit into an int 280 // int ms = (int) (time % 86400000); 281 final int ms = (int) (millisSinceMidnight(time)); 282 writeDate(buffer, startPos); 283 return writeTime(ms, buffer, startPos + dateLength) - startPos; 284 } 285 286 // Profiling showed this method is important to log4j performance. Modify with care! 287 // 22 bytes (allows immediate JVM inlining: <= -XX:MaxInlineSize=35 bytes) 288 private void writeDate(final char[] buffer, final int startPos) { 289 if (cachedDate != null) { 290 System.arraycopy(cachedDate, 0, buffer, startPos, dateLength); 291 } 292 } 293 294 // Profiling showed this method is important to log4j performance. Modify with care! 295 // 262 bytes (will be inlined when hot enough: <= -XX:FreqInlineSize=325 bytes on Linux) 296 private int writeTime(int ms, final char[] buffer, int pos) { 297 final int hours = ms / 3600000; 298 ms -= 3600000 * hours; 299 300 final int minutes = ms / 60000; 301 ms -= 60000 * minutes; 302 303 final int seconds = ms / 1000; 304 ms -= 1000 * seconds; 305 306 // Hour 307 int temp = hours / 10; 308 buffer[pos++] = ((char) (temp + '0')); 309 310 // Do subtract to get remainder instead of doing % 10 311 buffer[pos++] = ((char) (hours - 10 * temp + '0')); 312 buffer[pos] = timeSeparatorChar; 313 pos += timeSeparatorLength; 314 315 // Minute 316 temp = minutes / 10; 317 buffer[pos++] = ((char) (temp + '0')); 318 319 // Do subtract to get remainder instead of doing % 10 320 buffer[pos++] = ((char) (minutes - 10 * temp + '0')); 321 buffer[pos] = timeSeparatorChar; 322 pos += timeSeparatorLength; 323 324 // Second 325 temp = seconds / 10; 326 buffer[pos++] = ((char) (temp + '0')); 327 buffer[pos++] = ((char) (seconds - 10 * temp + '0')); 328 buffer[pos] = millisSeparatorChar; 329 pos += millisSeparatorLength; 330 331 // Millisecond 332 temp = ms / 100; 333 buffer[pos++] = ((char) (temp + '0')); 334 335 ms -= 100 * temp; 336 temp = ms / 10; 337 buffer[pos++] = ((char) (temp + '0')); 338 339 ms -= 10 * temp; 340 buffer[pos++] = ((char) (ms + '0')); 341 return pos; 342 } 343}