The problem with short dates is that they have to be interpreted as to which century they fall in, since they are after all only denoted with two digits.
This was the infamous Y2K problem when systems interpreting those last two digits after 1999 ("99") rolled over to 1900 ("00") instead of 2000 (also "00"). At some point, however, when whatever sliding century logic is in place and the end of its 100th year is reached, then a "Y2K" problem recurs.
For the most part, calamity does not ensue when the "next century" begins because software typically works on a relative basis. The only real problem for accounting software for example was that once we were in 2000, the software (depending on the version) might not understand that "99" was older then "00." And while this would be a problem for the proper display of certain detailed data when crossing years, it in no way prevented the software from continuing to work within the relative space of the "current" year. Hence, the world kept turning and life moved on. The main problems that users had in 2000 who failed to move to a compliant system was when running a report that crossed into a prior year or where there was date logic that required comparision with pre-2000 dates (such as making an accounting entry into a prior period). After a few years however most of those issues also became moot for those users who decided to live with that problem (although older historical data would still be out of chronological date sequence order).
The Windows operating system deals with the sliding century for short dates through regional settings where date formats can also be assigned. These settings also impact how short dates are interpreted. XP Pro, Windows 7 and Windows 10 all default the century as 1930 to 2029 (which can be changed via the Control Panel under Clock, Language and Region in Windows 7, 8 and 10; under the Control Panel's Regional and Language section in XP Pro).
It is the application itself typically that determines the sliding century and not the database. So for example with Btrieve/Pervasive data that we frequently work with, there was no Y2K type problem: all date values were already being stored in a long date format. It is instead the application that has to tell the database engine how to interpret the short date which is then saved into a long date (albeit potentially in the wrong century).
In the TAS programming world and the applications written in it (including Advanced Accounting), we wrote a memo in 1998 outlining short date issues in various versions of the products that then existed. See Year 2000 issues and statement of year 2000 compliancy. No version prior to 5.1 was technically compliant. Versions 3.0 (and prior) were not at all compliant. Version 5.0 was meant to be compliant but had some issues in the year 2000 itself.
Programmers of highly customized TAS 3.0 systems (including highly customized versions of Advanced Accounting 3.0) typically chose not to move those systems forward into version 5.1 as the year 2000 approached, but instead took on the time consuming task of changing every defined short date field (both temporary variables and permanent field variables established via the data dictionary) from the short size (i.e. 8) to the long date size (i.e. 10). Technically the data files did not require a restructure because the byte size of the field wasn't being changed, but typically the data was nonetheless restructured and every single use of those fields had to be identified and changed including on every screen (and every lookup list with a date field) and report form which required extensive manual repositioning of each and every date field since they now occupied two additional characters and also because of how the TAS 3.0 screen editor worked. To migrate the TAS 3.0 code forward was admittedly potentially costly particularly for programmers who had not been working with the newer versions and were not familiar with them (version 3.0 did not even support functions), plus because it was relatively primitive code to start with, so some re-writing of code was inevitable; hence the long date approach was typically always chosen. Converting code forward from version 4.0 (assuming it was written in that more modern language) is easier than 3.0. But there was a better way than the above in terms of dealing with the Y2K issue even staying in the 3.0 version.
Version 5.1's sliding century, as outlined in our 1998 memo, was set by the prior publisher to be 1920 to 2019. That century period however is now coming to a close and anyone running the 5.1 version will have to anticipate making some changes since January 1, 2020 will be here fairly soon, and since a 01/01/20 (mm-dd-yy) date will be saved by the 5.1 runtime engine as 01/01/1920 (mm-dd-yyyy) internally and not as 01/01/2020. So the exact same issue is coming up for the 5.1 version that users of 3.0 versions were faced with in the 1998-1999 timeframe. But does this mean that the same long date solution is required? While that would be a potential solution, no, nor it is necessarily the optimum solution.
Before outlining an alternative solution for version 5.1 (besides migrating the application to a newer version altogether), it should be noted that the sliding century of the current version 7i (then TAS Professional 7 as first released by us) was expanded back in 2004 to be 1961 to 2060. Users therefore of our 7 and 7i runtime versions should have no worries with respect to short dates. Prior to that in TAS Professional 6 as released by Business Tools, the sliding century was defined as 1931 to 2030, so even a TAS Pro 6 user has nothing to worry about for now with respect to short dates.
Version 5.1 users do have another option that does not involve having to modify and adjust every single screen and report form nor change all of the temporary and permanent data variables in every program. Instead, a one-line function can be called right after any field with a date value is being assigned and its century then adjusted programmatically. As with the long date option, it does still require that programs with date values be recompiled and still requires some amount of work. But it otherwise should involve far less time, end users will not notice any difference, there will be no repositioning issues, and users will still be able to enter short dates.
An example of the type of alternative function for 5.1 systems using short dates which we have called get_new_date() is outlined below. We have placed this function in a 5.1 library file (Addsum.lib) in-house for use by systems we support that may need it (the semi-colon reflects the principal way of commenting a 5.1 program):
;Assume that the permanent field BKAR.INV.INVDTE
;(the AR invoice date) has a value before saving
;it to its corresponding data file
bkar.inv.invdte=get_new_date(bkar.inv.invdte)
And here is the function contained in the LIB file (the calling program needs to have a #LIB compiler directive referring to that file and since all of accounting software programs already have a compiler directive referring to the Addsum.lib file where we have placed it, it creates no additional work - see however the postscript below):
;Copyright 2018 Addsum Business Software, Inc.
:get_new_date
#proc
define old_date,ret_date type D size 8 local
define newyear_a type A size 4 local
define longdate_a,newdate_a type A size 10 local
func get_new_date old_date
;we are simplistically assuming that any date less than 2020
;needs to be adjusted - there are many different ways
;this could be accomplished, this is just one example
;NOTE that more variables have been used than
;absolutely necessary to make it somewhat more understandable
;this routine could be used on data saved with older
;versions, changing 2020 to a date such as 1980
;and then when re-saved back to the data file, a date in
;the wrong century would then be fixed
ret_date=0
if val(year(old_date)) < 2020
longdate_a = dtoc(old_date)
newyear_a = "20"+mid(longdate_a,7,2)
newdate_a = mid(longdate_a,1,6)+newyear_a
ret_date = ctod(mid(longdate_a,1,6)+str(newyear_a))
endif
ret ret_date
#endp
::get_new_date
Just as there were reasons for some users to stay with TAS 3.0 and even though 5.1 is also 16-bit (unlike newer versions), the same reason for staying with a highly customized TAS 5.1 system persists potentially into 2019 and beyond just as it did in 1999 with TAS 3.0.
Postscript: (Oct. 14, 2019):
As we prepare to actually implement the above approach on over a dozen systems, many that are highly customized, the example above was merely an illustration of how the problem might be solved. That prior example however was simplistic because while it would properly place a date entered between 2000 and 2019 into a range beyond 2020, users might actually need to be entering dates in those ranges and it also would put any date entered prior to 2000 in the 2020-2099 range. The fact that some dates might need to be tested that contain no date value (i.e. 00/00/00) needs to be handled as well. A revised version implementing the same sliding century as implemented by us in newer graphical versions of TAS is:
;Copyright 2019 Addsum Business Software, Inc.
:get_new_date - ADDSUM.LIB
#proc
define old_date,ret_date type D size 8 local
define newyear_a type A size 4 local
define longdate_a,newdate_a type A size 10 local
func get_new_date old_date
ret_date=0
;no date passed, return
if old_date=0 then ret ret_date
if val(year(old_date)) < 1961
longdate_a = dtoc(old_date)
newyear_a = "20"+mid(longdate_a,7,2)
;exceeds the sliding century so just return the date passed
if val(newyear_a) > 2060
ret old_date
endif
newdate_a = mid(longdate_a,1,6)+newyear_a
ret_date = ctod(mid(longdate_a,1,6)+str(newyear_a))
else
;date is OK as is, just return what was passed
ret_date=old_date
endif
ret ret_date
#endp
::get_new_date
Note also that the get_new_date() function has to be called in connection with any permanent field dates that are saved to a data file as well as in logic involving dates or with dates used as variables in ranges for reports. In TAS 5.1 (and prior) and in TAS Premier legacy RUN programs (where no short date adjustment is required), date fields are entered by the user via ENTER programming commands. The get_new_date()function should typically be called in a post (or vld) in association with ENTER. In programs where a date is entered but its value is not needed for any logic until a SAVE to the a data file occurs, then it could be potentially called right before the save instead with simply a single line of code required. The number of code changes per program typically will be few but each program that involves the use of a date field has to be separately evaluated to determine the best and simplest approach (just as it would if dates were converted to long dates which would then be more invasive in terms of other changes that would be required).
Example 1:
define somedate type D size 8
;mount a screen that includes the field somedate
enter somedate post chk_somedate()
{
;unique per each field entry
func chk_somedate
somedate = get_new_date(somedate)
ret .t.
}
Another approach would be to use a pointer field and set it to the enter field in a pre:
Example 2:
define somedate type D size 8
define date_ptr type F
;mount a screen that includes the field somedate
enter somedate pre set_someptr() post chk_somedate()
{
;unique per each field entry
func set_someptr
date_ptr -> somedate
ret .t.
;this would be placed into a LIB file and used generically when possible
func chk_somedate
&date_ptr = get_new_date(&date_ptr)
ret .t.
Unfortunately using a pointer doesn't reduce the amount of code needed and requires both a unique pre expression and a generic post expression versus a single unique post expression per field. Since the goal would be to use the least amount of code as possible, example 1 has a slight advantage over example 2. So most date fields that are entered will need a new post (if one doesn't already exist; could also be placed in a vld expression) and a three line function.
We have now (as of October 14, 2019) also changed Advanced Accounting 5.1's maintain database program so that it too will make adjustments to date fields (any type "D" dictionary field that is size 8) for the sliding century specified in get_new_date() which physically resides in ADDSUM.LIB for Advanced Accounting but could be placed in any library file that is commonly included (with a #lib compiler directive towards the top of every program) in a given set of programs. Otherwise, making any database edits involving date fields could potentially also be saved to the wrong century.
Related information/posts:
Year 2000 issues and statement of year 2000 compliancy (1998)
From Books 2.04 to Advanced Accounting 7i (2017)
No comments:
Post a Comment