dt - Lightweight C library for date arithmetic and date conversion using the proleptic Gregorian calendar
Portable, C89
No sytem calls
No broken-down date structures
Fast arithmetic and compact representation of dates
Follows the ISO 8601 conventions
Conversion between all ISO 8601 date representations
Good test coverage
Dates values dt_t
are represented as an integer denoting the number of days since the zero epoch, December 31 of the year zero in the proleptic Gregorian calendar. The supported date range is from -5879610-06-22 (INT32_MIN
) to +5879611-07-11 (INT32_MAX
).
Functions provided by this library doesn't guard against integer overflows, it's the callers responsibility to ensure that dates and date arithmetic is within the supported range.
dt_t dt_from_cjdn(int cjdn);
Returns a date corresponding to the given chronological Julian day number cjdn.
dt_t dt_from_rdn(int rdn);
Returns a date corresponding to the given Rata Die number rdn.
dt_t dt_from_easter(int year, dt_computus_t computus);
Returns a date corresponding to the Easter Sunday for the given year. The parameter computus specifies the Easter calculation. Returns 0
if the given year is non-positive.
(computus = DT_WESTERN)
Calculation based on the Gregorian calendar.
(computus = DT_ORTHODOX)
Calculation based on the Julian calendar with the Julian date converted to the equivalent Gregorian date.
Example:
/* Easter Sunday */
dt = dt_from_easter(2012, DT_WESTERN);
/* Good Friday, 2 days before Easter */
dt = dt_from_easter(2012, DT_WESTERN) - 2;
/* Pentecost, 49 days after Easter (50th day of Easter) */
dt = dt_from_easter(2012, DT_WESTERN) + 49;
dt_t dt_from_struct_tm(const struct tm *tm);
Returns a date corresponding to the given structure tm. Folowing members of the tm structure must be set: tm_year, tm_mon and tm_mday. The members tm_mon and tm_mday may be outside usual range and will be normalized during the conversion.
dt_t dt_from_yd(int year, int day);
Returns a date corresponding to the given ordinal date: year and day of the year (1-366). The day may be outside usual range and will be normalized during the conversion.
dt_t dt_from_ymd(int year, int month, int day);
Returns a date corresponding to the given calendar date: year, month of the year (1-12), and day of the month (1-31). The month and day may be outside their usual ranges and will be normalized during the conversion. For example, January 32 converts to February 1.
dt_t dt_from_ywd(int year, int week, int day);
Returns a date corresponding to the given week date: year, week of the year (1-53), and day of the week (1-7). The week and day may be outside their usual ranges and will be normalized during the conversion.
dt_t dt_from_yqd(int year, int quarter, int day);
Returns a date corresponding to the given quarter date: year, quarter of the year (1-4), and day of the quarter (1-92). The quarter and day may be outside their usual ranges and will be normalized during the conversion.
bool dt_valid_yd(int year, int day);
Returns a boolean indicating whether or not the given ordinal date: year and day of the year constitute a valid date.
bool dt_valid_ymd(int year, int month, int day);
Returns a boolean indicating whether or not the given calendar date: year, month of the year and day of the month constitute a valid date.
bool dt_valid_yqd(int year, int quarter, int day);
Returns a boolean indicating whether or not the given quarter date: year, quarter of the year and day of the quarter constitute a valid date.
bool dt_valid_ywd(int year, int week, int day);
Returns a boolean indicating whether or not the given week date: year, week of the year and day of the week constitute a valid date.
void dt_to_struct_tm(dt_t dt, struct tm *tm);
Converts the given date dt to a broken down time struct *tm. Following members of the tm structure are set: tm_year, tm_mon, tm_mday, tm_wday and tm_yday.
void dt_to_yd(dt_t dt, int *year, int *day);
Converts the given date dt to the corresponding ordinal date: year and day of the year (1-366). The pointer parameters may be NULL
for any of the results that are not required.
void dt_to_ymd(dt_t dt, int *year, int *month, int *day);
Converts the given date dt to the corresponding calendar date: year, month of the year (1-12) and day of the month (1-31). The pointer parameters may be NULL
for any of the results that are not required.
void dt_to_yqd(dt_t dt, int *year, int *quarter, int *day);
Converts the given date dt to the corresponding quarter date: year, quarter of the year (1-4) and day of the quarter (1-92). The pointer parameters may be NULL
for any of the results that are not required.
void dt_to_ywd(dt_t dt, int *year, int *week, int *day);
Converts the given date dt to the corresponding week date: year, week of the year (1-53) and day of the week (1=Monday to 7=Sunday). The pointer parameters may be NULL
for any of the results that are not required.
int dt_cjdn(dt_t dt);
Returns the chronological Julian day number for the given date dt.
int dt_rdn(dt_t dt);
Returns the Rata Die number for the given date dt.
int dt_year(dt_t dt);
Returns the year for the given date dt.
int dt_quarter(dt_t dt);
Returns the quarter of the year (1-4) for the given date dt.
int dt_month(dt_t dt);
Returns the month of the year (1-12) for the given date dt.
int dt_doy(dt_t dt);
Returns the day of the year (1-366) for the given date dt.
int dt_doq(dt_t dt);
Returns the day of the quarter (1-92) for the given date dt.
int dt_dom(dt_t dt);
Returns the day of the month (1-31) for the given date dt.
int dt_dow(dt_t dt);
Returns the day of the week (1=Monday to 7=Sunday) for the given date dt.
int dt_woy(dt_t dt);
Returns the week of the year (1-53) for the given date dt.
int dt_yow(dt_t dt);
Returns the year of the week for the given date dt.
dt_t dt_start_of_year(dt_t dt, int offset);
Returns a date set to the first day of the year for the given date dt. The parameter offset specifies the number of years before (negative) or after (positive) the given date.
dt_t dt_start_of_quarter(dt_t dt, int offset);
Returns a date set to the first day of the quarter for the given date dt. The parameter offset specifies the number of quarters before (negative) or after (positive) the given date.
dt_t dt_start_of_month(dt_t dt, int offset);
Returns a date set to the first day of the month for the given date dt. The parameter offset specifies the number of months before (negative) or after (positive) the given date.
dt_t dt_start_of_week(dt_t dt, dt_dow_t first);
Returns a date set to the first day of the week for the given date dt. The parameter first specifies the first day of the week.
dt_t dt_end_of_year(dt_t dt, int offset);
Returns a date set to the last day of the year for the given date dt. The parameter offset specifies the number of years before (negative) or after (positive) the given date.
dt_t dt_end_of_quarter(dt_t dt, int offset);
Returns a date set to the last day of the quarter for the given date dt. The parameter offset specifies the number of quarters before (negative) or after (positive) the given date.
dt_t dt_end_of_month(dt_t dt, int offset);
Returns a date set to the last day of the month for the given date dt. The parameter offset specifies the number of months before (negative) or after (positive) the given date.
dt_t dt_end_of_week(dt_t dt, dt_dow_t first);
Returns a date set to the last day of the week for the given date dt. The parameter first specifies the first day of the week.
dt_t dt_nth_dow(dt_t dt, int nth, dt_dow_t day);
Returns a date set to the nth occurrence of the given day of week for given date dt. The parameter day specifies the day of week.
Example:
/* Birthday of Martin Luther King, Jr., the third Monday in January */
dt = dt_from_ymd(2012, 1, 1);
dt = dt_nth_dow(dt, 3, DT_MONDAY);
/* Memorial Day, the last Monday in May */
dt = dt_from_ymd(2012, 5, 31);
dt = dt_nth_dow(dt, -1, DT_MONDAY);
/* Thanksgiving Day, the fourth Thursday in November */
dt = dt_from_ymd(2012, 11, 1);
dt = dt_nth_dow(dt, 4, DT_THURSDAY);
/* Penultimate Monday in the second quarter */
dt = dt_from_yqd(2012, 2, 91);
dt = dt_nth_dow(dt, -2, DT_MONDAY);
/* 50th Wednesday of the year */
dt = dt_from_ymd(2012, 1, 1);
dt = dt_nth_dow(dt, 50, DT_WEDNESDAY);
dt_t dt_next_dow(dt_t dt, dt_dow_t day, bool current);
Returns a date set to the next or current day of week for the given date dt. The parameter day specifies the day of week. The parameter current specifies whether or not the returned date must succeed the given date.
dt_t dt_prev_dow(dt_t dt, dt_dow_t day, bool current);
Returns a date set to the previous or current day of week for the given date dt. The parameter day specifies the day of week. The parameter current specifies whether or not the returned date must precede the given date.
dt_t dt_next_weekday(dt_t dt, bool current);
Returns a date set to the next or current weekday (Monday through Friday) for the given date dt. The parameter current specifies whether or not the returned date must succeed the given date.
dt_t dt_prev_weekday(dt_t dt, bool current);
Returns a date set to the previous or current weekday (Monday through Friday) for the given date dt. The parameter current specifies whether or not the returned date must precede the given date.
dt_t dt_add_years(dt_t dt, int years, dt_adjust_t adjust);
Returns a date with the given number of years added. The parameter adjust defines the behaviour when the resulting year has one day less than the year of the given date.
(adjust = DT_EXCESS) If the resulting year has one day less than the year of the given date, then the result is counted forward or backward into the next or previous year by the excessive day. Otherwise, the result has the same day of year as the given date. For example:
2012-366 + 1 year = 2014-001 *
2011-365 + 1 year = 2012-365
(adjust = DT_LIMIT) If the resulting year has one day less than the year of the given date, then the result is the last day of the resulting year. Otherwise, the result has the same day of year as the given date. For example:
2012-366 + 1 year = 2013-365 *
2011-365 + 1 year = 2012-365
(adjust = DT_SNAP) If the given date is the last day of the year or if the resulting year has one day less than the year of the given date, then the result is the last day of the resulting year. Otherwise, the result has the same day of year as the given date. For example:
2012-366 + 1 year = 2013-365 *
2011-365 + 1 year = 2012-366 *
dt_t dt_add_quarters(dt_t dt, int quarters, dt_adjust_t adjust);
Returns a date with the given number of quarters added. The parameter adjust defines the behaviour when the resulting quarter has fewer days than the day of the quarter for the given date.
(adjust = DT_EXCESS) If the resulting quarter has fewer days than the day of quarter for the given date, then the result is counted forward or backward into the next or previous quarter by the number of excessive days. Otherwise, the result has the same day of quarter as the given date. For example:
2011-Q4-92 + 1 quarter = 2012-Q2-01 *
2012-Q2-91 + 1 quarter = 2012-Q3-91
(adjust = DT_LIMIT) If the resulting quarter has fewer days than the day of quarter for the given date, then the result is the last day of the resulting quarter. Otherwise, the result has the same day of quarter as the given date. For example:
2011-Q4-92 + 1 quarter = 2012-Q1-91 *
2012-Q2-91 + 1 quarter = 2012-Q3-91
(adjust = DT_SNAP) If the given date is the last day of the quarter or if the resulting quarter has fewer days than the day of quarter for the given date, then the result is the last day of the resulting quarter. Otherwise, the result has the same day of quarter as the given date. For example:
2011-Q4-92 + 1 quarter = 2012-Q1-91 *
2012-Q2-91 + 1 quarter = 2012-Q3-92 *
dt_t dt_add_months(dt_t dt, int months, dt_adjust_t adjust);
Returns a date with the given numbers of months added. The parameter adjust defines the behaviour when the resulting month has fewer days than the day of the month for the given date.
(adjust = DT_EXCESS) If the resulting month has fewer days than the day of month for the given date, then the result is counted forward or backward into the next or previous month by the number of excessive days. Otherwise, the result has the same day of month as the given date. For example:
2012-01-31 + 1 month = 2012-03-02 *
2012-02-29 + 1 month = 2012-03-29
(adjust = DT_LIMIT) If the resulting month has fewer days than the day of month for the given date, then the result is the last day of the resulting month. Otherwise, the result has the same day of month as the given date. For example:
2012-01-31 + 1 month = 2012-02-29 *
2012-02-29 + 1 month = 2012-03-29
(adjust = DT_SNAP) If the given date is the last day of the month or if the resulting month has fewer days than the day of month for the given date, then the result is the last day of the resulting month. Otherwise, the result has the same day of month as the given date. For example:
2012-01-31 + 1 month = 2012-02-29 *
2012-02-29 + 1 month = 2012-03-31 *
dt_t dt_add_weekdays(dt_t dt, int weekdays);
Returns a date with the given number of weekdays (Monday through Friday) added.
void dt_delta_yd(dt_t start, dt_t end, int *years, int *days);
Computes the difference between the given dates start and end in terms of years and days. If start is earlier than end, then the result is positive. If start is later than end, then the result is negative. The sign will be the same in each of years and days. The pointer parameters may be NULL
for any of the results that are not required. For example the difference between 2012-140 and 2013-150 is 1 year and 10 days; the difference between 2012-160 and 2013-150 is 0 years and 356 days.
void dt_delta_ymd(dt_t start, dt_t end, int *years, int *months, int *days);
Computes the difference between the given dates start and end in terms of years, months and days. If start is earlier than end, then the result is positive. If start is later than end, then the result is negative. The sign will be the same in each of years, months and days. The pointer parameters may be NULL
for any of the results that are not required. For example the difference between 2012-02-10 and 2013-10-20 is 1 year, 8 months and 10 days.
void dt_delta_yqd(dt_t start, dt_t end, int *years, int *quarters, int *days);
Computes the difference between the given dates start and end in terms of years, quarters and days. If start is earlier than end, then the result is positive. If start is later than end, then the result is negative. The sign will be the same in each of years, quarters and days. The pointer parameters may be NULL
for any of the results that are not required. For example the difference between 2012-Q1-10 and 2013-Q4-20 is 1 year, 3 quarters and 10 days.
int dt_delta_years(dt_t start, dt_t end, bool complete);
Returns the difference between the given dates start and end in years. The parameter complete specifies whether or not only complete years should be calculated. A year is considered to be complete if the day of the year of the end date is greater than or equal to the day of the year of the start date. If start is earlier than end, then the result is positive. If start is later than end, then the result is negative. For example the difference between 2011-12-31 and 2012-01-01 is 1 when complete
is false and 0 when complete
is true.
int dt_delta_quarters(dt_t start, dt_t end, bool complete);
Returns the difference between the given dates start and end in quarters. The parameter complete specifies whether or not only complete years should be calculated. A quarter is considered to be complete if the day of the quarter of the end date is greater than or equal to the day of the quarter of the start date. If start is earlier than end, then the result is positive. If start is later than end, then the result is negative.
int dt_delta_months(dt_t start, dt_t end, bool complete);
Returns the difference between the given dates start and end in months. The parameter complete specifies whether or not only complete years should be calculated. A month is considered to be complete if the day of the month of the end date is greater than or equal to the day of the month of the start date. If start is earlier than end, then the result is positive. If start is later than end, then the result is negative. For example the difference between 2012-01-25 and 2012-02-01 is 1 when complete
is false and 0 when complete
is true; the delta between 2012-01-01 and 2012-01-31 is 0 regardless of complete
.
int dt_delta_weeks(dt_t start, dt_t end);
Returns the difference between the given dates start and end in weeks. If start is earlier than end, then the result is positive. If start is later than end, then the result is negative.
int dt_delta_weekdays(dt_t start, dt_t end, bool inclusive);
Returns the number of weekdays (Monday through Friday) between the given dates start and end. The parameter inclusive specifies whether the end date should be inclusive. If start is earlier than end, then the result is positive. If start is later than end, then the result is negative.
size_t dt_parse_string(const char *str, size_t len, dt_t *dtp);
bool dt_leap_year(int year);
Returns a boolean indicating whether or not the given year is a leap year.
int dt_days_in_year(int year);
Returns the number of days in the given year (365-366).
int dt_days_in_quarter(int year, int quarter);
Returns the number of days in the given quarter (90-92) within the given year. Returns 0
if the given quarter is out of range (1-4).
int dt_days_in_month(int year, int month);
Returns the number of days in the given month (28-31) within the given year. Returns 0
if the given month is out of range (1-12).
int dt_weeks_in_year(int year);
Returns the number of weeks in the given year (52-53).
The unit tests is written in C using the Test Anything Protocol (TAP). Perl and the prove
command (provided by Perl module Test::Harness
) is required to run the harness. The version of Test::Harness
must be equal to or greater than v3.23.
$ prove -V
TAP::Harness v3.23 and Perl v5.14.1
Install using the cpan
client:
$ cpan install Test::Harness
Install using the source package (http://search.cpan.org/dist/Test-Harness/):
$ tar -zxf Test-Harness-3.25.tar.gz
$ cd Test-Harness-3.25
$ perl Makfile.PL
$ make
$ make test
$ sudo make install
$ make test
output:
t/yd.t ................ ok
t/ymd.t ............... ok
t/ymd_epochs.t ........ ok
t/yqd.t ............... ok
t/ywd.t ............... ok
t/tm.t ................ ok
t/easter_western.t .... ok
t/easter_orthodox.t ... ok
t/days_in_year.t ...... ok
t/days_in_month.t ..... ok
t/days_in_quarter.t ... ok
t/start_of_year.t ..... ok
t/start_of_quarter.t .. ok
t/start_of_month.t .... ok
t/start_of_week.t ..... ok
t/end_of_year.t ....... ok
t/end_of_quarter.t .... ok
t/end_of_month.t ...... ok
t/end_of_week.t ....... ok
t/next_dow.t .......... ok
t/prev_dow.t .......... ok
t/next_weekday.t ...... ok
t/prev_weekday.t ...... ok
t/nth_dow.t ........... ok
t/add_years.t ......... ok
t/add_quarters.t ...... ok
t/add_months.t ........ ok
t/add_weekdays.t ...... ok
t/delta_yd.t .......... ok
t/delta_ymd.t ......... ok
t/delta_yqd.t ......... ok
t/delta_weekdays.t .... ok
t/parse_string.t ...... ok
t/add_workdays.t ...... ok
t/delta_workdays.t .... ok
t/is_holiday.t ........ ok
t/is_workday.t ........ ok
t/adjust.t ............ ok
All tests successful.
Files=38, Tests=13181, 2 wallclock secs ( 1.14 usr 0.09 sys + 0.04 cusr 0.06 csys = 1.33 CPU)
Result: PASS
Please report any bugs or feature requests through the issue tracker at https://github.com/chansen/c-dt/issues.
This is open source software. The code repository is available for public review and contribution under the terms of the license.
https://github.com/chansen/c-dt
git clone https://github.com/chansen/c-dt.git
Easter algorithms by Al Petrofsky, San Mateo County, California, U.S.A. http://petrofsky.org/.
The TAP producer which is bundled with this library is written by Jake Gelbman https://github.com/zorgnax/libtap.
Christian Hansen <chansen@cpan.org>
Copyright (c) 2012-2015 Christian Hansen
This is free software; you can redistribute it and/or modify it under the terms of The BSD 2-Clause License.