GRASS DateTime Library



Introduction

Purpose

This Library may be used to record, manipulate, and perform arithmetic on date and time information. It is anticipated that GRASS database access routines will utilize this library to "timestamp" data files and perform temporal analysis. This library could also be used to generate, format and compare dates for labels, titles, or site descriptions.

Relative vs. Absolute

Successfully using this library requires understanding the two basic modes of DateTimes: 1) Absolute DateTimes express a single time or date referenced to the Gregorian calendar (e.g. 14 Feb 1995), and 2) Relative DateTimes express a difference or length of time (e.g., 201 days 6 hours). An interval for a DateTime is defined by its greatest unit (from) and its smallest unit (to). The absolute DateTime "14 Feb 1995" has the interval: from=year, to=day. There are specific rules for legal intervals. The mode and interval define the "type" of a DateTime. When doing DateTime artithmetic certain type combinations are not allowed because the result would be undefined.

Calendar Assumptions

This library uses the modern Gregorian calendar, correcting for leap years using the convention: ((year%4 == 0 && year%100 != 0) || year%400 == 0) , but also extrapolating those leap years back in time. There are no leap second corrections and there is no correction for the missing 11 days of September 1752 (in some locales) or prior corrections (in other locales). The year is always considered to start on January 1 and end 365 or 366 days later on December 31.

Authors

Michael Shapiro & Bill Brown

Include Files

       #include < datetime.h >

DateTime Structure

    typedef struct {
	int mode; 	     /* absolute or relative */
	int from, to;        /* range of values */
	int positive;	     /* positive/negative datetime */
	int year, month, day;
	int hour, minute;
	double second;
	int fracsec;	     /* #decimal place in printed seconds */
	int tz;		     /* timezone - minutes from UTC */
    } DateTime;
DateTimes have a 3-part type consisting of mode and range qualifiers from and to.

mode: one of
    #define DATETIME_ABSOLUTE 1
    #define DATETIME_RELATIVE 2
from, to: one of
    #define DATETIME_YEAR   1
    #define DATETIME_MONTH  2
    #define DATETIME_DAY    3
    #define DATETIME_HOUR   4
    #define DATETIME_MINUTE 5
    #define DATETIME_SECOND 6
year, month, day, hour, minute, second: fracsec: tz: positive:

C - API

Function Index (by function)
Function Index (alphabetical)

Contents:


Error Handling

All datetime functions that return int status codes should return: and register the error with a call to datetime_error()

Applications can test for error by:

    if (datetime_function() < 0) {process the error}
int datetime_error (int code, char *msg)
record 'code' and 'msg' as error code/msg (in static variables)
code==0 will clear the error (ie set msg=NULL)
returns 'code' so that it can be used like:
return datetime_error (-1, "bad date");

char * datetime_get_error_msg ()
returns pointer to static error msg (which is NULL if no error)

int datetime_get_error_code ()
returns error code

void datetime_clear_error ()
clears error code and message

Ascii Representation

    The ascii representation of DateTime is:

    ABSOLUTE:  15 Jan 1994 [bc] 10:35:23.456 -0500
    RELATIVE:  [-] 2 years 5 months
	       [-] 100 days 15 hours 25 minutes 35.34 seconds
    The parts can be missing.
    ABSOLUTE:  1994 [bc]
	       Jan 1994 [bc]
	       15 jan 1000 [bc]
	       15 jan 1994 [bc] 10 [+0000]
	       15 jan 1994 [bc] 10:00 [+0100]
	       15 jan 1994 [bc] 10:00:23.34 [-0500]
    RELATIVE:  [-] 2 years
	       [-] 5 months
	       [-] 2 years 5 months
	       [-] 100 days
	       [-] 15 hours 25 minutes 35.34 seconds
	       [-] 100 days 25 minutes
	       [-] 1000 hours 35.34 seconds
		    etc.
    NOTE: values missing between the from/to are assumed 
    to be zero; when scanning, they can be missing; when 
    formatting they will appear as 0 (to preserve the from/to):

       1000 hours 0 minutes 35.34 seconds
       0 days 10 hours 0 minutes

    NOTE: when scanning the from/to are determined by the 
    fields present. Compare:
       10 hours 0 minutes 35.34 seconds [from=HOUR,to=SECOND]
    and
       0 days 10 hours 0 minutes 35.34 seconds [from=DAY,to=SECOND]
