Skip to content

Commit

Permalink
Ref qax-os#1199, this support applies partial built-in language numbe…
Browse files Browse the repository at this point in the history
…r 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
  • Loading branch information
xuri committed May 11, 2023
1 parent dfdd97c commit 49234fb
Show file tree
Hide file tree
Showing 9 changed files with 806 additions and 919 deletions.
11 changes: 1 addition & 10 deletions cell.go
Original file line number Diff line number Diff line change
Expand Up @@ -1336,15 +1336,6 @@ func (f *File) getCellStringFunc(sheet, cell string, fn func(x *xlsxWorksheet, c
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
// 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.
Expand Down Expand Up @@ -1374,7 +1365,7 @@ func (f *File) formattedValue(c *xlsxC, raw bool, cellType CellType) (string, er
if wb != nil && wb.WorkbookPr != nil {
date1904 = wb.WorkbookPr.Date1904
}
if fmtCode, ok := builtInNumFmt[numFmtID]; ok {
if fmtCode, ok := f.getBuiltInNumFmtCode(numFmtID); ok {
return f.applyBuiltInNumFmt(c, fmtCode, numFmtID, date1904, cellType), err
}
if styleSheet.NumFmts == nil {
Expand Down
22 changes: 13 additions & 9 deletions excelize.go
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ type File struct {
// the spreadsheet from non-UTF-8 encoding.
type charsetTranscoderFn func(charset string, input io.Reader) (rdr io.Reader, err error)

// Options define the options for open and reading spreadsheet.
// Options define the options for o`pen and reading spreadsheet.
//
// MaxCalcIterations specifies the maximum iterations for iterative
// calculation, the default value is 0.
Expand All @@ -80,26 +80,30 @@ type charsetTranscoderFn func(charset string, input io.Reader) (rdr io.Reader, e
// should be less than or equal to UnzipSizeLimit, the default value is
// 16MB.
//
// ShortDateFmtCode specifies the short date number format code. In the
// ShortDatePattern 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
// system settings. The ShortDatePattern used for specifies apply date formats
// that begin with an asterisk.
//
// LongDateFmtCode specifies the long date number format code.
// LongDatePattern specifies the long date number format code.
//
// LongTimeFmtCode specifies the long time number format code.
// LongTimePattern specifies the long time number format code.
//
// CultureInfo specifies the country code for applying built-in language number
// format code these effect by the system's local language settings.
type Options struct {
MaxCalcIterations uint
Password string
RawCellValue bool
UnzipSizeLimit int64
UnzipXMLSizeLimit int64
ShortDateFmtCode string
LongDateFmtCode string
LongTimeFmtCode string
ShortDatePattern string
LongDatePattern string
LongTimePattern string
CultureInfo CultureName
}

// OpenFile take the name of an spreadsheet file and returns a populated
Expand Down Expand Up @@ -162,7 +166,7 @@ func (f *File) checkOpenReaderOptions() error {
if f.options.UnzipXMLSizeLimit > f.options.UnzipSizeLimit {
return ErrOptionsUnzipSizeLimit
}
return nil
return f.checkDateTimePattern()
}

// OpenReader read data stream from io.Reader and return a populated
Expand Down
68 changes: 64 additions & 4 deletions excelize_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -752,7 +752,7 @@ func TestSetCellStyleNumberFormat(t *testing.T) {
expected := [][]string{
{"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"},
{"-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"},
{"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"},
{"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"},
{"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"},
{"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"},
}
Expand Down Expand Up @@ -811,26 +811,61 @@ func TestSetCellStyleCurrencyNumberFormat(t *testing.T) {
assert.NoError(t, f.SetCellValue("Sheet1", "A1", 42920.5))
assert.NoError(t, f.SetCellValue("Sheet1", "A2", 42920.5))

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

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

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

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

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

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

func TestSetCellStyleLangNumberFormat(t *testing.T) {
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"}}
for lang, expected := range map[CultureName][][]string{
CultureNameUnknown: rawCellValues,
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"}},
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"}},
} {
f, err := prepareTestBook5(Options{CultureInfo: lang})
assert.NoError(t, err)
rows, err := f.GetRows("Sheet1")
assert.NoError(t, err)
assert.Equal(t, expected, rows)
assert.NoError(t, f.Close())
}
// Test apply language number format code with date and time pattern
for lang, expected := range map[CultureName][][]string{
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"}},
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"}},
} {
f, err := prepareTestBook5(Options{CultureInfo: lang, ShortDatePattern: "yyyy-M-d", LongTimePattern: "hh:mm:ss"})
assert.NoError(t, err)
rows, err := f.GetRows("Sheet1")
assert.NoError(t, err)
assert.Equal(t, expected, rows)
assert.NoError(t, f.Close())
}
// Test open workbook with invalid date and time pattern options
_, err := OpenFile(filepath.Join("test", "Book1.xlsx"), Options{LongDatePattern: "0.00"})
assert.Equal(t, ErrUnsupportedNumberFormat, err)
_, err = OpenFile(filepath.Join("test", "Book1.xlsx"), Options{LongTimePattern: "0.00"})
assert.Equal(t, ErrUnsupportedNumberFormat, err)
_, err = OpenFile(filepath.Join("test", "Book1.xlsx"), Options{ShortDatePattern: "0.00"})
assert.Equal(t, ErrUnsupportedNumberFormat, err)
}

func TestSetCellStyleCustomNumberFormat(t *testing.T) {
f := NewFile()
assert.NoError(t, f.SetCellValue("Sheet1", "A1", 42920.5))
Expand Down Expand Up @@ -1612,6 +1647,31 @@ func prepareTestBook4() (*File, error) {
return f, nil
}

func prepareTestBook5(opts Options) (*File, error) {
f := NewFile(opts)
var rowNum int
for _, idxRange := range [][]int{{27, 36}, {50, 81}} {
for numFmtIdx := idxRange[0]; numFmtIdx <= idxRange[1]; numFmtIdx++ {
rowNum++
styleID, err := f.NewStyle(&Style{NumFmt: numFmtIdx})
if err != nil {
return f, err
}
cell, err := CoordinatesToCellName(1, rowNum)
if err != nil {
return f, err
}
if err := f.SetCellValue("Sheet1", cell, 45162); err != nil {
return f, err
}
if err := f.SetCellStyle("Sheet1", cell, cell, styleID); err != nil {
return f, err
}
}
}
return f, nil
}

func fillCells(f *File, sheet string, colCount, rowCount int) error {
for col := 1; col <= colCount; col++ {
for row := 1; row <= rowCount; row++ {
Expand Down
Loading

0 comments on commit 49234fb

Please sign in to comment.