Explorar o código

Merge branch 'lunny-master' with a lot of modification and testing.

Geoffrey J. Teale %!s(int64=11) %!d(string=hai) anos
pai
achega
20a3503bc7
Modificáronse 7 ficheiros con 549 adicións e 3 borrados
  1. 99 0
      date.go
  2. 74 0
      date_test.go
  3. 174 2
      lib.go
  4. 185 0
      lib_test.go
  5. 12 0
      style.go
  6. 3 0
      workbook.go
  7. 2 1
      workbook_test.go

+ 99 - 0
date.go

@@ -0,0 +1,99 @@
+package xlsx
+
+import (
+	"math"
+	"time"
+)
+
+const MJD_0 float64 = 2400000.5
+const MJD_JD2000 float64 = 51544.5
+
+func shiftJulianToNoon(julianDays, julianFraction float64) (float64, float64) {
+	switch {
+	case -0.5 < julianFraction && julianFraction < 0.5:
+		julianFraction += 0.5
+	case julianFraction >= 0.5:
+		julianDays += 1
+		julianFraction -= 0.5
+	case julianFraction <= -0.5:
+		julianDays -= 1
+		julianFraction += 1.5
+	}
+	return julianDays, julianFraction
+}
+
+// Return the integer values for hour, minutes, seconds and
+// nanoseconds that comprised a given fraction of a day.
+func fractionOfADay(fraction float64) (hours, minutes, seconds, nanoseconds int) {
+	f := 5184000000000000 * fraction
+	nanoseconds = int(math.Mod(f, 1000000000))
+	f = f / 1000000000
+	seconds = int(math.Mod(f, 3600))
+	f = f / 3600
+	minutes = int(math.Mod(f, 60))
+	f = f / 60
+	hours = int(f)
+	return hours, minutes, seconds, nanoseconds
+}
+
+func julianDateToGregorianTime(part1, part2 float64) time.Time {
+	part1I, part1F := math.Modf(part1)
+	part2I, part2F := math.Modf(part2)
+	julianDays := part1I + part2I
+	julianFraction := part1F + part2F
+	julianDays, julianFraction = shiftJulianToNoon(julianDays, julianFraction)
+	day, month, year := doTheFliegelAndVanFlandernAlgorithm(int(julianDays))
+	hours, minutes, seconds, nanoseconds := fractionOfADay(julianFraction)
+	return time.Date(year, time.Month(month), day, hours, minutes, seconds, nanoseconds, time.Local)
+}
+
+// By this point generations of programmers have repeated the
+// algorithm sent to the editor of "Communications of the ACM" in 1968
+// (published in CACM, volume 11, number 10, October 1968, p.657).
+// None of those programmers seems to have found it necessary to
+// explain the constants or variable names set out by Henry F. Fliegel
+// and Thomas C. Van Flandern.  Maybe one day I'll buy that jounal and
+// expand an explanation here - that day is not today.
+func doTheFliegelAndVanFlandernAlgorithm(jd int) (day, month, year int) {
+	l := jd + 68569
+	n := (4 * l) / 146097
+	l = l - (146097*n+3)/4
+	i := (4000 * (l + 1)) / 1461001
+	l = l - (1461*i)/4 + 31
+	j := (80 * l) / 2447
+	d := l - (2447*j)/80
+	l = j / 11
+	m := j + 2 - (12 * l)
+	y := 100*(n-49) + i + l
+	return d, m, y
+}
+
+
+// Convert an excelTime representation (stored as a floating point number) to a time.Time.
+func TimeFromExcelTime(excelTime float64, date1904 bool) time.Time {
+	var date time.Time
+	var intPart int64 = int64(excelTime)
+	// Excel uses Julian dates prior to March 1st 1900, and
+	// Gregorian thereafter.
+	if intPart <= 61 {
+		const OFFSET1900 = 15018.0
+		const OFFSET1904 = 16480.0
+		var date time.Time
+		if date1904 {
+			date = julianDateToGregorianTime(MJD_0, excelTime + OFFSET1904)
+		} else {
+			date = julianDateToGregorianTime(MJD_0, excelTime + OFFSET1900)
+		}
+		return date
+	}
+	var floatPart float64 = excelTime - float64(intPart)
+	var dayNanoSeconds float64 = 24 * 60 * 60 * 1000 * 1000 * 1000
+	if date1904 {
+		date = time.Date(1904, 1, 1, 1, 0, 0, 0, time.Local)
+	} else {
+		date = time.Date(1899, 12, 30, 1, 0, 0, 0, time.Local)
+	}
+	durationDays := time.Duration(intPart) * time.Hour * 24
+	durationPart := time.Duration(dayNanoSeconds * floatPart)
+	return date.Add(durationDays).Add(durationPart)
+}

+ 74 - 0
date_test.go

