1 /*******************************************************************************
2 * Copyright (c) 2012, 2014 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 and fraction of sec
13 *******************************************************************************/
15 package org
.eclipse
.tracecompass
.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
.Locale
;
25 import java
.util
.TimeZone
;
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.
33 * The timestamp representation is broken down into a number of optional
34 * components that can be assembled into a fairly simple way.
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:
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">
48 * <td>The seconds since the epoch
49 * <td><code>0-9223372036</code>
50 * <td><code>1332170682</code>
54 * <td><code>N/A</code>
55 * <td><code>Not supported</code>
56 * <tr bgcolor="#eeeeff">
59 * <td><code>N/A</code>
60 * <td><code>Not supported</code>
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.
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.
73 * <h4>Sub-Seconds Patterns</h4>
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
83 * <td>Fraction of second
84 * <td><code>0-999999999</code>
85 * <td><code>123456789</code>
86 * <tr bgcolor="#eeeeff">
88 * <td>Microseconds in ms
89 * <td><code>0-999</code>
90 * <td><code>456</code>
93 * <td>Nanoseconds in µs
94 * <td><code>0-999</code>
95 * <td><code>789</code>
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
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.
108 * The recognized sub-second delimiters are:
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>")
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.
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.
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
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>
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>
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>
160 * @author Francois Chouinard
162 public class TmfTimestampFormat
extends SimpleDateFormat
{
164 // ------------------------------------------------------------------------
166 // ------------------------------------------------------------------------
169 * This class' serialization ID
171 private static final long serialVersionUID
= 2835829763122454020L;
174 * The default timestamp pattern
176 public static final String DEFAULT_TIME_PATTERN
= "HH:mm:ss.SSS SSS SSS"; //$NON-NLS-1$
179 * The default interval pattern
181 public static final String DEFAULT_INTERVAL_PATTERN
= "TTT.SSS SSS SSS"; //$NON-NLS-1$
183 // ------------------------------------------------------------------------
185 // ------------------------------------------------------------------------
187 // The default timestamp pattern
188 private static TmfTimestampFormat fDefaultTimeFormat
= null;
190 // The default time interval format
191 private static TmfTimestampFormat fDefaultIntervalFormat
= null;
193 // The timestamp pattern
194 private String fPattern
;
196 // The index of the decimal separator in the pattern
197 private int fPatternDecimalSeparatorIndex
;
199 // The decimal separator
200 private char fDecimalSeparator
= '\0';
202 // The date and time pattern unquoted characters
203 private String fDateTimePattern
;
205 // The sub-seconds pattern
206 private String fSubSecPattern
;
208 // The list of supplementary patterns
209 private List
<String
> fSupplPatterns
= new ArrayList
<>();
212 private final Locale fLocale
;
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
220 protected String fSupplPatternLetters
= "TSCN"; //$NON-NLS-1$
222 * The sub-second pattern letters.
225 protected String fSubSecPatternChars
= "SCN"; //$NON-NLS-1$
227 * The optional sub-second delimiter characters.
230 protected String fDelimiterChars
= " .,-_:;/'\""; //$NON-NLS-1$
233 * The bracketing symbols used to mitigate the risk of a format string
234 * that contains escaped sequences that would conflict with our format
237 /** The open bracket symbol */
238 protected String fOpenBracket
= "[&"; //$NON-NLS-1$
240 /** The closing bracket symbol */
241 protected String fCloseBracket
= "&]"; //$NON-NLS-1$
243 // ------------------------------------------------------------------------
245 // ------------------------------------------------------------------------
248 * The default constructor (uses the default pattern)
250 public TmfTimestampFormat() {
251 this(TmfTimePreferences
.getInstance().getTimePattern());
255 * The normal constructor
257 * @param pattern the format pattern
259 public TmfTimestampFormat(String pattern
) {
260 fLocale
= Locale
.getDefault();
261 applyPattern(pattern
);
265 * The full constructor
267 * @param pattern the format pattern
268 * @param timeZone the time zone
271 public TmfTimestampFormat(String pattern
, TimeZone timeZone
) {
272 fLocale
= Locale
.getDefault();
273 setTimeZone(timeZone
);
274 applyPattern(pattern
);
278 * The fuller constructor
280 * @param pattern the format pattern
281 * @param timeZone the time zone
282 * @param locale the locale
285 public TmfTimestampFormat(String pattern
, TimeZone timeZone
, Locale locale
) {
286 super("", locale
); //$NON-NLS-1$
288 setTimeZone(timeZone
);
289 setCalendar(Calendar
.getInstance(timeZone
, locale
));
290 applyPattern(pattern
);
294 * The copy constructor
296 * @param other the other format pattern
298 public TmfTimestampFormat(TmfTimestampFormat other
) {
299 this(other
.fPattern
, other
.getTimeZone(), other
.fLocale
);
302 // ------------------------------------------------------------------------
304 // ------------------------------------------------------------------------
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());
318 * @return the default time format pattern
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());
327 return fDefaultTimeFormat
;
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 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$
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$
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);
360 public String
toPattern() {
364 // ------------------------------------------------------------------------
366 // ------------------------------------------------------------------------
369 * Format the timestamp according to its pattern.
371 * @param value the timestamp value to format (in ns)
372 * @return the formatted timestamp
374 public synchronized String
format(long value
) {
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
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$
386 long nanosec
= 1000000000 - (1000000 * ms
+ 1000 * cs
+ ns
);
387 ms
= nanosec
/ 1000000;
388 cs
= (nanosec
% 1000000) / 1000;
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
);
397 int fractionDigitsPrinted
= 0;
398 // Fill in our extensions
399 for (String pattern
: fSupplPatterns
) {
400 int length
= pattern
.length();
404 // Format the proper value as per the pattern
405 switch (pattern
.charAt(0)) {
407 if (value
< 0 && sec
== 0) {
408 result
.insert(0, '-');
411 bufLength
= Math
.min(length
, 10);
414 val
= 1000000 * ms
+ 1000 * cs
+ ns
;
419 bufLength
= Math
.min(length
, 3);
423 bufLength
= Math
.min(length
, 3);
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$
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();
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
);
447 return result
.toString();
451 * Parse a string according to the format pattern
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
458 public synchronized long parseValue(final String source
, final long ref
) throws ParseException
{
461 if (source
== null || source
.length() == 0) {
466 boolean isNegative
= source
.charAt(0) == '-';
467 boolean isDateTimeFormat
= true;
469 int index
= indexOfSourceDecimalSeparator(source
);
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)) {
480 } else if (index
== source
.length()) {
481 return new DecimalFormat("0").parse(source
).longValue() * 1000000000; //$NON-NLS-1$
483 seconds
= new DecimalFormat("0").parse(source
.substring(0, index
)).longValue(); //$NON-NLS-1$
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
));
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;
504 if (setRemainingFields
|| dateTimePatternContains("M")) { //$NON-NLS-1$
505 newTime
.set(Calendar
.MONTH
, baseTime
.get(Calendar
.MONTH
));
506 setRemainingFields
= true;
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;
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;
522 if (dateTimePatternContains("Eu")) { //$NON-NLS-1$
523 newTime
.set(Calendar
.DAY_OF_WEEK
, baseTime
.get(Calendar
.DAY_OF_WEEK
));
524 setRemainingFields
= true;
526 if (setRemainingFields
|| dateTimePatternContains("aHkKh")) { //$NON-NLS-1$
527 newTime
.set(Calendar
.HOUR_OF_DAY
, baseTime
.get(Calendar
.HOUR_OF_DAY
));
528 setRemainingFields
= true;
530 if (setRemainingFields
|| dateTimePatternContains("m")) { //$NON-NLS-1$
531 newTime
.set(Calendar
.MINUTE
, baseTime
.get(Calendar
.MINUTE
));
532 setRemainingFields
= true;
534 if (setRemainingFields
|| dateTimePatternContains("s")) { //$NON-NLS-1$
535 newTime
.set(Calendar
.SECOND
, baseTime
.get(Calendar
.SECOND
));
537 newTime
.set(Calendar
.MILLISECOND
, 0);
538 seconds
= newTime
.getTimeInMillis() / 1000;
540 seconds
= baseDate
.getTime() / 1000;
542 } else if (isDateTimeFormat
&& ref
!= Long
.MIN_VALUE
) {
543 // If the date and time pattern is empty, adjust for reference
544 seconds
= ref
/ 1000000000;
547 long nanos
= parseSubSeconds(source
.substring(index
));
548 if (isNegative
&& !isDateTimeFormat
) {
551 // Compute the value in ns
552 return seconds
* 1000000000 + nanos
;
556 * Parse a string according to the format pattern
558 * @param source the source string
559 * @return the parsed value (in ns)
560 * @throws ParseException if the string has an invalid format
562 public long parseValue(final String source
) throws ParseException
{
563 long result
= parseValue(source
, Long
.MIN_VALUE
);
568 // ------------------------------------------------------------------------
570 // ------------------------------------------------------------------------
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.
577 private int indexOfPatternDecimalSeparator(String pattern
) {
578 int lastDelimiterIndex
= pattern
.length();
579 boolean inQuote
= false;
581 while (index
< pattern
.length()) {
582 char ch
= pattern
.charAt(index
);
584 if (index
+ 1 < pattern
.length()) {
586 ch
= pattern
.charAt(index
);
593 if (fSubSecPatternChars
.indexOf(ch
) != -1) {
594 if (lastDelimiterIndex
< pattern
.length()) {
595 fDecimalSeparator
= pattern
.charAt(lastDelimiterIndex
);
597 return lastDelimiterIndex
;
599 if (fDelimiterChars
.indexOf(ch
) != -1) {
600 lastDelimiterIndex
= index
;
602 lastDelimiterIndex
--;
608 return pattern
.length();
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.
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());
625 if (sourcePos
== -1) {
626 sourcePos
= source
.length();
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.
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;
642 int patternIndex
= 0;
643 while (patternIndex
< pattern
.length()) {
644 char ch
= pattern
.charAt(patternIndex
);
647 if (patternIndex
< pattern
.length()) {
648 ch
= pattern
.charAt(patternIndex
);
652 } else if (inQuote
) {
657 if (fDelimiterChars
.indexOf(ch
) != -1 && !inQuote
) {
658 // delimiter is optional if not in quote
659 if (inputIndex
< input
.length() && input
.charAt(inputIndex
) == ch
) {
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
));
673 // not a digit, stop parsing digits
674 digitIndex
= digits
.length();
679 if (inputIndex
>= input
.length() || input
.charAt(inputIndex
) != ch
) {
680 throw new ParseException("Unparseable sub-seconds: \"" + input
+ '\"', inputIndex
); //$NON-NLS-1$
685 return Long
.parseLong(digits
.toString());
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.
693 * It also keeps track of the corresponding quoted fields so they can be
694 * properly populated later on (by format()).
697 * the 'extended' pattern
698 * @param includeQuotes
699 * true to include quotes from pattern and add single quotes
701 * @return the quoted and bracketed pattern
703 private String
quoteSpecificTags(final String pattern
, boolean includeQuotes
) {
705 StringBuffer result
= new StringBuffer();
707 int length
= pattern
.length();
708 boolean inQuote
= false;
710 for (int i
= 0; i
< length
; i
++) {
711 char c
= pattern
.charAt(i
);
712 if (c
!= '\'' || includeQuotes
) {
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);
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
736 if (fSubSecPatternChars
.indexOf(c
) != -1) {
737 // do not quote sub-second pattern letters in date and time pattern
741 StringBuilder pat
= new StringBuilder();
744 result
.insert(result
.length() - 1, "'"); //$NON-NLS-1$
746 result
.insert(result
.length() - 1, fOpenBracket
);
747 while ((i
+ 1) < length
&& pattern
.charAt(i
+ 1) == c
) {
752 result
.append(fCloseBracket
);
754 result
.append("'"); //$NON-NLS-1$
756 fSupplPatterns
.add(pat
.toString());
759 return result
.toString();
763 * Returns the unquoted characters in this pattern.
765 private static String
unquotePattern(String pattern
) {
766 boolean inQuote
= false;
768 StringBuilder result
= new StringBuilder();
769 while (index
< pattern
.length()) {
770 char ch
= pattern
.charAt(index
);
772 if (index
+ 1 < pattern
.length()) {
774 ch
= pattern
.charAt(index
);
785 return result
.toString();
789 * Returns true if the date and time pattern contains any of these chars.
791 private boolean dateTimePatternContains(String chars
) {
793 while (index
< chars
.length()) {
794 char ch
= chars
.charAt(index
);
795 if (fDateTimePattern
.indexOf(ch
) != -1) {