From c48141c7ebf468552845756643b652fa248e8c7d Mon Sep 17 00:00:00 2001 From: xuri Date: Sat, 30 Apr 2022 09:54:11 +0800 Subject: [PATCH] This closes #1212, init support for 1900 or 1904 date system --- cell.go | 64 ++++++++++++++++++++++++++++-------------------- chart.go | 4 +-- date.go | 4 +-- numfmt.go | 8 +++--- numfmt_test.go | 2 +- picture.go | 4 +-- shape.go | 4 +-- styles.go | 16 ++++++------ workbook.go | 25 +++++++++++++++++-- workbook_test.go | 12 +++++++++ 10 files changed, 94 insertions(+), 49 deletions(-) diff --git a/cell.go b/cell.go index 3c44af45ec5..1d6ed847f6f 100644 --- a/cell.go +++ b/cell.go @@ -109,8 +109,12 @@ func (f *File) GetCellType(sheet, axis string) (CellType, error) { // bool // nil // -// Note that default date format is m/d/yy h:mm of time.Time type value. You can -// set numbers format by SetCellStyle() method. +// Note that default date format is m/d/yy h:mm of time.Time type value. You +// can set numbers format by SetCellStyle() method. If you need to set the +// specialized date in Excel like January 0, 1900 or February 29, 1900, these +// times can not representation in Go language time.Time data type. Please set +// the cell value as number 0 or 60, then create and bind the date-time number +// format style for the cell. func (f *File) SetCellValue(sheet, axis string, value interface{}) error { var err error switch v := value.(type) { @@ -240,7 +244,7 @@ func setCellTime(value time.Time) (t string, b string, isNum bool, err error) { // setCellDuration prepares cell type and value by given Go time.Duration type // time duration. func setCellDuration(value time.Duration) (t string, v string) { - v = strconv.FormatFloat(value.Seconds()/86400.0, 'f', -1, 32) + v = strconv.FormatFloat(value.Seconds()/86400, 'f', -1, 32) return } @@ -752,26 +756,8 @@ func (f *File) SetCellHyperLink(sheet, axis, link, linkType string, opts ...Hype return nil } -// GetCellRichText provides a function to get rich text of cell by given -// worksheet. -func (f *File) GetCellRichText(sheet, cell string) (runs []RichTextRun, err error) { - ws, err := f.workSheetReader(sheet) - if err != nil { - return - } - cellData, _, _, err := f.prepareCell(ws, cell) - if err != nil { - return - } - siIdx, err := strconv.Atoi(cellData.V) - if err != nil || cellData.T != "s" { - return - } - sst := f.sharedStringsReader() - if len(sst.SI) <= siIdx || siIdx < 0 { - return - } - si := sst.SI[siIdx] +// getCellRichText returns rich text of cell by given string item. +func getCellRichText(si *xlsxSI) (runs []RichTextRun) { for _, v := range si.R { run := RichTextRun{ Text: v.T.Val, @@ -803,6 +789,29 @@ func (f *File) GetCellRichText(sheet, cell string) (runs []RichTextRun, err erro return } +// GetCellRichText provides a function to get rich text of cell by given +// worksheet. +func (f *File) GetCellRichText(sheet, cell string) (runs []RichTextRun, err error) { + ws, err := f.workSheetReader(sheet) + if err != nil { + return + } + cellData, _, _, err := f.prepareCell(ws, cell) + if err != nil { + return + } + siIdx, err := strconv.Atoi(cellData.V) + if err != nil || cellData.T != "s" { + return + } + sst := f.sharedStringsReader() + if len(sst.SI) <= siIdx || siIdx < 0 { + return + } + runs = getCellRichText(&sst.SI[siIdx]) + return +} + // newRpr create run properties for the rich text by given font format. func newRpr(fnt *Font) *xlsxRPr { rpr := xlsxRPr{} @@ -1099,17 +1108,20 @@ func (f *File) formattedValue(s int, v string, raw bool) string { if styleSheet.CellXfs.Xf[s].NumFmtID != nil { numFmtID = *styleSheet.CellXfs.Xf[s].NumFmtID } - + date1904, wb := false, f.workbookReader() + if wb != nil && wb.WorkbookPr != nil { + date1904 = wb.WorkbookPr.Date1904 + } ok := builtInNumFmtFunc[numFmtID] if ok != nil { - return ok(v, builtInNumFmt[numFmtID]) + return ok(v, builtInNumFmt[numFmtID], date1904) } if styleSheet == nil || styleSheet.NumFmts == nil { return v } for _, xlsxFmt := range styleSheet.NumFmts.NumFmt { if xlsxFmt.NumFmtID == numFmtID { - return format(v, xlsxFmt.FormatCode) + return format(v, xlsxFmt.FormatCode, date1904) } } return v diff --git a/chart.go b/chart.go index f740a2b219f..7b7162ba965 100644 --- a/chart.go +++ b/chart.go @@ -479,8 +479,8 @@ func parseFormatChartSet(formatSet string) (*formatChart, error) { }, Format: formatPicture{ FPrintsWithSheet: true, - XScale: 1.0, - YScale: 1.0, + XScale: 1, + YScale: 1, }, Legend: formatChartLegend{ Position: "bottom", diff --git a/date.go b/date.go index 83d23ccf783..1574af78446 100644 --- a/date.go +++ b/date.go @@ -36,7 +36,7 @@ func timeToExcelTime(t time.Time) (float64, error) { // TODO in future this should probably also handle date1904 and like TimeFromExcelTime if t.Before(excelMinTime1900) { - return 0.0, nil + return 0, nil } tt := t @@ -58,7 +58,7 @@ func timeToExcelTime(t time.Time) (float64, error) { // 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) { - result += 1.0 + result++ } return result, nil } diff --git a/numfmt.go b/numfmt.go index 6cb7fc74931..5503027a92f 100644 --- a/numfmt.go +++ b/numfmt.go @@ -34,7 +34,7 @@ type numberFormat struct { section []nfp.Section t time.Time sectionIdx int - isNumeric, hours, seconds bool + date1904, isNumeric, hours, seconds bool number float64 ap, afterPoint, beforePoint, localCode, result, value, valueSectionType string } @@ -287,9 +287,9 @@ func (nf *numberFormat) prepareNumberic(value string) { // format provides a function to return a string parse by number format // expression. If the given number format is not supported, this will return // the original cell value. -func format(value, numFmt string) string { +func format(value, numFmt string, date1904 bool) string { p := nfp.NumberFormatParser() - nf := numberFormat{section: p.Parse(numFmt), value: value} + nf := numberFormat{section: p.Parse(numFmt), value: value, date1904: date1904} nf.number, nf.valueSectionType = nf.getValueSectionType(value) nf.prepareNumberic(value) for i, section := range nf.section { @@ -315,7 +315,7 @@ func format(value, numFmt string) string { // positiveHandler will be handling positive selection for a number format // expression. func (nf *numberFormat) positiveHandler() (result string) { - nf.t, nf.hours, nf.seconds = timeFromExcelTime(nf.number, false), false, false + nf.t, nf.hours, nf.seconds = timeFromExcelTime(nf.number, nf.date1904), false, false for i, token := range nf.section[nf.sectionIdx].Items { if inStrSlice(supportedTokenTypes, token.TType, true) == -1 || token.TType == nfp.TokenTypeGeneral { result = nf.value diff --git a/numfmt_test.go b/numfmt_test.go index 7dc3f770b2e..5cdf56bc4a2 100644 --- a/numfmt_test.go +++ b/numfmt_test.go @@ -1005,7 +1005,7 @@ func TestNumFmt(t *testing.T) { {"-8.0450685976001E-21", "0_);[Red]\\(0\\)", "(0)"}, {"-8.04506", "0_);[Red]\\(0\\)", "(8)"}, } { - result := format(item[0], item[1]) + result := format(item[0], item[1], false) assert.Equal(t, item[2], result, item) } } diff --git a/picture.go b/picture.go index 919262c99dc..5e8f6b874df 100644 --- a/picture.go +++ b/picture.go @@ -31,8 +31,8 @@ import ( func parseFormatPictureSet(formatSet string) (*formatPicture, error) { format := formatPicture{ FPrintsWithSheet: true, - XScale: 1.0, - YScale: 1.0, + XScale: 1, + YScale: 1, } err := json.Unmarshal(parseFormatSet(formatSet), &format) return &format, err diff --git a/shape.go b/shape.go index db768673554..6d86f3800db 100644 --- a/shape.go +++ b/shape.go @@ -25,8 +25,8 @@ func parseFormatShapeSet(formatSet string) (*formatShape, error) { Height: 160, Format: formatPicture{ FPrintsWithSheet: true, - XScale: 1.0, - YScale: 1.0, + XScale: 1, + YScale: 1, }, Line: formatLine{Width: 1}, } diff --git a/styles.go b/styles.go index 11f6f750199..6ef7dcbe20e 100644 --- a/styles.go +++ b/styles.go @@ -754,7 +754,7 @@ var currencyNumFmt = map[int]string{ // builtInNumFmtFunc defined the format conversion functions map. Partial format // code doesn't support currently and will return original string. -var builtInNumFmtFunc = map[int]func(v string, format string) string{ +var builtInNumFmtFunc = map[int]func(v, format string, date1904 bool) string{ 0: format, 1: formatToInt, 2: formatToFloat, @@ -847,7 +847,7 @@ var criteriaType = map[string]string{ // formatToInt provides a function to convert original string to integer // format as string type by given built-in number formats code and cell // string. -func formatToInt(v string, format string) string { +func formatToInt(v, format string, date1904 bool) string { f, err := strconv.ParseFloat(v, 64) if err != nil { return v @@ -858,7 +858,7 @@ func formatToInt(v string, format string) string { // formatToFloat provides a function to convert original string to float // format as string type by given built-in number formats code and cell // string. -func formatToFloat(v string, format string) string { +func formatToFloat(v, format string, date1904 bool) string { f, err := strconv.ParseFloat(v, 64) if err != nil { return v @@ -868,7 +868,7 @@ func formatToFloat(v string, format string) string { // formatToA provides a function to convert original string to special format // as string type by given built-in number formats code and cell string. -func formatToA(v string, format string) string { +func formatToA(v, format string, date1904 bool) string { f, err := strconv.ParseFloat(v, 64) if err != nil { return v @@ -883,7 +883,7 @@ func formatToA(v string, format string) string { // formatToB provides a function to convert original string to special format // as string type by given built-in number formats code and cell string. -func formatToB(v string, format string) string { +func formatToB(v, format string, date1904 bool) string { f, err := strconv.ParseFloat(v, 64) if err != nil { return v @@ -896,7 +896,7 @@ func formatToB(v string, format string) string { // formatToC provides a function to convert original string to special format // as string type by given built-in number formats code and cell string. -func formatToC(v string, format string) string { +func formatToC(v, format string, date1904 bool) string { f, err := strconv.ParseFloat(v, 64) if err != nil { return v @@ -907,7 +907,7 @@ func formatToC(v string, format string) string { // formatToD provides a function to convert original string to special format // as string type by given built-in number formats code and cell string. -func formatToD(v string, format string) string { +func formatToD(v, format string, date1904 bool) string { f, err := strconv.ParseFloat(v, 64) if err != nil { return v @@ -918,7 +918,7 @@ func formatToD(v string, format string) string { // formatToE provides a function to convert original string to special format // as string type by given built-in number formats code and cell string. -func formatToE(v string, format string) string { +func formatToE(v, format string, date1904 bool) string { f, err := strconv.ParseFloat(v, 64) if err != nil { return v diff --git a/workbook.go b/workbook.go index c65397b6fc6..417524b1da6 100644 --- a/workbook.go +++ b/workbook.go @@ -33,6 +33,10 @@ type WorkbookPrOptionPtr interface { } type ( + // Date1904 is an option used for WorkbookPrOption, that indicates whether + // to use a 1900 or 1904 date system when converting serial date-times in + // the workbook to dates + Date1904 bool // FilterPrivacy is an option used for WorkbookPrOption FilterPrivacy bool ) @@ -116,6 +120,7 @@ func (f *File) workBookWriter() { // SetWorkbookPrOptions provides a function to sets workbook properties. // // Available options: +// Date1904(bool) // FilterPrivacy(bool) // CodeName(string) func (f *File) SetWorkbookPrOptions(opts ...WorkbookPrOption) error { @@ -131,6 +136,11 @@ func (f *File) SetWorkbookPrOptions(opts ...WorkbookPrOption) error { return nil } +// setWorkbookPrOption implements the WorkbookPrOption interface. +func (o Date1904) setWorkbookPrOption(pr *xlsxWorkbookPr) { + pr.Date1904 = bool(o) +} + // setWorkbookPrOption implements the WorkbookPrOption interface. func (o FilterPrivacy) setWorkbookPrOption(pr *xlsxWorkbookPr) { pr.FilterPrivacy = bool(o) @@ -144,6 +154,7 @@ func (o CodeName) setWorkbookPrOption(pr *xlsxWorkbookPr) { // GetWorkbookPrOptions provides a function to gets workbook properties. // // Available options: +// Date1904(bool) // FilterPrivacy(bool) // CodeName(string) func (f *File) GetWorkbookPrOptions(opts ...WorkbookPrOptionPtr) error { @@ -156,7 +167,17 @@ func (f *File) GetWorkbookPrOptions(opts ...WorkbookPrOptionPtr) error { } // getWorkbookPrOption implements the WorkbookPrOption interface and get the -// filter privacy of thw workbook. +// date1904 of the workbook. +func (o *Date1904) getWorkbookPrOption(pr *xlsxWorkbookPr) { + if pr == nil { + *o = false + return + } + *o = Date1904(pr.Date1904) +} + +// getWorkbookPrOption implements the WorkbookPrOption interface and get the +// filter privacy of the workbook. func (o *FilterPrivacy) getWorkbookPrOption(pr *xlsxWorkbookPr) { if pr == nil { *o = false @@ -166,7 +187,7 @@ func (o *FilterPrivacy) getWorkbookPrOption(pr *xlsxWorkbookPr) { } // getWorkbookPrOption implements the WorkbookPrOption interface and get the -// code name of thw workbook. +// code name of the workbook. func (o *CodeName) getWorkbookPrOption(pr *xlsxWorkbookPr) { if pr == nil { *o = "" diff --git a/workbook_test.go b/workbook_test.go index e31caf26ac1..18b222c00f2 100644 --- a/workbook_test.go +++ b/workbook_test.go @@ -10,6 +10,7 @@ import ( func ExampleFile_SetWorkbookPrOptions() { f := NewFile() if err := f.SetWorkbookPrOptions( + Date1904(false), FilterPrivacy(false), CodeName("code"), ); err != nil { @@ -21,9 +22,13 @@ func ExampleFile_SetWorkbookPrOptions() { func ExampleFile_GetWorkbookPrOptions() { f := NewFile() var ( + date1904 Date1904 filterPrivacy FilterPrivacy codeName CodeName ) + if err := f.GetWorkbookPrOptions(&date1904); err != nil { + fmt.Println(err) + } if err := f.GetWorkbookPrOptions(&filterPrivacy); err != nil { fmt.Println(err) } @@ -31,10 +36,12 @@ func ExampleFile_GetWorkbookPrOptions() { fmt.Println(err) } fmt.Println("Defaults:") + fmt.Printf("- date1904: %t\n", date1904) fmt.Printf("- filterPrivacy: %t\n", filterPrivacy) fmt.Printf("- codeName: %q\n", codeName) // Output: // Defaults: + // - date1904: false // - filterPrivacy: true // - codeName: "" } @@ -42,6 +49,11 @@ func ExampleFile_GetWorkbookPrOptions() { func TestWorkbookPr(t *testing.T) { f := NewFile() wb := f.workbookReader() + wb.WorkbookPr = nil + var date1904 Date1904 + assert.NoError(t, f.GetWorkbookPrOptions(&date1904)) + assert.Equal(t, false, bool(date1904)) + wb.WorkbookPr = nil var codeName CodeName assert.NoError(t, f.GetWorkbookPrOptions(&codeName))