6.4 HLA Date Functions
HLA provides a wide array of date functions you can use to manipulate date objects. The following subsections describe many of these functions and how you use them.
6.4.1 date.IsValid and date.validate
When storing data directly into the fields of a date.daterec object, you must be careful to ensure that the resulting date is correct. The HLA date procedures will raise an ex.InvalidDate exception if the date values are out of range. The date.IsValid and date.validate procedures provide some handy code to check the validity of a date object. These two routines use either of the following calling seqeuences:
date.IsValid( dateVar ); // dateVar is type date.daterec date.IsValid( m, d, y ); // m, d, y are uns8, uns8, uns16, respectively date.validate( dateVar ); // See comments above. date.validate( m, d, y );The date.IsValid procedure checks the date to see if it is a valid date. This procedure returns true or false in the AL register to indicate whether the date is valid. The date.validate procedure also checks the validity of the date; however, it raises the ex.InvalidDate exception if the date is invalid. The following sample program demonstrates the use of these two routines:
program DateTimeDemo; #include( "stdlib.hhf" ); static m: uns8; d: uns8; y: uns16; theDate: date.daterec; begin DateTimeDemo; try stdout.put( "Enter the month (1-12):" ); stdin.get( m ); stdin.flushInput(); stdout.put( "Enter the day (1-31):" ); stdin.get( d ); stdin.flushInput(); stdout.put( "Enter the year (1583-9999): " ); stdin.get( y ); if( date.isValid( m, d, y )) then stdout.put( m, "/", d, "/", y, " is a valid date." nl ); endif; // Assign the fields to a date variable. mov( m, al ); mov( al, theDate.month ); mov( d, al ); mov( al, theDate.day ); mov( y, ax ); mov( ax, theDate.year ); // Force an exception if the date is illegal. date.validate( theDate ); exception( ex.ConversionError ) stdout.put ( "One of the input values contained illegal characters" nl ); exception( ex.ValueOutOfRange ) stdout.put ( "One of the input values was too large" nl ); exception( ex.InvalidDate ) stdout.put ( "The input date (", m, "/", d, "/", y, ") was invalid" nl ); endtry; end DateTimeDemo; Program 6.1 Date Validation Example6.4.2 Checking for Leap Years
Determining whether a given year is a leap year is somewhat complex. The exact algorithm is "any year that is evenly divisible by four and is not evenly divisible by 100 or is evenly divisible by 400 is a leap year1." The HLA "datetime.hhf" module provides a convenient function, date.IsLeapYear, that efficiently determines whether a given year is a leap year. There are two different ways you can call this function; either of the following will work:
date.IsLeapYear( dateVar ); // dateVar is a date.dateRec variable. date.IsLeapYear( y ); // y is a word value.The following code demonstrates the use of this routine.
program DemoIsLeapYear; #include( "stdlib.hhf" ); static m: uns8; d: uns8; y: uns16; theDate: date.daterec; begin DemoIsLeapYear; try stdout.put( "Enter the month (1-12):" ); stdin.get( m ); stdin.flushInput(); stdout.put( "Enter the day (1-31):" ); stdin.get( d ); stdin.flushInput(); stdout.put( "Enter the year (1583-9999): " ); stdin.get( y ); // Assign the fields to a date variable. mov( m, al ); mov( al, theDate.month ); mov( d, al ); mov( al, theDate.day ); mov( y, ax ); mov( ax, theDate.year ); // Force an exception if the date is illegal. date.validate( theDate ); // Okay, report whether this is a leap year: if( date.isLeapYear( theDate )) then stdout.put( "The year ", y, " is a leap year." nl ); else stdout.put( "The year ", y, " is not a leap year." nl ); endif; // Technically, the leap day is Feb 25, but most people don't // realize this, so use the following output to keep them happy: if( date.isLeapYear( y )) then if( m = 2 ) then if( d = 29 ) then stdout.put( m, "/", d, "/", y, " is the leap day." nl ); endif; endif; endif; exception( ex.ConversionError ) stdout.put ( "One of the input values contained illegal characters" nl ); exception( ex.ValueOutOfRange ) stdout.put ( "One of the input values was too large" nl ); exception( ex.InvalidDate ) stdout.put ( "The input date (", m, "/", d, "/", y, ") was invalid" nl ); endtry; end DemoIsLeapYear; Program 6.2 Calling the date.IsLeapYear Function6.4.3 Obtaining the System Date
The date.today function returns the current system date in the date.daterec variable you pass as a parameter2. The following program demonstrates how to call this routine:
program DemoToday; #include( "stdlib.hhf" ); static TodaysDate: date.daterec; begin DemoToday; date.today( TodaysDate ); stdout.put ( "Today is ", (type uns8 TodaysDate.month), "/", (type uns8 TodaysDate.day), "/", (type uns16 TodaysDate.year), nl ); // Okay, report whether this is a leap year: if( date.isLeapYear( TodaysDate )) then stdout.put( "This is a leap year." nl ); else stdout.put( "This is not a leap year." nl ); endif; end DemoToday; Program 6.3 Reading the System DateLinux users should be aware that date.today returns the current date based on Universal Coordinated Time (UTC). Depending upon your time zone, date.today may return yesterday's or tomorrow's date within your particular timezone.
6.4.4 Date to String Conversions and Date Output
The HLA date module provides a set of routines that will convert a date.daterec object to the string representation of that date. HLA provides a mechanism that lets you select from one of several different conversion formats when translating dates to strings. The date package defines an enumerated data type, date.OutputFormat, that specifies the different conversion mechanisms. The possible conversions are (these examples assume you are converting the date January 2, 2033):
date.mdyy - Outputs date as 1/2/33. date.mdyyyy - Outputs date as 1/2/2033. date.mmddyy - Outputs date as 01/02/33. date.mmddyyyy - Outputs date as 01/02/2033. date.yymd - Outputs date as 33/1/2. date.yyyymd - Outputs date as 2033/1/2. date.yymmdd - Outputs date as 33/01/02. date.yyyymmdd - Outputs date as 2033/01/02. date.MONdyyyy - Outputs date as Jan 1, 2033. date.MONTHdyyyy - Outputs date as January 1, 2033.To set the conversion format, you must call the date.SetFormat procedure and pass one of the above values as the single parameter3. For all but the last two formats above, the default month/day/year separator is the slash ("/") character. You can call the date.SetSeparator procedure, passing it a single character parameter, to change the separator character.
The date.toString and date.a_toString procedures convert a date to string data. Like the other string routines this chapter discusses, the difference between the date.toString and date.a_toString procedures is that date.a_toString automatically allocates storage for the string whereas you must supply a string with sufficient storage to the date.toString procedure. Note that a string containing 20 characters is sufficient for all the different date formats. The date.toString and date.a_toString procedures use the following calling sequences:
date.toString( m, d, y, s ); date.toString( dateVar, s ); date.a_toString( m, d, y ); date.a_toString( dateVar );Note that m and d are byte values, y is a word value, dateVar is a date.dateRec value, and s is a string variable that must point at a string that holds at least 20 characters.
The date.Print procedure uses the date.toString function to print a date to the standard output device. This is a convenient function to use to display a date after some date calculation.
The following program demonstrates the use of the procedures this section discusses:
program DemoStrConv; #include( "stdlib.hhf" ); static TodaysDate: date.daterec; s: string; begin DemoStrConv; date.today( TodaysDate ); stdout.put( "Today's date is " ); date.print( TodaysDate ); stdout.newln(); // Convert the date using various formats // and display the results: date.setFormat( date.mdyy ); date.a_toString( TodaysDate ); mov( eax, s ); stdout.put( "Date in mdyy format: `", s, "`" nl ); strfree( s ); date.setFormat( date.mmddyy ); date.a_toString( TodaysDate ); mov( eax, s ); stdout.put( "Date in mmddyy format: `", s, "`" nl ); strfree( s ); date.setFormat( date.mdyyyy ); date.a_toString( TodaysDate ); mov( eax, s ); stdout.put( "Date in mdyyyy format: `", s, "`" nl ); strfree( s ); date.setFormat( date.mmddyyyy ); date.a_toString( TodaysDate ); mov( eax, s ); stdout.put( "Date in mmddyyyy format: `", s, "`" nl ); strfree( s ); date.setFormat( date.MONdyyyy ); date.a_toString( TodaysDate ); mov( eax, s ); stdout.put( "Date in MONdyyyy format: `", s, "`" nl ); strfree( s ); date.setFormat( date.MONTHdyyyy ); date.a_toString( TodaysDate ); mov( eax, s ); stdout.put( "Date in MONTHdyyyy format: `", s, "`" nl ); strfree( s ); end DemoStrConv; Program 6.4 Date <-> String Conversion and Date Output Routines6.4.5 date.unpack and data.pack
The date.pack and date.unpack functions pack and unpack date data. The calling syntax for these functions is the following:
date.pack( y, m, d, dr ); date.unpack( dr, y, m, d );Note: y, m, d must be uns32 or dword variables; dr must be a date.daterec object.
The date.pack function takes the y, m, and d values and packs them into a date.daterec format and stores the result into dr. The date.unpack function does just the opposite. Neither of these routines check their parameters for proper range. It is the caller's resposibility to ensure that d's value is in the range 1..31 (as appropriate for the month and year), m's value is in the range 1..12, and y's value is in the range 1583..9999.
6.4.6 date.Julian, date.fromJulian
These two functions convert a Gregorian date to and from a Julian day number4. Julian day numbers specify January 1, 4713 BCE as day zero and number the days consecutively from that point5. One nice thing about Julian day numbers is that date calculations are very easy. You can compute the number of days between two dates by simply subtracting them, you can compute new dates by adding an integer number of days to a Julian day number, etc. The biggest problem with Julian day numbers is converting them to and from the Gregorian Calendar with which we're familiar. Fortunately, these two functions handle that chore. The syntax for calling these two functions is:
date.fromJulian( julian, dateRecVar ); date.Julian( m, d, y ); date.Julian( dateRecVar );The first call above converts the Julian day number that you pass in the first parameter to a Gregorian date and stores the result into the date.daterec variable you pass as the second parameter. Keep in mind that Julian day numbers that correspond to dates before Jan 1, 1582, will not produce accurate calendar dates since the Gregorian calendar did not exist prior to that point.
The second two calls above compute the Julian day number and return the value in the EAX register. They differ only in the types of parameters they expect. The first call to date.Julian above expects three parameters, m and b being byte values and y being a word value. The second call expects a date.daterec parameter; it extracts those three fields and converts them to the Julian day number.
6.4.7 date.datePlusDays, date.datePlusMonths, and date.daysBetween
These two functions provide some simple date arithmetic.operations. The compute a new date by adding some number of days or months to an existing date. The calling syntax for these functions is
date.datePlusDays( numDays, dateRecVar ); date.datePlusMonths( numMonths, dateRecVar );Note: numDays and numMonths are uns32 values, dateRecVar must be a date.daterec variable.
The date.datePlusDays function computes a new date that is numDays days beyond the date that dateRecVar specifies. This function leaves the resulting date in dateRecVar. This function automatically compensates for the differing number of days in each month as well as the differing number of days in leap years. The date.datePlusMonths function does a similar calculation except it adds numMonths months, rather than days to dateRecVar.
The date.datePlusDays function is not particularly efficient if the numDays parameter is large. There is a more efficient way to calculate a new date if numDays exceeds 1,000: convert the date to a Julian Day Number, add the numDays value directly to the Julian Number, and then convert the result back to a date.
The date.daysBetween function computes the number of days between two dates. Like date.datePlusDays, this function is not particularly efficient if the two dates are more than about three years apart; it is more efficient to compute the Julian day numbers of the two dates and subtract those values. For spans of less than three years, this function is probably more efficient. The calling sequence for this function is the following:
date.daysBetween( m1, d1, y1, m2, d2, y2 ); date.daysBetween( m1, d1, y1, dateRecVar2 ); date.daysBetween( dateRecVar1, m2, d2, y2 ); date.daysBetween( dateRecVar1, dateRecVar2 );The four different calls allow you to specify either date as a m/d/y value or as a date.daterec value. The m and d parameters in these calls must be byte values and the y parameter must be a word value. The dateRecVar1 and dateRecVar2 parameters must, obviously, be date.daterec values. These functions return the number of days between the two dates in the EAX register. Note that the dates must be valid, but there is no requirement that the first date be less than the second date.
6.4.8 date.dayNumber, date.daysLeft, and date.dayOfWeek
The date.dayNumber function computes the day number into the current year (with Jan 1 being day number one) and returns this value in EAX. This value is always in the range 1..365 (or 1..366 for leap years). A call to this function uses the following syntax:
date.dayNumber( m, d, y ); date.dayNumber( dateRecVar );The two forms differ only in the way you pass the date. The first call above expects two byte values (m and d) and a word value (y). The second form above expects a date.daterec value.
The date.daysLeft function computes the number of days left in a year. This function returns the number of days left in a year counting the date you pass as a parameter. Therefore, this function returns one for Dec 31st. Like date.dayNumber, this function always returns a value in the range 1..365/366 (regular/leap year). The calling syntax for this function is similar to date.dayNumber, it is
date.daysLeft( m, d, y ); date.daysLeft( dateRecVar );The parameters have the same meaning as for date.dayNumber.
The date.dayOfWeek function accepts a date and returns a day of week value in the EAX register. A call to this function uses the following syntax:
date.dayOfWeek( m, d, y ); date.dayOfWeek( dateRecVar );The parameters have their usual meanings.
These function calls return a value in the range 0..7 (in EAX) as follows:
- 0: Sunday
- 1: Monday
- 2: Tuesday
- 3: Wednesday
- 4: Thursday
- 5: Friday
- 6: Saturday
1The Gregorian Calendar does not account for the fact that sometime between the years 3,000 and 4,000 we will have to add an extra leap day to keep the Calendar in sync with the Earth's rotation around the Sun. The HLA date.IsLeapYear does not handle this situation either. Keep this in mind if you are doing date calculations that involve dates after the year 3,000. This is a defect in the current definition of the Gregorian Calendar, which HLA's routines faithfully reproduce.
2This function was not available in the Linux version of the HLA Standard Library as this was written. It may have been added by the time you read this, however.
3the date.SetFormat routine raises the ex.InvalidDateFormat exception if the parameter is not one of these values.
4Note that a Julian date and a Julian day number are not the same thing. Julian dates are based on the Julian Calendar, commisioned by Julius Caesar, which is very similar to the Gregorian Calendar; Julian day numbers were invented in the 1800's and are primarily used by astronomers.
5Jan 1, 4713 BCE was chosen as a date that predates recorded history.
|