forked from p30928647/excelize
This closes #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 #1212 - Improve performance for set sheet row and the merging cells, fix performance impact when resolving #1129
This commit is contained in:
parent
773d4afa32
commit
eed431e0fc
23
cell.go
23
cell.go
|
@ -211,9 +211,12 @@ func (f *File) setCellTimeFunc(sheet, axis string, value time.Time) error {
|
|||
ws.Lock()
|
||||
cellData.S = f.prepareCellStyle(ws, col, row, cellData.S)
|
||||
ws.Unlock()
|
||||
|
||||
date1904, wb := false, f.workbookReader()
|
||||
if wb != nil && wb.WorkbookPr != nil {
|
||||
date1904 = wb.WorkbookPr.Date1904
|
||||
}
|
||||
var isNum bool
|
||||
cellData.T, cellData.V, isNum, err = setCellTime(value)
|
||||
cellData.T, cellData.V, isNum, err = setCellTime(value, date1904)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -225,11 +228,11 @@ func (f *File) setCellTimeFunc(sheet, axis string, value time.Time) error {
|
|||
|
||||
// setCellTime prepares cell type and Excel time by given Go time.Time type
|
||||
// timestamp.
|
||||
func setCellTime(value time.Time) (t string, b string, isNum bool, err error) {
|
||||
func setCellTime(value time.Time, date1904 bool) (t string, b string, isNum bool, err error) {
|
||||
var excelTime float64
|
||||
_, offset := value.In(value.Location()).Zone()
|
||||
value = value.Add(time.Duration(offset) * time.Second)
|
||||
if excelTime, err = timeToExcelTime(value); err != nil {
|
||||
if excelTime, err = timeToExcelTime(value, date1904); err != nil {
|
||||
return
|
||||
}
|
||||
isNum = excelTime > 0
|
||||
|
@ -1122,8 +1125,7 @@ func (f *File) formattedValue(s int, v string, raw bool) string {
|
|||
if wb != nil && wb.WorkbookPr != nil {
|
||||
date1904 = wb.WorkbookPr.Date1904
|
||||
}
|
||||
ok := builtInNumFmtFunc[numFmtID]
|
||||
if ok != nil {
|
||||
if ok := builtInNumFmtFunc[numFmtID]; ok != nil {
|
||||
return ok(v, builtInNumFmt[numFmtID], date1904)
|
||||
}
|
||||
if styleSheet == nil || styleSheet.NumFmts == nil {
|
||||
|
@ -1140,15 +1142,18 @@ func (f *File) formattedValue(s int, v string, raw bool) string {
|
|||
// prepareCellStyle provides a function to prepare style index of cell in
|
||||
// worksheet by given column index and style index.
|
||||
func (f *File) prepareCellStyle(ws *xlsxWorksheet, col, row, style int) int {
|
||||
if ws.Cols != nil && style == 0 {
|
||||
if style != 0 {
|
||||
return style
|
||||
}
|
||||
if ws.Cols != nil {
|
||||
for _, c := range ws.Cols.Col {
|
||||
if c.Min <= col && col <= c.Max && c.Style != 0 {
|
||||
return c.Style
|
||||
}
|
||||
}
|
||||
}
|
||||
for rowIdx := range ws.SheetData.Row {
|
||||
if styleID := ws.SheetData.Row[rowIdx].S; style == 0 && styleID != 0 {
|
||||
if row <= len(ws.SheetData.Row) {
|
||||
if styleID := ws.SheetData.Row[row-1].S; styleID != 0 {
|
||||
return styleID
|
||||
}
|
||||
}
|
||||
|
|
|
@ -192,7 +192,7 @@ func TestSetCellTime(t *testing.T) {
|
|||
} {
|
||||
timezone, err := time.LoadLocation(location)
|
||||
assert.NoError(t, err)
|
||||
_, b, isNum, err := setCellTime(date.In(timezone))
|
||||
_, b, isNum, err := setCellTime(date.In(timezone), false)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, true, isNum)
|
||||
assert.Equal(t, expected, b)
|
||||
|
|
20
date.go
20
date.go
|
@ -32,21 +32,19 @@ var (
|
|||
)
|
||||
|
||||
// timeToExcelTime provides a function to convert time to Excel time.
|
||||
func timeToExcelTime(t time.Time) (float64, error) {
|
||||
// TODO in future this should probably also handle date1904 and like TimeFromExcelTime
|
||||
|
||||
if t.Before(excelMinTime1900) {
|
||||
func timeToExcelTime(t time.Time, date1904 bool) (float64, error) {
|
||||
date := excelMinTime1900
|
||||
if date1904 {
|
||||
date = excel1904Epoc
|
||||
}
|
||||
if t.Before(date) {
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
tt := t
|
||||
diff := t.Sub(excelMinTime1900)
|
||||
result := float64(0)
|
||||
|
||||
tt, diff, result := t, t.Sub(date), 0.0
|
||||
for diff >= maxDuration {
|
||||
result += float64(maxDuration / dayNanoseconds)
|
||||
tt = tt.Add(-maxDuration)
|
||||
diff = tt.Sub(excelMinTime1900)
|
||||
diff = tt.Sub(date)
|
||||
}
|
||||
|
||||
rem := diff % dayNanoseconds
|
||||
|
@ -57,7 +55,7 @@ func timeToExcelTime(t time.Time) (float64, error) {
|
|||
// Microsoft intentionally included this bug in Excel so that it would remain compatible with the spreadsheet
|
||||
// program that had the majority market share at the time; Lotus 1-2-3.
|
||||
// https://www.myonlinetraininghub.com/excel-date-and-time
|
||||
if t.After(excelBuggyPeriodStart) {
|
||||
if !date1904 && t.After(excelBuggyPeriodStart) {
|
||||
result++
|
||||
}
|
||||
return result, nil
|
||||
|
|
33
date_test.go
33
date_test.go
|
@ -40,7 +40,7 @@ var excelTimeInputList = []dateTest{
|
|||
func TestTimeToExcelTime(t *testing.T) {
|
||||
for i, test := range trueExpectedDateList {
|
||||
t.Run(fmt.Sprintf("TestData%d", i+1), func(t *testing.T) {
|
||||
excelTime, err := timeToExcelTime(test.GoValue)
|
||||
excelTime, err := timeToExcelTime(test.GoValue, false)
|
||||
assert.NoError(t, err)
|
||||
assert.Equalf(t, test.ExcelValue, excelTime,
|
||||
"Time: %s", test.GoValue.String())
|
||||
|
@ -55,7 +55,7 @@ func TestTimeToExcelTime_Timezone(t *testing.T) {
|
|||
}
|
||||
for i, test := range trueExpectedDateList {
|
||||
t.Run(fmt.Sprintf("TestData%d", i+1), func(t *testing.T) {
|
||||
_, err := timeToExcelTime(test.GoValue.In(location))
|
||||
_, err := timeToExcelTime(test.GoValue.In(location), false)
|
||||
assert.NoError(t, err)
|
||||
})
|
||||
}
|
||||
|
@ -71,21 +71,34 @@ func TestTimeFromExcelTime(t *testing.T) {
|
|||
for min := 0; min < 60; min++ {
|
||||
for sec := 0; sec < 60; sec++ {
|
||||
date := time.Date(2021, time.December, 30, hour, min, sec, 0, time.UTC)
|
||||
excelTime, err := timeToExcelTime(date)
|
||||
// Test use 1900 date system
|
||||
excel1900Time, err := timeToExcelTime(date, false)
|
||||
assert.NoError(t, err)
|
||||
dateOut := timeFromExcelTime(excelTime, false)
|
||||
assert.EqualValues(t, hour, dateOut.Hour())
|
||||
assert.EqualValues(t, min, dateOut.Minute())
|
||||
assert.EqualValues(t, sec, dateOut.Second())
|
||||
date1900Out := timeFromExcelTime(excel1900Time, false)
|
||||
assert.EqualValues(t, hour, date1900Out.Hour())
|
||||
assert.EqualValues(t, min, date1900Out.Minute())
|
||||
assert.EqualValues(t, sec, date1900Out.Second())
|
||||
// Test use 1904 date system
|
||||
excel1904Time, err := timeToExcelTime(date, true)
|
||||
assert.NoError(t, err)
|
||||
date1904Out := timeFromExcelTime(excel1904Time, true)
|
||||
assert.EqualValues(t, hour, date1904Out.Hour())
|
||||
assert.EqualValues(t, min, date1904Out.Minute())
|
||||
assert.EqualValues(t, sec, date1904Out.Second())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestTimeFromExcelTime_1904(t *testing.T) {
|
||||
_, _ = shiftJulianToNoon(1, -0.6)
|
||||
timeFromExcelTime(61, true)
|
||||
timeFromExcelTime(62, true)
|
||||
julianDays, julianFraction := shiftJulianToNoon(1, -0.6)
|
||||
assert.Equal(t, julianDays, 0.0)
|
||||
assert.Equal(t, julianFraction, 0.9)
|
||||
julianDays, julianFraction = shiftJulianToNoon(1, 0.1)
|
||||
assert.Equal(t, julianDays, 1.0)
|
||||
assert.Equal(t, julianFraction, 0.6)
|
||||
assert.Equal(t, timeFromExcelTime(61, true), time.Date(1904, time.March, 2, 0, 0, 0, 0, time.UTC))
|
||||
assert.Equal(t, timeFromExcelTime(62, true), time.Date(1904, time.March, 3, 0, 0, 0, 0, time.UTC))
|
||||
}
|
||||
|
||||
func TestExcelDateToTime(t *testing.T) {
|
||||
|
|
7
merge.go
7
merge.go
|
@ -11,9 +11,7 @@
|
|||
|
||||
package excelize
|
||||
|
||||
import (
|
||||
"strings"
|
||||
)
|
||||
import "strings"
|
||||
|
||||
// Rect gets merged cell rectangle coordinates sequence.
|
||||
func (mc *xlsxMergeCell) Rect() ([]int, error) {
|
||||
|
@ -70,8 +68,7 @@ func (f *File) MergeCell(sheet, hCell, vCell string) error {
|
|||
ws.MergeCells = &xlsxMergeCells{Cells: []*xlsxMergeCell{{Ref: ref, rect: rect}}}
|
||||
}
|
||||
ws.MergeCells.Count = len(ws.MergeCells.Cells)
|
||||
styleID, _ := f.GetCellStyle(sheet, hCell)
|
||||
return f.SetCellStyle(sheet, hCell, vCell, styleID)
|
||||
return err
|
||||
}
|
||||
|
||||
// UnmergeCell provides a function to unmerge a given coordinate area.
|
||||
|
|
|
@ -939,10 +939,11 @@ func (nf *numberFormat) textHandler() (result string) {
|
|||
// getValueSectionType returns its applicable number format expression section
|
||||
// based on the given value.
|
||||
func (nf *numberFormat) getValueSectionType(value string) (float64, string) {
|
||||
number, err := strconv.ParseFloat(value, 64)
|
||||
if err != nil {
|
||||
return number, nfp.TokenSectionText
|
||||
isNum, _ := isNumeric(value)
|
||||
if !isNum {
|
||||
return 0, nfp.TokenSectionText
|
||||
}
|
||||
number, _ := strconv.ParseFloat(value, 64)
|
||||
if number > 0 {
|
||||
return number, nfp.TokenSectionPositive
|
||||
}
|
||||
|
|
|
@ -928,6 +928,11 @@ func TestSetRowStyle(t *testing.T) {
|
|||
cellStyleID, err := f.GetCellStyle("Sheet1", "B2")
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, style2, cellStyleID)
|
||||
// Test cell inheritance rows style
|
||||
assert.NoError(t, f.SetCellValue("Sheet1", "C1", nil))
|
||||
cellStyleID, err = f.GetCellStyle("Sheet1", "C1")
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, style2, cellStyleID)
|
||||
assert.NoError(t, f.SaveAs(filepath.Join("test", "TestSetRowStyle.xlsx")))
|
||||
}
|
||||
|
||||
|
|
|
@ -440,7 +440,11 @@ func (sw *StreamWriter) setCellValFunc(c *xlsxC, val interface{}) (err error) {
|
|||
c.T, c.V = setCellDuration(val)
|
||||
case time.Time:
|
||||
var isNum bool
|
||||
c.T, c.V, isNum, err = setCellTime(val)
|
||||
date1904, wb := false, sw.File.workbookReader()
|
||||
if wb != nil && wb.WorkbookPr != nil {
|
||||
date1904 = wb.WorkbookPr.Date1904
|
||||
}
|
||||
c.T, c.V, isNum, err = setCellTime(val, date1904)
|
||||
if isNum && c.S == 0 {
|
||||
style, _ := sw.File.NewStyle(&Style{NumFmt: 22})
|
||||
c.S = style
|
||||
|
|
Loading…
Reference in New Issue