EMMA Coverage Report (generated Sat Jul 01 16:38:45 PDT 2006)
[all classes][jade.jademx.util.iso8601]

COVERAGE SUMMARY FOR SOURCE FILE [Duration.java]

nameclass, %method, %block, %line, %
Duration.java100% (1/1)100% (36/36)87%  (1948/2250)88%  (382/435)

COVERAGE BREAKDOWN BY CLASS AND METHOD

nameclass, %method, %block, %line, %
     
class Duration100% (1/1)100% (36/36)87%  (1948/2250)88%  (382/435)
setFractionInternal (char, String): void 100% (1/1)49%  (47/96)70%  (11.1/16)
toMilliseconds (): long 100% (1/1)71%  (157/222)62%  (31/50)
toSeconds (): long 100% (1/1)74%  (93/126)65%  (17/26)
parseHMS (String, int): void 100% (1/1)85%  (484/567)91%  (86/94)
parse (String): void 100% (1/1)91%  (515/566)94%  (91/97)
toString (): String 100% (1/1)94%  (314/334)91%  (62.9/69)
setFraction (char, String): void 100% (1/1)96%  (22/23)98%  (2/2)
Duration (): void 100% (1/1)100% (42/42)100% (15/15)
Duration (String): void 100% (1/1)100% (45/45)100% (16/16)
appendFraction (StringBuffer): void 100% (1/1)100% (25/25)100% (4/4)
getDays (): int 100% (1/1)100% (3/3)100% (1/1)
getFraction (): String 100% (1/1)100% (3/3)100% (1/1)
getFractionUnit (): char 100% (1/1)100% (3/3)100% (1/1)
getHours (): int 100% (1/1)100% (3/3)100% (1/1)
getLocale (): Locale 100% (1/1)100% (3/3)100% (1/1)
getMinutes (): int 100% (1/1)100% (3/3)100% (1/1)
getMonths (): int 100% (1/1)100% (3/3)100% (1/1)
getSeconds (): int 100% (1/1)100% (3/3)100% (1/1)
getWeeks (): int 100% (1/1)100% (3/3)100% (1/1)
getYears (): int 100% (1/1)100% (3/3)100% (1/1)
isAlternative (): boolean 100% (1/1)100% (3/3)100% (1/1)
isAsciiDigit (char): boolean 100% (1/1)100% (10/10)100% (1/1)
isExtended (): boolean 100% (1/1)100% (3/3)100% (1/1)
isWeeks (): boolean 100% (1/1)100% (3/3)100% (1/1)
nonDigitIndex (String, int): int 100% (1/1)100% (57/57)100% (9/9)
padIntString (int, int): String 100% (1/1)100% (19/19)100% (5/5)
setAlternative (boolean): void 100% (1/1)100% (14/14)100% (4/4)
setDays (int): void 100% (1/1)100% (7/7)100% (3/3)
setExtended (boolean): void 100% (1/1)100% (4/4)100% (2/2)
setHours (int): void 100% (1/1)100% (7/7)100% (3/3)
setLocale (Locale): void 100% (1/1)100% (4/4)100% (2/2)
setMinutes (int): void 100% (1/1)100% (7/7)100% (3/3)
setMonths (int): void 100% (1/1)100% (7/7)100% (3/3)
setSeconds (int): void 100% (1/1)100% (7/7)100% (3/3)
setWeeks (int): void 100% (1/1)100% (15/15)100% (5/5)
setYears (int): void 100% (1/1)100% (7/7)100% (3/3)

