GRASS DateTime Library
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 < 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
-
The values for the from/to #defines must increase from YEAR to SECOND
In other words YEAR < MONTH < DAY < HOUR < MINUTE < SECOND. The idea
is that the higher elements represent higher precision for a date/time.
For example, having seconds in the time is more precise than if seconds
are not present.
-
There are some restrictions on legal values for from/to:
- from <= to
- if the 'mode' is ABSOLUTE, then 'from' must be YEAR
- if the 'mode' is RELATIVE, then
'from' and 'to' must be in {YEAR,MONTH} or in {DAY,HOUR,MINUTE,SECOND}
year, month, day, hour, minute, second:
- These are non-negative values.
- For ABSOLUTE types, these must be valid date/time values:
- year
- a complete year (not just the last 2 digits)
- must be positive (since 0 isn't a legal year).
- month [1,12]
- day [1,n] where n depends on the year/month.
- hour [0,23]
- minute [0,59]
- second [0.0,<60.0]
- For RELATIVE types, the value corresponding to 'from' is
unrestricted (except that it can't be negative). The other values
are restricted as follows:
if from==YEAR, month is [0,11]
if from==DAY, hour is [0,23], min is [0,59], sec is [0.0,<60.0]
if from==HOUR, min is [0,59], sec is [0.0,<60.0]
if from==MINUTE, sec is [0.0,<60.0]
fracsec:
- This controls the number of decimal places to print after the seconds.
- It is only used if the 'to' element is SECOND.
- It must be non-negative.
tz:
- The time (hour/minute) in ABSOLUTE types is in local time.
- The specification of a timezone (tz) is an (subtractive)
offset to convert from local time to UTC.
- To get UTC from localtime: LT - TZ
- tz is expressed in minutes from -720 to 780
(720 == 12 hours, 780 minutes == 13 hours).
[See ANSI X3.51-1975, section 2.2.3]
- For a timezone to be allowed, the 'to' field must be one of
{MINUTE, SECOND}
positive:
- this indicates if the datetime value is to considered
"positive" (!=0) or "negative" (==0)
- For mode ABSOLUTE, positive==0 means BC
Contents:
All datetime functions that return int status codes should return:
- 0 (or positive) if OK;
- a negative integer if not;
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
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.
-
int
datetime_set_type
(DateTime *dt; int mode, from, to, fracsec)
-
- This routine must be called
can be made with other datetime functions.
- initialize all the elements in dt.
- Set all values to zero except:
tz (set to illegal value - 99*24)
positive (set to 1 for positive)
- Set the type info in dt: mode, from, to, fracsec
- validate the mode/from/to/fracsec (according to the rules for the mode)
- return the return value from
datetime_check_type(dt)
-
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:
- 0: OK
- -1: mode is invalid - not one of {ABSOLUTE,RELATIVE}
- -2: from is invalid - not one of {YEAR,MONTH,DAY,HOUR,MINUTE,SECOND}
- -3: to is invalid - not one of {YEAR,MONTH,DAY,HOUR,MINUTE,SECOND}
- -4: from/to are reversed (from>to is illegal)
- -5: invalid from/to combination for RELATIVE mode:
from in {YEAR,MONTH} but to is not, or
from in {DAY,HOUR,MINUTE,SECOND} but to is not
- -6: from is invalid for ABSOLUTE mode (from != YEAR is illegal)
- -7: fracsec is negative (only if to==SECOND)
-
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)
-
- changes the from/to of the type for dt.
- The 'from/to' must be legal values for the mode of dt;
(if they are not legal, then the original values are preserved,
dt is not changed).
- returns:
0 OK
-1 invalid 'dt'
-2 invalid 'from/to'
- round =
negative implies floor() [decrease magnitude]
0 implies normal rounding, [incr/decr magnitude]
positive implies ceil() [increase magnitude]
- If dt.from < 'from' (losing "lower" elements), convert the
"lost" values to the equivalent value for the new 'from'
Lost elements are then set to zero.
(This case can only occur for dt.mode relative):
months += lost years * 12 ; years = 0
hours += lost days * 24 ; days = 0
minutes += lost hours * 60 ; hours = 0
seconds += lost minutes * 60.0 ; minutes = 0
- If dt.from > 'from' (adding "lower" elements), the new elements
are set to zero.
- If dt.to < 'to' (adding "higher" elements), the new elements
are set to zero.
- If dt.to > 'to' (losing "higher" elements), the
the new 'to' is adjusted according to the value for 'round'
After rounding the "lost" elements are set to zero.
-
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
These routines get/set elements of 'dt'. They return:
- 0 if OK
- -1 if the value being gotten or set is not a legal value
- -2 if the from/to for 'dt' doesn't include this value
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
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)
-
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,
- positive 'incr' moves into the future,
- negative 'incr' moves into the past.
- BC implies the year is negative, but all else is positive.
Also, year==0 is illegal: adding 1 year to 1[bc] gives 1[ad]
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:
- 0 valid increment
- 1 src is not a legal DateTime, error code/msg
are those set by
datetime_is_valid_type()
- 2 incr is not a legal DateTime, error code/msg
are those set by
datetime_is_valid_type()
- -1 incr.mode not relative
- -2 incr more precise that src
- -3 illegal incr, must be YEAR-MONTH
- -4 illegal incr, must be DAY-SECOND
-
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);
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.
-
int
datetime_days_in_month
(int month, year)
-
-
int
datetime_is_leap_year
(int year, ad)
-
-
int
datetime_days_in_year
(int year, ad)
-
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