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