8ec5f4efb2541a5e52fa5beaeb621a4312db55c0
[deliverable/tracecompass.git] / org.eclipse.tracecompass.tmf.core / src / org / eclipse / tracecompass / tmf / core / timestamp / TmfTimestampFormat.java
1 /*******************************************************************************
2 * Copyright (c) 2012, 2015 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.tracecompass.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 * @author Francois Chouinard
159 */
160 public class TmfTimestampFormat extends SimpleDateFormat {
161
162 // ------------------------------------------------------------------------
163 // Constants
164 // ------------------------------------------------------------------------
165
166 /**
167 * This class' serialization ID
168 */
169 private static final long serialVersionUID = 2835829763122454020L;
170
171 /**
172 * The default timestamp pattern
173 */
174 public static final String DEFAULT_TIME_PATTERN = "HH:mm:ss.SSS SSS SSS"; //$NON-NLS-1$
175
176 /**
177 * The default interval pattern
178 */
179 public static final String DEFAULT_INTERVAL_PATTERN = "TTT.SSS SSS SSS"; //$NON-NLS-1$
180
181 // ------------------------------------------------------------------------
182 // Attributes
183 // ------------------------------------------------------------------------
184
185 // The default timestamp pattern
186 private static TmfTimestampFormat fDefaultTimeFormat = null;
187
188 // The default time interval format
189 private static TmfTimestampFormat fDefaultIntervalFormat = null;
190
191 // The timestamp pattern
192 private String fPattern;
193
194 // The index of the decimal separator in the pattern
195 private int fPatternDecimalSeparatorIndex;
196
197 // The decimal separator
198 private char fDecimalSeparator = '\0';
199
200 // The date and time pattern unquoted characters
201 private String fDateTimePattern;
202
203 // The sub-seconds pattern
204 private String fSubSecPattern;
205
206 // The list of supplementary patterns
207 private List<String> fSupplPatterns = new ArrayList<>();
208
209 // The locale
210 private final Locale fLocale;
211
212 /**
213 * The supplementary pattern letters. Can be redefined by sub-classes
214 * to either override existing letters or augment the letter set.
215 * If so, the format() method must provide the (re-)implementation of the
216 * pattern.
217 */
218 protected String fSupplPatternLetters = "TSCN"; //$NON-NLS-1$
219 /**
220 * The sub-second pattern letters.
221 */
222 protected String fSubSecPatternChars = "SCN"; //$NON-NLS-1$
223 /**
224 * The optional sub-second delimiter characters.
225 */
226 protected String fDelimiterChars = " .,-_:;/'\""; //$NON-NLS-1$
227
228 /*
229 * The bracketing symbols used to mitigate the risk of a format string
230 * that contains escaped sequences that would conflict with our format
231 * extension.
232 */
233 /** The open bracket symbol */
234 protected String fOpenBracket = "[&"; //$NON-NLS-1$
235
236 /** The closing bracket symbol */
237 protected String fCloseBracket = "&]"; //$NON-NLS-1$
238
239 // ------------------------------------------------------------------------
240 // Constructors
241 // ------------------------------------------------------------------------
242
243 /**
244 * The default constructor (uses the default pattern)
245 */
246 public TmfTimestampFormat() {
247 this(TmfTimePreferences.getTimePattern());
248 }
249
250 /**
251 * The normal constructor
252 *
253 * @param pattern the format pattern
254 */
255 public TmfTimestampFormat(String pattern) {
256 fLocale = Locale.getDefault();
257 applyPattern(pattern);
258 }
259
260 /**
261 * The full constructor
262 *
263 * @param pattern the format pattern
264 * @param timeZone the time zone
265 */
266 public TmfTimestampFormat(String pattern, TimeZone timeZone) {
267 fLocale = Locale.getDefault();
268 setTimeZone(timeZone);
269 applyPattern(pattern);
270 }
271
272 /**
273 * The fuller constructor
274 *
275 * @param pattern the format pattern
276 * @param timeZone the time zone
277 * @param locale the locale
278 */
279 public TmfTimestampFormat(String pattern, TimeZone timeZone, Locale locale) {
280 super("", locale); //$NON-NLS-1$
281 fLocale = locale;
282 setTimeZone(timeZone);
283 setCalendar(Calendar.getInstance(timeZone, locale));
284 applyPattern(pattern);
285 }
286
287 /**
288 * The copy constructor
289 *
290 * @param other the other format pattern
291 */
292 public TmfTimestampFormat(TmfTimestampFormat other) {
293 this(other.fPattern, other.getTimeZone(), other.fLocale);
294 }
295
296 // ------------------------------------------------------------------------
297 // Getters/setters
298 // ------------------------------------------------------------------------
299
300 /**
301 * Update the default time and default interval formats
302 */
303 public static synchronized void updateDefaultFormats() {
304 fDefaultTimeFormat = new TmfTimestampFormat(
305 TmfTimePreferences.getTimePattern(),
306 TmfTimePreferences.getTimeZone(),
307 TmfTimePreferences.getLocale());
308 fDefaultIntervalFormat = new TmfTimestampFormat(TmfTimePreferences.getIntervalPattern());
309 }
310
311 /**
312 * @return the default time format pattern
313 */
314 public static synchronized TmfTimestampFormat getDefaulTimeFormat() {
315 if (fDefaultTimeFormat == null) {
316 fDefaultTimeFormat = new TmfTimestampFormat(
317 TmfTimePreferences.getTimePattern(),
318 TmfTimePreferences.getTimeZone(),
319 TmfTimePreferences.getLocale());
320 }
321 return fDefaultTimeFormat;
322 }
323
324 /**
325 * @return the default interval format pattern
326 */
327 public static synchronized TmfTimestampFormat getDefaulIntervalFormat() {
328 if (fDefaultIntervalFormat == null) {
329 fDefaultIntervalFormat = new TmfTimestampFormat(TmfTimePreferences.getIntervalPattern());
330 }
331 return fDefaultIntervalFormat;
332 }
333
334 @Override
335 public void applyPattern(String pattern) {
336 fPattern = pattern;
337 fPatternDecimalSeparatorIndex = indexOfPatternDecimalSeparator(pattern);
338 fDateTimePattern = unquotePattern(pattern.substring(0, fPatternDecimalSeparatorIndex));
339 // Check that 'S' is not present in the date and time pattern
340 if (fDateTimePattern.indexOf('S') != -1) {
341 throw new IllegalArgumentException("Illegal pattern character 'S'"); //$NON-NLS-1$
342 }
343 // Check that 'W' is not present in the date and time pattern
344 if (fDateTimePattern.indexOf('W') != -1) {
345 throw new IllegalArgumentException("Illegal pattern character 'W'"); //$NON-NLS-1$
346 }
347 // The super pattern is the date/time pattern, quoted and bracketed
348 super.applyPattern(quoteSpecificTags(pattern.substring(0, fPatternDecimalSeparatorIndex), true));
349 // The sub-seconds pattern is bracketed (but not quoted)
350 fSubSecPattern = quoteSpecificTags(pattern.substring(fPatternDecimalSeparatorIndex), false);
351 }
352
353 @Override
354 public String toPattern() {
355 return fPattern;
356 }
357
358 // ------------------------------------------------------------------------
359 // Operations
360 // ------------------------------------------------------------------------
361
362 /**
363 * Format the timestamp according to its pattern.
364 *
365 * @param value the timestamp value to format (in ns)
366 * @return the formatted timestamp
367 */
368 public synchronized String format(long value) {
369
370 // Split the timestamp value into its sub-components
371 long date = value / 1000000; // milliseconds since January 1, 1970, 00:00:00 GMT
372 long sec = value / 1000000000; // seconds since January 1, 1970, 00:00:00 GMT
373 long ms = Math.abs((value % 1000000000) / 1000000); // milliseconds
374 long cs = Math.abs((value % 1000000) / 1000); // microseconds
375 long ns = Math.abs(value % 1000); // nanoseconds
376
377 // Adjust for negative value when formatted as a date
378 if (value < 0 && ms + cs + ns > 0 && !super.toPattern().contains(fOpenBracket + "T")) { //$NON-NLS-1$
379 date -= 1;
380 long nanosec = 1000000000 - (1000000 * ms + 1000 * cs + ns);
381 ms = nanosec / 1000000;
382 cs = (nanosec % 1000000) / 1000;
383 ns = nanosec % 1000;
384 }
385
386 // Let the base class format the date/time pattern
387 StringBuffer result = new StringBuffer(super.format(date));
388 // Append the sub-second pattern
389 result.append(fSubSecPattern);
390
391 int fractionDigitsPrinted = 0;
392 // Fill in our extensions
393 for (String pattern : fSupplPatterns) {
394 int length = pattern.length();
395 long val = 0;
396 int bufLength = 0;
397
398 // Format the proper value as per the pattern
399 switch (pattern.charAt(0)) {
400 case 'T':
401 if (value < 0 && sec == 0) {
402 result.insert(0, '-');
403 }
404 val = sec;
405 bufLength = Math.min(length, 10);
406 break;
407 case 'S':
408 val = 1000000 * ms + 1000 * cs + ns;
409 bufLength = 9;
410 break;
411 case 'C':
412 val = cs;
413 bufLength = Math.min(length, 3);
414 break;
415 case 'N':
416 val = ns;
417 bufLength = Math.min(length, 3);
418 break;
419 default:
420 break;
421 }
422
423 // Prepare the format buffer
424 StringBuffer fmt = new StringBuffer();
425 for (int i = 0; i < bufLength; i++) {
426 fmt.append("0"); //$NON-NLS-1$
427 }
428 DecimalFormat dfmt = new DecimalFormat(fmt.toString());
429 String fmtVal = dfmt.format(val);
430 if (pattern.charAt(0) == 'S') {
431 fmtVal = fmtVal.substring(fractionDigitsPrinted, Math.min(bufLength, fractionDigitsPrinted + length));
432 fractionDigitsPrinted += fmtVal.length();
433 }
434
435 // Substitute the placeholder pattern with the formatted value
436 String ph = new StringBuffer(fOpenBracket + pattern + fCloseBracket).toString();
437 int loc = result.indexOf(ph);
438 result.replace(loc, loc + length + fOpenBracket.length() + fCloseBracket.length(), fmtVal);
439 }
440
441 return result.toString();
442 }
443
444 /**
445 * Parse a string according to the format pattern
446 *
447 * @param source the source string
448 * @param ref the reference (base) time (in ns)
449 * @return the parsed value (in ns)
450 * @throws ParseException if the string has an invalid format
451 */
452 public synchronized long parseValue(final String source, final long ref) throws ParseException {
453
454 // Trivial case
455 if (source == null || source.length() == 0) {
456 return 0;
457 }
458
459 long seconds = 0;
460 boolean isNegative = source.charAt(0) == '-';
461 boolean isDateTimeFormat = true;
462
463 int index = indexOfSourceDecimalSeparator(source);
464
465 // Check for seconds in epoch pattern
466 for (String pattern : fSupplPatterns) {
467 if (pattern.charAt(0) == 'T') {
468 isDateTimeFormat = false;
469 // Remove everything up to the first "." and compute the
470 // number of seconds since the epoch. If there is no period,
471 // assume an integer value and return immediately
472 if (index == 0 || (isNegative && index <= 1)) {
473 seconds = 0;
474 } else if (index == source.length()) {
475 return new DecimalFormat("0").parse(source).longValue() * 1000000000; //$NON-NLS-1$
476 } else {
477 seconds = new DecimalFormat("0").parse(source.substring(0, index)).longValue(); //$NON-NLS-1$
478 }
479 break;
480 }
481 }
482
483 // If there was no "T" (thus not an interval), parse as a date
484 if (isDateTimeFormat && super.toPattern().length() > 0) {
485 Date baseDate = super.parse(source.substring(0, index));
486 getCalendar();
487
488 if (ref != Long.MIN_VALUE) {
489 Calendar baseTime = Calendar.getInstance(getTimeZone(), fLocale);
490 baseTime.setTimeInMillis(baseDate.getTime());
491 Calendar newTime = Calendar.getInstance(getTimeZone(), fLocale);
492 newTime.setTimeInMillis(ref / 1000000);
493 boolean setRemainingFields = false;
494 if (dateTimePatternContains("yY")) { //$NON-NLS-1$
495 newTime.set(Calendar.YEAR, baseTime.get(Calendar.YEAR));
496 setRemainingFields = true;
497 }
498 if (setRemainingFields || dateTimePatternContains("M")) { //$NON-NLS-1$
499 newTime.set(Calendar.MONTH, baseTime.get(Calendar.MONTH));
500 setRemainingFields = true;
501 }
502 if (setRemainingFields || dateTimePatternContains("d")) { //$NON-NLS-1$
503 newTime.set(Calendar.DATE, baseTime.get(Calendar.DATE));
504 setRemainingFields = true;
505 } else if (dateTimePatternContains("D")) { //$NON-NLS-1$
506 newTime.set(Calendar.DAY_OF_YEAR, baseTime.get(Calendar.DAY_OF_YEAR));
507 setRemainingFields = true;
508 } else if (dateTimePatternContains("w")) { //$NON-NLS-1$
509 newTime.set(Calendar.WEEK_OF_YEAR, baseTime.get(Calendar.WEEK_OF_YEAR));
510 setRemainingFields = true;
511 }
512 if (dateTimePatternContains("F")) { //$NON-NLS-1$
513 newTime.set(Calendar.DAY_OF_WEEK_IN_MONTH, baseTime.get(Calendar.DAY_OF_WEEK_IN_MONTH));
514 setRemainingFields = true;
515 }
516 if (dateTimePatternContains("Eu")) { //$NON-NLS-1$
517 newTime.set(Calendar.DAY_OF_WEEK, baseTime.get(Calendar.DAY_OF_WEEK));
518 setRemainingFields = true;
519 }
520 if (setRemainingFields || dateTimePatternContains("aHkKh")) { //$NON-NLS-1$
521 newTime.set(Calendar.HOUR_OF_DAY, baseTime.get(Calendar.HOUR_OF_DAY));
522 setRemainingFields = true;
523 }
524 if (setRemainingFields || dateTimePatternContains("m")) { //$NON-NLS-1$
525 newTime.set(Calendar.MINUTE, baseTime.get(Calendar.MINUTE));
526 setRemainingFields = true;
527 }
528 if (setRemainingFields || dateTimePatternContains("s")) { //$NON-NLS-1$
529 newTime.set(Calendar.SECOND, baseTime.get(Calendar.SECOND));
530 }
531 newTime.set(Calendar.MILLISECOND, 0);
532 seconds = newTime.getTimeInMillis() / 1000;
533 } else {
534 seconds = baseDate.getTime() / 1000;
535 }
536 } else if (isDateTimeFormat && ref != Long.MIN_VALUE) {
537 // If the date and time pattern is empty, adjust for reference
538 seconds = ref / 1000000000;
539 }
540
541 long nanos = parseSubSeconds(source.substring(index));
542 if (isNegative && !isDateTimeFormat) {
543 nanos = -nanos;
544 }
545 // Compute the value in ns
546 return seconds * 1000000000 + nanos;
547 }
548
549 /**
550 * Parse a string according to the format pattern
551 *
552 * @param source the source string
553 * @return the parsed value (in ns)
554 * @throws ParseException if the string has an invalid format
555 */
556 public long parseValue(final String source) throws ParseException {
557 long result = parseValue(source, Long.MIN_VALUE);
558 return result;
559
560 }
561
562 // ------------------------------------------------------------------------
563 // Helper functions
564 // ------------------------------------------------------------------------
565
566 /**
567 * Finds the index of the decimal separator in the pattern string, which is
568 * the last delimiter found before the first sub-second pattern character.
569 * Returns the pattern string length if decimal separator is not found.
570 */
571 private int indexOfPatternDecimalSeparator(String pattern) {
572 int lastDelimiterIndex = pattern.length();
573 boolean inQuote = false;
574 int index = 0;
575 while (index < pattern.length()) {
576 char ch = pattern.charAt(index);
577 if (ch == '\'') {
578 if (index + 1 < pattern.length()) {
579 index++;
580 ch = pattern.charAt(index);
581 if (ch != '\'') {
582 inQuote = !inQuote;
583 }
584 }
585 }
586 if (!inQuote) {
587 if (fSubSecPatternChars.indexOf(ch) != -1) {
588 if (lastDelimiterIndex < pattern.length()) {
589 fDecimalSeparator = pattern.charAt(lastDelimiterIndex);
590 }
591 return lastDelimiterIndex;
592 }
593 if (fDelimiterChars.indexOf(ch) != -1) {
594 lastDelimiterIndex = index;
595 if (ch == '\'') {
596 lastDelimiterIndex--;
597 }
598 }
599 }
600 index++;
601 }
602 return pattern.length();
603 }
604
605 /**
606 * Finds the first index of a decimal separator in the source string.
607 * Skips the number of decimal separators in the format pattern.
608 * Returns the source string length if decimal separator is not found.
609 */
610 private int indexOfSourceDecimalSeparator(String source) {
611 String separator = fDecimalSeparator == '\'' ? "''" : String.valueOf(fDecimalSeparator); //$NON-NLS-1$
612 int patternPos = fPattern.indexOf(separator);
613 int sourcePos = -1;
614 while (patternPos != -1 && patternPos <= fPatternDecimalSeparatorIndex) {
615 sourcePos = source.indexOf(fDecimalSeparator, sourcePos + 1);
616 if (sourcePos == -1) {
617 break;
618 }
619 // skip optional spaces and tabs before a pattern letter
620 char p = patternPos < fPattern.length() - 1 ? fPattern.charAt(patternPos + 1) : '\0';
621 if ((p >= 'a' && p <= 'z') || (p >= 'A' && p <= 'Z')) {
622 while (sourcePos < source.length() - 1) {
623 char s = source.charAt(sourcePos + 1);
624 if (s == ' ' || s == '\t') {
625 sourcePos++;
626 } else {
627 break;
628 }
629 }
630 }
631 patternPos = fPattern.indexOf(separator, patternPos + separator.length());
632 }
633 if (sourcePos == -1) {
634 sourcePos = source.length();
635 }
636 return sourcePos;
637 }
638
639 /**
640 * Parse the sub-second digits in the input. Handle delimiters as optional
641 * characters. Match any non-pattern and non-delimiter pattern characters
642 * against the input. Returns the number of nanoseconds.
643 */
644 private long parseSubSeconds(String input) throws ParseException {
645 StringBuilder digits = new StringBuilder("000000000"); //$NON-NLS-1$
646 String pattern = fPattern.substring(fPatternDecimalSeparatorIndex);
647 boolean inQuote = false;
648 int digitIndex = 0;
649 int inputIndex = 0;
650 int patternIndex = 0;
651 while (patternIndex < pattern.length()) {
652 char ch = pattern.charAt(patternIndex);
653 if (ch == '\'') {
654 patternIndex++;
655 if (patternIndex < pattern.length()) {
656 ch = pattern.charAt(patternIndex);
657 if (ch != '\'') {
658 inQuote = !inQuote;
659 }
660 } else if (inQuote) {
661 // final end quote
662 break;
663 }
664 }
665 if (fDelimiterChars.indexOf(ch) != -1 && !inQuote) {
666 // delimiter is optional if not in quote
667 if (inputIndex < input.length() && input.charAt(inputIndex) == ch) {
668 inputIndex++;
669 }
670 patternIndex++;
671 continue;
672 } else if (fSubSecPatternChars.indexOf(ch) != -1 && !inQuote) {
673 // read digit if not in quote
674 if (inputIndex < input.length() && Character.isDigit(input.charAt(inputIndex))) {
675 if (digitIndex < digits.length()) {
676 digits.setCharAt(digitIndex, input.charAt(inputIndex));
677 digitIndex++;
678 }
679 inputIndex++;
680 } else {
681 // not a digit, stop parsing digits
682 digitIndex = digits.length();
683 }
684 patternIndex++;
685 continue;
686 }
687 if (inputIndex >= input.length() || input.charAt(inputIndex) != ch) {
688 throw new ParseException("Unparseable sub-seconds: \"" + input + '\"', inputIndex); //$NON-NLS-1$
689 }
690 patternIndex++;
691 inputIndex++;
692 }
693 return Long.parseLong(digits.toString());
694 }
695
696 /**
697 * Copy the pattern but quote (bracket with "[&" and "&]") the
698 * TmfTimestampFormat specific tags. Optionally surround tags with single
699 * quotes so these fields are treated as comments by the base class.
700 *
701 * It also keeps track of the corresponding quoted fields so they can be
702 * properly populated later on (by format()).
703 *
704 * @param pattern
705 * the 'extended' pattern
706 * @param includeQuotes
707 * true to include quotes from pattern and add single quotes
708 * around tags
709 * @return the quoted and bracketed pattern
710 */
711 private String quoteSpecificTags(final String pattern, boolean includeQuotes) {
712
713 StringBuffer result = new StringBuffer();
714
715 int length = pattern.length();
716 boolean inQuote = false;
717
718 for (int i = 0; i < length; i++) {
719 char c = pattern.charAt(i);
720 if (c != '\'' || includeQuotes) {
721 result.append(c);
722 }
723 if (c == '\'') {
724 // '' is treated as a single quote regardless of being
725 // in a quoted section.
726 if ((i + 1) < length) {
727 c = pattern.charAt(i + 1);
728 if (c == '\'') {
729 i++;
730 result.append(c);
731 continue;
732 }
733 }
734 inQuote = !inQuote;
735 continue;
736 }
737 if (!inQuote && (fSupplPatternLetters.indexOf(c) != -1)) {
738 if (pattern.charAt(0) == fDecimalSeparator) {
739 if (fSubSecPatternChars.indexOf(c) == -1) {
740 // do not quote non-sub-second pattern letters in sub-second pattern
741 continue;
742 }
743 } else {
744 if (fSubSecPatternChars.indexOf(c) != -1) {
745 // do not quote sub-second pattern letters in date and time pattern
746 continue;
747 }
748 }
749 StringBuilder pat = new StringBuilder();
750 pat.append(c);
751 if (includeQuotes) {
752 result.insert(result.length() - 1, "'"); //$NON-NLS-1$
753 }
754 result.insert(result.length() - 1, fOpenBracket);
755 while ((i + 1) < length && pattern.charAt(i + 1) == c) {
756 result.append(c);
757 pat.append(c);
758 i++;
759 }
760 result.append(fCloseBracket);
761 if (includeQuotes) {
762 result.append("'"); //$NON-NLS-1$
763 }
764 fSupplPatterns.add(pat.toString());
765 }
766 }
767 return result.toString();
768 }
769
770 /**
771 * Returns the unquoted characters in this pattern.
772 */
773 private static String unquotePattern(String pattern) {
774 boolean inQuote = false;
775 int index = 0;
776 StringBuilder result = new StringBuilder();
777 while (index < pattern.length()) {
778 char ch = pattern.charAt(index);
779 if (ch == '\'') {
780 if (index + 1 < pattern.length()) {
781 index++;
782 ch = pattern.charAt(index);
783 if (ch != '\'') {
784 inQuote = !inQuote;
785 }
786 }
787 }
788 if (!inQuote) {
789 result.append(ch);
790 }
791 index++;
792 }
793 return result.toString();
794 }
795
796 /**
797 * Returns true if the date and time pattern contains any of these chars.
798 */
799 private boolean dateTimePatternContains(String chars) {
800 int index = 0;
801 while (index < chars.length()) {
802 char ch = chars.charAt(index);
803 if (fDateTimePattern.indexOf(ch) != -1) {
804 return true;
805 }
806 index++;
807 }
808 return false;
809 }
810 }
This page took 0.056584 seconds and 4 git commands to generate.