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