int datetime_scan (DateTime *dt, char *string)
Convert the ascii string into a DateTime. This determines the mode/from/to based on the string, inits 'dt' and then sets values in 'dt' based on the 'string'

Returns 0 if 'string' is legal, -1 if not.

void datetime_format (DateTime *dt, char *string)
Convert 'dt' to a printable string. 'string' should be large enough to hold the result, perhaps 80 bytes.

Initializing, Creating and Checking DateTime Structures

int datetime_set_type (DateTime *dt; int mode, from, to, fracsec)

void datetime_get_type (DateTime *dt; int *mode, *from, *to, *fracsec)
extract the mode, from, to, and fracsec out of dt.

int datetime_check_type (DateTime *dt)
checks the mode/from/to/fracsec in dt.

returns:

int datetime_is_valid_type (DateTime *dt)
returns:
1 if datetime_check_type() returns 0
0 if not.

int datetime_change_from_to (DateTime *dt; int from, to; int round)

int datetime_is_absolute (DateTime *dt)
Returns:
1 if dt.mode is absolute
0 if not (even if dt.mode is not defined)

int datetime_is_relative (DateTime *dt)
Returns
1 if dt.mode is relative
0 if not (even if dt.mode is not defined)

void datetime_copy (DateTime *dst, *src)
Copies the DateTime src to dst

int datetime_is_same (DateTime *dt1, *dt2)
Returns
1 if dt1 is exactly the same as dt2
0 if they differ

Getting & Setting Values from DateTime Structure

These routines get/set elements of 'dt'. They return: Values don't get set if they are invalid.

int datetime_set_year (DateTime *dt, int year)
if dt.mode = ABSOLUTE, this also sets dt.day = 0

int datetime_get_year (DateTime *dt, int *year)

int datetime_set_month (DateTime *dt, int month)
if dt.mode = ABSOLUTE, this also sets dt.day = 0

int datetime_get_month (DateTime *dt, int *month)

int datetime_set_day (DateTime *dt, int day)
if dt.mode = ABSOLUTE, then 'day' must be less than or equal to the number of days in the dt.year,dt.month:
if (day > datetime_days_in_month (dt.year, dt.month))
{error}

This implies that year/month must be set before days for ABSOLUTE datetimes.

int datetime_get_day (DateTime *dt, int *day)

int datetime_set_hour (DateTime *dt, int hour)

int datetime_get_hour (DateTime *dt, int *hour)

int datetime_set_minute (DateTime *dt, int minute)

int datetime_get_minute (DateTime *dt, int *minute)

int datetime_set_second (DateTime *dt, double second)

int datetime_get_second (DateTime *dt, double *second)

int datetime_set_fracsec (DateTime *dt, int fracsec)

int datetime_get_fracsec (DateTime *dt, int *fracsec)

int datetime_check_year (DateTime *dt, int year)
Returns:
0 is legal year for dt
-1 illegal year for this dt
-2 dt has no year component

int datetime_check_month (DateTime *dt, int month)
Returns:
0 is legal month for dt
-1 illegal month for this dt
-2 dt has no month component

int datetime_check_day (DateTime *dt, int day)
Returns:
0 is legal day for dt
-1 illegal day for this dt
-2 dt has no day component

Note: if dt.mode is ABSOLUTE, then dt.year and dt.month must also be legal, since the 'day' must be a legal value for the dt.year/dt.month

