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}