Move alltests plugin to the Trace Compass namespace
[deliverable/tracecompass.git] / org.eclipse.tracecompass.tmf.core / src / org / eclipse / linuxtools / tmf / core / timestamp / TmfTimestampFormat.java
1 /*******************************************************************************
2 * Copyright (c) 2012, 2014 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 and fraction of sec
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.Locale;
25 import java.util.TimeZone;
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 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.
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 and Time Patterns</h4>
37 * All date and time pattern letters defined in {@link SimpleDateFormat} are
38 * supported with the following exceptions:
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
46 * <tr bgcolor="#eeeeff">
47 * <td><code>T</code>
48 * <td>The seconds since the epoch
49 * <td><code>0-9223372036</code>
50 * <td><code>1332170682</code>
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>
61 * </table>
62 * </blockquote>
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>
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>
82 * <td><code>S</code>
83 * <td>Fraction of second
84 * <td><code>0-999999999</code>
85 * <td><code>123456789</code>
86 * <tr bgcolor="#eeeeff">
87 * <td><code>C</code>
88 * <td>Microseconds in ms
89 * <td><code>0-999</code>
90 * <td><code>456</code>
91 * <tr>
92 * <td><code>N</code>
93 * <td>Nanoseconds in &#181s
94 * <td><code>0-999</code>
95 * <td><code>789</code>
96 * </table>
97 * </blockquote>
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.
102 * <p>
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.
106 * <p>
107 *
108 * The recognized sub-second delimiters are:
109 * <ul>
110 * <li>Space ("<code> </code>")
111 * <li>Period ("<code>.</code>")
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>")
118 * <li>Single-quote ("<code>''</code>")
119 * <li>Double-quote ("<code>"</code>")
120 * </ul>
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.
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>
138 * <td><code>"yyyy-MM-dd HH:mm:ss.SSS.SSS.SSS"</code>
139 * <td><code>2012-03-19 11:24:42.539.677.389</code>
140 * <tr bgcolor="#eeeeff">
141 * <td><code>"yyyy-MM-dd HH:mm:ss.SSS.SSS"</code>
142 * <td><code>2012-03-19 11:24:42.539.677</code>
143 * <tr>
144 * <td><code>"yyyy-D HH:mm:ss.SSS.SSS"</code>
145 * <td><code>2012-79 11:24:42.539.677</code>
146 * <tr bgcolor="#eeeeff">
147 * <td><code>"ss,SSSS"</code>
148 * <td><code>42,5397</code>
149 * <tr>
150 * <td><code>"T.SSS SSS SSS"</code>
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 */
162 public 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 */
176 public static final String DEFAULT_TIME_PATTERN = "HH:mm:ss.SSS SSS SSS"; //$NON-NLS-1$
177
178 /**
179 * The default interval pattern
180 */
181 public static final String DEFAULT_INTERVAL_PATTERN = "TTT.SSS SSS SSS"; //$NON-NLS-1$
182
183 // ------------------------------------------------------------------------
184 // Attributes
185 // ------------------------------------------------------------------------
186
187 // The default timestamp pattern
188 private static TmfTimestampFormat fDefaultTimeFormat = null;
189
190 // The default time interval format
191 private static TmfTimestampFormat fDefaultIntervalFormat = null;
192
193 // The timestamp pattern
194 private String fPattern;
195
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
205 // The sub-seconds pattern
206 private String fSubSecPattern;
207
208 // The list of supplementary patterns
209 private List<String> fSupplPatterns = new ArrayList<>();
210
211 // The locale
212 private final Locale fLocale;
213
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$
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$
231
232 /*
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 */
237 /** The open bracket symbol */
238 protected String fOpenBracket = "[&"; //$NON-NLS-1$
239
240 /** The closing bracket symbol */
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() {
251 this(TmfTimePreferences.getInstance().getTimePattern());
252 }
253
254 /**
255 * The normal constructor
256 *
257 * @param pattern the format pattern
258 */
259 public TmfTimestampFormat(String pattern) {
260 fLocale = Locale.getDefault();
261 applyPattern(pattern);
262 }
263
264 /**
265 * The full constructor
266 *
267 * @param pattern the format pattern
268 * @param timeZone the time zone
269 * @since 2.1
270 */
271 public TmfTimestampFormat(String pattern, TimeZone timeZone) {
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
283 * @since 3.2
284 */
285 public TmfTimestampFormat(String pattern, TimeZone timeZone, Locale locale) {
286 super("", locale); //$NON-NLS-1$
287 fLocale = locale;
288 setTimeZone(timeZone);
289 setCalendar(Calendar.getInstance(timeZone, locale));
290 applyPattern(pattern);
291 }
292
293 /**
294 * The copy constructor
295 *
296 * @param other the other format pattern
297 */
298 public TmfTimestampFormat(TmfTimestampFormat other) {
299 this(other.fPattern, other.getTimeZone(), other.fLocale);
300 }
301
302 // ------------------------------------------------------------------------
303 // Getters/setters
304 // ------------------------------------------------------------------------
305
306 /**
307 * @since 2.1
308 */
309 public static void updateDefaultFormats() {
310 fDefaultTimeFormat = new TmfTimestampFormat(
311 TmfTimePreferences.getInstance().getTimePattern(),
312 TmfTimePreferences.getInstance().getTimeZone(),
313 TmfTimePreferences.getInstance().getLocale());
314 fDefaultIntervalFormat = new TmfTimestampFormat(TmfTimePreferences.getInstance().getIntervalPattern());
315 }
316
317 /**
318 * @return the default time format pattern
319 */
320 public static TmfTimestampFormat getDefaulTimeFormat() {
321 if (fDefaultTimeFormat == null) {
322 fDefaultTimeFormat = new TmfTimestampFormat(
323 TmfTimePreferences.getInstance().getTimePattern(),
324 TmfTimePreferences.getInstance().getTimeZone(),
325 TmfTimePreferences.getInstance().getLocale());
326 }
327 return fDefaultTimeFormat;
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 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$
352 }
353 // The super pattern is the date/time pattern, quoted and bracketed
354 super.applyPattern(quoteSpecificTags(pattern.substring(0, fPatternDecimalSeparatorIndex), true));
355 // The sub-seconds pattern is bracketed (but not quoted)
356 fSubSecPattern = quoteSpecificTags(pattern.substring(fPatternDecimalSeparatorIndex), false);
357 }
358
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 */
374 public synchronized String format(long value) {
375
376 // Split the timestamp value into its sub-components
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
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
382
383 // Adjust for negative value when formatted as a date
384 if (value < 0 && ms + cs + ns > 0 && !super.toPattern().contains(fOpenBracket + "T")) { //$NON-NLS-1$
385 date -= 1;
386 long nanosec = 1000000000 - (1000000 * ms + 1000 * cs + ns);
387 ms = nanosec / 1000000;
388 cs = (nanosec % 1000000) / 1000;
389 ns = nanosec % 1000;
390 }
391
392 // Let the base class format the date/time pattern
393 StringBuffer result = new StringBuffer(super.format(date));
394 // Append the sub-second pattern
395 result.append(fSubSecPattern);
396
397 int fractionDigitsPrinted = 0;
398 // Fill in our extensions
399 for (String pattern : fSupplPatterns) {
400 int length = pattern.length();
401 long val = 0;
402 int bufLength = 0;
403
404 // Format the proper value as per the pattern
405 switch (pattern.charAt(0)) {
406 case 'T':
407 if (value < 0 && sec == 0) {
408 result.insert(0, '-');
409 }
410 val = sec;
411 bufLength = Math.min(length, 10);
412 break;
413 case 'S':
414 val = 1000000 * ms + 1000 * cs + ns;
415 bufLength = 9;
416 break;
417 case 'C':
418 val = cs;
419 bufLength = Math.min(length, 3);
420 break;
421 case 'N':
422 val = ns;
423 bufLength = Math.min(length, 3);
424 break;
425 default:
426 break;
427 }
428
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
441 // Substitute the placeholder pattern with the formatted value
442 String ph = new StringBuffer(fOpenBracket + pattern + fCloseBracket).toString();
443 int loc = result.indexOf(ph);
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 *
453 * @param source the source string
454 * @param ref the reference (base) time (in ns)
455 * @return the parsed value (in ns)
456 * @throws ParseException if the string has an invalid format
457 */
458 public synchronized long parseValue(final String source, final long ref) throws ParseException {
459
460 // Trivial case
461 if (source == null || source.length() == 0) {
462 return 0;
463 }
464
465 long seconds = 0;
466 boolean isNegative = source.charAt(0) == '-';
467 boolean isDateTimeFormat = true;
468
469 int index = indexOfSourceDecimalSeparator(source);
470
471 // Check for seconds in epoch pattern
472 for (String pattern : fSupplPatterns) {
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;
486 }
487 }
488
489 // If there was no "T" (thus not an interval), parse as a date
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) {
495 Calendar baseTime = Calendar.getInstance(getTimeZone(), fLocale);
496 baseTime.setTimeInMillis(baseDate.getTime());
497 Calendar newTime = Calendar.getInstance(getTimeZone(), fLocale);
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;
503 }
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;
525 }
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 }
537 newTime.set(Calendar.MILLISECOND, 0);
538 seconds = newTime.getTimeInMillis() / 1000;
539 } else {
540 seconds = baseDate.getTime() / 1000;
541 }
542 } else if (isDateTimeFormat && ref != Long.MIN_VALUE) {
543 // If the date and time pattern is empty, adjust for reference
544 seconds = ref / 1000000000;
545 }
546
547 long nanos = parseSubSeconds(source.substring(index));
548 if (isNegative && !isDateTimeFormat) {
549 nanos = -nanos;
550 }
551 // Compute the value in ns
552 return seconds * 1000000000 + nanos;
553 }
554
555 /**
556 * Parse a string according to the format pattern
557 *
558 * @param source the source string
559 * @return the parsed value (in ns)
560 * @throws ParseException if the string has an invalid format
561 */
562 public long parseValue(final String source) throws ParseException {
563 long result = parseValue(source, Long.MIN_VALUE);
564 return result;
565
566 }
567
568 // ------------------------------------------------------------------------
569 // Helper functions
570 // ------------------------------------------------------------------------
571
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
688 /**
689 * Copy the pattern but quote (bracket with "[&" and "&]") the
690 * TmfTimestampFormat specific tags. Optionally surround tags with single
691 * quotes so these fields are treated as comments by the base class.
692 *
693 * It also keeps track of the corresponding quoted fields so they can be
694 * properly populated later on (by format()).
695 *
696 * @param pattern
697 * the 'extended' pattern
698 * @param includeQuotes
699 * true to include quotes from pattern and add single quotes
700 * around tags
701 * @return the quoted and bracketed pattern
702 */
703 private String quoteSpecificTags(final String pattern, boolean includeQuotes) {
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);
712 if (c != '\'' || includeQuotes) {
713 result.append(c);
714 }
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 }
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;
734 }
735 } else {
736 if (fSubSecPatternChars.indexOf(c) != -1) {
737 // do not quote sub-second pattern letters in date and time pattern
738 continue;
739 }
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;
777 }
778 }
779 }
780 if (!inQuote) {
781 result.append(ch);
782 }
783 index++;
784 }
785 return result.toString();
786 }
787
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 }
802 }
This page took 0.048405 seconds and 5 git commands to generate.