Przeglądaj źródła

Test built-in numeric formats.

Geoffrey J. Teale 11 lat temu
rodzic
commit
18526a4602
2 zmienionych plików z 314 dodań i 5 usunięć
  1. 148 1
      lib.go
  2. 166 4
      lib_test.go

+ 148 - 1
lib.go

@@ -6,6 +6,7 @@ import (
 	"errors"
 	"fmt"
 	"io"
+	"math"
 	"strconv"
 	"strings"
 )
@@ -29,6 +30,7 @@ type Cell struct {
 	styleIndex int
 	styles     *xlsxStyles
 	numFmtRefTable map[int]xlsxNumFmt
+	date1904   bool
 }
 
 // CellInterface defines the public API of the Cell.
@@ -82,9 +84,151 @@ func (c *Cell) GetNumberFormat() string {
 		numFmt := c.numFmtRefTable[xf.NumFmtId]
 		numberFormat = numFmt.FormatCode
 	}
-	return numberFormat
+	return strings.ToLower(numberFormat)
 }
 
+func (c *Cell) formatToTime(format string) string {
+	f, err := strconv.ParseFloat(c.Value, 64)
+	if err != nil {
+		return err.Error()
+	}
+	return TimeFromExcelTime(f, c.date1904).Format(format)
+}
+
+func (c *Cell) formatToFloat(format string) string {
+	f, err := strconv.ParseFloat(c.Value, 64)
+	if err != nil {
+		return err.Error()
+	}
+	return fmt.Sprintf(format, f)
+}
+
+
+func (c *Cell) formatToInt(format string) string {
+	f, err := strconv.ParseFloat(c.Value, 64)
+	if err != nil {
+		return err.Error()
+	}
+	return fmt.Sprintf(format, int(f))
+}
+
+// Return the formatted version of the value.
+func (c *Cell) FormattedValue() string {
+	var numberFormat string = c.GetNumberFormat()
+	switch numberFormat {
+	case "general":
+		return c.Value
+	case "0", "#,##0":
+		return c.formatToInt("%d")
+	case "0.00", "#,##0.00", "@":
+		return c.formatToFloat("%.2f")
+	case "#,##0 ;(#,##0)", "#,##0 ;[red](#,##0)":
+		f, err := strconv.ParseFloat(c.Value, 64)
+		if err != nil {
+			return err.Error()
+		}
+		if f < 0 {
+			i := int(math.Abs(f))
+			return fmt.Sprintf("(%d)", i)
+		}
+		i := int(f)
+		return fmt.Sprintf("%d", i)
+	case  "#,##0.00;(#,##0.00)", "#,##0.00;[red](#,##0.00)":
+		f, err := strconv.ParseFloat(c.Value, 64)
+		if err != nil {
+			return err.Error()
+		}
+		if f < 0 {
+			return fmt.Sprintf("(%.2f)", f)
+		}
+		return fmt.Sprintf("%.2f", f)
+	case "0%":
+		f, err := strconv.ParseFloat(c.Value, 64)
+		if err != nil {
+			return err.Error()
+		}
+		f = f * 100
+		return fmt.Sprintf("%d%%", int(f))
+	case "0.00%":
+		f, err := strconv.ParseFloat(c.Value, 64)
+		if err != nil {
+			return err.Error()
+		}
+		f = f * 100
+		return fmt.Sprintf("%.2f%%", f)
+	case "0.00e+00", "##0.0e+0":
+		return c.formatToFloat("%e")
+	case "mm-dd-yy":
+		return c.formatToTime("01-02-06")
+	case "d-mmm-yy":
+		return c.formatToTime("2-Jan-06")
+	case "d-mmm":
+		return c.formatToTime("2-Jan")
+	case "mmm-yy":
+		return c.formatToTime("Jan-06")
+	case "h:mm am/pm":
+		return c.formatToTime("3:04 pm")
+	case "h:mm:ss am/pm":
+		return c.formatToTime("3:04:05 pm")
+	case "h:mm":
+		return c.formatToTime("15:04")
+	case "h:mm:ss":
+		return c.formatToTime("15:04:05")
+	case "m/d/yy h:mm":
+		return c.formatToTime("1/2/06 15:04")
+	case "mm:ss":
+		return c.formatToTime("04:05")
+	case "[h]:mm:ss":
+		f, err := strconv.ParseFloat(c.Value, 64)
+		if err != nil {
+			return err.Error()
+		}
+		t := TimeFromExcelTime(f, c.date1904)
+		if t.Hour() > 0 {
+			return t.Format("15:04:05")
+		}
+		return t.Format("04:05")
+	case "mmss.0":
+		f, err := strconv.ParseFloat(c.Value, 64)
+		if err != nil {
+			return err.Error()
+		}
+		t := TimeFromExcelTime(f, c.date1904)
+		return fmt.Sprintf("%0d%0d.%d", t.Minute(), t.Second(), t.Nanosecond() / 1000)
+
+	case "yyyy\\-mm\\-dd":
+		return c.formatToTime("2006\\-01\\-02")
+	case "dd/mm/yy":
+		return c.formatToTime("02/01/06")
+	case "hh:mm:ss":
+		return c.formatToTime("15:04:05")
+	case "dd/mm/yy\\ hh:mm":
+		return c.formatToTime("02/01/06\\ 15:04")
+	case "dd/mm/yyyy hh:mm:ss":
+		return c.formatToTime("02/01/2006 15:04:05")
+	case "yy-mm-dd":
+		return c.formatToTime("06-01-02")
+	case "d-mmm-yyyy":
+		return c.formatToTime("2-Jan-2006")
+	case  "m/d/yy":
+		return c.formatToTime("1/2/06")
+	case "m/d/yyyy":
+		return c.formatToTime("1/2/2006")
+	case "dd-mmm-yyyy":
+		return c.formatToTime("02-Jan-2006")
+	case "dd/mm/yyyy":
+		return c.formatToTime("02/01/2006")
+	case "mm/dd/yy hh:mm am/pm":
+		return c.formatToTime("01/02/06 03:04 pm")
+	case "mm/dd/yyyy hh:mm:ss":
+		return c.formatToTime("01/02/2006 15:04:05")
+	case "yyyy-mm-dd hh:mm:ss":
+		return c.formatToTime("2006-01-02 15:04:05")
+	}
+	return c.Value
+}
+
+
 // Row is a high level structure indended to provide user access to a
 // row within a xlsx.Sheet.  An xlsx.Row contains a slice of xlsx.Cell.
 type Row struct {
@@ -142,6 +286,7 @@ type File struct {
 	styles         *xlsxStyles
 	Sheets         []*Sheet          // sheet access by index
 	Sheet          map[string]*Sheet // sheet access by name
+	Date1904       bool
 }
 
 // getRangeFromString is an internal helper function that converts
@@ -410,6 +555,7 @@ func readRowsFromSheet(Worksheet *xlsxWorksheet, file *File) ([]*Row, int, int)
 			row.Cells[cellX].styleIndex = rawcell.S
 			row.Cells[cellX].styles = file.styles
 			row.Cells[cellX].numFmtRefTable = file.numFmtRefTable
+			row.Cells[cellX].date1904 = file.Date1904
 			insertColIndex++
 		}
 		rows[insertRowIndex-minRow] = row