int datetime_check_hour (DateTime *dt, int hour)

int datetime_check_minute (DateTime *dt, int minute)

int datetime_check_second (DateTime *dt, double second)

int datetime_check_fracsec (DateTime *dt, int fracsec)

DateTime Arithmetic

These functions perform addition/subtraction on datetimes.
int datetime_increment (DateTime *src, *incr)
This function changes the 'src' date/time data based on the 'incr'
The type (mode/from/to) of the 'src' can be anything.
The mode of the 'incr' must be RELATIVE, and the type (mode/from/to) for 'incr' must be a valid increment for 'src'. See datetime_is_valid_increment(), datetime_check_increment()

returns:
0: OK
-1: 'incr' is invalid increment for 'src'

For src.mode ABSOLUTE,

The 'fracsec' in 'src' is preserved.
The 'from/to' of the 'src' is preserved.
A timezone in 'src' is allowed - it's presence is ignored.
NOTE: There is no datetime_decrement() To decrement, set the 'incr' negative.

void datetime_set_positive (DateTime *dt)
Makes the DateTime positive. (A.D. for ABSOLUTE DateTimes)

void datetime_set_negative (DateTime *dt)
Makes the DateTime negative. (B.C. for ABSOLUTE DateTimes)

void datetime_invert_sign (DateTime *dt)

int datetime_is_positive (DateTime *dt)
Returns:
1 if the Datetime is positive
0 otherwise

int datetime_difference (DateTime *a, *b, *result)

int datetime_is_valid_increment (DateTime *src, *incr)
Returns:
datetime_check_increment(src, incr) == 0

int datetime_check_increment (DateTime *src, *incr)
This checks if the type of 'incr' is valid for incrementing/decrementing 'src'.
The type (mode/from/to) of the 'src' can be anything.
The incr.mode must be RELATIVE
A timezone in 'src' is allowed - it's presence is ignored.
To aid in setting the 'incr' type, see datetime_get_increment_type().

Returns:

int datetime_get_increment_type (DateTime *src; int *mode, *from, *to, *fracsec)
This returns the components of a type (mode/from/to/fracsec) that can be used to construct a DateTime object that can be used to increment the 'src'. Also see datetime_set_increment_type().

returns:
0 dt is legal
!=0 why dt is illegal

Implemented as follows:

    *mode    = RELATIVE
    *to      = src.to
    *fracsec = src.fracsec

    if src.mode is ABSOLUTE
	if src.to is in {YEAR,MONTH} then
	    *from = YEAR
	if src.to is in {DAY,HOUR,MINUTE,SECOND} then 
	    *from = DAY

    if src.mode is RELATIVE, then
	*from = src.from

int datetime_set_increment_type (DateTime *src, *incr)
src must be legal
This is a convenience routine which is implemented as follows:
    int mode, from ,to;
    int fracsec;

    if(datetime_get_increment_type(src, &mode, &from, &to, &fracsec))
	return datetime_get_error_code();
    return datetime_set_type (incr, mode, from, to, fracsec);

Timezone

Timezones are represented in minutes from GMT in the range [-720,+780]. For a DateTime to have a timezone, it must be of type ABSOLUTE, and "to" must be in {MINUTE,SECOND}.

The next 3 functions return:
0: OK
-1: mode not ABSOLUTE
-2: dt.to not in {MINUTE,SECOND}
-3: minutes not valid - not in the range [-720,+780]

int datetime_check_timezone (DateTime *dt, int minutes)

int datetime_set_timezone (DateTime *dt, int minutes)

int datetime_get_timezone (DateTime *dt, int *minutes)

int datetime_is_valid_timezone (int minutes)
Returns:
1 OK: -720 <= minutes <= 780 (720 = 12 hours; 780 = 13 hours)
0 NOT OK

void datetime_unset_timezone (DateTime *dt)
Remove timezone from 'dt'
dt.tz = 99*60 (some illegal value)

