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 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          FixedFormat(final String pattern, final String datePattern, final int escapeCount, final char timeSeparator,
91                      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         /**
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 }