KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > apache > commons > lang > time > DurationFormatUtils


1 /*
2  * Copyright 2002-2005 The Apache Software Foundation.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */

16 package org.apache.commons.lang.time;
17
18 import org.apache.commons.lang.StringUtils;
19
20 import java.util.Calendar JavaDoc;
21 import java.util.Date JavaDoc;
22 import java.util.TimeZone JavaDoc;
23
24 /**
25  * <p>Duration formatting utilities and constants. The following table describes the tokens
26  * used in the pattern language for formatting. </p>
27  * <table border="1">
28  * <tr><th>character</th><th>duration element</th></tr>
29  * <tr><td>y</td><td>years</td></tr>
30  * <tr><td>M</td><td>months</td></tr>
31  * <tr><td>d</td><td>days</td></tr>
32  * <tr><td>H</td><td>hours</td></tr>
33  * <tr><td>m</td><td>minutes</td></tr>
34  * <tr><td>s</td><td>seconds</td></tr>
35  * <tr><td>S</td><td>milliseconds</td></tr>
36  * </table>
37  *
38  * @author Apache Ant - DateUtils
39  * @author <a HREF="mailto:sbailliez@apache.org">Stephane Bailliez</a>
40  * @author <a HREF="mailto:stefan.bodewig@epost.de">Stefan Bodewig</a>
41  * @author Stephen Colebourne
42  * @author <a HREF="mailto:ggregory@seagullsw.com">Gary Gregory</a>
43  * @author Henri Yandell
44  * @since 2.1
45  * @version $Id: DurationFormatUtils.java 165657 2005-05-02 18:31:49Z ggregory $
46  */

