Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

extend cell value load to support custom datetime format #703

Merged
merged 11 commits into from
Oct 4, 2020
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
~$*.xlsx
test/Test*.xlsx
test/Test*.xlsm
*.out
*.test
.idea
18 changes: 16 additions & 2 deletions cell.go
Original file line number Diff line number Diff line change
@@ -762,9 +762,23 @@ func (f *File) formattedValue(s int, v string) string {
return v
}
styleSheet := f.stylesReader()
ok := builtInNumFmtFunc[*styleSheet.CellXfs.Xf[s].NumFmtID]
if s >= len(styleSheet.CellXfs.Xf) {
return v
}
numFmtId := *styleSheet.CellXfs.Xf[s].NumFmtID
ok := builtInNumFmtFunc[numFmtId]
if ok != nil {
return ok(*styleSheet.CellXfs.Xf[s].NumFmtID, v)
return ok(v, builtInNumFmt[numFmtId])
}
for _, xlsxFmt := range styleSheet.NumFmts.NumFmt {
if xlsxFmt.NumFmtID == numFmtId {
format := strings.ToLower(xlsxFmt.FormatCode)
if strings.Contains(format, "y") || strings.Contains(format, "m") || strings.Contains(format, "d") || strings.Contains(format, "h") {
return parseTime(v, format)
}

return v
}
}
return v
}
36 changes: 36 additions & 0 deletions cell_test.go
Original file line number Diff line number Diff line change
@@ -111,6 +111,23 @@ func TestSetCellValue(t *testing.T) {
assert.EqualError(t, f.SetCellValue("Sheet1", "A", time.Duration(1e13)), `cannot convert cell "A" to coordinates: invalid cell name "A"`)
}

func TestSetCellValues(t *testing.T) {
f := NewFile()
err := f.SetCellValue("Sheet1", "A1", time.Date(2010, time.December, 31, 0, 0, 0, 0, time.UTC))
assert.NoError(t, err)

v, err := f.GetCellValue("Sheet1", "A1")
assert.NoError(t, err)
assert.Equal(t, v, "12/31/10 12:00")

// test date value lower than min date supported by Excel
err = f.SetCellValue("Sheet1", "A1", time.Date(1600, time.December, 31, 0, 0, 0, 0, time.UTC))
assert.NoError(t, err)

_, err = f.GetCellValue("Sheet1", "A1")
assert.EqualError(t, err, `strconv.ParseFloat: parsing "1600-12-31T00:00:00Z": invalid syntax`)
}

