From 8d84e8f7f19f6e211818ed7095cf9e74e6eb52b5 Mon Sep 17 00:00:00 2001 From: Stani Michiels Date: Thu, 19 Aug 2021 19:40:53 +0200 Subject: [PATCH 1/7] Implement DAY function --- calc.go | 23 +++++++++++++++++++++++ calc_test.go | 6 ++++++ 2 files changed, 29 insertions(+) diff --git a/calc.go b/calc.go index f52ed538e8..6312849689 100644 --- a/calc.go +++ b/calc.go @@ -269,6 +269,7 @@ var tokenPriority = map[string]int{ // CUMPRINC // DATE // DATEDIF +// DAY // DB // DDB // DEC2BIN @@ -6108,6 +6109,28 @@ func (fn *formulaFuncs) DATEDIF(argsList *list.List) formulaArg { return newNumberFormulaArg(diff) } +// DAY Returns the day of a date, represented by a serial number. +// The day is given as an integer ranging from 1 to 31. +// The syntax of the function is: +// +// DAY(serial_number) +// +// Serial_number (required) is the date of the day you are trying to find. +// Dates should be entered by using the DATE function, +// or as results of other formulas or functions. +// For example, use DATE(2008,5,23) for the 23rd day of May, 2008. +func (fn *formulaFuncs) DAY(argsList *list.List) formulaArg { + if argsList.Len() != 1 { + return newErrorFormulaArg(formulaErrorVALUE, "DAY requires exactly one argument") + } + excelDate := argsList.Front().Value.(formulaArg).ToNumber() + if excelDate.Type != ArgNumber { + return newErrorFormulaArg(formulaErrorVALUE, "DAY requires a number argument") + } + t := timeFromExcelTime(excelDate.Number, false) + return newNumberFormulaArg(float64(t.Day())) +} + // NOW function returns the current date and time. The function receives no // arguments and therefore. The syntax of the function is: // diff --git a/calc_test.go b/calc_test.go index ffcdb4dbda..fecfa81f38 100644 --- a/calc_test.go +++ b/calc_test.go @@ -944,6 +944,8 @@ func TestCalcCellValue(t *testing.T) { "=DATEDIF(43101,43891,\"YD\")": "59", "=DATEDIF(36526,73110,\"YD\")": "60", "=DATEDIF(42171,44242,\"yd\")": "244", + // DAY + "=DAY(42171)": "16", // Text Functions // CHAR "=CHAR(65)": "A", @@ -1927,6 +1929,10 @@ func TestCalcCellValue(t *testing.T) { "=DATEDIF(\"\",\"\",\"\")": "strconv.ParseFloat: parsing \"\": invalid syntax", "=DATEDIF(43891,43101,\"Y\")": "start_date > end_date", "=DATEDIF(43101,43891,\"x\")": "DATEDIF has invalid unit", + // DAY + "=DAY()": "DAY requires exactly one argument", + "=DAY(43891,43101)": "DAY requires exactly one argument", + `=DAY("text")`: "DAY requires a number argument", // NOW "=NOW(A1)": "NOW accepts no arguments", // TODAY From f3bcd85627b92b3094144ad43b1dc62cee5d7457 Mon Sep 17 00:00:00 2001 From: Stani Michiels Date: Thu, 19 Aug 2021 20:41:33 +0200 Subject: [PATCH 2/7] Implement MONTH function --- calc.go | 46 ++++++++++++++++++++++++++++++++++++---------- calc_test.go | 6 ++++++ 2 files changed, 42 insertions(+), 10 deletions(-) diff --git a/calc.go b/calc.go index 6312849689..1ce354d94a 100644 --- a/calc.go +++ b/calc.go @@ -6109,6 +6109,26 @@ func (fn *formulaFuncs) DATEDIF(argsList *list.List) formulaArg { return newNumberFormulaArg(diff) } +// timeFunc is a helper function for DAY and MONTH +func timeFunc(name string, argsList *list.List) formulaArg { + if argsList.Len() != 1 { + return newErrorFormulaArg(formulaErrorVALUE, name+" requires exactly one argument") + } + excelDate := argsList.Front().Value.(formulaArg).ToNumber() + if excelDate.Type != ArgNumber { + return newErrorFormulaArg(formulaErrorVALUE, name+" requires a number argument") + } + t := timeFromExcelTime(excelDate.Number, false) + var result float64 + switch name { + case "DAY": + result = float64(t.Day()) + case "MONTH": + result = float64(t.Month()) + } + return newNumberFormulaArg(result) +} + // DAY Returns the day of a date, represented by a serial number. // The day is given as an integer ranging from 1 to 31. // The syntax of the function is: @@ -6118,17 +6138,23 @@ func (fn *formulaFuncs) DATEDIF(argsList *list.List) formulaArg { // Serial_number (required) is the date of the day you are trying to find. // Dates should be entered by using the DATE function, // or as results of other formulas or functions. -// For example, use DATE(2008,5,23) for the 23rd day of May, 2008. +// For example, use DATE(2008,5,23) for the 23rd day of May, 2008. func (fn *formulaFuncs) DAY(argsList *list.List) formulaArg { - if argsList.Len() != 1 { - return newErrorFormulaArg(formulaErrorVALUE, "DAY requires exactly one argument") - } - excelDate := argsList.Front().Value.(formulaArg).ToNumber() - if excelDate.Type != ArgNumber { - return newErrorFormulaArg(formulaErrorVALUE, "DAY requires a number argument") - } - t := timeFromExcelTime(excelDate.Number, false) - return newNumberFormulaArg(float64(t.Day())) + return timeFunc("DAY", argsList) +} + +// Returns the month of a date represented by a serial number. +// The month is given as an integer, ranging from 1 (January) to 12 (December). +// The syntax of the function is: +// +// MONTH(serial_number) +// +// Serial_number (required) is the date of the month you are trying to find. +// Dates should be entered by using the DATE function, +// or as results of other formulas or functions. +// For example, use DATE(2008,5,23) for the 23rd day of May, 2008. +func (fn *formulaFuncs) MONTH(argsList *list.List) formulaArg { + return timeFunc("MONTH", argsList) } // NOW function returns the current date and time. The function receives no diff --git a/calc_test.go b/calc_test.go index fecfa81f38..fcaf291b30 100644 --- a/calc_test.go +++ b/calc_test.go @@ -946,6 +946,8 @@ func TestCalcCellValue(t *testing.T) { "=DATEDIF(42171,44242,\"yd\")": "244", // DAY "=DAY(42171)": "16", + // MONTH + "=MONTH(42171)": "6", // Text Functions // CHAR "=CHAR(65)": "A", @@ -1933,6 +1935,10 @@ func TestCalcCellValue(t *testing.T) { "=DAY()": "DAY requires exactly one argument", "=DAY(43891,43101)": "DAY requires exactly one argument", `=DAY("text")`: "DAY requires a number argument", + // MONTH + "=MONTH()": "MONTH requires exactly one argument", + "=MONTH(43891,43101)": "MONTH requires exactly one argument", + `=MONTH("text")`: "MONTH requires a number argument", // NOW "=NOW(A1)": "NOW accepts no arguments", // TODAY From 81e73d2750033576c2724f91fd3122b61b05daed Mon Sep 17 00:00:00 2001 From: Stani Michiels Date: Thu, 19 Aug 2021 21:15:37 +0200 Subject: [PATCH 3/7] Implement YEAR function --- calc.go | 20 ++++++++++++++++++-- calc_test.go | 2 ++ 2 files changed, 20 insertions(+), 2 deletions(-) diff --git a/calc.go b/calc.go index 1ce354d94a..a86e938dcd 100644 --- a/calc.go +++ b/calc.go @@ -6125,11 +6125,13 @@ func timeFunc(name string, argsList *list.List) formulaArg { result = float64(t.Day()) case "MONTH": result = float64(t.Month()) + case "YEAR": + result = float64(t.Year()) } return newNumberFormulaArg(result) } -// DAY Returns the day of a date, represented by a serial number. +// DAY function returns the day of a date, represented by a serial number. // The day is given as an integer ranging from 1 to 31. // The syntax of the function is: // @@ -6143,7 +6145,7 @@ func (fn *formulaFuncs) DAY(argsList *list.List) formulaArg { return timeFunc("DAY", argsList) } -// Returns the month of a date represented by a serial number. +// MONTH function returns the month of a date represented by a serial number. // The month is given as an integer, ranging from 1 (January) to 12 (December). // The syntax of the function is: // @@ -6157,6 +6159,20 @@ func (fn *formulaFuncs) MONTH(argsList *list.List) formulaArg { return timeFunc("MONTH", argsList) } +// YEAR function returns the year corresponding to a date. +// The year is returned as an integer in the range 1900-9999. +// The syntax of the function is: +// +// YEAR(serial_number) +// +// Serial_number (required) is the date of the year you want to find. +// Dates should be entered by using the DATE function, +// or as results of other formulas or functions. +// For example, use DATE(2008,5,23) for the 23rd day of May, 2008. +func (fn *formulaFuncs) YEAR(argsList *list.List) formulaArg { + return timeFunc("YEAR", argsList) +} + // NOW function returns the current date and time. The function receives no // arguments and therefore. The syntax of the function is: // diff --git a/calc_test.go b/calc_test.go index fcaf291b30..20dc9c3f77 100644 --- a/calc_test.go +++ b/calc_test.go @@ -948,6 +948,8 @@ func TestCalcCellValue(t *testing.T) { "=DAY(42171)": "16", // MONTH "=MONTH(42171)": "6", + // YEAR + "=YEAR(42171)": "2015", // Text Functions // CHAR "=CHAR(65)": "A", From f6df860b5cbeb750ad9bc879782f2c955fb8583b Mon Sep 17 00:00:00 2001 From: Stani Michiels Date: Thu, 19 Aug 2021 23:55:00 +0200 Subject: [PATCH 4/7] Improve documentation comment of DAY function --- calc.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/calc.go b/calc.go index 6312849689..b6bd214745 100644 --- a/calc.go +++ b/calc.go @@ -6109,7 +6109,7 @@ func (fn *formulaFuncs) DATEDIF(argsList *list.List) formulaArg { return newNumberFormulaArg(diff) } -// DAY Returns the day of a date, represented by a serial number. +// DAY function returns the day of a date, represented by a serial number. // The day is given as an integer ranging from 1 to 31. // The syntax of the function is: // From 8f9619383d8311350f9373cd3dbbf998b366f433 Mon Sep 17 00:00:00 2001 From: Stani Michiels Date: Thu, 19 Aug 2021 23:58:45 +0200 Subject: [PATCH 5/7] Improve documentation comment of MONTH function --- calc.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/calc.go b/calc.go index 1ce354d94a..059d7d0a26 100644 --- a/calc.go +++ b/calc.go @@ -367,6 +367,7 @@ var tokenPriority = map[string]int{ // MINA // MIRR // MOD +// MONTH // MROUND // MULTINOMIAL // MUNIT @@ -6143,7 +6144,7 @@ func (fn *formulaFuncs) DAY(argsList *list.List) formulaArg { return timeFunc("DAY", argsList) } -// Returns the month of a date represented by a serial number. +// MONTH function returns the month of a date represented by a serial number. // The month is given as an integer, ranging from 1 (January) to 12 (December). // The syntax of the function is: // From e227d008954289a5ad8e8e9ec7f8c5dae442dab0 Mon Sep 17 00:00:00 2001 From: Stani Michiels Date: Fri, 20 Aug 2021 00:05:35 +0200 Subject: [PATCH 6/7] Improve documentation comment of YEAR function --- calc.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/calc.go b/calc.go index ce04f16a54..5e91735caa 100644 --- a/calc.go +++ b/calc.go @@ -451,6 +451,7 @@ var tokenPriority = map[string]int{ // VAR.P // VARP // VLOOKUP +// YEAR // func (f *File) CalcCellValue(sheet, cell string) (result string, err error) { var ( @@ -6110,7 +6111,7 @@ func (fn *formulaFuncs) DATEDIF(argsList *list.List) formulaArg { return newNumberFormulaArg(diff) } -// timeFunc is a helper function for DAY and MONTH +// timeFunc is a helper function for DAY, MONTH and YEAR func timeFunc(name string, argsList *list.List) formulaArg { if argsList.Len() != 1 { return newErrorFormulaArg(formulaErrorVALUE, name+" requires exactly one argument") From e1066c81d344241197a546dcc55545f556b969ea Mon Sep 17 00:00:00 2001 From: xuri Date: Sat, 21 Aug 2021 20:06:21 +0800 Subject: [PATCH 7/7] This closes #1006, new fn: MONTH ref #65 --- calc.go | 52 +++++++++++++++++++++++----------------------------- calc_test.go | 15 +++++++++++++-- 2 files changed, 36 insertions(+), 31 deletions(-) diff --git a/calc.go b/calc.go index 4bb101db84..e59d344bfb 100644 --- a/calc.go +++ b/calc.go @@ -6446,40 +6446,34 @@ func (fn *formulaFuncs) MONTH(argsList *list.List) formulaArg { return newNumberFormulaArg(float64(timeFromExcelTime(num.Number, false).Month())) } -// timeFunc is a helper function for DAY, MONTH and YEAR -func timeFunc(name string, argsList *list.List) formulaArg { - if argsList.Len() != 1 { - return newErrorFormulaArg(formulaErrorVALUE, name+" requires exactly one argument") - } - excelDate := argsList.Front().Value.(formulaArg).ToNumber() - if excelDate.Type != ArgNumber { - return newErrorFormulaArg(formulaErrorVALUE, name+" requires a number argument") - } - t := timeFromExcelTime(excelDate.Number, false) - var result float64 - switch name { - case "DAY": - result = float64(t.Day()) - case "MONTH": - result = float64(t.Month()) - case "YEAR": - result = float64(t.Year()) - } - return newNumberFormulaArg(result) -} - -// YEAR function returns the year corresponding to a date. -// The year is returned as an integer in the range 1900-9999. +// YEAR function returns an integer representing the year of a supplied date. // The syntax of the function is: // // YEAR(serial_number) // -// Serial_number (required) is the date of the year you want to find. -// Dates should be entered by using the DATE function, -// or as results of other formulas or functions. -// For example, use DATE(2008,5,23) for the 23rd day of May, 2008. func (fn *formulaFuncs) YEAR(argsList *list.List) formulaArg { - return timeFunc("YEAR", argsList) + if argsList.Len() != 1 { + return newErrorFormulaArg(formulaErrorVALUE, "YEAR requires exactly 1 argument") + } + arg := argsList.Front().Value.(formulaArg) + num := arg.ToNumber() + if num.Type != ArgNumber { + dateString := strings.ToLower(arg.Value()) + if !isDateOnlyFmt(dateString) { + if _, _, _, _, _, err := strToTime(dateString); err.Type == ArgError { + return err + } + } + year, _, _, _, err := strToDate(dateString) + if err.Type == ArgError { + return err + } + return newNumberFormulaArg(float64(year)) + } + if num.Number < 0 { + return newErrorFormulaArg(formulaErrorNUM, "YEAR only accepts positive argument") + } + return newNumberFormulaArg(float64(timeFromExcelTime(num.Number, false).Year())) } // NOW function returns the current date and time. The function receives no diff --git a/calc_test.go b/calc_test.go index 218d6b0e38..e5a6e1e09d 100644 --- a/calc_test.go +++ b/calc_test.go @@ -966,7 +966,12 @@ func TestCalcCellValue(t *testing.T) { "=MONTH(42171)": "6", "=MONTH(\"31-May-2015\")": "5", // YEAR - "=YEAR(42171)": "2015", + "=YEAR(15)": "1900", + "=YEAR(\"15\")": "1900", + "=YEAR(2048)": "1905", + "=YEAR(42171)": "2015", + "=YEAR(\"29-May-2015\")": "2015", + "=YEAR(\"05/03/1984\")": "1984", // Text Functions // CHAR "=CHAR(65)": "A", @@ -1988,10 +1993,16 @@ func TestCalcCellValue(t *testing.T) { "=DAY(\"0-January-1900\")": "#VALUE!", // MONTH "=MONTH()": "MONTH requires exactly 1 argument", - "=MONTH(43891,43101)": "MONTH requires exactly 1 argument", + "=MONTH(0,0)": "MONTH requires exactly 1 argument", "=MONTH(-1)": "MONTH only accepts positive argument", "=MONTH(\"text\")": "#VALUE!", "=MONTH(\"January 25, 100\")": "#VALUE!", + // YEAR + "=YEAR()": "YEAR requires exactly 1 argument", + "=YEAR(0,0)": "YEAR requires exactly 1 argument", + "=YEAR(-1)": "YEAR only accepts positive argument", + "=YEAR(\"text\")": "#VALUE!", + "=YEAR(\"January 25, 100\")": "#VALUE!", // NOW "=NOW(A1)": "NOW accepts no arguments", // TODAY