Add timezones to preference page
[deliverable/tracecompass.git] / org.eclipse.linuxtools.tmf.core / src / org / eclipse / linuxtools / tmf / core / timestamp / TmfTimestampFormat.java
1 /*******************************************************************************
2 * Copyright (c) 2012, 2013 Ericsson
3 *
4 * All rights reserved. This program and the accompanying materials are
5 * made available under the terms of the Eclipse Public License v1.0 which
6 * accompanies this distribution, and is available at
7 * http://www.eclipse.org/legal/epl-v10.html
8 *
9 * Contributors:
10 * Francois Chouinard - Initial API and implementation
11 * Marc-Andre Laperle - Add time zone preference
12 *******************************************************************************/
13
14 package org.eclipse.linuxtools.tmf.core.timestamp;
15
16 import java.text.DecimalFormat;
17 import java.text.ParseException;
18 import java.text.SimpleDateFormat;
19 import java.util.ArrayList;
20 import java.util.Calendar;
21 import java.util.Date;
22 import java.util.List;
23 import java.util.TimeZone;
24 import java.util.regex.Matcher;
25 import java.util.regex.Pattern;
26
27 /**
28 * A formatting and parsing facility that can handle timestamps that span the
29 * epoch with a precision down to the nanosecond. It can be understood as a
30 * simplified and more constrained version of SimpleDateFormat as it limits the
31 * number of allowed pattern characters and the acceptable timestamp formats.
32 * <p>
33 * The timestamp representation is broken down into a number of optional
34 * components that can be assembled into a fairly simple way.
35 *
36 * <h4>Date Pattern</h4>
37 * <blockquote>
38 * <table border=0 cellspacing=3 cellpadding=0 >
39 * <tr bgcolor="#ccccff">
40 * <th align=left>Format
41 * <th align=left>Description
42 * <th align=left>Value Range
43 * <th align=left>Example
44 * <tr>
45 * <td><code>yyyy</code>
46 * <td>Year
47 * <td><code>1970-...</code>
48 * <td><code>2012</code>
49 * <tr bgcolor="#eeeeff">
50 * <td><code>MM</code>
51 * <td>Month in year
52 * <td><code>01-12</code>
53 * <td><code>09</code>
54 * <tr>
55 * <td><code>dd</code>
56 * <td>Day in month
57 * <td><code>01-31</code>
58 * <td><code>22</code>
59 * </table>
60 * </blockquote>
61 *
62 * <h4>Time Pattern</h4>
63 * <blockquote>
64 * <table border=0 cellspacing=3 cellpadding=0 >
65 * <tr bgcolor="#ccccff">
66 * <th align=left>Format
67 * <th align=left>Description
68 * <th align=left>Value Range
69 * <th align=left>Example
70 * <tr>
71 * <td><code>HH</code>
72 * <td>Hour in day
73 * <td><code>00-23</code>
74 * <td><code>07</code>
75 * <tr bgcolor="#eeeeff">
76 * <td><code>mm</code>
77 * <td>Minute in hour
78 * <td><code>00-59</code>
79 * <td><code>35</code>
80 * <tr>
81 * <td><code>ss</code>
82 * <td>Second in minute
83 * <td><code>00-59</code>
84 * <td><code>41</code>
85 * <tr bgcolor="#eeeeff">
86 * <td><code>T</code>
87 * <td>The seconds since the epoch
88 * <td><code>00-...</code>
89 * <td><code>1332170682</code>
90 * </table>
91 * </blockquote>
92 *
93 * <h4>Sub-Seconds Pattern</h4>
94 * <blockquote>
95 * <table border=0 cellspacing=3 cellpadding=0 >
96 * <tr bgcolor="#ccccff">
97 * <th align=left>Format
98 * <th align=left>Description
99 * <th align=left>Value Range
100 * <th align=left>Example
101 * <tr>
102 * <td><code>SSS</code>
103 * <td>Millisecond in second
104 * <td><code>000-999</code>
105 * <td><code>123</code>
106 * <tr bgcolor="#eeeeff">
107 * <td><code>CCC</code>
108 * <td>Microseconds in ms
109 * <td><code>000-999</code>
110 * <td><code>456</code>
111 * <tr>
112 * <td><code>NNN</code>
113 * <td>Nanosecond in &#181s
114 * <td><code>000-999</code>
115 * <td><code>789</code>
116 * </table>
117 * </blockquote>
118 *
119 * <strong>Note: </strong>If "T" is used, no other Date or Time pattern
120 * can be used. Also, "T" should be used for time intervals.
121 * <p>
122 * <strong>Note: </strong>Each sub-field can be separated by a single,
123 * optional character delimiter. However, the between Date/Time and the
124 * Sub-seconds pattern is mandatory (if there is a fractional part) and
125 * has to be separated from Date/time by "." (period).
126 * <p>
127 * The recognized delimiters are:
128 * <ul>
129 * <li>Space ("<code> </code>")
130 * <li>Period (<code>".</code>")
131 * <li>Comma ("<code>,</code>")
132 * <li>Dash ("<code>-</code>")
133 * <li>Underline ("<code>_</code>")
134 * <li>Colon ("<code>:</code>")
135 * <li>Semicolon ("<code>;</code>")
136 * <li>Slash ("<code>/</code>")
137 * <li>Double-quote ("<code>"</code>")
138 * </ul>
139 *
140 * <h4>Examples</h4>
141 * The following examples show how timestamp patterns are interpreted in
142 * the U.S. locale. The given timestamp is 1332170682539677389L, the number
143 * of nanoseconds since 1970/01/01.
144 *
145 * <blockquote>
146 * <table border=0 cellspacing=3 cellpadding=0>
147 * <tr bgcolor="#ccccff">
148 * <th align=left>Date and Time Pattern
149 * <th align=left>Result
150 * <tr>
151 * <td><code>"yyyy-MM-dd HH:mm:ss.SSS.CCC.NNN"</code>
152 * <td><code>2012-03-19 11:24:42.539.677.389</code>
153 * <tr bgcolor="#eeeeff">
154 * <td><code>"yyyy-MM-dd HH:mm:ss.SSS.CCC"</code>
155 * <td><code>2012-03-19 11:24:42.539.677</code>
156 * <tr>
157 * <td><code>"yyyy-D HH:mm:ss.SSS.CCC"</code>
158 * <td><code>2012-79 11:24:42.539.677</code>
159 * <tr bgcolor="#eeeeff">
160 * <td><code>"ss.SSSCCCNNN"</code>
161 * <td><code>42.539677389</code>
162 * <tr>
163 * <td><code>"T.SSS CCC NNN"</code>
164 * <td><code>1332170682.539 677 389</code>
165 * <tr bgcolor="#eeeeff">
166 * <td><code>"T"</code>
167 * <td><code>1332170682</code>
168 * </table>
169 * </blockquote>
170 * <p>
171 * @version 1.0
172 * @since 2.0
173 * @author Francois Chouinard
174 */
175 public class TmfTimestampFormat extends SimpleDateFormat {
176
177 // ------------------------------------------------------------------------
178 // Constants
179 // ------------------------------------------------------------------------
180
181 /**
182 * This class' serialization ID
183 */
184 private static final long serialVersionUID = 2835829763122454020L;
185
186 /**
187 * The default timestamp pattern
188 */
189 public static final String DEFAULT_TIME_PATTERN = "HH:mm:ss.SSS CCC NNN"; //$NON-NLS-1$
190
191 /**
192 * The LTTng 0.x legacy timestamp format
193 */
194 public static final String DEFAULT_INTERVAL_PATTERN = "TTT.SSS CCC NNN"; //$NON-NLS-1$
195
196 // Fractions of seconds supported patterns
197 private static final String DOT_RE = "\\."; //$NON-NLS-1$
198 private static final String SEP_RE = "[ \\.,-_:;/\\\"]?"; //$NON-NLS-1$
199 private static final String DGTS_3_RE = "(\\d{3})"; //$NON-NLS-1$
200 private static final String DGTS_13_RE = "(\\d{1,3})"; //$NON-NLS-1$
201
202 private static final String MILLISEC_RE = DOT_RE + DGTS_13_RE;
203 private static final String MICROSEC_RE = DOT_RE + DGTS_3_RE + SEP_RE + DGTS_13_RE;
204 private static final String NANOSEC_RE = DOT_RE + DGTS_3_RE + SEP_RE + DGTS_3_RE + SEP_RE + DGTS_13_RE;
205
206 private static final Pattern MILLISEC_PAT = Pattern.compile(MILLISEC_RE);
207 private static final Pattern MICROSEC_PAT = Pattern.compile(MICROSEC_RE);
208 private static final Pattern NANOSEC_PAT = Pattern.compile(NANOSEC_RE);
209
210 // ------------------------------------------------------------------------
211 // Attributes
212 // ------------------------------------------------------------------------
213
214 // The default timestamp pattern
215 private static TmfTimestampFormat fDefaultTimeFormat = null;
216
217 // The default time interval format
218 private static TmfTimestampFormat fDefaultIntervalFormat = null;
219
220 // The timestamp pattern
221 private String fPattern;
222
223 // The timestamp pattern
224 private List<String> fSupplPatterns = new ArrayList<String>();
225
226 /**
227 * The supplementary pattern letters. Can be redefined by sub-classes
228 * to either override existing letters or augment the letter set.
229 * If so, the format() method must provide the (re-)implementation of the
230 * pattern.
231 */
232 protected String fSupplPatternLetters = "TSCN"; //$NON-NLS-1$
233
234 /*
235 * The bracketing symbols used to mitigate the risk of a format string
236 * that contains escaped sequences that would conflict with our format
237 * extension.
238 */
239 /** The open bracket symbol */
240 protected String fOpenBracket = "[&"; //$NON-NLS-1$
241
242 /** The closing bracket symbol */
243 protected String fCloseBracket = "&]"; //$NON-NLS-1$
244
245 // ------------------------------------------------------------------------
246 // Constructors
247 // ------------------------------------------------------------------------
248
249 /**
250 * The default constructor (uses the default pattern)
251 */
252 public TmfTimestampFormat() {
253 this(TmfTimePreferences.getInstance().getTimePattern());
254 }
255
256 /**
257 * The normal constructor
258 *
259 * @param pattern the format pattern
260 */
261 public TmfTimestampFormat(String pattern) {
262 applyPattern(pattern);
263 }
264
265 /**
266 * The full constructor
267 *
268 * @param pattern the format pattern
269 * @param timeZone the time zone
270 * @since 2.1
271 */
272 public TmfTimestampFormat(String pattern, TimeZone timeZone) {
273 setTimeZone(timeZone);
274 applyPattern(pattern);
275 }
276
277 /**
278 * The copy constructor
279 *
280 * @param other the other format pattern
281 */
282 public TmfTimestampFormat(TmfTimestampFormat other) {
283 this(other.fPattern);
284 }
285
286 // ------------------------------------------------------------------------
287 // Getters/setters
288 // ------------------------------------------------------------------------
289
290 /**
291 * @since 2.1
292 */
293 public static void updateDefaultFormats() {
294 fDefaultTimeFormat = new TmfTimestampFormat(TmfTimePreferences.getInstance().getTimePattern(), TmfTimePreferences.getInstance().getTimeZone());
295 fDefaultIntervalFormat = new TmfTimestampFormat(TmfTimePreferences.getInstance().getIntervalPattern());
296 }
297
298 /**
299 * @param pattern the new default time pattern
300 * @deprecated The default time pattern depends on the preferences, see
301 * {@link TmfTimePreferences}. To change the default time
302 * pattern, modify the preferences and call {@link #updateDefaultFormats()}
303 */
304 @Deprecated
305 public static void setDefaultTimeFormat(final String pattern) {
306 }
307
308 /**
309 * @return the default time format pattern
310 */
311 public static TmfTimestampFormat getDefaulTimeFormat() {
312 if (fDefaultTimeFormat == null) {
313 fDefaultTimeFormat = new TmfTimestampFormat(TmfTimePreferences.getInstance().getTimePattern(), TmfTimePreferences.getInstance().getTimeZone());
314 }
315 return fDefaultTimeFormat;
316 }
317
318 /**
319 * @param pattern the new default interval pattern
320 * @deprecated The default interval format pattern depends on the
321 * preferences, see {@link TmfTimePreferences}. To change the
322 * default time pattern, modify the preferences and call
323 * {@link #updateDefaultFormats()}
324 */
325 @Deprecated
326 public static void setDefaultIntervalFormat(final String pattern) {
327 }
328
329 /**
330 * @return the default interval format pattern
331 */
332 public static TmfTimestampFormat getDefaulIntervalFormat() {
333 if (fDefaultIntervalFormat == null) {
334 fDefaultIntervalFormat = new TmfTimestampFormat(TmfTimePreferences.getInstance().getIntervalPattern());
335 }
336 return fDefaultIntervalFormat;
337 }
338
339 @Override
340 public void applyPattern(String pattern) {
341 fPattern = pattern;
342 String quotedPattern = quoteSpecificTags(pattern);
343 super.applyPattern(quotedPattern);
344 }
345
346 @Override
347 public String toPattern() {
348 return fPattern;
349 }
350
351 // ------------------------------------------------------------------------
352 // Operations
353 // ------------------------------------------------------------------------
354
355 /**
356 * Format the timestamp according to its pattern.
357 *
358 * @param value the timestamp value to format (in ns)
359 * @return the formatted timestamp
360 */
361 public synchronized String format(long value) {
362
363 // Split the timestamp value into its sub-components
364 long sec = value / 1000000000; // seconds
365 long ms = value % 1000000000 / 1000000; // milliseconds
366 long cs = value % 1000000 / 1000; // microseconds
367 long ns = value % 1000; // nanoseconds
368
369 // Let the base class fill the stuff it knows about
370 StringBuffer result = new StringBuffer(super.format(sec * 1000 + ms));
371
372 // In the case where there is no separation between 2 supplementary
373 // fields, the pattern will have the form "..'[pat-1]''[pat-2]'.." and
374 // the base class format() will interpret the 2 adjacent quotes as a
375 // wanted character in the result string as ("..[pat-1]'[pat-2]..").
376 // Remove these extra quotes before filling the supplementary fields.
377 int loc = result.indexOf(fCloseBracket + "'" + fOpenBracket); //$NON-NLS-1$
378 while (loc != -1) {
379 result.deleteCharAt(loc + fCloseBracket.length());
380 loc = result.indexOf(fCloseBracket + "'" + fOpenBracket); //$NON-NLS-1$
381 }
382
383 // Fill in our extensions
384 for (String pattern : fSupplPatterns) {
385 int length = pattern.length();
386
387 // Prepare the format buffer
388 StringBuffer fmt = new StringBuffer(length);
389 for (int i = 0; i < length; i++) {
390 fmt.append("0"); //$NON-NLS-1$
391 }
392 DecimalFormat dfmt = new DecimalFormat(fmt.toString());
393 String fmtVal = ""; //$NON-NLS-1$;
394
395 // Format the proper value as per the pattern
396 switch (pattern.charAt(0)) {
397 case 'T':
398 fmtVal = dfmt.format(sec);
399 break;
400 case 'S':
401 fmtVal = dfmt.format(ms);
402 break;
403 case 'C':
404 fmtVal = dfmt.format(cs);
405 break;
406 case 'N':
407 fmtVal = dfmt.format(ns);
408 break;
409 default:
410 break;
411 }
412
413 // Substitute the placeholder with the formatted value
414 String ph = new StringBuffer(fOpenBracket + pattern + fCloseBracket).toString();
415 loc = result.indexOf(ph);
416 result.replace(loc, loc + length + fOpenBracket.length() + fCloseBracket.length(), fmtVal);
417 }
418
419 return result.toString();
420 }
421
422 /**
423 * Parse a string according to the format pattern
424 *
425 * @param string the source string
426 * @param ref the reference (base) time
427 * @return the parsed value
428 * @throws ParseException if the string has an invalid format
429 */
430 public synchronized long parseValue(final String string, final long ref) throws ParseException {
431
432 // Trivial case
433 if (string == null || string.length() == 0) {
434 return 0;
435 }
436
437 // The timestamp sub-components
438 long seconds = -1;
439 long millisec = 0;
440 long microsec = 0;
441 long nanosec = 0;
442
443 // Since we are processing the fractional part, substitute it with
444 // its pattern so the base parser doesn't complain
445 StringBuilder sb = new StringBuilder(string);
446 int dot = string.indexOf('.');
447 if (dot == -1) {
448 sb.append('.');
449 dot = string.length();
450 }
451 sb = new StringBuilder(string.substring(0, dot));
452 String basePattern = super.toPattern();
453 int dot2 = basePattern.indexOf('.');
454 if (dot2 != -1) {
455 sb.append(basePattern.substring(dot2));
456 }
457
458 // Fill in our extensions
459 for (String pattern : fSupplPatterns) {
460 String pat = fOpenBracket + pattern + fCloseBracket;
461 Matcher matcher;
462
463 // Extract the substring corresponding to the extra pattern letters
464 // and replace with the pattern so the base parser can do its job.
465 switch (pattern.charAt(0)) {
466 case 'T':
467 // Remove everything up to the first "." and compute the
468 // number of seconds since the epoch. If there is no period,
469 // assume an integer value and return immediately
470 if (dot < 1) {
471 return new DecimalFormat("0").parse(string).longValue() * 1000000000; //$NON-NLS-1$
472 }
473 seconds = new DecimalFormat("0").parse(string.substring(0, dot)).longValue(); //$NON-NLS-1$
474 sb.delete(0, dot);
475 sb.insert(0, pat);
476 break;
477 case 'S':
478 matcher = MILLISEC_PAT.matcher(string.substring(dot));
479 if (matcher.find()) {
480 millisec = new Long(matcher.group(1));
481 for (int l = matcher.group(1).length(); l < 3; l++) {
482 millisec *= 10;
483 }
484 }
485 stripQuotes(sb, pattern);
486 break;
487 case 'C':
488 matcher = MICROSEC_PAT.matcher(string.substring(dot));
489 if (matcher.find()) {
490 microsec = new Long(matcher.group(2));
491 for (int l = matcher.group(2).length(); l < 3; l++) {
492 microsec *= 10;
493 }
494 }
495 stripQuotes(sb, pattern);
496 break;
497 case 'N':
498 matcher = NANOSEC_PAT.matcher(string.substring(dot));
499 if (matcher.find()) {
500 nanosec = new Long(matcher.group(3));
501 for (int l = matcher.group(3).length(); l < 3; l++) {
502 nanosec *= 10;
503 }
504 }
505 stripQuotes(sb, pattern);
506 break;
507 default:
508 break;
509 }
510 }
511
512 // If there was no "T" (thus not an interval), parse as a date
513 if (seconds == -1) {
514 Date baseDate = super.parse(sb.toString());
515
516 Calendar refTime = Calendar.getInstance(getTimeZone());
517 refTime.setTimeInMillis(ref / 1000000);
518 Calendar newTime = Calendar.getInstance(getTimeZone());
519 newTime.setTimeInMillis(baseDate.getTime());
520
521 int[] fields = new int[] { Calendar.YEAR, Calendar.MONTH, Calendar.DATE, Calendar.HOUR_OF_DAY, Calendar.MINUTE, Calendar.SECOND };
522 for (int field : fields) {
523 int value = newTime.get(field);
524 // Do some adjustments...
525 if (field == Calendar.YEAR) {
526 value -= 1970;
527 } else if (field == Calendar.DATE) {
528 value -= 1;
529 }
530 // ... and fill-in the empty fields
531 if (value == 0) {
532 newTime.set(field, refTime.get(field));
533 } else {
534 break; // Get out as soon as we have a significant value
535 }
536 }
537 seconds = newTime.getTimeInMillis() / 1000;
538 }
539
540 // Compute the value in ns
541 return seconds * 1000000000 + millisec * 1000000 + microsec * 1000 + nanosec;
542 }
543
544 /**
545 * Parse a string according to the format pattern
546 *
547 * @param string the source string
548 * @return the parsed value
549 * @throws ParseException if the string has an invalid format
550 */
551 public long parseValue(final String string) throws ParseException {
552 long result = parseValue(string, 0);
553 return result;
554
555 }
556
557 // ------------------------------------------------------------------------
558 // Helper functions
559 // ------------------------------------------------------------------------
560
561 /**
562 * Copy the pattern but quote (bracket with "[&" and "&]") the
563 * TmfTimestampFormat specific tags so these fields are treated as
564 * comments by the base class.
565 *
566 * It also keeps track of the corresponding quoted fields so they can be
567 * properly populated later on (by format()).
568 *
569 * @param pattern the 'extended' pattern
570 * @return the quoted and bracketed pattern
571 */
572 private String quoteSpecificTags(final String pattern) {
573
574 StringBuffer result = new StringBuffer();
575
576 int length = pattern.length();
577 boolean inQuote = false;
578
579 for (int i = 0; i < length; i++) {
580 char c = pattern.charAt(i);
581 result.append(c);
582 if (c == '\'') {
583 // '' is treated as a single quote regardless of being
584 // in a quoted section.
585 if ((i + 1) < length) {
586 c = pattern.charAt(i + 1);
587 if (c == '\'') {
588 i++;
589 result.append(c);
590 continue;
591 }
592 }
593 inQuote = !inQuote;
594 continue;
595 }
596 if (!inQuote) {
597 if (fSupplPatternLetters.indexOf(c) != -1) {
598 StringBuilder pat = new StringBuilder();
599 pat.append(c);
600 result.insert(result.length() - 1, "'" + fOpenBracket); //$NON-NLS-1$
601 while ((i + 1) < length && pattern.charAt(i + 1) == c) {
602 result.append(c);
603 pat.append(c);
604 i++;
605 }
606 result.append(fCloseBracket + "'"); //$NON-NLS-1$
607 fSupplPatterns.add(pat.toString());
608 }
609 }
610 }
611 return result.toString();
612 }
613
614 /**
615 * Remove the quotes from the pattern
616 *
617 * @param sb
618 * @param pattern
619 */
620 private void stripQuotes(StringBuilder sb, String pattern) {
621 String pt = "'" + fOpenBracket + pattern + fCloseBracket + "'"; //$NON-NLS-1$//$NON-NLS-2$
622 int l = sb.indexOf(pt);
623 if (l != -1) {
624 sb.delete(l + pt.length() - 1, l + pt.length());
625 sb.delete(l, l + 1);
626 }
627 }
628
629 }
This page took 0.048593 seconds and 5 git commands to generate.