@@ -0,0 +1,74 @@
+package xlsx
+
+import (
+	. "gopkg.in/check.v1"
+	"time"
+)
+
+type DateSuite struct{}
+
+var _ = Suite(&DateSuite{})
+
+func (d *DateSuite) TestFractionOfADay(c *C) {
+	var h, m, s, n int
+	h, m, s, n = fractionOfADay(0)
+	c.Assert(h, Equals, 0)
+	c.Assert(m, Equals, 0)
+	c.Assert(s, Equals, 0)
+	c.Assert(n, Equals, 0)
+	h, m, s, n = fractionOfADay(1.0 / 24.0)
+	c.Assert(h, Equals, 1)
+	c.Assert(m, Equals, 0)
+	c.Assert(s, Equals, 0)
+	c.Assert(n, Equals, 0)
+}
+
+
+func (d *DateSuite) TestJulianDateToGregorianTime(c *C) {
+	c.Assert(julianDateToGregorianTime(2400000.5, 51544.0),
+		Equals, time.Date(2000, 1, 1, 0, 0, 0, 0, time.Local))
+	c.Assert(julianDateToGregorianTime(2400000.5, 51544.5),
+		Equals, time.Date(2000, 1, 1, 12, 0, 0, 0, time.Local))
+	c.Assert(julianDateToGregorianTime(2400000.5, 51544.245),
+		Equals, time.Date(2000, 1, 1, 6, 40, 0, 13578, time.Local))
+	c.Assert(julianDateToGregorianTime(2400000.5, 51544.1),
+		Equals, time.Date(2000, 1, 1, 3, 22, 59, 999992456, time.Local))
+	c.Assert(julianDateToGregorianTime(2400000.5, 51544.75),
+		Equals, time.Date(2000, 1, 1, 18, 0, 0, 0, time.Local))
+}
+
+
+func (d *DateSuite) TestTimeFromExcelTime(c *C) {
+	date := TimeFromExcelTime(0, false)
+	c.Assert(date, Equals, time.Date(1899, 12, 30, 0, 0, 0, 0, time.Local))
+	date = TimeFromExcelTime(60, false)
+	c.Assert(date, Equals, time.Date(1900, 2, 28, 0, 0, 0, 0, time.Local))
+	date = TimeFromExcelTime(61, false)
+	c.Assert(date, Equals, time.Date(1900, 3, 1, 0, 0, 0, 0, time.Local))
+	date = TimeFromExcelTime(41275.0, false)
+	c.Assert(date, Equals, time.Date(2013, 1, 1, 0, 0, 0, 0, time.Local))
+}
+
+
+func (d *DateSuite) TestTimeFromExcelTimeWithFractionalPart(c *C) {
+	date := TimeFromExcelTime(0.114583333333333, false)
+	c.Assert(date.Round(time.Second), Equals, time.Date(1899, 12, 30, 2, 45, 0, 0, time.Local))
+
+	date = TimeFromExcelTime(60.1145833333333, false)
+	c.Assert(date.Round(time.Second), Equals, time.Date(1900, 2, 28, 2, 45, 0, 0, time.Local))
+
+	date = TimeFromExcelTime(61.3986111111111, false)
+	c.Assert(date.Round(time.Second), Equals, time.Date(1900, 3, 1, 9, 34, 0, 0, time.Local))
+
+	date = TimeFromExcelTime(37947.75, false)
+	c.Assert(date.Round(time.Second), Equals, time.Date(2003, 11, 22, 18, 0, 0, 0, time.Local))
+
+	date = TimeFromExcelTime(41275.1145833333, false)
+	c.Assert(date.Round(time.Second), Equals, time.Date(2013, 1, 1, 2, 45, 0, 0, time.Local))
+}
+
+func (d *DateSuite) TestTimeFromExcelTimeWith1904Offest(c *C){
+	date1904Offset := TimeFromExcelTime(39813.0, true)
+	c.Assert(date1904Offset, Equals, time.Date(2013, 1, 1, 0, 0, 0, 0, time.Local))
+
+}

+ 174 - 2
lib.go

@@ -6,6 +6,7 @@ import (
 	"errors"
 	"fmt"
 	"io"
+	"math"
 	"strconv"
 	"strings"
 )
@@ -28,6 +29,8 @@ type Cell struct {
 	Value      string
 	styleIndex int
 	styles     *xlsxStyles
+	numFmtRefTable map[int]xlsxNumFmt
+	date1904   bool
 }
 
 // CellInterface defines the public API of the Cell.
@@ -73,6 +76,159 @@ func (c *Cell) GetStyle() *Style {
 	return style
 }
 
