/* * NOTE: This copyright does *not* cover user programs that use HQ * program services by normal system calls through the application * program interfaces provided as part of the Hyperic Plug-in Development * Kit or the Hyperic Client Development Kit - this is merely considered * normal use of the program, and does *not* fall under the heading of * "derived work". * * Copyright (C) [2004-2008], Hyperic, Inc. * This file is part of HQ. * * HQ is free software; you can redistribute it and/or modify * it under the terms version 2 of the GNU General Public License as * published by the Free Software Foundation. This program is distributed * in the hope that it will be useful, but WITHOUT ANY WARRANTY; without * even the implied warranty of MERCHANTABILITY or FITNESS FOR A * PARTICULAR PURPOSE. See the GNU General Public License for more * details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 * USA. */ package org.hyperic.util.units; import java.math.BigDecimal; import java.text.NumberFormat; import java.text.ParseException; import java.util.Locale; import org.hyperic.util.ArrayUtil; import org.hyperic.util.StringUtil; /** * Format a value into a duration. */ public class DurationFormatter implements Formatter { protected static final int GRANULAR_YEARS = 1; // Time > 1 year protected static final int GRANULAR_DAYS = 2; // 1 day < time < 1 year protected static final int GRANULAR_HOURS = 3; // 1 hour < time < 1 day protected static final int GRANULAR_MINS = 4; // 1 min < time < 1 hour protected static final int GRANULAR_SECS = 5; // 1 sec < time < 1 min protected static final int GRANULAR_MILLIS = 6; // 0 < time < 1 sec protected static final int MILLISEC_DIGITS = 3; // Standard 3 digits to the // right of the decimal point class TimeBreakDown { long nYears, nDays, nHours, nMins, nSecs, nMilli; } public FormattedNumber format(UnitNumber val, Locale locale, FormatSpecifics specifics) { int granularity; granularity = getGranularity(val); return format(val.getBaseValue(), granularity, MILLISEC_DIGITS, locale); } public FormattedNumber[] formatSame(double[] val, int unitType, int scale, Locale locale, FormatSpecifics specifics) { FormattedNumber[] res; double[] secs; UnitNumber tmpNum; int maxIdx, granularity, milliDigits; TimeBreakDown tbd; boolean wholeNum; res = new FormattedNumber[val.length]; if ((maxIdx = ArrayUtil.max(val)) == -1) { return res; } tmpNum = new UnitNumber(val[maxIdx], unitType, scale); granularity = getGranularity(tmpNum); // Determine the number scale (right of the decimal point) that is // needed to ensure that every formatted number is unique and a // linear scale wholeNum = true; secs = new double[val.length]; for(int i = 0;i < secs.length;i++) { tbd = breakDownTime( UnitsFormat.getBaseValue(val[i], unitType, scale)); secs[i] = tbd.nSecs + ((double)tbd.nMilli / 1000); if(tbd.nMilli > 0) wholeNum = false; } milliDigits = UnitsUtil.getUniqueDigits(secs, locale); if(milliDigits == 0 && wholeNum == false) milliDigits = 1; // Format the numbers for(int i=0; i<val.length; i++){ tmpNum = new UnitNumber(val[i], unitType, scale); res[i] = format(tmpNum.getBaseValue(), granularity, milliDigits, locale); } return res; } protected FormattedNumber format(BigDecimal baseTime, int granularity, int milliDigits, Locale locale) { TimeBreakDown tbd; String res; // We work in a few different time ranges depending on the // magnitude of the duration. Quite hardcoded if(granularity == GRANULAR_YEARS){ tbd = breakDownTime(baseTime); res = tbd.nYears + "y " + tbd.nDays + "d"; } else if (granularity == GRANULAR_DAYS) { tbd = breakDownTime(baseTime); long nDays = tbd.nYears * 365 + tbd.nDays; res = nDays + (nDays == 1 ? " day " : " days ") + StringUtil.formatDuration(tbd.nHours * 60 * 60 * 1000 + tbd.nMins * 60 * 1000 + tbd.nSecs * 1000 + tbd.nMilli); } else if(granularity == GRANULAR_HOURS || granularity == GRANULAR_MINS || granularity == GRANULAR_SECS) { long nMillis = baseTime.divide(UnitsUtil.FACT_MILLIS, BigDecimal.ROUND_HALF_EVEN).longValue(); res = StringUtil.formatDuration(nMillis, milliDigits, granularity == GRANULAR_SECS); if(granularity == GRANULAR_SECS) res = res + 's'; } else if (granularity == GRANULAR_MILLIS) { // Format into milliseconds double dMillis = baseTime.doubleValue() / 1000000; NumberFormat fmt = NumberFormat.getInstance(); fmt.setMinimumIntegerDigits(1); fmt.setMaximumIntegerDigits(3); fmt.setMinimumFractionDigits(0); fmt.setMaximumFractionDigits(milliDigits); res = fmt.format(dMillis) + "ms"; } else { throw new IllegalStateException("Unexpected granularity"); } return new FormattedNumber(res.trim(), ""); } protected TimeBreakDown breakDownTime(BigDecimal val){ TimeBreakDown r = new TimeBreakDown(); r.nYears = val.divide(UnitsUtil.FACT_YEARS, BigDecimal.ROUND_DOWN).intValue(); if(r.nYears > 0) val = val.subtract(UnitsUtil.FACT_YEARS. multiply(new BigDecimal(Long.toString(r.nYears)))); r.nDays = val.divide(UnitsUtil.FACT_DAYS, BigDecimal.ROUND_DOWN).intValue(); if(r.nDays > 0) val = val.subtract(UnitsUtil.FACT_DAYS. multiply(new BigDecimal(Long.toString(r.nDays)))); r.nHours = val.divide(UnitsUtil.FACT_HOURS, BigDecimal.ROUND_DOWN).intValue(); if(r.nHours > 0) val = val.subtract(UnitsUtil.FACT_HOURS. multiply(new BigDecimal(Long.toString(r.nHours)))); r.nMins = val.divide(UnitsUtil.FACT_MINS, BigDecimal.ROUND_DOWN).intValue(); if(r.nMins > 0) val = val.subtract(UnitsUtil.FACT_MINS. multiply(new BigDecimal(Long.toString(r.nMins)))); r.nSecs = val.divide(UnitsUtil.FACT_SECS, BigDecimal.ROUND_DOWN).intValue(); if(r.nSecs > 0) val = val.subtract(UnitsUtil.FACT_SECS. multiply(new BigDecimal(Long.toString(r.nSecs)))); r.nMilli = val.divide(UnitsUtil.FACT_MILLIS, BigDecimal.ROUND_DOWN).intValue(); return r; } private int getGranularity(UnitNumber val){ BigDecimal nanoSecs = val.getBaseValue(); if (nanoSecs.longValue() > 0) { TimeBreakDown tbd = breakDownTime(nanoSecs); if(tbd.nYears > 0) return GRANULAR_YEARS; else if(tbd.nDays > 0) return GRANULAR_DAYS; else if(tbd.nHours > 0) return GRANULAR_HOURS; else if(tbd.nMins > 0) return GRANULAR_MINS; else if (tbd.nSecs > 0) return GRANULAR_SECS; else return GRANULAR_MILLIS; } else { // Use original scale as granularity for 0 switch (val.getScale()) { case UnitsConstants.SCALE_YEAR: return GRANULAR_YEARS; case UnitsConstants.SCALE_DAY: return GRANULAR_DAYS; case UnitsConstants.SCALE_HOUR: return GRANULAR_HOURS; case UnitsConstants.SCALE_MIN: return GRANULAR_MINS; case UnitsConstants.SCALE_SEC: return GRANULAR_SECS; default: return GRANULAR_MILLIS; } } } public BigDecimal getBaseValue(double value, int scale){ return DateFormatter.getBaseTime(value, scale); } public BigDecimal getScaledValue(BigDecimal value, int targScale){ return DateFormatter.getScaledTime(value, targScale); } /** * Returns the # of seconds in a string in the form of "xx.xs" or * "xx:yy:zz.a" */ protected double parseTimeStr(String duration) throws ParseException { double nHours, nMins, nSecs; String[] vals = (String[])StringUtil.explode(duration, ":").toArray(new String[0]); if(vals.length != 3){ throw new ParseException(duration, 0); } try { nHours = Double.parseDouble(vals[0]); nMins = Double.parseDouble(vals[1]); nSecs = Double.parseDouble(vals[2]); return nHours * 60 * 60 + nMins * 60 + nSecs; } catch(NumberFormatException exc){ throw new ParseException(duration, 0); } } /** * Parse a string which is of the same format that we output, * when outputting durations. */ private UnitNumber parseRegular(String val, Locale locale, ParseSpecifics specifics) throws ParseException { String[] vals = (String[]) StringUtil.explode(val, " ").toArray(new String[0]); double value; int scale = UnitsConstants.SCALE_SEC; try { if (vals.length == 2 && vals[0].charAt(vals[0].length() - 1) == 'y' && vals[1].charAt(vals[1].length() - 1) == 'd') { String yStr = vals[0].substring(0, vals[0].length() - 1); String dStr = vals[1].substring(0, vals[1].length() - 1); value = Integer.parseInt(yStr) * 365 + Integer.parseInt(dStr); scale = UnitsConstants.SCALE_DAY; } else if (vals.length == 3 && (vals[1].equals("day") || vals[1].equals("days"))) { value = Integer.parseInt(vals[0]) * 24 * 60 * 60+ parseTimeStr(vals[2]); } else if (vals.length == 1) { value = parseTimeStr(vals[0]); } else { // String not recognized throw new ParseException(val, 0); } } catch(NumberFormatException exc){ throw new ParseException(val, 0); } return new UnitNumber(value, UnitsConstants.UNIT_DURATION, scale); } public UnitNumber parse(String val, Locale locale, ParseSpecifics specifics) throws ParseException { NumberFormat fmt = NumberFormat.getInstance(locale); double numberPart; String tagPart; int nonIdx, scale; try { return parseRegular(val, locale, specifics); } catch(ParseException exc){ // That's fine, try another format } nonIdx = UnitsUtil.findNonNumberIdx(val, fmt); if(nonIdx == -1){ throw new ParseException("Number had no units with it", val.length()); } if(nonIdx == 0){ throw new ParseException("Invalid number specified", 0); } numberPart = fmt.parse(val.substring(0, nonIdx)).doubleValue(); tagPart = val.substring(nonIdx, val.length()).trim(); if(tagPart.equalsIgnoreCase("y") || tagPart.equalsIgnoreCase("yr") || tagPart.equalsIgnoreCase("yrs") || tagPart.equalsIgnoreCase("year") || tagPart.equalsIgnoreCase("years")) { scale = UnitsConstants.SCALE_YEAR; } else if(tagPart.equalsIgnoreCase("w") || tagPart.equalsIgnoreCase("wk") || tagPart.equalsIgnoreCase("wks") || tagPart.equalsIgnoreCase("week") || tagPart.equalsIgnoreCase("weeks")) { scale = UnitsConstants.SCALE_WEEK; } else if(tagPart.equalsIgnoreCase("d") || tagPart.equalsIgnoreCase("day") || tagPart.equalsIgnoreCase("days")) { scale = UnitsConstants.SCALE_DAY; } else if(tagPart.equalsIgnoreCase("h") || tagPart.equalsIgnoreCase("hr") || tagPart.equalsIgnoreCase("hrs") || tagPart.equalsIgnoreCase("hour") || tagPart.equalsIgnoreCase("hours")) { scale = UnitsConstants.SCALE_HOUR; } else if(tagPart.equalsIgnoreCase("m") || tagPart.equalsIgnoreCase("min") || tagPart.equalsIgnoreCase("mins") || tagPart.equalsIgnoreCase("minute") || tagPart.equalsIgnoreCase("minutes")) { scale = UnitsConstants.SCALE_MIN; } else if(tagPart.equalsIgnoreCase("s") || tagPart.equalsIgnoreCase("sec") || tagPart.equalsIgnoreCase("secs") || tagPart.equalsIgnoreCase("second") || tagPart.equalsIgnoreCase("seconds")) { scale = UnitsConstants.SCALE_SEC; } else if(tagPart.equalsIgnoreCase("j") || tagPart.equalsIgnoreCase("jif") || tagPart.equalsIgnoreCase("jifs") || tagPart.equalsIgnoreCase("jiffy") || tagPart.equalsIgnoreCase("jiffys") || tagPart.equalsIgnoreCase("jifferoonies")) { scale = UnitsConstants.SCALE_JIFFY; } else if(tagPart.equalsIgnoreCase("ms") || tagPart.equalsIgnoreCase("milli") || tagPart.equalsIgnoreCase("millis") || tagPart.equalsIgnoreCase("millisecond") || tagPart.equalsIgnoreCase("milliseconds")) { scale = UnitsConstants.SCALE_MILLI; } else if(tagPart.equalsIgnoreCase("us") || tagPart.equalsIgnoreCase("micro") || tagPart.equalsIgnoreCase("micros") || tagPart.equalsIgnoreCase("microsecond") || tagPart.equalsIgnoreCase("microseconds")) { scale = UnitsConstants.SCALE_MICRO; } else if(tagPart.equalsIgnoreCase("ns") || tagPart.equalsIgnoreCase("nano") || tagPart.equalsIgnoreCase("nanos") || tagPart.equalsIgnoreCase("nanosecond") || tagPart.equalsIgnoreCase("nanoseconds")) { scale = UnitsConstants.SCALE_NANO; } else { throw new ParseException("Unknown duration '" + tagPart + "'", nonIdx); } return new UnitNumber(numberPart, UnitsConstants.UNIT_DURATION, scale); } }