Bug 139363 - BASIC: DateSerial() only accepts the most obvious month and day values.
Summary: BASIC: DateSerial() only accepts the most obvious month and day values.
Status: UNCONFIRMED
Alias: None
Product: LibreOffice
Classification: Unclassified
Component: BASIC (show other bugs)
Version:
(earliest affected)
unspecified
Hardware: All All
: medium normal
Assignee: Not Assigned
URL:
Whiteboard:
Keywords:
Depends on:
Blocks:
 
Reported: 2021-01-01 22:40 UTC by Johnny Rosenberg
Modified: 2021-01-03 11:44 UTC (History)
1 user (show)

See Also:
Crash report or crash signature:


Attachments

Note You need to log in before you can comment on or make changes to this bug.
Description Johnny Rosenberg 2021-01-01 22:40:36 UTC
The BASIC command SerialDate() doesn't accept months and days out of bounds. The Calc cell-function DATE() does and so does Excel's DateSerial(), and it would be very practical if SerialDate() also did.

Example 1:
Print SerialDate(2021,2,29)
Gives an error message.
In a spreadsheet cell: =DATE(2021;2;29)
Result: 2021-03-01 (if cell is formatted properly, as YYYY-MM-DD, of course)
Expected result: 2021-03-01

Example 2:
Find the last date in February 2021:
Print SerialDate(2021,3,0)
Error message.
In a spreadsheet cell: =DATE(2021;3;0)
Result: 2021-02-28
Expected result: 2021-02-28

Example 3:
Just about anything, such as:
Print SerialDate(2021,-73,-4500)
Error message.
In a spreadsheet cell: =DATE(2021;-73;-4500)
Result: 2002-07-06
Expected result: 2002-07-06

Example 4:
Loop through months:

Dim dtCurrentDate As Date, dtDate As Date
Dim iYear As Integer, iMonth As Integer, i As Integer

dtCurrentDate=Now()
iYear=Year(dtCurrentDate)
iMonth=Month(dtCurrentDate)

For i=-100 To 100
	dtDate=SerialDate(iYear, iMonth+i, 1)
	' Print dtDate somewhere or whatever.
Next i

The above only works as long as 1≤(iMonth+i)≤12, otherwise there is an error.
Expected result: It should loop through all months and figure out the dtDate values without questioning anything.


Workaround:
A custom function for it. Here are mine, I wrote two different global ones:

' This one adjusts the input values before using the built in SerialDate.
Public Function DateSerialA(iYear As Integer, iMonth As Integer, iDay As Integer) As Date
	If iMonth>12 Or iMonth<1 Then
		Dim iYearDiff As Integer
		iYearDiff=int((iMonth-1)/12)
		iYear=iYear+iYearDiff
		iMonth=iMonth-12*iYearDiff
	End If
	DateSerialA=DateSerial(iYear, iMonth, 1)+iDay-1
End Function

' This one simply use Calc's DATE() function.
Public Function DateSerialC(iYear As Integer, iMonth As Integer, iDay As Integer) As Date
	Dim CalcFunction As com.sun.star.uno.XInterface
	CalcFunction=createUnoService("com.sun.star.sheet.FunctionAccess")
	DateSerialC=CalcFunction.callFunction("DATE", Array(iYear, iMonth, iDay))
End Function

If not using extended data types, Dim CalcFunction As Object instead, of course.

Example 3 again:
Print SerialDateA(2021,-73,-4500)
Result: 2002-07-06
Print SerialDateC(2021,-73,-4500)
Result: 2002-07-06
Both match the expected results.

I just find it very strange and inconsistent that SerialDate() is so primitive compared to the DATE() cell function in Calc. Accepting just about any integer for month and day is very useful in loops and makes the code more compact and easier to understand.
Comment 1 himajin100000 2021-01-02 03:05:52 UTC
I believe there is no function named SerialDate.

For DateSerial() in StarBasic, see

https://opengrok.libreoffice.org/xref/core/basic/source/runtime/methods.cxx?r=3482f590#4757

for the implementation details.
Comment 2 Johnny Rosenberg 2021-01-02 08:02:31 UTC
Ooops, my bad. I'm terribly sorry. I don't know what happened there. I meant DateSerial() all the time. I'm not sure from where I got SerialDate().

I don't understand how this happened. I even used DateSerial() in the workaround function DateSerialA(), so I'm quite puzzled now.

There doesn't seem to be a way to change my first post (or any posts), so here it is again, corrected:

————————————————————————————————————————————————————————————————————————————————
The BASIC command DateSerial() doesn't accept months and days out of bounds. The Calc cell-function DATE() does and so does Excel's DateSerial(), and it would be very practical if DateSerial() also did.

Example 1:
Print DateSerial(2021,2,29)
Gives an error message.
In a spreadsheet cell: =DATE(2021;2;29)
Result: 2021-03-01 (if cell is formatted properly, as YYYY-MM-DD, of course)
Expected result: 2021-03-01

Example 2:
Find the last date in February 2021:
Print DateSerial(2021,3,0)
Error message.
In a spreadsheet cell: =DATE(2021;3;0)
Result: 2021-02-28
Expected result: 2021-02-28

Example 3:
Just about anything, such as:
Print DateSerial(2021,-73,-4500)
Error message.
In a spreadsheet cell: =DATE(2021;-73;-4500)
Result: 2002-07-06
Expected result: 2002-07-06

