]> gerrit.simantics Code Review - simantics/platform.git/blob - bundles/org.simantics.utils/src/org/simantics/utils/format/TimeFormat.java
Fixed multiple issues causing dangling references to discarded queries
[simantics/platform.git] / bundles / org.simantics.utils / src / org / simantics / utils / format / TimeFormat.java
1 /*******************************************************************************
2  * Copyright (c) 2007, 2011 Association for Decentralized Information Management in
3  * Industry THTH ry.
4  * All rights reserved. This program and the accompanying materials
5  * are made available under the terms of the Eclipse Public License v1.0
6  * which accompanies this distribution, and is available at
7  * http://www.eclipse.org/legal/epl-v10.html
8  *
9  * Contributors:
10  *     VTT Technical Research Centre of Finland - initial API and implementation
11  *******************************************************************************/
12 package org.simantics.utils.format;
13
14 import java.math.BigDecimal;
15 import java.math.MathContext;
16 import java.math.RoundingMode;
17 import java.text.FieldPosition;
18 import java.text.Format;
19 import java.text.ParseException;
20 import java.text.ParsePosition;
21 import java.util.regex.Matcher;
22 import java.util.regex.Pattern;
23
24 /**
25  * Time format consists of four parts [Year Part] [Day part] [Time part] [Decimal part]
26  *  <p>
27  *  Year part. 
28  *    "[y]y "
29  *    "[yy]y ", and so on.
30  *  <p>
31  *  Day part. 
32  *    "[d]d "
33  *    "[dd]d ", and so on.
34  *   
35  *  <p>
36  *  Time part five formats:
37  *    "HH:mm:ss"
38  *    "H:mm:ss"
39  *    "mm:ss"
40  *    "ss"
41  *    "s"
42  *  <p>
43  *  When time values are formatted into Strings, hours will be
44  *  formatted with at most two digits and the the rest is converted
45  *  into days and years. However, while parsing TimeFormat-Strings
46  *  into time values (double seconds), the hour part (H) can consist
47  *  of one or more digits. This is simply for parsing convenience.
48  *  
49  *  <p>
50  *  Decimal part has 1->* decimals. It is optional. It cannot exist without time part. 
51  *    ".d"
52  *    ".dd"
53  *    ".ddd", and so on.
54  * 
55  * @author Toni Kalajainen
56  */
57 public class TimeFormat extends Format {
58
59         private static final long serialVersionUID = 1L;
60         
61         public static final Pattern PATTERN = 
62                         Pattern.compile(
63                                         "(-)?" +                                    // Minus (-)
64                                         "(?:(\\d+)y *)?" +                                                      // Year part    "[y]y"
65                                         "(?:(\\d+)d *)?" +                                                      // Day part     "[d]d"
66                                         "(?:(?:(\\d{1,}):)??(?:(\\d{1,2}):)?(\\d{1,2}))?" +  // Time part    "[H*]H:mm:ss"
67                                     "(?:\\.(\\d+))?"                                                    // Decimal part ".ddd"
68                                         );
69
70         private static final BigDecimal TWO = BigDecimal.valueOf(2L);
71
72         double maxValue;
73         int decimals;
74         RoundingMode rounding = RoundingMode.HALF_UP;
75         MathContext decimalRoundingContext;
76         
77         public TimeFormat(double maxValue, int decimals)
78         {
79                 this.maxValue = maxValue;
80                 this.decimals = decimals;
81                 this.decimalRoundingContext = new MathContext(Math.max(1, decimals+1), rounding);
82         }
83
84         public void setMaxValue(double maxValue) {
85                 this.maxValue = maxValue;
86         }
87         
88         public void setDecimals(int decimals) {
89                 this.decimals = decimals;
90                 this.decimalRoundingContext = new MathContext(Math.max(1, decimals+1), rounding);
91         }
92         
93         public void setRounding(RoundingMode rounding) {
94                 this.rounding = rounding;
95                 this.decimalRoundingContext = new MathContext(Math.max(1, decimals+1), rounding);
96         }
97
98         @Override
99         public StringBuffer format(Object obj, StringBuffer toAppendTo, FieldPosition pos) {
100                 // Prevent recurrent locking when invoking toAppendTo-methods.
101                 synchronized (toAppendTo) {
102                         return formatSync(obj, toAppendTo, pos);
103                 }
104         }
105
106         private StringBuffer formatSync(Object obj, StringBuffer toAppendTo, FieldPosition pos) {
107                 double x = ( (Number) obj ).doubleValue(); 
108                 int initLen = toAppendTo.length();
109
110                 if (Double.isInfinite(x)) {
111                         return toAppendTo.append((x == Float.POSITIVE_INFINITY) ? "\u221E" : "-\u221E");
112                 } else if (Double.isNaN(x)) {
113                         return toAppendTo.append("NaN");
114                 }
115
116                 if (x<0) {
117                         toAppendTo.append("-");
118                         x=-x;
119                         initLen = toAppendTo.length();
120                 }
121
122                 // The value of x-floor(x) is between [0,1].
123                 // We want use BigDecimal to round to the specified number of decimals.
124                 // The problem is that if x is 0.99999... so that it will be rounded to 1.000...
125                 // the 1 at the front will count as a decimal in the rounding logic
126                 // and we end up losing 1 actual decimal.
127                 // Therefore we add 1.0 to x make it be between [1,2] in which case
128                 // we can just round to n+1 decimals and it will always work.
129                 BigDecimal decimalPart = new BigDecimal(x - Math.floor(x) + 1.0);
130                 decimalPart = decimalPart.round(decimalRoundingContext);
131                 // decimal is now [1.000..,2.000...].
132                 // If decimalPart equals 2.0 it means that the
133                 // requested decimal part value was close enough
134                 // to 1.0 that it overflows and becomes 000...
135                 // This means that the overflow must be added to/subtracted from
136                 // the integer part of the input number.
137                 boolean needToRound = TWO.compareTo(decimalPart) == 0;
138
139                 double max = Math.max(this.maxValue, x);
140                 long xl = needToRound ? (long) Math.round( x ) : (long) Math.floor( x );
141
142                 // Write years
143                 if (xl>=(24L*60L*60L*365L)) {
144                         long years = xl / (24L*60L*60L*365L);
145                         toAppendTo.append( (long) years );
146                         toAppendTo.append("y");
147                 }
148
149                 // Write days
150                 if (xl>=(24L*60L*60L)) {
151                         if (toAppendTo.length()!=initLen) toAppendTo.append(' ');
152                         long days = (xl % (24L*60L*60L*365L)) / (24L*60L*60L); 
153                         toAppendTo.append( (long) days );
154                         toAppendTo.append("d");
155                 }
156
157                 // Write HH:mm:ss
158                 if (decimals>=-5) {
159                         if (toAppendTo.length()!=initLen) toAppendTo.append(' ');
160                         // Seconds of the day
161                         long seconds = xl % 86400L;
162                         
163                         // Write HH:
164                         if (max>=24*60) {
165                                 long hh = seconds / 3600;
166                                 if (x>3600) {
167                                         toAppendTo.append( hh/10 );
168                                 }
169                                 toAppendTo.append( hh%10 );
170                                 toAppendTo.append(":");
171                         }
172                         
173                         // Write mm:
174                         if (max>=60) {
175                                 long mm = (seconds / 60) % 60;
176                                 toAppendTo.append( mm/10 );
177                                 toAppendTo.append( mm%10 );
178                                 toAppendTo.append(":");
179                         }
180                         
181                         // Write ss
182                         {
183                                 long ss = seconds % 60;
184                                 if (x>=10 || initLen!=toAppendTo.length()) {
185                                         toAppendTo.append( ss/10 );
186                                 }
187                                 toAppendTo.append( ss%10 );
188                         }
189                         
190                         // Write milliseconds and more
191                         if (decimals>0) {
192                                 // add the decimal separator and part to the result.
193                                 toAppendTo.append('.');
194                                 String dps = decimalPart.toString();
195                                 int decimalPartLen = dps.length();
196                                 int trailingZeros = decimals;
197                                 if (decimalPartLen > 2) {
198                                         // If the original number was exact (e.g. 1)
199                                         // dp will contain only "1"
200                                         toAppendTo.append(dps, 2, decimalPartLen);
201                                         trailingZeros -= decimalPartLen - 2; 
202                                 }
203                                 for (int d = 0; d < trailingZeros; ++d)
204                                         toAppendTo.append('0');
205                         }
206                 }
207                 
208                 if (toAppendTo.length()==initLen) toAppendTo.append('-');
209                 
210                 return toAppendTo;
211         }
212
213         @Override
214         public Object parseObject(String source) throws ParseException {
215                 Matcher m = PATTERN.matcher(source);
216                 if (!m.matches()) {
217                         try {
218                                 return Double.parseDouble( source );
219                         } catch (NumberFormatException nfe) {
220                                 throw new ParseException("TimeFormat.parseObject('" + source + "') failed", m.regionStart());
221                         }
222                 }
223                 
224                 String negG = m.group(1);
225                 String yearG = m.group(2);
226                 String dayG = m.group(3);
227                 String hourG = m.group(4);
228                 String minuteG = m.group(5);
229                 String secondG = m.group(6);
230                 String decimalG = m.group(7);
231                 boolean negative = negG==null?false:negG.equals("-");
232                 double years = yearG==null?0.:Double.parseDouble(yearG);
233                 double days = dayG==null?0.:Double.parseDouble(dayG);
234                 double hours = hourG==null?0.:Double.parseDouble(hourG);
235                 double minutes = minuteG==null?0.:Double.parseDouble(minuteG);
236                 double seconds = secondG==null?0.:Double.parseDouble(secondG);
237                 double decimals = decimalG==null?0.:Double.parseDouble(decimalG)/Math.pow(10, decimalG.length());
238
239                 double value = years*31536000. + days*86400. + hours*3600. + minutes*60. + seconds + decimals;
240                 if ( negative ) value = -value;
241                 return value;
242         }
243         
244         @Override
245         public Object parseObject(String source, ParsePosition pos) {
246                 try {
247             return parseObject(source);
248         } catch (ParseException e) {
249             pos.setErrorIndex(e.getErrorOffset());
250             return null;
251         }
252         }
253         
254 }