Date and Time Manipulation

Technical Notes

Overview
Supported Date Formats
Supported Time Formats
Getting and Setting System Date and Time
Manipulating Date and Time Values
Simple Conversion of Dates and Times to ASCII Strings
Initializing the Date/Time Formatting Functions
Converting Date/Time to Formatted ASCII Strings
Converting Date/Time Strings to Structure Format
Measuring Time and Delaying Program Execution
Sample Program

Overview

This unit consists of functions which get and set the system date and time, manipulate date and time values, convert dates and times to ASCII strings, measure elapsed time, and suspend program execution for specified periods of time.

Supported Date Formats

Three different formats are supported for maintaining and manipulating date information. The date structure, integer and the Julian formats are as follows:

Structure format:


typedef struct {
   unsigned char day;                     /* day (1-31) */
   unsigned char month;                   /* month (1-12) */
   unsigned int year;                     /* year (1980-2099) */
   unsigned char day_of_week;     /* day-of-week (0-6) */
} std_date;
Integer format:

unsigned int
   bits 0x09-0x0F        year (0-119, number of years since 1980)
   bits 0x05-0x08        month (1-12)
   bits 0x00-0x04        day (1-31)

Julian format:

unsigned int             days elapsed since 01-01-1980
Note that dates in the Julian or integer formats can be compared directly to determine relative order. Julian dates may also be subtracted or adjusted directly. The std_date structure is defined in the DATETIME.H header file. All of these date formats support dates from 01-01-1980 through 12-31-2099.

Supported Time Formats

Four different formats are supported for maintaining and manipulating time information. These time formats and their default register assignments are as follows:

Structure format:


typedef struct  {
   unsigned char hseconds;        /* hundredths of seconds (0-99) */
   unsigned char seconds;         /* seconds (0-59) */
   unsigned char minutes;         /* minutes (0-31) */
   unsigned char hours;           /* hours (0-255) */
} std_time;
Integer format:

unsigned int
   bits 0x0B-0x0F        hours (0-23)
   bits 0x05-0x0A        minutes (0-59)
   bits 0x00-0x04        2-second increments (0-29)

Hundredths of seconds:

unsigned long            hundredths of seconds elapsed since  midnight (0-92159999)

Timer ticks:

unsigned long            number of timer ticks elapsed since  midnight (0-1573039)
Note that times in all formats except the structure format can be compared directly to determine relative order. Hundredths or timer ticks may also be subtracted or adjusted directly. Times in the structure format can be added to or subtracted from other times in the same format using functions in this unit. The std_time structure is defined in the DATETIME.H header file.

Getting and Setting System Date and Time

_get_clock
Returns the system clock tick count.
_get_date
Fills a date structure with the current system date and day of the week.
_get_time
Fills a time structure with the current system time.
_set_clock
Sets the system clock tick count.
_set_date
Sets the system date.
_set_time
Sets the system time.
These functions provide a simple interface for getting or setting the date and time maintained by the system. _get_date, _get_time, _set_date, and _set_time all use the structure formats to represent system time, and _get_clock and _set_clock use clock ticks. Since all of these functions depend on the system clock, they all have an effective resolution of .0549 seconds.

Manipulating Date and Time Values

_add_time
Returns the sum of two times.
_diff_time
Returns the difference of two times.
_date_to_days
Returns the number of days between January 1, 1980 and a given date.
_days_to_date
Returns a date for the given number of days since January 1, 1980.
_get_day
Returns the day of the week for a given date.
_hund_to_time
Converts hundredths of seconds into time structure format.
_time_to_hund
Converts a time from structure format to hundredths of seconds.
_merge_date
Converts a date from structure format to integer format.
_split_date
Converts a date from integer format to time structure format.
_merge_time
Converts a time from structure format to integer format.
_split_time
Converts a time from integer format to time structure format.
_ticks_to_time
Converts a time from timer ticks format to structure format.
_time_to_ticks
Converts a time from structure format to timer tick format.
These functions add, subtract, and convert dates and times between all supported formats (except for the more specialized timer ticks format). A function is also provided for determining the day of the week for a given date.

