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}