This closes #2004, support apply number format for time and duration cell value

- Add unit tests
- Update dependencies modules
This commit is contained in:
xuri 2024-10-10 22:44:38 +08:00
parent b23e5a26df
commit f1d1a5dc2b
No known key found for this signature in database
GPG Key ID: BA5E5BB1C948EDF7
6 changed files with 71 additions and 17 deletions

View File

@ -144,7 +144,7 @@ func (f *File) SetCellValue(sheet, cell string, value interface{}) error {
if err != nil { if err != nil {
return err return err
} }
err = f.setDefaultTimeStyle(sheet, cell, 21) err = f.setDefaultTimeStyle(sheet, cell, getDurationNumFmt(v))
case time.Time: case time.Time:
err = f.setCellTimeFunc(sheet, cell, v) err = f.setCellTimeFunc(sheet, cell, v)
case bool: case bool:
@ -256,7 +256,7 @@ func (f *File) setCellTimeFunc(sheet, cell string, value time.Time) error {
return err return err
} }
if isNum { if isNum {
_ = f.setDefaultTimeStyle(sheet, cell, 22) _ = f.setDefaultTimeStyle(sheet, cell, getTimeNumFmt(value))
} }
return err return err
} }

View File

@ -305,6 +305,29 @@ func TestSetCellValue(t *testing.T) {
assert.NoError(t, err) assert.NoError(t, err)
assert.Equal(t, expected, val) assert.Equal(t, expected, val)
} }
// Test set cell value with time duration
for val, expected := range map[time.Duration]string{
time.Hour*21 + time.Minute*51 + time.Second*44: "21:51:44",
time.Hour*21 + time.Minute*50: "21:50",
time.Hour*24 + time.Minute*51 + time.Second*44: "24:51:44",
time.Hour*24 + time.Minute*50: "24:50:00",
} {
assert.NoError(t, f.SetCellValue("Sheet1", "A1", val))
val, err := f.GetCellValue("Sheet1", "A1")
assert.NoError(t, err)
assert.Equal(t, expected, val)
}
// Test set cell value with time
for val, expected := range map[time.Time]string{
time.Date(2024, time.October, 1, 0, 0, 0, 0, time.UTC): "Oct-24",
time.Date(2024, time.October, 10, 0, 0, 0, 0, time.UTC): "10-10-24",
time.Date(2024, time.October, 10, 12, 0, 0, 0, time.UTC): "10/10/24 12:00",
} {
assert.NoError(t, f.SetCellValue("Sheet1", "A1", val))
val, err := f.GetCellValue("Sheet1", "A1")
assert.NoError(t, err)
assert.Equal(t, expected, val)
}
} }
func TestSetCellValues(t *testing.T) { func TestSetCellValues(t *testing.T) {
@ -314,7 +337,7 @@ func TestSetCellValues(t *testing.T) {
v, err := f.GetCellValue("Sheet1", "A1") v, err := f.GetCellValue("Sheet1", "A1")
assert.NoError(t, err) assert.NoError(t, err)
assert.Equal(t, v, "12/31/10 00:00") assert.Equal(t, v, "12-31-10")
// Test date value lower than min date supported by Excel // 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)) err = f.SetCellValue("Sheet1", "A1", time.Date(1600, time.December, 31, 0, 0, 0, 0, time.UTC))

28
date.go
View File

@ -214,3 +214,31 @@ func formatYear(y int) int {
} }
return y return y
} }
// getDurationNumFmt returns most simplify numbers format code for time
// duration type cell value by given worksheet name, cell reference and number.
func getDurationNumFmt(d time.Duration) int {
if d >= time.Hour*24 {
return 46
}
// Whole minutes
if d.Minutes() == float64(int(d.Minutes())) {
return 20
}
return 21
}
// getTimeNumFmt returns most simplify numbers format code for time type cell
// value by given worksheet name, cell reference and number.
func getTimeNumFmt(t time.Time) int {
nextMonth := t.AddDate(0, 1, 0)
// Whole months
if t.Day() == 1 && nextMonth.Day() == 1 {
return 17
}
// Whole days
if t.Hour() == 0 && t.Minute() == 0 && t.Second() == 0 && t.Nanosecond() == 0 {
return 14
}
return 22
}

View File

