ss: Move plugins to Trace Compass namespace
[deliverable/tracecompass.git] / org.eclipse.linuxtools.tmf.core / src / org / eclipse / linuxtools / tmf / core / timestamp / TmfTimestampFormat.java
CommitLineData
f8177ba2 1/*******************************************************************************
18ec12d7 2 * Copyright (c) 2012, 2014 Ericsson
f8177ba2
FC
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:
c1cd9635
MAL
10 * Francois Chouinard - Initial API and implementation
11 * Marc-Andre Laperle - Add time zone preference
081a0804 12 * Patrick Tasse - Updated for negative value formatting and fraction of sec
f8177ba2
FC
13 *******************************************************************************/
14
3bd46eef 15package org.eclipse.linuxtools.tmf.core.timestamp;
f8177ba2
FC
16
17import java.text.DecimalFormat;
18import java.text.ParseException;
19import java.text.SimpleDateFormat;
20import java.util.ArrayList;
21import java.util.Calendar;
22import java.util.Date;
23import java.util.List;
8765b2d4 24import java.util.Locale;
c1cd9635 25import java.util.TimeZone;
f8177ba2 26
f8177ba2
FC
27/**
28 * A formatting and parsing facility that can handle timestamps that span the
081a0804
PT
29 * epoch with a precision down to the nanosecond. It can be understood as an
30 * extension of SimpleDateFormat that supports seconds since the epoch (Jan 1,
31 * 1970, 00:00:00 GMT), additional sub-second patterns and optional delimiters.
f8177ba2
FC
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 *
081a0804
PT
36 * <h4>Date and Time Patterns</h4>
37 * All date and time pattern letters defined in {@link SimpleDateFormat} are
38 * supported with the following exceptions:
f8177ba2
FC
39 * <blockquote>
40 * <table border=0 cellspacing=3 cellpadding=0 >
41 * <tr bgcolor="#ccccff">
42 * <th align=left>Format
43 * <th align=left>Description
44 * <th align=left>Value Range
45 * <th align=left>Example
f8177ba2
FC
46 * <tr bgcolor="#eeeeff">
47 * <td><code>T</code>
48 * <td>The seconds since the epoch
081a0804 49 * <td><code>0-9223372036</code>
f8177ba2 50 * <td><code>1332170682</code>
081a0804
PT
51 * <tr>
52 * <td><code>S</code>
53 * <td>Millisecond
54 * <td><code>N/A</code>
55 * <td><code>Not supported</code>
56 * <tr bgcolor="#eeeeff">
57 * <td><code>W</code>
58 * <td>Week in month
59 * <td><code>N/A</code>
60 * <td><code>Not supported</code>
f8177ba2
FC
61 * </table>
62 * </blockquote>
081a0804
PT
63 * <p>
64 * <strong>Note:</strong> When parsing, if "T" is used, no other Date and Time
65 * pattern letter will be interpreted and the entire pre-delimiter input string
66 * will be parsed as a number. Also, "T" should be used for time intervals.
67 * <p>
68 * <strong>Note:</strong> The decimal separator between the Date and Time
69 * pattern and the Sub-Seconds pattern is mandatory (if there is a fractional
70 * part) and must be one of the sub-second delimiters. Date and Time pattern
71 * letters are not interpreted after the decimal separator.
72 * <p>
73 * <h4>Sub-Seconds Patterns</h4>
f8177ba2
FC
74 * <blockquote>
75 * <table border=0 cellspacing=3 cellpadding=0 >
76 * <tr bgcolor="#ccccff">
77 * <th align=left>Format
78 * <th align=left>Description
79 * <th align=left>Value Range
80 * <th align=left>Example
81 * <tr>
081a0804
PT
82 * <td><code>S</code>
83 * <td>Fraction of second
84 * <td><code>0-999999999</code>
85 * <td><code>123456789</code>
f8177ba2 86 * <tr bgcolor="#eeeeff">
081a0804 87 * <td><code>C</code>
f8177ba2 88 * <td>Microseconds in ms
081a0804 89 * <td><code>0-999</code>
f8177ba2
FC
90 * <td><code>456</code>
91 * <tr>
081a0804 92 * <td><code>N</code>
18ec12d7 93 * <td>Nanoseconds in &#181s
081a0804 94 * <td><code>0-999</code>
f8177ba2
FC
95 * <td><code>789</code>
96 * </table>
97 * </blockquote>
081a0804
PT
98 * <strong>Note:</strong> The fraction of second pattern can be split, in which
99 * case parsing and formatting continues at the next digit. Digits beyond the
100 * total number of pattern letters are ignored when parsing and truncated when
101 * formatting.
f8177ba2 102 * <p>
081a0804
PT
103 * <strong>Note:</strong> When parsing, "S", "C" and "N" are interchangeable
104 * and are all handled as fraction of second ("S"). The use of "C" and "N" is
105 * discouraged but is supported for backward compatibility.
f8177ba2 106 * <p>
081a0804
PT
107 *
108 * The recognized sub-second delimiters are:
f8177ba2
FC
109 * <ul>
110 * <li>Space ("<code> </code>")
081a0804 111 * <li>Period ("<code>.</code>")
f8177ba2
FC
112 * <li>Comma ("<code>,</code>")
113 * <li>Dash ("<code>-</code>")
114 * <li>Underline ("<code>_</code>")
115 * <li>Colon ("<code>:</code>")
116 * <li>Semicolon ("<code>;</code>")
117 * <li>Slash ("<code>/</code>")
081a0804 118 * <li>Single-quote ("<code>''</code>")
f8177ba2
FC
119 * <li>Double-quote ("<code>"</code>")
120 * </ul>
081a0804
PT
121 * <p>
122 * <strong>Note:</strong> When parsing, sub-second delimiters are optional if
123 * unquoted. However, an extra delimiter or any other unexpected character in
124 * the input string ends the parsing of digits. All other quoted or unquoted
125 * characters in the sub-second pattern are matched against the input string.
f8177ba2
FC
126 *
127 * <h4>Examples</h4>
128 * The following examples show how timestamp patterns are interpreted in
129 * the U.S. locale. The given timestamp is 1332170682539677389L, the number
130 * of nanoseconds since 1970/01/01.
131 *
132 * <blockquote>
133 * <table border=0 cellspacing=3 cellpadding=0>
134 * <tr bgcolor="#ccccff">
135 * <th align=left>Date and Time Pattern
136 * <th align=left>Result
137 * <tr>
081a0804 138 * <td><code>"yyyy-MM-dd HH:mm:ss.SSS.SSS.SSS"</code>
f8177ba2
FC
139 * <td><code>2012-03-19 11:24:42.539.677.389</code>
140 * <tr bgcolor="#eeeeff">
081a0804 141 * <td><code>"yyyy-MM-dd HH:mm:ss.SSS.SSS"</code>
f8177ba2
FC
142 * <td><code>2012-03-19 11:24:42.539.677</code>
143 * <tr>
081a0804 144 * <td><code>"yyyy-D HH:mm:ss.SSS.SSS"</code>
f8177ba2
FC
145 * <td><code>2012-79 11:24:42.539.677</code>
146 * <tr bgcolor="#eeeeff">
081a0804
PT
147 * <td><code>"ss,SSSS"</code>
148 * <td><code>42,5397</code>
f8177ba2 149 * <tr>
081a0804 150 * <td><code>"T.SSS SSS SSS"</code>
f8177ba2
FC
151 * <td><code>1332170682.539 677 389</code>
152 * <tr bgcolor="#eeeeff">
153 * <td><code>"T"</code>
154 * <td><code>1332170682</code>
155 * </table>
156 * </blockquote>
157 * <p>
158 * @version 1.0
159 * @since 2.0
160 * @author Francois Chouinard
161 */
162public class TmfTimestampFormat extends SimpleDateFormat {
163
164 // ------------------------------------------------------------------------
165 // Constants
166 // ------------------------------------------------------------------------
167
168 /**
169 * This class' serialization ID
170 */
171 private static final long serialVersionUID = 2835829763122454020L;
172
173 /**
174 * The default timestamp pattern
175 */
081a0804 176 public static final String DEFAULT_TIME_PATTERN = "HH:mm:ss.SSS SSS SSS"; //$NON-NLS-1$
f8177ba2
FC
177
178 /**
18ec12d7 179 * The default interval pattern
f8177ba2 180 */
081a0804 181 public static final String DEFAULT_INTERVAL_PATTERN = "TTT.SSS SSS SSS"; //$NON-NLS-1$
f8177ba2
FC
182
183 // ------------------------------------------------------------------------
184 // Attributes
185 // ------------------------------------------------------------------------
186
187 // The default timestamp pattern
f8177ba2
FC
188 private static TmfTimestampFormat fDefaultTimeFormat = null;
189
190 // The default time interval format
f8177ba2
FC
191 private static TmfTimestampFormat fDefaultIntervalFormat = null;
192
193 // The timestamp pattern
194 private String fPattern;
195
081a0804
PT
196 // The index of the decimal separator in the pattern
197 private int fPatternDecimalSeparatorIndex;
198
199 // The decimal separator
200 private char fDecimalSeparator = '\0';
201
202 // The date and time pattern unquoted characters
203 private String fDateTimePattern;
204
18ec12d7
PT
205 // The sub-seconds pattern
206 private String fSubSecPattern;
207
208 // The list of supplementary patterns
a4524c1b 209 private List<String> fSupplPatterns = new ArrayList<>();
f8177ba2 210
8765b2d4
PT
211 // The locale
212 private final Locale fLocale;
213
f8177ba2
FC
214 /**
215 * The supplementary pattern letters. Can be redefined by sub-classes
216 * to either override existing letters or augment the letter set.
217 * If so, the format() method must provide the (re-)implementation of the
218 * pattern.
219 */
220 protected String fSupplPatternLetters = "TSCN"; //$NON-NLS-1$
081a0804
PT
221 /**
222 * The sub-second pattern letters.
223 * @since 3.0
224 */
225 protected String fSubSecPatternChars = "SCN"; //$NON-NLS-1$
226 /**
227 * The optional sub-second delimiter characters.
228 * @since 3.0
229 */
230 protected String fDelimiterChars = " .,-_:;/'\""; //$NON-NLS-1$
f8177ba2 231
6f4e8ec0 232 /*
f8177ba2
FC
233 * The bracketing symbols used to mitigate the risk of a format string
234 * that contains escaped sequences that would conflict with our format
235 * extension.
236 */
6f4e8ec0 237 /** The open bracket symbol */
f8177ba2 238 protected String fOpenBracket = "[&"; //$NON-NLS-1$
6f4e8ec0
AM
239
240 /** The closing bracket symbol */
f8177ba2
FC
241 protected String fCloseBracket = "&]"; //$NON-NLS-1$
242
243 // ------------------------------------------------------------------------
244 // Constructors
245 // ------------------------------------------------------------------------
246
247 /**
248 * The default constructor (uses the default pattern)
249 */
250 public TmfTimestampFormat() {
7bab46e6 251 this(TmfTimePreferences.getInstance().getTimePattern());
f8177ba2
FC
252 }
253
254 /**
255 * The normal constructor
256 *
257 * @param pattern the format pattern
258 */
259 public TmfTimestampFormat(String pattern) {
8765b2d4 260 fLocale = Locale.getDefault();
f8177ba2
FC
261 applyPattern(pattern);
262 }
263
c1cd9635
MAL
264 /**
265 * The full constructor
266 *
267 * @param pattern the format pattern
268 * @param timeZone the time zone
4b121c48 269 * @since 2.1
c1cd9635
MAL
270 */
271 public TmfTimestampFormat(String pattern, TimeZone timeZone) {
8765b2d4
PT
272 fLocale = Locale.getDefault();
273 setTimeZone(timeZone);
274 applyPattern(pattern);
275 }
276
277 /**
278 * The fuller constructor
279 *
280 * @param pattern the format pattern
281 * @param timeZone the time zone
282 * @param locale the locale
a465519a 283 * @since 3.2
8765b2d4
PT
284 */
285 public TmfTimestampFormat(String pattern, TimeZone timeZone, Locale locale) {
286 super("", locale); //$NON-NLS-1$
287 fLocale = locale;
c1cd9635 288 setTimeZone(timeZone);
8765b2d4 289 setCalendar(Calendar.getInstance(timeZone, locale));
c1cd9635
MAL
290 applyPattern(pattern);
291 }
292
f8177ba2
FC
293 /**
294 * The copy constructor
295 *
296 * @param other the other format pattern
297 */
298 public TmfTimestampFormat(TmfTimestampFormat other) {
8765b2d4 299 this(other.fPattern, other.getTimeZone(), other.fLocale);
f8177ba2
FC
300 }
301
302 // ------------------------------------------------------------------------
303 // Getters/setters
304 // ------------------------------------------------------------------------
305
c1cd9635 306 /**
4b121c48 307 * @since 2.1
c1cd9635
MAL
308 */
309 public static void updateDefaultFormats() {
8765b2d4
PT
310 fDefaultTimeFormat = new TmfTimestampFormat(
311 TmfTimePreferences.getInstance().getTimePattern(),
312 TmfTimePreferences.getInstance().getTimeZone(),
313 TmfTimePreferences.getInstance().getLocale());
7bab46e6 314 fDefaultIntervalFormat = new TmfTimestampFormat(TmfTimePreferences.getInstance().getIntervalPattern());
c1cd9635
MAL
315 }
316
f8177ba2
FC
317 /**
318 * @return the default time format pattern
319 */
5419a136 320 public static TmfTimestampFormat getDefaulTimeFormat() {
f8177ba2 321 if (fDefaultTimeFormat == null) {
8765b2d4
PT
322 fDefaultTimeFormat = new TmfTimestampFormat(
323 TmfTimePreferences.getInstance().getTimePattern(),
324 TmfTimePreferences.getInstance().getTimeZone(),
325 TmfTimePreferences.getInstance().getLocale());
f8177ba2
FC
326 }
327 return fDefaultTimeFormat;
328 }
329
f8177ba2
FC
330 /**
331 * @return the default interval format pattern
332 */
5419a136 333 public static TmfTimestampFormat getDefaulIntervalFormat() {
f8177ba2 334 if (fDefaultIntervalFormat == null) {
7bab46e6 335 fDefaultIntervalFormat = new TmfTimestampFormat(TmfTimePreferences.getInstance().getIntervalPattern());
f8177ba2
FC
336 }
337 return fDefaultIntervalFormat;
338 }
339
f8177ba2
FC
340 @Override
341 public void applyPattern(String pattern) {
342 fPattern = pattern;
081a0804
PT
343 fPatternDecimalSeparatorIndex = indexOfPatternDecimalSeparator(pattern);
344 fDateTimePattern = unquotePattern(pattern.substring(0, fPatternDecimalSeparatorIndex));
345 // Check that 'S' is not present in the date and time pattern
346 if (fDateTimePattern.indexOf('S') != -1) {
347 throw new IllegalArgumentException("Illegal pattern character 'S'"); //$NON-NLS-1$
348 }
349 // Check that 'W' is not present in the date and time pattern
350 if (fDateTimePattern.indexOf('W') != -1) {
351 throw new IllegalArgumentException("Illegal pattern character 'W'"); //$NON-NLS-1$
18ec12d7
PT
352 }
353 // The super pattern is the date/time pattern, quoted and bracketed
081a0804 354 super.applyPattern(quoteSpecificTags(pattern.substring(0, fPatternDecimalSeparatorIndex), true));
18ec12d7 355 // The sub-seconds pattern is bracketed (but not quoted)
081a0804 356 fSubSecPattern = quoteSpecificTags(pattern.substring(fPatternDecimalSeparatorIndex), false);
f8177ba2
FC
357 }
358
f8177ba2
FC
359 @Override
360 public String toPattern() {
361 return fPattern;
362 }
363
364 // ------------------------------------------------------------------------
365 // Operations
366 // ------------------------------------------------------------------------
367
368 /**
369 * Format the timestamp according to its pattern.
370 *
371 * @param value the timestamp value to format (in ns)
372 * @return the formatted timestamp
373 */
1e10705b 374 public synchronized String format(long value) {
f8177ba2
FC
375
376 // Split the timestamp value into its sub-components
0fb3c830
PT
377 long date = value / 1000000; // milliseconds since January 1, 1970, 00:00:00 GMT
378 long sec = value / 1000000000; // seconds since January 1, 1970, 00:00:00 GMT
18ec12d7
PT
379 long ms = Math.abs((value % 1000000000) / 1000000); // milliseconds
380 long cs = Math.abs((value % 1000000) / 1000); // microseconds
381 long ns = Math.abs(value % 1000); // nanoseconds
0fb3c830
PT
382
383 // Adjust for negative value when formatted as a date
18ec12d7 384 if (value < 0 && ms + cs + ns > 0 && !super.toPattern().contains(fOpenBracket + "T")) { //$NON-NLS-1$
0fb3c830
PT
385 date -= 1;
386 long nanosec = 1000000000 - (1000000 * ms + 1000 * cs + ns);
387 ms = nanosec / 1000000;
18ec12d7 388 cs = (nanosec % 1000000) / 1000;
0fb3c830
PT
389 ns = nanosec % 1000;
390 }
f8177ba2 391
18ec12d7 392 // Let the base class format the date/time pattern
0fb3c830 393 StringBuffer result = new StringBuffer(super.format(date));
18ec12d7
PT
394 // Append the sub-second pattern
395 result.append(fSubSecPattern);
f8177ba2 396
081a0804 397 int fractionDigitsPrinted = 0;
f8177ba2
FC
398 // Fill in our extensions
399 for (String pattern : fSupplPatterns) {
400 int length = pattern.length();
081a0804
PT
401 long val = 0;
402 int bufLength = 0;
f8177ba2
FC
403
404 // Format the proper value as per the pattern
405 switch (pattern.charAt(0)) {
406 case 'T':
0fb3c830
PT
407 if (value < 0 && sec == 0) {
408 result.insert(0, '-');
409 }
081a0804
PT
410 val = sec;
411 bufLength = Math.min(length, 10);
f8177ba2
FC
412 break;
413 case 'S':
081a0804
PT
414 val = 1000000 * ms + 1000 * cs + ns;
415 bufLength = 9;
f8177ba2
FC
416 break;
417 case 'C':
081a0804
PT
418 val = cs;
419 bufLength = Math.min(length, 3);
f8177ba2
FC
420 break;
421 case 'N':
081a0804
PT
422 val = ns;
423 bufLength = Math.min(length, 3);
f8177ba2
FC
424 break;
425 default:
426 break;
427 }
428
081a0804
PT
429 // Prepare the format buffer
430 StringBuffer fmt = new StringBuffer();
431 for (int i = 0; i < bufLength; i++) {
432 fmt.append("0"); //$NON-NLS-1$
433 }
434 DecimalFormat dfmt = new DecimalFormat(fmt.toString());
435 String fmtVal = dfmt.format(val);
436 if (pattern.charAt(0) == 'S') {
437 fmtVal = fmtVal.substring(fractionDigitsPrinted, Math.min(bufLength, fractionDigitsPrinted + length));
438 fractionDigitsPrinted += fmtVal.length();
439 }
440
18ec12d7 441 // Substitute the placeholder pattern with the formatted value
f8177ba2 442 String ph = new StringBuffer(fOpenBracket + pattern + fCloseBracket).toString();
18ec12d7 443 int loc = result.indexOf(ph);
f8177ba2
FC
444 result.replace(loc, loc + length + fOpenBracket.length() + fCloseBracket.length(), fmtVal);
445 }
446
447 return result.toString();
448 }
449
450 /**
451 * Parse a string according to the format pattern
452 *
081a0804 453 * @param source the source string
18ec12d7
PT
454 * @param ref the reference (base) time (in ns)
455 * @return the parsed value (in ns)
f8177ba2
FC
456 * @throws ParseException if the string has an invalid format
457 */
081a0804 458 public synchronized long parseValue(final String source, final long ref) throws ParseException {
f8177ba2
FC
459
460 // Trivial case
081a0804 461 if (source == null || source.length() == 0) {
f8177ba2
FC
462 return 0;
463 }
464
081a0804
PT
465 long seconds = 0;
466 boolean isNegative = source.charAt(0) == '-';
467 boolean isDateTimeFormat = true;
f8177ba2 468
081a0804 469 int index = indexOfSourceDecimalSeparator(source);
f8177ba2 470
081a0804 471 // Check for seconds in epoch pattern
f8177ba2 472 for (String pattern : fSupplPatterns) {
081a0804
PT
473 if (pattern.charAt(0) == 'T') {
474 isDateTimeFormat = false;
475 // Remove everything up to the first "." and compute the
476 // number of seconds since the epoch. If there is no period,
477 // assume an integer value and return immediately
478 if (index == 0 || (isNegative && index <= 1)) {
479 seconds = 0;
480 } else if (index == source.length()) {
481 return new DecimalFormat("0").parse(source).longValue() * 1000000000; //$NON-NLS-1$
482 } else {
483 seconds = new DecimalFormat("0").parse(source.substring(0, index)).longValue(); //$NON-NLS-1$
484 }
485 break;
f8177ba2
FC
486 }
487 }
488
489 // If there was no "T" (thus not an interval), parse as a date
081a0804
PT
490 if (isDateTimeFormat && super.toPattern().length() > 0) {
491 Date baseDate = super.parse(source.substring(0, index));
492 getCalendar();
493
494 if (ref != Long.MIN_VALUE) {
8765b2d4 495 Calendar baseTime = Calendar.getInstance(getTimeZone(), fLocale);
081a0804 496 baseTime.setTimeInMillis(baseDate.getTime());
8765b2d4 497 Calendar newTime = Calendar.getInstance(getTimeZone(), fLocale);
081a0804
PT
498 newTime.setTimeInMillis(ref / 1000000);
499 boolean setRemainingFields = false;
500 if (dateTimePatternContains("yY")) { //$NON-NLS-1$
501 newTime.set(Calendar.YEAR, baseTime.get(Calendar.YEAR));
502 setRemainingFields = true;
f8177ba2 503 }
081a0804
PT
504 if (setRemainingFields || dateTimePatternContains("M")) { //$NON-NLS-1$
505 newTime.set(Calendar.MONTH, baseTime.get(Calendar.MONTH));
506 setRemainingFields = true;
507 }
508 if (setRemainingFields || dateTimePatternContains("d")) { //$NON-NLS-1$
509 newTime.set(Calendar.DATE, baseTime.get(Calendar.DATE));
510 setRemainingFields = true;
511 } else if (dateTimePatternContains("D")) { //$NON-NLS-1$
512 newTime.set(Calendar.DAY_OF_YEAR, baseTime.get(Calendar.DAY_OF_YEAR));
513 setRemainingFields = true;
514 } else if (dateTimePatternContains("w")) { //$NON-NLS-1$
515 newTime.set(Calendar.WEEK_OF_YEAR, baseTime.get(Calendar.WEEK_OF_YEAR));
516 setRemainingFields = true;
517 }
518 if (dateTimePatternContains("F")) { //$NON-NLS-1$
519 newTime.set(Calendar.DAY_OF_WEEK_IN_MONTH, baseTime.get(Calendar.DAY_OF_WEEK_IN_MONTH));
520 setRemainingFields = true;
521 }
522 if (dateTimePatternContains("Eu")) { //$NON-NLS-1$
523 newTime.set(Calendar.DAY_OF_WEEK, baseTime.get(Calendar.DAY_OF_WEEK));
524 setRemainingFields = true;
f8177ba2 525 }
081a0804
PT
526 if (setRemainingFields || dateTimePatternContains("aHkKh")) { //$NON-NLS-1$
527 newTime.set(Calendar.HOUR_OF_DAY, baseTime.get(Calendar.HOUR_OF_DAY));
528 setRemainingFields = true;
529 }
530 if (setRemainingFields || dateTimePatternContains("m")) { //$NON-NLS-1$
531 newTime.set(Calendar.MINUTE, baseTime.get(Calendar.MINUTE));
532 setRemainingFields = true;
533 }
534 if (setRemainingFields || dateTimePatternContains("s")) { //$NON-NLS-1$
535 newTime.set(Calendar.SECOND, baseTime.get(Calendar.SECOND));
536 }
8765b2d4 537 newTime.set(Calendar.MILLISECOND, 0);
081a0804
PT
538 seconds = newTime.getTimeInMillis() / 1000;
539 } else {
540 seconds = baseDate.getTime() / 1000;
f8177ba2 541 }
081a0804
PT
542 } else if (isDateTimeFormat && ref != Long.MIN_VALUE) {
543 // If the date and time pattern is empty, adjust for reference
544 seconds = ref / 1000000000;
f8177ba2
FC
545 }
546
081a0804
PT
547 long nanos = parseSubSeconds(source.substring(index));
548 if (isNegative && !isDateTimeFormat) {
549 nanos = -nanos;
550 }
f8177ba2 551 // Compute the value in ns
081a0804 552 return seconds * 1000000000 + nanos;
f8177ba2
FC
553 }
554
555 /**
556 * Parse a string according to the format pattern
557 *
081a0804 558 * @param source the source string
18ec12d7 559 * @return the parsed value (in ns)
f8177ba2
FC
560 * @throws ParseException if the string has an invalid format
561 */
081a0804
PT
562 public long parseValue(final String source) throws ParseException {
563 long result = parseValue(source, Long.MIN_VALUE);
f8177ba2
FC
564 return result;
565
566 }
567
568 // ------------------------------------------------------------------------
569 // Helper functions
570 // ------------------------------------------------------------------------
571
081a0804
PT
572 /**
573 * Finds the index of the decimal separator in the pattern string, which is
574 * the last delimiter found before the first sub-second pattern character.
575 * Returns the pattern string length if decimal separator is not found.
576 */
577 private int indexOfPatternDecimalSeparator(String pattern) {
578 int lastDelimiterIndex = pattern.length();
579 boolean inQuote = false;
580 int index = 0;
581 while (index < pattern.length()) {
582 char ch = pattern.charAt(index);
583 if (ch == '\'') {
584 if (index + 1 < pattern.length()) {
585 index++;
586 ch = pattern.charAt(index);
587 if (ch != '\'') {
588 inQuote = !inQuote;
589 }
590 }
591 }
592 if (!inQuote) {
593 if (fSubSecPatternChars.indexOf(ch) != -1) {
594 if (lastDelimiterIndex < pattern.length()) {
595 fDecimalSeparator = pattern.charAt(lastDelimiterIndex);
596 }
597 return lastDelimiterIndex;
598 }
599 if (fDelimiterChars.indexOf(ch) != -1) {
600 lastDelimiterIndex = index;
601 if (ch == '\'') {
602 lastDelimiterIndex--;
603 }
604 }
605 }
606 index++;
607 }
608 return pattern.length();
609 }
610
611 /**
612 * Finds the first index of a decimal separator in the source string.
613 * Skips the number of decimal separators in the format pattern.
614 * Returns the source string length if decimal separator is not found.
615 */
616 private int indexOfSourceDecimalSeparator(String source) {
617 String pattern = fPattern.substring(0, fPatternDecimalSeparatorIndex);
618 String separator = fDecimalSeparator == '\'' ? "''" : String.valueOf(fDecimalSeparator); //$NON-NLS-1$
619 int sourcePos = source.indexOf(fDecimalSeparator);
620 int patternPos = pattern.indexOf(separator);
621 while (patternPos != -1 && sourcePos != -1) {
622 sourcePos = source.indexOf(fDecimalSeparator, sourcePos + 1);
623 patternPos = pattern.indexOf(separator, patternPos + separator.length());
624 }
625 if (sourcePos == -1) {
626 sourcePos = source.length();
627 }
628 return sourcePos;
629 }
630
631 /**
632 * Parse the sub-second digits in the input. Handle delimiters as optional
633 * characters. Match any non-pattern and non-delimiter pattern characters
634 * against the input. Returns the number of nanoseconds.
635 */
636 private long parseSubSeconds(String input) throws ParseException {
637 StringBuilder digits = new StringBuilder("000000000"); //$NON-NLS-1$
638 String pattern = fPattern.substring(fPatternDecimalSeparatorIndex);
639 boolean inQuote = false;
640 int digitIndex = 0;
641 int inputIndex = 0;
642 int patternIndex = 0;
643 while (patternIndex < pattern.length()) {
644 char ch = pattern.charAt(patternIndex);
645 if (ch == '\'') {
646 patternIndex++;
647 if (patternIndex < pattern.length()) {
648 ch = pattern.charAt(patternIndex);
649 if (ch != '\'') {
650 inQuote = !inQuote;
651 }
652 } else if (inQuote) {
653 // final end quote
654 break;
655 }
656 }
657 if (fDelimiterChars.indexOf(ch) != -1 && !inQuote) {
658 // delimiter is optional if not in quote
659 if (inputIndex < input.length() && input.charAt(inputIndex) == ch) {
660 inputIndex++;
661 }
662 patternIndex++;
663 continue;
664 } else if (fSubSecPatternChars.indexOf(ch) != -1 && !inQuote) {
665 // read digit if not in quote
666 if (inputIndex < input.length() && Character.isDigit(input.charAt(inputIndex))) {
667 if (digitIndex < digits.length()) {
668 digits.setCharAt(digitIndex, input.charAt(inputIndex));
669 digitIndex++;
670 }
671 inputIndex++;
672 } else {
673 // not a digit, stop parsing digits
674 digitIndex = digits.length();
675 }
676 patternIndex++;
677 continue;
678 }
679 if (inputIndex >= input.length() || input.charAt(inputIndex) != ch) {
680 throw new ParseException("Unparseable sub-seconds: \"" + input + '\"', inputIndex); //$NON-NLS-1$
681 }
682 patternIndex++;
683 inputIndex++;
684 }
685 return Long.parseLong(digits.toString());
686 }
687
f8177ba2
FC
688 /**
689 * Copy the pattern but quote (bracket with "[&" and "&]") the
18ec12d7
PT
690 * TmfTimestampFormat specific tags. Optionally surround tags with single
691 * quotes so these fields are treated as comments by the base class.
f8177ba2
FC
692 *
693 * It also keeps track of the corresponding quoted fields so they can be
694 * properly populated later on (by format()).
695 *
081a0804
PT
696 * @param pattern
697 * the 'extended' pattern
698 * @param includeQuotes
699 * true to include quotes from pattern and add single quotes
700 * around tags
f8177ba2
FC
701 * @return the quoted and bracketed pattern
702 */
081a0804 703 private String quoteSpecificTags(final String pattern, boolean includeQuotes) {
f8177ba2
FC
704
705 StringBuffer result = new StringBuffer();
706
707 int length = pattern.length();
708 boolean inQuote = false;
709
710 for (int i = 0; i < length; i++) {
711 char c = pattern.charAt(i);
081a0804
PT
712 if (c != '\'' || includeQuotes) {
713 result.append(c);
714 }
f8177ba2
FC
715 if (c == '\'') {
716 // '' is treated as a single quote regardless of being
717 // in a quoted section.
718 if ((i + 1) < length) {
719 c = pattern.charAt(i + 1);
720 if (c == '\'') {
721 i++;
722 result.append(c);
723 continue;
724 }
725 }
726 inQuote = !inQuote;
727 continue;
728 }
081a0804
PT
729 if (!inQuote && (fSupplPatternLetters.indexOf(c) != -1)) {
730 if (pattern.charAt(0) == fDecimalSeparator) {
731 if (fSubSecPatternChars.indexOf(c) == -1) {
732 // do not quote non-sub-second pattern letters in sub-second pattern
733 continue;
18ec12d7 734 }
081a0804
PT
735 } else {
736 if (fSubSecPatternChars.indexOf(c) != -1) {
737 // do not quote sub-second pattern letters in date and time pattern
738 continue;
f8177ba2 739 }
081a0804
PT
740 }
741 StringBuilder pat = new StringBuilder();
742 pat.append(c);
743 if (includeQuotes) {
744 result.insert(result.length() - 1, "'"); //$NON-NLS-1$
745 }
746 result.insert(result.length() - 1, fOpenBracket);
747 while ((i + 1) < length && pattern.charAt(i + 1) == c) {
748 result.append(c);
749 pat.append(c);
750 i++;
751 }
752 result.append(fCloseBracket);
753 if (includeQuotes) {
754 result.append("'"); //$NON-NLS-1$
755 }
756 fSupplPatterns.add(pat.toString());
757 }
758 }
759 return result.toString();
760 }
761
762 /**
763 * Returns the unquoted characters in this pattern.
764 */
765 private static String unquotePattern(String pattern) {
766 boolean inQuote = false;
767 int index = 0;
768 StringBuilder result = new StringBuilder();
769 while (index < pattern.length()) {
770 char ch = pattern.charAt(index);
771 if (ch == '\'') {
772 if (index + 1 < pattern.length()) {
773 index++;
774 ch = pattern.charAt(index);
775 if (ch != '\'') {
776 inQuote = !inQuote;
18ec12d7 777 }
f8177ba2
FC
778 }
779 }
081a0804
PT
780 if (!inQuote) {
781 result.append(ch);
782 }
783 index++;
f8177ba2
FC
784 }
785 return result.toString();
786 }
787
081a0804
PT
788 /**
789 * Returns true if the date and time pattern contains any of these chars.
790 */
791 private boolean dateTimePatternContains(String chars) {
792 int index = 0;
793 while (index < chars.length()) {
794 char ch = chars.charAt(index);
795 if (fDateTimePattern.indexOf(ch) != -1) {
796 return true;
797 }
798 index++;
799 }
800 return false;
801 }
f8177ba2 802}
This page took 0.085497 seconds and 5 git commands to generate.