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        /**
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;
392    private volatile long midnightTomorrow;
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}