_ticks_to_time and _time_to_ticks, when used in conjunction with _get_clock and _set_clock, are faster than the _get_time and _set_time functions, respectively. _get_time and _set_time should be used when code size is more important than speed.

Simple Conversion of Dates and Times to ASCII Strings

_asc_date
Creates an ASCIIZ date string in MM/DD/YY format.
_asc_time
Creates an ASCIIZ time string in HH:MM Xm format.
These functions convert date and time values to simple ASCII strings suitable for displaying or writing to a file. The _date_to_asc and _time_to_asc functions (discussed later in this chapter) provide more flexible facilities for creating ASCII date and time strings.

Initializing the Date/Time Formatting Functions

dtc_addr
(Variable) Contains the address of the current date/time formatting options structure.
_dtc_init
Initializes the current date/time formatting options structure with DOS country information.
dtc_addr is a near/far pointer which contains the address of the current date/time options structure (dtcopts). In small data models (TINY, SMALL, MEDIUM), this variable is a near pointer which contains the offset of the structure within DGROUP (global or static non-far data). In large data models (COMPACT, LARGE, HUGE), this variable is a far pointer which contains the segment and offset of the structure. The current dtcopts structure specifies global date/time conversion formatting options and is used by _asc_to_date, _asc_to_time, _date_to_asc, _time_to_asc, and the scripted I/O functions. The behavior of these functions may be changed by modifying this structure or by modifying dtc_addr to point to a different dtcopts structure.

dtc_addr is initialized with the address of the default dtcopts structure. The dtcopts structure is defined in DATETIME.H, as follows:


typedef struct {
   char dtc_dtformat;   /* date format MDY/DMY/YMD (country) */
   char dtc_dtdelim;    /* date delimiter (country) */
   char dtc_tmformat    /* time format 12/24-hour (country) */
   char dtc_tmdelim;    /* time delimiter (country) */
   char dtc_hdelim;         /* delimiter for 100ths (default country decimal delimiter) */
   char *dtc_amstr;         /* address of AM string (default string is " AM") */
   char *dtc_pmstr;         /* address of PM string (default string is " PM") */
} dtcopts;
The default dtcopts structure (in the library) is supplied with standard U.S.A. defaults. The DTC_DEFAULT symbolic constant (defined in DATETIME.H) may be specified for any field which accepts a pointer. This causes the date/time conversion functions to use the default string, shown in the comments above, for each field in which it is specified.

The DTCOPTS structure fields are defined as follows:

dtc_dtformat DOS Date format. The default is U.S. format (0). This field is updated by _dtc_init. DOS is documented as supporting the following formats:

            0 = U.S. (MM/DD/YY)
            1 = European (DD/MM/YY)
            2 = Japanese (YY/MM/DD)
dtc_dtdelim
Date delimiter. This is the character that separates the specified elements of a date string. The default is "/". This field is updated by _dtc_init.
dtc_tmformat
DOS time format. The default is 24-hour format (1). This field is updated by _dtc_init. DOS is documented as supporting the following formats:
            0 = 12-hour format (HH:MM:SS.hh AM/PM)
            1 = 24-hour format (HH:MM:SS.hh)
