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