<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>
// 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) {
* 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 {
/** 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)
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.
*/
*/
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;
}
/**
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;
return getDefaultFormat(cellValue);
}
-
if(DateUtil.isADateFormat(formatIndex,formatStr) &&
DateUtil.isValidExcelDate(cellValue)) {
return createDateFormat(formatStr, cellValue);
if (numPattern.matcher(formatStr).find()) {
return createNumberFormat(formatStr, cellValue);
}
+
+ if (emulateCsv) {
+ return new ConstantStringFormat(cleanFormatForNumber(formatStr));
+ }
// TODO - when does this occur?
return null;
}
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');
}
- 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++) {
}
}
+ 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) {
*/
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;
}
/**
* 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) {
// Not much we can do here
}
}
-
+
/**
* Format class for Excel's SSN format. This class mimics Excel's built-in
* SSN formatting.
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);
+ }
+ }
}
*/
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,
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("");
==================================================================== */
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)
- );
- }
-
- return new StringBuffer(s);
- }
+ 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';
+
+ 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);
+ }
}
"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(
"0.0", "0.000",
"0%", "0.0%",
"[]Foo", "[BLACK]0.00%",
+ "[ms]", "[Mh]",
"", null
};
for(int i=0; i<formats.length; i++) {
/**
* Tests of {@link DataFormatter}
*
- * See {@link TestHSSFDataFormatter} too for
+ * See {@link TestHSSFDataFormatter} too for
* more tests.
*/
public class TestDataFormatter extends TestCase {
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
* gets turned into the first letter of the month
}
/**
- * 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"));
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"));
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);
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_-;_-$* \"-\"??_-;_-@_-"));
+ }
}