+// The number format string is returnable from a cell.
+func (c *Cell) GetNumberFormat() string {
+	var numberFormat string = ""
+	if c.styleIndex > 0 && c.styleIndex <= len(c.styles.CellXfs) {
+		xf := c.styles.CellXfs[c.styleIndex-1]
+		numFmt := c.numFmtRefTable[xf.NumFmtId]
+		numberFormat = numFmt.FormatCode
+	}
+	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 {
@@ -88,6 +244,7 @@ type Sheet struct {
 	MaxCol int
 }
 
+
 // Style is a high level structure intended to provide user access to
 // the contents of Style within an XLSX file.
 type Style struct {
@@ -124,10 +281,12 @@ type Font struct {
 // to the user.
 type File struct {
 	worksheets     map[string]*zip.File
+	numFmtRefTable map[int]xlsxNumFmt
 	referenceTable []string
 	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
@@ -323,13 +482,14 @@ func getValueFromCellData(rawcell xlsxC, reftable []string) string {
 	var data string = rawcell.V
 	if len(data) > 0 {
 		vval := strings.Trim(data, " \t\n\r")
-		if rawcell.T == "s" {
+		switch rawcell.T {
+		case "s":  // Shared String
 			ref, error := strconv.Atoi(vval)
 			if error != nil {
 				panic(error)
 			}
 			value = reftable[ref]
-		} else {
+		default:
 			value = vval
 		}
 	}
@@ -394,6 +554,8 @@ func readRowsFromSheet(Worksheet *xlsxWorksheet, file *File) ([]*Row, int, int)
 			row.Cells[cellX].Value = getValueFromCellData(rawcell, reftable)
 			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
@@ -445,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)
@@ -506,6 +669,14 @@ func readStylesFromZipFile(f *zip.File) (*xlsxStyles, error) {
 	return style, nil
 }
 
+func buildNumFmtRefTable(style *xlsxStyles) map[int]xlsxNumFmt {
+	refTable := make(map[int]xlsxNumFmt)
+	for _, numFmt := range style.NumFmts {
+		refTable[numFmt.NumFmtId] = numFmt
+	}
+	return refTable
+}
+
 // readWorkbookRelationsFromZipFile is an internal helper function to
 // extract a map of relationship ID strings to the name of the
 // worksheet.xml file they refer to.  The resulting map can be used to
@@ -611,6 +782,7 @@ func ReadZipReader(r *zip.Reader) (*File, error) {
 		return nil, err
 	}
 	file.styles = style
+	file.numFmtRefTable = buildNumFmtRefTable(style)
 	sheets, err = readSheetsFromZipFile(workbook, file, sheetXMLMap)
 	if err != nil {
 		return nil, err

+ 185 - 0
lib_test.go

@@ -55,6 +55,191 @@ func (l *LibSuite) TestCreateSheet(c *C) {
 	c.Assert(cellstring, Equals, "Foo")
 }
 
+func (l *LibSuite) TestGetNumberFormat(c *C) {
+	var cell *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: "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")
+
+	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.
 func (l *LibSuite) TestGetStyleWithFonts(c *C) {
 	var cell *Cell

+ 12 - 0
style.go

@@ -17,8 +17,20 @@ type xlsxStyles struct {
 	Borders      []xlsxBorder `xml:"borders>border"`
 	CellStyleXfs []xlsxXf     `xml:"cellStyleXfs>xf"`
 	CellXfs      []xlsxXf     `xml:"cellXfs>xf"`
+	NumFmts      []xlsxNumFmt `xml:numFmts>numFmt"`
 }
 
+// xlsxNumFmt directly maps the numFmt element in the namespace
+// http://schemas.openxmlformats.org/spreadsheetml/2006/main -
+// currently I have not checked it for completeness - it does as much
+// as I need.
+type xlsxNumFmt struct {
+	NumFmtId int `xml:"numFmtId"`
+	FormatCode string `xml:"formatCode"`
+}
+
+
+
 // xlsxFont directly maps the font element in the namespace
 // http://schemas.openxmlformats.org/spreadsheetml/2006/main -
 // currently I have not checked it for completeness - it does as much

+ 3 - 0
workbook.go

@@ -49,6 +49,9 @@ type xlsxFileVersion struct {
 // much as I need.
 type xlsxWorkbookPr struct {
 	DefaultThemeVersion string `xml:"defaultThemeVersion,attr"`
+	BackUpFile bool `xml:"backupFile,attr"`
+	ShowObjects string `xml:"showObjects,attr"`
+	Date1904 bool `xml:"date1904,attr"`
 }
 
 // xlsxBookViews directly maps the bookViews element from the

+ 2 - 1
workbook_test.go

@@ -23,7 +23,7 @@ func (w *WorkbookSuite) TestUnmarshallWorkbookXML(c *C) {
                        lastEdited="4"
                        lowestEdited="4"
                        rupBuild="4506"/>
-          <workbookPr defaultThemeVersion="124226"/>
+          <workbookPr defaultThemeVersion="124226" date1904="true"/>
           <bookViews>
             <workbookView xWindow="120"
                           yWindow="75"
@@ -56,6 +56,7 @@ func (w *WorkbookSuite) TestUnmarshallWorkbookXML(c *C) {
 	c.Assert(workbook.FileVersion.LowestEdited, Equals, "4")
 	c.Assert(workbook.FileVersion.RupBuild, Equals, "4506")
 	c.Assert(workbook.WorkbookPr.DefaultThemeVersion, Equals, "124226")
+	c.Assert(workbook.WorkbookPr.Date1904, Equals, true)
 	c.Assert(workbook.BookViews.WorkBookView, HasLen,  1)
 	workBookView := workbook.BookViews.WorkBookView[0]
 	c.Assert(workBookView.XWindow, Equals, "120")