Author: yegor
Date: Sat Mar 19 12:42:01 2011
New Revision: 1083173

URL: http://svn.apache.org/viewvc?rev=1083173&view=rev
Log:
improved DataFormatter, see Bugzilla 50841

Modified:
    poi/trunk/src/documentation/content/xdocs/status.xml
    poi/trunk/src/examples/src/org/apache/poi/ss/examples/ToCSV.java
    poi/trunk/src/java/org/apache/poi/ss/usermodel/DataFormatter.java
    poi/trunk/src/java/org/apache/poi/ss/usermodel/DateUtil.java
    poi/trunk/src/java/org/apache/poi/ss/usermodel/ExcelStyleDateFormatter.java
    poi/trunk/src/testcases/org/apache/poi/hssf/usermodel/TestHSSFDateUtil.java
    poi/trunk/src/testcases/org/apache/poi/ss/usermodel/TestDataFormatter.java

Modified: poi/trunk/src/documentation/content/xdocs/status.xml
URL: 
http://svn.apache.org/viewvc/poi/trunk/src/documentation/content/xdocs/status.xml?rev=1083173&r1=1083172&r2=1083173&view=diff
==============================================================================
--- poi/trunk/src/documentation/content/xdocs/status.xml (original)
+++ poi/trunk/src/documentation/content/xdocs/status.xml Sat Mar 19 12:42:01 
2011
@@ -34,6 +34,7 @@
 
     <changes>
         <release version="3.8-beta2" date="2011-??-??">
+           <action dev="poi-developers" type="fix">50841 - Improved 
SpreadSheet DataFormatter to handle scientific notation, invalid dates and 
format spacers</action>
            <action dev="poi-developers" type="fix">49381 - Correct 
createFreezePane in XSSF, so that the left row/column matches the documentation 
+ HSSF</action>
            <action dev="poi-developers" type="fix">49253 - When setting 
repeating rows and columns for XSSF, don't break the print settings if they 
were already there</action>
            <action dev="poi-developers" type="fix">49219 - ExternalNameRecord 
support for DDE Link entries without an operation</action>

