Skip to content

Commit dfdd97c

Browse files
authored
This closes #1199, support apply number format by system date and time options
- Add new options `ShortDateFmtCode`, `LongDateFmtCode` and `LongTimeFmtCode` - Update unit tests
1 parent bbdb83a commit dfdd97c

File tree

7 files changed

+82
-18
lines changed

7 files changed

+82
-18
lines changed

cell.go

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1336,6 +1336,15 @@ func (f *File) getCellStringFunc(sheet, cell string, fn func(x *xlsxWorksheet, c
13361336
return "", nil
13371337
}
13381338

1339+
// applyBuiltInNumFmt provides a function to returns a value after formatted
1340+
// with built-in number format code, or specified sort date format code.
1341+
func (f *File) applyBuiltInNumFmt(c *xlsxC, fmtCode string, numFmtID int, date1904 bool, cellType CellType) string {
1342+
if numFmtID == 14 && f.options != nil && f.options.ShortDateFmtCode != "" {
1343+
fmtCode = f.options.ShortDateFmtCode
1344+
}
1345+
return format(c.V, fmtCode, date1904, cellType, f.options)
1346+
}
1347+
13391348
// formattedValue provides a function to returns a value after formatted. If
13401349
// it is possible to apply a format to the cell value, it will do so, if not
13411350
// then an error will be returned, along with the raw value of the cell.
@@ -1366,14 +1375,14 @@ func (f *File) formattedValue(c *xlsxC, raw bool, cellType CellType) (string, er
13661375
date1904 = wb.WorkbookPr.Date1904
13671376
}
13681377
if fmtCode, ok := builtInNumFmt[numFmtID]; ok {
1369-
return format(c.V, fmtCode, date1904, cellType), err
1378+
return f.applyBuiltInNumFmt(c, fmtCode, numFmtID, date1904, cellType), err
13701379
}
13711380
if styleSheet.NumFmts == nil {
13721381
return c.V, err
13731382
}
13741383
for _, xlsxFmt := range styleSheet.NumFmts.NumFmt {
13751384
if xlsxFmt.NumFmtID == numFmtID {
1376-
return format(c.V, xlsxFmt.FormatCode, date1904, cellType), err
1385+
return format(c.V, xlsxFmt.FormatCode, date1904, cellType, f.options), err
13771386
}
13781387
}
13791388
return c.V, err

cell_test.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -873,7 +873,7 @@ func TestFormattedValue(t *testing.T) {
873873
assert.NoError(t, err)
874874
assert.Equal(t, "311", result)
875875

876-
assert.Equal(t, "0_0", format("0_0", "", false, CellTypeNumber))
876+
assert.Equal(t, "0_0", format("0_0", "", false, CellTypeNumber, nil))
877877

878878
// Test format value with unsupported charset workbook
879879
f.WorkBook = nil
@@ -887,7 +887,7 @@ func TestFormattedValue(t *testing.T) {
887887
_, err = f.formattedValue(&xlsxC{S: 1, V: "43528"}, false, CellTypeNumber)
888888
assert.EqualError(t, err, "XML syntax error on line 1: invalid UTF-8")
889889

890-
assert.Equal(t, "text", format("text", "0", false, CellTypeNumber))
890+
assert.Equal(t, "text", format("text", "0", false, CellTypeNumber, nil))
891891
}
892892

893893
func TestFormattedValueNilXfs(t *testing.T) {

excelize.go

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,12 +79,27 @@ type charsetTranscoderFn func(charset string, input io.Reader) (rdr io.Reader, e
7979
// temporary directory when the file size is over this value, this value
8080
// should be less than or equal to UnzipSizeLimit, the default value is
8181
// 16MB.
82+
//
83+
// ShortDateFmtCode specifies the short date number format code. In the
84+
// spreadsheet applications, date formats display date and time serial numbers
85+
// as date values. Date formats that begin with an asterisk (*) respond to
86+
// changes in regional date and time settings that are specified for the
87+
// operating system. Formats without an asterisk are not affected by operating
88+
// system settings. The ShortDateFmtCode used for specifies apply date formats
89+
// that begin with an asterisk.
90+
//
91+
// LongDateFmtCode specifies the long date number format code.
92+
//
93+
// LongTimeFmtCode specifies the long time number format code.
8294
type Options struct {
8395
MaxCalcIterations uint
8496
Password string
8597
RawCellValue bool
8698
UnzipSizeLimit int64
8799
UnzipXMLSizeLimit int64
100+
ShortDateFmtCode string
101+
LongDateFmtCode string
102+
LongTimeFmtCode string
88103
}
89104

90105
// OpenFile take the name of an spreadsheet file and returns a populated

file.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ import (
2626
// For example:
2727
//
2828
// f := NewFile()
29-
func NewFile() *File {
29+
func NewFile(opts ...Options) *File {
3030
f := newFile()
3131
f.Pkg.Store("_rels/.rels", []byte(xml.Header+templateRels))
3232
f.Pkg.Store(defaultXMLPathDocPropsApp, []byte(xml.Header+templateDocpropsApp))
@@ -49,6 +49,7 @@ func NewFile() *File {
4949
ws, _ := f.workSheetReader("Sheet1")
5050
f.Sheet.Store("xl/worksheets/sheet1.xml", ws)
5151
f.Theme, _ = f.themeReader()
52+
f.options = getOptions(opts...)
5253
return f
5354
}
5455

numfmt.go

Lines changed: 23 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ type languageInfo struct {
3232
// numberFormat directly maps the number format parser runtime required
3333
// fields.
3434
type numberFormat struct {
35+
opts *Options
3536
cellType CellType
3637
section []nfp.Section
3738
t time.Time
@@ -396,9 +397,9 @@ func (nf *numberFormat) prepareNumberic(value string) {
396397
// format provides a function to return a string parse by number format
397398
// expression. If the given number format is not supported, this will return
398399
// the original cell value.
399-
func format(value, numFmt string, date1904 bool, cellType CellType) string {
400+
func format(value, numFmt string, date1904 bool, cellType CellType, opts *Options) string {
400401
p := nfp.NumberFormatParser()
401-
nf := numberFormat{section: p.Parse(numFmt), value: value, date1904: date1904, cellType: cellType}
402+
nf := numberFormat{opts: opts, section: p.Parse(numFmt), value: value, date1904: date1904, cellType: cellType}
402403
nf.number, nf.valueSectionType = nf.getValueSectionType(value)
403404
nf.prepareNumberic(value)
404405
for i, section := range nf.section {
@@ -480,7 +481,7 @@ func (nf *numberFormat) printNumberLiteral(text string) string {
480481
}
481482
for i, token := range nf.section[nf.sectionIdx].Items {
482483
if token.TType == nfp.TokenTypeCurrencyLanguage {
483-
if err := nf.currencyLanguageHandler(i, token); err != nil {
484+
if err, changeNumFmtCode := nf.currencyLanguageHandler(i, token); err != nil || changeNumFmtCode {
484485
return nf.value
485486
}
486487
result += nf.currencyString
@@ -616,7 +617,7 @@ func (nf *numberFormat) dateTimeHandler() string {
616617
nf.t, nf.hours, nf.seconds = timeFromExcelTime(nf.number, nf.date1904), false, false
617618
for i, token := range nf.section[nf.sectionIdx].Items {
618619
if token.TType == nfp.TokenTypeCurrencyLanguage {
619-
if err := nf.currencyLanguageHandler(i, token); err != nil {
620+
if err, changeNumFmtCode := nf.currencyLanguageHandler(i, token); err != nil || changeNumFmtCode {
620621
return nf.value
621622
}
622623
nf.result += nf.currencyString
@@ -687,24 +688,36 @@ func (nf *numberFormat) positiveHandler() string {
687688

688689
// currencyLanguageHandler will be handling currency and language types tokens
689690
// for a number format expression.
690-
func (nf *numberFormat) currencyLanguageHandler(i int, token nfp.Token) (err error) {
691+
func (nf *numberFormat) currencyLanguageHandler(i int, token nfp.Token) (error, bool) {
691692
for _, part := range token.Parts {
692693
if inStrSlice(supportedTokenTypes, part.Token.TType, true) == -1 {
693-
err = ErrUnsupportedNumberFormat
694-
return
694+
return ErrUnsupportedNumberFormat, false
695695
}
696696
if part.Token.TType == nfp.TokenSubTypeLanguageInfo {
697+
if strings.EqualFold(part.Token.TValue, "F800") { // [$-x-sysdate]
698+
if nf.opts != nil && nf.opts.LongDateFmtCode != "" {
699+
nf.value = format(nf.value, nf.opts.LongDateFmtCode, nf.date1904, nf.cellType, nf.opts)
700+
return nil, true
701+
}
702+
part.Token.TValue = "409"
703+
}
704+
if strings.EqualFold(part.Token.TValue, "F400") { // [$-x-systime]
705+
if nf.opts != nil && nf.opts.LongTimeFmtCode != "" {
706+
nf.value = format(nf.value, nf.opts.LongTimeFmtCode, nf.date1904, nf.cellType, nf.opts)
707+
return nil, true
708+
}
709+
part.Token.TValue = "409"
710+
}
697711
if _, ok := supportedLanguageInfo[strings.ToUpper(part.Token.TValue)]; !ok {
698-
err = ErrUnsupportedNumberFormat
699-
return
712+
return ErrUnsupportedNumberFormat, false
700713
}
701714
nf.localCode = strings.ToUpper(part.Token.TValue)
702715
}
703716
if part.Token.TType == nfp.TokenSubTypeCurrencyString {
704717
nf.currencyString = part.Token.TValue
705718
}
706719
}
707-
return
720+
return nil, false
708721
}
709722

710723
// localAmPm return AM/PM name by supported language ID.

numfmt_test.go

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -995,6 +995,8 @@ func TestNumFmt(t *testing.T) {
995995
{"44835.18957170139", "[$-435]mmmmm dd yyyy h:mm AM/PM", "O 01 2022 4:32 AM"},
996996
{"44866.18957170139", "[$-435]mmmmm dd yyyy h:mm AM/PM", "N 01 2022 4:32 AM"},
997997
{"44896.18957170139", "[$-435]mmmmm dd yyyy h:mm AM/PM", "D 01 2022 4:32 AM"},
998+
{"43543.503206018519", "[$-F800]dddd, mmmm dd, yyyy", "Tuesday, March 19, 2019"},
999+
{"43543.503206018519", "[$-F400]h:mm:ss AM/PM", "12:04:37 PM"},
9981000
{"text_", "General", "text_"},
9991001
{"text_", "\"=====\"@@@\"--\"@\"----\"", "=====text_text_text_--text_----"},
10001002
{"0.0450685976001E+21", "0_);[Red]\\(0\\)", "45068597600100000000"},
@@ -1061,9 +1063,22 @@ func TestNumFmt(t *testing.T) {
10611063
{"1234.5678", "0.0xxx00", "1234.5678"},
10621064
{"-1234.5678", "00000.00###;s;", "-1234.5678"},
10631065
} {
1064-
result := format(item[0], item[1], false, CellTypeNumber)
1066+
result := format(item[0], item[1], false, CellTypeNumber, nil)
10651067
assert.Equal(t, item[2], result, item)
10661068
}
1069+
// Test format number with specified date and time format code
1070+
for _, item := range [][]string{
1071+
{"43543.503206018519", "[$-F800]dddd, mmmm dd, yyyy", "2019年3月19日"},
1072+
{"43543.503206018519", "[$-F400]h:mm:ss AM/PM", "12:04:37"},
1073+
} {
1074+
result := format(item[0], item[1], false, CellTypeNumber, &Options{
1075+
ShortDateFmtCode: "yyyy/m/d",
1076+
LongDateFmtCode: "yyyy\"\"M\"\"d\"\"",
1077+
LongTimeFmtCode: "H:mm:ss",
1078+
})
1079+
assert.Equal(t, item[2], result, item)
1080+
}
1081+
// Test format number with string data type cell value
10671082
for _, cellType := range []CellType{CellTypeSharedString, CellTypeInlineString} {
10681083
for _, item := range [][]string{
10691084
{"1234.5678", "General", "1234.5678"},
@@ -1073,10 +1088,12 @@ func TestNumFmt(t *testing.T) {
10731088
{"1234.5678", "0_);[Red]\\(0\\)", "1234.5678"},
10741089
{"1234.5678", "\"text\"@", "text1234.5678"},
10751090
} {
1076-
result := format(item[0], item[1], false, cellType)
1091+
result := format(item[0], item[1], false, cellType, nil)
10771092
assert.Equal(t, item[2], result, item)
10781093
}
10791094
}
10801095
nf := numberFormat{}
1081-
assert.Equal(t, ErrUnsupportedNumberFormat, nf.currencyLanguageHandler(0, nfp.Token{Parts: []nfp.Part{{}}}))
1096+
err, changeNumFmtCode := nf.currencyLanguageHandler(0, nfp.Token{Parts: []nfp.Part{{}}})
1097+
assert.Equal(t, ErrUnsupportedNumberFormat, err)
1098+
assert.False(t, changeNumFmtCode)
10821099
}

rows_test.go

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1117,6 +1117,15 @@ func TestNumberFormats(t *testing.T) {
11171117
assert.Equal(t, expected, result, cell)
11181118
}
11191119
assert.NoError(t, f.SaveAs(filepath.Join("test", "TestNumberFormats.xlsx")))
1120+
1121+
f = NewFile(Options{ShortDateFmtCode: "yyyy/m/d"})
1122+
assert.NoError(t, f.SetCellValue("Sheet1", "A1", 43543.503206018519))
1123+
numFmt14, err := f.NewStyle(&Style{NumFmt: 14})
1124+
assert.NoError(t, err)
1125+
assert.NoError(t, f.SetCellStyle("Sheet1", "A1", "A1", numFmt14))
1126+
result, err := f.GetCellValue("Sheet1", "A1")
1127+
assert.NoError(t, err)
1128+
assert.Equal(t, "2019/3/19", result, "A1")
11201129
}
11211130

11221131
func BenchmarkRows(b *testing.B) {

0 commit comments

Comments
 (0)