dtc_tmdelim
Time delimiter. This is the character that separates the hours, minutes and seconds elements of a time string. The default is ":". This field is updated by _dtc_init.
dtc_hdelim
Hundredths of seconds time delimiter. This is the character that separates the seconds and hundredths of seconds elements in a time field. The default is ".". This field is updated by _dtc_init to contain the country decimal delimiter character.
dtc_amstr
Address of the "AM" ASCIIZ string. If a string is specified, it is used in place of the default string. If DTC_DEFAULT is specified, the default "AM" string (" AM") is used. The default is DTC_DEFAULT.
dtc_pmstr
Address of the "PM" ASCIIZ string. If a string is specified, it is used in place of the default string. If DTC_DEFAULT is specified, the default "PM" string (" PM") is used. The default is DTC_DEFAULT.
_dtc_init
updates the current date/time conversion options structure (referenced by dtc_addr) with the DOS country information for the current or specified country. Updated information includes the date format, date delimiter, time format, time delimiter, and 100ths delimiter. This function does not need to be called if the dtcopts structure already contains the correct information.

Converting Date/Time to Formatted ASCII Strings

_date_to_asc
Converts date in a structure to a formatted ASCIIZ string.
_date_to_asce
Converts date in a structure to a formatted ASCII string, returning a pointer to the end of the resulting string.
_time_to_asc
Converts time in a structure to a formatted ASCIIZ string.
_time_to_asce
Converts time in a structure to a formatted ASCII string, returning a pointer to the end of the resulting string.
These functions convert date and time values from structure format to formatted ASCII strings. The _date_to_asc and _time_to_asc functions return a pointer to the resulting ASCIIZ string. The _date_to_asce and _time_to_asce functions create a non-NULL terminated ASCII string and return a pointer to the end of the string to facilitate appending of additional data.

The format of the resulting date or time string depends on the current dtcopts structure as well as date and time formatting flags which are specified for each conversion. The current date/time conversion options structure specifies global formatting options such as the country-default time format, the time delimiter character, and the 100ths separator character. These options may be accessed or modified through the dtc_addr variable. (See dtc_addr, above, for further information.) Date and time formatting flags are specified using the following symbolic constants (defined in DATETIME.H):

Date field order flags:

DATE_CDATE
(0x00) Use the country-specific date format ("D/M/Y", "Y/M/D", or "M/D/Y") specified in the current dtcopts structure.
DATE_DMY
(0x01) Use DAY/MONTH/YEAR format.
DATE_YMD
(0x02) Use YEAR/MONTH/DAY format.
DATE_MDY
(0x03) Use MONTH/DAY/YEAR format.
Date field suppress flags:
DATE_NOYR
(0x04) Suppress the YEAR field.
DATE_NOMON
(0x08) Suppress the MONTH field.
DATE_NODAY
(0x10) Suppress the DAY field.
Date leading zero flags (ignored by _asc_to_date):
DATE_PADMON
(0x00) Output a leading zero in the MONTH field.
DATE_NOPADMON
(0x20) No leading zero in MONTH field.
DATE_PADDAY
(0x00) Output a leading zero in the DAY field.
DATE_NOPADDAY
(0x40) No leading zero in DAY field.
Year digits flags (ignored by _asc_to_date):
DATE_2DIGYR
(0x00) Output 2 digits in year field ("95").
DATE_4DIGYR
(0x80) Output 4 digits in year field ("1995").
Two digit year format assumes the years "80" to "99" represent 1980 to 1999 and the years "00" to "79" represent 2000 to 2079.

Time format flags:

TIME_FLEXHR
(0x00) Writes time using the country-specific time format (12-hour or 24-hour) specified in the current dtcopts structure; reads both 12-hour and 24-hour formats.
TIME_12HR
(0x01) Use 12-hour format.
TIME_24HR
(0x02) Use 24-hour format.
TIME_CTIME
(0x03) Use the country-specific time format (12-hour or 24-hour) from the current dtcopts structure.
Time field flags:
TIME_HR
(0x00) Use the HOUR field.
TIME_NOHR
(0x04) Suppress the HOUR field.
TIME_MIN
(0x00) Use the MINUTES field.
TIME_NOMIN
(0x08) Suppress the MINUTES field.
TIME_SEC
(0x00) Use the SECONDS field.
TIME_NOSEC
(0x10) Suppress the SECONDS field.
TIME_100THS
(0x00) Use the 100THS field.
TIME_NO100THS
(0x20) Suppress the 100THS field.
Time leading zero flags (ignored by _asc_to_time):
TIME_PAD
(0x00) Pad first output field with a leading zero (e.g. "01:00" instead of "1:00").
TIME_NOPAD
(0x40) Do not pad first field with a leading zero.
Equates with a value of zero are the defaults and are provided for completeness only.