1// jademx - JADE management using JMX
2// Copyright 2004-2005 Caboodle Networks, Inc.
3//
4// This library is free software; you can redistribute it and/or
5// modify it under the terms of the GNU Lesser General Public
6// License as published by the Free Software Foundation; either
7// version 2.1 of the License, or (at your option) any later version.
8//
9// This library is distributed in the hope that it will be useful,
10// but WITHOUT ANY WARRANTY; without even the implied warranty of
11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12// Lesser General Public License for more details.
13//
14// You should have received a copy of the GNU Lesser General Public
15// License along with this library; if not, write to the Free Software
16// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
17 
18package jade.jademx.util.iso8601;
19 
20import java.io.Serializable;
21import java.text.DecimalFormatSymbols;
22import java.util.Locale;
23import java.math.BigDecimal;
24import java.math.BigInteger;
25 
26/**
27 * Represent an ISO 8601 duration.
28 * Compliant with ISO 8601:2000 section 5.5.4.2.
29 * Note that the setting of weeks is mutually exclusive from setting of 
30 * all other time fields.
31 * !!!TODO allow non-second fractional parts
32 * @author David Bernstein, <a href="http://www.caboodlenetworks.com"
33 *  >Caboodle Networks, Inc.</a>
34 */
35public class Duration implements Serializable, Cloneable {
36 
37    /** is duration expressed in weeks or not */
38    private boolean isWeeks = false;
39 
40    /** is extended format used */
41    private boolean extended = false;
42 
43    /** is alternative format used */
44    private boolean alternative = false;
45 
46    /** years */
47    private int years   = 0;
48 
49    /** months */
50    private int months  = 0;
51 
52    /** days */
53    private int days    = 0;
54 
55    /** hours */
56    private int hours   = 0;
57 
58    /** minutes */
59    private int minutes = 0;
60 
61    /** seconds */
62    private int seconds = 0;
63 
64    /** weeks */
65    private int weeks   = 0;
66 
67    /** locale */
68    private Locale locale = null;
69 
70    /** unit fraction applies to */
71    private char fractionUnit = UNSET_FRACTION;
72 
73    /** fraction */
74    private String fraction = null;
75 
76    // put format constants in ISO8601.java and allow setting here to govern toString()
77 
78    // fraction constant are different from designators because
79    // months and minutes would collide
80 
81    /** year fraction */
82    public final static char YEAR_FRACTION     = 'Y';
83 
84    /** month fraction */
85    public final static char MONTH_FRACTION    = 'M';
86 
87    /** day fraction */
88    public final static char DAY_FRACTION      = 'D';
89 
90    /** hour fraction */
91    public final static char HOUR_FRACTION     = 'H';
92 
93    /** minute fraction */
94    public final static char MINUTE_FRACTION   = 'm';
95 
96    /** second fraction */
97    public final static char SECOND_FRACTION   = 'S';
98 
99    /** week fraction */
100    public final static char WEEK_FRACTION     = 'W';
101    
102    /** unset fraction */
103    public final static char UNSET_FRACTION    = 'T';
104 
105    // move to ISO8601.java
106 
107    /** duration designator */
108    private final static char DURATION_DESIGNATOR = 'P';
109 
110    /** year designator */
111    private final static char YEAR_DESIGNATOR     = 'Y';
112 
113    /** month designator */
114    private final static char MONTH_DESIGNATOR    = 'M';
115 
116    /** day designator */
117    private final static char DAY_DESIGNATOR      = 'D';
118 
119    /** hour designator */
120    private final static char HOUR_DESIGNATOR     = 'H';
121 
122    /** minute designator */
123    private final static char MINUTE_DESIGNATOR   = 'M';
124 
125    /** second designator */
126    private final static char SECOND_DESIGNATOR   = 'S';
127 
128    /** week designator */
129    private final static char WEEK_DESIGNATOR     = 'W';
130 
131    /** time designator */
132    private final static char TIME_DESIGNATOR     = 'T';
133 
134    /** full stop character */
135    private final static char FULL_STOP = '.';
136 
137    /** comma character */
138    private final static char COMMA     = ',';
139 
140    /** hyphen character */
141    private final static char HYPHEN    = '-';
142 
143    /** colon character */
144    private final static char COLON     = ':';
145 
146    /** number of milliseconds in a second */
147    private final static int MS_IN_SEC  = 1000;
148 
149    /** number of seconds in a minute  */
150    private final static int SEC_IN_MIN = 60;
151 
152    /** number of minutes in an hour */
153    private final static int MIN_IN_HR  = 60;
154 
155    /** number of hours in a day */
156    private final static int HR_IN_DAY  = 24;
157 
158    /** number of days in week */
159    private final static int DAY_IN_WK  = 7;
160 
161    /** number of days in month - ARBITRARY */
162    private final static int DAY_IN_MON = 30;
163 
164    /** number of days in year - ARBITRARY */
165    private final static int DAY_IN_YR = 365;
166 
167    /**
168     * make an ISO 8601 duration object
169     */
170    public Duration() {
171    }
172 
173    /**
174     * return whether given character is an ASCII digit
175     * @param c character to test for digitness
176     * @return whether given character is an ASCII digit
177     */
178    private boolean isAsciiDigit( char c ) {
179        return ( c >= '0' ) && ( c <= '9' );
180    }
181 
182    /**
183     * return 
184     * @param s string to look at
185     * @param start index to start at
186     * @return index after start of first non-digit character (may be length of string)
187     */
188    private int nonDigitIndex( String s, int start ) {
189        if ( !isAsciiDigit( s.charAt( start ) ) ) {
190            throw new IllegalArgumentException( "in string \"" + s + "\":" +
191                    "expected digit at position " + start +
192                    " but found '" + s.charAt( start ) + "'" );
193        }
194        int sLen = s.length();
195        int firstNonDigitIndex = sLen;
196        for ( int i = start+1; i < sLen; i++ ) {
197            if ( !isAsciiDigit( s.charAt( i ) ) ) {
198                firstNonDigitIndex = i;
199                break;
200            }
201        }
202        return firstNonDigitIndex;
203    }
204 
205    /**
206     * helper method to parse() to get just the HMS part.
207     * @param s string representation
208     * @param tIndex where the 'T' was found
209     */
210    private void parseHMS( String s, int tIndex ) {
211        int sLen = s.length();
212        int firstDelimIndex = nonDigitIndex( s, tIndex+1 );
213        if ( firstDelimIndex >= sLen ) {
214            throw new IllegalArgumentException( "in string \"" + s + "\":" +
215            "no fields specified" );
216        }
217        char firstDelimChar = s.charAt( firstDelimIndex );
218        int thisInt;
219        try {
220            thisInt = Integer.parseInt( s.substring( tIndex+1, firstDelimIndex ) );
221        }
222        catch ( Exception e ) {
223            throw new IllegalArgumentException( "in string \"" + s + "\":" +
224            "unable to parse integer substring" );
225        }
226        String thisFraction = null;
227        int fractionStart;
228        if ( ( firstDelimChar == FULL_STOP ) ||
229                ( firstDelimChar == COMMA     ) ) {
230            if ( fractionUnit != UNSET_FRACTION ) {
231                throw new IllegalArgumentException( "in string \"" + s + "\":" +
232                "only one fractional part allowed" );
233            }
234            fractionStart = firstDelimIndex+1;
235            firstDelimIndex = nonDigitIndex( s, firstDelimIndex+1 );
236            thisFraction = s.substring( fractionStart, firstDelimIndex );
237            firstDelimChar = s.charAt( firstDelimIndex );
238        }
239        switch ( firstDelimChar ) {
240            case HOUR_DESIGNATOR:
241                hours = thisInt;
242                if ( null != thisFraction ) {
243                    setFractionInternal( HOUR_FRACTION,
244                            thisFraction );
245                    thisFraction = null;
246                }
247                break;
248            case MINUTE_DESIGNATOR:
249                minutes= thisInt;
250                if ( null != thisFraction ) {
251                    setFractionInternal( MINUTE_FRACTION,
252                            thisFraction );
253                    thisFraction = null;
254                }
255                break;
256            case SECOND_DESIGNATOR:
257                seconds = thisInt;
258                if ( null != thisFraction ) {
259                    setFractionInternal( SECOND_FRACTION,
260                            thisFraction );
261                    thisFraction = null;
262                }
263                break;
264            default:
265                throw new IllegalArgumentException( "in string \"" + s + "\":" +
266                        "'" + firstDelimChar + 
267                        "' found where '" +
268                        HOUR_DESIGNATOR + "', '" +
269                        MINUTE_DESIGNATOR + "', or '" +
270                        SECOND_DESIGNATOR + "' expected" );
271        }
272        if ( sLen > ( firstDelimIndex+1 ) ) {
273            char lastUnitSeen = firstDelimChar;
274            int secondDelimIndex = nonDigitIndex( s, firstDelimIndex+1 );
275            if ( secondDelimIndex < sLen ) {
276                char secondDelimChar = s.charAt( secondDelimIndex );
277                try {
278                    thisInt = Integer.parseInt( s.substring( firstDelimIndex+1, 
279                            secondDelimIndex ) );
280                }
281                catch ( Exception e ) {
282                    throw new IllegalArgumentException( "in string \"" + s + "\":" +
283                    "unable to parse integer substring" );
284                }
285                if ( ( secondDelimChar == FULL_STOP ) ||
286                        ( secondDelimChar == COMMA     ) ) {
287                    if ( fractionUnit != UNSET_FRACTION ) {
288                        throw new IllegalArgumentException( "in string \"" + s + "\":" +
289                        "only one fractional part allowed" );
290                    }
291                    fractionStart = secondDelimIndex+1;
292                    secondDelimIndex = nonDigitIndex( s, secondDelimIndex+1 );
293                    thisFraction = s.substring( fractionStart, secondDelimIndex );
294                    secondDelimChar = s.charAt( secondDelimIndex );
295                }
296                switch ( secondDelimChar ) {
297                    case MINUTE_DESIGNATOR:
298                        if ( ( MINUTE_DESIGNATOR == lastUnitSeen ) ||
299                                ( SECOND_DESIGNATOR == lastUnitSeen ) ) {
300                            throw new IllegalArgumentException( "in string \"" + s + "\":" +
301                                    "'" + secondDelimChar + 
302                                    "' follows '" +
303                                    firstDelimChar + "'" );
304                        }
305                        minutes = thisInt;
306                        if ( null != thisFraction ) {
307                            setFractionInternal( MINUTE_FRACTION,
308                                    thisFraction );
309                            thisFraction = null;
310                        }
311                        break;
312                    case SECOND_DESIGNATOR:
313                        if ( SECOND_DESIGNATOR == lastUnitSeen ) {
314                            throw new IllegalArgumentException( "in string \"" + s + "\":" +
315                                    "'" + secondDelimChar + 
316                                    "' follows '" +
317                                    firstDelimChar + "'" );
318                        }
319                        seconds = thisInt;
320                        if ( null != thisFraction ) {
321                            setFractionInternal( SECOND_FRACTION,
322                                    thisFraction );
323                            thisFraction = null;
324                        }
325                        break;
326                    default:
327                        throw new IllegalArgumentException( "in string \"" + s + "\":" +
328                                "'" + secondDelimChar + 
329                                "' found where '" +
330                                MINUTE_DESIGNATOR + "' or '" +
331                                SECOND_DESIGNATOR + "' expected" );
332                }
333                if ( sLen > ( secondDelimIndex+1 ) ) {
334                    lastUnitSeen = secondDelimChar;
335                    int thirdDelimIndex = nonDigitIndex( s, secondDelimIndex+1 );
336                    if ( thirdDelimIndex < sLen ) {
337                        char thirdDelimChar = s.charAt( thirdDelimIndex );
338                        try {
339                            thisInt = Integer.parseInt( s.substring( secondDelimIndex+1,
340                                    thirdDelimIndex ) );
341                        }
342                        catch ( Exception e ) {
343                            throw new IllegalArgumentException( "in string \"" + s + "\":" +
344                            "unable to parse integer substring" );
345                        }
346                        thisFraction = null;
347                        if ( ( thirdDelimChar == FULL_STOP ) ||
348                                ( thirdDelimChar == COMMA     ) ) {
349                            if ( fractionUnit != UNSET_FRACTION ) {
350                                throw new IllegalArgumentException( "in string \"" + s + "\":" +
351                                "only one fractional part allowed" );
352                            }
353                            fractionStart = thirdDelimIndex+1;
354                            thirdDelimIndex = nonDigitIndex( s, thirdDelimIndex+1 );
355                            thisFraction = s.substring( fractionStart, thirdDelimIndex );
356                            thirdDelimChar = s.charAt( thirdDelimIndex );
357                        }
358                        switch ( thirdDelimChar ) {
359                            case SECOND_DESIGNATOR:
360                                if ( SECOND_DESIGNATOR == lastUnitSeen ) {
361                                    throw new IllegalArgumentException( "in string \"" + s + "\":" +
362                                            "'" + thirdDelimChar + 
363                                            "' follows '" +
364                                            secondDelimChar + "'" );
365                                }
366                                seconds = thisInt;
367                                if ( null != thisFraction ) {
368                                    setFractionInternal( SECOND_FRACTION,
369                                            thisFraction );
370                                }
371                                break;
372                            default:
373                                throw new IllegalArgumentException( "in string \"" + s + "\":" +
374                                        "'" + thirdDelimChar + 
375                                        "' found where '" +
376                                        SECOND_DESIGNATOR + "' expected" );
377                        }
378                        if ( sLen > ( thirdDelimIndex+1 ) ) {
379                            throw new IllegalArgumentException( "in string \"" + s + "\":" +
380                            "extra unexpected characters found" );
381                        }
382                    }
383                }
384            }
385        }
386        
387    }
388 
389    /**
390     * set fields according to string representation
391     * @param s string representation
392     */
393    private void parse( String s ) {
394        
395        fraction = null;
396        fractionUnit = UNSET_FRACTION;
397        
398        if ( s.charAt(0) != DURATION_DESIGNATOR ) {
399            throw new IllegalArgumentException( "in string \"" + s + "\":" +
400                    "first character of ISO 8601 duration is not '"+
401                    DURATION_DESIGNATOR + "'" );
402        }
403        if ( s.length() == 1 ) {
404            throw new IllegalArgumentException( "no fields in string \"" + s + "\"" );
405        }
406 
407        // if next char is time designator
408        //   format is primary with values < day
409        // else 
410        //   get following integer string (parse exception if not)
411        //   if character after that is decimal sign (full stop or comma)
412        //     get next integer string as fraction
413        //   if character after parsed integer strings is week designator
414        //     format is PnW (primary/weeks) (fraction ok)
415        //   elsif int string is 4 digits and followed by hyphen
416        //     format is PYYYY-MM-DDThh:mm:ss (alternative/extended) (only seconds can have fraction)
417        //   elsif int string is 8 digits and followed by time designator
418        //     format is PYYYYMMDDThhmmss (alternative/basic) (only seconds can have fraction)
419        //   else
420        //     format must be primary: PnYnMnDTnHnMnS
421        //       where zero values may be omitted and fraction can only occur
422        //       in final field integer string and T must be used only if hours,
423        //       minutes, and/or seconds are specified.
424 
425        int sLen = s.length();
426        
427        if ( s.charAt(1) == TIME_DESIGNATOR ) {
428            // PTnHnMnS (primary - no year/month/day)
429            isWeeks = false;
430            extended = false;
431            alternative = false;
432            parseHMS( s, 1 );
433        }
434        else {
435            int firstDelimIndex = nonDigitIndex( s, 1 );
436            if ( firstDelimIndex >= sLen ) {
437                throw new IllegalArgumentException( "in string \"" + s + "\":" +
438                "no fields specified" );
439            }
440            char firstDelimChar = s.charAt( firstDelimIndex );
441            if ( ( FULL_STOP == firstDelimChar ) ||
442                    ( COMMA     == firstDelimChar ) ) {
443                throw new RuntimeException("fractions not yet implemented");
444                // be sure to set firstDelimChar to char *after* fractional part
445            }
446            if ( WEEK_DESIGNATOR == firstDelimChar ) {
447                // PnW
448                isWeeks = true;
449                alternative = false;
450                // extended not applicable
451                throw new RuntimeException("week parsing yet implemented");
452            }
453            else if ( ( 5 == firstDelimIndex ) && ( HYPHEN == firstDelimChar ) ) {
454                // PYYYY-MM-DDThh:mm:ss (alternative/extended)
455                isWeeks = false;
456                alternative = true;
457                extended    = true;
458                throw new RuntimeException("alternative extended parsing yet implemented");
459            }
460            else if ( ( 9 == firstDelimIndex ) && ( TIME_DESIGNATOR == firstDelimChar ) ) {
461                // PYYYYMMDDThhmmss (alternative/basic)
462                isWeeks = false;
463                alternative = true;
464                extended    = false;
465                throw new RuntimeException("alternative basic parsing yet implemented");
466            }
467            else {
468                // PnYnMnDTnHnMnS (primary)
469                isWeeks = false;
470                alternative = false;
471                // extended not applicable
472                int tIndex = -1; // where 'T' seen
473                // get YMD, then T delim if HMS, then HMS
474                int thisInt;
475                try {
476                    thisInt = Integer.parseInt( s.substring( 1, firstDelimIndex ) );
477                }
478                catch ( Exception e ) {
479                    throw new IllegalArgumentException( "in string \"" + s + "\":" +
480                    "unable to parse integer substring" );
481                }
482                switch ( firstDelimChar ) {
483                    case YEAR_DESIGNATOR:
484                        years = thisInt;
485                        break;
486                    case MONTH_DESIGNATOR:
487                        months = thisInt;
488                        break;
489                    case DAY_DESIGNATOR:
490                        days = thisInt;
491                        break;
492                    default:
493                        throw new IllegalArgumentException( "in string \"" + s + "\":" +
494                                "'" + firstDelimChar + 
495                                "' found where '" +
496                                YEAR_DESIGNATOR + "', '" +
497                                MONTH_DESIGNATOR + "', or '" +
498                                DAY_DESIGNATOR + "' expected" );
499                }
500                if ( sLen > ( firstDelimIndex+1 ) ) {
501                    if ( TIME_DESIGNATOR == s.charAt( firstDelimIndex+1 ) ) {
502                        tIndex = firstDelimIndex + 1;
503                    }
504                    else {
505                        char lastUnitSeen = firstDelimChar;
506                        int secondDelimIndex = nonDigitIndex( s, firstDelimIndex+1 );
507                        if ( secondDelimIndex < sLen ) {
508                            char secondDelimChar = s.charAt( secondDelimIndex );
509                            try {
510                                thisInt = Integer.parseInt( s.substring( firstDelimIndex+1, 
511                                        secondDelimIndex ) );
512                            }
513                            catch ( Exception e ) {
514                                throw new IllegalArgumentException( "in string \"" + s + "\":" +
515                                "unable to parse integer substring" );
516                            }
517                            switch ( secondDelimChar ) {
518                                case MONTH_DESIGNATOR:
519                                    if ( ( DAY_DESIGNATOR == lastUnitSeen ) ||
520                                            ( MONTH_DESIGNATOR == lastUnitSeen ) ) {
521                                        throw new IllegalArgumentException( "in string \"" + s + "\":" +
522                                                "'" + secondDelimChar + 
523                                                "' follows '" +
524                                                firstDelimChar + "'" );
525                                    }
526                                    months = thisInt;
527                                    break;
528                                case DAY_DESIGNATOR:
529                                    if ( DAY_DESIGNATOR == lastUnitSeen ) {
530                                        throw new IllegalArgumentException( "in string \"" + s + "\":" +
531                                                "'" + secondDelimChar + 
532                                                "' follows '" +
533                                                firstDelimChar + "'" );
534                                    }
535                                    days = thisInt;
536                                    break;
537                                default:
538                                    throw new IllegalArgumentException( "in string \"" + s + "\":" +
539                                            "'" + secondDelimChar + 
540                                            "' found where '" +
541                                            MONTH_DESIGNATOR + "' or '" +
542                                            DAY_DESIGNATOR + "' expected" );
543                            }
544                            
545                            if ( sLen > ( secondDelimIndex+1 ) ) {
546                                if ( TIME_DESIGNATOR == s.charAt( secondDelimIndex+1 ) ) {
547                                    tIndex = secondDelimIndex + 1;
548                                }
549                                else {
550                                    lastUnitSeen = secondDelimChar;
551                                    int thirdDelimIndex = nonDigitIndex( s, secondDelimIndex+1 );
552                                    if ( thirdDelimIndex < sLen ) {
553                                        char thirdDelimChar = s.charAt( thirdDelimIndex );
554                                        try {
555                                            thisInt = Integer.parseInt( s.substring( secondDelimIndex+1,
556                                                    thirdDelimIndex ) );
557                                        }
558                                        catch ( Exception e ) {
559                                            throw new IllegalArgumentException( "in string \"" + s + "\":" +
560                                            "unable to parse integer substring" );
561                                        }
562                                        switch ( thirdDelimChar ) {
563                                            case DAY_DESIGNATOR:
564                                                if ( DAY_DESIGNATOR == lastUnitSeen ) {
565                                                    throw new IllegalArgumentException( "in string \"" + s + "\":" +
566                                                            "'" + thirdDelimChar + 
567                                                            "' follows '" +
568                                                            secondDelimChar + "'" );
569                                                }
570                                                days = thisInt;
571                                                lastUnitSeen = thirdDelimChar;
572                                                break;
573                                            default:
574                                                throw new IllegalArgumentException( "in string \"" + s + "\":" +
575                                                        "'" + thirdDelimChar + 
576                                                        "' found where '" +
577                                                        DAY_DESIGNATOR + "' or '" +
578                                                        TIME_DESIGNATOR + "' expected" );
579                                        }
580                                    }
581                                    if ( sLen > ( thirdDelimIndex+1 ) ) {
582                                        if ( TIME_DESIGNATOR == s.charAt( thirdDelimIndex+1 ) ) {
583                                            tIndex = thirdDelimIndex + 1;
584                                        }
585                                        else {
586                                            throw new IllegalArgumentException( "in string \"" + s + "\":" +
587                                                    "only '" + TIME_DESIGNATOR +
588                                                    "' expected after '" + 
589                                                    lastUnitSeen + "'" );
590                                        }
591                                    }
592                                }
593                            }
594                            
595                        }
596                        
597                    }
598                }
599                if ( tIndex > 0 ) {
600                    parseHMS( s, tIndex );
601                }
602            }
603        }
604        
605    }
606    
607    /**
608     * make an ISO 8601 duration object
609     * @param s string representation
610     */
611    public Duration( String s ) {
612        parse( s );
613    }
614    
615    /**
616     * set years
617     * @param years years
618     */
619    public void setYears( int years ) {
620        isWeeks = false;
621        this.years = years;
622    }
623    
624    /**
625     * get years
626     * @return years
627     */
628    public int getYears() {
629        return years;
630    }
631    
632    /**
633     * set months
634     * @param months months
635     */
636    public void setMonths( int months ) {
637        isWeeks = false;
638        this.months = months;
639    }
640    
641    /**
642     * get months
643     * @return months
644     */
645    public int getMonths() {
646        return months;
647    }
648    
649    /**
650     * set days
651     * @param days days
652     */
653    public void setDays( int days ) {
654        isWeeks = false;
655        this.days = days;
656    }
657    
658    /**
659     * get days
660     * @return days
661     */
662    public int getDays() {
663        return days;
664    }
665    
666    /**
667     * set hours
668     * @param hours hours
669     */
670    public void setHours( int hours ) {
671        isWeeks = false;
672        this.hours = hours;
673    }
674    
675    /**
676     * get hours
677     * @return hours
678     */
679    public int getHours() {
680        return hours;
681    }
682    
683    /**
684     * set minutes
685     * @param minutes minutes
686     */
687    public void setMinutes( int minutes ) {
688        isWeeks = false;
689        this.minutes = minutes;
690    }
691    
692    /**
693     * get minutes
694     * @return minutes
695     */
696    public int getMinutes() {
697        return minutes;
698    }
699    
700    /**
701     * set seconds
702     * @param seconds seconds
703     */
704    public void setSeconds( int seconds ) {
705        isWeeks = false;
706        this.seconds = seconds;
707    }
708    
709    /**
710     * get seconds
711     * @return seconds
712     */
713    public int getSeconds() {
714        return seconds;
715    }
716    
717    /**
718     * set weeks
719     * @param weeks weeks
720     */
721    public void setWeeks( int weeks ) {
722        if ( alternative ) {
723            throw new RuntimeException("weeks are incompatible with alternative format");
724        }
725        isWeeks = true;
726        this.weeks = weeks;
727    }
728    
729    /**
730     * get weeks
731     * @return weeks
732     */
733    public int getWeeks() {
734        return weeks;
735    }
736    
737    /**
738     * return whether using weeks or other
739     * @return whether using weeks or other
740     */
741    public boolean isWeeks() {
742        return isWeeks;
743    }
744    
745    /**
746     * set locale
747     * @param locale locale
748     */
749    public void setLocale( Locale locale ) {
750        this.locale = locale;
751    }
752    
753    /**
754     * get locale
755     * @return locale
756     */
757    public Locale getLocale() {
758        return locale;
759    }
760    
761    /**
762     * set fraction. Until public fraction setting supported
763     * fraction can only be set for least significant used unit.
764     * for example, specify fraction for minutes and then specifying nonzero
765     * for seconds is illegal.
766     * @param fractionUnit which unit fraction is for
767     * @param fraction fraction
768     */
769    private void setFractionInternal( char fractionUnit, String fraction ) {
770        
771        switch ( fractionUnit ) {
772            case YEAR_FRACTION:
773            case MONTH_FRACTION:
774            case WEEK_FRACTION:
775            case DAY_FRACTION:
776            case HOUR_FRACTION:
777            case MINUTE_FRACTION:
778            case SECOND_FRACTION:
779                // OK: NOP
780                break;
781            default:
782                throw new IllegalArgumentException("illegal fraction unit '"+
783                        fractionUnit + "'");
784        }
785        
786        int fractionLen = fraction.length();
787        
788        // check for non-digit characters
789        for ( int i = 0; i < fractionLen; i++ ) {
790            if ( !isAsciiDigit( fraction.charAt( i ) ) ) {
791                throw new IllegalArgumentException( "expected digit at position " + i +
792                        " of fraction but found '" + 
793                        fraction.charAt( i ) + "'" );
794            }
795        }
796        
797        // truncate trailing zeroes
798        StringBuffer sb = new StringBuffer( fraction );
799        for ( int i = fraction.length()-1; i >= 0; i-- ) {
800            if ( ( sb.length() == i ) &&
801                    ( fraction.charAt( i ) == '0' ) ) {
802                sb.setLength( i );
803            }
804            else {
805                break;
806            }
807        }
808        
809        if ( 0 == sb.length() ) {
810            throw new IllegalArgumentException( "zero or empty fraction" );
811        }
812        
813        this.fractionUnit = fractionUnit;
814        this.fraction = sb.toString();
815        
816    }
817    
818    /**
819     * set fraction.
820     * fraction can only be set for least significant used unit.
821     * for example, specify fraction for minutes and then specifying nonzero
822     * for seconds is illegal.
823     * @param fractionUnit which unit fraction is for
824     * @param fraction     fraction
825     */
826    public void setFraction( char fractionUnit, String fraction ) {
827        // need to validate only digits
828        // need to normalize: truncate trailing zeros - eliminate if all zeros
829        this.fraction = fraction;
830        throw new RuntimeException(Duration.class.getName()+".setFraction(): fractions not yet implemented");
831    }
832    
833    /**
834     * get fraction
835     * @return fraction (null if unset)
836     */
837    public String getFraction() {
838        return fraction;
839    }
840    
841    /**
842     * get fraction unit
843     * @return fraction unit ('T' if unset)
844     */
845    public char getFractionUnit() {
846        return fractionUnit;
847    }
848    
849    /**
850     * set extended format
851     * @param extended extended format
852     */
853    public void setExtended( boolean extended ) {
854        this.extended = extended;
855    }
856    
857    /**
858     * get whether extended format
859     * @return whether extended
860     */
861    public boolean isExtended() {
862        return extended;
863    }
864    
865    /**
866     * set alternative format
867     * @param alternative alternative format
868     */
869    public void setAlternative( boolean alternative ) {
870        if ( alternative && isWeeks ) {
871            throw new RuntimeException("alternative format is incompatible with weeks");
872        }
873        this.alternative = alternative;
874    }
875    
876    /**
877     * get whether alternative format
878     * @return whether alternative
879     */
880    public boolean isAlternative() {
881        return alternative;
882    }
883    
884    /**
885     * return the number of seconds this duration represents.
886     * rounded to nearest second if necessary.
887     * month have 30 days and years have 365 days.
888     * @return the number of seconds this duration represents
889     */
890    public long toSeconds() {
891        long inSeconds;
892        if ( isWeeks ) {
893            inSeconds = 
894                ( weeks *   ( DAY_IN_WK  * HR_IN_DAY * MIN_IN_HR * SEC_IN_MIN ) );
895        }
896        else {
897            inSeconds = 
898                ( years   * ( DAY_IN_YR  * HR_IN_DAY * MIN_IN_HR * SEC_IN_MIN ) ) +
899                ( months  * ( DAY_IN_MON * HR_IN_DAY * MIN_IN_HR * SEC_IN_MIN ) ) +
900                ( days    * (              HR_IN_DAY * MIN_IN_HR * SEC_IN_MIN ) ) +
901                ( hours   * (                          MIN_IN_HR * SEC_IN_MIN ) ) +
902                ( minutes * (                                      SEC_IN_MIN ) ) +
903                seconds;
904        }
905        if ( UNSET_FRACTION != fractionUnit ) {
906            String fractionString = "0." + fraction;
907            BigDecimal decimalFraction = new BigDecimal( fractionString );
908            BigDecimal unitMultiplier;
909            BigDecimal fractionsPart;
910            switch ( fractionUnit ) {
911                case YEAR_FRACTION:
912                    unitMultiplier =
913                        new BigDecimal( BigInteger.valueOf( DAY_IN_YR  * HR_IN_DAY * MIN_IN_HR * SEC_IN_MIN ) );
914                    break;
915                case MONTH_FRACTION:
916                    unitMultiplier =
917                        new BigDecimal( BigInteger.valueOf( DAY_IN_MON * HR_IN_DAY * MIN_IN_HR * SEC_IN_MIN ) );
918                    break;
919                case WEEK_FRACTION:
920                    unitMultiplier =
921                        new BigDecimal( BigInteger.valueOf( DAY_IN_WK  * HR_IN_DAY * MIN_IN_HR * SEC_IN_MIN ) );
922                    break;
923                case DAY_FRACTION:
924                    unitMultiplier =
925                        new BigDecimal( BigInteger.valueOf(              HR_IN_DAY * MIN_IN_HR * SEC_IN_MIN ) );
926                    break;
927                case HOUR_FRACTION:
928                    unitMultiplier =
929                        new BigDecimal( BigInteger.valueOf(                          MIN_IN_HR * SEC_IN_MIN ) );
930                    break;
931                case MINUTE_FRACTION:
932                    unitMultiplier =
933                        new BigDecimal( BigInteger.valueOf(                                      SEC_IN_MIN ) );
934                    break;
935                case SECOND_FRACTION:
936                    unitMultiplier =
937                        new BigDecimal( BigInteger.valueOf(                                               1 ) );
938                    break;
939                default:
940                    throw new RuntimeException("unreachable");
941            }
942            fractionsPart = decimalFraction.multiply( unitMultiplier );
943            inSeconds += fractionsPart.longValue();
944        }
945        return inSeconds;
946    }
947    
948    /**
949     * return the number of milliseconds this duration represents
950     * rounded to nearest millisecond if necessary.
951     * month have 30 days and years have 365 days.
952     * @return the number of milliseconds this duration represents
953     */
954    public long toMilliseconds() {
955        // code is duplicated rather than called from toSeconds() to avoid rounding error
956        long inMilliseconds;
957        if ( isWeeks ) {
958            inMilliseconds = 
959                ( weeks *   ( DAY_IN_WK  * HR_IN_DAY * MIN_IN_HR * SEC_IN_MIN * MS_IN_SEC ) );
960        }
961        else {
962            inMilliseconds = 
963                ( years   * ( DAY_IN_YR  * HR_IN_DAY * MIN_IN_HR * SEC_IN_MIN * MS_IN_SEC ) ) +
964                ( months  * ( DAY_IN_MON * HR_IN_DAY * MIN_IN_HR * SEC_IN_MIN * MS_IN_SEC ) ) +
965                ( days    * (              HR_IN_DAY * MIN_IN_HR * SEC_IN_MIN * MS_IN_SEC ) ) +
966                ( hours   * (                          MIN_IN_HR * SEC_IN_MIN * MS_IN_SEC ) ) +
967                ( minutes * (                                      SEC_IN_MIN * MS_IN_SEC ) ) +
968                ( seconds * (                                                   MS_IN_SEC ) );
969        }
970        if ( UNSET_FRACTION != fractionUnit ) {
971            if ( SECOND_FRACTION == fractionUnit ) {
972                // can cheaply and easily and accurately compute case where fraction
973                // is in seconds
974                String fractionString = fraction;
975                boolean roundUpMs = false;
976                int fractionLen = fraction.length();
977                if ( fractionLen > 3 ) {
978                    fractionString = fraction.substring( 0, 3 );
979                    // check for needing to round up
980                    char c = fraction.charAt( 3 );
981                    if ( ( c >= '5' ) && ( c <= '9' ) ) {
982                        roundUpMs = true;
983                    }
984                }
985                else if ( fractionLen == 3 ) {
986                    fractionString = fraction;
987                }
988                else {
989                    StringBuffer fractionSB = new StringBuffer( fraction );
990                    while ( fractionSB.length() < 3 ) {
991                        fractionSB.append( '0' );
992                    }
993                    fractionString = fractionSB.toString();
994                }
995                int msInFraction  = 100 * ( fractionString.charAt(0) - '0' );
996                msInFraction     +=  10 * ( fractionString.charAt(1) - '0' );
997                msInFraction     +=         fractionString.charAt(2) - '0'  ;
998                if ( roundUpMs ) {
999                    msInFraction++;
1000                }
1001                inMilliseconds += msInFraction;
1002            }
1003            else {
1004                BigDecimal decimalFraction = new BigDecimal( "0." + fraction );
1005                BigDecimal unitMultiplier;
1006                BigDecimal fractionsPart;
1007                switch ( fractionUnit ) {
1008                    case YEAR_FRACTION:
1009                        unitMultiplier =
1010                            new BigDecimal( BigInteger.valueOf( DAY_IN_YR  * HR_IN_DAY * MIN_IN_HR * SEC_IN_MIN * MS_IN_SEC ) );
1011                        break;
1012                    case MONTH_FRACTION:
1013                        unitMultiplier =
1014                            new BigDecimal( BigInteger.valueOf( DAY_IN_MON * HR_IN_DAY * MIN_IN_HR * SEC_IN_MIN * MS_IN_SEC ) );
1015                        break;
1016                    case WEEK_FRACTION:
1017                        unitMultiplier =
1018                            new BigDecimal( BigInteger.valueOf( DAY_IN_WK  * HR_IN_DAY * MIN_IN_HR * SEC_IN_MIN * MS_IN_SEC ) );
1019                        break;
1020                    case DAY_FRACTION:
1021                        unitMultiplier =
1022                            new BigDecimal( BigInteger.valueOf(              HR_IN_DAY * MIN_IN_HR * SEC_IN_MIN * MS_IN_SEC ) );
1023                        break;
1024                    case HOUR_FRACTION:
1025                        unitMultiplier =
1026                            new BigDecimal( BigInteger.valueOf(                          MIN_IN_HR * SEC_IN_MIN * MS_IN_SEC ) );
1027                        break;
1028                    case MINUTE_FRACTION:
1029                        unitMultiplier =
1030                            new BigDecimal( BigInteger.valueOf(                                      SEC_IN_MIN * MS_IN_SEC ) );
1031                        break;
1032                    case SECOND_FRACTION:
1033                        unitMultiplier =
1034                            new BigDecimal( BigInteger.valueOf(                                                   MS_IN_SEC ) );
1035                        break;
1036                    default:
1037                        throw new RuntimeException("unreachable");
1038                }
1039                fractionsPart = decimalFraction.multiply( unitMultiplier );
1040                inMilliseconds += fractionsPart.longValue();
1041            }
1042        }
1043        return inMilliseconds;
1044    }
1045    
1046    /** 
1047     * convert integer to zero-padded string
1048     * @param i integer to convert
1049     * @param minLen minimum length of return string
1050     * @return zero-padded s
1051     */
1052    private String padIntString( int i, int minLen ) {
1053        String s = Integer.toString( i );
1054        while ( s.length() < minLen ) {
1055            s = "0" + s;
1056        }
1057        return s;
1058    }
1059    
1060    /**
1061     * convert this duration to string representation
1062     * @return string representation of this duration
1063     */
1064    public String toString() {
1065        StringBuffer sb = new StringBuffer();
1066        sb.append( DURATION_DESIGNATOR );
1067        if ( !alternative ) {
1068            // primary
1069            if ( isWeeks ) {
1070                sb.append( weeks );
1071                if ( WEEK_DESIGNATOR == fractionUnit ) {
1072                    appendFraction( sb );
1073                }
1074                sb.append( WEEK_DESIGNATOR );
1075            }
1076            else {
1077                if ( ( 0 == years   ) &&
1078                        ( 0 == months  ) &&
1079                        ( 0 == days    ) &&
1080                        ( 0 == minutes ) &&
1081                        ( 0 == seconds ) ) {
1082                    // zero
1083                    sb.append( TIME_DESIGNATOR );
1084                    sb.append( '0' );
1085                    char unitDesignator =
1086                        ( UNSET_FRACTION == fractionUnit )
1087                        ? SECOND_FRACTION
1088                                : fractionUnit;
1089                    sb.append( unitDesignator );
1090                    if ( UNSET_FRACTION != fractionUnit ) {
1091                        appendFraction( sb );
1092                    }
1093                }
1094                else {
1095                    // nonzero
1096                    if ( ( 0 != years ) || ( YEAR_FRACTION == fractionUnit ) ) {
1097                        sb.append( years );
1098                        if ( YEAR_FRACTION == fractionUnit ) {
1099                            appendFraction( sb );
1100                        }
1101                        sb.append( YEAR_DESIGNATOR );
1102                    }
1103                    if ( ( 0 != months ) || ( MONTH_FRACTION == fractionUnit ) ) {
1104                        sb.append( months );
1105                        if ( MONTH_FRACTION == fractionUnit ) {
1106                            appendFraction( sb );
1107                        }
1108                        sb.append( MONTH_DESIGNATOR );
1109                    }
1110                    if ( ( 0 != days ) || ( DAY_FRACTION == fractionUnit ) ) {
1111                        sb.append( days );
1112                        if ( DAY_FRACTION == fractionUnit ) {
1113                            appendFraction( sb );
1114                        }
1115                        sb.append( DAY_DESIGNATOR );
1116                    }
1117                    if ( ( 0 != hours   ) ||
1118                            ( 0 != minutes ) ||
1119                            ( 0 != seconds ) ) {
1120                        sb.append( TIME_DESIGNATOR );
1121                        if ( ( 0 != hours || ( HOUR_FRACTION == fractionUnit ) ) ) {
1122                            sb.append( hours );
1123                            if ( HOUR_FRACTION == fractionUnit ) {
1124                                appendFraction( sb );
1125                            }
1126                            sb.append( HOUR_DESIGNATOR );
1127                        }
1128                        if ( ( 0 != minutes ) || ( MINUTE_FRACTION == fractionUnit ) ) {
1129                            sb.append( minutes );
1130                            if ( MINUTE_FRACTION == fractionUnit ) {
1131                                appendFraction( sb );
1132                            }
1133                            sb.append( MINUTE_DESIGNATOR );
1134                        }
1135                        if ( ( 0 != seconds ) || ( SECOND_FRACTION == fractionUnit ) ) {
1136                            sb.append( seconds );
1137                            if ( SECOND_FRACTION == fractionUnit ) {
1138                                appendFraction( sb );
1139                            }
1140                            sb.append( SECOND_DESIGNATOR );
1141                        }
1142                    }
1143                }
1144            }
1145        }
1146        else {
1147            // alternative
1148            sb.append( padIntString( years, 4 ) );
1149            if ( extended ) {
1150                sb.append( HYPHEN );
1151            }
1152            sb.append( padIntString( months, 2 ) );
1153            if ( extended ) {
1154                sb.append( HYPHEN );
1155            }
1156            sb.append( padIntString( days, 2 ) );
1157            if ( extended ) {
1158                sb.append( TIME_DESIGNATOR );
1159            }
1160            sb.append( padIntString( hours, 2 ) );
1161            if ( extended ) {
1162                sb.append( COLON );
1163            }
1164            sb.append( padIntString( minutes, 2 ) );
1165            if ( extended ) {
1166                sb.append( COLON );
1167            }
1168            sb.append( padIntString( seconds, 2 ) );
1169            if ( SECOND_FRACTION == fractionUnit ) {
1170                appendFraction( sb );
1171            }
1172        }
1173        return sb.toString();
1174    }
1175    
1176    /**
1177     * append this duration's fraction (including decimal separator) to given string buffer
1178     * @param sb string buffer to which to append this duration's fraction
1179     */
1180    private void appendFraction( StringBuffer sb ) {
1181        DecimalFormatSymbols dfs = 
1182            ( ( null == locale )
1183                    ? new DecimalFormatSymbols()
1184                            : new DecimalFormatSymbols( locale ) );
1185        sb.append( dfs.getDecimalSeparator() );
1186        sb.append( fraction );
1187    }
1188    
1189}

[all classes][jade.jademx.util.iso8601]
EMMA 2.0.5312 (C) Vladimir Roubtsov