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 org.apache.logging.log4j.core.time.Instant;
21  
22  import java.util.Arrays;
23  import java.util.Calendar;
24  import java.util.Objects;
25  import java.util.TimeZone;
26  import java.util.concurrent.TimeUnit;
27  
28  /**
29   * Custom time formatter that trades flexibility for performance. This formatter only supports the date patterns defined
30   * in {@link FixedFormat}. For any other date patterns use {@link FastDateFormat}.
31   * <p>
32   * Related benchmarks: /log4j-perf/src/main/java/org/apache/logging/log4j/perf/jmh/TimeFormatBenchmark.java and
33   * /log4j-perf/src/main/java/org/apache/logging/log4j/perf/jmh/ThreadsafeDateFormatBenchmark.java
34   * </p>
35   */
36  public class FixedDateFormat {
37  
38      /**
39       * Enumeration over the supported date/time format patterns.
40       * <p>
41       * Package protected for unit tests.
42       * </p>
43       */
44      public enum FixedFormat {
45          
46          /**
47           * ABSOLUTE time format: {@code "HH:mm:ss,SSS"}.
48           */
49          ABSOLUTE("HH:mm:ss,SSS", null, 0, ':', 1, ',', 1, 3, null),
50          /**
51           * ABSOLUTE time format with microsecond precision: {@code "HH:mm:ss,nnnnnn"}.
52           */
53          ABSOLUTE_MICROS("HH:mm:ss,nnnnnn", null, 0, ':', 1, ',', 1, 6, null),
54          /**
55           * ABSOLUTE time format with nanosecond precision: {@code "HH:mm:ss,nnnnnnnnn"}.
56           */
57          ABSOLUTE_NANOS("HH:mm:ss,nnnnnnnnn", null, 0, ':', 1, ',', 1, 9, null),
58  
59          /**
60           * ABSOLUTE time format variation with period separator: {@code "HH:mm:ss.SSS"}.
61           */
62          ABSOLUTE_PERIOD("HH:mm:ss.SSS", null, 0, ':', 1, '.', 1, 3, null),
63  
64          /**
65           * COMPACT time format: {@code "yyyyMMddHHmmssSSS"}.
66           */
67          COMPACT("yyyyMMddHHmmssSSS", "yyyyMMdd", 0, ' ', 0, ' ', 0, 3, null),
68  
69          /**
70           * DATE_AND_TIME time format: {@code "dd MMM yyyy HH:mm:ss,SSS"}.
71           */
72          DATE("dd MMM yyyy HH:mm:ss,SSS", "dd MMM yyyy ", 0, ':', 1, ',', 1, 3, null),
73  
74          /**
75           * DATE_AND_TIME time format variation with period separator: {@code "dd MMM yyyy HH:mm:ss.SSS"}.
76           */
77          DATE_PERIOD("dd MMM yyyy HH:mm:ss.SSS", "dd MMM yyyy ", 0, ':', 1, '.', 1, 3, null),
78  
79          /**
80           * DEFAULT time format: {@code "yyyy-MM-dd HH:mm:ss,SSS"}.
81           */
82          DEFAULT("yyyy-MM-dd HH:mm:ss,SSS", "yyyy-MM-dd ", 0, ':', 1, ',', 1, 3, null),
83          /**
84           * DEFAULT time format with microsecond precision: {@code "yyyy-MM-dd HH:mm:ss,nnnnnn"}.
85           */
86          DEFAULT_MICROS("yyyy-MM-dd HH:mm:ss,nnnnnn", "yyyy-MM-dd ", 0, ':', 1, ',', 1, 6, null),
87          /**
88           * DEFAULT time format with nanosecond precision: {@code "yyyy-MM-dd HH:mm:ss,nnnnnnnnn"}.
89           */
90          DEFAULT_NANOS("yyyy-MM-dd HH:mm:ss,nnnnnnnnn", "yyyy-MM-dd ", 0, ':', 1, ',', 1, 9, null),
91  
92          /**
93           * DEFAULT time format variation with period separator: {@code "yyyy-MM-dd HH:mm:ss.SSS"}.
94           */
95          DEFAULT_PERIOD("yyyy-MM-dd HH:mm:ss.SSS", "yyyy-MM-dd ", 0, ':', 1, '.', 1, 3, null),
96  
97          /**
98           * ISO8601_BASIC time format: {@code "yyyyMMdd'T'HHmmss,SSS"}.
99           */
100         ISO8601_BASIC("yyyyMMdd'T'HHmmss,SSS", "yyyyMMdd'T'", 2, ' ', 0, ',', 1, 3, null),
101 
102         /**
103          * ISO8601_BASIC time format: {@code "yyyyMMdd'T'HHmmss.SSS"}.
104          */
105         ISO8601_BASIC_PERIOD("yyyyMMdd'T'HHmmss.SSS", "yyyyMMdd'T'", 2, ' ', 0, '.', 1, 3, null),
106 
107         /**
108          * ISO8601 time format: {@code "yyyy-MM-dd'T'HH:mm:ss,SSS"}.
109          */
110         ISO8601("yyyy-MM-dd'T'HH:mm:ss,SSS", "yyyy-MM-dd'T'", 2, ':', 1, ',', 1, 3, null),
111 
112 // TODO Do we even want a format without seconds?
113 //        /**
114 //         * ISO8601_OFFSET_DATE_TIME time format: {@code "yyyy-MM-dd'T'HH:mmXXX"}.
115 //         */
116 //        // Would need work in org.apache.logging.log4j.core.util.datetime.FixedDateFormat.writeTime(int, char[], int)
117 //        ISO8601_OFFSET_DATE_TIME("yyyy-MM-dd'T'HH:mmXXX", "yyyy-MM-dd'T'", 2, ':', 1, ' ', 0, 0, FixedTimeZoneFormat.XXX),
118 
119         /**
120          * ISO8601 time format: {@code "yyyy-MM-dd'T'HH:mm:ss,SSSX"} with a time zone like {@code -07}.
121          */
122         ISO8601_OFFSET_DATE_TIME_HH("yyyy-MM-dd'T'HH:mm:ss,SSSX", "yyyy-MM-dd'T'", 2, ':', 1, ',', 1, 3, FixedTimeZoneFormat.HH),
123 
124         /**
125          * ISO8601 time format: {@code "yyyy-MM-dd'T'HH:mm:ss,SSSXX"} with a time zone like {@code -0700}.
126          */
127         ISO8601_OFFSET_DATE_TIME_HHMM("yyyy-MM-dd'T'HH:mm:ss,SSSXX", "yyyy-MM-dd'T'", 2, ':', 1, ',', 1, 3, FixedTimeZoneFormat.HHMM),
128 
129         /**
130          * ISO8601 time format: {@code "yyyy-MM-dd'T'HH:mm:ss,SSSXXX"} with a time zone like {@code -07:00}.
131          */
132         ISO8601_OFFSET_DATE_TIME_HHCMM("yyyy-MM-dd'T'HH:mm:ss,SSSXXX", "yyyy-MM-dd'T'", 2, ':', 1, ',', 1, 3, FixedTimeZoneFormat.HHCMM),
133 
134         /**
135          * ISO8601 time format: {@code "yyyy-MM-dd'T'HH:mm:ss.SSS"}.
136          */
137         ISO8601_PERIOD("yyyy-MM-dd'T'HH:mm:ss.SSS", "yyyy-MM-dd'T'", 2, ':', 1, '.', 1, 3, null),
138 
139         /**
140          * ISO8601 time format with support for microsecond precision: {@code "yyyy-MM-dd'T'HH:mm:ss.nnnnnn"}.
141          */
142         ISO8601_PERIOD_MICROS("yyyy-MM-dd'T'HH:mm:ss.nnnnnn", "yyyy-MM-dd'T'", 2, ':', 1, '.', 1, 6, null);
143 
144         private static final String DEFAULT_SECOND_FRACTION_PATTERN = "SSS";
145         private static final int MILLI_FRACTION_DIGITS = DEFAULT_SECOND_FRACTION_PATTERN.length();
146         private static final char SECOND_FRACTION_PATTERN = 'n';
147 
148         private final String pattern;
149         private final String datePattern;
150         private final int escapeCount;
151         private final char timeSeparatorChar;
152         private final int timeSeparatorLength;
153         private final char millisSeparatorChar;
154         private final int millisSeparatorLength;
155         private final int secondFractionDigits;
156         private final FixedTimeZoneFormat fixedTimeZoneFormat;
157 
158         FixedFormat(final String pattern, final String datePattern, final int escapeCount, final char timeSeparator,
159                     final int timeSepLength, final char millisSeparator, final int millisSepLength,
160                     final int secondFractionDigits, final FixedTimeZoneFormat timeZoneFormat) {
161             this.timeSeparatorChar = timeSeparator;
162             this.timeSeparatorLength = timeSepLength;
163             this.millisSeparatorChar = millisSeparator;
164             this.millisSeparatorLength = millisSepLength;
165             this.pattern = Objects.requireNonNull(pattern);
166             this.datePattern = datePattern; // may be null
167             this.escapeCount = escapeCount;
168             this.secondFractionDigits = secondFractionDigits;
169             this.fixedTimeZoneFormat = timeZoneFormat;
170         }
171 
172         /**
173          * Returns the full pattern.
174          *
175          * @return the full pattern
176          */
177         public String getPattern() {
178             return pattern;
179         }
180 
181         /**
182          * Returns the date part of the pattern.
183          *
184          * @return the date part of the pattern
185          */
186         public String getDatePattern() {
187             return datePattern;
188         }
189 
190         /**
191          * Returns the FixedFormat with the name or pattern matching the specified string or {@code null} if not found.
192          *
193          * @param nameOrPattern the name or pattern to find a FixedFormat for
194          * @return the FixedFormat with the name or pattern matching the specified string
195          */
196         public static FixedFormat lookup(final String nameOrPattern) {
197             for (final FixedFormat type : FixedFormat.values()) {
198                 if (type.name().equals(nameOrPattern) || type.getPattern().equals(nameOrPattern)) {
199                     return type;
200                 }
201             }
202             return null;
203         }
204 
205         static FixedFormat lookupIgnoringNanos(final String pattern) {
206             final int[] nanoRange = nanoRange(pattern);
207             final int nanoStart = nanoRange[0];
208             final int nanoEnd = nanoRange[1];
209             if (nanoStart > 0) {
210                 final String subPattern = pattern.substring(0, nanoStart) + DEFAULT_SECOND_FRACTION_PATTERN
211                         + pattern.substring(nanoEnd, pattern.length());
212                 for (final FixedFormat type : FixedFormat.values()) {
213                     if (type.getPattern().equals(subPattern)) {
214                         return type;
215                     }
216                 }
217             }
218             return null;
219         }
220 
221         private final static int[] EMPTY_RANGE = { -1, -1 };
222         
223         /**
224          * @return int[0] start index inclusive; int[1] end index exclusive
225          */
226         private static int[] nanoRange(final String pattern) {
227             final int indexStart = pattern.indexOf(SECOND_FRACTION_PATTERN);
228             int indexEnd = -1;
229             if (indexStart >= 0) {
230                 indexEnd = pattern.indexOf('Z', indexStart);
231                 indexEnd = indexEnd < 0 ? pattern.indexOf('X', indexStart) : indexEnd;
232                 indexEnd = indexEnd < 0 ? pattern.length() : indexEnd; 
233                 for (int i = indexStart + 1; i < indexEnd; i++) {
234                     if (pattern.charAt(i) != SECOND_FRACTION_PATTERN) {
235                         return EMPTY_RANGE;
236                     }
237                 }
238             }
239             return new int [] {indexStart, indexEnd};
240         }
241 
242         /**
243          * Returns the length of the resulting formatted date and time strings.
244          *
245          * @return the length of the resulting formatted date and time strings
246          */
247         public int getLength() {
248             return pattern.length() - escapeCount;
249         }
250 
251         /**
252          * Returns the length of the date part of the resulting formatted string.
253          *
254          * @return the length of the date part of the resulting formatted string
255          */
256         public int getDatePatternLength() {
257             return getDatePattern() == null ? 0 : getDatePattern().length() - escapeCount;
258         }
259 
260         /**
261          * Returns the {@code FastDateFormat} object for formatting the date part of the pattern or {@code null} if the
262          * pattern does not have a date part.
263          *
264          * @return the {@code FastDateFormat} object for formatting the date part of the pattern or {@code null}
265          */
266         public FastDateFormat getFastDateFormat() {
267             return getFastDateFormat(null);
268         }
269 
270         /**
271          * Returns the {@code FastDateFormat} object for formatting the date part of the pattern or {@code null} if the
272          * pattern does not have a date part.
273          *
274          * @param tz the time zone to use
275          * @return the {@code FastDateFormat} object for formatting the date part of the pattern or {@code null}
276          */
277         public FastDateFormat getFastDateFormat(final TimeZone tz) {
278             return getDatePattern() == null ? null : FastDateFormat.getInstance(getDatePattern(), tz);
279         }
280 
281         /**
282          * Returns the number of digits specifying the fraction of the second to show
283          * @return 3 for millisecond precision, 6 for microsecond precision or 9 for nanosecond precision
284          */
285         public int getSecondFractionDigits() {
286             return secondFractionDigits;
287         }
288 
289         /**
290          * Returns the optional time zone format.
291          * @return the optional time zone format, may be null.
292          */
293         public FixedTimeZoneFormat getFixedTimeZoneFormat() {
294             return fixedTimeZoneFormat;
295         }
296     }
297 
298     private static final char NONE = (char) 0;
299 
300     /**
301      * Fixed time zone formats. The enum names are symbols from Java's <a href=
302      * "https://docs.oracle.com/javase/8/docs/api/java/time/format/DateTimeFormatter.html">DateTimeFormatter</a>.
303      * 
304      * @see <a href=
305      * "https://docs.oracle.com/javase/8/docs/api/java/time/format/DateTimeFormatter.html">DateTimeFormatter</a>
306      */
307     public enum FixedTimeZoneFormat {
308 
309         /**
310          * Offset like {@code -07}.
311          */
312         HH(NONE, false, 3),
313 
314         /**
315          * Offset like {@code -0700}.
316          */
317         HHMM(NONE, true, 5), 
318         
319         /** 
320          * Offset like {@code -07:00}.
321          */
322         HHCMM(':', true, 6);
323         
324         private FixedTimeZoneFormat() {
325             this(NONE, true, 4);
326         }
327 
328         private FixedTimeZoneFormat(final char timeSeparatorChar, final boolean minutes, final int length) {
329             this.timeSeparatorChar = timeSeparatorChar;
330             this.timeSeparatorCharLen = timeSeparatorChar != NONE ? 1 : 0;
331             this.useMinutes = minutes;
332             this.length = length;
333         }
334 
335         private final char timeSeparatorChar;
336         private final int timeSeparatorCharLen;
337         private final boolean useMinutes;
338         // The length includes 1 for the leading sign 
339         private final int length;
340 
341         public int getLength() {
342             return length;
343         }
344 
345         // Profiling showed this method is important to log4j performance. Modify with care!
346         // 262 bytes (will be inlined when hot enough: <= -XX:FreqInlineSize=325 bytes on Linux)
347         private int write(final int offset, final char[] buffer, int pos) {
348             // This method duplicates part of writeTime()
349 
350             buffer[pos++] = offset < 0 ? '-' : '+';
351             final int absOffset = Math.abs(offset);
352             final int hours = absOffset / 3600000;
353             int ms = absOffset - (3600000 * hours);
354 
355             // Hour
356             int temp = hours / 10;
357             buffer[pos++] = ((char) (temp + '0'));
358 
359             // Do subtract to get remainder instead of doing % 10
360             buffer[pos++] = ((char) (hours - 10 * temp + '0'));
361 
362             // Minute
363             if (useMinutes) {
364                 buffer[pos] = timeSeparatorChar;
365                 pos += timeSeparatorCharLen;
366                 final int minutes = ms / 60000;
367                 ms -= 60000 * minutes;
368 
369                 temp = minutes / 10;
370                 buffer[pos++] = ((char) (temp + '0'));
371 
372                 // Do subtract to get remainder instead of doing % 10
373                 buffer[pos++] = ((char) (minutes - 10 * temp + '0'));
374             }
375             return pos;
376         }
377 
378     }
379 
380     private final FixedFormat fixedFormat;
381     private final TimeZone timeZone;
382     private final int length;
383     private final int secondFractionDigits;
384     private final FastDateFormat fastDateFormat; // may be null
385     private final char timeSeparatorChar;
386     private final char millisSeparatorChar;
387     private final int timeSeparatorLength;
388     private final int millisSeparatorLength;
389     private final FixedTimeZoneFormat fixedTimeZoneFormat;
390 
391     private volatile long midnightToday = 0;
392     private volatile long midnightTomorrow = 0;
393     private final int[] dstOffsets = new int[25];
394 
395     // cachedDate does not need to be volatile because
396     // there is a write to a volatile field *after* cachedDate is modified,
397     // and there is a read from a volatile field *before* cachedDate is read.
398     // The Java memory model guarantees that because of the above,
399     // changes to cachedDate in one thread are visible to other threads.
400     // See http://g.oswego.edu/dl/jmm/cookbook.html
401     private char[] cachedDate; // may be null
402     private int dateLength;
403 
404     /**
405      * Constructs a FixedDateFormat for the specified fixed format.
406      * <p>
407      * Package protected for unit tests.
408      *
409      * @param fixedFormat the fixed format
410      * @param tz time zone
411      */
412     FixedDateFormat(final FixedFormat fixedFormat, final TimeZone tz) {
413         this(fixedFormat, tz, fixedFormat.getSecondFractionDigits());
414     }
415 
416     /**
417      * Constructs a FixedDateFormat for the specified fixed format.
418      * <p>
419      * Package protected for unit tests.
420      * </p>
421      *
422      * @param fixedFormat the fixed format
423      * @param tz time zone
424      * @param secondFractionDigits the number of digits specifying the fraction of the second to show
425      */
426     FixedDateFormat(final FixedFormat fixedFormat, final TimeZone tz, final int secondFractionDigits) {
427         this.fixedFormat = Objects.requireNonNull(fixedFormat);
428         this.timeZone = Objects.requireNonNull(tz);
429         this.timeSeparatorChar = fixedFormat.timeSeparatorChar;
430         this.timeSeparatorLength = fixedFormat.timeSeparatorLength;
431         this.millisSeparatorChar = fixedFormat.millisSeparatorChar;
432         this.millisSeparatorLength = fixedFormat.millisSeparatorLength;
433         this.fixedTimeZoneFormat = fixedFormat.fixedTimeZoneFormat; // may be null
434         this.length = fixedFormat.getLength();
435         this.secondFractionDigits = Math.max(1, Math.min(9, secondFractionDigits));
436         this.fastDateFormat = fixedFormat.getFastDateFormat(tz);
437     }
438 
439     public static FixedDateFormat createIfSupported(final String... options) {
440         if (options == null || options.length == 0 || options[0] == null) {
441             return new FixedDateFormat(FixedFormat.DEFAULT, TimeZone.getDefault());
442         }
443         final TimeZone tz;
444         if (options.length > 1) {
445             if (options[1] != null) {
446                 tz = TimeZone.getTimeZone(options[1]);
447             } else {
448                 tz = TimeZone.getDefault();
449             }
450         } else {
451             tz = TimeZone.getDefault();
452         }
453 
454         final String option0 = options[0];
455         final FixedFormat withNanos = FixedFormat.lookupIgnoringNanos(option0);
456         if (withNanos != null) {
457             final int[] nanoRange = FixedFormat.nanoRange(option0);
458             final int nanoStart = nanoRange[0];
459             final int nanoEnd = nanoRange[1];
460             final int secondFractionDigits = nanoEnd - nanoStart;
461             return new FixedDateFormat(withNanos, tz, secondFractionDigits);
462         }
463         final FixedFormat type = FixedFormat.lookup(option0);
464         return type == null ? null : new FixedDateFormat(type, tz);
465     }
466 
467     /**
468      * Returns a new {@code FixedDateFormat} object for the specified {@code FixedFormat} and a {@code TimeZone.getDefault()} TimeZone.
469      *
470      * @param format the format to use
471      * @return a new {@code FixedDateFormat} object
472      */
473     public static FixedDateFormat create(final FixedFormat format) {
474         return new FixedDateFormat(format, TimeZone.getDefault());
475     }
476 
477     /**
478      * Returns a new {@code FixedDateFormat} object for the specified {@code FixedFormat} and TimeZone.
479      *
480      * @param format the format to use
481      * @param tz the time zone to use
482      * @return a new {@code FixedDateFormat} object
483      */
484     public static FixedDateFormat create(final FixedFormat format, final TimeZone tz) {
485         return new FixedDateFormat(format, tz != null ? tz : TimeZone.getDefault());
486     }
487 
488     /**
489      * Returns the full pattern of the selected fixed format.
490      *
491      * @return the full date-time pattern
492      */
493     public String getFormat() {
494         return fixedFormat.getPattern();
495     }
496 
497     /**
498      * Returns the time zone.
499      *
500      * @return the time zone
501      */
502     public TimeZone getTimeZone() {
503         return timeZone;
504     }
505 
506     /**
507      * <p>Returns the number of milliseconds since midnight in the time zone that this {@code FixedDateFormat}
508      * was constructed with for the specified currentTime.</p>
509      * <p>As a side effect, this method updates the cached formatted date and the cached date demarcation timestamps
510      * when the specified current time is outside the previously set demarcation timestamps for the start or end
511      * of the current day.</p>
512      * @param currentTime the current time in millis since the epoch
513      * @return the number of milliseconds since midnight for the specified time
514      */
515     // Profiling showed this method is important to log4j performance. Modify with care!
516     // 30 bytes (allows immediate JVM inlining: <= -XX:MaxInlineSize=35 bytes)
517     public long millisSinceMidnight(final long currentTime) {
518         if (currentTime >= midnightTomorrow || currentTime < midnightToday) {
519             updateMidnightMillis(currentTime);
520         }
521         return currentTime - midnightToday;
522     }
523 
524     private void updateMidnightMillis(final long now) {
525         if (now >= midnightTomorrow || now < midnightToday) {
526             synchronized (this) {
527                 updateCachedDate(now);
528                 midnightToday = calcMidnightMillis(now, 0);
529                 midnightTomorrow = calcMidnightMillis(now, 1);
530 
531                 updateDaylightSavingTime();
532             }
533         }
534     }
535 
536     private long calcMidnightMillis(final long time, final int addDays) {
537         final Calendar cal = Calendar.getInstance(timeZone);
538         cal.setTimeInMillis(time);
539         cal.set(Calendar.HOUR_OF_DAY, 0);
540         cal.set(Calendar.MINUTE, 0);
541         cal.set(Calendar.SECOND, 0);
542         cal.set(Calendar.MILLISECOND, 0);
543         cal.add(Calendar.DATE, addDays);
544         return cal.getTimeInMillis();
545     }
546 
547     private void updateDaylightSavingTime() {
548         Arrays.fill(dstOffsets, 0);
549         final int ONE_HOUR = (int) TimeUnit.HOURS.toMillis(1);
550         if (timeZone.getOffset(midnightToday) != timeZone.getOffset(midnightToday + 23 * ONE_HOUR)) {
551             for (int i = 0; i < dstOffsets.length; i++) {
552                 final long time = midnightToday + i * ONE_HOUR;
553                 dstOffsets[i] = timeZone.getOffset(time) - timeZone.getRawOffset();
554             }
555             if (dstOffsets[0] > dstOffsets[23]) { // clock is moved backwards.
556                 // we obtain midnightTonight with Calendar.getInstance(TimeZone), so it already includes raw offset
557                 for (int i = dstOffsets.length - 1; i >= 0; i--) {
558                     dstOffsets[i] -= dstOffsets[0]; //
559                 }
560             }
561         }
562     }
563 
564     private void updateCachedDate(final long now) {
565         if (fastDateFormat != null) {
566             final StringBuilder result = fastDateFormat.format(now, new StringBuilder());
567             cachedDate = result.toString().toCharArray();
568             dateLength = result.length();
569         }
570     }
571 
572     public String formatInstant(final Instant instant) {
573         final char[] result = new char[length << 1]; // double size for locales with lengthy DateFormatSymbols
574         final int written = formatInstant(instant, result, 0);
575         return new String(result, 0, written);
576     }
577 
578     public int formatInstant(final Instant instant, final char[] buffer, final int startPos) {
579         final long epochMillisecond = instant.getEpochMillisecond();
580         int result = format(epochMillisecond, buffer, startPos);
581         result -= digitsLessThanThree();
582         final int pos = formatNanoOfMillisecond(instant.getNanoOfMillisecond(), buffer, startPos + result);
583         return writeTimeZone(epochMillisecond, buffer, pos);
584     }
585 
586     private int digitsLessThanThree() { // in case user specified only 1 or 2 'n' format characters
587         return Math.max(0, FixedFormat.MILLI_FRACTION_DIGITS - secondFractionDigits);
588     }
589 
590     // Profiling showed this method is important to log4j performance. Modify with care!
591     // 28 bytes (allows immediate JVM inlining: <= -XX:MaxInlineSize=35 bytes)
592     public String format(final long epochMillis) {
593         final char[] result = new char[length << 1]; // double size for locales with lengthy DateFormatSymbols
594         final int written = format(epochMillis, result, 0);
595         return new String(result, 0, written);
596     }
597 
598     // Profiling showed this method is important to log4j performance. Modify with care!
599     // 31 bytes (allows immediate JVM inlining: <= -XX:MaxInlineSize=35 bytes)
600     public int format(final long epochMillis, final char[] buffer, final int startPos) {
601         // Calculate values by getting the ms values first and do then
602         // calculate the hour minute and second values divisions.
603 
604         // Get daytime in ms: this does fit into an int
605         // int ms = (int) (time % 86400000);
606         final int ms = (int) (millisSinceMidnight(epochMillis));
607         writeDate(buffer, startPos);
608         final int pos = writeTime(ms, buffer, startPos + dateLength);
609         return pos - startPos;
610     }
611 
612     // Profiling showed this method is important to log4j performance. Modify with care!
613     // 22 bytes (allows immediate JVM inlining: <= -XX:MaxInlineSize=35 bytes)
614     private void writeDate(final char[] buffer, final int startPos) {
615         if (cachedDate != null) {
616             System.arraycopy(cachedDate, 0, buffer, startPos, dateLength);
617         }
618     }
619 
620     // Profiling showed this method is important to log4j performance. Modify with care!
621     // 262 bytes (will be inlined when hot enough: <= -XX:FreqInlineSize=325 bytes on Linux)
622     private int writeTime(int ms, final char[] buffer, int pos) {
623         final int hourOfDay = ms / 3600000;
624         final int hours = hourOfDay + daylightSavingTime(hourOfDay) / 3600000;
625         ms -= 3600000 * hourOfDay;
626 
627         final int minutes = ms / 60000;
628         ms -= 60000 * minutes;
629 
630         final int seconds = ms / 1000;
631         ms -= 1000 * seconds;
632 
633         // Hour
634         int temp = hours / 10;
635         buffer[pos++] = ((char) (temp + '0'));
636 
637         // Do subtract to get remainder instead of doing % 10
638         buffer[pos++] = ((char) (hours - 10 * temp + '0'));
639         buffer[pos] = timeSeparatorChar;
640         pos += timeSeparatorLength;
641 
642         // Minute
643         temp = minutes / 10;
644         buffer[pos++] = ((char) (temp + '0'));
645 
646         // Do subtract to get remainder instead of doing % 10
647         buffer[pos++] = ((char) (minutes - 10 * temp + '0'));
648         buffer[pos] = timeSeparatorChar;
649         pos += timeSeparatorLength;
650 
651         // Second
652         temp = seconds / 10;
653         buffer[pos++] = ((char) (temp + '0'));
654         buffer[pos++] = ((char) (seconds - 10 * temp + '0'));
655         buffer[pos] = millisSeparatorChar;
656         pos += millisSeparatorLength;
657 
658         // Millisecond
659         temp = ms / 100;
660         buffer[pos++] = ((char) (temp + '0'));
661 
662         ms -= 100 * temp;
663         temp = ms / 10;
664         buffer[pos++] = ((char) (temp + '0'));
665 
666         ms -= 10 * temp;
667         buffer[pos++] = ((char) (ms + '0'));
668         return pos;
669     }
670 
671     private int writeTimeZone(final long epochMillis, final char[] buffer, int pos) {
672         if (fixedTimeZoneFormat != null) {
673             pos = fixedTimeZoneFormat.write(timeZone.getOffset(epochMillis), buffer, pos);
674         }
675         return pos;
676     }
677 
678     static int[] TABLE = {
679             100000, // 0
680             10000, // 1
681             1000, // 2
682             100, // 3
683             10, // 4
684             1, // 5
685     };
686 
687     private int formatNanoOfMillisecond(final int nanoOfMillisecond, final char[] buffer, int pos) {
688         int temp;
689         int remain = nanoOfMillisecond;
690         for (int i = 0; i < secondFractionDigits - FixedFormat.MILLI_FRACTION_DIGITS; i++) {
691             final int divisor = TABLE[i];
692             temp = remain / divisor;
693             buffer[pos++] = ((char) (temp + '0'));
694             remain -= divisor * temp; // equivalent of remain % 10
695         }
696         return pos;
697     }
698 
699     private int daylightSavingTime(final int hourOfDay) {
700         return hourOfDay > 23 ? dstOffsets[23] : dstOffsets[hourOfDay];
701     }
702 }