/** * Formats the time gap as a string, using the specified format. Padding the left hand side of * numbers with zeroes is optional. * * <p>This method formats durations using the days and lower fields of the format pattern. Months * and larger are not used. * * @param durationMillis the duration to format * @param format the way in which to format the duration, not null * @param padWithZeros whether to pad the left hand side of numbers with 0's * @return the formatted duration, not null * @throws java.lang.IllegalArgumentException if durationMillis is negative */ public static String formatDuration( final long durationMillis, final String format, final boolean padWithZeros) { Validate.inclusiveBetween( 0, Long.MAX_VALUE, durationMillis, "durationMillis must not be negative"); final Token[] tokens = lexx(format); long days = 0; long hours = 0; long minutes = 0; long seconds = 0; long milliseconds = durationMillis; if (Token.containsTokenWithValue(tokens, d)) { days = milliseconds / DateUtils.MILLIS_PER_DAY; milliseconds = milliseconds - (days * DateUtils.MILLIS_PER_DAY); } if (Token.containsTokenWithValue(tokens, H)) { hours = milliseconds / DateUtils.MILLIS_PER_HOUR; milliseconds = milliseconds - (hours * DateUtils.MILLIS_PER_HOUR); } if (Token.containsTokenWithValue(tokens, m)) { minutes = milliseconds / DateUtils.MILLIS_PER_MINUTE; milliseconds = milliseconds - (minutes * DateUtils.MILLIS_PER_MINUTE); } if (Token.containsTokenWithValue(tokens, s)) { seconds = milliseconds / DateUtils.MILLIS_PER_SECOND; milliseconds = milliseconds - (seconds * DateUtils.MILLIS_PER_SECOND); } return format(tokens, 0, 0, days, hours, minutes, seconds, milliseconds, padWithZeros); }
/** * Formats the time gap as a string, using the specified format. Padding the left hand side of * numbers with zeroes is optional and the timezone may be specified. * * <p>This method formats durations using the days and lower fields of the format pattern. Months * and larger are not used. * * @param durationMillis the duration to format * @param format the way in which to format the duration * @param padWithZeros whether to pad the left hand side of numbers with 0's * @return the time as a String */ public static String formatDuration(long durationMillis, String format, boolean padWithZeros) { Token[] tokens = lexx(format); int days = 0; int hours = 0; int minutes = 0; int seconds = 0; int milliseconds = 0; if (Token.containsTokenWithValue(tokens, d)) { days = (int) (durationMillis / DateUtils.MILLIS_PER_DAY); durationMillis = durationMillis - (days * DateUtils.MILLIS_PER_DAY); } if (Token.containsTokenWithValue(tokens, H)) { hours = (int) (durationMillis / DateUtils.MILLIS_PER_HOUR); durationMillis = durationMillis - (hours * DateUtils.MILLIS_PER_HOUR); } if (Token.containsTokenWithValue(tokens, m)) { minutes = (int) (durationMillis / DateUtils.MILLIS_PER_MINUTE); durationMillis = durationMillis - (minutes * DateUtils.MILLIS_PER_MINUTE); } if (Token.containsTokenWithValue(tokens, s)) { seconds = (int) (durationMillis / DateUtils.MILLIS_PER_SECOND); durationMillis = durationMillis - (seconds * DateUtils.MILLIS_PER_SECOND); } if (Token.containsTokenWithValue(tokens, S)) { milliseconds = (int) durationMillis; } return format(tokens, 0, 0, days, hours, minutes, seconds, milliseconds, padWithZeros); }
/** * Formats the time gap as a string, using the specified format. Padding the left hand side of * numbers with zeroes is optional and the timezone may be specified. * * <p>When calculating the difference between months/days, it chooses to calculate months first. * So when working out the number of months and days between January 15th and March 10th, it * choose 1 month and 23 days gained by choosing January->February = 1 month and then * calculating days forwards, and not the 1 month and 26 days gained by choosing March -> * February = 1 month and then calculating days backwards. * * <p>For more control, the <a href="http://joda-time.sf.net/">Joda-Time</a> library is * recommended. * * @param startMillis the start of the duration * @param endMillis the end of the duration * @param format the way in which to format the duration, not null * @param padWithZeros whether to pad the left hand side of numbers with 0's * @param timezone the millis are defined in * @return the formatted duration, not null * @throws java.lang.IllegalArgumentException if startMillis is greater than endMillis */ public static String formatPeriod( final long startMillis, final long endMillis, final String format, final boolean padWithZeros, final TimeZone timezone) { Validate.isTrue(startMillis <= endMillis, "startMillis must not be greater than endMillis"); // Used to optimise for differences under 28 days and // called formatDuration(millis, format); however this did not work // over leap years. // TODO: Compare performance to see if anything was lost by // losing this optimisation. final Token[] tokens = lexx(format); // timezones get funky around 0, so normalizing everything to GMT // stops the hours being off final Calendar start = Calendar.getInstance(timezone); start.setTime(new Date(startMillis)); final Calendar end = Calendar.getInstance(timezone); end.setTime(new Date(endMillis)); // initial estimates int milliseconds = end.get(Calendar.MILLISECOND) - start.get(Calendar.MILLISECOND); int seconds = end.get(Calendar.SECOND) - start.get(Calendar.SECOND); int minutes = end.get(Calendar.MINUTE) - start.get(Calendar.MINUTE); int hours = end.get(Calendar.HOUR_OF_DAY) - start.get(Calendar.HOUR_OF_DAY); int days = end.get(Calendar.DAY_OF_MONTH) - start.get(Calendar.DAY_OF_MONTH); int months = end.get(Calendar.MONTH) - start.get(Calendar.MONTH); int years = end.get(Calendar.YEAR) - start.get(Calendar.YEAR); // each initial estimate is adjusted in case it is under 0 while (milliseconds < 0) { milliseconds += 1000; seconds -= 1; } while (seconds < 0) { seconds += 60; minutes -= 1; } while (minutes < 0) { minutes += 60; hours -= 1; } while (hours < 0) { hours += 24; days -= 1; } if (Token.containsTokenWithValue(tokens, M)) { while (days < 0) { days += start.getActualMaximum(Calendar.DAY_OF_MONTH); months -= 1; start.add(Calendar.MONTH, 1); } while (months < 0) { months += 12; years -= 1; } if (!Token.containsTokenWithValue(tokens, y) && years != 0) { while (years != 0) { months += 12 * years; years = 0; } } } else { // there are no M's in the format string if (!Token.containsTokenWithValue(tokens, y)) { int target = end.get(Calendar.YEAR); if (months < 0) { // target is end-year -1 target -= 1; } while (start.get(Calendar.YEAR) != target) { days += start.getActualMaximum(Calendar.DAY_OF_YEAR) - start.get(Calendar.DAY_OF_YEAR); // Not sure I grok why this is needed, but the brutal tests show it is if (start instanceof GregorianCalendar && start.get(Calendar.MONTH) == Calendar.FEBRUARY && start.get(Calendar.DAY_OF_MONTH) == 29) { days += 1; } start.add(Calendar.YEAR, 1); days += start.get(Calendar.DAY_OF_YEAR); } years = 0; } while (start.get(Calendar.MONTH) != end.get(Calendar.MONTH)) { days += start.getActualMaximum(Calendar.DAY_OF_MONTH); start.add(Calendar.MONTH, 1); } months = 0; while (days < 0) { days += start.getActualMaximum(Calendar.DAY_OF_MONTH); months -= 1; start.add(Calendar.MONTH, 1); } } // The rest of this code adds in values that // aren't requested. This allows the user to ask for the // number of months and get the real count and not just 0->11. if (!Token.containsTokenWithValue(tokens, d)) { hours += 24 * days; days = 0; } if (!Token.containsTokenWithValue(tokens, H)) { minutes += 60 * hours; hours = 0; } if (!Token.containsTokenWithValue(tokens, m)) { seconds += 60 * minutes; minutes = 0; } if (!Token.containsTokenWithValue(tokens, s)) { milliseconds += 1000 * seconds; seconds = 0; } return format(tokens, years, months, days, hours, minutes, seconds, milliseconds, padWithZeros); }