Converting Date/Time Strings to Structure Format

_asc_to_date
Converts a formatted ASCII date string to structure date.
_asc_to_time
Converts a formatted ASCII time string to structure date.
These functions convert formatted or standard ASCII date and time strings to structure format. The expected format of the input date or time string is specified in the current dtcopts structure as well as in the date and time formatting flags which are specified for each conversion. Date and time formatting flags are described in the previous section. For information about the dtcopts structure, see dtc_addr, above.

Only those subfields specified in the formatting flags are actually read and stored; the remaining fields are neither read nor stored. This means that only the specified portions of the destination structure are modified on a successful read. It also means that the input value must be in the specified format or the field values may be interpreted incorrectly.

An invalid format error is returned if fewer fields were read than expected, a required field is zero or empty, or no interpretable characters were found in a field. For example, if three date fields are expected when calling ASC_TO_DATE, then "//92", "2/A/92", or "11/12" would generate invalid format errors. An overflow error condition is also returned if numeric overflow occurs in any required field (i.e., the read value exceeds the maximum allowed value for that field).

If a date or time string contains more fields than are required for the read, the leading fields must match the specified format and trailing fields are ignored. For example, if DATE_MDY+DATE_NOYR were specified, then "12/31" and "12/31/95" would both be read correctly. If DATE_MDY+DATE_NODAY were specified (only month and year expected), an input string of "12/31" would be interpreted as representing month 12 of the year 2031.

Year values in date strings are read in whatever format is encountered (2-digit or 4-digit) regardless of the format specified in the date format flags. When 2-digit year values are converted, values from 80 to 99 are assumed to represent the years 1980 through 1999; other values represent years from 2000 to 2079.

The time format (12- or 24-hour) specified in the format flags is significant. If 12-hour format is specified, then the scripted input function requires a am/pm indicator after the time. This indicator must match the am/pm strings specified in the current date/time conversion options structure, or it must match "a", "p", "am", or "pm" (case-insensitive comparison) with at most one space character between the last digit of the time and the am/pm indicator. An invalid format error is returned if no am/pm indicator is found when 12-hour time format has been specified. This error is also returned if TIME_FLEXHR is specified, the hour value is greater than 12, and a trailing am/pm string is encountered. If 24-hour format is specified, then no attempt is made to match am/pm strings.

These functions do not automatically skip leading white space characters. To skip white space characters, call _str_skipw or _str_skips before calling these formatting functions.

Measuring Time and Delaying Program Execution

_init_msec
Calibrates the millisecond timer.
_sleep_msec
Suspends program execution for a specified number of milliseconds.
_sleep_sec
Suspends program execution for a specified number of hundredths of a second.
_tmr_read
Returns the elapsed time since the last call to _tmr_reset.
_tmr_reset
Begins a new timing sequence.
These functions measure elapsed time and cause program execution to be suspended for specified periods of time.

_tmr_reset and _tmr_read measure elapsed time. _tmr_reset establishes the starting point from which elapsed time is measured. Elapsed time may then be read at any time by calling _tmr_read. The timing sequence is not interrupted when _tmr_read is called. Subsequent calls to _tmr_read continue to report elapsed time from the initial starting point. _tmr_reset may be called at any time to establish a new starting point for the timing sequence. Interrupts must be enabled for these functions to work properly. These functions only support timing sequences of less than 24 hours. Longer sequences may be tracked by periodically calling _tmr_read, adding the elapsed time into the total elapsed time, and then calling _tmr_reset to establish a new starting point.

