Skip to content

Commit eed431e

Browse files
authored
This closes qax-os#1219, fixes cell value reading issue, improves performance, and 1904 date system support
- Fix incorrect cell data types casting results when number formatting - Support set cell value on 1904 date system enabled, ref qax-os#1212 - Improve performance for set sheet row and the merging cells, fix performance impact when resolving qax-os#1129
1 parent 773d4af commit eed431e

8 files changed

+63
-40
lines changed

cell.go

+14-9
Original file line numberDiff line numberDiff line change
@@ -211,9 +211,12 @@ func (f *File) setCellTimeFunc(sheet, axis string, value time.Time) error {
211211
ws.Lock()
212212
cellData.S = f.prepareCellStyle(ws, col, row, cellData.S)
213213
ws.Unlock()
214-
214+
date1904, wb := false, f.workbookReader()
215+
if wb != nil && wb.WorkbookPr != nil {
216+
date1904 = wb.WorkbookPr.Date1904
217+
}
215218
var isNum bool
216-
cellData.T, cellData.V, isNum, err = setCellTime(value)
219+
cellData.T, cellData.V, isNum, err = setCellTime(value, date1904)
217220
if err != nil {
218221
return err
219222
}
@@ -225,11 +228,11 @@ func (f *File) setCellTimeFunc(sheet, axis string, value time.Time) error {
225228

226229
// setCellTime prepares cell type and Excel time by given Go time.Time type
227230
// timestamp.
228-
func setCellTime(value time.Time) (t string, b string, isNum bool, err error) {
231+
func setCellTime(value time.Time, date1904 bool) (t string, b string, isNum bool, err error) {
229232
var excelTime float64
230233
_, offset := value.In(value.Location()).Zone()
231234
value = value.Add(time.Duration(offset) * time.Second)
232-
if excelTime, err = timeToExcelTime(value); err != nil {
235+
if excelTime, err = timeToExcelTime(value, date1904); err != nil {
233236
return
234237
}
235238
isNum = excelTime > 0
@@ -1122,8 +1125,7 @@ func (f *File) formattedValue(s int, v string, raw bool) string {
11221125
if wb != nil && wb.WorkbookPr != nil {
11231126
date1904 = wb.WorkbookPr.Date1904
11241127
}
1125-
ok := builtInNumFmtFunc[numFmtID]
1126-
if ok != nil {
1128+
if ok := builtInNumFmtFunc[numFmtID]; ok != nil {
11271129
return ok(v, builtInNumFmt[numFmtID], date1904)
11281130
}
11291131
if styleSheet == nil || styleSheet.NumFmts == nil {
@@ -1140,15 +1142,18 @@ func (f *File) formattedValue(s int, v string, raw bool) string {
11401142
// prepareCellStyle provides a function to prepare style index of cell in
11411143
// worksheet by given column index and style index.
11421144
func (f *File) prepareCellStyle(ws *xlsxWorksheet, col, row, style int) int {
1143-
if ws.Cols != nil && style == 0 {
1145+
if style != 0 {
1146+
return style
1147+
}
1148+
if ws.Cols != nil {
11441149
for _, c := range ws.Cols.Col {
11451150
if c.Min <= col && col <= c.Max && c.Style != 0 {
11461151
return c.Style
11471152
}
11481153
}
11491154
}
1150-
for rowIdx := range ws.SheetData.Row {
1151-
if styleID := ws.SheetData.Row[rowIdx].S; style == 0 && styleID != 0 {
1155+
if row <= len(ws.SheetData.Row) {
1156+
if styleID := ws.SheetData.Row[row-1].S; styleID != 0 {
11521157
return styleID
11531158
}
11541159
}

cell_test.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -192,7 +192,7 @@ func TestSetCellTime(t *testing.T) {
192192
} {
193193
timezone, err := time.LoadLocation(location)
194194
assert.NoError(t, err)
195-
_, b, isNum, err := setCellTime(date.In(timezone))
195+
_, b, isNum, err := setCellTime(date.In(timezone), false)
196196
assert.NoError(t, err)
197197
assert.Equal(t, true, isNum)
198198
assert.Equal(t, expected, b)

date.go

+9-11
Original file line numberDiff line numberDiff line change
@@ -32,21 +32,19 @@ var (
3232
)
3333

3434
// timeToExcelTime provides a function to convert time to Excel time.
35-
func timeToExcelTime(t time.Time) (float64, error) {
36-
// TODO in future this should probably also handle date1904 and like TimeFromExcelTime
37-
38-
if t.Before(excelMinTime1900) {
35+
func timeToExcelTime(t time.Time, date1904 bool) (float64, error) {
36+
date := excelMinTime1900
37+
if date1904 {
38+
date = excel1904Epoc
39+
}
40+
if t.Before(date) {
3941
return 0, nil
4042
}
41-
42-
tt := t
43-
diff := t.Sub(excelMinTime1900)
44-
result := float64(0)
45-
43+
tt, diff, result := t, t.Sub(date), 0.0
4644
for diff >= maxDuration {
4745
result += float64(maxDuration / dayNanoseconds)
4846
tt = tt.Add(-maxDuration)
49-
diff = tt.Sub(excelMinTime1900)
47+
diff = tt.Sub(date)
5048
}
5149

5250
rem := diff % dayNanoseconds
@@ -57,7 +55,7 @@ func timeToExcelTime(t time.Time) (float64, error) {
5755
// Microsoft intentionally included this bug in Excel so that it would remain compatible with the spreadsheet
5856
// program that had the majority market share at the time; Lotus 1-2-3.
5957
// https://www.myonlinetraininghub.com/excel-date-and-time
60-
if t.After(excelBuggyPeriodStart) {
58+
if !date1904 && t.After(excelBuggyPeriodStart) {
6159
result++
6260
}
6361
return result, nil

date_test.go

+23-10
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ var excelTimeInputList = []dateTest{
4040
func TestTimeToExcelTime(t *testing.T) {
4141
for i, test := range trueExpectedDateList {
4242
t.Run(fmt.Sprintf("TestData%d", i+1), func(t *testing.T) {
43-
excelTime, err := timeToExcelTime(test.GoValue)
43+
excelTime, err := timeToExcelTime(test.GoValue, false)
4444
assert.NoError(t, err)
4545
assert.Equalf(t, test.ExcelValue, excelTime,
4646
"Time: %s", test.GoValue.String())
@@ -55,7 +55,7 @@ func TestTimeToExcelTime_Timezone(t *testing.T) {
5555
}
5656
for i, test := range trueExpectedDateList {
5757
t.Run(fmt.Sprintf("TestData%d", i+1), func(t *testing.T) {
58-
_, err := timeToExcelTime(test.GoValue.In(location))
58+
_, err := timeToExcelTime(test.GoValue.In(location), false)
5959
assert.NoError(t, err)
6060
})
6161
}
@@ -71,21 +71,34 @@ func TestTimeFromExcelTime(t *testing.T) {
7171
for min := 0; min < 60; min++ {
7272
for sec := 0; sec < 60; sec++ {
7373
date := time.Date(2021, time.December, 30, hour, min, sec, 0, time.UTC)
74-
excelTime, err := timeToExcelTime(date)
74+
// Test use 1900 date system
75+
excel1900Time, err := timeToExcelTime(date, false)
7576
assert.NoError(t, err)
76-
dateOut := timeFromExcelTime(excelTime, false)
77-
assert.EqualValues(t, hour, dateOut.Hour())
78-
assert.EqualValues(t, min, dateOut.Minute())
79-
assert.EqualValues(t, sec, dateOut.Second())
77+
date1900Out := timeFromExcelTime(excel1900Time, false)
78+
assert.EqualValues(t, hour, date1900Out.Hour())
79+
assert.EqualValues(t, min, date1900Out.Minute())
80+
assert.EqualValues(t, sec, date1900Out.Second())
81+
// Test use 1904 date system
82+
excel1904Time, err := timeToExcelTime(date, true)
83+
assert.NoError(t, err)
84+
date1904Out := timeFromExcelTime(excel1904Time, true)
85+
assert.EqualValues(t, hour, date1904Out.Hour())
86+
assert.EqualValues(t, min, date1904Out.Minute())
87+
assert.EqualValues(t, sec, date1904Out.Second())
8088
}
8189
}
8290
}
8391
}
8492

8593
func TestTimeFromExcelTime_1904(t *testing.T) {
86-
_, _ = shiftJulianToNoon(1, -0.6)
87-
timeFromExcelTime(61, true)
88-
timeFromExcelTime(62, true)
94+
julianDays, julianFraction := shiftJulianToNoon(1, -0.6)
95+
assert.Equal(t, julianDays, 0.0)
96+
assert.Equal(t, julianFraction, 0.9)
97+
julianDays, julianFraction = shiftJulianToNoon(1, 0.1)
98+
assert.Equal(t, julianDays, 1.0)
99+
assert.Equal(t, julianFraction, 0.6)
100+
assert.Equal(t, timeFromExcelTime(61, true), time.Date(1904, time.March, 2, 0, 0, 0, 0, time.UTC))
101+
assert.Equal(t, timeFromExcelTime(62, true), time.Date(1904, time.March, 3, 0, 0, 0, 0, time.UTC))
89102
}
90103

91104
func TestExcelDateToTime(t *testing.T) {

merge.go

+2-5
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,7 @@
1111

1212
package excelize
1313

14-
import (
15-
"strings"
16-
)
14+
import "strings"
1715

1816
// Rect gets merged cell rectangle coordinates sequence.
1917
func (mc *xlsxMergeCell) Rect() ([]int, error) {
@@ -70,8 +68,7 @@ func (f *File) MergeCell(sheet, hCell, vCell string) error {
7068
ws.MergeCells = &xlsxMergeCells{Cells: []*xlsxMergeCell{{Ref: ref, rect: rect}}}
7169
}
7270
ws.MergeCells.Count = len(ws.MergeCells.Cells)
73-
styleID, _ := f.GetCellStyle(sheet, hCell)
74-
return f.SetCellStyle(sheet, hCell, vCell, styleID)
71+
return err
7572
}
7673

7774
// UnmergeCell provides a function to unmerge a given coordinate area.

numfmt.go

+4-3
Original file line numberDiff line numberDiff line change
@@ -939,10 +939,11 @@ func (nf *numberFormat) textHandler() (result string) {
939939
// getValueSectionType returns its applicable number format expression section
940940
// based on the given value.
941941
func (nf *numberFormat) getValueSectionType(value string) (float64, string) {
942-
number, err := strconv.ParseFloat(value, 64)
943-
if err != nil {
944-
return number, nfp.TokenSectionText
942+
isNum, _ := isNumeric(value)
943+
if !isNum {
944+
return 0, nfp.TokenSectionText
945945
}
946+
number, _ := strconv.ParseFloat(value, 64)
946947
if number > 0 {
947948
return number, nfp.TokenSectionPositive
948949
}

rows_test.go

+5
Original file line numberDiff line numberDiff line change
@@ -928,6 +928,11 @@ func TestSetRowStyle(t *testing.T) {
928928
cellStyleID, err := f.GetCellStyle("Sheet1", "B2")
929929
assert.NoError(t, err)
930930
assert.Equal(t, style2, cellStyleID)
931+
// Test cell inheritance rows style
932+
assert.NoError(t, f.SetCellValue("Sheet1", "C1", nil))
933+
cellStyleID, err = f.GetCellStyle("Sheet1", "C1")
934+
assert.NoError(t, err)
935+
assert.Equal(t, style2, cellStyleID)
931936
assert.NoError(t, f.SaveAs(filepath.Join("test", "TestSetRowStyle.xlsx")))
932937
}
933938

stream.go

+5-1
Original file line numberDiff line numberDiff line change
@@ -440,7 +440,11 @@ func (sw *StreamWriter) setCellValFunc(c *xlsxC, val interface{}) (err error) {
440440
c.T, c.V = setCellDuration(val)
441441
case time.Time:
442442
var isNum bool
443-
c.T, c.V, isNum, err = setCellTime(val)
443+
date1904, wb := false, sw.File.workbookReader()
444+
if wb != nil && wb.WorkbookPr != nil {
445+
date1904 = wb.WorkbookPr.Date1904
446+
}
447+
c.T, c.V, isNum, err = setCellTime(val, date1904)
444448
if isNum && c.S == 0 {
445449
style, _ := sw.File.NewStyle(&Style{NumFmt: 22})
446450
c.S = style

0 commit comments

Comments
 (0)