Skip to content

Commit 49234fb

Browse files
committed
Ref qax-os#1199, this support applies partial built-in language number format code
- Remove the `Lang` field in the `Style` data type - Rename field name `ShortDateFmtCode` to `ShortDatePattern` in the `Options` data type - Rename field name `LongDateFmtCode` to `LongDatePattern` in the `Options` data type - Rename field name `LongTimeFmtCode` to `LongTimePattern` in the `Options` data type - Apply built-in language number format code number when creating a new style - Checking and returning error if the date and time pattern was invalid - Add new `Options` field `CultureInfo` and new exported data type `CultureName` - Add new culture name types enumeration for country code - Update unit tests - Move built-in number format code and currency number format code definition source code - Remove the built-in language number format code mapping with Unicode values - Fix incorrect number formatted result for date and time with 12 hours at AM
1 parent dfdd97c commit 49234fb

9 files changed

+806
-919
lines changed

cell.go

+1-10
Original file line numberDiff line numberDiff line change
@@ -1336,15 +1336,6 @@ 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-
13481339
// formattedValue provides a function to returns a value after formatted. If
13491340
// it is possible to apply a format to the cell value, it will do so, if not
13501341
// then an error will be returned, along with the raw value of the cell.
@@ -1374,7 +1365,7 @@ func (f *File) formattedValue(c *xlsxC, raw bool, cellType CellType) (string, er
13741365
if wb != nil && wb.WorkbookPr != nil {
13751366
date1904 = wb.WorkbookPr.Date1904
13761367
}
1377-
if fmtCode, ok := builtInNumFmt[numFmtID]; ok {
1368+
if fmtCode, ok := f.getBuiltInNumFmtCode(numFmtID); ok {
13781369
return f.applyBuiltInNumFmt(c, fmtCode, numFmtID, date1904, cellType), err
13791370
}
13801371
if styleSheet.NumFmts == nil {

excelize.go

+13-9
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ type File struct {
6060
// the spreadsheet from non-UTF-8 encoding.
6161
type charsetTranscoderFn func(charset string, input io.Reader) (rdr io.Reader, err error)
6262

63-
// Options define the options for open and reading spreadsheet.
63+
// Options define the options for o`pen and reading spreadsheet.
6464
//
6565
// MaxCalcIterations specifies the maximum iterations for iterative
6666
// calculation, the default value is 0.
@@ -80,26 +80,30 @@ type charsetTranscoderFn func(charset string, input io.Reader) (rdr io.Reader, e
8080
// should be less than or equal to UnzipSizeLimit, the default value is
8181
// 16MB.
8282
//
83-
// ShortDateFmtCode specifies the short date number format code. In the
83+
// ShortDatePattern specifies the short date number format code. In the
8484
// spreadsheet applications, date formats display date and time serial numbers
8585
// as date values. Date formats that begin with an asterisk (*) respond to
8686
// changes in regional date and time settings that are specified for the
8787
// operating system. Formats without an asterisk are not affected by operating
88-
// system settings. The ShortDateFmtCode used for specifies apply date formats
88+
// system settings. The ShortDatePattern used for specifies apply date formats
8989
// that begin with an asterisk.
9090
//
91-
// LongDateFmtCode specifies the long date number format code.
91+
// LongDatePattern specifies the long date number format code.
9292
//
93-
// LongTimeFmtCode specifies the long time number format code.
93+
// LongTimePattern specifies the long time number format code.
94+
//
95+
// CultureInfo specifies the country code for applying built-in language number
96+
// format code these effect by the system's local language settings.
9497
type Options struct {
9598
MaxCalcIterations uint
9699
Password string
97100
RawCellValue bool
98101
UnzipSizeLimit int64
99102
UnzipXMLSizeLimit int64
100-
ShortDateFmtCode string
101-
LongDateFmtCode string
102-
LongTimeFmtCode string
103+
ShortDatePattern string
104+
LongDatePattern string
105+
LongTimePattern string
106+
CultureInfo CultureName
103107
}
104108

105109
// OpenFile take the name of an spreadsheet file and returns a populated
@@ -162,7 +166,7 @@ func (f *File) checkOpenReaderOptions() error {
162166
if f.options.UnzipXMLSizeLimit > f.options.UnzipSizeLimit {
163167
return ErrOptionsUnzipSizeLimit
164168
}
165-
return nil
169+
return f.checkDateTimePattern()
166170
}
167171

168172
// OpenReader read data stream from io.Reader and return a populated

excelize_test.go

+64-4
Original file line numberDiff line numberDiff line change
@@ -752,7 +752,7 @@ func TestSetCellStyleNumberFormat(t *testing.T) {
752752
expected := [][]string{
753753
{"37947.7500001", "37948", "37947.75", "37,948", "37,947.75", "3794775%", "3794775.00%", "3.79E+04", "37947.7500001", "37947.7500001", "11-22-03", "22-Nov-03", "22-Nov", "Nov-03", "6:00 PM", "6:00:00 PM", "18:00", "18:00:00", "11/22/03 18:00", "37,948 ", "37,948 ", "37,947.75 ", "37,947.75 ", "37947.7500001", "37947.7500001", "37947.7500001", "37947.7500001", "00:00", "910746:00:00", "00:00.0", "37947.7500001", "37947.7500001"},
754754
{"-37947.7500001", "-37948", "-37947.75", "-37,948", "-37,947.75", "-3794775%", "-3794775.00%", "-3.79E+04", "-37947.7500001", "-37947.7500001", "-37947.7500001", "-37947.7500001", "-37947.7500001", "-37947.7500001", "-37947.7500001", "-37947.7500001", "-37947.7500001", "-37947.7500001", "-37947.7500001", "(37,948)", "(37,948)", "(37,947.75)", "(37,947.75)", "-37947.7500001", "-37947.7500001", "-37947.7500001", "-37947.7500001", "-37947.7500001", "-37947.7500001", "-37947.7500001", "-37947.7500001", "-37947.7500001"},
755-
{"0.007", "0", "0.01", "0", "0.01", "1%", "0.70%", "7.00E-03", "0.007", "0.007", "12-30-99", "30-Dec-99", "30-Dec", "Dec-99", "0:10 AM", "0:10:05 AM", "00:10", "00:10:05", "12/30/99 00:10", "0 ", "0 ", "0.01 ", "0.01 ", "0.007", "0.007", "0.007", "0.007", "10:05", "0:10:05", "10:04.8", "0.007", "0.007"},
755+
{"0.007", "0", "0.01", "0", "0.01", "1%", "0.70%", "7.00E-03", "0.007", "0.007", "12-30-99", "30-Dec-99", "30-Dec", "Dec-99", "12:10 AM", "12:10:05 AM", "00:10", "00:10:05", "12/30/99 00:10", "0 ", "0 ", "0.01 ", "0.01 ", "0.007", "0.007", "0.007", "0.007", "10:05", "0:10:05", "10:04.8", "0.007", "0.007"},
756756
{"2.1", "2", "2.10", "2", "2.10", "210%", "210.00%", "2.10E+00", "2.1", "2.1", "01-01-00", "1-Jan-00", "1-Jan", "Jan-00", "2:24 AM", "2:24:00 AM", "02:24", "02:24:00", "1/1/00 02:24", "2 ", "2 ", "2.10 ", "2.10 ", "2.1", "2.1", "2.1", "2.1", "24:00", "50:24:00", "24:00.0", "2.1", "2.1"},
757757
{"String", "String", "String", "String", "String", "String", "String", "String", "String", "String", "String", "String", "String", "String", "String", "String", "String", "String", "String", "String", "String", "String", "String", "String", "String", "String", "String", "String", "String", "String", "String", "String"},
758758
}
@@ -811,26 +811,61 @@ func TestSetCellStyleCurrencyNumberFormat(t *testing.T) {
811811
assert.NoError(t, f.SetCellValue("Sheet1", "A1", 42920.5))
812812
assert.NoError(t, f.SetCellValue("Sheet1", "A2", 42920.5))
813813

814-
_, err = f.NewStyle(&Style{NumFmt: 26, Lang: "zh-tw"})
814+
_, err = f.NewStyle(&Style{NumFmt: 26})
815815
assert.NoError(t, err)
816816

817817
style, err := f.NewStyle(&Style{NumFmt: 27})
818818
assert.NoError(t, err)
819819

820820
assert.NoError(t, f.SetCellStyle("Sheet1", "A1", "A1", style))
821-
style, err = f.NewStyle(&Style{NumFmt: 31, Lang: "ko-kr"})
821+
style, err = f.NewStyle(&Style{NumFmt: 31})
822822
assert.NoError(t, err)
823823

824824
assert.NoError(t, f.SetCellStyle("Sheet1", "A2", "A2", style))
825825

826-
style, err = f.NewStyle(&Style{NumFmt: 71, Lang: "th-th"})
826+
style, err = f.NewStyle(&Style{NumFmt: 71})
827827
assert.NoError(t, err)
828828
assert.NoError(t, f.SetCellStyle("Sheet1", "A2", "A2", style))
829829

830830
assert.NoError(t, f.SaveAs(filepath.Join("test", "TestSetCellStyleCurrencyNumberFormat.TestBook4.xlsx")))
831831
})
832832
}
833833

834+
func TestSetCellStyleLangNumberFormat(t *testing.T) {
835+
rawCellValues := [][]string{{"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}}
836+
for lang, expected := range map[CultureName][][]string{
837+
CultureNameUnknown: rawCellValues,
838+
CultureNameEnUS: {{"8/24/23"}, {"8/24/23"}, {"8/24/23"}, {"8/24/23"}, {"8/24/23"}, {"0:00:00"}, {"0:00:00"}, {"0:00:00"}, {"0:00:00"}, {"45162"}, {"8/24/23"}, {"8/24/23"}, {"8/24/23"}, {"8/24/23"}, {"8/24/23"}, {"8/24/23"}, {"8/24/23"}, {"8/24/23"}, {"8/24/23"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}},
839+
CultureNameZhCN: {{"2023年8月"}, {"8月24日"}, {"8月24日"}, {"8/24/23"}, {"2023年8月24日"}, {"0时00分"}, {"0时00分00秒"}, {"上午12时00分"}, {"上午12时00分00秒"}, {"2023年8月"}, {"2023年8月"}, {"8月24日"}, {"2023年8月"}, {"8月24日"}, {"8月24日"}, {"上午12时00分"}, {"上午12时00分00秒"}, {"2023年8月"}, {"8月24日"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}},
840+
} {
841+
f, err := prepareTestBook5(Options{CultureInfo: lang})
842+
assert.NoError(t, err)
843+
rows, err := f.GetRows("Sheet1")
844+
assert.NoError(t, err)
845+
assert.Equal(t, expected, rows)
846+
assert.NoError(t, f.Close())
847+
}
848+
// Test apply language number format code with date and time pattern
849+
for lang, expected := range map[CultureName][][]string{
850+
CultureNameEnUS: {{"2023-8-24"}, {"2023-8-24"}, {"2023-8-24"}, {"2023-8-24"}, {"2023-8-24"}, {"00:00:00"}, {"00:00:00"}, {"00:00:00"}, {"00:00:00"}, {"45162"}, {"2023-8-24"}, {"2023-8-24"}, {"2023-8-24"}, {"2023-8-24"}, {"2023-8-24"}, {"2023-8-24"}, {"2023-8-24"}, {"2023-8-24"}, {"2023-8-24"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}},
851+
CultureNameZhCN: {{"2023年8月"}, {"8月24日"}, {"8月24日"}, {"2023-8-24"}, {"2023年8月24日"}, {"00:00:00"}, {"00:00:00"}, {"上午12时00分"}, {"上午12时00分00秒"}, {"2023年8月"}, {"2023年8月"}, {"8月24日"}, {"2023年8月"}, {"8月24日"}, {"8月24日"}, {"上午12时00分"}, {"上午12时00分00秒"}, {"2023年8月"}, {"8月24日"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}, {"45162"}},
852+
} {
853+
f, err := prepareTestBook5(Options{CultureInfo: lang, ShortDatePattern: "yyyy-M-d", LongTimePattern: "hh:mm:ss"})
854+
assert.NoError(t, err)
855+
rows, err := f.GetRows("Sheet1")
856+
assert.NoError(t, err)
857+
assert.Equal(t, expected, rows)
858+
assert.NoError(t, f.Close())
859+
}
860+
// Test open workbook with invalid date and time pattern options
861+
_, err := OpenFile(filepath.Join("test", "Book1.xlsx"), Options{LongDatePattern: "0.00"})
862+
assert.Equal(t, ErrUnsupportedNumberFormat, err)
863+
_, err = OpenFile(filepath.Join("test", "Book1.xlsx"), Options{LongTimePattern: "0.00"})
864+
assert.Equal(t, ErrUnsupportedNumberFormat, err)
865+
_, err = OpenFile(filepath.Join("test", "Book1.xlsx"), Options{ShortDatePattern: "0.00"})
866+
assert.Equal(t, ErrUnsupportedNumberFormat, err)
867+
}
868+
834869
func TestSetCellStyleCustomNumberFormat(t *testing.T) {
835870
f := NewFile()
836871
assert.NoError(t, f.SetCellValue("Sheet1", "A1", 42920.5))
@@ -1612,6 +1647,31 @@ func prepareTestBook4() (*File, error) {
16121647
return f, nil
16131648
}
16141649

1650+
func prepareTestBook5(opts Options) (*File, error) {
1651+
f := NewFile(opts)
1652+
var rowNum int
1653+
for _, idxRange := range [][]int{{27, 36}, {50, 81}} {
1654+
for numFmtIdx := idxRange[0]; numFmtIdx <= idxRange[1]; numFmtIdx++ {
1655+
rowNum++
1656+
styleID, err := f.NewStyle(&Style{NumFmt: numFmtIdx})
1657+
if err != nil {
1658+
return f, err
1659+
}
1660+
cell, err := CoordinatesToCellName(1, rowNum)
1661+
if err != nil {
1662+
return f, err
1663+
}
1664+
if err := f.SetCellValue("Sheet1", cell, 45162); err != nil {
1665+
return f, err
1666+
}
1667+
if err := f.SetCellStyle("Sheet1", cell, cell, styleID); err != nil {
1668+
return f, err
1669+
}
1670+
}
1671+
}
1672+
return f, nil
1673+
}
1674+
16151675
func fillCells(f *File, sheet string, colCount, rowCount int) error {
16161676
for col := 1; col <= colCount; col++ {
16171677
for row := 1; row <= rowCount; row++ {

0 commit comments

Comments
 (0)