This closes #1212, init support for 1900 or 1904 date system

This commit is contained in:
xuri 2022-04-30 09:54:11 +08:00 committed by GitHub
parent 0f93bd23c9
commit 856ee57c40
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 94 additions and 49 deletions

64
cell.go
View File

@ -109,8 +109,12 @@ func (f *File) GetCellType(sheet, axis string) (CellType, error) {
// bool // bool
// nil // nil
// //
// Note that default date format is m/d/yy h:mm of time.Time type value. You can // Note that default date format is m/d/yy h:mm of time.Time type value. You
// set numbers format by SetCellStyle() method. // 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 { func (f *File) SetCellValue(sheet, axis string, value interface{}) error {
var err error var err error
switch v := value.(type) { 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 // setCellDuration prepares cell type and value by given Go time.Duration type
// time duration. // time duration.
func setCellDuration(value time.Duration) (t string, v string) { 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 return
} }
@ -752,26 +756,8 @@ func (f *File) SetCellHyperLink(sheet, axis, link, linkType string, opts ...Hype
return nil return nil
} }
// GetCellRichText provides a function to get rich text of cell by given // getCellRichText returns rich text of cell by given string item.
// worksheet. func getCellRichText(si *xlsxSI) (runs []RichTextRun) {
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]
for _, v := range si.R { for _, v := range si.R {
run := RichTextRun{ run := RichTextRun{
Text: v.T.Val, Text: v.T.Val,
@ -803,6 +789,29 @@ func (f *File) GetCellRichText(sheet, cell string) (runs []RichTextRun, err erro
return 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. // newRpr create run properties for the rich text by given font format.
func newRpr(fnt *Font) *xlsxRPr { func newRpr(fnt *Font) *xlsxRPr {
rpr := 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 { if styleSheet.CellXfs.Xf[s].NumFmtID != nil {
numFmtID = *styleSheet.CellXfs.Xf[s].NumFmtID numFmtID = *styleSheet.CellXfs.Xf[s].NumFmtID
} }
date1904, wb := false, f.workbookReader()
if wb != nil && wb.WorkbookPr != nil {
date1904 = wb.WorkbookPr.Date1904
}
ok := builtInNumFmtFunc[numFmtID] ok := builtInNumFmtFunc[numFmtID]
if ok != nil { if ok != nil {
return ok(v, builtInNumFmt[numFmtID]) return ok(v, builtInNumFmt[numFmtID], date1904)
} }
if styleSheet == nil || styleSheet.NumFmts == nil { if styleSheet == nil || styleSheet.NumFmts == nil {
return v return v
} }
for _, xlsxFmt := range styleSheet.NumFmts.NumFmt { for _, xlsxFmt := range styleSheet.NumFmts.NumFmt {
if xlsxFmt.NumFmtID == numFmtID { if xlsxFmt.NumFmtID == numFmtID {
return format(v, xlsxFmt.FormatCode) return format(v, xlsxFmt.FormatCode, date1904)
} }
} }
return v return v

View File

@ -479,8 +479,8 @@ func parseFormatChartSet(formatSet string) (*formatChart, error) {
}, },
Format: formatPicture{ Format: formatPicture{
FPrintsWithSheet: true, FPrintsWithSheet: true,
XScale: 1.0, XScale: 1,
YScale: 1.0, YScale: 1,
}, },
Legend: formatChartLegend{ Legend: formatChartLegend{
Position: "bottom", Position: "bottom",

View File

@ -36,7 +36,7 @@ func timeToExcelTime(t time.Time) (float64, error) {
// TODO in future this should probably also handle date1904 and like TimeFromExcelTime // TODO in future this should probably also handle date1904 and like TimeFromExcelTime
if t.Before(excelMinTime1900) { if t.Before(excelMinTime1900) {
return 0.0, nil return 0, nil
} }
tt := t 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. // program that had the majority market share at the time; Lotus 1-2-3.
// https://www.myonlinetraininghub.com/excel-date-and-time // https://www.myonlinetraininghub.com/excel-date-and-time
if t.After(excelBuggyPeriodStart) { if t.After(excelBuggyPeriodStart) {
result += 1.0 result++
} }
return result, nil return result, nil
} }

View File

@ -34,7 +34,7 @@ type numberFormat struct {
section []nfp.Section section []nfp.Section
t time.Time t time.Time
sectionIdx int sectionIdx int
isNumeric, hours, seconds bool date1904, isNumeric, hours, seconds bool
number float64 number float64
ap, afterPoint, beforePoint, localCode, result, value, valueSectionType string 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 // format provides a function to return a string parse by number format
// expression. If the given number format is not supported, this will return // expression. If the given number format is not supported, this will return
// the original cell value. // the original cell value.
func format(value, numFmt string) string { func format(value, numFmt string, date1904 bool) string {
p := nfp.NumberFormatParser() 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.number, nf.valueSectionType = nf.getValueSectionType(value)
nf.prepareNumberic(value) nf.prepareNumberic(value)
for i, section := range nf.section { 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 // positiveHandler will be handling positive selection for a number format
// expression. // expression.
func (nf *numberFormat) positiveHandler() (result string) { 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 { for i, token := range nf.section[nf.sectionIdx].Items {
if inStrSlice(supportedTokenTypes, token.TType, true) == -1 || token.TType == nfp.TokenTypeGeneral { if inStrSlice(supportedTokenTypes, token.TType, true) == -1 || token.TType == nfp.TokenTypeGeneral {
result = nf.value result = nf.value

View File

@ -1005,7 +1005,7 @@ func TestNumFmt(t *testing.T) {
{"-8.0450685976001E-21", "0_);[Red]\\(0\\)", "(0)"}, {"-8.0450685976001E-21", "0_);[Red]\\(0\\)", "(0)"},
{"-8.04506", "0_);[Red]\\(0\\)", "(8)"}, {"-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) assert.Equal(t, item[2], result, item)
} }
} }

View File

@ -31,8 +31,8 @@ import (
func parseFormatPictureSet(formatSet string) (*formatPicture, error) { func parseFormatPictureSet(formatSet string) (*formatPicture, error) {
format := formatPicture{ format := formatPicture{
FPrintsWithSheet: true, FPrintsWithSheet: true,
XScale: 1.0, XScale: 1,
YScale: 1.0, YScale: 1,
} }
err := json.Unmarshal(parseFormatSet(formatSet), &format) err := json.Unmarshal(parseFormatSet(formatSet), &format)
return &format, err return &format, err

View File

@ -25,8 +25,8 @@ func parseFormatShapeSet(formatSet string) (*formatShape, error) {
Height: 160, Height: 160,
Format: formatPicture{ Format: formatPicture{
FPrintsWithSheet: true, FPrintsWithSheet: true,
XScale: 1.0, XScale: 1,
YScale: 1.0, YScale: 1,
}, },
Line: formatLine{Width: 1}, Line: formatLine{Width: 1},
} }

View File

@ -754,7 +754,7 @@ var currencyNumFmt = map[int]string{
// builtInNumFmtFunc defined the format conversion functions map. Partial format // builtInNumFmtFunc defined the format conversion functions map. Partial format
// code doesn't support currently and will return original string. // 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, 0: format,
1: formatToInt, 1: formatToInt,
2: formatToFloat, 2: formatToFloat,
@ -847,7 +847,7 @@ var criteriaType = map[string]string{
// formatToInt provides a function to convert original string to integer // formatToInt provides a function to convert original string to integer
// format as string type by given built-in number formats code and cell // format as string type by given built-in number formats code and cell
// string. // string.
func formatToInt(v string, format string) string { func formatToInt(v, format string, date1904 bool) string {
f, err := strconv.ParseFloat(v, 64) f, err := strconv.ParseFloat(v, 64)
if err != nil { if err != nil {
return v return v
@ -858,7 +858,7 @@ func formatToInt(v string, format string) string {
// formatToFloat provides a function to convert original string to float // formatToFloat provides a function to convert original string to float
// format as string type by given built-in number formats code and cell // format as string type by given built-in number formats code and cell
// string. // string.
func formatToFloat(v string, format string) string { func formatToFloat(v, format string, date1904 bool) string {
f, err := strconv.ParseFloat(v, 64) f, err := strconv.ParseFloat(v, 64)
if err != nil { if err != nil {
return v return v
@ -868,7 +868,7 @@ func formatToFloat(v string, format string) string {
// formatToA provides a function to convert original string to special format // formatToA provides a function to convert original string to special format
// as string type by given built-in number formats code and cell string. // 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) f, err := strconv.ParseFloat(v, 64)
if err != nil { if err != nil {
return v return v
@ -883,7 +883,7 @@ func formatToA(v string, format string) string {
// formatToB provides a function to convert original string to special format // formatToB provides a function to convert original string to special format
// as string type by given built-in number formats code and cell string. // 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) f, err := strconv.ParseFloat(v, 64)
if err != nil { if err != nil {
return v return v
@ -896,7 +896,7 @@ func formatToB(v string, format string) string {
// formatToC provides a function to convert original string to special format // formatToC provides a function to convert original string to special format
// as string type by given built-in number formats code and cell string. // 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) f, err := strconv.ParseFloat(v, 64)
if err != nil { if err != nil {
return v return v
@ -907,7 +907,7 @@ func formatToC(v string, format string) string {
// formatToD provides a function to convert original string to special format // formatToD provides a function to convert original string to special format
// as string type by given built-in number formats code and cell string. // 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) f, err := strconv.ParseFloat(v, 64)
if err != nil { if err != nil {
return v return v
@ -918,7 +918,7 @@ func formatToD(v string, format string) string {
// formatToE provides a function to convert original string to special format // formatToE provides a function to convert original string to special format
// as string type by given built-in number formats code and cell string. // 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) f, err := strconv.ParseFloat(v, 64)
if err != nil { if err != nil {
return v return v

View File

@ -33,6 +33,10 @@ type WorkbookPrOptionPtr interface {
} }
type ( 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 is an option used for WorkbookPrOption
FilterPrivacy bool FilterPrivacy bool
) )
@ -116,6 +120,7 @@ func (f *File) workBookWriter() {
// SetWorkbookPrOptions provides a function to sets workbook properties. // SetWorkbookPrOptions provides a function to sets workbook properties.
// //
// Available options: // Available options:
// Date1904(bool)
// FilterPrivacy(bool) // FilterPrivacy(bool)
// CodeName(string) // CodeName(string)
func (f *File) SetWorkbookPrOptions(opts ...WorkbookPrOption) error { func (f *File) SetWorkbookPrOptions(opts ...WorkbookPrOption) error {
@ -131,6 +136,11 @@ func (f *File) SetWorkbookPrOptions(opts ...WorkbookPrOption) error {
return nil return nil
} }
// setWorkbookPrOption implements the WorkbookPrOption interface.
func (o Date1904) setWorkbookPrOption(pr *xlsxWorkbookPr) {
pr.Date1904 = bool(o)
}
// setWorkbookPrOption implements the WorkbookPrOption interface. // setWorkbookPrOption implements the WorkbookPrOption interface.
func (o FilterPrivacy) setWorkbookPrOption(pr *xlsxWorkbookPr) { func (o FilterPrivacy) setWorkbookPrOption(pr *xlsxWorkbookPr) {
pr.FilterPrivacy = bool(o) pr.FilterPrivacy = bool(o)
@ -144,6 +154,7 @@ func (o CodeName) setWorkbookPrOption(pr *xlsxWorkbookPr) {
// GetWorkbookPrOptions provides a function to gets workbook properties. // GetWorkbookPrOptions provides a function to gets workbook properties.
// //
// Available options: // Available options:
// Date1904(bool)
// FilterPrivacy(bool) // FilterPrivacy(bool)
// CodeName(string) // CodeName(string)
func (f *File) GetWorkbookPrOptions(opts ...WorkbookPrOptionPtr) error { 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 // 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) { func (o *FilterPrivacy) getWorkbookPrOption(pr *xlsxWorkbookPr) {
if pr == nil { if pr == nil {
*o = false *o = false
@ -166,7 +187,7 @@ func (o *FilterPrivacy) getWorkbookPrOption(pr *xlsxWorkbookPr) {
} }
// getWorkbookPrOption implements the WorkbookPrOption interface and get the // getWorkbookPrOption implements the WorkbookPrOption interface and get the
// code name of thw workbook. // code name of the workbook.
func (o *CodeName) getWorkbookPrOption(pr *xlsxWorkbookPr) { func (o *CodeName) getWorkbookPrOption(pr *xlsxWorkbookPr) {
if pr == nil { if pr == nil {
*o = "" *o = ""

View File

@ -10,6 +10,7 @@ import (
func ExampleFile_SetWorkbookPrOptions() { func ExampleFile_SetWorkbookPrOptions() {
f := NewFile() f := NewFile()
if err := f.SetWorkbookPrOptions( if err := f.SetWorkbookPrOptions(
Date1904(false),
FilterPrivacy(false), FilterPrivacy(false),
CodeName("code"), CodeName("code"),
); err != nil { ); err != nil {
@ -21,9 +22,13 @@ func ExampleFile_SetWorkbookPrOptions() {
func ExampleFile_GetWorkbookPrOptions() { func ExampleFile_GetWorkbookPrOptions() {
f := NewFile() f := NewFile()
var ( var (
date1904 Date1904
filterPrivacy FilterPrivacy filterPrivacy FilterPrivacy
codeName CodeName codeName CodeName
) )
if err := f.GetWorkbookPrOptions(&date1904); err != nil {
fmt.Println(err)
}
if err := f.GetWorkbookPrOptions(&filterPrivacy); err != nil { if err := f.GetWorkbookPrOptions(&filterPrivacy); err != nil {
fmt.Println(err) fmt.Println(err)
} }
@ -31,10 +36,12 @@ func ExampleFile_GetWorkbookPrOptions() {
fmt.Println(err) fmt.Println(err)
} }
fmt.Println("Defaults:") fmt.Println("Defaults:")
fmt.Printf("- date1904: %t\n", date1904)
fmt.Printf("- filterPrivacy: %t\n", filterPrivacy) fmt.Printf("- filterPrivacy: %t\n", filterPrivacy)
fmt.Printf("- codeName: %q\n", codeName) fmt.Printf("- codeName: %q\n", codeName)
// Output: // Output:
// Defaults: // Defaults:
// - date1904: false
// - filterPrivacy: true // - filterPrivacy: true
// - codeName: "" // - codeName: ""
} }
@ -42,6 +49,11 @@ func ExampleFile_GetWorkbookPrOptions() {
func TestWorkbookPr(t *testing.T) { func TestWorkbookPr(t *testing.T) {
f := NewFile() f := NewFile()
wb := f.workbookReader() wb := f.workbookReader()
wb.WorkbookPr = nil
var date1904 Date1904
assert.NoError(t, f.GetWorkbookPrOptions(&date1904))
assert.Equal(t, false, bool(date1904))
wb.WorkbookPr = nil wb.WorkbookPr = nil
var codeName CodeName var codeName CodeName
assert.NoError(t, f.GetWorkbookPrOptions(&codeName)) assert.NoError(t, f.GetWorkbookPrOptions(&codeName))