@@ -461,6 +607,7 @@ func readSheetsFromZipFile(f *zip.File, file *File, sheetXMLMap map[string]strin
 	if error != nil {
 		return nil, error
 	}
+	file.Date1904 = workbook.WorkbookPr.Date1904
 	sheetCount = len(workbook.Sheets.Sheet)
 	sheets := make([]*Sheet, sheetCount)
 	sheetChan := make(chan *indexedSheet, sheetCount)

+ 166 - 4
lib_test.go

@@ -66,16 +66,178 @@ func (l *LibSuite) TestGetNumberFormat(c *C) {
 	cellXfs = make([]xlsxXf, 1)
 	cellXfs[0] = xlsxXf{NumFmtId: 1}
 
-	numFmt = xlsxNumFmt{NumFmtId: 1, FormatCode: "DD/MM/YY"}
 	numFmts = make([]xlsxNumFmt, 1)
-	numFmts[0] = numFmt
 	numFmtRefTable = make(map[int]xlsxNumFmt)
+
+	xStyles = &xlsxStyles{NumFmts: numFmts, CellXfs: cellXfs}
+
+	cell = &Cell{Value: "123.123", numFmtRefTable: numFmtRefTable, styleIndex: 1, styles: xStyles}
+
+	numFmt = xlsxNumFmt{NumFmtId: 1, FormatCode: "dd/mm/yy"}
+	numFmts[0] = numFmt
 	numFmtRefTable[1] = numFmt
+	c.Assert(cell.GetNumberFormat(), Equals, "dd/mm/yy")
+}
+
+// We can return a string representation of the formatted data
+func (l *LibSuite) TestFormattedValue(c *C) {
+	var cell, earlyCell, negativeCell, smallCell *Cell
+	var cellXfs []xlsxXf
+	var numFmt xlsxNumFmt
+	var numFmts []xlsxNumFmt
+	var xStyles *xlsxStyles
+	var numFmtRefTable map[int]xlsxNumFmt
+
+	cellXfs = make([]xlsxXf, 1)
+	cellXfs[0] = xlsxXf{NumFmtId: 1}
+
+	numFmts = make([]xlsxNumFmt, 1)
+	numFmtRefTable = make(map[int]xlsxNumFmt)
 
 	xStyles = &xlsxStyles{NumFmts: numFmts, CellXfs: cellXfs}
+	cell = &Cell{Value: "37947.7500001", numFmtRefTable: numFmtRefTable, styleIndex: 1, styles: xStyles}
+	negativeCell = &Cell{Value: "-37947.7500001", numFmtRefTable: numFmtRefTable, styleIndex: 1, styles: xStyles}
+	smallCell = &Cell{Value: "0.007", numFmtRefTable: numFmtRefTable, styleIndex: 1, styles: xStyles}
+	earlyCell = &Cell{Value: "2.1", numFmtRefTable: numFmtRefTable, styleIndex: 1, styles: xStyles}
+	setCode := func(code string) {
+		numFmt = xlsxNumFmt{NumFmtId: 1, FormatCode: code}
+		numFmts[0] = numFmt
+		numFmtRefTable[1] = numFmt
+	}
+
+	setCode("general")
+	c.Assert(cell.FormattedValue(), Equals, "37947.7500001")
+	c.Assert(negativeCell.FormattedValue(), Equals, "-37947.7500001")
+
+	setCode("0")
+	c.Assert(cell.FormattedValue(), Equals, "37947")
+
+	setCode("#,##0") // For the time being we're not doing this
+			 // comma formatting, so it'll fall back to
+			 // the related non-comma form.
+	c.Assert(cell.FormattedValue(), Equals, "37947")
+
+	setCode("0.00")
+	c.Assert(cell.FormattedValue(), Equals, "37947.75")
+
+	setCode("#,##0.00") // For the time being we're not doing this
+	                    // comma formatting, so it'll fall back to
+	                    // the related non-comma form.
+	c.Assert(cell.FormattedValue(), Equals, "37947.75")
+
+	setCode("#,##0 ;(#,##0)")
+	c.Assert(cell.FormattedValue(), Equals, "37947")
+	c.Assert(negativeCell.FormattedValue(), Equals, "(37947)")
+
+	setCode("#,##0 ;[red](#,##0)")
+	c.Assert(cell.FormattedValue(), Equals, "37947")
+	c.Assert(negativeCell.FormattedValue(), Equals, "(37947)")
+
+	setCode("0%")
+	c.Assert(cell.FormattedValue(), Equals, "3794775%")
+
+	setCode("0.00%")
+	c.Assert(cell.FormattedValue(), Equals, "3794775.00%")
+
+	setCode("0.00e+00")
+	c.Assert(cell.FormattedValue(), Equals, "3.794775e+04")
+
+	setCode("##0.0e+0") // This is wrong, but we'll use it for now.
+	c.Assert(cell.FormattedValue(), Equals, "3.794775e+04")
+
+	setCode("mm-dd-yy")
+	c.Assert(cell.FormattedValue(), Equals, "11-22-03")
+
+	setCode("d-mmm-yy")
+	c.Assert(cell.FormattedValue(), Equals, "22-Nov-03")
+	c.Assert(earlyCell.FormattedValue(), Equals, "1-Jan-00")
+
+	setCode("d-mmm")
+	c.Assert(cell.FormattedValue(), Equals, "22-Nov")
+	c.Assert(earlyCell.FormattedValue(), Equals, "1-Jan")
+
+	setCode("mmm-yy")
+	c.Assert(cell.FormattedValue(), Equals, "Nov-03")
+
+	setCode("h:mm am/pm")
+	c.Assert(cell.FormattedValue(), Equals, "6:00 pm")
+	c.Assert(smallCell.FormattedValue(), Equals, "12:14 am")
+
+	setCode("h:mm:ss am/pm")
+	c.Assert(cell.FormattedValue(), Equals, "6:00:00 pm")
+	c.Assert(smallCell.FormattedValue(), Equals, "12:14:47 am")
+
+	setCode("h:mm")
+	c.Assert(cell.FormattedValue(), Equals, "18:00")
+	c.Assert(smallCell.FormattedValue(), Equals, "00:14")
+
+	setCode("h:mm:ss")
+	c.Assert(cell.FormattedValue(), Equals, "18:00:00")
+	 // This is wrong, but there's no eary way aroud it in Go right now, AFAICT.
+	c.Assert(smallCell.FormattedValue(), Equals, "00:14:47")
+
+	setCode("m/d/yy h:mm")
+	c.Assert(cell.FormattedValue(), Equals, "11/22/03 18:00")
+	c.Assert(smallCell.FormattedValue(), Equals, "12/30/99 00:14") // Note, that's 1899
+	c.Assert(earlyCell.FormattedValue(), Equals, "1/1/00 02:24")   // and 1900
+
+	setCode("mm:ss")
+	c.Assert(cell.FormattedValue(), Equals, "00:00")
+	c.Assert(smallCell.FormattedValue(), Equals, "14:47")
+
+	setCode("[h]:mm:ss")
+	c.Assert(cell.FormattedValue(), Equals, "18:00:00")
+	c.Assert(smallCell.FormattedValue(), Equals, "14:47")
+
+	setCode("mmss.0") // I'm not sure about these.
+	c.Assert(cell.FormattedValue(), Equals, "00.8640")
+	c.Assert(smallCell.FormattedValue(), Equals, "1447.999997")
+
+	setCode("yyyy\\-mm\\-dd")
+	c.Assert(cell.FormattedValue(), Equals, "2003\\-11\\-22")
+
+	setCode("dd/mm/yy")
+	c.Assert(cell.FormattedValue(), Equals, "22/11/03")
+	c.Assert(earlyCell.FormattedValue(), Equals, "01/01/00")
+
+	setCode("hh:mm:ss")
+	c.Assert(cell.FormattedValue(), Equals, "18:00:00")
+	c.Assert(smallCell.FormattedValue(), Equals, "00:14:47")
+
+	setCode("dd/mm/yy\\ hh:mm")
+	c.Assert(cell.FormattedValue(), Equals, "22/11/03\\ 18:00")
+
+	setCode("yy-mm-dd")
+	c.Assert(cell.FormattedValue(), Equals, "03-11-22")
+
+	setCode("d-mmm-yyyy")
+	c.Assert(cell.FormattedValue(), Equals, "22-Nov-2003")
+	c.Assert(earlyCell.FormattedValue(), Equals, "1-Jan-1900")
+
+	setCode("m/d/yy")
+	c.Assert(cell.FormattedValue(), Equals, "11/22/03")
+	c.Assert(earlyCell.FormattedValue(), Equals, "1/1/00")
+
+	setCode("m/d/yyyy")
+	c.Assert(cell.FormattedValue(), Equals, "11/22/2003")
+	c.Assert(earlyCell.FormattedValue(), Equals, "1/1/1900")
+
+	setCode("dd-mmm-yyyy")
+	c.Assert(cell.FormattedValue(), Equals, "22-Nov-2003")
+
+	setCode("dd/mm/yyyy")
+	c.Assert(cell.FormattedValue(), Equals, "22/11/2003")
+
+	setCode("mm/dd/yy hh:mm am/pm")
+	c.Assert(cell.FormattedValue(), Equals, "11/22/03 06:00 pm")
+
+	setCode("mm/dd/yyyy hh:mm:ss")
+	c.Assert(cell.FormattedValue(), Equals, "11/22/2003 18:00:00")
+	c.Assert(smallCell.FormattedValue(), Equals, "12/30/1899 00:14:47")
 
-	cell = &Cell{Value: "123", numFmtRefTable: numFmtRefTable, styleIndex: 1, styles: xStyles}
-	c.Assert(cell.GetNumberFormat(), Equals, "DD/MM/YY")
+	setCode("yyyy-mm-dd hh:mm:ss")
+	c.Assert(cell.FormattedValue(), Equals, "2003-11-22 18:00:00")
+	c.Assert(smallCell.FormattedValue(), Equals, "1899-12-30 00:14:47")
 }
 
 // Test that GetStyle correctly converts the xlsxStyle.Fonts.