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 public class FixedDateFormat {
36
37
38
39
40
41
42 public enum FixedFormat {
43
44
45
46 ABSOLUTE("HH:mm:ss,SSS", null, 0, ':', 1, ',', 1, 3),
47
48
49
50 ABSOLUTE_MICROS("HH:mm:ss,nnnnnn", null, 0, ':', 1, ',', 1, 6),
51
52
53
54 ABSOLUTE_NANOS("HH:mm:ss,nnnnnnnnn", null, 0, ':', 1, ',', 1, 9),
55
56
57
58
59 ABSOLUTE_PERIOD("HH:mm:ss.SSS", null, 0, ':', 1, '.', 1, 3),
60
61
62
63
64 COMPACT("yyyyMMddHHmmssSSS", "yyyyMMdd", 0, ' ', 0, ' ', 0, 3),
65
66
67
68
69 DATE("dd MMM yyyy HH:mm:ss,SSS", "dd MMM yyyy ", 0, ':', 1, ',', 1, 3),
70
71
72
73
74 DATE_PERIOD("dd MMM yyyy HH:mm:ss.SSS", "dd MMM yyyy ", 0, ':', 1, '.', 1, 3),
75
76
77
78
79 DEFAULT("yyyy-MM-dd HH:mm:ss,SSS", "yyyy-MM-dd ", 0, ':', 1, ',', 1, 3),
80
81
82
83 DEFAULT_MICROS("yyyy-MM-dd HH:mm:ss,nnnnnn", "yyyy-MM-dd ", 0, ':', 1, ',', 1, 6),
84
85
86
87 DEFAULT_NANOS("yyyy-MM-dd HH:mm:ss,nnnnnnnnn", "yyyy-MM-dd ", 0, ':', 1, ',', 1, 9),
88
89
90
91
92 DEFAULT_PERIOD("yyyy-MM-dd HH:mm:ss.SSS", "yyyy-MM-dd ", 0, ':', 1, '.', 1, 3),
93
94
95
96
97 ISO8601_BASIC("yyyyMMdd'T'HHmmss,SSS", "yyyyMMdd'T'", 2, ' ', 0, ',', 1, 3),
98
99
100
101
102 ISO8601_BASIC_PERIOD("yyyyMMdd'T'HHmmss.SSS", "yyyyMMdd'T'", 2, ' ', 0, '.', 1, 3),
103
104
105
106
107 ISO8601("yyyy-MM-dd'T'HH:mm:ss,SSS", "yyyy-MM-dd'T'", 2, ':', 1, ',', 1, 3),
108
109
110
111
112 ISO8601_PERIOD("yyyy-MM-dd'T'HH:mm:ss.SSS", "yyyy-MM-dd'T'", 2, ':', 1, '.', 1, 3);
113
114 private static final String DEFAULT_SECOND_FRACTION_PATTERN = "SSS";
115 private static final int MILLI_FRACTION_DIGITS = DEFAULT_SECOND_FRACTION_PATTERN.length();
116 private static final char SECOND_FRACTION_PATTERN = 'n';
117
118 private final String pattern;
119 private final String datePattern;
120 private final int escapeCount;
121 private final char timeSeparatorChar;
122 private final int timeSeparatorLength;
123 private final char millisSeparatorChar;
124 private final int millisSeparatorLength;
125 private final int secondFractionDigits;
126
127 FixedFormat(final String pattern, final String datePattern, final int escapeCount, final char timeSeparator,
128 final int timeSepLength, final char millisSeparator, final int millisSepLength,
129 final int secondFractionDigits) {
130 this.timeSeparatorChar = timeSeparator;
131 this.timeSeparatorLength = timeSepLength;
132 this.millisSeparatorChar = millisSeparator;
133 this.millisSeparatorLength = millisSepLength;
134 this.pattern = Objects.requireNonNull(pattern);
135 this.datePattern = datePattern;
136 this.escapeCount = escapeCount;
137 this.secondFractionDigits = secondFractionDigits;
138 }
139
140
141
142
143
144
145 public String getPattern() {
146 return pattern;
147 }
148
149
150
151
152
153
154 public String getDatePattern() {
155 return datePattern;
156 }
157
158
159
160
161
162
163
164 public static FixedFormat lookup(final String nameOrPattern) {
165 for (final FixedFormat type : FixedFormat.values()) {
166 if (type.name().equals(nameOrPattern) || type.getPattern().equals(nameOrPattern)) {
167 return type;
168 }
169 }
170 return null;
171 }
172
173 static FixedFormat lookupIgnoringNanos(final String pattern) {
174 final int nanoStart = nanoStart(pattern);
175 if (nanoStart > 0) {
176 final String subPattern = pattern.substring(0, nanoStart) + DEFAULT_SECOND_FRACTION_PATTERN;
177 for (final FixedFormat type : FixedFormat.values()) {
178 if (type.getPattern().equals(subPattern)) {
179 return type;
180 }
181 }
182 }
183 return null;
184 }
185
186 private static int nanoStart(final String pattern) {
187 final int index = pattern.indexOf(SECOND_FRACTION_PATTERN);
188 if (index >= 0) {
189 for (int i = index + 1; i < pattern.length(); i++) {
190 if (pattern.charAt(i) != SECOND_FRACTION_PATTERN) {
191 return -1;
192 }
193 }
194 }
195 return index;
196 }
197
198
199
200
201
202
203 public int getLength() {
204 return pattern.length() - escapeCount;
205 }
206
207
208
209
210
211
212 public int getDatePatternLength() {
213 return getDatePattern() == null ? 0 : getDatePattern().length() - escapeCount;
214 }
215
216
217
218
219
220
221
222 public FastDateFormat getFastDateFormat() {
223 return getFastDateFormat(null);
224 }
225
226
227
228
229
230
231
232
233 public FastDateFormat getFastDateFormat(final TimeZone tz) {
234 return getDatePattern() == null ? null : FastDateFormat.getInstance(getDatePattern(), tz);
235 }
236
237
238
239
240
241 public int getSecondFractionDigits() {
242 return secondFractionDigits;
243 }
244 }
245
246 private final FixedFormat fixedFormat;
247 private final TimeZone timeZone;
248 private final int length;
249 private final int secondFractionDigits;
250 private final FastDateFormat fastDateFormat;
251 private final char timeSeparatorChar;
252 private final char millisSeparatorChar;
253 private final int timeSeparatorLength;
254 private final int millisSeparatorLength;
255
256 private volatile long midnightToday = 0;
257 private volatile long midnightTomorrow = 0;
258 private final int[] dstOffsets = new int[25];
259
260
261
262
263
264
265
266 private char[] cachedDate;
267 private int dateLength;
268
269
270
271
272
273
274
275
276
277 FixedDateFormat(final FixedFormat fixedFormat, final TimeZone tz) {
278 this(fixedFormat, tz, fixedFormat.getSecondFractionDigits());
279 }
280
281
282
283
284
285
286
287
288
289
290 FixedDateFormat(final FixedFormat fixedFormat, final TimeZone tz, final int secondFractionDigits) {
291 this.fixedFormat = Objects.requireNonNull(fixedFormat);
292 this.timeZone = Objects.requireNonNull(tz);
293 this.timeSeparatorChar = fixedFormat.timeSeparatorChar;
294 this.timeSeparatorLength = fixedFormat.timeSeparatorLength;
295 this.millisSeparatorChar = fixedFormat.millisSeparatorChar;
296 this.millisSeparatorLength = fixedFormat.millisSeparatorLength;
297 this.length = fixedFormat.getLength();
298 this.secondFractionDigits = Math.max(1, Math.min(9, secondFractionDigits));
299 this.fastDateFormat = fixedFormat.getFastDateFormat(tz);
300 }
301
302 public static FixedDateFormat createIfSupported(final String... options) {
303 if (options == null || options.length == 0 || options[0] == null) {
304 return new FixedDateFormat(FixedFormat.DEFAULT, TimeZone.getDefault());
305 }
306 final TimeZone tz;
307 if (options.length > 1) {
308 if (options[1] != null) {
309 tz = TimeZone.getTimeZone(options[1]);
310 } else {
311 tz = TimeZone.getDefault();
312 }
313 } else {
314 tz = TimeZone.getDefault();
315 }
316
317 final FixedFormat withNanos = FixedFormat.lookupIgnoringNanos(options[0]);
318 if (withNanos != null) {
319 final int secondFractionDigits = options[0].length() - FixedFormat.nanoStart(options[0]);
320 return new FixedDateFormat(withNanos, tz, secondFractionDigits);
321 }
322 final FixedFormat type = FixedFormat.lookup(options[0]);
323 return type == null ? null : new FixedDateFormat(type, tz);
324 }
325
326
327
328
329
330
331
332 public static FixedDateFormat create(final FixedFormat format) {
333 return new FixedDateFormat(format, TimeZone.getDefault());
334 }
335
336
337
338
339
340
341
342
343 public static FixedDateFormat create(final FixedFormat format, final TimeZone tz) {
344 return new FixedDateFormat(format, tz != null ? tz : TimeZone.getDefault());
345 }
346
347
348
349
350
351
352 public String getFormat() {
353 return fixedFormat.getPattern();
354 }
355
356
357
358
359
360
361
362 public TimeZone getTimeZone() {
363 return timeZone;
364 }
365
366
367
368
369
370
371
372
373
374
375
376
377 public long millisSinceMidnight(final long currentTime) {
378 if (currentTime >= midnightTomorrow || currentTime < midnightToday) {
379 updateMidnightMillis(currentTime);
380 }
381 return currentTime - midnightToday;
382 }
383
384 private void updateMidnightMillis(final long now) {
385 if (now >= midnightTomorrow || now < midnightToday) {
386 synchronized (this) {
387 updateCachedDate(now);
388 midnightToday = calcMidnightMillis(now, 0);
389 midnightTomorrow = calcMidnightMillis(now, 1);
390
391 updateDaylightSavingTime();
392 }
393 }
394 }
395
396 private long calcMidnightMillis(final long time, final int addDays) {
397 final Calendar cal = Calendar.getInstance(timeZone);
398 cal.setTimeInMillis(time);
399 cal.set(Calendar.HOUR_OF_DAY, 0);
400 cal.set(Calendar.MINUTE, 0);
401 cal.set(Calendar.SECOND, 0);
402 cal.set(Calendar.MILLISECOND, 0);
403 cal.add(Calendar.DATE, addDays);
404 return cal.getTimeInMillis();
405 }
406
407 private void updateDaylightSavingTime() {
408 Arrays.fill(dstOffsets, 0);
409 final int ONE_HOUR = (int) TimeUnit.HOURS.toMillis(1);
410 if (timeZone.getOffset(midnightToday) != timeZone.getOffset(midnightToday + 23 * ONE_HOUR)) {
411 for (int i = 0; i < dstOffsets.length; i++) {
412 final long time = midnightToday + i * ONE_HOUR;
413 dstOffsets[i] = timeZone.getOffset(time) - timeZone.getRawOffset();
414 }
415 if (dstOffsets[0] > dstOffsets[23]) {
416
417 for (int i = dstOffsets.length - 1; i >= 0; i--) {
418 dstOffsets[i] -= dstOffsets[0];
419 }
420 }
421 }
422 }
423
424 private void updateCachedDate(final long now) {
425 if (fastDateFormat != null) {
426 final StringBuilder result = fastDateFormat.format(now, new StringBuilder());
427 cachedDate = result.toString().toCharArray();
428 dateLength = result.length();
429 }
430 }
431
432 public String formatInstant(final Instant instant) {
433 final char[] result = new char[length << 1];
434 final int written = formatInstant(instant, result, 0);
435 return new String(result, 0, written);
436 }
437
438 public int formatInstant(final Instant instant, final char[] buffer, final int startPos) {
439 int result = format(instant.getEpochMillisecond(), buffer, startPos);
440 result -= digitsLessThanThree();
441 formatNanoOfMillisecond(instant.getNanoOfMillisecond(), buffer, startPos + result);
442 return result + digitsMorePreciseThanMillis();
443 }
444
445 private int digitsLessThanThree() {
446 return Math.max(0, FixedFormat.MILLI_FRACTION_DIGITS - secondFractionDigits);
447 }
448
449 private int digitsMorePreciseThanMillis() {
450 return Math.max(0, secondFractionDigits - FixedFormat.MILLI_FRACTION_DIGITS);
451 }
452
453
454
455 public String format(final long epochMillis) {
456 final char[] result = new char[length << 1];
457 final int written = format(epochMillis, result, 0);
458 return new String(result, 0, written);
459 }
460
461
462
463 public int format(final long epochMillis, final char[] buffer, final int startPos) {
464
465
466
467
468
469 final int ms = (int) (millisSinceMidnight(epochMillis));
470 writeDate(buffer, startPos);
471 return writeTime(ms, buffer, startPos + dateLength) - startPos;
472 }
473
474
475
476 private void writeDate(final char[] buffer, final int startPos) {
477 if (cachedDate != null) {
478 System.arraycopy(cachedDate, 0, buffer, startPos, dateLength);
479 }
480 }
481
482
483
484 private int writeTime(int ms, final char[] buffer, int pos) {
485 final int hourOfDay = ms / 3600000;
486 final int hours = hourOfDay + daylightSavingTime(hourOfDay) / 3600000;
487 ms -= 3600000 * hourOfDay;
488
489 final int minutes = ms / 60000;
490 ms -= 60000 * minutes;
491
492 final int seconds = ms / 1000;
493 ms -= 1000 * seconds;
494
495
496 int temp = hours / 10;
497 buffer[pos++] = ((char) (temp + '0'));
498
499
500 buffer[pos++] = ((char) (hours - 10 * temp + '0'));
501 buffer[pos] = timeSeparatorChar;
502 pos += timeSeparatorLength;
503
504
505 temp = minutes / 10;
506 buffer[pos++] = ((char) (temp + '0'));
507
508
509 buffer[pos++] = ((char) (minutes - 10 * temp + '0'));
510 buffer[pos] = timeSeparatorChar;
511 pos += timeSeparatorLength;
512
513
514 temp = seconds / 10;
515 buffer[pos++] = ((char) (temp + '0'));
516 buffer[pos++] = ((char) (seconds - 10 * temp + '0'));
517 buffer[pos] = millisSeparatorChar;
518 pos += millisSeparatorLength;
519
520
521 temp = ms / 100;
522 buffer[pos++] = ((char) (temp + '0'));
523
524 ms -= 100 * temp;
525 temp = ms / 10;
526 buffer[pos++] = ((char) (temp + '0'));
527
528 ms -= 10 * temp;
529 buffer[pos++] = ((char) (ms + '0'));
530 return pos;
531 }
532
533 static int[] TABLE = {
534 100000,
535 10000,
536 1000,
537 100,
538 10,
539 1,
540 };
541
542 private void formatNanoOfMillisecond(int nanoOfMillisecond, final char[] buffer, int pos) {
543 int temp;
544 int remain = nanoOfMillisecond;
545 for (int i = 0; i < secondFractionDigits - FixedFormat.MILLI_FRACTION_DIGITS; i++) {
546 int divisor = TABLE[i];
547 temp = remain / divisor;
548 buffer[pos++] = ((char) (temp + '0'));
549 remain -= divisor * temp;
550 }
551 }
552
553 private int daylightSavingTime(final int hourOfDay) {
554 return hourOfDay > 23 ? dstOffsets[23] : dstOffsets[hourOfDay];
555 }
556 }