_tmr_reset and _tmr_read depend on the system clock. This means that they have a maximum resolution of .0549 seconds with an accuracy of ñ .0549 seconds.

_sleep_sec and _sleep_msec suspend execution for a specified period of time. _sleep_sec is a simple function which has a resolution of 1 clock tick (.0549 seconds) with an effective accuracy of ñ .0549 seconds. _sleep_msec is about four times as large as _sleep_sec, but it has millisecond resolution (.001 seconds) with an accuracy of better than ñ .0005 seconds. Since _sleep_sec depends on the system clock, interrupts must be enabled when it is called. _sleep_msec, on the other hand, depends on the "millisecond timer", which measures elapsed time by continuously monitoring system timer zero. Therefore, _sleep_msec may be used with interrupts disabled to obtain even greater accuracy.

_init_msec is provided for use with _sleep_msec and other functions which use the millisecond timer. _init_msec calibrates the millisecond timer by determining the operating mode and reload count of system timer zero. This allows _sleep_msec and other millisecond timer functions to operate properly even if the system timer has been reprogrammed. _init_msec should always be called after reprogramming timer zero. If _init_msec is never called, _sleep_msec assumes that timer zero is operating in the system default mode (mode three with a reload value of zero).

Sample Program

These date/time functions make it very easy to manipulate and analyze dates and times. The following example uses several date/time functions to calculate the number of days from the current date to a given date, then calculates the day of the week for that date:

#include 
#include 
#include 
#define CR 13
void main()
{
   char *strindx;
   char day, datebuf[5];
   unsigned num_days, curr_days, future_days;
   static std_date date;

   _get_date(&date);
   curr_days = _date_to_days(&date);      /* get current number of days since 01-01-1980 */
    do
     {
       _put_newline();
       do {
       _put_str("Enter month: ");
       date.month = _dec_to_c(_get_str(datebuf, 2), &strindx);
       } while (date.month < 1 || date.month > 12);
       do {
       _put_str("Enter day: ");
       date.day = _dec_to_c(_get_str(datebuf, 2), &strindx);
       } while (date.day < 1 || date.day > 31);
       do {
       _put_str("Enter year: ");
       date.year = _dec_to_i(_get_str(datebuf, 4), &strindx);
       } while (date.year < 1980 || date.year > 2080);
        future_days = _date_to_days(&date);        /* number of days from 01-01-1980 to ?date */
        num_days = future_days - curr_days;        /* num_days = days remaining until ?date */
       _put_newline();
       _put_str("Number of days to ");
       _days_to_date(future_days,&date);        /* convert to actual date (June 31 = July 1) */
       _put_str(_asc_date(datebuf,&date)); _put_str(" = ");
       _put_str(_i_to_dec(num_days, datebuf));  /* display number of days to ?date */
       _put_newline();
       _put_str("The day is: ");
       switch (_get_day(&date))
         {
           case 0: _put_str("Sunday"); break;
           case 1: _put_str("Monday"); break;
           case 2: _put_str("Tuesday"); break;
           case 3: _put_str("Wednesday"); break;
           case 4: _put_str("Thursday"); break;
           case 5: _put_str("Friday"); break;
           case 6: _put_str("Saturday"); break;
           default: break;
         }
       _put_str("\n\n\r to continue: ");
     } while(_get_chre() == CR);
}
The sample program shown above (CALCDAY.C) is provided on the distribution diskettes and may be compiled and linked using the following Microsoft C and Borland C command lines:

Microsoft C:


cl /c /I\msc\include /I\sa\include calcday.c
link calcday,calcday,,\sa\lib\_sas \msc\lib\slibce
Borland C:

bcc /c /ms /I\bc\include /I\sa\include calcday.c
tlink \bc\lib\c0s calcday,calcday,,\sa\lib\_sas \bc\lib\cs