Example 4:
Loop through months:

Dim dtCurrentDate As Date, dtDate As Date
Dim iYear As Integer, iMonth As Integer, i As Integer

dtCurrentDate=Now()
iYear=Year(dtCurrentDate)
iMonth=Month(dtCurrentDate)

For i=-100 To 100
	dtDate=DateSerial(iYear, iMonth+i, 1)
	' Print dtDate somewhere or whatever.
Next i

The above only works as long as 1≤(iMonth+i)≤12, otherwise there is an error.
Expected result: It should loop through all months and figure out the dtDate values without questioning anything.


Workaround:
A custom function for it. Here are mine, I wrote two different global ones:

' This one adjusts the input values before using the built in DateSerial.
Public Function DateSerialA(iYear As Integer, iMonth As Integer, iDay As Integer) As Date
	If iMonth>12 Or iMonth<1 Then
		Dim iYearDiff As Integer
		iYearDiff=int((iMonth-1)/12)
		iYear=iYear+iYearDiff
		iMonth=iMonth-12*iYearDiff
	End If
	DateSerialA=DateSerial(iYear, iMonth, 1)+iDay-1
End Function

' This one simply use Calc's DATE() function.
Public Function DateSerialC(iYear As Integer, iMonth As Integer, iDay As Integer) As Date
	Dim CalcFunction As com.sun.star.uno.XInterface
	CalcFunction=createUnoService("com.sun.star.sheet.FunctionAccess")
	DateSerialC=CalcFunction.callFunction("DATE", Array(iYear, iMonth, iDay))
End Function

If not using extended data types, Dim CalcFunction As Object instead, of course.

Example 3 again:
Print DateSerialA(2021,-73,-4500)
Result: 2002-07-06
Print DateSerialC(2021,-73,-4500)
Result: 2002-07-06
Both match the expected results.

I just find it very strange and inconsistent that DateSerial() is so primitive compared to the DATE() cell function in Calc. Accepting just about any integer for month and day is very useful in loops and makes the code more compact and easier to understand.
————————————————————————————————————————————————————————————————————————————————

Thanks for the https://opengrok.libreoffice.org/xref/core/basic/source/runtime/methods.cxx?r=3482f590#4757 link. I had a quick look. I'm not a programmer so I'm not exactly sure what I was looking at, but one of the comments seemed interesting to me:

/* TODO: we could enable the same rollover mechanism for StarBASIC to be
 * compatible with VBA (just with our wider supported date range), then
 * documentation would need to be adapted. As is, the DateSerial() runtime
 * function works as dumb as documented... (except that the resulting date
 * is checked for validity now and not just day<=31 and month<=12).
 * If change wanted then simply remove overriding RollOver here and adapt
 * documentation.*/

Isn't this what I'm asking for? I'm not sure, but it looks a bit like it to me, but maybe I just don't understand what I'm reading.
I just thought it might be a good idea to be compatible with VBA, since many LibreOffice users switched from Microsoft Office, at least that's what it feels like when reading LibreOffice forums and mailing lists.
Comment 3 Julien Nabet 2021-01-03 10:43:31 UTC
You're talking about Excel compatibility but what about backward compatibility in LO?
Just my personal opinion but I found it strange "DATE" doesn't return an error when trying 29/02/2021, rather a bug here as Excel behaviour for "DATESERIAL".
But don't worry,  about this kind of subject I'm rather in a very small minority (certainly even alone :-)) so I suppose someone will "fix" this in the sake of Excel compatibility.
Comment 4 Johnny Rosenberg 2021-01-03 11:24:09 UTC
I just can't think of any issues that could occur due to lack of compatibility with previous versions of LibreOffice. As far as I can see, everything would still work, wouldn't it? If you wrote code to handle values that are ”out of bounds” (for instance month=13), that code wouldn't be necessary any more but it wouldn't hurt either.

My main argument for changing this isn't Excel compatibility anyway. I just think that the function should handle things like this automatically, because it makes it very convenient when using it in a loop and you can easily do calculations by just adding and subtracting years, months and days. Days would of course work well anyway, since you can always convert to the date format and just add and subtract days, but calculating months is more difficult. Let's say you want the date for today + 18 months. Wouldn't it make sense then to just do something like DateSerial(ThisYear, ThisMonth+18, ThisDay)? If that can't be done with DateSerial, you need to keep track of leap years and how many days each month has, things like DateSerial keeps track of anyway. For instance DateSerial(1900,2,29) throws an error message, I have tested that, so obviously it already keeps track of leap-years and everything else needed to know that this date does not exist.

And, since Excel was mentioned, I just have to add that I personally don't care much for Excel compatibility, I don't even use Windows (except at work), but many users are used to Excel so I don't think avoiding compatibility is a good thing either. In this case we can have both compatibility and great functionality, so why not? And as I said, I can't see the case that changing this the way I described would break anything in old LibreOffice macros. The worst thing I can see is that people wrote code that is no longer needed. They can just keep it, it won't do any harm.
Comment 5 Julien Nabet 2021-01-03 11:44:58 UTC
In a macro, you can expect the trigger of some error and plan to deal with them (eg with "ISERR"/"ISERROR")
If new behaviour doesn't trigger an error, the result of the macro will be different.
For me people may do the same as you did.
Personally, I prefer the KISS principle and I'd put "RESOLVED"/"NOTABUG" but since I suppose people will have a different opinion, I'll uncc myself.