Modified: poi/trunk/src/examples/src/org/apache/poi/ss/examples/ToCSV.java
URL: 
http://svn.apache.org/viewvc/poi/trunk/src/examples/src/org/apache/poi/ss/examples/ToCSV.java?rev=1083173&r1=1083172&r2=1083173&view=diff
==============================================================================
--- poi/trunk/src/examples/src/org/apache/poi/ss/examples/ToCSV.java (original)
+++ poi/trunk/src/examples/src/org/apache/poi/ss/examples/ToCSV.java Sat Mar 19 
12:42:01 2011
@@ -389,7 +389,7 @@ public class ToCSV {
             // formatted String encapsulating the cells contents.
             this.workbook = WorkbookFactory.create(fis);
             this.evaluator = 
this.workbook.getCreationHelper().createFormulaEvaluator();
-            this.formatter = new DataFormatter();
+            this.formatter = new DataFormatter(true);
         }
         finally {
             if(fis != null) {

Modified: poi/trunk/src/java/org/apache/poi/ss/usermodel/DataFormatter.java
URL: 
http://svn.apache.org/viewvc/poi/trunk/src/java/org/apache/poi/ss/usermodel/DataFormatter.java?rev=1083173&r1=1083172&r2=1083173&view=diff
==============================================================================
--- poi/trunk/src/java/org/apache/poi/ss/usermodel/DataFormatter.java (original)
+++ poi/trunk/src/java/org/apache/poi/ss/usermodel/DataFormatter.java Sat Mar 
19 12:42:01 2011
@@ -63,8 +63,27 @@ import java.text.*;
  * default format will only be used when a Format cannot be created from the
  * cell's data format string.
  *
+ * <p>
+ * Note that by default formatted numeric values are trimmed.
+ * Excel formats can contain spacers and padding and the default behavior is 
to strip them off.
+ * </p>
+ * <p>Example:</p>
+ * <p>
+ * Consider a numeric cell with a value <code>12.343</code> and format 
<code>"##.##_ "</code>.
+ *  The trailing underscore and space ("_ ") in the format adds a space to the 
end and Excel formats this cell as <code>"12.34 "</code>,
+ *  but <code>DataFormatter</code> trims the formatted value and returns 
<code>"12.34"</code>.
+ * </p>
+ * You can enable spaces by passing the <code>emulateCsv=true</code> flag in 
the <code>DateFormatter</code> cosntructor.
+ * If set to true, then the output tries to conform to what you get when you 
take an xls or xlsx in Excel and Save As CSV file:
+ * <ul>
+ *  <li>returned values are not trimmed</li>
+ *  <li>Invalid dates are formatted as  255 pound signs ("#")</li>
+ *  <li>simulate Excel's handling of a format string of all # when the value 
is 0.
+ *   Excel will output "", <code>DataFormatter</code> will output "0".
+ * </ul>
  * @author James May (james dot may at fmr dot com)
- *
+ * @author Robert Kish
+  *
  */
 public class DataFormatter {
 
@@ -80,7 +99,7 @@ public class DataFormatter {
     /** A regex to find patterns like [$$-1009] and [$?-452]. */
     private static final Pattern specialPatternGroup = 
Pattern.compile("(\\[\\$[^-\\]]*-[0-9A-Z]+\\])");
 
-    /** 
+    /**
      * A regex to match the colour formattings rules.
      * Allowed colours are: Black, Blue, Cyan, Green,
      *  Magenta, Red, White, Yellow, "Color n" (1<=n<=56)
@@ -89,7 +108,18 @@ public class DataFormatter {
        
Pattern.compile("(\\[BLACK\\])|(\\[BLUE\\])|(\\[CYAN\\])|(\\[GREEN\\])|" +
                        
"(\\[MAGENTA\\])|(\\[RED\\])|(\\[WHITE\\])|(\\[YELLOW\\])|" +
                        "(\\[COLOR\\s*\\d\\])|(\\[COLOR\\s*[0-5]\\d\\])", 
Pattern.CASE_INSENSITIVE);
-    
+
+    /**
+      * Cells formatted with a date or time format and which contain invalid 
date or time values
+     *  show 255 pound signs ("#").
+      */
+     private static final String invalidDateTimeString;
+     static {
+         StringBuilder buf = new StringBuilder();
+         for(int i = 0; i < 255; i++) buf.append('#');
+         invalidDateTimeString = buf.toString();
+     }
+
     /**
      * The decimal symbols of the locale used for formatting values.
      */
@@ -115,11 +145,33 @@ public class DataFormatter {
      */
     private final Map<String,Format> formats;
 
+    private boolean emulateCsv = false;
+
     /**
      * Creates a formatter using the {@link Locale#getDefault() default 
locale}.
      */
     public DataFormatter() {
+        this(false);
+    }
+
+    /**
+     * Creates a formatter using the {@link Locale#getDefault() default 
locale}.
+     *
+     * @param  emulateCsv whether to emulate CSV output.
+     */
+    public DataFormatter(boolean emulateCsv) {
         this(Locale.getDefault());
+        this.emulateCsv = emulateCsv;
+    }
+
+    /**
+     * Creates a formatter using the given locale.
+     *
+     * @param  emulateCsv whether to emulate CSV output.
+     */
+    public DataFormatter(Locale locale, boolean emulateCsv) {
+        this(locale);
+        this.emulateCsv = emulateCsv;
     }
 
     /**
@@ -177,27 +229,42 @@ public class DataFormatter {
         return getFormat(cell.getNumericCellValue(), formatIndex, formatStr);
     }
 
-    private Format getFormat(double cellValue, int formatIndex, String 
formatStr) {
-       // Excel supports positive/negative/zero, but java
-       // doesn't, so we need to do it specially
-       if(formatStr.indexOf(';') != formatStr.lastIndexOf(';')) {
-          int lastAt = formatStr.lastIndexOf(';');
-          String zeroFormat = formatStr.substring(lastAt+1);
-          String normalFormat = formatStr.substring(0,lastAt);
-          if(cellValue == 0.0) {
-             formatStr = zeroFormat;
-          } else {
-             formatStr = normalFormat;
-          }
+    private Format getFormat(double cellValue, int formatIndex, String 
formatStrIn) {
+        String formatStr = formatStrIn;
+        // Excel supports positive/negative/zero, but java
+        // doesn't, so we need to do it specially
+        final int firstAt = formatStr.indexOf(';');
+        final int lastAt = formatStr.lastIndexOf(';');
+        // p and p;n are ok by default. p;n;z and p;n;z;s need to be fixed.
+        if (firstAt != -1 && firstAt != lastAt) {
+            final int secondAt = formatStr.indexOf(';', firstAt + 1);
+            if (secondAt == lastAt) { // p;n;z
+                if (cellValue == 0.0) {
+                    formatStr = formatStr.substring(lastAt + 1);
+                } else {
+                    formatStr = formatStr.substring(0, lastAt);
+                }
+            } else {
+                if (cellValue == 0.0) { // p;n;z;s
+                    formatStr = formatStr.substring(secondAt + 1, lastAt);
+                } else {
+                    formatStr = formatStr.substring(0, secondAt);
+                }
+            }
+        }
+
+       // Excel's # with value 0 will output empty where Java will output 0. 
This hack removes the # from the format.
+       if (emulateCsv && cellValue == 0.0 && formatStr.contains("#") && 
!formatStr.contains("0")) {
+           formatStr = formatStr.replaceAll("#", "");
        }
-      
-       // See if we already have it cached
+
+        // See if we already have it cached
         Format format = formats.get(formatStr);
         if (format != null) {
             return format;
         }
         if ("General".equalsIgnoreCase(formatStr) || "@".equals(formatStr)) {
-            if (DataFormatter.isWholeNumber(cellValue)) {
+            if (isWholeNumber(cellValue)) {
                 return generalWholeNumFormat;
             }
             return generalDecimalNumFormat;
@@ -261,7 +328,6 @@ public class DataFormatter {
             return getDefaultFormat(cellValue);
         }
 
-
         if(DateUtil.isADateFormat(formatIndex,formatStr) &&
                 DateUtil.isValidExcelDate(cellValue)) {
             return createDateFormat(formatStr, cellValue);
@@ -269,6 +335,10 @@ public class DataFormatter {
         if (numPattern.matcher(formatStr).find()) {
             return createNumberFormat(formatStr, cellValue);
         }
+
+        if (emulateCsv) {
+            return new ConstantStringFormat(cleanFormatForNumber(formatStr));
+        }
         // TODO - when does this occur?
         return null;
     }
@@ -311,9 +381,33 @@ public class DataFormatter {
         char[] chars = formatStr.toCharArray();
         boolean mIsMonth = true;
         List<Integer> ms = new ArrayList<Integer>();
+        boolean isElapsed = false;
         for(int j=0; j<chars.length; j++) {
             char c = chars[j];
+            if (c == '[' && !isElapsed) {
+                isElapsed = true;
+                mIsMonth = false;
+                sb.append(c);
+            }
+            else if (c == ']' && isElapsed) {
+                isElapsed = false;
+                sb.append(c);
+            }
+            else if (isElapsed) {
             if (c == 'h' || c == 'H') {
+                    sb.append('H');
+                }
+                else if (c == 'm' || c == 'M') {
+                    sb.append('m');
+                }
+                else if (c == 's' || c == 'S') {
+                    sb.append('s');
+                }
+                else {
+                    sb.append(c);
+                }
+            }
+            else if (c == 'h' || c == 'H') {
                 mIsMonth = false;
                 if (hasAmPm) {
                     sb.append('h');
@@ -373,32 +467,61 @@ public class DataFormatter {
 
     }
 
-    private Format createNumberFormat(String formatStr, double cellValue) {
+    private String cleanFormatForNumber(String formatStr) {
         StringBuffer sb = new StringBuffer(formatStr);
-        
-        // If they requested spacers, with "_",
-        //  remove those as we don't do spacing
-        // If they requested full-column-width
-        //  padding, with "*", remove those too
-        for(int i = 0; i < sb.length(); i++) {
-            char c = sb.charAt(i);
-            if(c == '_' || c == '*') {
-               if(i > 0 && sb.charAt((i-1)) == '\\') {
-                  // It's escaped, don't worry
-                  continue;
-               } else {
-                  if(i < sb.length()-1) {
-                     // Remove the character we're supposed
-                     //  to match the space of / pad to the
-                     //  column width with
-                     sb.deleteCharAt(i+1);
-                  }
-                  // Remove the _ too
-                  sb.deleteCharAt(i);
-               }
+
+        if (emulateCsv) {
+            // Requested spacers with "_" are replaced by a single space.
+            // Full-column-width padding "*" are removed.
+            // Not processing fractions at this time. Replace ? with space.
+            // This matches CSV output.
+            for (int i = 0; i < sb.length(); i++) {
+                char c = sb.charAt(i);
+                if (c == '_' || c == '*' || c == '?') {
+                    if (i > 0 && sb.charAt((i - 1)) == '\\') {
+                        // It's escaped, don't worry
+                        continue;
+                    }
+                    if (c == '?') {
+                        sb.setCharAt(i, ' ');
+                    } else if (i < sb.length() - 1) {
+                        // Remove the character we're supposed
+                        //  to match the space of / pad to the
+                        //  column width with
+                        if (c == '_') {
+                            sb.setCharAt(i + 1, ' ');
+                        } else {
+                            sb.deleteCharAt(i + 1);
+                        }
+                        // Remove the character too
+                        sb.deleteCharAt(i);
+                    }
+                }
+            }
+        } else {
+            // If they requested spacers, with "_",
+            //  remove those as we don't do spacing
+            // If they requested full-column-width
+            //  padding, with "*", remove those too
+            for (int i = 0; i < sb.length(); i++) {
+                char c = sb.charAt(i);
+                if (c == '_' || c == '*') {
+                    if (i > 0 && sb.charAt((i - 1)) == '\\') {
+                        // It's escaped, don't worry
+                        continue;
+                    }
+                    if (i < sb.length() - 1) {
+                        // Remove the character we're supposed
+                        //  to match the space of / pad to the
+                        //  column width with
+                        sb.deleteCharAt(i + 1);
+                    }
+                    // Remove the _ too
+                    sb.deleteCharAt(i);
+                }
             }
         }
-        
+
         // Now, handle the other aspects like 
         //  quoting and scientific notation
         for(int i = 0; i < sb.length(); i++) {
@@ -415,8 +538,14 @@ public class DataFormatter {
             }
         }
 
+        return sb.toString();
+    }
+
+    private Format createNumberFormat(String formatStr, double cellValue) {
+        final String format = cleanFormatForNumber(formatStr);
+        
         try {
-            DecimalFormat df = new DecimalFormat(sb.toString(), 
decimalSymbols);
+            DecimalFormat df = new DecimalFormat(format, decimalSymbols);
             setExcelStyleRoundingMode(df);
             return df;
         } catch(IllegalArgumentException iae) {
@@ -522,22 +651,32 @@ public class DataFormatter {
      */
     public String formatRawCellContents(double value, int formatIndex, String 
formatString, boolean use1904Windowing) {
         // Is it a date?
-        if(DateUtil.isADateFormat(formatIndex,formatString) &&
-                DateUtil.isValidExcelDate(value)) {
-            Format dateFormat = getFormat(value, formatIndex, formatString);
-            if(dateFormat instanceof ExcelStyleDateFormatter) {
-               // Hint about the raw excel value
-               
((ExcelStyleDateFormatter)dateFormat).setDateToBeFormatted(value);
+        if(DateUtil.isADateFormat(formatIndex,formatString)) {
+            if(DateUtil.isValidExcelDate(value)) {
+                Format dateFormat = getFormat(value, formatIndex, 
formatString);
+                if(dateFormat instanceof ExcelStyleDateFormatter) {
+                   // Hint about the raw excel value
+                   
((ExcelStyleDateFormatter)dateFormat).setDateToBeFormatted(value);
+                }
+                Date d = DateUtil.getJavaDate(value, use1904Windowing);
+                return performDateFormatting(d, dateFormat);
             }
-            Date d = DateUtil.getJavaDate(value, use1904Windowing);
-            return performDateFormatting(d, dateFormat);
+             // RK: Invalid dates are 255 #s.
+             if (emulateCsv) {
+                 return invalidDateTimeString;
+             }
         }
         // else Number
-        Format numberFormat = getFormat(value, formatIndex, formatString);
-        if (numberFormat == null) {
-            return String.valueOf(value);
-        }
-        return numberFormat.format(new Double(value));
+            Format numberFormat = getFormat(value, formatIndex, formatString);
+            if (numberFormat == null) {
+                return String.valueOf(value);
+            }
+            // RK: This hack handles scientific notation by adding the missing 
+ back.
+            String result = numberFormat.format(new Double(value));
+            if (result.contains("E") && !result.contains("E-")) {
+                result = result.replaceFirst("E", "E+");
+            }
+            return result;
     }
 
     /**
@@ -669,9 +808,21 @@ public class DataFormatter {
      *  on Java 1.5. 
      */
     public static void setExcelStyleRoundingMode(DecimalFormat format) {
+        setExcelStyleRoundingMode(format, RoundingMode.HALF_UP);
+    }
+
+    /**
+     * Enables custom rounding mode
+     *  on the Decimal Format if possible.
+     * This will work for Java 1.6, but isn't possible
+     *  on Java 1.5.
+     * @param format DecimalFormat
+     * @param roundingMode RoundingMode
+     */
+    public static void setExcelStyleRoundingMode(DecimalFormat format, 
RoundingMode roundingMode) {
        try {
           Method srm = format.getClass().getMethod("setRoundingMode", 
RoundingMode.class);
-          srm.invoke(format, RoundingMode.HALF_UP);
+          srm.invoke(format, roundingMode);
        } catch(NoSuchMethodException e) {
           // Java 1.5
        } catch(IllegalAccessException iae) {
@@ -684,7 +835,7 @@ public class DataFormatter {
           // Not much we can do here
        }
     }
-    
+
     /**
      * Format class for Excel's SSN format. This class mimics Excel's built-in
      * SSN formatting.
@@ -794,4 +945,31 @@ public class DataFormatter {
             return df.parseObject(source, pos);
         }
     }
+
+    /**
+     * Format class that does nothing and always returns a constant string.
+     *
+     * This format is used to simulate Excel's handling of a format string
+     * of all # when the value is 0. Excel will output "", Java will output 
"0".
+     *
+     * @see DataFormatter#createFormat(double, int, String)
+     */
+    @SuppressWarnings("serial")
+   private static final class ConstantStringFormat extends Format {
+        private static final DecimalFormat df = 
createIntegerOnlyFormat("##########");
+        private final String str;
+        public ConstantStringFormat(String s) {
+            str = s;
+        }
+
+        @Override
+        public StringBuffer format(Object obj, StringBuffer toAppendTo, 
FieldPosition pos) {
+            return toAppendTo.append(str);
+        }
+
+        @Override
+        public Object parseObject(String source, ParsePosition pos) {
+            return df.parseObject(source, pos);
+        }
+    }
 }

Modified: poi/trunk/src/java/org/apache/poi/ss/usermodel/DateUtil.java
URL: 
http://svn.apache.org/viewvc/poi/trunk/src/java/org/apache/poi/ss/usermodel/DateUtil.java?rev=1083173&r1=1083172&r2=1083173&view=diff
==============================================================================
--- poi/trunk/src/java/org/apache/poi/ss/usermodel/DateUtil.java (original)
+++ poi/trunk/src/java/org/apache/poi/ss/usermodel/DateUtil.java Sat Mar 19 
12:42:01 2011
@@ -52,7 +52,9 @@ public class DateUtil {
      */
     private static final Pattern date_ptrn1 = 
Pattern.compile("^\\[\\$\\-.*?\\]");
     private static final Pattern date_ptrn2 = 
Pattern.compile("^\\[[a-zA-Z]+\\]");
-    private static final Pattern date_ptrn3 = 
Pattern.compile("^[yYmMdDhHsS\\-/,. :\"\\\\]+0?[ampAMP/]*$");
+    private static final Pattern date_ptrn3 = 
Pattern.compile("^[\\[\\]yYmMdDhHsS\\-/,. :\"\\\\]+0*[ampAMP/]*$");
+    //  elapsed time patterns: [h],[m] and [s]
+    private static final Pattern date_ptrn4 = 
Pattern.compile("^\\[([hH]+|[mM]+|[sS]+)\\]");
 
     /**
      * Given a Date, converts it into a double representing its internal Excel 
representation,
@@ -258,7 +260,12 @@ public class DateUtil {
             sb.append(c);
         }
         fs = sb.toString();
-        
+
+        // short-circuit if it indicates elapsed time: [h], [m] or [s]
+        if(date_ptrn4.matcher(fs).matches()){
+            return true;
+        }
+
         // If it starts with [$-...], then could be a date, but
         //  who knows what that starting bit is all about
         fs = date_ptrn1.matcher(fs).replaceAll("");

Modified: 
poi/trunk/src/java/org/apache/poi/ss/usermodel/ExcelStyleDateFormatter.java
URL: 
http://svn.apache.org/viewvc/poi/trunk/src/java/org/apache/poi/ss/usermodel/ExcelStyleDateFormatter.java?rev=1083173&r1=1083172&r2=1083173&view=diff
==============================================================================
--- poi/trunk/src/java/org/apache/poi/ss/usermodel/ExcelStyleDateFormatter.java 
(original)
+++ poi/trunk/src/java/org/apache/poi/ss/usermodel/ExcelStyleDateFormatter.java 
Sat Mar 19 12:42:01 2011
@@ -16,130 +16,157 @@
 ==================================================================== */
 package org.apache.poi.ss.usermodel;
 
-import java.util.regex.Pattern;
-import java.util.regex.Matcher;
 import java.util.*;
 import java.math.RoundingMode;
 import java.text.*;
 
 /**
  * A wrapper around a {@link SimpleDateFormat} instance,
- *  which handles a few Excel-style extensions that 
- *  are not supported by {@link SimpleDateFormat}.
+ * which handles a few Excel-style extensions that
+ * are not supported by {@link SimpleDateFormat}.
  * Currently, the extensions are around the handling
- *  of elapsed time, eg rendering 1 day 2 hours
- *  as 26 hours.
+ * of elapsed time, eg rendering 1 day 2 hours
+ * as 26 hours.
  */
 public class ExcelStyleDateFormatter extends SimpleDateFormat {
-   public static final char MMMMM_START_SYMBOL = '\ue001';
-   public static final char MMMMM_TRUNCATE_SYMBOL = '\ue002';
-   public static final char H_BRACKET_SYMBOL  = '\ue010';
-   public static final char HH_BRACKET_SYMBOL = '\ue011';
-   public static final char M_BRACKET_SYMBOL  = '\ue012';
-   public static final char MM_BRACKET_SYMBOL = '\ue013';
-   public static final char S_BRACKET_SYMBOL  = '\ue014';
-   public static final char SS_BRACKET_SYMBOL = '\ue015';
-   
-   private DecimalFormat format1digit = new DecimalFormat("0");
-   private DecimalFormat format2digits = new DecimalFormat("00");
-   {
-      DataFormatter.setExcelStyleRoundingMode(format1digit);
-      DataFormatter.setExcelStyleRoundingMode(format2digits);
-   }
-   
-   private double dateToBeFormatted = 0.0;
-   
-   public ExcelStyleDateFormatter() {
-      super();
-   }
-   public ExcelStyleDateFormatter(String pattern) {
-      super(processFormatPattern(pattern));
-   }
-   public ExcelStyleDateFormatter(String pattern,
-         DateFormatSymbols formatSymbols) {
-      super(processFormatPattern(pattern), formatSymbols);
-   }
-   public ExcelStyleDateFormatter(String pattern, Locale locale) {
-      super(processFormatPattern(pattern), locale);
-   }
-
-   /**
-    * Takes a format String, and replaces Excel specific bits
-    *  with our detection sequences
-    */
-   private static String processFormatPattern(String f) {
-      f = f.replaceAll("MMMMM", MMMMM_START_SYMBOL + "MMM" + 
MMMMM_TRUNCATE_SYMBOL);
-      f = f.replaceAll("\\[H\\]",  String.valueOf(H_BRACKET_SYMBOL));
-      f = f.replaceAll("\\[HH\\]", String.valueOf(HH_BRACKET_SYMBOL));
-      f = f.replaceAll("\\[m\\]",  String.valueOf(M_BRACKET_SYMBOL));
-      f = f.replaceAll("\\[mm\\]", String.valueOf(MM_BRACKET_SYMBOL));
-      f = f.replaceAll("\\[s\\]",  String.valueOf(S_BRACKET_SYMBOL));
-      f = f.replaceAll("\\[ss\\]", String.valueOf(SS_BRACKET_SYMBOL));
-      return f;
-   }
-
-   /**
-    * Used to let us know what the date being
-    *  formatted is, in Excel terms, which we
-    *  may wish to use when handling elapsed
-    *  times.
-    */
-   public void setDateToBeFormatted(double date) {
-      this.dateToBeFormatted = date;
-   }
-   
-   @Override
-   public StringBuffer format(Date date, StringBuffer paramStringBuffer,
-         FieldPosition paramFieldPosition) {
-      // Do the normal format
-      String s = super.format(date, paramStringBuffer, 
paramFieldPosition).toString();
-      
-      // Now handle our special cases
-      if(s.indexOf(MMMMM_START_SYMBOL) != -1) {
-         s = s.replaceAll(
-               MMMMM_START_SYMBOL + "(\\w)\\w+" + MMMMM_TRUNCATE_SYMBOL,
-               "$1"
-         );
-      }
-      
-      if(s.indexOf(H_BRACKET_SYMBOL) != -1 ||
-            s.indexOf(HH_BRACKET_SYMBOL) != -1) {
-         double hours = dateToBeFormatted * 24;
-         s = s.replaceAll(
-               String.valueOf(H_BRACKET_SYMBOL), 
-               format1digit.format(hours)
-         );
-         s = s.replaceAll(
-               String.valueOf(HH_BRACKET_SYMBOL),
-               format2digits.format(hours)
-         );
-      }
-      
-      if(s.indexOf(M_BRACKET_SYMBOL) != -1 ||
-           s.indexOf(MM_BRACKET_SYMBOL) != -1) {
-         double minutes = dateToBeFormatted * 24 * 60;
-         s = s.replaceAll(
-               String.valueOf(M_BRACKET_SYMBOL), 
-               format1digit.format(minutes)
-         );
-         s = s.replaceAll(
-               String.valueOf(MM_BRACKET_SYMBOL),
-               format2digits.format(minutes)
-         );
-      }
-      if(s.indexOf(S_BRACKET_SYMBOL) != -1 ||
-            s.indexOf(SS_BRACKET_SYMBOL) != -1) {
-         double seconds = dateToBeFormatted * 24 * 60 * 60;
-         s = s.replaceAll(
-               String.valueOf(S_BRACKET_SYMBOL), 
-               format1digit.format(seconds)
-         );
-         s = s.replaceAll(
-               String.valueOf(SS_BRACKET_SYMBOL),
-               format2digits.format(seconds)
-         );
-      }
+    public static final char MMMMM_START_SYMBOL = '\ue001';
+    public static final char MMMMM_TRUNCATE_SYMBOL = '\ue002';
+    public static final char H_BRACKET_SYMBOL = '\ue010';
+    public static final char HH_BRACKET_SYMBOL = '\ue011';
+    public static final char M_BRACKET_SYMBOL = '\ue012';
+    public static final char MM_BRACKET_SYMBOL = '\ue013';
+    public static final char S_BRACKET_SYMBOL = '\ue014';
+    public static final char SS_BRACKET_SYMBOL = '\ue015';
+    public static final char L_BRACKET_SYMBOL = '\ue016';
+    public static final char LL_BRACKET_SYMBOL = '\ue017';
 
-      return new StringBuffer(s);
-   }
+    private DecimalFormat format1digit = new DecimalFormat("0");
+    private DecimalFormat format2digits = new DecimalFormat("00");
+
+    private DecimalFormat format3digit = new DecimalFormat("0");
+    private DecimalFormat format4digits = new DecimalFormat("00");
+
+    {
+        DataFormatter.setExcelStyleRoundingMode(format1digit, 
RoundingMode.DOWN);
+        DataFormatter.setExcelStyleRoundingMode(format2digits, 
RoundingMode.DOWN);
+        DataFormatter.setExcelStyleRoundingMode(format3digit);
+        DataFormatter.setExcelStyleRoundingMode(format4digits);
+    }
+
+    private double dateToBeFormatted = 0.0;
+
+    public ExcelStyleDateFormatter() {
+        super();
+    }
+
+    public ExcelStyleDateFormatter(String pattern) {
+        super(processFormatPattern(pattern));
+    }
+
+    public ExcelStyleDateFormatter(String pattern,
+                                   DateFormatSymbols formatSymbols) {
+        super(processFormatPattern(pattern), formatSymbols);
+    }
+
+    public ExcelStyleDateFormatter(String pattern, Locale locale) {
+        super(processFormatPattern(pattern), locale);
+    }
+
+    /**
+     * Takes a format String, and replaces Excel specific bits
+     * with our detection sequences
+     */
+    private static String processFormatPattern(String f) {
+        String t = f.replaceAll("MMMMM", MMMMM_START_SYMBOL + "MMM" + 
MMMMM_TRUNCATE_SYMBOL);
+        t = t.replaceAll("\\[H\\]", String.valueOf(H_BRACKET_SYMBOL));
+        t = t.replaceAll("\\[HH\\]", String.valueOf(HH_BRACKET_SYMBOL));
+        t = t.replaceAll("\\[m\\]", String.valueOf(M_BRACKET_SYMBOL));
+        t = t.replaceAll("\\[mm\\]", String.valueOf(MM_BRACKET_SYMBOL));
+        t = t.replaceAll("\\[s\\]", String.valueOf(S_BRACKET_SYMBOL));
+        t = t.replaceAll("\\[ss\\]", String.valueOf(SS_BRACKET_SYMBOL));
+        t = t.replaceAll("s.000", "s.S");
+        t = t.replaceAll("s.00", "s." + LL_BRACKET_SYMBOL);
+        t = t.replaceAll("s.0", "s." + L_BRACKET_SYMBOL);
+        return t;
+    }
+
+    /**
+     * Used to let us know what the date being
+     * formatted is, in Excel terms, which we
+     * may wish to use when handling elapsed
+     * times.
+     */
+    public void setDateToBeFormatted(double date) {
+        this.dateToBeFormatted = date;
+    }
+
+    @Override
+    public StringBuffer format(Date date, StringBuffer paramStringBuffer,
+                               FieldPosition paramFieldPosition) {
+        // Do the normal format
+        String s = super.format(date, paramStringBuffer, 
paramFieldPosition).toString();
+
+        // Now handle our special cases
+        if (s.indexOf(MMMMM_START_SYMBOL) != -1) {
+            s = s.replaceAll(
+                    MMMMM_START_SYMBOL + "(\\w)\\w+" + MMMMM_TRUNCATE_SYMBOL,
+                    "$1"
+            );
+        }
+
+        if (s.indexOf(H_BRACKET_SYMBOL) != -1 ||
+                s.indexOf(HH_BRACKET_SYMBOL) != -1) {
+            float hours = (float) dateToBeFormatted * 24;
+
+            s = s.replaceAll(
+                    String.valueOf(H_BRACKET_SYMBOL),
+                    format1digit.format(hours)
+            );
+            s = s.replaceAll(
+                    String.valueOf(HH_BRACKET_SYMBOL),
+                    format2digits.format(hours)
+            );
+        }
+
+        if (s.indexOf(M_BRACKET_SYMBOL) != -1 ||
+                s.indexOf(MM_BRACKET_SYMBOL) != -1) {
+            float minutes = (float) dateToBeFormatted * 24 * 60;
+            s = s.replaceAll(
+                    String.valueOf(M_BRACKET_SYMBOL),
+                    format1digit.format(minutes)
+            );
+            s = s.replaceAll(
+                    String.valueOf(MM_BRACKET_SYMBOL),
+                    format2digits.format(minutes)
+            );
+        }
+        if (s.indexOf(S_BRACKET_SYMBOL) != -1 ||
+                s.indexOf(SS_BRACKET_SYMBOL) != -1) {
+            float seconds = (float) (dateToBeFormatted * 24.0 * 60.0 * 60.0);
+            s = s.replaceAll(
+                    String.valueOf(S_BRACKET_SYMBOL),
+                    format1digit.format(seconds)
+            );
+            s = s.replaceAll(
+                    String.valueOf(SS_BRACKET_SYMBOL),
+                    format2digits.format(seconds)
+            );
+        }
+
+        if (s.indexOf(L_BRACKET_SYMBOL) != -1 ||
+                s.indexOf(LL_BRACKET_SYMBOL) != -1) {
+            float millisTemp = (float) ((dateToBeFormatted - 
Math.floor(dateToBeFormatted)) * 24.0 * 60.0 * 60.0);
+            float millis = (millisTemp - (int) millisTemp);
+            s = s.replaceAll(
+                    String.valueOf(L_BRACKET_SYMBOL),
+                    format3digit.format(millis * 10)
+            );
+            s = s.replaceAll(
+                    String.valueOf(LL_BRACKET_SYMBOL),
+                    format4digits.format(millis * 100)
+            );
+        }
+
+        return new StringBuffer(s);
+    }
 }

Modified: 
poi/trunk/src/testcases/org/apache/poi/hssf/usermodel/TestHSSFDateUtil.java
URL: 
http://svn.apache.org/viewvc/poi/trunk/src/testcases/org/apache/poi/hssf/usermodel/TestHSSFDateUtil.java?rev=1083173&r1=1083172&r2=1083173&view=diff
==============================================================================
--- poi/trunk/src/testcases/org/apache/poi/hssf/usermodel/TestHSSFDateUtil.java 
(original)
+++ poi/trunk/src/testcases/org/apache/poi/hssf/usermodel/TestHSSFDateUtil.java 
Sat Mar 19 12:42:01 2011
@@ -271,7 +271,8 @@ public final class TestHSSFDateUtil exte
                 "mm/dd HH:MM PM", "mm/dd HH:MM pm",
                 "m/d/yy h:mm AM/PM",
                 "hh:mm:ss", "hh:mm:ss.0", "mm:ss.0",
-
+                //support elapsed time [h],[m],[s]
+                "[hh]", "[mm]", "[ss]", "[SS]", "[red][hh]"
         };
         for(int i=0; i<formats.length; i++) {
             assertTrue(
@@ -286,6 +287,7 @@ public final class TestHSSFDateUtil exte
                 "0.0", "0.000",
                 "0%", "0.0%",
                 "[]Foo", "[BLACK]0.00%",
+                "[ms]", "[Mh]",
                 "", null
         };
         for(int i=0; i<formats.length; i++) {

Modified: 
poi/trunk/src/testcases/org/apache/poi/ss/usermodel/TestDataFormatter.java
URL: 
http://svn.apache.org/viewvc/poi/trunk/src/testcases/org/apache/poi/ss/usermodel/TestDataFormatter.java?rev=1083173&r1=1083172&r2=1083173&view=diff
==============================================================================
--- poi/trunk/src/testcases/org/apache/poi/ss/usermodel/TestDataFormatter.java 
(original)
+++ poi/trunk/src/testcases/org/apache/poi/ss/usermodel/TestDataFormatter.java 
Sat Mar 19 12:42:01 2011
@@ -27,7 +27,7 @@ import org.apache.poi.hssf.usermodel.Tes
 /**
  * Tests of {@link DataFormatter}
  *
- * See {@link TestHSSFDataFormatter} too for 
+ * See {@link TestHSSFDataFormatter} too for
  *  more tests.
  */
 public class TestDataFormatter extends TestCase {
@@ -180,6 +180,38 @@ public class TestDataFormatter extends T
     }
     
     /**
+     * DataFormatter is the CSV mode preserves spaces
+     */
+    public void testPaddingSpacesCSV() {
+       DataFormatter dfUS = new DataFormatter(Locale.US, true);
+       assertEquals("12.34 ", dfUS.formatRawCellContents(12.343, -1, "##.##_ 
"));
+       assertEquals("-12.34 ", dfUS.formatRawCellContents(-12.343, -1, "##.##_ 
"));
+       assertEquals(". ", dfUS.formatRawCellContents(0.0, -1, "##.##_ "));
+       assertEquals("12.34 ", dfUS.formatRawCellContents(12.343, -1, 
"##.##_1"));
+       assertEquals("-12.34 ", dfUS.formatRawCellContents(-12.343, -1, 
"##.##_1"));
+       assertEquals(". ", dfUS.formatRawCellContents(0.0, -1, "##.##_1"));
+       assertEquals("12.34 ", dfUS.formatRawCellContents(12.343, -1, 
"##.##_)"));
+       assertEquals("-12.34 ", dfUS.formatRawCellContents(-12.343, -1, 
"##.##_)"));
+       assertEquals(". ", dfUS.formatRawCellContents(0.0, -1, "##.##_)"));
+       assertEquals(" 12.34", dfUS.formatRawCellContents(12.343, -1, 
"_-##.##"));
+       assertEquals("- 12.34", dfUS.formatRawCellContents(-12.343, -1, 
"_-##.##"));
+       assertEquals(" .", dfUS.formatRawCellContents(0.0, -1, "_-##.##"));
+
+       assertEquals("12.34", dfUS.formatRawCellContents(12.343, -1, "##.##* 
"));
+       assertEquals("-12.34", dfUS.formatRawCellContents(-12.343, -1, "##.##* 
"));
+       assertEquals(".", dfUS.formatRawCellContents(0.0, -1, "##.##* "));
+       assertEquals("12.34", dfUS.formatRawCellContents(12.343, -1, 
"##.##*1"));
+       assertEquals("-12.34", dfUS.formatRawCellContents(-12.343, -1, 
"##.##*1"));
+       assertEquals(".", dfUS.formatRawCellContents(0.0, -1, "##.##*1"));
+       assertEquals("12.34", dfUS.formatRawCellContents(12.343, -1, 
"##.##*)"));
+       assertEquals("-12.34", dfUS.formatRawCellContents(-12.343, -1, 
"##.##*)"));
+       assertEquals(".", dfUS.formatRawCellContents(0.0, -1, "##.##*)"));
+       assertEquals("12.34", dfUS.formatRawCellContents(12.343, -1, 
"*-##.##"));
+       assertEquals("-12.34", dfUS.formatRawCellContents(-12.343, -1, 
"*-##.##"));
+       assertEquals(".", dfUS.formatRawCellContents(0.0, -1, "*-##.##"));
+    }
+
+    /**
      * Test that the special Excel month format MMMMM
      *  gets turned into the first letter of the month
      */
@@ -196,14 +228,14 @@ public class TestDataFormatter extends T
     }
     
     /**
-     * Test that we can handle elapsed time, 
+     * Test that we can handle elapsed time,
      *  eg formatting 1 day 4 hours as 28 hours
      */
     public void testElapsedTime() {
        DataFormatter dfUS = new DataFormatter(Locale.US);
-       
+
        double hour = 1.0/24.0;
-       
+
        assertEquals("01:00", dfUS.formatRawCellContents(1*hour, -1, "hh:mm"));
        assertEquals("05:00", dfUS.formatRawCellContents(5*hour, -1, "hh:mm"));
        assertEquals("20:00", dfUS.formatRawCellContents(20*hour, -1, "hh:mm"));
@@ -212,7 +244,7 @@ public class TestDataFormatter extends T
        assertEquals("02:00", dfUS.formatRawCellContents(26*hour, -1, "hh:mm"));
        assertEquals("20:00", dfUS.formatRawCellContents(44*hour, -1, "hh:mm"));
        assertEquals("02:00", dfUS.formatRawCellContents(50*hour, -1, "hh:mm"));
-       
+
        assertEquals("01:00", dfUS.formatRawCellContents(1*hour, -1, 
"[hh]:mm"));
        assertEquals("05:00", dfUS.formatRawCellContents(5*hour, -1, 
"[hh]:mm"));
        assertEquals("20:00", dfUS.formatRawCellContents(20*hour, -1, 
"[hh]:mm"));
@@ -221,12 +253,73 @@ public class TestDataFormatter extends T
        assertEquals("26:00", dfUS.formatRawCellContents(26*hour, -1, 
"[hh]:mm"));
        assertEquals("44:00", dfUS.formatRawCellContents(44*hour, -1, 
"[hh]:mm"));
        assertEquals("50:00", dfUS.formatRawCellContents(50*hour, -1, 
"[hh]:mm"));
-       
-       assertEquals("30:00", dfUS.formatRawCellContents(0.5*hour, -1, 
"[mm]:ss"));
-       assertEquals("60:00", dfUS.formatRawCellContents(1*hour, -1, 
"[mm]:ss"));
-       assertEquals("120:00", dfUS.formatRawCellContents(2*hour, -1, 
"[mm]:ss"));
+
+       assertEquals("01", dfUS.formatRawCellContents(1*hour, -1, "[hh]"));
+       assertEquals("05", dfUS.formatRawCellContents(5*hour, -1, "[hh]"));
+       assertEquals("20", dfUS.formatRawCellContents(20*hour, -1, "[hh]"));
+       assertEquals("23", dfUS.formatRawCellContents(23*hour, -1, "[hh]"));
+       assertEquals("24", dfUS.formatRawCellContents(24*hour, -1, "[hh]"));
+       assertEquals("26", dfUS.formatRawCellContents(26*hour, -1, "[hh]"));
+       assertEquals("44", dfUS.formatRawCellContents(44*hour, -1, "[hh]"));
+       assertEquals("50", dfUS.formatRawCellContents(50*hour, -1, "[hh]"));
+
+       double minute = 1.0/24.0/60.0;
+       assertEquals("01:00", dfUS.formatRawCellContents(1*minute, -1, 
"[mm]:ss"));
+       assertEquals("05:00", dfUS.formatRawCellContents(5*minute, -1, 
"[mm]:ss"));
+       assertEquals("20:00", dfUS.formatRawCellContents(20*minute, -1, 
"[mm]:ss"));
+       assertEquals("23:00", dfUS.formatRawCellContents(23*minute, -1, 
"[mm]:ss"));
+       assertEquals("24:00", dfUS.formatRawCellContents(24*minute, -1, 
"[mm]:ss"));
+       assertEquals("26:00", dfUS.formatRawCellContents(26*minute, -1, 
"[mm]:ss"));
+       assertEquals("44:00", dfUS.formatRawCellContents(44*minute, -1, 
"[mm]:ss"));
+       assertEquals("50:00", dfUS.formatRawCellContents(50*minute, -1, 
"[mm]:ss"));
+       assertEquals("59:00", dfUS.formatRawCellContents(59*minute, -1, 
"[mm]:ss"));
+       assertEquals("60:00", dfUS.formatRawCellContents(60*minute, -1, 
"[mm]:ss"));
+       assertEquals("61:00", dfUS.formatRawCellContents(61*minute, -1, 
"[mm]:ss"));
+       assertEquals("119:00", dfUS.formatRawCellContents(119*minute, -1, 
"[mm]:ss"));
+       assertEquals("120:00", dfUS.formatRawCellContents(120*minute, -1, 
"[mm]:ss"));
+       assertEquals("121:00", dfUS.formatRawCellContents(121*minute, -1, 
"[mm]:ss"));
+
+       assertEquals("01", dfUS.formatRawCellContents(1*minute, -1, "[mm]"));
+       assertEquals("05", dfUS.formatRawCellContents(5*minute, -1, "[mm]"));
+       assertEquals("20", dfUS.formatRawCellContents(20*minute, -1, "[mm]"));
+       assertEquals("23", dfUS.formatRawCellContents(23*minute, -1, "[mm]"));
+       assertEquals("24", dfUS.formatRawCellContents(24*minute, -1, "[mm]"));
+       assertEquals("26", dfUS.formatRawCellContents(26*minute, -1, "[mm]"));
+       assertEquals("44", dfUS.formatRawCellContents(44*minute, -1, "[mm]"));
+       assertEquals("50", dfUS.formatRawCellContents(50*minute, -1, "[mm]"));
+       assertEquals("59", dfUS.formatRawCellContents(59*minute, -1, "[mm]"));
+       assertEquals("60", dfUS.formatRawCellContents(60*minute, -1, "[mm]"));
+       assertEquals("61", dfUS.formatRawCellContents(61*minute, -1, "[mm]"));
+       assertEquals("119", dfUS.formatRawCellContents(119*minute, -1, "[mm]"));
+       assertEquals("120", dfUS.formatRawCellContents(120*minute, -1, "[mm]"));
+       assertEquals("121", dfUS.formatRawCellContents(121*minute, -1, "[mm]"));
+
+       double second = 1.0/24.0/60.0/60.0;
+       assertEquals("86400", dfUS.formatRawCellContents(86400*second, -1, 
"[ss]"));
+       assertEquals("01", dfUS.formatRawCellContents(1*second, -1, "[ss]"));
+       assertEquals("05", dfUS.formatRawCellContents(5*second, -1, "[ss]"));
+       assertEquals("20", dfUS.formatRawCellContents(20*second, -1, "[ss]"));
+       assertEquals("23", dfUS.formatRawCellContents(23*second, -1, "[ss]"));
+       assertEquals("24", dfUS.formatRawCellContents(24*second, -1, "[ss]"));
+       assertEquals("26", dfUS.formatRawCellContents(26*second, -1, "[ss]"));
+       assertEquals("44", dfUS.formatRawCellContents(44*second, -1, "[ss]"));
+       assertEquals("50", dfUS.formatRawCellContents(50*second, -1, "[ss]"));
+       assertEquals("59", dfUS.formatRawCellContents(59*second, -1, "[ss]"));
+       assertEquals("60", dfUS.formatRawCellContents(60*second, -1, "[ss]"));
+       assertEquals("61", dfUS.formatRawCellContents(61*second, -1, "[ss]"));
+       assertEquals("119", dfUS.formatRawCellContents(119*second, -1, "[ss]"));
+       assertEquals("120", dfUS.formatRawCellContents(120*second, -1, "[ss]"));
+       assertEquals("121", dfUS.formatRawCellContents(121*second, -1, "[ss]"));
+
+       assertEquals("28:48:00",dfUS.formatRawCellContents(1.2, -1, 
"[h]:mm:ss"));
+
+       assertEquals("57:07.2", dfUS.formatRawCellContents(.123, -1, 
"mm:ss.0;@"));
+       assertEquals("57:41.8", dfUS.formatRawCellContents(.1234, -1, 
"mm:ss.0;@"));
+       assertEquals("57:41.76", dfUS.formatRawCellContents(.1234, -1, 
"mm:ss.00;@"));
+       assertEquals("57:41.760", dfUS.formatRawCellContents(.1234, -1, 
"mm:ss.000;@"));
+       assertEquals("24:00.0", dfUS.formatRawCellContents(123456.6, -1, 
"mm:ss.0"));
     }
-    
+
     public void testDateWindowing() {
        DataFormatter dfUS = new DataFormatter(Locale.US);
        
@@ -234,4 +327,31 @@ public class TestDataFormatter extends T
        assertEquals("1899-12-31 00:00:00", dfUS.formatRawCellContents(0.0, -1, 
"yyyy-mm-dd hh:mm:ss", false));
        assertEquals("1904-01-01 00:00:00", dfUS.formatRawCellContents(0.0, -1, 
"yyyy-mm-dd hh:mm:ss", true));
     }
+
+    public void testScientificNotation() {
+        DataFormatter dfUS = new DataFormatter(Locale.US);
+
+        assertEquals("1.23E+01", dfUS.formatRawCellContents(12.343, -1, 
"0.00E+00"));
+        assertEquals("-1.23E+01", dfUS.formatRawCellContents(-12.343, -1, 
"0.00E+00"));
+        assertEquals("0.00E+00", dfUS.formatRawCellContents(0.0, -1, 
"0.00E+00"));
+     }
+
+    public void testInvalidDate() {
+        DataFormatter df1 = new DataFormatter(Locale.US);
+        assertEquals("-1.0", df1.formatRawCellContents(-1, -1, "mm/dd/yyyy"));
+
+        DataFormatter df2 = new DataFormatter(Locale.US, true);
+        
assertEquals("###############################################################################################################################################################################################################################################################",
+                df2.formatRawCellContents(-1, -1, "mm/dd/yyyy"));
+    }
+
+
+    public void testOther() {
+        DataFormatter dfUS = new DataFormatter(Locale.US, true);
+
+        assertEquals(" 12.34 ", dfUS.formatRawCellContents(12.34, -1, "_-* 
#,##0.00_-;-* #,##0.00_-;_-* \"-\"??_-;_-@_-"));
+        assertEquals("-12.34 ", dfUS.formatRawCellContents(-12.34, -1, "_-* 
#,##0.00_-;-* #,##0.00_-;_-* \"-\"??_-;_-@_-"));
+        assertEquals(" -   ", dfUS.formatRawCellContents(0.0, -1, "_-* 
#,##0.00_-;-* #,##0.00_-;_-* \"-\"??_-;_-@_-"));
+        assertEquals(" $-   ", dfUS.formatRawCellContents(0.0, -1, "_-$* 
#,##0.00_-;-$* #,##0.00_-;_-$* \"-\"??_-;_-@_-"));
+    }
 }



---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to