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 java.util.Arrays;
21 import java.util.Calendar;
22 import java.util.Objects;
23 import java.util.TimeZone;
24 import java.util.concurrent.TimeUnit;
25
26
27
28
29
30
31
32
33 public class FixedDateFormat {
34
35
36
37
38
39 public enum FixedFormat {
40
41
42
43 ABSOLUTE("HH:mm:ss,SSS", null, 0, ':', 1, ',', 1),
44
45
46
47
48 ABSOLUTE_PERIOD("HH:mm:ss.SSS", null, 0, ':', 1, '.', 1),
49
50
51
52
53 COMPACT("yyyyMMddHHmmssSSS", "yyyyMMdd", 0, ' ', 0, ' ', 0),
54
55
56
57
58 DATE("dd MMM yyyy HH:mm:ss,SSS", "dd MMM yyyy ", 0, ':', 1, ',', 1),
59
60
61
62
63 DATE_PERIOD("dd MMM yyyy HH:mm:ss.SSS", "dd MMM yyyy ", 0, ':', 1, '.', 1),
64
65
66
67
68 DEFAULT("yyyy-MM-dd HH:mm:ss,SSS", "yyyy-MM-dd ", 0, ':', 1, ',', 1),
69
70
71
72
73 DEFAULT_PERIOD("yyyy-MM-dd HH:mm:ss.SSS", "yyyy-MM-dd ", 0, ':', 1, '.', 1),
74
75
76
77
78 ISO8601_BASIC("yyyyMMdd'T'HHmmss,SSS", "yyyyMMdd'T'", 2, ' ', 0, ',', 1),
79
80
81
82
83 ISO8601_BASIC_PERIOD("yyyyMMdd'T'HHmmss.SSS", "yyyyMMdd'T'", 2, ' ', 0, '.', 1),
84
85
86
87
88 ISO8601("yyyy-MM-dd'T'HH:mm:ss,SSS", "yyyy-MM-dd'T'", 2, ':', 1, ',', 1),
89
90
91
92
93 ISO8601_PERIOD("yyyy-MM-dd'T'HH:mm:ss.SSS", "yyyy-MM-dd'T'", 2, ':', 1, '.', 1);
94
95 private final String pattern;
96 private final String datePattern;
97 private final int escapeCount;
98 private final char timeSeparatorChar;
99 private final int timeSeparatorLength;
100 private final char millisSeparatorChar;
101 private final int millisSeparatorLength;
102
103 FixedFormat(final String pattern, final String datePattern, final int escapeCount, final char timeSeparator,
104 final int timeSepLength, final char millisSeparator, final int millisSepLength) {
105 this.timeSeparatorChar = timeSeparator;
106 this.timeSeparatorLength = timeSepLength;
107 this.millisSeparatorChar = millisSeparator;
108 this.millisSeparatorLength = millisSepLength;
109 this.pattern = Objects.requireNonNull(pattern);
110 this.datePattern = datePattern;
111 this.escapeCount = escapeCount;
112 }
113
114
115
116
117
118
119 public String getPattern() {
120 return pattern;
121 }
122
123
124
125
126
127
128 public String getDatePattern() {
129 return datePattern;
130 }
131
132
133
134
135
136
137
138 public static FixedFormat lookup(final String nameOrPattern) {
139 for (final FixedFormat type : FixedFormat.values()) {
140 if (type.name().equals(nameOrPattern) || type.getPattern().equals(nameOrPattern)) {
141 return type;
142 }
143 }
144 return null;
145 }
146
147
148
149
150
151
152 public int getLength() {
153 return pattern.length() - escapeCount;
154 }
155
156
157
158
159
160
161 public int getDatePatternLength() {
162 return getDatePattern() == null ? 0 : getDatePattern().length() - escapeCount;
163 }
164
165
166
167
168
169
170
171 public FastDateFormat getFastDateFormat() {
172 return getFastDateFormat(null);
173 }
174
175
176
177
178
179
180
181
182 public FastDateFormat getFastDateFormat(final TimeZone tz) {
183 return getDatePattern() == null ? null : FastDateFormat.getInstance(getDatePattern(), tz);
184 }
185 }
186
187 private final FixedFormat fixedFormat;
188 private final TimeZone timeZone;
189 private final int length;
190 private final FastDateFormat fastDateFormat;
191 private final char timeSeparatorChar;
192 private final char millisSeparatorChar;
193 private final int timeSeparatorLength;
194 private final int millisSeparatorLength;
195
196 private volatile long midnightToday = 0;
197 private volatile long midnightTomorrow = 0;
198 private final int[] dstOffsets = new int[25];
199
200
201
202
203
204
205
206 private char[] cachedDate;
207 private int dateLength;
208
209
210
211
212
213
214
215
216 FixedDateFormat(final FixedFormat fixedFormat, final TimeZone tz) {
217 this.fixedFormat = Objects.requireNonNull(fixedFormat);
218 this.timeZone = Objects.requireNonNull(tz);
219 this.timeSeparatorChar = fixedFormat.timeSeparatorChar;
220 this.timeSeparatorLength = fixedFormat.timeSeparatorLength;
221 this.millisSeparatorChar = fixedFormat.millisSeparatorChar;
222 this.millisSeparatorLength = fixedFormat.millisSeparatorLength;
223 this.length = fixedFormat.getLength();
224 this.fastDateFormat = fixedFormat.getFastDateFormat(tz);
225 }
226
227 public static FixedDateFormat createIfSupported(final String... options) {
228 if (options == null || options.length == 0 || options[0] == null) {
229 return new FixedDateFormat(FixedFormat.DEFAULT, TimeZone.getDefault());
230 }
231 final TimeZone tz;
232 if (options.length > 1) {
233 if (options[1] != null){
234 tz = TimeZone.getTimeZone(options[1]);
235 } else {
236 tz = TimeZone.getDefault();
237 }
238 } else if (options.length > 2) {
239 return null;
240 } else {
241 tz = TimeZone.getDefault();
242 }
243
244 final FixedFormat type = FixedFormat.lookup(options[0]);
245 return type == null ? null : new FixedDateFormat(type, tz);
246 }
247
248
249
250
251
252
253
254 public static FixedDateFormat create(final FixedFormat format) {
255 return new FixedDateFormat(format, TimeZone.getDefault());
256 }
257
258
259
260
261
262
263
264
265 public static FixedDateFormat create(final FixedFormat format, final TimeZone tz) {
266 return new FixedDateFormat(format, tz != null ? tz : TimeZone.getDefault());
267 }
268
269
270
271
272
273
274 public String getFormat() {
275 return fixedFormat.getPattern();
276 }
277
278
279
280
281
282
283
284 public TimeZone getTimeZone() {
285 return timeZone;
286 }
287
288
289
290
291
292
293
294
295
296
297
298
299 public long millisSinceMidnight(final long currentTime) {
300 if (currentTime >= midnightTomorrow || currentTime < midnightToday) {
301 updateMidnightMillis(currentTime);
302 }
303 return currentTime - midnightToday;
304 }
305
306 private void updateMidnightMillis(final long now) {
307 if (now >= midnightTomorrow || now < midnightToday) {
308 synchronized (this) {
309 updateCachedDate(now);
310 midnightToday = calcMidnightMillis(now, 0);
311 midnightTomorrow = calcMidnightMillis(now, 1);
312
313 updateDaylightSavingTime();
314 }
315 }
316 }
317
318 private long calcMidnightMillis(final long time, final int addDays) {
319 final Calendar cal = Calendar.getInstance(timeZone);
320 cal.setTimeInMillis(time);
321 cal.set(Calendar.HOUR_OF_DAY, 0);
322 cal.set(Calendar.MINUTE, 0);
323 cal.set(Calendar.SECOND, 0);
324 cal.set(Calendar.MILLISECOND, 0);
325 cal.add(Calendar.DATE, addDays);
326 return cal.getTimeInMillis();
327 }
328
329 private void updateDaylightSavingTime() {
330 Arrays.fill(dstOffsets, 0);
331 final int ONE_HOUR = (int) TimeUnit.HOURS.toMillis(1);
332 if (timeZone.getOffset(midnightToday) != timeZone.getOffset(midnightToday + 23 * ONE_HOUR)) {
333 for (int i = 0; i < dstOffsets.length; i++) {
334 final long time = midnightToday + i * ONE_HOUR;
335 dstOffsets[i] = timeZone.getOffset(time) - timeZone.getRawOffset();
336 }
337 if (dstOffsets[0] > dstOffsets[23]) {
338
339 for (int i = dstOffsets.length - 1; i >= 0; i--) {
340 dstOffsets[i] -= dstOffsets[0];
341 }
342 }
343 }
344 }
345
346 private void updateCachedDate(final long now) {
347 if (fastDateFormat != null) {
348 final StringBuilder result = fastDateFormat.format(now, new StringBuilder());
349 cachedDate = result.toString().toCharArray();
350 dateLength = result.length();
351 }
352 }
353
354
355
356 public String format(final long time) {
357 final char[] result = new char[length << 1];
358 final int written = format(time, result, 0);
359 return new String(result, 0, written);
360 }
361
362
363
364 public int format(final long time, final char[] buffer, final int startPos) {
365
366
367
368
369
370 final int ms = (int) (millisSinceMidnight(time));
371 writeDate(buffer, startPos);
372 return writeTime(ms, buffer, startPos + dateLength) - startPos;
373 }
374
375
376
377 private void writeDate(final char[] buffer, final int startPos) {
378 if (cachedDate != null) {
379 System.arraycopy(cachedDate, 0, buffer, startPos, dateLength);
380 }
381 }
382
383
384
385 private int writeTime(int ms, final char[] buffer, int pos) {
386 final int hourOfDay = ms / 3600000;
387 final int hours = hourOfDay + daylightSavingTime(hourOfDay) / 3600000;
388 ms -= 3600000 * hourOfDay;
389
390 final int minutes = ms / 60000;
391 ms -= 60000 * minutes;
392
393 final int seconds = ms / 1000;
394 ms -= 1000 * seconds;
395
396
397 int temp = hours / 10;
398 buffer[pos++] = ((char) (temp + '0'));
399
400
401 buffer[pos++] = ((char) (hours - 10 * temp + '0'));
402 buffer[pos] = timeSeparatorChar;
403 pos += timeSeparatorLength;
404
405
406 temp = minutes / 10;
407 buffer[pos++] = ((char) (temp + '0'));
408
409
410 buffer[pos++] = ((char) (minutes - 10 * temp + '0'));
411 buffer[pos] = timeSeparatorChar;
412 pos += timeSeparatorLength;
413
414
415 temp = seconds / 10;
416 buffer[pos++] = ((char) (temp + '0'));
417 buffer[pos++] = ((char) (seconds - 10 * temp + '0'));
418 buffer[pos] = millisSeparatorChar;
419 pos += millisSeparatorLength;
420
421
422 temp = ms / 100;
423 buffer[pos++] = ((char) (temp + '0'));
424
425 ms -= 100 * temp;
426 temp = ms / 10;
427 buffer[pos++] = ((char) (temp + '0'));
428
429 ms -= 10 * temp;
430 buffer[pos++] = ((char) (ms + '0'));
431 return pos;
432 }
433
434 private int daylightSavingTime(final int hourOfDay) {
435 return hourOfDay > 23 ? dstOffsets[23] : dstOffsets[hourOfDay];
436 }
437 }