func TestSetCellBool(t *testing.T) {
f := NewFile()
assert.EqualError(t, f.SetCellBool("Sheet1", "A", true), `cannot convert cell "A" to coordinates: invalid cell name "A"`)
@@ -264,3 +281,22 @@ func TestSetCellRichText(t *testing.T) {
// Test set cell rich text with illegal cell coordinates
assert.EqualError(t, f.SetCellRichText("Sheet1", "A", richTextRun), `cannot convert cell "A" to coordinates: invalid cell name "A"`)
}

func TestFormattedValue(t *testing.T) {
f := NewFile()
v := f.formattedValue(0, "43528")
assert.Equal(t, "43528", v)

v = f.formattedValue(15, "43528")
assert.Equal(t, "43528", v)

v = f.formattedValue(1, "43528")
assert.Equal(t, "43528", v)
customNumFmt := "[$-409]MM/DD/YYYY"
_, err := f.NewStyle(&Style{
CustomNumFmt: &customNumFmt,
})
assert.NoError(t, err)
v = f.formattedValue(1, "43528")
assert.Equal(t, "03/04/2019", v)
}
2 changes: 1 addition & 1 deletion crypt_test.go
Original file line number Diff line number Diff line change
@@ -19,5 +19,5 @@ import (
func TestEncrypt(t *testing.T) {
f, err := OpenFile(filepath.Join("test", "encryptSHA1.xlsx"), Options{Password: "password"})
assert.NoError(t, err)
assert.EqualError(t, f.SaveAs(filepath.Join("test", "TestEncrypt.xlsx"), Options{Password: "password"}), "not support encryption currently")
assert.EqualError(t, f.SaveAs(filepath.Join("test", "BadEncrypt.xlsx"), Options{Password: "password"}), "not support encryption currently")
}
2 changes: 1 addition & 1 deletion excelize.go
Original file line number Diff line number Diff line change
@@ -158,7 +158,7 @@ func (f *File) setDefaultTimeStyle(sheet, axis string, format int) error {
}
if s == 0 {
style, _ := f.NewStyle(&Style{NumFmt: format})
_ = f.SetCellStyle(sheet, axis, axis, style)
err = f.SetCellStyle(sheet, axis, axis, style)
}
return err
}
5 changes: 4 additions & 1 deletion excelize_test.go
Original file line number Diff line number Diff line change
@@ -257,7 +257,7 @@ func TestBrokenFile(t *testing.T) {

t.Run("SaveAsEmptyStruct", func(t *testing.T) {
// Test write file with broken file struct with given path.
assert.NoError(t, f.SaveAs(filepath.Join("test", "BrokenFile.SaveAsEmptyStruct.xlsx")))
assert.NoError(t, f.SaveAs(filepath.Join("test", "BadWorkbook.SaveAsEmptyStruct.xlsx")))
})

t.Run("OpenBadWorkbook", func(t *testing.T) {
@@ -1175,6 +1175,9 @@ func TestSetDefaultTimeStyle(t *testing.T) {
f := NewFile()
// Test set default time style on not exists worksheet.
assert.EqualError(t, f.setDefaultTimeStyle("SheetN", "", 0), "sheet SheetN is not exist")

// Test set default time style on invalid cell
assert.EqualError(t, f.setDefaultTimeStyle("Sheet1", "", 42), "cannot convert cell \"\" to coordinates: invalid cell name \"\"")
}

func TestAddVBAProject(t *testing.T) {
2 changes: 1 addition & 1 deletion file.go
Original file line number Diff line number Diff line change
@@ -123,7 +123,7 @@ func (f *File) WriteToBuffer() (*bytes.Buffer, error) {
}
}

if f.options != nil {
if f.options != nil && f.options.Password != "" {
if err := zw.Close(); err != nil {
return buf, err
}
19 changes: 19 additions & 0 deletions rows.go
Original file line number Diff line number Diff line change
@@ -345,6 +345,25 @@ func (xlsx *xlsxC) getValueFrom(f *File, d *xlsxSST) (string, error) {
}
return f.formattedValue(xlsx.S, xlsx.V), nil
default:
// correct numeric values as legacy Excel app
// https://en.wikipedia.org/wiki/Numeric_precision_in_Microsoft_Excel
// In the top figure the fraction 1/9000 in Excel is displayed.
// Although this number has a decimal representation that is an infinite string of ones,
// Excel displays only the leading 15 figures. In the second line, the number one is added to the fraction, and again Excel displays only 15 figures.
const precision = 1000000000000000
if len(xlsx.V) > 16 {
num, err := strconv.ParseFloat(xlsx.V, 64)
if err != nil {
return "", err
}

num = math.Round(num*precision) / precision
val := fmt.Sprintf("%g", num)
if val != xlsx.V {
return f.formattedValue(xlsx.S, val), nil
}
}

return f.formattedValue(xlsx.S, xlsx.V), nil
}
}
37 changes: 36 additions & 1 deletion rows_test.go
Original file line number Diff line number Diff line change
@@ -817,7 +817,7 @@ func TestDuplicateMergeCells(t *testing.T) {
assert.EqualError(t, f.duplicateMergeCells("SheetN", xlsx, 1, 2), "sheet SheetN is not exist")
}

func TestGetValueFrom(t *testing.T) {
func TestGetValueFromInlineStr(t *testing.T) {
c := &xlsxC{T: "inlineStr"}
f := NewFile()
d := &xlsxSST{}
@@ -826,6 +826,20 @@ func TestGetValueFrom(t *testing.T) {
assert.Equal(t, "", val)
}

func TestGetValueFromNumber(t *testing.T) {
c := &xlsxC{T: "n", V: "2.2200000000000002"}
f := NewFile()
d := &xlsxSST{}
val, err := c.getValueFrom(f, d)
assert.NoError(t, err)
assert.Equal(t, "2.22", val)

c = &xlsxC{T: "n", V: "2.220000ddsf0000000002-r"}
val, err = c.getValueFrom(f, d)
assert.NotNil(t, err)
assert.Equal(t, "strconv.ParseFloat: parsing \"2.220000ddsf0000000002-r\": invalid syntax", err.Error())
}

func TestErrSheetNotExistError(t *testing.T) {
err := ErrSheetNotExist{SheetName: "Sheet1"}
assert.EqualValues(t, err.Error(), "sheet Sheet1 is not exist")
@@ -842,6 +856,27 @@ func TestCheckRow(t *testing.T) {
assert.EqualError(t, f.SetCellValue("Sheet1", "A1", false), `cannot convert cell "-" to coordinates: invalid cell name "-"`)
}

func TestNumberFormats(t *testing.T) {
f, err := OpenFile(filepath.Join("test", "Book1.xlsx"))
if !assert.NoError(t, err) {
t.FailNow()
}
cells := make([][]string, 0)
cols, err := f.Cols("Sheet2")
if !assert.NoError(t, err) {
t.FailNow()
}
for cols.Next() {
col, err := cols.Rows()
assert.NoError(t, err)
if err != nil {
break
}
cells = append(cells, col)
}
assert.Equal(t, []string{"", "200", "450", "200", "510", "315", "127", "89", "348", "53", "37"}, cells[3])
}

func BenchmarkRows(b *testing.B) {
f, _ := OpenFile(filepath.Join("test", "Book1.xlsx"))
for i := 0; i < b.N; i++ {
Loading