47 public class DurationFormatUtils {
48
49     /**
50      * <p>DurationFormatUtils instances should NOT be constructed in standard programming.</p>
51      *
52      * <p>This constructor is public to permit tools that require a JavaBean instance
53      * to operate.</p>
54      */

55     public DurationFormatUtils() {
56         super();
57     }
58
59     /**
60      * <p>Pattern used with <code>FastDateFormat</code> and <code>SimpleDateFormat</code>
61      * for the ISO8601 period format used in durations.</p>
62      *
63      * @see org.apache.commons.lang.time.FastDateFormat
64      * @see java.text.SimpleDateFormat
65      */

66     public static final String JavaDoc ISO_EXTENDED_FORMAT_PATTERN = "'P'yyyy'Y'M'M'd'DT'H'H'm'M's.S'S'";
67
68     //-----------------------------------------------------------------------
69
/**
70      * <p>Get the time gap as a string.</p>
71      *
72      * <p>The format used is ISO8601-like:
73      * <i>H</i>:<i>m</i>:<i>s</i>.<i>S</i>.</p>
74      *
75      * @param durationMillis the duration to format
76      * @return the time as a String
77      */

78     public static String JavaDoc formatDurationHMS(long durationMillis) {
79         return formatDuration(durationMillis, "H:mm:ss.SSS");
80     }
81
82     /**
83      * <p>Get the time gap as a string.</p>
84      *
85      * <p>The format used is the ISO8601 period format.</p>
86      *
87      * <p>This method formats durations using the days and lower fields of the
88      * ISO format pattern, such as P7D6H5M4.321S.</p>
89      *
90      * @param durationMillis the duration to format
91      * @return the time as a String
92      */

93     public static String JavaDoc formatDurationISO(long durationMillis) {
94         return formatDuration(durationMillis, ISO_EXTENDED_FORMAT_PATTERN, false);
95     }
96
97     /**
98      * <p>Get the time gap as a string, using the specified format, and padding with zeros and
99      * using the default timezone.</p>
100      *
101      * <p>This method formats durations using the days and lower fields of the
102      * format pattern. Months and larger are not used.</p>
103      *
104      * @param durationMillis the duration to format
105      * @param format the way in which to format the duration
106      * @return the time as a String
107      */

108     public static String JavaDoc formatDuration(long durationMillis, String JavaDoc format) {
109         return formatDuration(durationMillis, format, true);
110     }
111
112     /**
113      * <p>Get the time gap as a string, using the specified format.
114      * Padding the left hand side of numbers with zeroes is optional and
115      * the timezone may be specified.</p>
116      *
117      * <p>This method formats durations using the days and lower fields of the
118      * format pattern. Months and larger are not used.</p>
119      *
120      * @param durationMillis the duration to format
121      * @param format the way in which to format the duration
122      * @param padWithZeros whether to pad the left hand side of numbers with 0's
123      * @return the time as a String
124      */

125     public static String JavaDoc formatDuration(long durationMillis, String JavaDoc format, boolean padWithZeros) {
126
127         Token[] tokens = lexx(format);
128
129         int days = 0;
130         int hours = 0;
131         int minutes = 0;
132         int seconds = 0;
133         int milliseconds = 0;
134         
135         if (Token.containsTokenWithValue(tokens, d) ) {
136             days = (int) (durationMillis / DateUtils.MILLIS_PER_DAY);
137             durationMillis = durationMillis - (days * DateUtils.MILLIS_PER_DAY);
138         }
139         if (Token.containsTokenWithValue(tokens, H) ) {
140             hours = (int) (durationMillis / DateUtils.MILLIS_PER_HOUR);
141             durationMillis = durationMillis - (hours * DateUtils.MILLIS_PER_HOUR);
142         }
143         if (Token.containsTokenWithValue(tokens, m) ) {
144             minutes = (int) (durationMillis / DateUtils.MILLIS_PER_MINUTE);
145             durationMillis = durationMillis - (minutes * DateUtils.MILLIS_PER_MINUTE);
146         }
147         if (Token.containsTokenWithValue(tokens, s) ) {
148             seconds = (int) (durationMillis / DateUtils.MILLIS_PER_SECOND);
149             durationMillis = durationMillis - (seconds * DateUtils.MILLIS_PER_SECOND);
150         }
151         if (Token.containsTokenWithValue(tokens, S) ) {
152             milliseconds = (int) durationMillis;
153         }
154
155         return format(tokens, 0, 0, days, hours, minutes, seconds, milliseconds, padWithZeros);
156     }
157
158     /**
159      * <p>Format an elapsed time into a plurialization correct string.</p>
160      *
161      * <p>This method formats durations using the days and lower fields of the
162      * format pattern. Months and larger are not used.</p>
163      *
164      * @param durationMillis the elapsed time to report in milliseconds
165      * @param suppressLeadingZeroElements suppresses leading 0 elements
166      * @param suppressTrailingZeroElements suppresses trailing 0 elements
167      * @return the formatted text in days/hours/minutes/seconds
168      */

169     public static String JavaDoc formatDurationWords(
170         long durationMillis,
171         boolean suppressLeadingZeroElements,
172         boolean suppressTrailingZeroElements) {
173
174         // This method is generally replacable by the format method, but
175
// there are a series of tweaks and special cases that require
176
// trickery to replicate.
177
String JavaDoc duration = formatDuration(durationMillis, "d' days 'H' hours 'm' minutes 's' seconds'");
178         if (suppressLeadingZeroElements) {
179             // this is a temporary marker on the front. Like ^ in regexp.
180
duration = " " + duration;
181             String JavaDoc tmp = StringUtils.replaceOnce(duration, " 0 days", "");
182             if (tmp.length() != duration.length()) {
183                 duration = tmp;
184                 tmp = StringUtils.replaceOnce(duration, " 0 hours", "");
185                 if (tmp.length() != duration.length()) {
186                     duration = tmp;
187                     tmp = StringUtils.replaceOnce(duration, " 0 minutes", "");
188                     duration = tmp;
189                     if (tmp.length() != duration.length()) {
190                         duration = StringUtils.replaceOnce(tmp, " 0 seconds", "");
191                     }
192                 }
193             }
194             if (duration.length() != 0) {
195                 // strip the space off again
196
duration = duration.substring(1);
197             }
198         }
199         if (suppressTrailingZeroElements) {
200             String JavaDoc tmp = StringUtils.replaceOnce(duration, " 0 seconds", "");
201             if (tmp.length() != duration.length()) {
202                 duration = tmp;
203                 tmp = StringUtils.replaceOnce(duration, " 0 minutes", "");
204                 if (tmp.length() != duration.length()) {
205                     duration = tmp;
206                     tmp = StringUtils.replaceOnce(duration, " 0 hours", "");
207                     if (tmp.length() != duration.length()) {
208                         duration = StringUtils.replaceOnce(tmp, " 0 days", "");
209                     }
210                 }
211             }
212         }
213         // handle plurals
214
duration = StringUtils.replaceOnce(duration, "1 seconds", "1 second");
215         duration = StringUtils.replaceOnce(duration, "1 minutes", "1 minute");
216         duration = StringUtils.replaceOnce(duration, "1 hours", "1 hour");
217         duration = StringUtils.replaceOnce(duration, "1 days", "1 day");
218         return duration;
219     }
220
221     //-----------------------------------------------------------------------
222
/**
223      * <p>Get the time gap as a string.</p>
224      *
225      * <p>The format used is the ISO8601 period format.</p>
226      *
227      * @param startMillis the start of the duration to format
228      * @param endMillis the end of the duration to format
229      * @return the time as a String
230      */

231     public static String JavaDoc formatPeriodISO(long startMillis, long endMillis) {
232         return formatPeriod(startMillis, endMillis, ISO_EXTENDED_FORMAT_PATTERN, false, TimeZone.getDefault());
233     }
234
235     /**
236      * <p>Get the time gap as a string, using the specified format.
237      * Padding the left hand side of numbers with zeroes is optional.
238      *
239      * @param startMillis the start of the duration
240      * @param endMillis the end of the duration
241      * @param format the way in which to format the duration
242      * @return the time as a String
243      */

244     public static String JavaDoc formatPeriod(long startMillis, long endMillis, String JavaDoc format) {
245         return formatPeriod(startMillis, endMillis, format, true, TimeZone.getDefault());
246     }
247
248     /**
249      * <p>Get the time gap as a string, using the specified format.
250      * Padding the left hand side of numbers with zeroes is optional and
251      * the timezone may be specified.
252      *
253      * @param startMillis the start of the duration
254      * @param endMillis the end of the duration
255      * @param format the way in which to format the duration
256      * @param padWithZeros whether to pad the left hand side of numbers with 0's
257      * @param timezone the millis are defined in
258      * @return the time as a String
259      */

260     public static String JavaDoc formatPeriod(long startMillis, long endMillis, String JavaDoc format, boolean padWithZeros,
261             TimeZone JavaDoc timezone) {
262
263         long millis = endMillis - startMillis;
264         if (millis < 28 * DateUtils.MILLIS_PER_DAY) {
265             return formatDuration(millis, format, padWithZeros);
266         }
267
268         Token[] tokens = lexx(format);
269
270         // timezones get funky around 0, so normalizing everything to GMT
271
// stops the hours being off
272
Calendar JavaDoc start = Calendar.getInstance(timezone);
273         start.setTime(new Date JavaDoc(startMillis));
274         Calendar JavaDoc end = Calendar.getInstance(timezone);
275         end.setTime(new Date JavaDoc(endMillis));
276
277         // initial estimates
278
int years = end.get(Calendar.YEAR) - start.get(Calendar.YEAR);
279         int months = end.get(Calendar.MONTH) - start.get(Calendar.MONTH);
280         // each initial estimate is adjusted in case it is under 0
281
while (months < 0) {
282             months += 12;
283             years -= 1;
284         }
285         int days = end.get(Calendar.DAY_OF_MONTH) - start.get(Calendar.DAY_OF_MONTH);
286         while (days < 0) {
287             days += 31; // such overshooting is taken care of later on
288
months -= 1;
289         }
290         int hours = end.get(Calendar.HOUR_OF_DAY) - start.get(Calendar.HOUR_OF_DAY);
291         while (hours < 0) {
292             hours += 24;
293             days -= 1;
294         }
295         int minutes = end.get(Calendar.MINUTE) - start.get(Calendar.MINUTE);
296         while (minutes < 0) {
297             minutes += 60;
298             hours -= 1;
299         }
300         int seconds = end.get(Calendar.SECOND) - start.get(Calendar.SECOND);
301         while (seconds < 0) {
302             seconds += 60;
303             minutes -= 1;
304         }
305         int milliseconds = end.get(Calendar.MILLISECOND) - start.get(Calendar.MILLISECOND);
306         while (milliseconds < 0) {
307             milliseconds += 1000;
308             seconds -= 1;
309         }
310
311         // take estimates off of end to see if we can equal start, when it overshoots recalculate
312
milliseconds -= reduceAndCorrect(start, end, Calendar.MILLISECOND, milliseconds);
313         seconds -= reduceAndCorrect(start, end, Calendar.SECOND, seconds);
314         minutes -= reduceAndCorrect(start, end, Calendar.MINUTE, minutes);
315         hours -= reduceAndCorrect(start, end, Calendar.HOUR_OF_DAY, hours);
316         days -= reduceAndCorrect(start, end, Calendar.DAY_OF_MONTH, days);
317         months -= reduceAndCorrect(start, end, Calendar.MONTH, months);
318         years -= reduceAndCorrect(start, end, Calendar.YEAR, years);
319
320         // This next block of code adds in values that
321
// aren't requested. This allows the user to ask for the
322
// number of months and get the real count and not just 0->11.
323
if (!Token.containsTokenWithValue(tokens, y)) {
324             if (Token.containsTokenWithValue(tokens, M)) {
325                 months += 12 * years;
326                 years = 0;
327             } else {
328                 // TODO: this is a bit weak, needs work to know about leap years
329
days += 365 * years;
330                 years = 0;
331             }
332         }
333         if (!Token.containsTokenWithValue(tokens, M)) {
334             days += end.get(Calendar.DAY_OF_YEAR) - start.get(Calendar.DAY_OF_YEAR);
335             months = 0;
336         }
337         if (!Token.containsTokenWithValue(tokens, d)) {
338             hours += 24 * days;
339             days = 0;
340         }
341         if (!Token.containsTokenWithValue(tokens, H)) {
342             minutes += 60 * hours;
343             hours = 0;
344         }
345         if (!Token.containsTokenWithValue(tokens, m)) {
346             seconds += 60 * minutes;
347             minutes = 0;
348         }
349         if (!Token.containsTokenWithValue(tokens, s)) {
350             milliseconds += 1000 * seconds;
351             seconds = 0;
352         }
353
354         return format(tokens, years, months, days, hours, minutes, seconds, milliseconds, padWithZeros);
355     }
356
357     //-----------------------------------------------------------------------
358
/**
359      * <p>The internal method to do the formatting.</p>
360      *
361      * @param tokens the tokens
362      * @param years the number of years
363      * @param months the number of months
364      * @param days the number of days
365      * @param hours the number of hours
366      * @param minutes the number of minutes
367      * @param seconds the number of seconds
368      * @param milliseconds the number of millis
369      * @param padWithZeros whether to pad
370      * @return the formetted string
371      */

372     static String JavaDoc format(Token[] tokens, int years, int months, int days, int hours, int minutes, int seconds,
373             int milliseconds, boolean padWithZeros) {
374         StringBuffer JavaDoc buffer = new StringBuffer JavaDoc();
375         boolean lastOutputSeconds = false;
376         int sz = tokens.length;
377         for (int i = 0; i < sz; i++) {
378             Token token = tokens[i];
379             Object JavaDoc value = token.getValue();
380             int count = token.getCount();
381             if (value instanceof StringBuffer JavaDoc) {
382                 buffer.append(value.toString());
383             } else {
384                 if (value == y) {
385                     buffer.append(padWithZeros ? StringUtils.leftPad(Integer.toString(years), count, '0') : Integer
386                             .toString(years));
387                     lastOutputSeconds = false;
388                 } else if (value == M) {
389                     buffer.append(padWithZeros ? StringUtils.leftPad(Integer.toString(months), count, '0') : Integer
390                             .toString(months));
391                     lastOutputSeconds = false;
392                 } else if (value == d) {
393                     buffer.append(padWithZeros ? StringUtils.leftPad(Integer.toString(days), count, '0') : Integer
394                             .toString(days));
395                     lastOutputSeconds = false;
396                 } else if (value == H) {
397                     buffer.append(padWithZeros ? StringUtils.leftPad(Integer.toString(hours), count, '0') : Integer
398                             .toString(hours));
399                     lastOutputSeconds = false;
400                 } else if (value == m) {
401                     buffer.append(padWithZeros ? StringUtils.leftPad(Integer.toString(minutes), count, '0') : Integer
402                             .toString(minutes));
403                     lastOutputSeconds = false;
404                 } else if (value == s) {
405                     buffer.append(padWithZeros ? StringUtils.leftPad(Integer.toString(seconds), count, '0') : Integer
406                             .toString(seconds));
407                     lastOutputSeconds = true;
408                 } else if (value == S) {
409                     if (lastOutputSeconds) {
410                         milliseconds += 1000;
411                         String JavaDoc str = padWithZeros
412                                 ? StringUtils.leftPad(Integer.toString(milliseconds), count, '0')
413                                 : Integer.toString(milliseconds);
414                         buffer.append(str.substring(1));
415                     } else {
416                         buffer.append(padWithZeros
417                                 ? StringUtils.leftPad(Integer.toString(milliseconds), count, '0')
418                                 : Integer.toString(milliseconds));
419                     }
420                     lastOutputSeconds = false;
421                 }
422             }
423         }
424         return buffer.toString();
425     }
426
427     /**
428      * Reduces by difference, then if it overshot, calculates the overshot amount and
429      * fixes and returns the amount to change by.
430      *
431      * @param start Start of period being formatted
432      * @param end End of period being formatted
433      * @param field Field to reduce, as per constants in {@link java.util.Calendar}
434      * @param difference amount to reduce by
435      * @return int reduced value
436      */

437     static int reduceAndCorrect(Calendar JavaDoc start, Calendar JavaDoc end, int field, int difference) {
438         end.add( field, -1 * difference );
439         int endValue = end.get(field);
440         int startValue = start.get(field);
441         if (endValue < startValue) {
442             int newdiff = startValue - endValue;
443             end.add( field, newdiff );
444             return newdiff;
445         } else {
446             return 0;
447         }
448     }
449
450     static final Object JavaDoc y = "y";
451     static final Object JavaDoc M = "M";
452     static final Object JavaDoc d = "d";
453     static final Object JavaDoc H = "H";
454     static final Object JavaDoc m = "m";
455     static final Object JavaDoc s = "s";
456     static final Object JavaDoc S = "S";
457     
458     /**
459      * Parse a classic date format string into Tokens
460      *
461      * @param format to parse
462      * @return Token[] of tokens
463      */

464     static Token[] lexx(String JavaDoc format) {
465         char[] array = format.toCharArray();
466         java.util.ArrayList JavaDoc list = new java.util.ArrayList JavaDoc(array.length);
467
468         boolean inLiteral = false;
469         StringBuffer JavaDoc buffer = null;
470         Token previous = null;
471         int sz = array.length;
472         for(int i=0; i<sz; i++) {
473             char ch = array[i];
474             if(inLiteral && ch != '\'') {
475                 buffer.append(ch);
476                 continue;
477             }
478             Object JavaDoc value = null;
479             switch(ch) {
480                 // TODO: Need to handle escaping of '
481
case '\'' :
482                   if(inLiteral) {
483                       buffer = null;
484                       inLiteral = false;
485                   } else {
486                       buffer = new StringBuffer JavaDoc();
487                       list.add(new Token(buffer));
488                       inLiteral = true;
489                   }
490                   break;
491                 case 'y' : value = y; break;
492                 case 'M' : value = M; break;
493                 case 'd' : value = d; break;
494                 case 'H' : value = H; break;
495                 case 'm' : value = m; break;
496                 case 's' : value = s; break;
497                 case 'S' : value = S; break;
498                 default :
499                   if(buffer == null) {
500                       buffer = new StringBuffer JavaDoc();
501                       list.add(new Token(buffer));
502                   }
503                   buffer.append(ch);
504             }
505
506             if(value != null) {
507                 if(previous != null && previous.getValue() == value) {
508                     previous.increment();
509                 } else {
510                     Token token = new Token(value);
511                     list.add(token);
512                     previous = token;
513                 }
514                 buffer = null;
515             }
516         }
517         return (Token[]) list.toArray( new Token[0] );
518     }
519
520     /**
521      * Element that is parsed from the format pattern.
522      */

523     static class Token {
524
525         /**
526          * Helper method to determine if a set of tokens contain a value
527          *
528          * @param tokens set to look in
529          * @param value to look for
530          * @return boolean <code>true</code> if contained
531          */

532         static boolean containsTokenWithValue(Token[] tokens, Object JavaDoc value) {
533             int sz = tokens.length;
534             for (int i = 0; i < sz; i++) {
535                 if (tokens[i].getValue() == value) {
536                     return true;
537                 }
538             }
539             return false;
540         }
541
542         private Object JavaDoc value;
543         private int count;
544
545         /**
546          * Wrap a token around a value. A value would be something like a 'Y'.
547          *
548          * @param value to wrap
549          */

550         Token(Object JavaDoc value) {
551             this.value = value;
552             this.count = 1;
553         }
554
555         /**
556          * Wrap a token around a repeated number of a value, for example it would
557          * store 'yyyy' as a value for y and a count of 4.
558          *
559          * @param value to wrap
560          * @param count to wrap
561          */

562         Token(Object JavaDoc value, int count) {
563             this.value = value;
564             this.count = count;
565         }
566
567         /**
568          * Add another one of the value
569          */

570         void increment() {
571             count++;
572         }
573
574         /**
575          * Get the current number of values represented
576          *
577          * @return int number of values represented
578          */

579         int getCount() {
580             return count;
581         }
582
583         /**
584          * Get the particular value this token represents.
585          *
586          * @return Object value
587          */

588         Object JavaDoc getValue() {
589             return value;
590         }
591
592         /**
593          * Supports equality of this Token to another Token.
594          *
595          * @param obj2 Object to consider equality of
596          * @return boolean <code>true</code> if equal
597          */

598         public boolean equals(Object JavaDoc obj2) {
599             if (obj2 instanceof Token) {
600                 Token tok2 = (Token) obj2;
601                 if (this.value.getClass() != tok2.value.getClass()) {
602                     return false;
603                 }
604                 if (this.count != tok2.count) {
605                     return false;
606                 }
607                 if (this.value instanceof StringBuffer JavaDoc) {
608                     return this.value.toString().equals(tok2.value.toString());
609                 } else if (this.value instanceof Number JavaDoc) {
610                     return this.value.equals(tok2.value);
611                 } else {
612                     return this.value == tok2.value;
613                 }
614             } else {
615                 return false;
616             }
617         }
618
619         /**
620          * Represent this token as a String.
621          *
622          * @return String representation of the token
623          */

624         public String JavaDoc toString() {
625             return StringUtils.repeat(this.value.toString(), this.count);
626         }
627     }
628
629 }
630
Popular Tags