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 org.apache.logging.log4j.core.time.Instant;
021
022import java.util.Arrays;
023import java.util.Calendar;
024import java.util.Objects;
025import java.util.TimeZone;
026import java.util.concurrent.TimeUnit;
027
028/**
029 * Custom time formatter that trades flexibility for performance. This formatter only supports the date patterns defined
030 * in {@link FixedFormat}. For any other date patterns use {@link FastDateFormat}.
031 * <p>
032 * Related benchmarks: /log4j-perf/src/main/java/org/apache/logging/log4j/perf/jmh/TimeFormatBenchmark.java and
033 * /log4j-perf/src/main/java/org/apache/logging/log4j/perf/jmh/ThreadsafeDateFormatBenchmark.java
034 * </p>
035 */
036public class FixedDateFormat {
037
038    /**
039     * Enumeration over the supported date/time format patterns.
040     * <p>
041     * Package protected for unit tests.
042     * </p>
043     */
044    public enum FixedFormat {
045        
046        /**
047         * ABSOLUTE time format: {@code "HH:mm:ss,SSS"}.
048         */
049        ABSOLUTE("HH:mm:ss,SSS", null, 0, ':', 1, ',', 1, 3, null),
050        /**
051         * ABSOLUTE time format with microsecond precision: {@code "HH:mm:ss,nnnnnn"}.
052         */
053        ABSOLUTE_MICROS("HH:mm:ss,nnnnnn", null, 0, ':', 1, ',', 1, 6, null),
054        /**
055         * ABSOLUTE time format with nanosecond precision: {@code "HH:mm:ss,nnnnnnnnn"}.
056         */
057        ABSOLUTE_NANOS("HH:mm:ss,nnnnnnnnn", null, 0, ':', 1, ',', 1, 9, null),
058
059        /**
060         * ABSOLUTE time format variation with period separator: {@code "HH:mm:ss.SSS"}.
061         */
062        ABSOLUTE_PERIOD("HH:mm:ss.SSS", null, 0, ':', 1, '.', 1, 3, null),
063
064        /**
065         * COMPACT time format: {@code "yyyyMMddHHmmssSSS"}.
066         */
067        COMPACT("yyyyMMddHHmmssSSS", "yyyyMMdd", 0, ' ', 0, ' ', 0, 3, null),
068
069        /**
070         * DATE_AND_TIME time format: {@code "dd MMM yyyy HH:mm:ss,SSS"}.
071         */
072        DATE("dd MMM yyyy HH:mm:ss,SSS", "dd MMM yyyy ", 0, ':', 1, ',', 1, 3, null),
073
074        /**
075         * DATE_AND_TIME time format variation with period separator: {@code "dd MMM yyyy HH:mm:ss.SSS"}.
076         */
077        DATE_PERIOD("dd MMM yyyy HH:mm:ss.SSS", "dd MMM yyyy ", 0, ':', 1, '.', 1, 3, null),
078
079        /**
080         * DEFAULT time format: {@code "yyyy-MM-dd HH:mm:ss,SSS"}.
081         */
082        DEFAULT("yyyy-MM-dd HH:mm:ss,SSS", "yyyy-MM-dd ", 0, ':', 1, ',', 1, 3, null),
083        /**
084         * DEFAULT time format with microsecond precision: {@code "yyyy-MM-dd HH:mm:ss,nnnnnn"}.
085         */
086        DEFAULT_MICROS("yyyy-MM-dd HH:mm:ss,nnnnnn", "yyyy-MM-dd ", 0, ':', 1, ',', 1, 6, null),
087        /**
088         * DEFAULT time format with nanosecond precision: {@code "yyyy-MM-dd HH:mm:ss,nnnnnnnnn"}.
089         */
090        DEFAULT_NANOS("yyyy-MM-dd HH:mm:ss,nnnnnnnnn", "yyyy-MM-dd ", 0, ':', 1, ',', 1, 9, null),
091
092        /**
093         * DEFAULT time format variation with period separator: {@code "yyyy-MM-dd HH:mm:ss.SSS"}.
094         */
095        DEFAULT_PERIOD("yyyy-MM-dd HH:mm:ss.SSS", "yyyy-MM-dd ", 0, ':', 1, '.', 1, 3, null),
096
097        /**
098         * ISO8601_BASIC time format: {@code "yyyyMMdd'T'HHmmss,SSS"}.
099         */
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}