blob: 4316a59cbdabee0ab59a15062a60db70408d405b [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache license, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the license for the specific language governing permissions and
* limitations under the license.
*/
package org.apache.logging.log4j.core.time.internal.format;
import java.util.Arrays;
import java.util.Calendar;
import java.util.Objects;
import java.util.TimeZone;
import java.util.concurrent.TimeUnit;
import org.apache.logging.log4j.core.time.Instant;
/**
* Custom time formatter that trades flexibility for performance. This formatter only supports the date patterns defined
* in {@link FixedFormat}. For any other date patterns use {@link FastDateFormat}.
* <p>
* Related benchmarks: /log4j-perf/src/main/java/org/apache/logging/log4j/perf/jmh/TimeFormatBenchmark.java and
* /log4j-perf/src/main/java/org/apache/logging/log4j/perf/jmh/ThreadsafeDateFormatBenchmark.java
* </p>
*/
public class FixedDateFormat {
/**
* Enumeration over the supported date/time format patterns.
* <p>
* Package protected for unit tests.
* </p>
*/
public enum FixedFormat {
/**
* ABSOLUTE time format: {@code "HH:mm:ss,SSS"}.
*/
ABSOLUTE("HH:mm:ss,SSS", null, 0, ':', 1, ',', 1, 3, null),
/**
* ABSOLUTE time format with microsecond precision: {@code "HH:mm:ss,nnnnnn"}.
*/
ABSOLUTE_MICROS("HH:mm:ss,nnnnnn", null, 0, ':', 1, ',', 1, 6, null),
/**
* ABSOLUTE time format with nanosecond precision: {@code "HH:mm:ss,nnnnnnnnn"}.
*/
ABSOLUTE_NANOS("HH:mm:ss,nnnnnnnnn", null, 0, ':', 1, ',', 1, 9, null),
/**
* ABSOLUTE time format variation with period separator: {@code "HH:mm:ss.SSS"}.
*/
ABSOLUTE_PERIOD("HH:mm:ss.SSS", null, 0, ':', 1, '.', 1, 3, null),
/**
* COMPACT time format: {@code "yyyyMMddHHmmssSSS"}.
*/
COMPACT("yyyyMMddHHmmssSSS", "yyyyMMdd", 0, ' ', 0, ' ', 0, 3, null),
/**
* DATE_AND_TIME time format: {@code "dd MMM yyyy HH:mm:ss,SSS"}.
*/
DATE("dd MMM yyyy HH:mm:ss,SSS", "dd MMM yyyy ", 0, ':', 1, ',', 1, 3, null),
/**
* DATE_AND_TIME time format variation with period separator: {@code "dd MMM yyyy HH:mm:ss.SSS"}.
*/
DATE_PERIOD("dd MMM yyyy HH:mm:ss.SSS", "dd MMM yyyy ", 0, ':', 1, '.', 1, 3, null),
/**
* DEFAULT time format: {@code "yyyy-MM-dd HH:mm:ss,SSS"}.
*/
DEFAULT("yyyy-MM-dd HH:mm:ss,SSS", "yyyy-MM-dd ", 0, ':', 1, ',', 1, 3, null),
/**
* DEFAULT time format with microsecond precision: {@code "yyyy-MM-dd HH:mm:ss,nnnnnn"}.
*/
DEFAULT_MICROS("yyyy-MM-dd HH:mm:ss,nnnnnn", "yyyy-MM-dd ", 0, ':', 1, ',', 1, 6, null),
/**
* DEFAULT time format with nanosecond precision: {@code "yyyy-MM-dd HH:mm:ss,nnnnnnnnn"}.
*/
DEFAULT_NANOS("yyyy-MM-dd HH:mm:ss,nnnnnnnnn", "yyyy-MM-dd ", 0, ':', 1, ',', 1, 9, null),
/**
* DEFAULT time format variation with period separator: {@code "yyyy-MM-dd HH:mm:ss.SSS"}.
*/
DEFAULT_PERIOD("yyyy-MM-dd HH:mm:ss.SSS", "yyyy-MM-dd ", 0, ':', 1, '.', 1, 3, null),
/**
* ISO8601_BASIC time format: {@code "yyyyMMdd'T'HHmmss,SSS"}.
*/
ISO8601_BASIC("yyyyMMdd'T'HHmmss,SSS", "yyyyMMdd'T'", 2, ' ', 0, ',', 1, 3, null),
/**
* ISO8601_BASIC time format: {@code "yyyyMMdd'T'HHmmss.SSS"}.
*/
ISO8601_BASIC_PERIOD("yyyyMMdd'T'HHmmss.SSS", "yyyyMMdd'T'", 2, ' ', 0, '.', 1, 3, null),
/**
* ISO8601 time format: {@code "yyyy-MM-dd'T'HH:mm:ss,SSS"}.
*/
ISO8601("yyyy-MM-dd'T'HH:mm:ss,SSS", "yyyy-MM-dd'T'", 2, ':', 1, ',', 1, 3, null),
// TODO Do we even want a format without seconds?
// /**
// * ISO8601_OFFSET_DATE_TIME time format: {@code "yyyy-MM-dd'T'HH:mmXXX"}.
// */
// // Would need work in org.apache.logging.log4j.core.util.datetime.FixedDateFormat.writeTime(int, char[], int)
// ISO8601_OFFSET_DATE_TIME("yyyy-MM-dd'T'HH:mmXXX", "yyyy-MM-dd'T'", 2, ':', 1, ' ', 0, 0, FixedTimeZoneFormat.XXX),
/**
* ISO8601 time format: {@code "yyyy-MM-dd'T'HH:mm:ss,SSSX"} with a time zone like {@code -07}.
*/
ISO8601_OFFSET_DATE_TIME_HH("yyyy-MM-dd'T'HH:mm:ss,SSSX", "yyyy-MM-dd'T'", 2, ':', 1, ',', 1, 3,
FixedTimeZoneFormat.HH),
/**
* ISO8601 time format: {@code "yyyy-MM-dd'T'HH:mm:ss,SSSXX"} with a time zone like {@code -0700}.
*/
ISO8601_OFFSET_DATE_TIME_HHMM("yyyy-MM-dd'T'HH:mm:ss,SSSXX", "yyyy-MM-dd'T'", 2, ':', 1, ',', 1, 3,
FixedTimeZoneFormat.HHMM),
/**
* ISO8601 time format: {@code "yyyy-MM-dd'T'HH:mm:ss,SSSXXX"} with a time zone like {@code -07:00}.
*/
ISO8601_OFFSET_DATE_TIME_HHCMM("yyyy-MM-dd'T'HH:mm:ss,SSSXXX", "yyyy-MM-dd'T'", 2, ':', 1, ',', 1, 3,
FixedTimeZoneFormat.HHCMM),
/**
* ISO8601 time format: {@code "yyyy-MM-dd'T'HH:mm:ss.SSS"}.
*/
ISO8601_PERIOD("yyyy-MM-dd'T'HH:mm:ss.SSS", "yyyy-MM-dd'T'", 2, ':', 1, '.', 1, 3, null),
/**
* ISO8601 time format with support for microsecond precision: {@code "yyyy-MM-dd'T'HH:mm:ss.nnnnnn"}.
*/
ISO8601_PERIOD_MICROS("yyyy-MM-dd'T'HH:mm:ss.nnnnnn", "yyyy-MM-dd'T'", 2, ':', 1, '.', 1, 6, null);
private static final String DEFAULT_SECOND_FRACTION_PATTERN = "SSS";
private static final int MILLI_FRACTION_DIGITS = DEFAULT_SECOND_FRACTION_PATTERN.length();
private static final char SECOND_FRACTION_PATTERN = 'n';
private final String pattern;
private final String datePattern;
private final int escapeCount;
private final char timeSeparatorChar;
private final int timeSeparatorLength;
private final char millisSeparatorChar;
private final int millisSeparatorLength;
private final int secondFractionDigits;
private final FixedTimeZoneFormat fixedTimeZoneFormat;
FixedFormat(final String pattern, final String datePattern, final int escapeCount, final char timeSeparator,
final int timeSepLength, final char millisSeparator, final int millisSepLength,
final int secondFractionDigits, final FixedTimeZoneFormat fixedTimeZoneFormat) {
this.timeSeparatorChar = timeSeparator;
this.timeSeparatorLength = timeSepLength;
this.millisSeparatorChar = millisSeparator;
this.millisSeparatorLength = millisSepLength;
this.pattern = Objects.requireNonNull(pattern);
this.datePattern = datePattern; // may be null
this.escapeCount = escapeCount;
this.secondFractionDigits = secondFractionDigits;
this.fixedTimeZoneFormat = fixedTimeZoneFormat;
}
/**
* Returns the full pattern.
*
* @return the full pattern
*/
public String getPattern() {
return pattern;
}
/**
* Returns the date part of the pattern.
*
* @return the date part of the pattern
*/
public String getDatePattern() {
return datePattern;
}
/**
* Returns the FixedFormat with the name or pattern matching the specified string or {@code null} if not found.
*
* @param nameOrPattern the name or pattern to find a FixedFormat for
* @return the FixedFormat with the name or pattern matching the specified string
*/
public static FixedFormat lookup(final String nameOrPattern) {
for (final FixedFormat type : FixedFormat.values()) {
if (type.name().equals(nameOrPattern) || type.getPattern().equals(nameOrPattern)) {
return type;
}
}
return null;
}
static FixedFormat lookupIgnoringNanos(final String pattern) {
final int[] nanoRange = nanoRange(pattern);
final int nanoStart = nanoRange[0];
final int nanoEnd = nanoRange[1];
if (nanoStart > 0) {
final String subPattern = pattern.substring(0, nanoStart) + DEFAULT_SECOND_FRACTION_PATTERN
+ pattern.substring(nanoEnd, pattern.length());
for (final FixedFormat type : FixedFormat.values()) {
if (type.getPattern().equals(subPattern)) {
return type;
}
}
}
return null;
}
private final static int[] EMPTY_RANGE = { -1, -1 };
/**
* @return int[0] start index inclusive; int[1] end index exclusive
*/
private static int[] nanoRange(final String pattern) {
final int indexStart = pattern.indexOf(SECOND_FRACTION_PATTERN);
int indexEnd = -1;
if (indexStart >= 0) {
indexEnd = pattern.indexOf('Z', indexStart);
indexEnd = indexEnd < 0 ? pattern.indexOf('X', indexStart) : indexEnd;
indexEnd = indexEnd < 0 ? pattern.length() : indexEnd;
for (int i = indexStart + 1; i < indexEnd; i++) {
if (pattern.charAt(i) != SECOND_FRACTION_PATTERN) {
return EMPTY_RANGE;
}
}
}
return new int [] {indexStart, indexEnd};
}
/**
* Returns the optional time zone format.
* @return the optional time zone format, may be null.
*/
public FixedTimeZoneFormat getTimeZoneFormat() {
return fixedTimeZoneFormat;
}
/**
* Returns the length of the resulting formatted date and time strings.
*
* @return the length of the resulting formatted date and time strings
*/
public int getLength() {
return pattern.length() - escapeCount;
}
/**
* Returns the length of the date part of the resulting formatted string.
*
* @return the length of the date part of the resulting formatted string
*/
public int getDatePatternLength() {
return getDatePattern() == null ? 0 : getDatePattern().length() - escapeCount;
}
/**
* Returns the {@code FastDateFormat} object for formatting the date part of the pattern or {@code null} if the
* pattern does not have a date part.
*
* @return the {@code FastDateFormat} object for formatting the date part of the pattern or {@code null}
*/
public FastDateFormat getFastDateFormat() {
return getFastDateFormat(null);
}
/**
* Returns the {@code FastDateFormat} object for formatting the date part of the pattern or {@code null} if the
* pattern does not have a date part.
*
* @param tz the time zone to use
* @return the {@code FastDateFormat} object for formatting the date part of the pattern or {@code null}
*/
public FastDateFormat getFastDateFormat(final TimeZone tz) {
return getDatePattern() == null ? null : FastDateFormat.getInstance(getDatePattern(), tz);
}
/**
* Returns the number of digits specifying the fraction of the second to show
* @return 3 for millisecond precision, 6 for microsecond precision or 9 for nanosecond precision
*/
public int getSecondFractionDigits() {
return secondFractionDigits;
}
}
private static final char NONE = (char) 0;
/**
* Fixed time zone formats. The enum names are symbols from Java's <a href=
* "https://docs.oracle.com/javase/8/docs/api/java/time/format/DateTimeFormatter.html">DateTimeFormatter</a>.
*
* @see <a href=
* "https://docs.oracle.com/javase/8/docs/api/java/time/format/DateTimeFormatter.html">DateTimeFormatter</a>
*/
public enum FixedTimeZoneFormat {
/**
* Offset like {@code -07}
*/
HH(NONE, false, 3),
/**
* Offset like {@code -0700}.
* Same as Z.
*/
HHMM(NONE, true, 5),
/**
* Offset like {@code -07:00}
*/
HHCMM(':', true, 6);
private FixedTimeZoneFormat() {
this(NONE, true, 4);
}
private FixedTimeZoneFormat(final char timeSeparatorChar, final boolean minutes, final int length) {
this.timeSeparatorChar = timeSeparatorChar;
this.timeSeparatorCharLen = timeSeparatorChar != NONE ? 1 : 0;
this.useMinutes = minutes;
this.length = length;
}
private final char timeSeparatorChar;
private final int timeSeparatorCharLen;
private final boolean useMinutes;
// The length includes 1 for the leading sign
private final int length;
public int getLength() {
return length;
}
// Profiling showed this method is important to log4j performance. Modify with care!
// 262 bytes (will be inlined when hot enough: <= -XX:FreqInlineSize=325 bytes on Linux)
private int write(final int offset, final char[] buffer, int pos) {
// This method duplicates part of writeTime()
buffer[pos++] = offset < 0 ? '-' : '+';
final int absOffset = Math.abs(offset);
final int hours = absOffset / 3600000;
int ms = absOffset - (3600000 * hours);
// Hour
int temp = hours / 10;
buffer[pos++] = ((char) (temp + '0'));
// Do subtract to get remainder instead of doing % 10
buffer[pos++] = ((char) (hours - 10 * temp + '0'));
// Minute
if (useMinutes) {
buffer[pos] = timeSeparatorChar;
pos += timeSeparatorCharLen;
final int minutes = ms / 60000;
ms -= 60000 * minutes;
temp = minutes / 10;
buffer[pos++] = ((char) (temp + '0'));
// Do subtract to get remainder instead of doing % 10
buffer[pos++] = ((char) (minutes - 10 * temp + '0'));
}
return pos;
}
}
private final FixedFormat fixedFormat;
private final TimeZone timeZone;
private final int length;
private final int secondFractionDigits;
private final FastDateFormat fastDateFormat; // may be null
private final char timeSeparatorChar;
private final char millisSeparatorChar;
private final int timeSeparatorLength;
private final int millisSeparatorLength;
private final FixedTimeZoneFormat fixedTimeZoneFormat;
private volatile long midnightToday;
private volatile long midnightTomorrow;
private final int[] dstOffsets = new int[25];
// cachedDate does not need to be volatile because
// there is a write to a volatile field *after* cachedDate is modified,
// and there is a read from a volatile field *before* cachedDate is read.
// The Java memory model guarantees that because of the above,
// changes to cachedDate in one thread are visible to other threads.
// See http://g.oswego.edu/dl/jmm/cookbook.html
private char[] cachedDate; // may be null
private int dateLength;
/**
* Constructs a FixedDateFormat for the specified fixed format.
* <p>
* Package protected for unit tests.
* </p>
*
* @param fixedFormat the fixed format
* @param tz time zone
*/
FixedDateFormat(final FixedFormat fixedFormat, final TimeZone tz) {
this(fixedFormat, tz, fixedFormat.getSecondFractionDigits());
}
/**
* Constructs a FixedDateFormat for the specified fixed format.
* <p>
* Package protected for unit tests.
* </p>
*
* @param fixedFormat the fixed format
* @param tz time zone
* @param secondFractionDigits the number of digits specifying the fraction of the second to show
*/
FixedDateFormat(final FixedFormat fixedFormat, final TimeZone tz, final int secondFractionDigits) {
this.fixedFormat = Objects.requireNonNull(fixedFormat);
this.timeZone = Objects.requireNonNull(tz);
this.timeSeparatorChar = fixedFormat.timeSeparatorChar;
this.timeSeparatorLength = fixedFormat.timeSeparatorLength;
this.millisSeparatorChar = fixedFormat.millisSeparatorChar;
this.millisSeparatorLength = fixedFormat.millisSeparatorLength;
this.fixedTimeZoneFormat = fixedFormat.fixedTimeZoneFormat;
this.length = fixedFormat.getLength();
this.secondFractionDigits = Math.max(1, Math.min(9, secondFractionDigits));
this.fastDateFormat = fixedFormat.getFastDateFormat(tz);
}
public static FixedDateFormat createIfSupported(final String... options) {
if (options == null || options.length == 0 || options[0] == null) {
return new FixedDateFormat(FixedFormat.DEFAULT, TimeZone.getDefault());
}
final TimeZone tz;
if (options.length > 1) {
if (options[1] != null) {
String zoneId = options[1];
if (zoneId.startsWith("-") || zoneId.startsWith("+")) {
zoneId = "GMT" + zoneId;
}
tz = TimeZone.getTimeZone(zoneId);
} else {
tz = TimeZone.getDefault();
}
} else {
tz = TimeZone.getDefault();
}
final String option0 = options[0];
final FixedFormat withNanos = FixedFormat.lookupIgnoringNanos(option0);
if (withNanos != null) {
final int[] nanoRange = FixedFormat.nanoRange(option0);
final int nanoStart = nanoRange[0];
final int nanoEnd = nanoRange[1];
final int secondFractionDigits = nanoEnd - nanoStart;
return new FixedDateFormat(withNanos, tz, secondFractionDigits);
}
final FixedFormat type = FixedFormat.lookup(option0);
return type == null ? null : new FixedDateFormat(type, tz);
}
/**
* Returns a new {@code FixedDateFormat} object for the specified {@code FixedFormat} and a {@code TimeZone.getDefault()} TimeZone.
*
* @param format the format to use
* @return a new {@code FixedDateFormat} object
*/
public static FixedDateFormat create(final FixedFormat format) {
return new FixedDateFormat(format, TimeZone.getDefault());
}
/**
* Returns a new {@code FixedDateFormat} object for the specified {@code FixedFormat} and TimeZone.
*
* @param format the format to use
* @param tz the time zone to use
* @return a new {@code FixedDateFormat} object
*/
public static FixedDateFormat create(final FixedFormat format, final TimeZone tz) {
return new FixedDateFormat(format, tz != null ? tz : TimeZone.getDefault());
}
/**
* Returns the full pattern of the selected fixed format.
*
* @return the full date-time pattern
*/
public String getFormat() {
return fixedFormat.getPattern();
}
/**
* Returns the time zone.
*
* @return the time zone
*/
public TimeZone getTimeZone() {
return timeZone;
}
/**
* <p>Returns the number of milliseconds since midnight in the time zone that this {@code FixedDateFormat}
* was constructed with for the specified currentTime.</p>
* <p>As a side effect, this method updates the cached formatted date and the cached date demarcation timestamps
* when the specified current time is outside the previously set demarcation timestamps for the start or end
* of the current day.</p>
* @param currentTime the current time in millis since the epoch
* @return the number of milliseconds since midnight for the specified time
*/
// Profiling showed this method is important to log4j performance. Modify with care!
// 30 bytes (allows immediate JVM inlining: <= -XX:MaxInlineSize=35 bytes)
public long millisSinceMidnight(final long currentTime) {
if (currentTime >= midnightTomorrow || currentTime < midnightToday) {
updateMidnightMillis(currentTime);
}
return currentTime - midnightToday;
}
private void updateMidnightMillis(final long now) {
if (now >= midnightTomorrow || now < midnightToday) {
synchronized (this) {
updateCachedDate(now);
midnightToday = calcMidnightMillis(now, 0);
midnightTomorrow = calcMidnightMillis(now, 1);
updateDaylightSavingTime();
}
}
}
private long calcMidnightMillis(final long time, final int addDays) {
final Calendar cal = Calendar.getInstance(timeZone);
cal.setTimeInMillis(time);
cal.set(Calendar.HOUR_OF_DAY, 0);
cal.set(Calendar.MINUTE, 0);
cal.set(Calendar.SECOND, 0);
cal.set(Calendar.MILLISECOND, 0);
cal.add(Calendar.DATE, addDays);
return cal.getTimeInMillis();
}
private void updateDaylightSavingTime() {
Arrays.fill(dstOffsets, 0);
final int ONE_HOUR = (int) TimeUnit.HOURS.toMillis(1);
if (timeZone.getOffset(midnightToday) != timeZone.getOffset(midnightToday + 23 * ONE_HOUR)) {
for (int i = 0; i < dstOffsets.length; i++) {
final long time = midnightToday + i * ONE_HOUR;
dstOffsets[i] = timeZone.getOffset(time) - timeZone.getRawOffset();
}
if (dstOffsets[0] > dstOffsets[23]) { // clock is moved backwards.
// we obtain midnightTonight with Calendar.getInstance(TimeZone), so it already includes raw offset
for (int i = dstOffsets.length - 1; i >= 0; i--) {
dstOffsets[i] -= dstOffsets[0]; //
}
}
}
}
private void updateCachedDate(final long now) {
if (fastDateFormat != null) {
final StringBuilder result = fastDateFormat.format(now, new StringBuilder());
cachedDate = result.toString().toCharArray();
dateLength = result.length();
}
}
public String formatInstant(final Instant instant) {
final char[] result = new char[length << 1]; // double size for locales with lengthy DateFormatSymbols
final int written = formatInstant(instant, result, 0);
return new String(result, 0, written);
}
public int formatInstant(final Instant instant, final char[] buffer, final int startPos) {
final long epochMillisecond = instant.getEpochMillisecond();
int result = format(epochMillisecond, buffer, startPos);
result -= digitsLessThanThree();
final int pos = formatNanoOfMillisecond(instant.getNanoOfMillisecond(), buffer, startPos + result);
return writeTimeZone(epochMillisecond, buffer, pos);
}
private int digitsLessThanThree() { // in case user specified only 1 or 2 'n' format characters
return Math.max(0, FixedFormat.MILLI_FRACTION_DIGITS - secondFractionDigits);
}
// Profiling showed this method is important to log4j performance. Modify with care!
// 28 bytes (allows immediate JVM inlining: <= -XX:MaxInlineSize=35 bytes)
public String format(final long epochMillis) {
final char[] result = new char[length << 1]; // double size for locales with lengthy DateFormatSymbols
final int written = format(epochMillis, result, 0);
return new String(result, 0, written);
}
// Profiling showed this method is important to log4j performance. Modify with care!
// 31 bytes (allows immediate JVM inlining: <= -XX:MaxInlineSize=35 bytes)
public int format(final long epochMillis, final char[] buffer, final int startPos) {
// Calculate values by getting the ms values first and do then
// calculate the hour minute and second values divisions.
// Get daytime in ms: this does fit into an int
// int ms = (int) (time % 86400000);
final int ms = (int) (millisSinceMidnight(epochMillis));
writeDate(buffer, startPos);
final int pos = writeTime(ms, buffer, startPos + dateLength);
return pos - startPos;
}
// Profiling showed this method is important to log4j performance. Modify with care!
// 22 bytes (allows immediate JVM inlining: <= -XX:MaxInlineSize=35 bytes)
private void writeDate(final char[] buffer, final int startPos) {
if (cachedDate != null) {
System.arraycopy(cachedDate, 0, buffer, startPos, dateLength);
}
}
// Profiling showed this method is important to log4j performance. Modify with care!
// 262 bytes (will be inlined when hot enough: <= -XX:FreqInlineSize=325 bytes on Linux)
private int writeTime(int ms, final char[] buffer, int pos) {
final int hourOfDay = ms / 3600000;
final int hours = hourOfDay + daylightSavingTime(hourOfDay) / 3600000;
ms -= 3600000 * hourOfDay;
final int minutes = ms / 60000;
ms -= 60000 * minutes;
final int seconds = ms / 1000;
ms -= 1000 * seconds;
// Hour
int temp = hours / 10;
buffer[pos++] = ((char) (temp + '0'));
// Do subtract to get remainder instead of doing % 10
buffer[pos++] = ((char) (hours - 10 * temp + '0'));
buffer[pos] = timeSeparatorChar;
pos += timeSeparatorLength;
// Minute
temp = minutes / 10;
buffer[pos++] = ((char) (temp + '0'));
// Do subtract to get remainder instead of doing % 10
buffer[pos++] = ((char) (minutes - 10 * temp + '0'));
buffer[pos] = timeSeparatorChar;
pos += timeSeparatorLength;
// Second
temp = seconds / 10;
buffer[pos++] = ((char) (temp + '0'));
buffer[pos++] = ((char) (seconds - 10 * temp + '0'));
buffer[pos] = millisSeparatorChar;
pos += millisSeparatorLength;
// Millisecond
temp = ms / 100;
buffer[pos++] = ((char) (temp + '0'));
ms -= 100 * temp;
temp = ms / 10;
buffer[pos++] = ((char) (temp + '0'));
ms -= 10 * temp;
buffer[pos++] = ((char) (ms + '0'));
return pos;
}
private int writeTimeZone(final long epochMillis, final char[] buffer, int pos) {
if (fixedTimeZoneFormat != null) {
pos = fixedTimeZoneFormat.write(timeZone.getOffset(epochMillis), buffer, pos);
}
return pos;
}
static int[] TABLE = {
100000, // 0
10000, // 1
1000, // 2
100, // 3
10, // 4
1, // 5
};
private int formatNanoOfMillisecond(final int nanoOfMillisecond, final char[] buffer, int pos) {
int temp;
int remain = nanoOfMillisecond;
for (int i = 0; i < secondFractionDigits - FixedFormat.MILLI_FRACTION_DIGITS; i++) {
final int divisor = TABLE[i];
temp = remain / divisor;
buffer[pos++] = ((char) (temp + '0'));
remain -= divisor * temp; // equivalent of remain % 10
}
return pos;
}
private int daylightSavingTime(final int hourOfDay) {
return hourOfDay > 23 ? dstOffsets[23] : dstOffsets[hourOfDay];
}
}