CALENDRIC & TEMPORAL PROGRAMMING
Posted: Fri Mar 13, 2015 8:12 pm
CALENDRIC & TEMPORAL PROGRAMMING
Over in my "Designing A New Filesystem" topic I posted about time-stamping as part of filesystem management. Calendric and temporal matters are an interesting topic, especially in organizing dates and times in a fashion that lends itself to computer usage. In particular, the apparent irregularity of the calendar presents a challenge in trying to reduce any given date to a sequential number that lends itself to mechanical computation. Even more of a challenge is doing this stuff in assembly language using only integer arithmetic on a microprocessor that has no multiplication and division instructions. It can be done, of course, but that's getting a bit ahead of the program, so to speak.
I first got into this stuff in the mid-1980s, when I wrote a Clock-Calendar program to run on the Commodore 128, using interrupts to drive a continuous on-screen display, as well as maintain a set of date and time "registers" in software. CC128, as I called it, could provide the date and time to other programs and even had an alarm feature that would cause the C-128 to emit an irritating sound when the alarm went off. Years later while laid up in the hospital following major surgery and being bored out of my skull watching daytime TV and listening to the other guy in the room moaning and groaning, I resurrected the Clock-Calendar program on paper, modified it to run underneath the C-128's kernel ROM above the BASIC text area, and added a bit to it. In the process, I wrote a more efficient algorithm to handle leap years and was able to shrink the code to fit in about 1KB total. The program was subsequently made available on the now-defunct C-128 forum that was run by Lance Lyon in Australia.
CC128 used the time-of-day (TOD) clock in one the 6526 complex interface adapters (CIA) as the primary time source, and maintained the date as a set of BCD values, one each for the day, month and year. The day-of-the-week was computed from these values when requested. The interrupt request handler would read the TOD clock as required and update some RAM locations each time the tenths register changed. The code to do all this was somewhat convoluted because it was working with date and time values that were the analog of human-readable data. Back when I originally wrote CC128 (late 1985), I wasn't aware of other timekeeping methods that might have been batter for this application, especially the methods used in UNIX—I was relatively new to UNIX at the time and wasn't very familiar with what was going on inside the kernal.
Not too long after constructing my POC V1.0 computer, I added code to the firmware that would make a continuously-updating time-and-date field available to any application that needed it. Rather than do what I did with CC128, I decided to adopt a modified form of UNIX time. Early on in the development of UNIX, a simple but effective system-level method of keeping time had been devised. UNIX timekeeping is nothing more than counting the number of seconds that have elapsed since an arbitrary point in the past, which point is referred to as the "epoch." The epoch that was chosen for UNIX was midnight Greenwich Mean Time¹ on Thursday, January 1, 1970, a date that approximately coincided with the genesis of UNIX itself.² That epoch, also used by Linux, was a pragmatic choice with the PDP-11 hardware on which UNIX was developed, but has more recently proved to be a bit of a nuisance, as you will soon see.
On the PDP-11, a 32-bit signed integer was the largest integer data type that was available, which made it the natural choice for keeping time. The UNIX time field is defined in C programs as being of type time_t, which is a 64-bit signed integer on UNIX and Linux systems running a 64-bit kernel, and continues to be a 32-bit signed integer on older hardware. I will refer to that integer in a generalized way as "UNIX time."
When a UNIX or Linux system is booted, the battery-backed hardware clock is read, and the date and time-of-day is converted into seconds relative to the epoch. The result of this conversion is then used to initialize the internal UNIX time field maintained by the kernel. As UNIX time is nothing more than a count of the number of seconds that have elapsed since the epoch, and since the epoch is anchored to UTC, the kernel's timekeeping will be set to UTC, not the local time zone. Thereafter, the kernel's interrupt request handler will increment UNIX time at one second intervals. That is the only calendric and temporal processing by the kernel, as it "knows" nothing about the human-readable form of the date and time-of-day, as well as nothing about time zones and daylight saving time (summer time). All such matters are handled in external library functions.
UNIX time is a non-zero positive value if the date and time is in the future relative to the epoch, and a negative value if the date is prior to the epoch. As the numeric range that can be stored in a 32-bit signed integer is +2,147,483,647 ($7FFFFFFF) to -2,147,483,648 ($80000000), the usable date range is slightly more than 136 years, with it split between the past and the future, relative to the epoch. A consequence of this limitation is that on 32-bit systems, UNIX time will roll over on January 19, 2038 at 03:14:07 UTC, less than 23 years hence at this writing. The rollover will cause UNIX time to become a negative value and drop back to December 13, 1901 at 20:45:52 UTC (coincidentally, that day is Friday the 13th, for any reader who is superstitious). This event is often referred to as the "year 2038" problem, and is analogous to the Y2K problem. Systems that keep time in a 64-bit integer will not roll over for some 292 billion years.
Internally, the kernel uses its ever-incrementing copy of UNIX time to time-stamp files, directories and filesystems as they are created, read and written. This process is succinct because all that has to be done to time-stamp something is to make a copy of the kernel's UNIX time field, with provisions to assure that the copy is an atomic operation to prevent a carry error.
UNIX time is also available via a kernel API call to external programs that need to maintain the date and time, such as database managers. User programs such as date fetch UNIX time from the kernel and then apply various mathematical operations to generate a human-readable date and time of day that is adjusted to the local time zone, with compensation for daylight saving time where applicable. The library functions that do the conversions are also used by standard UNIX utilities such as ls (directory lister) that need to convert filesystem time-stamps to human-readable format. In all cases, library functions process calendric and time zone calculations, not the kernel.
In addition to UNIX time, another time field referred to as system up-time ("uptime") is maintained by the kernel. Uptime is similar to UNIX time in that it is incremented at one second intervals. However, uptime is always initialized to zero when the system is booted and cannot be changed by ordinary means. Hence uptime represents the number of seconds that have elapsed since the system came up.
In my next post, I'll bloviate a bit on how the UNIX timekeeping methods can be utilized in a 6502 system.
——————————————————————————————————————
¹UTC (coordinated universal time) has replaced GMT in most civil time reckoning.
²The original UNIX timekeeping method used midnight GMT on January 1, 1971 as the epoch and incremented the 32-bit counter at a 60 Hz rate, which means the count would have overflowed in about 2.5 years. Hence the decision was made to increment the count at a 1 Hz rate. At the same time, the epoch was changed to its present definition.
Over in my "Designing A New Filesystem" topic I posted about time-stamping as part of filesystem management. Calendric and temporal matters are an interesting topic, especially in organizing dates and times in a fashion that lends itself to computer usage. In particular, the apparent irregularity of the calendar presents a challenge in trying to reduce any given date to a sequential number that lends itself to mechanical computation. Even more of a challenge is doing this stuff in assembly language using only integer arithmetic on a microprocessor that has no multiplication and division instructions. It can be done, of course, but that's getting a bit ahead of the program, so to speak.
I first got into this stuff in the mid-1980s, when I wrote a Clock-Calendar program to run on the Commodore 128, using interrupts to drive a continuous on-screen display, as well as maintain a set of date and time "registers" in software. CC128, as I called it, could provide the date and time to other programs and even had an alarm feature that would cause the C-128 to emit an irritating sound when the alarm went off. Years later while laid up in the hospital following major surgery and being bored out of my skull watching daytime TV and listening to the other guy in the room moaning and groaning, I resurrected the Clock-Calendar program on paper, modified it to run underneath the C-128's kernel ROM above the BASIC text area, and added a bit to it. In the process, I wrote a more efficient algorithm to handle leap years and was able to shrink the code to fit in about 1KB total. The program was subsequently made available on the now-defunct C-128 forum that was run by Lance Lyon in Australia.
CC128 used the time-of-day (TOD) clock in one the 6526 complex interface adapters (CIA) as the primary time source, and maintained the date as a set of BCD values, one each for the day, month and year. The day-of-the-week was computed from these values when requested. The interrupt request handler would read the TOD clock as required and update some RAM locations each time the tenths register changed. The code to do all this was somewhat convoluted because it was working with date and time values that were the analog of human-readable data. Back when I originally wrote CC128 (late 1985), I wasn't aware of other timekeeping methods that might have been batter for this application, especially the methods used in UNIX—I was relatively new to UNIX at the time and wasn't very familiar with what was going on inside the kernal.
Not too long after constructing my POC V1.0 computer, I added code to the firmware that would make a continuously-updating time-and-date field available to any application that needed it. Rather than do what I did with CC128, I decided to adopt a modified form of UNIX time. Early on in the development of UNIX, a simple but effective system-level method of keeping time had been devised. UNIX timekeeping is nothing more than counting the number of seconds that have elapsed since an arbitrary point in the past, which point is referred to as the "epoch." The epoch that was chosen for UNIX was midnight Greenwich Mean Time¹ on Thursday, January 1, 1970, a date that approximately coincided with the genesis of UNIX itself.² That epoch, also used by Linux, was a pragmatic choice with the PDP-11 hardware on which UNIX was developed, but has more recently proved to be a bit of a nuisance, as you will soon see.
On the PDP-11, a 32-bit signed integer was the largest integer data type that was available, which made it the natural choice for keeping time. The UNIX time field is defined in C programs as being of type time_t, which is a 64-bit signed integer on UNIX and Linux systems running a 64-bit kernel, and continues to be a 32-bit signed integer on older hardware. I will refer to that integer in a generalized way as "UNIX time."
When a UNIX or Linux system is booted, the battery-backed hardware clock is read, and the date and time-of-day is converted into seconds relative to the epoch. The result of this conversion is then used to initialize the internal UNIX time field maintained by the kernel. As UNIX time is nothing more than a count of the number of seconds that have elapsed since the epoch, and since the epoch is anchored to UTC, the kernel's timekeeping will be set to UTC, not the local time zone. Thereafter, the kernel's interrupt request handler will increment UNIX time at one second intervals. That is the only calendric and temporal processing by the kernel, as it "knows" nothing about the human-readable form of the date and time-of-day, as well as nothing about time zones and daylight saving time (summer time). All such matters are handled in external library functions.
UNIX time is a non-zero positive value if the date and time is in the future relative to the epoch, and a negative value if the date is prior to the epoch. As the numeric range that can be stored in a 32-bit signed integer is +2,147,483,647 ($7FFFFFFF) to -2,147,483,648 ($80000000), the usable date range is slightly more than 136 years, with it split between the past and the future, relative to the epoch. A consequence of this limitation is that on 32-bit systems, UNIX time will roll over on January 19, 2038 at 03:14:07 UTC, less than 23 years hence at this writing. The rollover will cause UNIX time to become a negative value and drop back to December 13, 1901 at 20:45:52 UTC (coincidentally, that day is Friday the 13th, for any reader who is superstitious). This event is often referred to as the "year 2038" problem, and is analogous to the Y2K problem. Systems that keep time in a 64-bit integer will not roll over for some 292 billion years.
Internally, the kernel uses its ever-incrementing copy of UNIX time to time-stamp files, directories and filesystems as they are created, read and written. This process is succinct because all that has to be done to time-stamp something is to make a copy of the kernel's UNIX time field, with provisions to assure that the copy is an atomic operation to prevent a carry error.
UNIX time is also available via a kernel API call to external programs that need to maintain the date and time, such as database managers. User programs such as date fetch UNIX time from the kernel and then apply various mathematical operations to generate a human-readable date and time of day that is adjusted to the local time zone, with compensation for daylight saving time where applicable. The library functions that do the conversions are also used by standard UNIX utilities such as ls (directory lister) that need to convert filesystem time-stamps to human-readable format. In all cases, library functions process calendric and time zone calculations, not the kernel.
In addition to UNIX time, another time field referred to as system up-time ("uptime") is maintained by the kernel. Uptime is similar to UNIX time in that it is incremented at one second intervals. However, uptime is always initialized to zero when the system is booted and cannot be changed by ordinary means. Hence uptime represents the number of seconds that have elapsed since the system came up.
In my next post, I'll bloviate a bit on how the UNIX timekeeping methods can be utilized in a 6502 system.
——————————————————————————————————————
¹UTC (coordinated universal time) has replaced GMT in most civil time reckoning.
²The original UNIX timekeeping method used midnight GMT on January 1, 1971 as the epoch and incremented the 32-bit counter at a 60 Hz rate, which means the count would have overflowed in about 2.5 years. Hence the decision was made to increment the count at a 1 Hz rate. At the same time, the epoch was changed to its present definition.