TOC PREV NEXT INDEX

Put your logo here!


12 The Date & Time Module (datetime.hhf)


HLA contains a set of procedures and functions that simplify correct date and time calculations. Since these are two logical modules combined into a single module, this documentation will discuss the date and time routines separately within this section.

12.1 Date Functions

The date namespace defines the following useful identifiers:

date.daterec
 

Date representation. This is a dword object containing m, d, and y fields holding the obvious values. The y field is a 16-bit quantity supporting years 0..9,999. No Y2K problems here! (Of course, it does suffer from Y10K, but that's probably okay.) Since the Gregorian calendar began use in Oct, 1582, there is really no need to represent dates any earlier than this. In fact, many date calcuations will not allow dates earlier than Jan 1, 1600 for this very reason. The limitation of year 9999 is an arbitrary limit set in the library to help catch wild values. If you really need dates beyond 9999, feel free to modify the date validation code. The m and d fields are both byte objects. The date validation routines enforce the month limits of 1..12 and appropriate day limits (depending on the month and year).

date.IsLeapYear( y:uns32 ) @returns( "al" );
 
date.IsLeapYear( dr:date.daterec ) @returns( "al" );
 

This is an overloaded function. You may either pass it an unsigned integer containing the year or a date.daterec value specifying a m/d/y value. These functions return true or false in the AL register depending upon whether the parameter is a leap year (true if it is a leap year). Note that this function will be correct until sometime between the years 3000 and 4000, at which point people will probably have to agree upon adding an extra leap day in at some point (no such agreement has been made today, hence the absence from this function).

date.validate( m:byte; day:byte; year:word );
 
date.validate( dr:date.daterec )
 

These two functions check the date passed as a parameter and raise an ex.InvalidDate exception if the data in the fields (or the m/d/y) parameter is not a valid date between 1/1/1600 and 12/31/9999.

date.isValid( m:byte; day:byte; year:word );
 
date.isValid( dr:date.daterec )
 

Similar to the date.validate procedures, except these functions return true/false in the AL register if the date is valid/invalid. They do not raise an exception.

date.outputFormat
 

This is an enumerated data type that defines the following constants:

mdyy, mdyyyy, mmddyy, mmddyyyy, yymd, yyyymd, yymmdd, yyyymmdd, MONdyyyy, and MONTHdyyyy. These constants control the date output format in the (mostly) obvious way. Note that mdyy can output one digit for the day and month while mmddyy always inputs two digits for each field. The MONdyyyy format outputs dates in the form "Jan 1, 2000" while the MONTHdyyyy outputs the dates using the format "January 1, 2000".

date.setFormat( fmt : OutputFormat );
 

This sets the internal format variable to the date.OutputFormat value you specify. This constant must be one of the date.OutputFormat enumerated constants or date.SetFormat will raise an ex.InvalidDateFormat exception.

date.setSeparator( chr:char );
 

This procedure sets the internal date separator character (default is '/') to the character you pass as a parameter. This is used when printing dates and converting dates to strings.

date.toString( m:byte; d:byte; y:word; s:string );
 
date.toString( dr:date.daterec; s:string);
 

These functions will convert the specified date to a string (using the output format specified by date.SetFormat and the separator character specified by date.SetSeparator) and store the result in the specified string. An ex.StringOverflow exception occurs if the destination string's MaxStrLen field is too small (generally, 20 characters handles all string formats).

date.a_toString( m:byte; d:byte; y:word ); @returns( "eax" );
 
date.a_toString( dr:date.daterec );  @returns( "eax" );
 

This procedures are similar to the date.toString procedures above except they automatically allocate the storage for the string and return a pointer to the string object in the EAX register. You should free the string storage with strfree with you are done with this string.

date.print( m:byte; d:byte; y:word );
 
date.print( dr:date.daterec );
 

These two procedures write the specified string to the standard output device. They call date.a_toString to convert the date and then they print it using stdout.puts. Sorry, no file versions (yet), but you can always call date.toString yourself. In the future it would be better to implement the date object as a class so that the XXXX.put macros handle dates automatically.

date.Julian( m:byte; d:byte; y:word ); @returns( "eax" );
 
date.Julian( dr:date.daterec ); @returns( "eax" );
 

These functions convert the Gregorian (i.e., standard) date passed as a parameter into a "Julian day number." A Julian day number treats Jan 1, 4713 as day zero and simply numbers dates forward from that point. For example, Oct 9, 1995 is JD 2,450,000. Jan 1, 2000 is JD 2,452,545. Julian dates make date calculations of the form "what date is it X days from now?" trivial. You just compute JD+X to get the new date. In fact, for all X greater than some number (probably between 365 and 1,000) it's more efficient to compute a Gregorian date to a Julian date, add the days to the Julian Date, and then convert the Julian date back to a Gregorian date, than it is to add the dates directly into the Gregorian date (which is a real pain).

Note: technically, a "Julian Date" is not the same thing as a "Julian Day Number". A Julian date is based on the Julian Calendar created by Julius Caesar in about 45 BC. It was very similar to our current calendar except they didn't get the leap years quite right. Julian Day numbers are a different calendar system that, as explained above, number days consecutively after Jan 1 4713 BC (resetting to day one 7980 years later). Out of sheer laziness, this document will use the term "Julian Date" as a description of the calendar based on Julian day numbers despite that fact that this is technically incorrect.

date.fromJulian( jd:uns32; var gd:date.daterec ); 
 

This procedure converts a Julian date to a Gregorian date. The Julian date is the first parameter, the second (reference) parameter is a Gregorian date variable (data.daterec). See the note above about adding some number of days to a Gregorian date via translation to Julian. Note that adding months or years to a Julian date is a real pain in the rear. It's generally easier and faster to convert the Julian date to a Gregorian date, add the months and/or years to the Gregorian date (which is relatively easy), and then convert the whole thing back to a Julian day number.

date.dayNumber( m:byte; d:byte; y:word ); @returns( "eax" );
 
date.dayNumber( dr:date.daterec ); @returns( "eax" );
 

These functions convert the Gregorian date passed as a parameter into a day number into the current year (often erroneously called a "Julian Date" since NASA adopted this terminology in the late sixties). These functions return a value between 1 and 365 or 366 (for leap years) in the EAX register. Jan 1 is day 1, Dec 31 is day 365 or day 366.

date.daysLeft( m:byte; d:byte; y:word ); @returns( "eax" );
 
date.daysLeft( dr:date.daterec ); @returns( "eax" );
 

These functions return the number of days left in the current year counting the date passed as a parameter (hence Dec 31, yyyy always returns one).

date.dayOfWeek( m:byte; d:byte; y:word ); @returns( "eax" );
 
date.dayOfWeek( dr:date.daterec ); @returns( "eax" );
 

These functions return, in EAX, a value between zero and six denoting the day of the week of the given Gregorian date (0=sun, 1=mon, etc.)

date.daysBetween( m1:byte; d1:byte; y1:word; m2:byte; d2:byte; y2:word  );
 
	@returns( "eax" );
 
date.daysBetween( m1:byte; d1:byte; y1:word; dr:date.daterec ); 
 
	@returns( "eax" );
 
date.daysBetween( dr:date.daterec; m:byte; d:byte; y:word );
 
	@returns( "eax" );
 
date.daysBetween( dr1:date.daterec; dr2:date.daterec );
 
	@returns( "eax" );
 

These functions return an uns32 value in EAX that gives the number of days between the two specified dates. These functions work directly on the Gregorian dates. As noted earlier, these functions work okay if the number of days between the two dates is small (the smaller the better, but anything beyond 1,000 days is probably getting out of hand). This function runs in approximately O(n) time where n is the number of years between the dates. As such, it can be quite slow for dates that are widely separated. To calculate the number of dates between two dates that are widely separate, it is probably a lot better to convert the two Gregorian dates to Julian day numbers and take the difference of those two day numbers. For dates that are near one another (e.g., within the same year or within a few years of one another), date.daysBetween is probably more efficient that the Gregorian->Julian calculation. Certainly date.daysBetween is more convenient to use!

date.datePlusDays( days:uns32; var dr:date.daterec ); 
 

This procedure adds the first parameter (in days) directly to the Gregorian data variable passed by reference as the second parameter. As with date.daysBetween, this function runs in O(n) time where n is the number of years. If n is small, this is a very efficient routine. However, if n is large, you would be well advised to convert the Gregorian date to a Julian day number, add the days to the Julian day number, and then convert the result back to Gregorian. For n less than a couple years, date.datePlusDays is probably the best way to go.

date.datePlusMonths( months:uns32; var dr:date.daterec ); 
 

This procedure adds the first parameter (in months) directly to the Gregorian data variable passed by reference as the second parameter. This is a very efficient routine. In fact, if you want to add some number of months to a Julian day value, the only reasonable way to do it is to convert the Julian day number to a Gregorian date, use this function, and then convert it back to a Julian day number.

Note: there is no date.datePlusYears function. You can use an ADD instruction to directly add a years value to a Gregorian date. For Julian dates, it's still easier to convert it to Gregorian, add the years value to the date, and then convert it back (this handles leap years properly).

date.today( var dr:date.daterec ); 
 

Stores the local date (today's date) into the specified parameter.

12.2 Time Functions

While not as extensive as the date functions, the set of time functions the HLA library provides are still useful. (There aren't as many time functions because time isn't as messed up as the calendar.)

time.timerec
 

This is a data structure with the following fields:


 
s:byte;
 
m:byte;
 
h:word;
 

 

The HLA time module defines time in these terms. The fields are stored in memory so you may compare two time structures as uns32 objects to determine <, =, >.

time.curTime( theTime: time.timerec );
 

This returns the local time (provided by the system clock) in the specified time variable.

time.hmsToSecs( theTime: time.timerec); @returns( "eax");
 
time.hmsToSecs( h:uns16; m:byte; s:byte ); @returns( "eax");
 

These two functions convert a time span in HHMMSS format to some number of seconds (if HHMMSS is the time of day, then these functions return the time in seconds since midnight). Note that HHMMSS does not have to be a 12-hour or 24-hour clock value. You may specify any number of hours between 0 and 65535, and any number of seconds or minutes between 0 and 255 for this function.

time.secsToHMS( seconds:uns32; var HMS:time.timerec );
 

This function converts the seconds parameter to an HMS time value. The first parameter must be less than 235,929,600 since this is the maximum time representable by 65535 hours. If the seconds parameter exceeds this value, then time.secsToHMS will raise an ex.TimeOverflow exception.

Someday I'll have to add time output routines and millisecond time routines. But that's all for now!



TOC PREV NEXT INDEX