1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18 package org.apache.logging.log4j.core.util.datetime;
19
20 import org.apache.logging.log4j.core.time.Instant;
21
22 import java.util.Arrays;
23 import java.util.Calendar;
24 import java.util.Objects;
25 import java.util.TimeZone;
26 import java.util.concurrent.TimeUnit;
27
28
29
30
31
32
33
34
35
36 public class FixedDateFormat {
37
38
39
40
41
42
43
44 public enum FixedFormat {
45
46
47
48
49 ABSOLUTE("HH:mm:ss,SSS", null, 0, ':', 1, ',', 1, 3, null),
50
51
52
53 ABSOLUTE_MICROS("HH:mm:ss,nnnnnn", null, 0, ':', 1, ',', 1, 6, null),
54
55
56
57 ABSOLUTE_NANOS("HH:mm:ss,nnnnnnnnn", null, 0, ':', 1, ',', 1, 9, null),
58
59
60
61
62 ABSOLUTE_PERIOD("HH:mm:ss.SSS", null, 0, ':', 1, '.', 1, 3, null),
63
64
65
66
67 COMPACT("yyyyMMddHHmmssSSS", "yyyyMMdd", 0, ' ', 0, ' ', 0, 3, null),
68
69
70
71
72 DATE("dd MMM yyyy HH:mm:ss,SSS", "dd MMM yyyy ", 0, ':', 1, ',', 1, 3, null),
73
74
75
76
77 DATE_PERIOD("dd MMM yyyy HH:mm:ss.SSS", "dd MMM yyyy ", 0, ':', 1, '.', 1, 3, null),
78
79
80
81
82 DEFAULT("yyyy-MM-dd HH:mm:ss,SSS", "yyyy-MM-dd ", 0, ':', 1, ',', 1, 3, null),
83
84
85
86 DEFAULT_MICROS("yyyy-MM-dd HH:mm:ss,nnnnnn", "yyyy-MM-dd ", 0, ':', 1, ',', 1, 6, null),
87
88
89
90 DEFAULT_NANOS("yyyy-MM-dd HH:mm:ss,nnnnnnnnn", "yyyy-MM-dd ", 0, ':', 1, ',', 1, 9, null),
91
92
93
94
95 DEFAULT_PERIOD("yyyy-MM-dd HH:mm:ss.SSS", "yyyy-MM-dd ", 0, ':', 1, '.', 1, 3, null),
96
97
98
99
100 ISO8601_BASIC("yyyyMMdd'T'HHmmss,SSS", "yyyyMMdd'T'", 2, ' ', 0, ',', 1, 3, null),
101
102
103
104
105 ISO8601_BASIC_PERIOD("yyyyMMdd'T'HHmmss.SSS", "yyyyMMdd'T'", 2, ' ', 0, '.', 1, 3, null),
106
107
108
109
110 ISO8601("yyyy-MM-dd'T'HH:mm:ss,SSS", "yyyy-MM-dd'T'", 2, ':', 1, ',', 1, 3, null),
111
112
113
114
115
116
117
118
119
120
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
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
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
136
137 ISO8601_PERIOD("yyyy-MM-dd'T'HH:mm:ss.SSS", "yyyy-MM-dd'T'", 2, ':', 1, '.', 1, 3, null),
138
139
140
141
142 ISO8601_PERIOD_MICROS("yyyy-MM-dd'T'HH:mm:ss.nnnnnn", "yyyy-MM-dd'T'", 2, ':', 1, '.', 1, 6, null);
143
144 private static final String DEFAULT_SECOND_FRACTION_PATTERN = "SSS";
145 private static final int MILLI_FRACTION_DIGITS = DEFAULT_SECOND_FRACTION_PATTERN.length();
146 private static final char SECOND_FRACTION_PATTERN = 'n';
147
148 private final String pattern;
149 private final String datePattern;
150 private final int escapeCount;
151 private final char timeSeparatorChar;
152 private final int timeSeparatorLength;
153 private final char millisSeparatorChar;
154 private final int millisSeparatorLength;
155 private final int secondFractionDigits;
156 private final FixedTimeZoneFormat fixedTimeZoneFormat;
157
158 FixedFormat(final String pattern, final String datePattern, final int escapeCount, final char timeSeparator,
159 final int timeSepLength, final char millisSeparator, final int millisSepLength,
160 final int secondFractionDigits, final FixedTimeZoneFormat timeZoneFormat) {
161 this.timeSeparatorChar = timeSeparator;
162 this.timeSeparatorLength = timeSepLength;
163 this.millisSeparatorChar = millisSeparator;
164 this.millisSeparatorLength = millisSepLength;
165 this.pattern = Objects.requireNonNull(pattern);
166 this.datePattern = datePattern;
167 this.escapeCount = escapeCount;
168 this.secondFractionDigits = secondFractionDigits;
169 this.fixedTimeZoneFormat = timeZoneFormat;
170 }
171
172
173
174
175
176
177 public String getPattern() {
178 return pattern;
179 }
180
181
182
183
184
185
186 public String getDatePattern() {
187 return datePattern;
188 }
189
190
191
192
193
194
195
196 public static FixedFormat lookup(final String nameOrPattern) {
197 for (final FixedFormat type : FixedFormat.values()) {
198 if (type.name().equals(nameOrPattern) || type.getPattern().equals(nameOrPattern)) {
199 return type;
200 }
201 }
202 return null;
203 }
204
205 static FixedFormat lookupIgnoringNanos(final String pattern) {
206 final int[] nanoRange = nanoRange(pattern);
207 final int nanoStart = nanoRange[0];
208 final int nanoEnd = nanoRange[1];
209 if (nanoStart > 0) {
210 final String subPattern = pattern.substring(0, nanoStart) + DEFAULT_SECOND_FRACTION_PATTERN
211 + pattern.substring(nanoEnd, pattern.length());
212 for (final FixedFormat type : FixedFormat.values()) {
213 if (type.getPattern().equals(subPattern)) {
214 return type;
215 }
216 }
217 }
218 return null;
219 }
220
221 private final static int[] EMPTY_RANGE = { -1, -1 };
222
223
224
225
226 private static int[] nanoRange(final String pattern) {
227 final int indexStart = pattern.indexOf(SECOND_FRACTION_PATTERN);
228 int indexEnd = -1;
229 if (indexStart >= 0) {
230 indexEnd = pattern.indexOf('Z', indexStart);
231 indexEnd = indexEnd < 0 ? pattern.indexOf('X', indexStart) : indexEnd;
232 indexEnd = indexEnd < 0 ? pattern.length() : indexEnd;
233 for (int i = indexStart + 1; i < indexEnd; i++) {
234 if (pattern.charAt(i) != SECOND_FRACTION_PATTERN) {
235 return EMPTY_RANGE;
236 }
237 }
238 }
239 return new int [] {indexStart, indexEnd};
240 }
241
242
243
244
245
246
247 public int getLength() {
248 return pattern.length() - escapeCount;
249 }
250
251
252
253
254
255
256 public int getDatePatternLength() {
257 return getDatePattern() == null ? 0 : getDatePattern().length() - escapeCount;
258 }
259
260
261
262
263
264
265
266 public FastDateFormat getFastDateFormat() {
267 return getFastDateFormat(null);
268 }
269
270
271
272
273
274
275
276
277 public FastDateFormat getFastDateFormat(final TimeZone tz) {
278 return getDatePattern() == null ? null : FastDateFormat.getInstance(getDatePattern(), tz);
279 }
280
281
282
283
284
285 public int getSecondFractionDigits() {
286 return secondFractionDigits;
287 }
288
289
290
291
292
293 public FixedTimeZoneFormat getFixedTimeZoneFormat() {
294 return fixedTimeZoneFormat;
295 }
296 }
297
298 private static final char NONE = (char) 0;
299
300
301
302
303
304
305
306
307 public enum FixedTimeZoneFormat {
308
309
310
311
312 HH(NONE, false, 3),
313
314
315
316
317 HHMM(NONE, true, 5),
318
319
320
321
322 HHCMM(':', true, 6);
323
324 private FixedTimeZoneFormat() {
325 this(NONE, true, 4);
326 }
327
328 private FixedTimeZoneFormat(final char timeSeparatorChar, final boolean minutes, final int length) {
329 this.timeSeparatorChar = timeSeparatorChar;
330 this.timeSeparatorCharLen = timeSeparatorChar != NONE ? 1 : 0;
331 this.useMinutes = minutes;
332 this.length = length;
333 }
334
335 private final char timeSeparatorChar;
336 private final int timeSeparatorCharLen;
337 private final boolean useMinutes;
338
339 private final int length;
340
341 public int getLength() {
342 return length;
343 }
344
345
346
347 private int write(final int offset, final char[] buffer, int pos) {
348
349
350 buffer[pos++] = offset < 0 ? '-' : '+';
351 final int absOffset = Math.abs(offset);
352 final int hours = absOffset / 3600000;
353 int ms = absOffset - (3600000 * hours);
354
355
356 int temp = hours / 10;
357 buffer[pos++] = ((char) (temp + '0'));
358
359
360 buffer[pos++] = ((char) (hours - 10 * temp + '0'));
361
362
363 if (useMinutes) {
364 buffer[pos] = timeSeparatorChar;
365 pos += timeSeparatorCharLen;
366 final int minutes = ms / 60000;
367 ms -= 60000 * minutes;
368
369 temp = minutes / 10;
370 buffer[pos++] = ((char) (temp + '0'));
371
372
373 buffer[pos++] = ((char) (minutes - 10 * temp + '0'));
374 }
375 return pos;
376 }
377
378 }
379
380 private final FixedFormat fixedFormat;
381 private final TimeZone timeZone;
382 private final int length;
383 private final int secondFractionDigits;
384 private final FastDateFormat fastDateFormat;
385 private final char timeSeparatorChar;
386 private final char millisSeparatorChar;
387 private final int timeSeparatorLength;
388 private final int millisSeparatorLength;
389 private final FixedTimeZoneFormat fixedTimeZoneFormat;
390
391 private volatile long midnightToday = 0;
392 private volatile long midnightTomorrow = 0;
393 private final int[] dstOffsets = new int[25];
394
395
396
397
398
399
400
401 private char[] cachedDate;
402 private int dateLength;
403
404
405
406
407
408
409
410
411
412 FixedDateFormat(final FixedFormat fixedFormat, final TimeZone tz) {
413 this(fixedFormat, tz, fixedFormat.getSecondFractionDigits());
414 }
415
416
417
418
419
420
421
422
423
424
425
426 FixedDateFormat(final FixedFormat fixedFormat, final TimeZone tz, final int secondFractionDigits) {
427 this.fixedFormat = Objects.requireNonNull(fixedFormat);
428 this.timeZone = Objects.requireNonNull(tz);
429 this.timeSeparatorChar = fixedFormat.timeSeparatorChar;
430 this.timeSeparatorLength = fixedFormat.timeSeparatorLength;
431 this.millisSeparatorChar = fixedFormat.millisSeparatorChar;
432 this.millisSeparatorLength = fixedFormat.millisSeparatorLength;
433 this.fixedTimeZoneFormat = fixedFormat.fixedTimeZoneFormat;
434 this.length = fixedFormat.getLength();
435 this.secondFractionDigits = Math.max(1, Math.min(9, secondFractionDigits));
436 this.fastDateFormat = fixedFormat.getFastDateFormat(tz);
437 }
438
439 public static FixedDateFormat createIfSupported(final String... options) {
440 if (options == null || options.length == 0 || options[0] == null) {
441 return new FixedDateFormat(FixedFormat.DEFAULT, TimeZone.getDefault());
442 }
443 final TimeZone tz;
444 if (options.length > 1) {
445 if (options[1] != null) {
446 tz = TimeZone.getTimeZone(options[1]);
447 } else {
448 tz = TimeZone.getDefault();
449 }
450 } else {
451 tz = TimeZone.getDefault();
452 }
453
454 final String option0 = options[0];
455 final FixedFormat withNanos = FixedFormat.lookupIgnoringNanos(option0);
456 if (withNanos != null) {
457 final int[] nanoRange = FixedFormat.nanoRange(option0);
458 final int nanoStart = nanoRange[0];
459 final int nanoEnd = nanoRange[1];
460 final int secondFractionDigits = nanoEnd - nanoStart;
461 return new FixedDateFormat(withNanos, tz, secondFractionDigits);
462 }
463 final FixedFormat type = FixedFormat.lookup(option0);
464 return type == null ? null : new FixedDateFormat(type, tz);
465 }
466
467
468
469
470
471
472
473 public static FixedDateFormat create(final FixedFormat format) {
474 return new FixedDateFormat(format, TimeZone.getDefault());
475 }
476
477
478
479
480
481
482
483
484 public static FixedDateFormat create(final FixedFormat format, final TimeZone tz) {
485 return new FixedDateFormat(format, tz != null ? tz : TimeZone.getDefault());
486 }
487
488
489
490
491
492
493 public String getFormat() {
494 return fixedFormat.getPattern();
495 }
496
497
498
499
500
501
502 public TimeZone getTimeZone() {
503 return timeZone;
504 }
505
506
507
508
509
510
511
512
513
514
515
516
517 public long millisSinceMidnight(final long currentTime) {
518 if (currentTime >= midnightTomorrow || currentTime < midnightToday) {
519 updateMidnightMillis(currentTime);
520 }
521 return currentTime - midnightToday;
522 }
523
524 private void updateMidnightMillis(final long now) {
525 if (now >= midnightTomorrow || now < midnightToday) {
526 synchronized (this) {
527 updateCachedDate(now);
528 midnightToday = calcMidnightMillis(now, 0);
529 midnightTomorrow = calcMidnightMillis(now, 1);
530
531 updateDaylightSavingTime();
532 }
533 }
534 }
535
536 private long calcMidnightMillis(final long time, final int addDays) {
537 final Calendar cal = Calendar.getInstance(timeZone);
538 cal.setTimeInMillis(time);
539 cal.set(Calendar.HOUR_OF_DAY, 0);
540 cal.set(Calendar.MINUTE, 0);
541 cal.set(Calendar.SECOND, 0);
542 cal.set(Calendar.MILLISECOND, 0);
543 cal.add(Calendar.DATE, addDays);
544 return cal.getTimeInMillis();
545 }
546
547 private void updateDaylightSavingTime() {
548 Arrays.fill(dstOffsets, 0);
549 final int ONE_HOUR = (int) TimeUnit.HOURS.toMillis(1);
550 if (timeZone.getOffset(midnightToday) != timeZone.getOffset(midnightToday + 23 * ONE_HOUR)) {
551 for (int i = 0; i < dstOffsets.length; i++) {
552 final long time = midnightToday + i * ONE_HOUR;
553 dstOffsets[i] = timeZone.getOffset(time) - timeZone.getRawOffset();
554 }
555 if (dstOffsets[0] > dstOffsets[23]) {
556
557 for (int i = dstOffsets.length - 1; i >= 0; i--) {
558 dstOffsets[i] -= dstOffsets[0];
559 }
560 }
561 }
562 }
563
564 private void updateCachedDate(final long now) {
565 if (fastDateFormat != null) {
566 final StringBuilder result = fastDateFormat.format(now, new StringBuilder());
567 cachedDate = result.toString().toCharArray();
568 dateLength = result.length();
569 }
570 }
571
572 public String formatInstant(final Instant instant) {
573 final char[] result = new char[length << 1];
574 final int written = formatInstant(instant, result, 0);
575 return new String(result, 0, written);
576 }
577
578 public int formatInstant(final Instant instant, final char[] buffer, final int startPos) {
579 final long epochMillisecond = instant.getEpochMillisecond();
580 int result = format(epochMillisecond, buffer, startPos);
581 result -= digitsLessThanThree();
582 final int pos = formatNanoOfMillisecond(instant.getNanoOfMillisecond(), buffer, startPos + result);
583 return writeTimeZone(epochMillisecond, buffer, pos);
584 }
585
586 private int digitsLessThanThree() {
587 return Math.max(0, FixedFormat.MILLI_FRACTION_DIGITS - secondFractionDigits);
588 }
589
590
591
592 public String format(final long epochMillis) {
593 final char[] result = new char[length << 1];
594 final int written = format(epochMillis, result, 0);
595 return new String(result, 0, written);
596 }
597
598
599
600 public int format(final long epochMillis, final char[] buffer, final int startPos) {
601
602
603
604
605
606 final int ms = (int) (millisSinceMidnight(epochMillis));
607 writeDate(buffer, startPos);
608 final int pos = writeTime(ms, buffer, startPos + dateLength);
609 return pos - startPos;
610 }
611
612
613
614 private void writeDate(final char[] buffer, final int startPos) {
615 if (cachedDate != null) {
616 System.arraycopy(cachedDate, 0, buffer, startPos, dateLength);
617 }
618 }
619
620
621
622 private int writeTime(int ms, final char[] buffer, int pos) {
623 final int hourOfDay = ms / 3600000;
624 final int hours = hourOfDay + daylightSavingTime(hourOfDay) / 3600000;
625 ms -= 3600000 * hourOfDay;
626
627 final int minutes = ms / 60000;
628 ms -= 60000 * minutes;
629
630 final int seconds = ms / 1000;
631 ms -= 1000 * seconds;
632
633
634 int temp = hours / 10;
635 buffer[pos++] = ((char) (temp + '0'));
636
637
638 buffer[pos++] = ((char) (hours - 10 * temp + '0'));
639 buffer[pos] = timeSeparatorChar;
640 pos += timeSeparatorLength;
641
642
643 temp = minutes / 10;
644 buffer[pos++] = ((char) (temp + '0'));
645
646
647 buffer[pos++] = ((char) (minutes - 10 * temp + '0'));
648 buffer[pos] = timeSeparatorChar;
649 pos += timeSeparatorLength;
650
651
652 temp = seconds / 10;
653 buffer[pos++] = ((char) (temp + '0'));
654 buffer[pos++] = ((char) (seconds - 10 * temp + '0'));
655 buffer[pos] = millisSeparatorChar;
656 pos += millisSeparatorLength;
657
658
659 temp = ms / 100;
660 buffer[pos++] = ((char) (temp + '0'));
661
662 ms -= 100 * temp;
663 temp = ms / 10;
664 buffer[pos++] = ((char) (temp + '0'));
665
666 ms -= 10 * temp;
667 buffer[pos++] = ((char) (ms + '0'));
668 return pos;
669 }
670
671 private int writeTimeZone(final long epochMillis, final char[] buffer, int pos) {
672 if (fixedTimeZoneFormat != null) {
673 pos = fixedTimeZoneFormat.write(timeZone.getOffset(epochMillis), buffer, pos);
674 }
675 return pos;
676 }
677
678 static int[] TABLE = {
679 100000,
680 10000,
681 1000,
682 100,
683 10,
684 1,
685 };
686
687 private int formatNanoOfMillisecond(final int nanoOfMillisecond, final char[] buffer, int pos) {
688 int temp;
689 int remain = nanoOfMillisecond;
690 for (int i = 0; i < secondFractionDigits - FixedFormat.MILLI_FRACTION_DIGITS; i++) {
691 final int divisor = TABLE[i];
692 temp = remain / divisor;
693 buffer[pos++] = ((char) (temp + '0'));
694 remain -= divisor * temp;
695 }
696 return pos;
697 }
698
699 private int daylightSavingTime(final int hourOfDay) {
700 return hourOfDay > 23 ? dstOffsets[23] : dstOffsets[hourOfDay];
701 }
702 }