@ -242,15 +242,18 @@ func (f *File) xmlNewDecoder(rdr io.Reader) (ret *xml.Decoder) {
// time.Time type cell value by given worksheet name, cell reference and // time.Time type cell value by given worksheet name, cell reference and
// number format code. // number format code.
func (f *File) setDefaultTimeStyle(sheet, cell string, format int) error { func (f *File) setDefaultTimeStyle(sheet, cell string, format int) error {
s, err := f.GetCellStyle(sheet, cell) styleIdx, err := f.GetCellStyle(sheet, cell)
if err != nil { if err != nil {
return err return err
} }
if s == 0 { if styleIdx == 0 {
style, _ := f.NewStyle(&Style{NumFmt: format}) styleIdx, _ = f.NewStyle(&Style{NumFmt: format})
err = f.SetCellStyle(sheet, cell, cell, style) } else {
style, _ := f.GetStyle(styleIdx)
style.NumFmt = format
styleIdx, _ = f.NewStyle(style)
} }
return err return f.SetCellStyle(sheet, cell, cell, styleIdx)
} }
// workSheetReader provides a function to get the pointer to the structure // workSheetReader provides a function to get the pointer to the structure

6
go.mod
View File

@ -8,10 +8,10 @@ require (
github.com/stretchr/testify v1.8.4 github.com/stretchr/testify v1.8.4
github.com/xuri/efp v0.0.0-20240408161823-9ad904a10d6d github.com/xuri/efp v0.0.0-20240408161823-9ad904a10d6d
github.com/xuri/nfp v0.0.0-20240318013403-ab9948c2c4a7 github.com/xuri/nfp v0.0.0-20240318013403-ab9948c2c4a7
golang.org/x/crypto v0.27.0 golang.org/x/crypto v0.28.0
golang.org/x/image v0.18.0 golang.org/x/image v0.18.0
golang.org/x/net v0.29.0 golang.org/x/net v0.30.0
golang.org/x/text v0.18.0 golang.org/x/text v0.19.0
) )
require ( require (

12
go.sum
View File

@ -15,14 +15,14 @@ github.com/xuri/efp v0.0.0-20240408161823-9ad904a10d6d h1:llb0neMWDQe87IzJLS4Ci7
github.com/xuri/efp v0.0.0-20240408161823-9ad904a10d6d/go.mod h1:ybY/Jr0T0GTCnYjKqmdwxyxn2BQf2RcQIIvex5QldPI= github.com/xuri/efp v0.0.0-20240408161823-9ad904a10d6d/go.mod h1:ybY/Jr0T0GTCnYjKqmdwxyxn2BQf2RcQIIvex5QldPI=
github.com/xuri/nfp v0.0.0-20240318013403-ab9948c2c4a7 h1:hPVCafDV85blFTabnqKgNhDCkJX25eik94Si9cTER4A= github.com/xuri/nfp v0.0.0-20240318013403-ab9948c2c4a7 h1:hPVCafDV85blFTabnqKgNhDCkJX25eik94Si9cTER4A=
github.com/xuri/nfp v0.0.0-20240318013403-ab9948c2c4a7/go.mod h1:WwHg+CVyzlv/TX9xqBFXEZAuxOPxn2k1GNHwG41IIUQ= github.com/xuri/nfp v0.0.0-20240318013403-ab9948c2c4a7/go.mod h1:WwHg+CVyzlv/TX9xqBFXEZAuxOPxn2k1GNHwG41IIUQ=
golang.org/x/crypto v0.27.0 h1:GXm2NjJrPaiv/h1tb2UH8QfgC/hOf/+z0p6PT8o1w7A= golang.org/x/crypto v0.28.0 h1:GBDwsMXVQi34v5CCYUm2jkJvu4cbtru2U4TN2PSyQnw=
golang.org/x/crypto v0.27.0/go.mod h1:1Xngt8kV6Dvbssa53Ziq6Eqn0HqbZi5Z6R0ZpwQzt70= golang.org/x/crypto v0.28.0/go.mod h1:rmgy+3RHxRZMyY0jjAJShp2zgEdOqj2AO7U0pYmeQ7U=
golang.org/x/image v0.18.0 h1:jGzIakQa/ZXI1I0Fxvaa9W7yP25TqT6cHIHn+6CqvSQ= golang.org/x/image v0.18.0 h1:jGzIakQa/ZXI1I0Fxvaa9W7yP25TqT6cHIHn+6CqvSQ=
golang.org/x/image v0.18.0/go.mod h1:4yyo5vMFQjVjUcVk4jEQcU9MGy/rulF5WvUILseCM2E= golang.org/x/image v0.18.0/go.mod h1:4yyo5vMFQjVjUcVk4jEQcU9MGy/rulF5WvUILseCM2E=
golang.org/x/net v0.29.0 h1:5ORfpBpCs4HzDYoodCDBbwHzdR5UrLBZ3sOnUJmFoHo= golang.org/x/net v0.30.0 h1:AcW1SDZMkb8IpzCdQUaIq2sP4sZ4zw+55h6ynffypl4=
golang.org/x/net v0.29.0/go.mod h1:gLkgy8jTGERgjzMic6DS9+SP0ajcu6Xu3Orq/SpETg0= golang.org/x/net v0.30.0/go.mod h1:2wGyMJ5iFasEhkwi13ChkO/t1ECNC4X4eBKkVFyYFlU=
golang.org/x/text v0.18.0 h1:XvMDiNzPAl0jr17s6W9lcaIhGUfUORdGCNsuLmPG224= golang.org/x/text v0.19.0 h1:kTxAhCbGbxhK0IwgSKiMO5awPoDQ0RpfiVYBfK860YM=
golang.org/x/text v0.18.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= golang.org/x/text v0.19.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=