1 /*******************************************************************************
2 * Copyright (c) 2012, 2013 Ericsson
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
10 * Francois Chouinard - Initial API and implementation
11 * Marc-Andre Laperle - Add time zone preference
12 * Patrick Tasse - Updated for negative value formatting
13 *******************************************************************************/
15 package org
.eclipse
.linuxtools
.tmf
.core
.timestamp
;
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
;
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.
34 * The timestamp representation is broken down into a number of optional
35 * components that can be assembled into a fairly simple way.
37 * <h4>Date Pattern</h4>
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
46 * <td><code>yyyy</code>
48 * <td><code>1970-...</code>
49 * <td><code>2012</code>
50 * <tr bgcolor="#eeeeff">
53 * <td><code>01-12</code>
58 * <td><code>01-31</code>
63 * <h4>Time Pattern</h4>
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
74 * <td><code>00-23</code>
76 * <tr bgcolor="#eeeeff">
79 * <td><code>00-59</code>
83 * <td>Second in minute
84 * <td><code>00-59</code>
86 * <tr bgcolor="#eeeeff">
88 * <td>The seconds since the epoch
89 * <td><code>00-...</code>
90 * <td><code>1332170682</code>
94 * <h4>Sub-Seconds Pattern</h4>
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
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>
113 * <td><code>NNN</code>
114 * <td>Nanosecond in µs
115 * <td><code>000-999</code>
116 * <td><code>789</code>
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.
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).
128 * The recognized delimiters are:
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>")
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.
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
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>
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>
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>
174 * @author Francois Chouinard
176 public class TmfTimestampFormat
extends SimpleDateFormat
{
178 // ------------------------------------------------------------------------
180 // ------------------------------------------------------------------------
183 * This class' serialization ID
185 private static final long serialVersionUID
= 2835829763122454020L;
188 * The default timestamp pattern
190 public static final String DEFAULT_TIME_PATTERN
= "HH:mm:ss.SSS CCC NNN"; //$NON-NLS-1$
193 * The LTTng 0.x legacy timestamp format
195 public static final String DEFAULT_INTERVAL_PATTERN
= "TTT.SSS CCC NNN"; //$NON-NLS-1$
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$
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
;
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
);
211 // ------------------------------------------------------------------------
213 // ------------------------------------------------------------------------
215 // The default timestamp pattern
216 private static TmfTimestampFormat fDefaultTimeFormat
= null;
218 // The default time interval format
219 private static TmfTimestampFormat fDefaultIntervalFormat
= null;
221 // The timestamp pattern
222 private String fPattern
;
224 // The timestamp pattern
225 private List
<String
> fSupplPatterns
= new ArrayList
<>();
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
233 protected String fSupplPatternLetters
= "TSCN"; //$NON-NLS-1$
236 * The bracketing symbols used to mitigate the risk of a format string
237 * that contains escaped sequences that would conflict with our format
240 /** The open bracket symbol */
241 protected String fOpenBracket
= "[&"; //$NON-NLS-1$
243 /** The closing bracket symbol */
244 protected String fCloseBracket
= "&]"; //$NON-NLS-1$
246 // ------------------------------------------------------------------------
248 // ------------------------------------------------------------------------
251 * The default constructor (uses the default pattern)
253 public TmfTimestampFormat() {
254 this(TmfTimePreferences
.getInstance().getTimePattern());
258 * The normal constructor
260 * @param pattern the format pattern
262 public TmfTimestampFormat(String pattern
) {
263 applyPattern(pattern
);
267 * The full constructor
269 * @param pattern the format pattern
270 * @param timeZone the time zone
273 public TmfTimestampFormat(String pattern
, TimeZone timeZone
) {
274 setTimeZone(timeZone
);
275 applyPattern(pattern
);
279 * The copy constructor
281 * @param other the other format pattern
283 public TmfTimestampFormat(TmfTimestampFormat other
) {
284 this(other
.fPattern
);
287 // ------------------------------------------------------------------------
289 // ------------------------------------------------------------------------
294 public static void updateDefaultFormats() {
295 fDefaultTimeFormat
= new TmfTimestampFormat(TmfTimePreferences
.getInstance().getTimePattern(), TmfTimePreferences
.getInstance().getTimeZone());
296 fDefaultIntervalFormat
= new TmfTimestampFormat(TmfTimePreferences
.getInstance().getIntervalPattern());
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()}
306 public static void setDefaultTimeFormat(final String pattern
) {
310 * @return the default time format pattern
312 public static TmfTimestampFormat
getDefaulTimeFormat() {
313 if (fDefaultTimeFormat
== null) {
314 fDefaultTimeFormat
= new TmfTimestampFormat(TmfTimePreferences
.getInstance().getTimePattern(), TmfTimePreferences
.getInstance().getTimeZone());
316 return fDefaultTimeFormat
;
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()}
327 public static void setDefaultIntervalFormat(final String pattern
) {
331 * @return the default interval format pattern
333 public static TmfTimestampFormat
getDefaulIntervalFormat() {
334 if (fDefaultIntervalFormat
== null) {
335 fDefaultIntervalFormat
= new TmfTimestampFormat(TmfTimePreferences
.getInstance().getIntervalPattern());
337 return fDefaultIntervalFormat
;
341 public void applyPattern(String pattern
) {
343 String quotedPattern
= quoteSpecificTags(pattern
);
344 super.applyPattern(quotedPattern
);
348 public String
toPattern() {
352 // ------------------------------------------------------------------------
354 // ------------------------------------------------------------------------
357 * Format the timestamp according to its pattern.
359 * @param value the timestamp value to format (in ns)
360 * @return the formatted timestamp
362 public synchronized String
format(long value
) {
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
371 // Adjust for negative value when formatted as a date
372 if (value
< 0 && ms
+ cs
+ ns
> 0 && !fPattern
.contains("T")) { //$NON-NLS-1$
374 long nanosec
= 1000000000 - (1000000 * ms
+ 1000 * cs
+ ns
);
375 ms
= nanosec
/ 1000000;
376 cs
= nanosec
% 1000000 / 1000;
380 // Let the base class fill the stuff it knows about
381 StringBuffer result
= new StringBuffer(super.format(date
));
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$
390 result
.deleteCharAt(loc
+ fCloseBracket
.length());
391 loc
= result
.indexOf(fCloseBracket
+ "'" + fOpenBracket
); //$NON-NLS-1$
394 // Fill in our extensions
395 for (String pattern
: fSupplPatterns
) {
396 int length
= pattern
.length();
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$
403 DecimalFormat dfmt
= new DecimalFormat(fmt
.toString());
404 String fmtVal
= ""; //$NON-NLS-1$;
406 // Format the proper value as per the pattern
407 switch (pattern
.charAt(0)) {
409 if (value
< 0 && sec
== 0) {
410 result
.insert(0, '-');
412 fmtVal
= dfmt
.format(sec
);
415 fmtVal
= dfmt
.format(ms
);
418 fmtVal
= dfmt
.format(cs
);
421 fmtVal
= dfmt
.format(ns
);
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
);
433 return result
.toString();
437 * Parse a string according to the format pattern
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
444 public synchronized long parseValue(final String string
, final long ref
) throws ParseException
{
447 if (string
== null || string
.length() == 0) {
451 // The timestamp sub-components
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('.');
463 dot
= string
.length();
465 sb
= new StringBuilder(string
.substring(0, dot
));
466 String basePattern
= super.toPattern();
467 int dot2
= basePattern
.indexOf('.');
469 sb
.append(basePattern
.substring(dot2
));
472 // Fill in our extensions
473 for (String pattern
: fSupplPatterns
) {
474 String pat
= fOpenBracket
+ pattern
+ fCloseBracket
;
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)) {
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
485 return new DecimalFormat("0").parse(string
).longValue() * 1000000000; //$NON-NLS-1$
487 seconds
= new DecimalFormat("0").parse(string
.substring(0, dot
)).longValue(); //$NON-NLS-1$
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
++) {
499 stripQuotes(sb
, pattern
);
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
++) {
509 stripQuotes(sb
, pattern
);
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
++) {
519 stripQuotes(sb
, pattern
);
526 // If there was no "T" (thus not an interval), parse as a date
528 Date baseDate
= super.parse(sb
.toString());
530 Calendar refTime
= Calendar
.getInstance(getTimeZone());
531 refTime
.setTimeInMillis(ref
/ 1000000);
532 Calendar newTime
= Calendar
.getInstance(getTimeZone());
533 newTime
.setTimeInMillis(baseDate
.getTime());
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
) {
541 } else if (field
== Calendar
.DATE
) {
544 // ... and fill-in the empty fields
546 newTime
.set(field
, refTime
.get(field
));
548 break; // Get out as soon as we have a significant value
551 seconds
= newTime
.getTimeInMillis() / 1000;
554 // Compute the value in ns
555 return seconds
* 1000000000 + millisec
* 1000000 + microsec
* 1000 + nanosec
;
559 * Parse a string according to the format pattern
561 * @param string the source string
562 * @return the parsed value
563 * @throws ParseException if the string has an invalid format
565 public long parseValue(final String string
) throws ParseException
{
566 long result
= parseValue(string
, 0);
571 // ------------------------------------------------------------------------
573 // ------------------------------------------------------------------------
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.
580 * It also keeps track of the corresponding quoted fields so they can be
581 * properly populated later on (by format()).
583 * @param pattern the 'extended' pattern
584 * @return the quoted and bracketed pattern
586 private String
quoteSpecificTags(final String pattern
) {
588 StringBuffer result
= new StringBuffer();
590 int length
= pattern
.length();
591 boolean inQuote
= false;
593 for (int i
= 0; i
< length
; i
++) {
594 char c
= pattern
.charAt(i
);
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);
611 if (fSupplPatternLetters
.indexOf(c
) != -1) {
612 StringBuilder pat
= new StringBuilder();
614 result
.insert(result
.length() - 1, "'" + fOpenBracket
); //$NON-NLS-1$
615 while ((i
+ 1) < length
&& pattern
.charAt(i
+ 1) == c
) {
620 result
.append(fCloseBracket
+ "'"); //$NON-NLS-1$
621 fSupplPatterns
.add(pat
.toString());
625 return result
.toString();
629 * Remove the quotes from the pattern
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
);
638 sb
.delete(l
+ pt
.length() - 1, l
+ pt
.length());