forked from p30928647/excelize
This closes #1199, support apply number format by system date and time options
- Add new options `ShortDateFmtCode`, `LongDateFmtCode` and `LongTimeFmtCode` - Update unit tests
This commit is contained in:
parent
bbdb83abf0
commit
dfdd97c0a7
13
cell.go
13
cell.go
|
@ -1336,6 +1336,15 @@ func (f *File) getCellStringFunc(sheet, cell string, fn func(x *xlsxWorksheet, c
|
||||||
return "", nil
|
return "", nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// applyBuiltInNumFmt provides a function to returns a value after formatted
|
||||||
|
// with built-in number format code, or specified sort date format code.
|
||||||
|
func (f *File) applyBuiltInNumFmt(c *xlsxC, fmtCode string, numFmtID int, date1904 bool, cellType CellType) string {
|
||||||
|
if numFmtID == 14 && f.options != nil && f.options.ShortDateFmtCode != "" {
|
||||||
|
fmtCode = f.options.ShortDateFmtCode
|
||||||
|
}
|
||||||
|
return format(c.V, fmtCode, date1904, cellType, f.options)
|
||||||
|
}
|
||||||
|
|
||||||
// formattedValue provides a function to returns a value after formatted. If
|
// formattedValue provides a function to returns a value after formatted. If
|
||||||
// it is possible to apply a format to the cell value, it will do so, if not
|
// it is possible to apply a format to the cell value, it will do so, if not
|
||||||
// then an error will be returned, along with the raw value of the cell.
|
// 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
|
||||||
date1904 = wb.WorkbookPr.Date1904
|
date1904 = wb.WorkbookPr.Date1904
|
||||||
}
|
}
|
||||||
if fmtCode, ok := builtInNumFmt[numFmtID]; ok {
|
if fmtCode, ok := builtInNumFmt[numFmtID]; ok {
|
||||||
return format(c.V, fmtCode, date1904, cellType), err
|
return f.applyBuiltInNumFmt(c, fmtCode, numFmtID, date1904, cellType), err
|
||||||
}
|
}
|
||||||
if styleSheet.NumFmts == nil {
|
if styleSheet.NumFmts == nil {
|
||||||
return c.V, err
|
return c.V, err
|
||||||
}
|
}
|
||||||
for _, xlsxFmt := range styleSheet.NumFmts.NumFmt {
|
for _, xlsxFmt := range styleSheet.NumFmts.NumFmt {
|
||||||
if xlsxFmt.NumFmtID == numFmtID {
|
if xlsxFmt.NumFmtID == numFmtID {
|
||||||
return format(c.V, xlsxFmt.FormatCode, date1904, cellType), err
|
return format(c.V, xlsxFmt.FormatCode, date1904, cellType, f.options), err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return c.V, err
|
return c.V, err
|
||||||
|
|
|
@ -873,7 +873,7 @@ func TestFormattedValue(t *testing.T) {
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.Equal(t, "311", result)
|
assert.Equal(t, "311", result)
|
||||||
|
|
||||||
assert.Equal(t, "0_0", format("0_0", "", false, CellTypeNumber))
|
assert.Equal(t, "0_0", format("0_0", "", false, CellTypeNumber, nil))
|
||||||
|
|
||||||
// Test format value with unsupported charset workbook
|
// Test format value with unsupported charset workbook
|
||||||
f.WorkBook = nil
|
f.WorkBook = nil
|
||||||
|
@ -887,7 +887,7 @@ func TestFormattedValue(t *testing.T) {
|
||||||
_, err = f.formattedValue(&xlsxC{S: 1, V: "43528"}, false, CellTypeNumber)
|
_, err = f.formattedValue(&xlsxC{S: 1, V: "43528"}, false, CellTypeNumber)
|
||||||
assert.EqualError(t, err, "XML syntax error on line 1: invalid UTF-8")
|
assert.EqualError(t, err, "XML syntax error on line 1: invalid UTF-8")
|
||||||
|
|
||||||
assert.Equal(t, "text", format("text", "0", false, CellTypeNumber))
|
assert.Equal(t, "text", format("text", "0", false, CellTypeNumber, nil))
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestFormattedValueNilXfs(t *testing.T) {
|
func TestFormattedValueNilXfs(t *testing.T) {
|
||||||
|
|
15
excelize.go
15
excelize.go
|
@ -79,12 +79,27 @@ type charsetTranscoderFn func(charset string, input io.Reader) (rdr io.Reader, e
|
||||||
// temporary directory when the file size is over this value, this value
|
// temporary directory when the file size is over this value, this value
|
||||||
// should be less than or equal to UnzipSizeLimit, the default value is
|
// should be less than or equal to UnzipSizeLimit, the default value is
|
||||||
// 16MB.
|
// 16MB.
|
||||||
|
//
|
||||||
|
// ShortDateFmtCode specifies the short date number format code. In the
|
||||||
|
// spreadsheet applications, date formats display date and time serial numbers
|
||||||
|
// as date values. Date formats that begin with an asterisk (*) respond to
|
||||||
|
// changes in regional date and time settings that are specified for the
|
||||||
|
// operating system. Formats without an asterisk are not affected by operating
|
||||||
|
// system settings. The ShortDateFmtCode used for specifies apply date formats
|
||||||
|
// that begin with an asterisk.
|
||||||
|
//
|
||||||
|
// LongDateFmtCode specifies the long date number format code.
|
||||||
|
//
|
||||||
|
// LongTimeFmtCode specifies the long time number format code.
|
||||||
type Options struct {
|
type Options struct {
|
||||||
MaxCalcIterations uint
|
MaxCalcIterations uint
|
||||||
Password string
|
Password string
|
||||||
RawCellValue bool
|
RawCellValue bool
|
||||||
UnzipSizeLimit int64
|
UnzipSizeLimit int64
|
||||||
UnzipXMLSizeLimit int64
|
UnzipXMLSizeLimit int64
|
||||||
|
ShortDateFmtCode string
|
||||||
|
LongDateFmtCode string
|
||||||
|
LongTimeFmtCode string
|
||||||
}
|
}
|
||||||
|
|
||||||
// OpenFile take the name of an spreadsheet file and returns a populated
|
// OpenFile take the name of an spreadsheet file and returns a populated
|
||||||
|
|
3
file.go
3
file.go
|
@ -26,7 +26,7 @@ import (
|
||||||
// For example:
|
// For example:
|
||||||
//
|
//
|
||||||
// f := NewFile()
|
// f := NewFile()
|
||||||
func NewFile() *File {
|
func NewFile(opts ...Options) *File {
|
||||||
f := newFile()
|
f := newFile()
|
||||||
f.Pkg.Store("_rels/.rels", []byte(xml.Header+templateRels))
|
f.Pkg.Store("_rels/.rels", []byte(xml.Header+templateRels))
|
||||||
f.Pkg.Store(defaultXMLPathDocPropsApp, []byte(xml.Header+templateDocpropsApp))
|
f.Pkg.Store(defaultXMLPathDocPropsApp, []byte(xml.Header+templateDocpropsApp))
|
||||||
|
@ -49,6 +49,7 @@ func NewFile() *File {
|
||||||
ws, _ := f.workSheetReader("Sheet1")
|
ws, _ := f.workSheetReader("Sheet1")
|
||||||
f.Sheet.Store("xl/worksheets/sheet1.xml", ws)
|
f.Sheet.Store("xl/worksheets/sheet1.xml", ws)
|
||||||
f.Theme, _ = f.themeReader()
|
f.Theme, _ = f.themeReader()
|
||||||
|
f.options = getOptions(opts...)
|
||||||
return f
|
return f
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
33
numfmt.go
33
numfmt.go
|
@ -32,6 +32,7 @@ type languageInfo struct {
|
||||||
// numberFormat directly maps the number format parser runtime required
|
// numberFormat directly maps the number format parser runtime required
|
||||||
// fields.
|
// fields.
|
||||||
type numberFormat struct {
|
type numberFormat struct {
|
||||||
|
opts *Options
|
||||||
cellType CellType
|
cellType CellType
|
||||||
section []nfp.Section
|
section []nfp.Section
|
||||||
t time.Time
|
t time.Time
|
||||||
|
@ -396,9 +397,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, date1904 bool, cellType CellType) string {
|
func format(value, numFmt string, date1904 bool, cellType CellType, opts *Options) string {
|
||||||
p := nfp.NumberFormatParser()
|
p := nfp.NumberFormatParser()
|
||||||
nf := numberFormat{section: p.Parse(numFmt), value: value, date1904: date1904, cellType: cellType}
|
nf := numberFormat{opts: opts, section: p.Parse(numFmt), value: value, date1904: date1904, cellType: cellType}
|
||||||
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 {
|
||||||
|
@ -480,7 +481,7 @@ func (nf *numberFormat) printNumberLiteral(text string) string {
|
||||||
}
|
}
|
||||||
for i, token := range nf.section[nf.sectionIdx].Items {
|
for i, token := range nf.section[nf.sectionIdx].Items {
|
||||||
if token.TType == nfp.TokenTypeCurrencyLanguage {
|
if token.TType == nfp.TokenTypeCurrencyLanguage {
|
||||||
if err := nf.currencyLanguageHandler(i, token); err != nil {
|
if err, changeNumFmtCode := nf.currencyLanguageHandler(i, token); err != nil || changeNumFmtCode {
|
||||||
return nf.value
|
return nf.value
|
||||||
}
|
}
|
||||||
result += nf.currencyString
|
result += nf.currencyString
|
||||||
|
@ -616,7 +617,7 @@ func (nf *numberFormat) dateTimeHandler() string {
|
||||||
nf.t, nf.hours, nf.seconds = timeFromExcelTime(nf.number, nf.date1904), 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 token.TType == nfp.TokenTypeCurrencyLanguage {
|
if token.TType == nfp.TokenTypeCurrencyLanguage {
|
||||||
if err := nf.currencyLanguageHandler(i, token); err != nil {
|
if err, changeNumFmtCode := nf.currencyLanguageHandler(i, token); err != nil || changeNumFmtCode {
|
||||||
return nf.value
|
return nf.value
|
||||||
}
|
}
|
||||||
nf.result += nf.currencyString
|
nf.result += nf.currencyString
|
||||||
|
@ -687,16 +688,28 @@ func (nf *numberFormat) positiveHandler() string {
|
||||||
|
|
||||||
// currencyLanguageHandler will be handling currency and language types tokens
|
// currencyLanguageHandler will be handling currency and language types tokens
|
||||||
// for a number format expression.
|
// for a number format expression.
|
||||||
func (nf *numberFormat) currencyLanguageHandler(i int, token nfp.Token) (err error) {
|
func (nf *numberFormat) currencyLanguageHandler(i int, token nfp.Token) (error, bool) {
|
||||||
for _, part := range token.Parts {
|
for _, part := range token.Parts {
|
||||||
if inStrSlice(supportedTokenTypes, part.Token.TType, true) == -1 {
|
if inStrSlice(supportedTokenTypes, part.Token.TType, true) == -1 {
|
||||||
err = ErrUnsupportedNumberFormat
|
return ErrUnsupportedNumberFormat, false
|
||||||
return
|
|
||||||
}
|
}
|
||||||
if part.Token.TType == nfp.TokenSubTypeLanguageInfo {
|
if part.Token.TType == nfp.TokenSubTypeLanguageInfo {
|
||||||
|
if strings.EqualFold(part.Token.TValue, "F800") { // [$-x-sysdate]
|
||||||
|
if nf.opts != nil && nf.opts.LongDateFmtCode != "" {
|
||||||
|
nf.value = format(nf.value, nf.opts.LongDateFmtCode, nf.date1904, nf.cellType, nf.opts)
|
||||||
|
return nil, true
|
||||||
|
}
|
||||||
|
part.Token.TValue = "409"
|
||||||
|
}
|
||||||
|
if strings.EqualFold(part.Token.TValue, "F400") { // [$-x-systime]
|
||||||
|
if nf.opts != nil && nf.opts.LongTimeFmtCode != "" {
|
||||||
|
nf.value = format(nf.value, nf.opts.LongTimeFmtCode, nf.date1904, nf.cellType, nf.opts)
|
||||||
|
return nil, true
|
||||||
|
}
|
||||||
|
part.Token.TValue = "409"
|
||||||
|
}
|
||||||
if _, ok := supportedLanguageInfo[strings.ToUpper(part.Token.TValue)]; !ok {
|
if _, ok := supportedLanguageInfo[strings.ToUpper(part.Token.TValue)]; !ok {
|
||||||
err = ErrUnsupportedNumberFormat
|
return ErrUnsupportedNumberFormat, false
|
||||||
return
|
|
||||||
}
|
}
|
||||||
nf.localCode = strings.ToUpper(part.Token.TValue)
|
nf.localCode = strings.ToUpper(part.Token.TValue)
|
||||||
}
|
}
|
||||||
|
@ -704,7 +717,7 @@ func (nf *numberFormat) currencyLanguageHandler(i int, token nfp.Token) (err err
|
||||||
nf.currencyString = part.Token.TValue
|
nf.currencyString = part.Token.TValue
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return
|
return nil, false
|
||||||
}
|
}
|
||||||
|
|
||||||
// localAmPm return AM/PM name by supported language ID.
|
// localAmPm return AM/PM name by supported language ID.
|
||||||
|
|
|
@ -995,6 +995,8 @@ func TestNumFmt(t *testing.T) {
|
||||||
{"44835.18957170139", "[$-435]mmmmm dd yyyy h:mm AM/PM", "O 01 2022 4:32 AM"},
|
{"44835.18957170139", "[$-435]mmmmm dd yyyy h:mm AM/PM", "O 01 2022 4:32 AM"},
|
||||||
{"44866.18957170139", "[$-435]mmmmm dd yyyy h:mm AM/PM", "N 01 2022 4:32 AM"},
|
{"44866.18957170139", "[$-435]mmmmm dd yyyy h:mm AM/PM", "N 01 2022 4:32 AM"},
|
||||||
{"44896.18957170139", "[$-435]mmmmm dd yyyy h:mm AM/PM", "D 01 2022 4:32 AM"},
|
{"44896.18957170139", "[$-435]mmmmm dd yyyy h:mm AM/PM", "D 01 2022 4:32 AM"},
|
||||||
|
{"43543.503206018519", "[$-F800]dddd, mmmm dd, yyyy", "Tuesday, March 19, 2019"},
|
||||||
|
{"43543.503206018519", "[$-F400]h:mm:ss AM/PM", "12:04:37 PM"},
|
||||||
{"text_", "General", "text_"},
|
{"text_", "General", "text_"},
|
||||||
{"text_", "\"=====\"@@@\"--\"@\"----\"", "=====text_text_text_--text_----"},
|
{"text_", "\"=====\"@@@\"--\"@\"----\"", "=====text_text_text_--text_----"},
|
||||||
{"0.0450685976001E+21", "0_);[Red]\\(0\\)", "45068597600100000000"},
|
{"0.0450685976001E+21", "0_);[Red]\\(0\\)", "45068597600100000000"},
|
||||||
|
@ -1061,9 +1063,22 @@ func TestNumFmt(t *testing.T) {
|
||||||
{"1234.5678", "0.0xxx00", "1234.5678"},
|
{"1234.5678", "0.0xxx00", "1234.5678"},
|
||||||
{"-1234.5678", "00000.00###;s;", "-1234.5678"},
|
{"-1234.5678", "00000.00###;s;", "-1234.5678"},
|
||||||
} {
|
} {
|
||||||
result := format(item[0], item[1], false, CellTypeNumber)
|
result := format(item[0], item[1], false, CellTypeNumber, nil)
|
||||||
assert.Equal(t, item[2], result, item)
|
assert.Equal(t, item[2], result, item)
|
||||||
}
|
}
|
||||||
|
// Test format number with specified date and time format code
|
||||||
|
for _, item := range [][]string{
|
||||||
|
{"43543.503206018519", "[$-F800]dddd, mmmm dd, yyyy", "2019年3月19日"},
|
||||||
|
{"43543.503206018519", "[$-F400]h:mm:ss AM/PM", "12:04:37"},
|
||||||
|
} {
|
||||||
|
result := format(item[0], item[1], false, CellTypeNumber, &Options{
|
||||||
|
ShortDateFmtCode: "yyyy/m/d",
|
||||||
|
LongDateFmtCode: "yyyy\"年\"M\"月\"d\"日\"",
|
||||||
|
LongTimeFmtCode: "H:mm:ss",
|
||||||
|
})
|
||||||
|
assert.Equal(t, item[2], result, item)
|
||||||
|
}
|
||||||
|
// Test format number with string data type cell value
|
||||||
for _, cellType := range []CellType{CellTypeSharedString, CellTypeInlineString} {
|
for _, cellType := range []CellType{CellTypeSharedString, CellTypeInlineString} {
|
||||||
for _, item := range [][]string{
|
for _, item := range [][]string{
|
||||||
{"1234.5678", "General", "1234.5678"},
|
{"1234.5678", "General", "1234.5678"},
|
||||||
|
@ -1073,10 +1088,12 @@ func TestNumFmt(t *testing.T) {
|
||||||
{"1234.5678", "0_);[Red]\\(0\\)", "1234.5678"},
|
{"1234.5678", "0_);[Red]\\(0\\)", "1234.5678"},
|
||||||
{"1234.5678", "\"text\"@", "text1234.5678"},
|
{"1234.5678", "\"text\"@", "text1234.5678"},
|
||||||
} {
|
} {
|
||||||
result := format(item[0], item[1], false, cellType)
|
result := format(item[0], item[1], false, cellType, nil)
|
||||||
assert.Equal(t, item[2], result, item)
|
assert.Equal(t, item[2], result, item)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
nf := numberFormat{}
|
nf := numberFormat{}
|
||||||
assert.Equal(t, ErrUnsupportedNumberFormat, nf.currencyLanguageHandler(0, nfp.Token{Parts: []nfp.Part{{}}}))
|
err, changeNumFmtCode := nf.currencyLanguageHandler(0, nfp.Token{Parts: []nfp.Part{{}}})
|
||||||
|
assert.Equal(t, ErrUnsupportedNumberFormat, err)
|
||||||
|
assert.False(t, changeNumFmtCode)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1117,6 +1117,15 @@ func TestNumberFormats(t *testing.T) {
|
||||||
assert.Equal(t, expected, result, cell)
|
assert.Equal(t, expected, result, cell)
|
||||||
}
|
}
|
||||||
assert.NoError(t, f.SaveAs(filepath.Join("test", "TestNumberFormats.xlsx")))
|
assert.NoError(t, f.SaveAs(filepath.Join("test", "TestNumberFormats.xlsx")))
|
||||||
|
|
||||||
|
f = NewFile(Options{ShortDateFmtCode: "yyyy/m/d"})
|
||||||
|
assert.NoError(t, f.SetCellValue("Sheet1", "A1", 43543.503206018519))
|
||||||
|
numFmt14, err := f.NewStyle(&Style{NumFmt: 14})
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.NoError(t, f.SetCellStyle("Sheet1", "A1", "A1", numFmt14))
|
||||||
|
result, err := f.GetCellValue("Sheet1", "A1")
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, "2019/3/19", result, "A1")
|
||||||
}
|
}
|
||||||
|
|
||||||
func BenchmarkRows(b *testing.B) {
|
func BenchmarkRows(b *testing.B) {
|
||||||
|
|
Loading…
Reference in New Issue