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 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;
162 this.escapeCount = escapeCount;
163 this.secondFractionDigits = secondFractionDigits;
164 this.fixedTimeZoneFormat = timeZoneFormat;
165 }
166
167
168
169
170
171
172 public String getPattern() {
173 return pattern;
174 }
175
176
177
178
179
180
181 public String getDatePattern() {
182 return datePattern;
183 }
184
185
186
187
188
189
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
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
239
240
241
242 public int getLength() {
243 return pattern.length() - escapeCount;
244 }
245
246
247
248
249
250
251 public int getDatePatternLength() {
252 return getDatePattern() == null ? 0 : getDatePattern().length() - escapeCount;
253 }
254
255
256
257
258
259
260
261 public FastDateFormat getFastDateFormat() {
262 return getFastDateFormat(null);
263 }
264
265
266
267
268
269
270
271
272 public FastDateFormat getFastDateFormat(final TimeZone tz) {
273 return getDatePattern() == null ? null : FastDateFormat.getInstance(getDatePattern(), tz);
274 }
275
276
277
278
279
280 public int getSecondFractionDigits() {
281 return secondFractionDigits;
282 }
283
284
285
286
287
288 public FixedTimeZoneFormat getFixedTimeZoneFormat() {
289 return fixedTimeZoneFormat;
290 }
291 }
292
293 private static final char NONE = (char) 0;
294
295
296
297
298
299
300
301
302 public enum FixedTimeZoneFormat {
303
304
305
306
307 HH(NONE, false, 3),
308
309
310
311
312 HHMM(NONE, true, 5),
313
314
315
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
334 private final int length;
335
336 public int getLength() {
337 return length;
338 }
339
340
341
342 private int write(final int offset, final char[] buffer, int pos) {
343
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
351 int temp = hours / 10;
352 buffer[pos++] = ((char) (temp + '0'));
353
354
355 buffer[pos++] = ((char) (hours - 10 * temp + '0'));
356
357
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
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;
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
391
392
393
394
395
396 private char[] cachedDate;
397 private int dateLength;
398
399
400
401
402
403
404
405
406
407 FixedDateFormat(final FixedFormat fixedFormat, final TimeZone tz) {
408 this(fixedFormat, tz, fixedFormat.getSecondFractionDigits());
409 }
410
411
412
413
414
415
416
417
418
419
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;
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
464
465
466
467
468 public static FixedDateFormat create(final FixedFormat format) {
469 return new FixedDateFormat(format, TimeZone.getDefault());
470 }
471
472
473
474
475
476
477
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
485
486
487
488 public String getFormat() {
489 return fixedFormat.getPattern();
490 }
491
492
493
494
495
496
497 public TimeZone getTimeZone() {
498 return timeZone;
499 }
500
501
502
503
504
505
506
507
508
509
510
511
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]) {
551
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];
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() {
582 return Math.max(0, FixedFormat.MILLI_FRACTION_DIGITS - secondFractionDigits);
583 }
584
585
586
587 public String format(final long epochMillis) {
588 final char[] result = new char[length << 1];
589 final int written = format(epochMillis, result, 0);
590 return new String(result, 0, written);
591 }
592
593
594
595 public int format(final long epochMillis, final char[] buffer, final int startPos) {
596
597
598
599
600
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
608
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
616
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
629 int temp = hours / 10;
630 buffer[pos++] = ((char) (temp + '0'));
631
632
633 buffer[pos++] = ((char) (hours - 10 * temp + '0'));
634 buffer[pos] = timeSeparatorChar;
635 pos += timeSeparatorLength;
636
637
638 temp = minutes / 10;
639 buffer[pos++] = ((char) (temp + '0'));
640
641
642 buffer[pos++] = ((char) (minutes - 10 * temp + '0'));
643 buffer[pos] = timeSeparatorChar;
644 pos += timeSeparatorLength;
645
646
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
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,
675 10000,
676 1000,
677 100,
678 10,
679 1,
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;
690 }
691 return pos;
692 }
693
694 private int daylightSavingTime(final int hourOfDay) {
695 return hourOfDay > 23 ? dstOffsets[23] : dstOffsets[hourOfDay];
696 }
697 }