X-Git-Url: https://gerrit.simantics.org/r/gitweb?a=blobdiff_plain;f=bundles%2Forg.simantics.utils%2Fsrc%2Forg%2Fsimantics%2Futils%2Fformat%2FTimeFormat.java;h=89b14e8b089f2a7c43a55f671061be66a47b5ca0;hb=3a1169e5b601f99d66cadaf398329fde9cc8e14a;hp=30451e149f9213e181bfccf6e4b4f0b7060ab282;hpb=f23dc81afe57e77d20706a9a94002ce4c72f670d;p=simantics%2Fplatform.git 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 index 30451e149..89b14e8b0 100644 --- a/bundles/org.simantics.utils/src/org/simantics/utils/format/TimeFormat.java +++ b/bundles/org.simantics.utils/src/org/simantics/utils/format/TimeFormat.java @@ -1,254 +1,254 @@ -/******************************************************************************* - * 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] - *

- * Year part. - * "[y]y " - * "[yy]y ", and so on. - *

- * Day part. - * "[d]d " - * "[dd]d ", and so on. - * - *

- * Time part five formats: - * "HH:mm:ss" - * "H:mm:ss" - * "mm:ss" - * "ss" - * "s" - *

- * 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. - * - *

- * 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; - } - } - -} +/******************************************************************************* + * 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] + *

+ * Year part. + * "[y]y " + * "[yy]y ", and so on. + *

+ * Day part. + * "[d]d " + * "[dd]d ", and so on. + * + *

+ * Time part five formats: + * "HH:mm:ss" + * "H:mm:ss" + * "mm:ss" + * "ss" + * "s" + *

+ * 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. + * + *

+ * 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; + } + } + +}