Skip to content

Commit

Permalink
This closes qax-os#1212, init support for 1900 or 1904 date system
Browse files Browse the repository at this point in the history
  • Loading branch information
xuri committed Oct 9, 2022
1 parent 8a77505 commit 33cd855
Show file tree
Hide file tree
Showing 10 changed files with 94 additions and 49 deletions.
64 changes: 38 additions & 26 deletions cell.go
Expand Up @@ -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) {
Expand Down Expand Up @@ -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
}

Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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{}
Expand Down Expand Up @@ -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
Expand Down
4 changes: 2 additions & 2 deletions chart.go
Expand Up @@ -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",
Expand Down
4 changes: 2 additions & 2 deletions date.go
Expand Up @@ -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
Expand All @@ -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
}
Expand Down
8 changes: 4 additions & 4 deletions numfmt.go
Expand Up @@ -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
}
Expand Down Expand Up @@ -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 {
Expand All @@ -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
Expand Down
2 changes: 1 addition & 1 deletion numfmt_test.go
Expand Up @@ -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)
}
}
4 changes: 2 additions & 2 deletions picture.go
Expand Up @@ -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
Expand Down
4 changes: 2 additions & 2 deletions shape.go
Expand Up @@ -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},
}
Expand Down
16 changes: 8 additions & 8 deletions styles.go
Expand Up @@ -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,
Expand Down Expand Up @@ -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
Expand All @@ -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
Expand All @@ -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
Expand All @@ -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
Expand All @@ -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
Expand All @@ -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
Expand All @@ -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
Expand Down
25 changes: 23 additions & 2 deletions workbook.go
Expand Up @@ -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
)
Expand Down Expand Up @@ -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 {
Expand All @@ -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)
Expand All @@ -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 {
Expand All @@ -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
Expand All @@ -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 = ""
Expand Down
12 changes: 12 additions & 0 deletions workbook_test.go
Expand Up @@ -10,6 +10,7 @@ import (
func ExampleFile_SetWorkbookPrOptions() {
f := NewFile()
if err := f.SetWorkbookPrOptions(
Date1904(false),
FilterPrivacy(false),
CodeName("code"),
); err != nil {
Expand All @@ -21,27 +22,38 @@ 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)
}
if err := f.GetWorkbookPrOptions(&codeName); err != nil {
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: ""
}

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))
Expand Down

0 comments on commit 33cd855

Please sign in to comment.