View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements. See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache license, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License. You may obtain a copy of the License at
8    *
9    *      http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the license for the specific language governing permissions and
15   * limitations under the license.
16   */
17  
18  package org.apache.logging.log4j.core.util.datetime;
19  
20  import java.util.Calendar;
21  import java.util.Objects;
22  
23  /**
24   * Custom time formatter that trades flexibility for performance. This formatter only supports the date patterns defined
25   * in {@link FixedFormat}. For any other date patterns use {@link FastDateFormat}.
26   * <p>
27   * Related benchmarks: /log4j-perf/src/main/java/org/apache/logging/log4j/perf/jmh/TimeFormatBenchmark.java and
28   * /log4j-perf/src/main/java/org/apache/logging/log4j/perf/jmh/ThreadsafeDateFormatBenchmark.java
29   */
30  public class FixedDateFormat {
31      /**
32       * Enumeration over the supported date/time format patterns.
33       * <p>
34       * Package protected for unit tests.
35       */
36      public static enum FixedFormat {
37          /**
38           * ABSOLUTE time format: {@code "HH:mm:ss,SSS"}.
39           */
40          ABSOLUTE("HH:mm:ss,SSS", null, 0, ':', 1, ',', 1),
41  
42          /**
43           * ABSOLUTE time format variation with period separator: {@code "HH:mm:ss.SSS"}.
44           */
45          ABSOLUTE_PERIOD("HH:mm:ss.SSS", null, 0, ':', 1, '.', 1),
46  
47          /**
48           * COMPACT time format: {@code "yyyyMMddHHmmssSSS"}.
49           */
50          COMPACT("yyyyMMddHHmmssSSS", "yyyyMMdd", 0, ' ', 0, ' ', 0),
51  
52          /**
53           * DATE_AND_TIME time format: {@code "dd MMM yyyy HH:mm:ss,SSS"}.
54           */
55          DATE("dd MMM yyyy HH:mm:ss,SSS", "dd MMM yyyy ", 0, ':', 1, ',', 1),
56  
57          /**
58           * DATE_AND_TIME time format variation with period separator: {@code "dd MMM yyyy HH:mm:ss.SSS"}.
59           */
60          DATE_PERIOD("dd MMM yyyy HH:mm:ss.SSS", "dd MMM yyyy ", 0, ':', 1, '.', 1),
61  
62          /**
63           * DEFAULT time format: {@code "yyyy-MM-dd HH:mm:ss,SSS"}.
64           */
65          DEFAULT("yyyy-MM-dd HH:mm:ss,SSS", "yyyy-MM-dd ", 0, ':', 1, ',', 1),
66  
67          /**
68           * DEFAULT time format variation with period separator: {@code "yyyy-MM-dd HH:mm:ss.SSS"}.
69           */
70          DEFAULT_PERIOD("yyyy-MM-dd HH:mm:ss.SSS", "yyyy-MM-dd ", 0, ':', 1, '.', 1),
71  
72          /**
73           * ISO8601_BASIC time format: {@code "yyyyMMdd'T'HHmmss,SSS"}.
74           */
75          ISO8601_BASIC("yyyyMMdd'T'HHmmss,SSS", "yyyyMMdd'T'", 2, ' ', 0, ',', 1),
76  
77          /**
78           * ISO8601 time format: {@code "yyyy-MM-dd'T'HH:mm:ss,SSS"}.
79           */
80          ISO8601("yyyy-MM-dd'T'HH:mm:ss,SSS", "yyyy-MM-dd'T'", 2, ':', 1, ',', 1), ;
81  
82          private final String pattern;
83          private final String datePattern;
84          private final int escapeCount;
85          private final char timeSeparatorChar;
86          private final int timeSeparatorLength;
87          private final char millisSeparatorChar;
88          private final int millisSeparatorLength;
89  
90          private FixedFormat(final String pattern, final String datePattern, final int escapeCount,
91                  final char timeSeparator, final int timeSepLength, final char millisSeparator, final int millisSepLength) {
92              this.timeSeparatorChar = timeSeparator;
93              this.timeSeparatorLength = timeSepLength;
94              this.millisSeparatorChar = millisSeparator;
95              this.millisSeparatorLength = millisSepLength;
96              this.pattern = Objects.requireNonNull(pattern);
97              this.datePattern = datePattern; // may be null
98              this.escapeCount = escapeCount;
99          }
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 }