improved DataFormatter, see Bugzilla 50841

git-svn-id: https://svn.apache.org/repos/asf/poi/trunk@1083173 13f79535-47bb-0310-9956-ffa450edef68
This commit is contained in:
Yegor Kozlov 2011-03-19 12:42:01 +00:00
parent 8c44bb85d1
commit 1ba16fb3b9
7 changed files with 521 additions and 186 deletions

View File

@ -34,6 +34,7 @@
<changes> <changes>
<release version="3.8-beta2" date="2011-??-??"> <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">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">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> <action dev="poi-developers" type="fix">49219 - ExternalNameRecord support for DDE Link entries without an operation</action>

View File

@ -389,7 +389,7 @@ public class ToCSV {
// formatted String encapsulating the cells contents. // formatted String encapsulating the cells contents.
this.workbook = WorkbookFactory.create(fis); this.workbook = WorkbookFactory.create(fis);
this.evaluator = this.workbook.getCreationHelper().createFormulaEvaluator(); this.evaluator = this.workbook.getCreationHelper().createFormulaEvaluator();
this.formatter = new DataFormatter(); this.formatter = new DataFormatter(true);
} }
finally { finally {
if(fis != null) { if(fis != null) {

View File

@ -63,7 +63,26 @@ import java.text.*;
* default format will only be used when a Format cannot be created from the * default format will only be used when a Format cannot be created from the
* cell's data format string. * 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 James May (james dot may at fmr dot com)
* @author Robert Kish
* *
*/ */
public class DataFormatter { public class DataFormatter {
@ -90,6 +109,17 @@ public class DataFormatter {
"(\\[MAGENTA\\])|(\\[RED\\])|(\\[WHITE\\])|(\\[YELLOW\\])|" + "(\\[MAGENTA\\])|(\\[RED\\])|(\\[WHITE\\])|(\\[YELLOW\\])|" +
"(\\[COLOR\\s*\\d\\])|(\\[COLOR\\s*[0-5]\\d\\])", Pattern.CASE_INSENSITIVE); "(\\[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. * The decimal symbols of the locale used for formatting values.
*/ */
@ -115,11 +145,33 @@ public class DataFormatter {
*/ */
private final Map<String,Format> formats; private final Map<String,Format> formats;
private boolean emulateCsv = false;
/** /**
* Creates a formatter using the {@link Locale#getDefault() default locale}. * Creates a formatter using the {@link Locale#getDefault() default locale}.
*/ */
public DataFormatter() { 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(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,18 +229,33 @@ public class DataFormatter {
return getFormat(cell.getNumericCellValue(), formatIndex, formatStr); return getFormat(cell.getNumericCellValue(), formatIndex, formatStr);
} }
private Format getFormat(double cellValue, int formatIndex, String formatStr) { private Format getFormat(double cellValue, int formatIndex, String formatStrIn) {
String formatStr = formatStrIn;
// Excel supports positive/negative/zero, but java // Excel supports positive/negative/zero, but java
// doesn't, so we need to do it specially // doesn't, so we need to do it specially
if(formatStr.indexOf(';') != formatStr.lastIndexOf(';')) { final int firstAt = formatStr.indexOf(';');
int lastAt = formatStr.lastIndexOf(';'); final int lastAt = formatStr.lastIndexOf(';');
String zeroFormat = formatStr.substring(lastAt+1); // p and p;n are ok by default. p;n;z and p;n;z;s need to be fixed.
String normalFormat = formatStr.substring(0,lastAt); if (firstAt != -1 && firstAt != lastAt) {
final int secondAt = formatStr.indexOf(';', firstAt + 1);
if (secondAt == lastAt) { // p;n;z
if (cellValue == 0.0) { if (cellValue == 0.0) {
formatStr = zeroFormat; formatStr = formatStr.substring(lastAt + 1);
} else { } else {
formatStr = normalFormat; 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
@ -197,7 +264,7 @@ public class DataFormatter {
return format; return format;
} }
if ("General".equalsIgnoreCase(formatStr) || "@".equals(formatStr)) { if ("General".equalsIgnoreCase(formatStr) || "@".equals(formatStr)) {
if (DataFormatter.isWholeNumber(cellValue)) { if (isWholeNumber(cellValue)) {
return generalWholeNumFormat; return generalWholeNumFormat;
} }
return generalDecimalNumFormat; return generalDecimalNumFormat;
@ -261,7 +328,6 @@ public class DataFormatter {
return getDefaultFormat(cellValue); return getDefaultFormat(cellValue);
} }
if(DateUtil.isADateFormat(formatIndex,formatStr) && if(DateUtil.isADateFormat(formatIndex,formatStr) &&
DateUtil.isValidExcelDate(cellValue)) { DateUtil.isValidExcelDate(cellValue)) {
return createDateFormat(formatStr, cellValue); return createDateFormat(formatStr, cellValue);
@ -269,6 +335,10 @@ public class DataFormatter {
if (numPattern.matcher(formatStr).find()) { if (numPattern.matcher(formatStr).find()) {
return createNumberFormat(formatStr, cellValue); return createNumberFormat(formatStr, cellValue);
} }
if (emulateCsv) {
return new ConstantStringFormat(cleanFormatForNumber(formatStr));
}
// TODO - when does this occur? // TODO - when does this occur?
return null; return null;
} }
@ -311,9 +381,33 @@ public class DataFormatter {
char[] chars = formatStr.toCharArray(); char[] chars = formatStr.toCharArray();
boolean mIsMonth = true; boolean mIsMonth = true;
List<Integer> ms = new ArrayList<Integer>(); List<Integer> ms = new ArrayList<Integer>();
boolean isElapsed = false;
for(int j=0; j<chars.length; j++) { for(int j=0; j<chars.length; j++) {
char c = chars[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') { 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; mIsMonth = false;
if (hasAmPm) { if (hasAmPm) {
sb.append('h'); sb.append('h');
@ -373,9 +467,38 @@ public class DataFormatter {
} }
private Format createNumberFormat(String formatStr, double cellValue) { private String cleanFormatForNumber(String formatStr) {
StringBuffer sb = new StringBuffer(formatStr); StringBuffer sb = new StringBuffer(formatStr);
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 "_", // If they requested spacers, with "_",
// remove those as we don't do spacing // remove those as we don't do spacing
// If they requested full-column-width // If they requested full-column-width
@ -386,7 +509,7 @@ public class DataFormatter {
if (i > 0 && sb.charAt((i - 1)) == '\\') { if (i > 0 && sb.charAt((i - 1)) == '\\') {
// It's escaped, don't worry // It's escaped, don't worry
continue; continue;
} else { }
if (i < sb.length() - 1) { if (i < sb.length() - 1) {
// Remove the character we're supposed // Remove the character we're supposed
// to match the space of / pad to the // to match the space of / pad to the
@ -415,8 +538,14 @@ public class DataFormatter {
} }
} }
return sb.toString();
}
private Format createNumberFormat(String formatStr, double cellValue) {
final String format = cleanFormatForNumber(formatStr);
try { try {
DecimalFormat df = new DecimalFormat(sb.toString(), decimalSymbols); DecimalFormat df = new DecimalFormat(format, decimalSymbols);
setExcelStyleRoundingMode(df); setExcelStyleRoundingMode(df);
return df; return df;
} catch(IllegalArgumentException iae) { } catch(IllegalArgumentException iae) {
@ -522,8 +651,8 @@ public class DataFormatter {
*/ */
public String formatRawCellContents(double value, int formatIndex, String formatString, boolean use1904Windowing) { public String formatRawCellContents(double value, int formatIndex, String formatString, boolean use1904Windowing) {
// Is it a date? // Is it a date?
if(DateUtil.isADateFormat(formatIndex,formatString) && if(DateUtil.isADateFormat(formatIndex,formatString)) {
DateUtil.isValidExcelDate(value)) { if(DateUtil.isValidExcelDate(value)) {
Format dateFormat = getFormat(value, formatIndex, formatString); Format dateFormat = getFormat(value, formatIndex, formatString);
if(dateFormat instanceof ExcelStyleDateFormatter) { if(dateFormat instanceof ExcelStyleDateFormatter) {
// Hint about the raw excel value // Hint about the raw excel value
@ -531,13 +660,23 @@ public class DataFormatter {
} }
Date d = DateUtil.getJavaDate(value, use1904Windowing); Date d = DateUtil.getJavaDate(value, use1904Windowing);
return performDateFormatting(d, dateFormat); return performDateFormatting(d, dateFormat);
}
// RK: Invalid dates are 255 #s.
if (emulateCsv) {
return invalidDateTimeString;
}
} }
// else Number // else Number
Format numberFormat = getFormat(value, formatIndex, formatString); Format numberFormat = getFormat(value, formatIndex, formatString);
if (numberFormat == null) { if (numberFormat == null) {
return String.valueOf(value); return String.valueOf(value);
} }
return numberFormat.format(new Double(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. * on Java 1.5.
*/ */
public static void setExcelStyleRoundingMode(DecimalFormat format) { 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 { try {
Method srm = format.getClass().getMethod("setRoundingMode", RoundingMode.class); Method srm = format.getClass().getMethod("setRoundingMode", RoundingMode.class);
srm.invoke(format, RoundingMode.HALF_UP); srm.invoke(format, roundingMode);
} catch(NoSuchMethodException e) { } catch(NoSuchMethodException e) {
// Java 1.5 // Java 1.5
} catch(IllegalAccessException iae) { } catch(IllegalAccessException iae) {
@ -794,4 +945,31 @@ public class DataFormatter {
return df.parseObject(source, pos); 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);
}
}
} }

View File

@ -52,7 +52,9 @@ public class DateUtil {
*/ */
private static final Pattern date_ptrn1 = Pattern.compile("^\\[\\$\\-.*?\\]"); private static final Pattern date_ptrn1 = Pattern.compile("^\\[\\$\\-.*?\\]");
private static final Pattern date_ptrn2 = Pattern.compile("^\\[[a-zA-Z]+\\]"); 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, * Given a Date, converts it into a double representing its internal Excel representation,
@ -259,6 +261,11 @@ public class DateUtil {
} }
fs = sb.toString(); 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 // If it starts with [$-...], then could be a date, but
// who knows what that starting bit is all about // who knows what that starting bit is all about
fs = date_ptrn1.matcher(fs).replaceAll(""); fs = date_ptrn1.matcher(fs).replaceAll("");

View File

@ -16,8 +16,6 @@
==================================================================== */ ==================================================================== */
package org.apache.poi.ss.usermodel; package org.apache.poi.ss.usermodel;
import java.util.regex.Pattern;
import java.util.regex.Matcher;
import java.util.*; import java.util.*;
import java.math.RoundingMode; import java.math.RoundingMode;
import java.text.*; import java.text.*;
@ -39,12 +37,20 @@ public class ExcelStyleDateFormatter extends SimpleDateFormat {
public static final char MM_BRACKET_SYMBOL = '\ue013'; public static final char MM_BRACKET_SYMBOL = '\ue013';
public static final char S_BRACKET_SYMBOL = '\ue014'; public static final char S_BRACKET_SYMBOL = '\ue014';
public static final char SS_BRACKET_SYMBOL = '\ue015'; public static final char SS_BRACKET_SYMBOL = '\ue015';
public static final char L_BRACKET_SYMBOL = '\ue016';
public static final char LL_BRACKET_SYMBOL = '\ue017';
private DecimalFormat format1digit = new DecimalFormat("0"); private DecimalFormat format1digit = new DecimalFormat("0");
private DecimalFormat format2digits = new DecimalFormat("00"); private DecimalFormat format2digits = new DecimalFormat("00");
private DecimalFormat format3digit = new DecimalFormat("0");
private DecimalFormat format4digits = new DecimalFormat("00");
{ {
DataFormatter.setExcelStyleRoundingMode(format1digit); DataFormatter.setExcelStyleRoundingMode(format1digit, RoundingMode.DOWN);
DataFormatter.setExcelStyleRoundingMode(format2digits); DataFormatter.setExcelStyleRoundingMode(format2digits, RoundingMode.DOWN);
DataFormatter.setExcelStyleRoundingMode(format3digit);
DataFormatter.setExcelStyleRoundingMode(format4digits);
} }
private double dateToBeFormatted = 0.0; private double dateToBeFormatted = 0.0;
@ -52,13 +58,16 @@ public class ExcelStyleDateFormatter extends SimpleDateFormat {
public ExcelStyleDateFormatter() { public ExcelStyleDateFormatter() {
super(); super();
} }
public ExcelStyleDateFormatter(String pattern) { public ExcelStyleDateFormatter(String pattern) {
super(processFormatPattern(pattern)); super(processFormatPattern(pattern));
} }
public ExcelStyleDateFormatter(String pattern, public ExcelStyleDateFormatter(String pattern,
DateFormatSymbols formatSymbols) { DateFormatSymbols formatSymbols) {
super(processFormatPattern(pattern), formatSymbols); super(processFormatPattern(pattern), formatSymbols);
} }
public ExcelStyleDateFormatter(String pattern, Locale locale) { public ExcelStyleDateFormatter(String pattern, Locale locale) {
super(processFormatPattern(pattern), locale); super(processFormatPattern(pattern), locale);
} }
@ -68,14 +77,17 @@ public class ExcelStyleDateFormatter extends SimpleDateFormat {
* with our detection sequences * with our detection sequences
*/ */
private static String processFormatPattern(String f) { private static String processFormatPattern(String f) {
f = f.replaceAll("MMMMM", MMMMM_START_SYMBOL + "MMM" + MMMMM_TRUNCATE_SYMBOL); String t = f.replaceAll("MMMMM", MMMMM_START_SYMBOL + "MMM" + MMMMM_TRUNCATE_SYMBOL);
f = f.replaceAll("\\[H\\]", String.valueOf(H_BRACKET_SYMBOL)); t = t.replaceAll("\\[H\\]", String.valueOf(H_BRACKET_SYMBOL));
f = f.replaceAll("\\[HH\\]", String.valueOf(HH_BRACKET_SYMBOL)); t = t.replaceAll("\\[HH\\]", String.valueOf(HH_BRACKET_SYMBOL));
f = f.replaceAll("\\[m\\]", String.valueOf(M_BRACKET_SYMBOL)); t = t.replaceAll("\\[m\\]", String.valueOf(M_BRACKET_SYMBOL));
f = f.replaceAll("\\[mm\\]", String.valueOf(MM_BRACKET_SYMBOL)); t = t.replaceAll("\\[mm\\]", String.valueOf(MM_BRACKET_SYMBOL));
f = f.replaceAll("\\[s\\]", String.valueOf(S_BRACKET_SYMBOL)); t = t.replaceAll("\\[s\\]", String.valueOf(S_BRACKET_SYMBOL));
f = f.replaceAll("\\[ss\\]", String.valueOf(SS_BRACKET_SYMBOL)); t = t.replaceAll("\\[ss\\]", String.valueOf(SS_BRACKET_SYMBOL));
return f; 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;
} }
/** /**
@ -104,7 +116,8 @@ public class ExcelStyleDateFormatter extends SimpleDateFormat {
if (s.indexOf(H_BRACKET_SYMBOL) != -1 || if (s.indexOf(H_BRACKET_SYMBOL) != -1 ||
s.indexOf(HH_BRACKET_SYMBOL) != -1) { s.indexOf(HH_BRACKET_SYMBOL) != -1) {
double hours = dateToBeFormatted * 24; float hours = (float) dateToBeFormatted * 24;
s = s.replaceAll( s = s.replaceAll(
String.valueOf(H_BRACKET_SYMBOL), String.valueOf(H_BRACKET_SYMBOL),
format1digit.format(hours) format1digit.format(hours)
@ -117,7 +130,7 @@ public class ExcelStyleDateFormatter extends SimpleDateFormat {
if (s.indexOf(M_BRACKET_SYMBOL) != -1 || if (s.indexOf(M_BRACKET_SYMBOL) != -1 ||
s.indexOf(MM_BRACKET_SYMBOL) != -1) { s.indexOf(MM_BRACKET_SYMBOL) != -1) {
double minutes = dateToBeFormatted * 24 * 60; float minutes = (float) dateToBeFormatted * 24 * 60;
s = s.replaceAll( s = s.replaceAll(
String.valueOf(M_BRACKET_SYMBOL), String.valueOf(M_BRACKET_SYMBOL),
format1digit.format(minutes) format1digit.format(minutes)
@ -129,7 +142,7 @@ public class ExcelStyleDateFormatter extends SimpleDateFormat {
} }
if (s.indexOf(S_BRACKET_SYMBOL) != -1 || if (s.indexOf(S_BRACKET_SYMBOL) != -1 ||
s.indexOf(SS_BRACKET_SYMBOL) != -1) { s.indexOf(SS_BRACKET_SYMBOL) != -1) {
double seconds = dateToBeFormatted * 24 * 60 * 60; float seconds = (float) (dateToBeFormatted * 24.0 * 60.0 * 60.0);
s = s.replaceAll( s = s.replaceAll(
String.valueOf(S_BRACKET_SYMBOL), String.valueOf(S_BRACKET_SYMBOL),
format1digit.format(seconds) format1digit.format(seconds)
@ -140,6 +153,20 @@ public class ExcelStyleDateFormatter extends SimpleDateFormat {
); );
} }
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); return new StringBuffer(s);
} }
} }

View File

@ -271,7 +271,8 @@ public final class TestHSSFDateUtil extends TestCase {
"mm/dd HH:MM PM", "mm/dd HH:MM pm", "mm/dd HH:MM PM", "mm/dd HH:MM pm",
"m/d/yy h:mm AM/PM", "m/d/yy h:mm AM/PM",
"hh:mm:ss", "hh:mm:ss.0", "mm:ss.0", "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++) { for(int i=0; i<formats.length; i++) {
assertTrue( assertTrue(
@ -286,6 +287,7 @@ public final class TestHSSFDateUtil extends TestCase {
"0.0", "0.000", "0.0", "0.000",
"0%", "0.0%", "0%", "0.0%",
"[]Foo", "[BLACK]0.00%", "[]Foo", "[BLACK]0.00%",
"[ms]", "[Mh]",
"", null "", null
}; };
for(int i=0; i<formats.length; i++) { for(int i=0; i<formats.length; i++) {

View File

@ -179,6 +179,38 @@ public class TestDataFormatter extends TestCase {
assertEquals("12.34", dfUS.formatRawCellContents(12.343, -1, "*-##.##")); assertEquals("12.34", dfUS.formatRawCellContents(12.343, -1, "*-##.##"));
} }
/**
* 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 * Test that the special Excel month format MMMMM
* gets turned into the first letter of the month * gets turned into the first letter of the month
@ -222,9 +254,70 @@ public class TestDataFormatter extends TestCase {
assertEquals("44:00", dfUS.formatRawCellContents(44*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("50:00", dfUS.formatRawCellContents(50*hour, -1, "[hh]:mm"));
assertEquals("30:00", dfUS.formatRawCellContents(0.5*hour, -1, "[mm]:ss")); assertEquals("01", dfUS.formatRawCellContents(1*hour, -1, "[hh]"));
assertEquals("60:00", dfUS.formatRawCellContents(1*hour, -1, "[mm]:ss")); assertEquals("05", dfUS.formatRawCellContents(5*hour, -1, "[hh]"));
assertEquals("120:00", dfUS.formatRawCellContents(2*hour, -1, "[mm]:ss")); 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() { public void testDateWindowing() {
@ -234,4 +327,31 @@ public class TestDataFormatter extends TestCase {
assertEquals("1899-12-31 00:00:00", dfUS.formatRawCellContents(0.0, -1, "yyyy-mm-dd hh:mm:ss", false)); 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)); 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_-;_-$* \"-\"??_-;_-@_-"));
}
} }