int datetime_change_timezone (DateTime *dt; int minutes)
if dt has a timezone, increment dt by minutes-dt.tz MINUTES and set dt.tz = minutes

Returns:
0 OK
datetime_check_timezone (dt) if not
-4 if minutes invalid

int datetime_change_to_utc (DateTime *dt)
return datetime_change_timezone (dt, 0);

void datetime_decompose_timezone (int tz, int *hour, int *minute)
tz = abs(tz)
*hour = tz/60
*minute = tz%60

Note: hour,minute are non-negative. Must look at sign of tz itself to see if the tz is negative offset or not. This routine would be used to format tz for output. For example if tz=-350 this would be hour=5 minute=50, but negative. Output might encode this as -0550: printf ("%s%02d%02d", tz<0?"-":"", hour, minute)

int datetime_get_local_timezone (int *minutes)
Returns:
0 OK
-1 local timezone info not available

void datetime_get_local_time (DateTime *dt)
set mode/from/to ABSOLUTE/YEAR/SECOND
set the local time into 'dt'
does not set timezone.

Utilities

int datetime_days_in_month (int month, year)

int datetime_is_leap_year (int year, ad)

int datetime_days_in_year (int year, ad)

Example Application

Follow these links for the source code for mini applications called incr and dsub. The incr application simply adds or subtracts a legal increment and a datetime. The dsub application simply adds or subtracts two datetimes given on the command line. Once compiled with the datetime library, the following entries and results may be duplicated:
incr "3 feb 1" - "40 days" 
25 Dec 1 bc

incr "31 Jan 1 bc" + "360 days" 
26 Jan 1

incr "1 year 11 months" - "2 years" 
- 0 years 1 month

incr "- 1 year 11 months" + "2 years" 
0 years 1 month

incr "- 1 year 11 months" + "36 months" 
1 year 1 month

incr "1 day 23 hours 40 minutes" - "2 days" 
- 0 days 0 hours 20 minutes

incr "2 days 0 minutes" + "1 day 23 hours 40 minutes" 
3 days 23 hours 40 minutes

incr "- 2 days 0 minutes" + "1 day 23 hours 40 minutes" 
- 0 days 0 hours 20 minutes

incr "- 2 days 0 minutes 5 seconds" + "3 days 23 hours 40 minutes" 
1 day 23 hours 39 minutes 55 seconds

incr "1 day 23 hours 39 minutes 55 seconds" + "2 days 0 minutes 5 seconds" 
3 days 23 hours 40 minutes 0 seconds

incr "28 feb 1980" + "1 day" 
29 Feb 1980

incr "1 mar 1979" - "1 day" 
28 Feb 1979

incr "1 mar 1979" + "365 days" 
29 Feb 1980

dsub "4 jul 1776 12:00:00.123" "4 jul 1976 11:00"
- 73047 days 22 hours 59 minutes 59.877 seconds

dsub "4 jul 1976 11:00" "4 jul 1776 12:00:00.123"
73047 days 23 hours 0 minutes

dsub "4 jul 1976 11:00:00.00" "4 jul 1776 12:00:00.123"
73047 days 22 hours 59 minutes 59.88 seconds

dsub "4 jul 1976 11:00:00.000" "4 jul 1776 12:00:00.123"
73047 days 22 hours 59 minutes 59.877 seconds

incr "1 apr 1963 00:00:00.000" - "1440 minutes 1.23 seconds" 
30 Mar 1963 23:59:58.770

dsub "15 Jul 1995 11:53:17" "6 nov 1958 08:23:11" 
13400 days 3 hours 30 minutes 6 seconds

incr "15 Jul 1995 11:53:17" - "13400 days 3 hours 30 minutes 6 seconds"
6 Nov 1958 08:23:11

incr "0 seconds" + "1 day" 
86400 seconds


Implementation Notes on precision/overflow

........
HTML documentation by brown@gis.uiuc.edu