]> gerrit.simantics Code Review - simantics/platform.git/blobdiff - bundles/org.simantics.utils/src/org/simantics/utils/format/TimeFormat.java
Migrated source code from Simantics SVN
[simantics/platform.git] / bundles / org.simantics.utils / src / org / simantics / utils / format / TimeFormat.java
diff --git a/bundles/org.simantics.utils/src/org/simantics/utils/format/TimeFormat.java b/bundles/org.simantics.utils/src/org/simantics/utils/format/TimeFormat.java
new file mode 100644 (file)
index 0000000..48d9611
--- /dev/null
@@ -0,0 +1,248 @@
+/*******************************************************************************\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 (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