-/*******************************************************************************\r
- * Copyright (c) 2007, 2011 Association for Decentralized Information Management in\r
- * Industry THTH ry.\r
- * All rights reserved. This program and the accompanying materials\r
- * are made available under the terms of the Eclipse Public License v1.0\r
- * which accompanies this distribution, and is available at\r
- * http://www.eclipse.org/legal/epl-v10.html\r
- *\r
- * Contributors:\r
- * VTT Technical Research Centre of Finland - initial API and implementation\r
- *******************************************************************************/\r
-package org.simantics.utils.format;\r
-\r
-import java.math.BigDecimal;\r
-import java.math.MathContext;\r
-import java.math.RoundingMode;\r
-import java.text.FieldPosition;\r
-import java.text.Format;\r
-import java.text.ParseException;\r
-import java.text.ParsePosition;\r
-import java.util.regex.Matcher;\r
-import java.util.regex.Pattern;\r
-\r
-/**\r
- * Time format consists of four parts [Year Part] [Day part] [Time part] [Decimal part]\r
- * <p>\r
- * Year part. \r
- * "[y]y "\r
- * "[yy]y ", and so on.\r
- * <p>\r
- * Day part. \r
- * "[d]d "\r
- * "[dd]d ", and so on.\r
- * \r
- * <p>\r
- * Time part five formats:\r
- * "HH:mm:ss"\r
- * "H:mm:ss"\r
- * "mm:ss"\r
- * "ss"\r
- * "s"\r
- * <p>\r
- * When time values are formatted into Strings, hours will be\r
- * formatted with at most two digits and the the rest is converted\r
- * into days and years. However, while parsing TimeFormat-Strings\r
- * into time values (double seconds), the hour part (H) can consist\r
- * of one or more digits. This is simply for parsing convenience.\r
- * \r
- * <p>\r
- * Decimal part has 1->* decimals. It is optional. It cannot exist without time part. \r
- * ".d"\r
- * ".dd"\r
- * ".ddd", and so on.\r
- * \r
- * @author Toni Kalajainen\r
- */\r
-public class TimeFormat extends Format {\r
-\r
- private static final long serialVersionUID = 1L;\r
- \r
- public static final Pattern PATTERN = \r
- Pattern.compile(\r
- "(-)?" + // Minus (-)\r
- "(?:(\\d+)y *)?" + // Year part "[y]y"\r
- "(?:(\\d+)d *)?" + // Day part "[d]d"\r
- "(?:(?:(\\d{1,}):)??(?:(\\d{1,2}):)?(\\d{1,2}))?" + // Time part "[H*]H:mm:ss"\r
- "(?:\\.(\\d+))?" // Decimal part ".ddd"\r
- );\r
-\r
- private static final BigDecimal TWO = BigDecimal.valueOf(2L);\r
-\r
- double maxValue;\r
- int decimals;\r
- RoundingMode rounding = RoundingMode.HALF_UP;\r
- MathContext decimalRoundingContext;\r
- \r
- public TimeFormat(double maxValue, int decimals)\r
- {\r
- this.maxValue = maxValue;\r
- this.decimals = decimals;\r
- this.decimalRoundingContext = new MathContext(Math.max(1, decimals+1), rounding);\r
- }\r
-\r
- public void setMaxValue(double maxValue) {\r
- this.maxValue = maxValue;\r
- }\r
- \r
- public void setDecimals(int decimals) {\r
- this.decimals = decimals;\r
- this.decimalRoundingContext = new MathContext(Math.max(1, decimals+1), rounding);\r
- }\r
- \r
- public void setRounding(RoundingMode rounding) {\r
- this.rounding = rounding;\r
- this.decimalRoundingContext = new MathContext(Math.max(1, decimals+1), rounding);\r
- }\r
-\r
- @Override\r
- public StringBuffer format(Object obj, StringBuffer toAppendTo, FieldPosition pos) {\r
- // Prevent recurrent locking when invoking toAppendTo-methods.\r
- synchronized (toAppendTo) {\r
- return formatSync(obj, toAppendTo, pos);\r
- }\r
- }\r
-\r
- private StringBuffer formatSync(Object obj, StringBuffer toAppendTo, FieldPosition pos) {\r
- double x = ( (Number) obj ).doubleValue(); \r
- int initLen = toAppendTo.length();\r
-\r
- if (Double.isInfinite(x)) {\r
- return toAppendTo.append((x == Float.POSITIVE_INFINITY) ? "\u221E" : "-\u221E");\r
- } else if (Double.isNaN(x)) {\r
- return toAppendTo.append("NaN");\r
- }\r
-\r
- if (x<0) {\r
- toAppendTo.append("-");\r
- x=-x;\r
- initLen = toAppendTo.length();\r
- }\r
-\r
- // The value of x-floor(x) is between [0,1].\r
- // We want use BigDecimal to round to the specified number of decimals.\r
- // The problem is that if x is 0.99999... so that it will be rounded to 1.000...\r
- // the 1 at the front will count as a decimal in the rounding logic\r
- // and we end up losing 1 actual decimal.\r
- // Therefore we add 1.0 to x make it be between [1,2] in which case\r
- // we can just round to n+1 decimals and it will always work.\r
- BigDecimal decimalPart = new BigDecimal(x - Math.floor(x) + 1.0);\r
- decimalPart = decimalPart.round(decimalRoundingContext);\r
- // decimal is now [1.000..,2.000...].\r
- // If decimalPart equals 2.0 it means that the\r
- // requested decimal part value was close enough\r
- // to 1.0 that it overflows and becomes 000...\r
- // This means that the overflow must be added to/subtracted from\r
- // the integer part of the input number.\r
- boolean needToRound = TWO.compareTo(decimalPart) == 0;\r
-\r
- double max = Math.max(this.maxValue, x);\r
- long xl = needToRound ? (long) Math.round( x ) : (long) Math.floor( x );\r
-\r
- // Write years\r
- if (xl>=(24L*60L*60L*365L)) {\r
- long years = xl / (24L*60L*60L*365L);\r
- toAppendTo.append( (long) years );\r
- toAppendTo.append("y");\r
- }\r
-\r
- // Write days\r
- if (xl>=(24L*60L*60L)) {\r
- if (toAppendTo.length()!=initLen) toAppendTo.append(' ');\r
- long days = (xl % (24L*60L*60L*365L)) / (24L*60L*60L); \r
- toAppendTo.append( (long) days );\r
- toAppendTo.append("d");\r
- }\r
-\r
- // Write HH:mm:ss\r
- if (decimals>=-5) {\r
- if (toAppendTo.length()!=initLen) toAppendTo.append(' ');\r
- // Seconds of the day\r
- long seconds = xl % 86400L;\r
- \r
- // Write HH:\r
- if (max>=24*60) {\r
- long hh = seconds / 3600;\r
- if (x>3600) {\r
- toAppendTo.append( hh/10 );\r
- }\r
- toAppendTo.append( hh%10 );\r
- toAppendTo.append(":");\r
- }\r
- \r
- // Write mm:\r
- if (max>=60) {\r
- long mm = (seconds / 60) % 60;\r
- toAppendTo.append( mm/10 );\r
- toAppendTo.append( mm%10 );\r
- toAppendTo.append(":");\r
- }\r
- \r
- // Write ss\r
- {\r
- long ss = seconds % 60;\r
- if (x>=10 || initLen!=toAppendTo.length()) {\r
- toAppendTo.append( ss/10 );\r
- }\r
- toAppendTo.append( ss%10 );\r
- }\r
- \r
- // Write milliseconds and more\r
- if (decimals>0) {\r
- // add the decimal separator and part to the result.\r
- toAppendTo.append('.');\r
- String dps = decimalPart.toString();\r
- int decimalPartLen = dps.length();\r
- int trailingZeros = decimals;\r
- if (decimalPartLen > 2) {\r
- // If the original number was exact (e.g. 1)\r
- // dp will contain only "1"\r
- toAppendTo.append(dps, 2, decimalPartLen);\r
- trailingZeros -= decimalPartLen - 2; \r
- }\r
- for (int d = 0; d < trailingZeros; ++d)\r
- toAppendTo.append('0');\r
- }\r
- }\r
- \r
- if (toAppendTo.length()==initLen) toAppendTo.append('-');\r
- \r
- return toAppendTo;\r
- }\r
-\r
- @Override\r
- public Object parseObject(String source) throws ParseException {\r
- Matcher m = PATTERN.matcher(source);\r
- if (!m.matches()) {\r
- try {\r
- return Double.parseDouble( source );\r
- } catch (NumberFormatException nfe) {\r
- throw new ParseException("TimeFormat.parseObject('" + source + "') failed", m.regionStart());\r
- }\r
- }\r
- \r
- String negG = m.group(1);\r
- String yearG = m.group(2);\r
- String dayG = m.group(3);\r
- String hourG = m.group(4);\r
- String minuteG = m.group(5);\r
- String secondG = m.group(6);\r
- String decimalG = m.group(7);\r
- boolean negative = negG==null?false:negG.equals("-");\r
- double years = yearG==null?0.:Double.parseDouble(yearG);\r
- double days = dayG==null?0.:Double.parseDouble(dayG);\r
- double hours = hourG==null?0.:Double.parseDouble(hourG);\r
- double minutes = minuteG==null?0.:Double.parseDouble(minuteG);\r
- double seconds = secondG==null?0.:Double.parseDouble(secondG);\r
- double decimals = decimalG==null?0.:Double.parseDouble(decimalG)/Math.pow(10, decimalG.length());\r
-\r
- double value = years*31536000. + days*86400. + hours*3600. + minutes*60. + seconds + decimals;\r
- if ( negative ) value = -value;\r
- return value;\r
- }\r
- \r
- @Override\r
- public Object parseObject(String source, ParsePosition pos) {\r
- try {\r
- return parseObject(source);\r
- } catch (ParseException e) {\r
- pos.setErrorIndex(e.getErrorOffset());\r
- return null;\r
- }\r
- }\r
- \r
-}\r
+/*******************************************************************************
+ * Copyright (c) 2007, 2011 Association for Decentralized Information Management in
+ * Industry THTH ry.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * VTT Technical Research Centre of Finland - initial API and implementation
+ *******************************************************************************/
+package org.simantics.utils.format;
+
+import java.math.BigDecimal;
+import java.math.MathContext;
+import java.math.RoundingMode;
+import java.text.FieldPosition;
+import java.text.Format;
+import java.text.ParseException;
+import java.text.ParsePosition;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * Time format consists of four parts [Year Part] [Day part] [Time part] [Decimal part]
+ * <p>
+ * Year part.
+ * "[y]y "
+ * "[yy]y ", and so on.
+ * <p>
+ * Day part.
+ * "[d]d "
+ * "[dd]d ", and so on.
+ *
+ * <p>
+ * Time part five formats:
+ * "HH:mm:ss"
+ * "H:mm:ss"
+ * "mm:ss"
+ * "ss"
+ * "s"
+ * <p>
+ * When time values are formatted into Strings, hours will be
+ * formatted with at most two digits and the the rest is converted
+ * into days and years. However, while parsing TimeFormat-Strings
+ * into time values (double seconds), the hour part (H) can consist
+ * of one or more digits. This is simply for parsing convenience.
+ *
+ * <p>
+ * Decimal part has 1->* decimals. It is optional. It cannot exist without time part.
+ * ".d"
+ * ".dd"
+ * ".ddd", and so on.
+ *
+ * @author Toni Kalajainen
+ */
+public class TimeFormat extends Format {
+
+ private static final long serialVersionUID = 1L;
+
+ public static final Pattern PATTERN =
+ Pattern.compile(
+ "(-)?" + // Minus (-)
+ "(?:(\\d+)y *)?" + // Year part "[y]y"
+ "(?:(\\d+)d *)?" + // Day part "[d]d"
+ "(?:(?:(\\d{1,}):)??(?:(\\d{1,2}):)?(\\d{1,2}))?" + // Time part "[H*]H:mm:ss"
+ "(?:\\.(\\d+))?" // Decimal part ".ddd"
+ );
+
+ private static final BigDecimal TWO = BigDecimal.valueOf(2L);
+
+ double maxValue;
+ int decimals;
+ RoundingMode rounding = RoundingMode.HALF_UP;
+ MathContext decimalRoundingContext;
+
+ public TimeFormat(double maxValue, int decimals)
+ {
+ this.maxValue = maxValue;
+ this.decimals = decimals;
+ this.decimalRoundingContext = new MathContext(Math.max(1, decimals+1), rounding);
+ }
+
+ public void setMaxValue(double maxValue) {
+ this.maxValue = maxValue;
+ }
+
+ public void setDecimals(int decimals) {
+ this.decimals = decimals;
+ this.decimalRoundingContext = new MathContext(Math.max(1, decimals+1), rounding);
+ }
+
+ public void setRounding(RoundingMode rounding) {
+ this.rounding = rounding;
+ this.decimalRoundingContext = new MathContext(Math.max(1, decimals+1), rounding);
+ }
+
+ @Override
+ public StringBuffer format(Object obj, StringBuffer toAppendTo, FieldPosition pos) {
+ // Prevent recurrent locking when invoking toAppendTo-methods.
+ synchronized (toAppendTo) {
+ return formatSync(obj, toAppendTo, pos);
+ }
+ }
+
+ private StringBuffer formatSync(Object obj, StringBuffer toAppendTo, FieldPosition pos) {
+ double x = ( (Number) obj ).doubleValue();
+ int initLen = toAppendTo.length();
+
+ if (Double.isInfinite(x)) {
+ return toAppendTo.append((x == Float.POSITIVE_INFINITY) ? "\u221E" : "-\u221E");
+ } else if (Double.isNaN(x)) {
+ return toAppendTo.append("NaN");
+ }
+
+ if (x<0) {
+ toAppendTo.append("-");
+ x=-x;
+ initLen = toAppendTo.length();
+ }
+
+ // The value of x-floor(x) is between [0,1].
+ // We want use BigDecimal to round to the specified number of decimals.
+ // The problem is that if x is 0.99999... so that it will be rounded to 1.000...
+ // the 1 at the front will count as a decimal in the rounding logic
+ // and we end up losing 1 actual decimal.
+ // Therefore we add 1.0 to x make it be between [1,2] in which case
+ // we can just round to n+1 decimals and it will always work.
+ BigDecimal decimalPart = new BigDecimal(x - Math.floor(x) + 1.0);
+ decimalPart = decimalPart.round(decimalRoundingContext);
+ // decimal is now [1.000..,2.000...].
+ // If decimalPart equals 2.0 it means that the
+ // requested decimal part value was close enough
+ // to 1.0 that it overflows and becomes 000...
+ // This means that the overflow must be added to/subtracted from
+ // the integer part of the input number.
+ boolean needToRound = TWO.compareTo(decimalPart) == 0;
+
+ double max = Math.max(this.maxValue, x);
+ long xl = needToRound ? (long) Math.round( x ) : (long) Math.floor( x );
+
+ // Write years
+ if (xl>=(24L*60L*60L*365L)) {
+ long years = xl / (24L*60L*60L*365L);
+ toAppendTo.append( (long) years );
+ toAppendTo.append("y");
+ }
+
+ // Write days
+ if (xl>=(24L*60L*60L)) {
+ if (toAppendTo.length()!=initLen) toAppendTo.append(' ');
+ long days = (xl % (24L*60L*60L*365L)) / (24L*60L*60L);
+ toAppendTo.append( (long) days );
+ toAppendTo.append("d");
+ }
+
+ // Write HH:mm:ss
+ if (decimals>=-5) {
+ if (toAppendTo.length()!=initLen) toAppendTo.append(' ');
+ // Seconds of the day
+ long seconds = xl % 86400L;
+
+ // Write HH:
+ if (max>=24*60) {
+ long hh = seconds / 3600;
+ if (x>3600) {
+ toAppendTo.append( hh/10 );
+ }
+ toAppendTo.append( hh%10 );
+ toAppendTo.append(":");
+ }
+
+ // Write mm:
+ if (max>=60) {
+ long mm = (seconds / 60) % 60;
+ toAppendTo.append( mm/10 );
+ toAppendTo.append( mm%10 );
+ toAppendTo.append(":");
+ }
+
+ // Write ss
+ {
+ long ss = seconds % 60;
+ if (x>=10 || initLen!=toAppendTo.length()) {
+ toAppendTo.append( ss/10 );
+ }
+ toAppendTo.append( ss%10 );
+ }
+
+ // Write milliseconds and more
+ if (decimals>0) {
+ // add the decimal separator and part to the result.
+ toAppendTo.append('.');
+ String dps = decimalPart.toString();
+ int decimalPartLen = dps.length();
+ int trailingZeros = decimals;
+ if (decimalPartLen > 2) {
+ // If the original number was exact (e.g. 1)
+ // dp will contain only "1"
+ toAppendTo.append(dps, 2, decimalPartLen);
+ trailingZeros -= decimalPartLen - 2;
+ }
+ for (int d = 0; d < trailingZeros; ++d)
+ toAppendTo.append('0');
+ }
+ }
+
+ if (toAppendTo.length()==initLen) toAppendTo.append('-');
+
+ return toAppendTo;
+ }
+
+ @Override
+ public Object parseObject(String source) throws ParseException {
+ Matcher m = PATTERN.matcher(source);
+ if (!m.matches()) {
+ try {
+ return Double.parseDouble( source );
+ } catch (NumberFormatException nfe) {
+ throw new ParseException("TimeFormat.parseObject('" + source + "') failed", m.regionStart());
+ }
+ }
+
+ String negG = m.group(1);
+ String yearG = m.group(2);
+ String dayG = m.group(3);
+ String hourG = m.group(4);
+ String minuteG = m.group(5);
+ String secondG = m.group(6);
+ String decimalG = m.group(7);
+ boolean negative = negG==null?false:negG.equals("-");
+ double years = yearG==null?0.:Double.parseDouble(yearG);
+ double days = dayG==null?0.:Double.parseDouble(dayG);
+ double hours = hourG==null?0.:Double.parseDouble(hourG);
+ double minutes = minuteG==null?0.:Double.parseDouble(minuteG);
+ double seconds = secondG==null?0.:Double.parseDouble(secondG);
+ double decimals = decimalG==null?0.:Double.parseDouble(decimalG)/Math.pow(10, decimalG.length());
+
+ double value = years*31536000. + days*86400. + hours*3600. + minutes*60. + seconds + decimals;
+ if ( negative ) value = -value;
+ return value;
+ }
+
+ @Override
+ public Object parseObject(String source, ParsePosition pos) {
+ try {
+ return parseObject(source);
+ } catch (ParseException e) {
+ pos.setErrorIndex(e.getErrorOffset());
+ return null;
+ }
+ }
+
+}