Skip to content

Instantly share code, notes, and snippets.

@davidruhmann
Created January 30, 2013 15:13
Show Gist options
  • Save davidruhmann/4673902 to your computer and use it in GitHub Desktop.
Save davidruhmann/4673902 to your computer and use it in GitHub Desktop.
[C] Calendar FAQ Formulas
/*
* CALFAQ version 1.1, 4 April 2008
*
* COPYRIGHT:
* These functions are Copyright (c) 2008 by Claus Tondering
* (claus@tondering.dk).
*
* LICENSE:
* The code is distributed under the Boost Software License, which
* says:
*
* Boost Software License - Version 1.0 - August 17th, 2003
*
* Permission is hereby granted, free of charge, to any person or
* organization obtaining a copy of the software and accompanying
* documentation covered by this license (the "Software") to use,
* reproduce, display, distribute, execute, and transmit the
* Software, and to prepare derivative works of the Software, and
* to permit third-parties to whom the Software is furnished to do
* so, all subject to the following:
*
* The copyright notices in the Software and this entire
* statement, including the above license grant, this restriction
* and the following disclaimer, must be included in all copies of
* the Software, in whole or in part, and all derivative works of
* the Software, unless such copies or derivative works are solely
* in the form of machine-executable object code generated by a
* source language processor.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
* OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, TITLE AND
* NON-INFRINGEMENT. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR
* ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE FOR ANY DAMAGES OR
* OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,
* ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
* USE OR OTHER DEALINGS IN THE SOFTWARE.
*
* DESCRIPTION:
* These functions are an implementation in the C language of the
* formulas presented in the Calendar FAQ at
* http://www.tondering.dk/claus/calendar.html.
*
* The implementation follows the formulas mentioned in version 2.9
* of the FAQ quite closely. The focus of the implementation is on
* simplicity and clarity. For this reason, no complex data
* structures or classes are used, nor has any attempt been made to
* optimize the code. Also, no verification of the input parameters
* is performed (except in the function simple_gregorian_easter).
*
* All numbers (including Julian Day Numbers which current have
* values of almost 2,500,000) are assumed to be representable as
* variables of type 'int'.
*/
#include "calfaq.h"
/*
* is_leap:
* Determines if a year is a leap year.
* Input parameters:
* Calendar style (JULIAN or GREGORIAN)
* Year (must be >0)
* Returns:
* 1 if the year is a leap year, 0 otherwise.
*
* Note: The algorithm assumes that AD 4 is a leap year. This may be
* historically inaccurate. See the FAQ.
*
* Reference: Sections 2.1.1 and 2.2.1 of version 2.9 of the FAQ.
*/
int is_leap(int style, int year)
{
return style==JULIAN
? year%4==0
: (year%4==0 && year%100!=0) || year%400==0;
}
/*
* days_in_month:
* Calculates the number of days in a month.
* Input parameters:
* Calendar style (JULIAN or GREGORIAN)
* Year (must be >0)
* Month (1..12)
* Returns:
* The number of days in the month (28..31)
*/
int days_in_month(int style, int year, int month)
{
static int days[] = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
return month==2 && is_leap(style,year)
? 29
: days[month-1];
}
/*
* solar_number:
* Calculates the Solar Number of a given year.
* Input parameter:
* Year (must be >0)
* Returns:
* Solar Number (1..28)
*
* Reference: Section 2.4 of version 2.9 of the FAQ.
*/
int solar_number(int year)
{
return (year + 8) % 28 + 1;
}
/*
* day_of_week:
* Calculates the weekday for a given date.
* Input parameters:
* Calendar style (JULIAN or GREGORIAN)
* Year (must be >0)
* Month (1..12)
* Day (1..31)
* Returns:
* 0 for Sunday, 1 for Monday, 2 for Tuesday, etc.
*
* Reference: Section 2.6 of version 2.9 of the FAQ.
*/
int day_of_week(int style, int year, int month, int day)
{
int a = (14 - month) / 12;
int y = year - a;
int m = month + 12*a - 2;
return style== JULIAN
? (5 + day + y + y/4 + (31*m)/12) % 7
: (day + y + y/4 - y/100 + y/400 + (31*m)/12) % 7;
}
/*
* golden_number:
* Calculates the Golden Number of a given year.
* Input parameter:
* Year (must be >0)
* Returns:
* Golden Number (1..19)
*
* Reference: Section 2.13.3 of version 2.9 of the FAQ.
*/
int golden_number(int year)
{
return year%19 + 1;
}
/*
* epact:
* Calculates the Epact of a given year.
* Input parameters:
* Calendar style (JULIAN or GREGORIAN)
* Year (must be >0)
* Returns:
* Epact (1..30)
*
* Reference: Section 2.13.5 of version 2.9 of the FAQ.
*/
int epact(int style, int year)
{
if (style==JULIAN) {
int je = (11 * (golden_number(year)-1)) % 30;
return je==0 ? 30 : je;
}
else {
int century = year/100 + 1;
int solar_eq = (3*century)/4;
int lunar_eq = (8*century + 5)/25;
int greg_epact = epact(JULIAN,year) - solar_eq + lunar_eq + 8;
while (greg_epact>30)
greg_epact -= 30;
while (greg_epact<1)
greg_epact += 30;
return greg_epact;
}
}
/*
* paschal_full_moon:
* Calculates the date of the Paschal full moon.
* Input parameters:
* Calendar style (JULIAN or GREGORIAN)
* Year (must be >0)
* Output parameters:
* Address of month of Paschal full moon (3..4)
* Address of day of Pascal full moon (1..31)
*
* Reference: Section 2.13.4 and 2.13.6 of version 2.9 of the FAQ.
*/
void paschal_full_moon(int style, int year, int *month, int *day)
{
if (style==JULIAN) {
static struct {
int month;
int day;
} jul_pfm[] = {
{ 4, 5 }, { 3, 25 }, { 4, 13 }, { 4, 2 }, { 3, 22 },
{ 4, 10 }, { 3, 30 }, { 4, 18 }, { 4, 7 }, { 3, 27 },
{ 4, 15 }, { 4, 4 }, { 3, 24 }, { 4, 12 }, { 4, 1 },
{ 3, 21 }, { 4, 9 }, { 3, 29 }, { 4, 17 }
};
int gn = golden_number(year);
*month = jul_pfm[gn-1].month;
*day = jul_pfm[gn-1].day;
}
else {
int gepact = epact(GREGORIAN,year);
if (gepact<=12) {
*month = 4;
*day = 13-gepact;
}
else if (gepact<=23) {
*month = 3;
*day = 44-gepact;
}
else if (gepact==24) {
*month = 4;
*day = 18;
}
else if (gepact==25) {
*month = 4;
*day = golden_number(year)>11 ? 17 : 18;
}
else {
*month = 4;
*day = 43-gepact;
}
}
}
/*
* easter:
* Calculates the date of Easter Sunday.
* Input parameters:
* Calendar style (JULIAN or GREGORIAN)
* Year (must be >0)
* Output parameters:
* Address of month of Easter Sunday (3..4)
* Address of day of Easter Sunday (1..31)
*
* Reference: Section 2.13.7 of version 2.9 of the FAQ.
*/
void easter(int style, int year, int *month, int *day)
{
int G, I, J, C, H, L;
G = year % 19;
if (style==JULIAN) {
I = (19*G + 15) % 30;
J = (year + year/4 + I) % 7;
}
else {
C = year/100;
H = (C - C/4 - (8*C+13)/25 + 19*G + 15) % 30;
I = H - (H/28)*(1 - (29/(H + 1))*((21 - G)/11));
J = (year + year/4 + I + 2 - C + C/4) % 7;
}
L = I - J;
*month = 3 + (L + 40)/44;
*day = L + 28 - 31*(*month/4);
}
/*
* simple_gregorian_easter:
* Calculates the date of Easter Sunday in the Gregorian calendar.
* Input parameter:
* Year (must be in the range 1900..2099)
* Output parameters:
* Address of month of Easter Sunday (3..4)
* Address of day of Easter Sunday (1..31)
*
* If the year is outside the legal range, *month is set to zero.
*
* Reference: Section 2.13.8 of version 2.9 of the FAQ.
*/
void simple_gregorian_easter(int year, int *month, int *day)
{
int H, I, J, L;
if (year<1900 || year>2099) {
*month = 0;
return;
}
H = (24 + 19*(year % 19)) % 30;
I = H - H/28;
J = (year + year/4 + I - 13) % 7;
L = I - J;
*month = 3 + (L + 40)/44;
*day = L + 28 - 31*(*month/4);
}
/*
* indiction:
* Calculates the Indiction of a given year.
* Input parameter:
* Year (must be >0)
* Returns:
* Indiction (1..15)
*
* Reference: Section 2.15 of version 2.9 of the FAQ.
*/
int indiction(int year)
{
return (year + 2) % 15 + 1;
}
/*
* julian_period:
* Calculates the year in the Julian Period corresponding to a given
* year.
* Input parameter:
* Year (must be in the range -4712..3267). The year 1 BC must be
* given as 0, the year 2 BC must be given as -1, etc.
* Returns:
* The corresponding year in the Julian period
*
* Reference: Section 2.16 of version 2.9 of the FAQ.
*/
int julian_period(int year)
{
return year+4713;
}
/*
* date_to_jdn:
* Calculates the Julian Day Number for a given date.
* Input parameters:
* Calendar style (JULIAN or GREGORIAN)
* Year (must be > -4800). The year 1 BC must be given as 0, the
* year 2 BC must be given as -1, etc.
* Month (1..12)
* Day (1..31)
* Returns:
* Julian Day Number
*
* Reference: Section 2.16.1 of version 2.9 of the FAQ.
*/
int date_to_jdn(int style, int year, int month, int day)
{
int a = (14-month)/12;
int y = year+4800-a;
int m = month + 12*a - 3;
return style==JULIAN
? day + (153*m+2)/5 + y*365 + y/4 - 32083
: day + (153*m+2)/5 + y*365 + y/4 - y/100 + y/400 - 32045;
}
/*
* jdn_to_date:
* Calculates the date for a given Julian Day Number.
* Input parameter:
* Calendar style (JULIAN or GREGORIAN)
* Julian Day Number
* Output parameters:
* Address of year. The year 1 BC will be stored as 0, the year
* 2 BC will be stored as -1, etc.
* Address of month (1..12)
* Address of day (1..31)
*
* Reference: Section 2.16.1 of version 2.9 of the FAQ.
*/
void jdn_to_date(int style, int JD, int *year, int *month, int *day)
{
int b, c, d, e, m;
if (style==JULIAN) {
b = 0;
c = JD + 32082;
}
else {
int a = JD + 32044;
b = (4*a+3)/146097;
c = a - (b*146097)/4;
}
d = (4*c+3)/1461;
e = c - (1461*d)/4;
m = (5*e+2)/153;
*day = e - (153*m+2)/5 + 1;
*month = m + 3 - 12*(m/10);
*year = b*100 + d - 4800 + m/10;
}
/*
* week_number:
* Calculates the ISO 8601 week number (and corresponding year) for a given
* Gregorian date.
* Input parameters:
* Year (must be >0)
* Month (1..12)
* Day
* Output parameters:
* Address of week number (1..53)
* Address of corresponding year
*
* Reference: Section 7.8 of version 2.9 of the FAQ.
*/
void week_number(int year, int month, int day, int *week_number, int *week_year)
{
int a, b, c, s, e, f, g, d, n;
if (month<=2) {
a = year-1;
b = a/4 - a/100 + a/400;
c = (a-1)/4 - (a-1)/100 + (a-1)/400;
s = b-c;
e = 0;
f = day - 1 + 31*(month-1);
}
else {
a = year;
b = a/4 - a/100 + a/400;
c = (a-1)/4 - (a-1)/100 + (a-1)/400;
s = b-c;
e = s+1;
f = day + (153*(month-3)+2)/5 + 58 + s;
}
g = (a + b) % 7;
d = (f + g - e) % 7;
n = f + 3 - d;
if (n<0) {
*week_number = 53-(g-s)/5;
*week_year = year-1;
}
else if (n>364+s) {
*week_number = 1;
*week_year = year+1;
}
else {
*week_number = n/7 + 1;
*week_year = year;
}
}
/*
* CALFAQ version 1.1, 4 April 2008
*
* COPYRIGHT:
* These functions are Copyright (c) 2008 by Claus Tondering
* (claus@tondering.dk).
*
* LICENSE:
* The code is distributed under the Boost Software License, which
* says:
*
* Boost Software License - Version 1.0 - August 17th, 2003
*
* Permission is hereby granted, free of charge, to any person or
* organization obtaining a copy of the software and accompanying
* documentation covered by this license (the "Software") to use,
* reproduce, display, distribute, execute, and transmit the
* Software, and to prepare derivative works of the Software, and
* to permit third-parties to whom the Software is furnished to do
* so, all subject to the following:
*
* The copyright notices in the Software and this entire
* statement, including the above license grant, this restriction
* and the following disclaimer, must be included in all copies of
* the Software, in whole or in part, and all derivative works of
* the Software, unless such copies or derivative works are solely
* in the form of machine-executable object code generated by a
* source language processor.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
* OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, TITLE AND
* NON-INFRINGEMENT. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR
* ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE FOR ANY DAMAGES OR
* OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,
* ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
* USE OR OTHER DEALINGS IN THE SOFTWARE.
*
* DESCRIPTION:
* These functions are an implementation in the C language of the
* formulas presented in the Calendar FAQ at
* http://www.tondering.dk/claus/calendar.html.
*
* The implementation follows the formulas mentioned in version 2.9
* of the FAQ quite closely. The focus of the implementation is on
* simplicity and clarity. For this reason, no complex data
* structures or classes are used, nor has any attempt been made to
* optimize the code. Also, no verification of the input parameters
* is performed (except in the function simple_gregorian_easter).
*
* All numbers (including Julian Day Numbers which current have
* values of almost 2,500,000) are assumed to be representable as
* variables of type 'int'.
*/
/*
* Calendar styles
*/
#define JULIAN 0
#define GREGORIAN 1
/*
* is_leap:
* Determines if a year is a leap year.
* Input parameters:
* Calendar style (JULIAN or GREGORIAN)
* Year (must be >0)
* Returns:
* 1 if the year is a leap year, 0 otherwise.
*
* Note: The algorithm assumes that AD 4 is a leap year. This may be
* historically inaccurate. See the FAQ.
*
* Reference: Sections 2.1.1 and 2.2.1 of version 2.9 of the FAQ.
*/
int is_leap(int style, int year);
/*
* days_in_month:
* Calculates the number of days in a month.
* Input parameters:
* Calendar style (JULIAN or GREGORIAN)
* Year (must be >0)
* Month (1..12)
* Returns:
* The number of days in the month (28..31)
*/
int days_in_month(int style, int year, int month);
/*
* solar_number:
* Calculates the Solar Number of a given year.
* Input parameter:
* Year (must be >0)
* Returns:
* Solar Number (1..28)
*
* Reference: Section 2.4 of version 2.9 of the FAQ.
*/
int solar_number(int year);
/*
* day_of_week:
* Calculates the weekday for a given date.
* Input parameters:
* Calendar style (JULIAN or GREGORIAN)
* Year (must be >0)
* Month (1..12)
* Day (1..31)
* Returns:
* 0 for Sunday, 1 for Monday, 2 for Tuesday, etc.
*
* Reference: Section 2.6 of version 2.9 of the FAQ.
*/
int day_of_week(int style, int year, int month, int day);
/*
* golden_number:
* Calculates the Golden Number of a given year.
* Input parameter:
* Year (must be >0)
* Returns:
* Golden Number (1..19)
*
* Reference: Section 2.13.3 of version 2.9 of the FAQ.
*/
int golden_number(int year);
/*
* epact:
* Calculates the Epact of a given year.
* Input parameters:
* Calendar style (JULIAN or GREGORIAN)
* Year (must be >0)
* Returns:
* Epact (1..30)
*
* Reference: Section 2.13.5 of version 2.9 of the FAQ.
*/
int epact(int style, int year);
/*
* paschal_full_moon:
* Calculates the date of the Paschal full moon.
* Input parameters:
* Calendar style (JULIAN or GREGORIAN)
* Year (must be >0)
* Output parameters:
* Address of month of Paschal full moon (3..4)
* Address of day of Pascal full moon (1..31)
*
* Reference: Section 2.13.4 and 2.13.6 of version 2.9 of the FAQ.
*/
void paschal_full_moon(int style, int year, int *month, int *day);
/*
* easter:
* Calculates the date of Easter Sunday.
* Input parameters:
* Calendar style (JULIAN or GREGORIAN)
* Year (must be >0)
* Output parameters:
* Address of month of Easter Sunday (3..4)
* Address of day of Easter Sunday (1..31)
*
* Reference: Section 2.13.7 of version 2.9 of the FAQ.
*/
void easter(int style, int year, int *month, int *day);
/*
* simple_gregorian_easter:
* Calculates the date of Easter Sunday in the Gregorian calendar.
* Input parameter:
* Year (must be in the range 1900..2099)
* Output parameters:
* Address of month of Easter Sunday (3..4)
* Address of day of Easter Sunday (1..31)
*
* If the year is outside the legal range, *month is set to zero.
*
* Reference: Section 2.13.8 of version 2.9 of the FAQ.
*/
void simple_gregorian_easter(int year, int *month, int *day);
/*
* indiction:
* Calculates the Indiction of a given year.
* Input parameter:
* Year (must be >0)
* Returns:
* Indiction (1..15)
*
* Reference: Section 2.15 of version 2.9 of the FAQ.
*/
int indiction(int year);
/*
* julian_period:
* Calculates the year in the Julian Period corresponding to a given
* year.
* Input parameter:
* Year (must be in the range -4712..3267). The year 1 BC must be
* given as 0, the year 2 BC must be given as -1, etc.
* Returns:
* The corresponding year in the Julian period
*
* Reference: Section 2.16 of version 2.9 of the FAQ.
*/
int julian_period(int year);
/*
* date_to_jdn:
* Calculates the Julian Day Number for a given date.
* Input parameters:
* Calendar style (JULIAN or GREGORIAN)
* Year (must be > -4800). The year 1 BC must be given as 0, the
* year 2 BC must be given as -1, etc.
* Month (1..12)
* Day (1..31)
* Returns:
* Julian Day Number
*
* Reference: Section 2.16.1 of version 2.9 of the FAQ.
*/
int date_to_jdn(int style, int year, int month, int day);
/*
* jdn_to_date:
* Calculates the date for a given Julian Day Number.
* Input parameter:
* Calendar style (JULIAN or GREGORIAN)
* Julian Day Number
* Output parameters:
* Address of year. The year 1 BC will be stored as 0, the year
* 2 BC will be stored as -1, etc.
* Address of month (1..12)
* Address of day (1..31)
*
* Reference: Section 2.16.1 of version 2.9 of the FAQ.
*/
void jdn_to_date(int style, int JD, int *year, int *month, int *day);
/*
* week_number:
* Calculates the ISO 8601 week number (and corresponding year) for a given
* Gregorian date.
* Input parameters:
* Year (must be >0)
* Month (1..12)
* Day
* Output parameters:
* Address of week number (1..53)
* Address of corresponding year
*
* Reference: Section 7.8 of version 2.9 of the FAQ.
*/
void week_number(int year, int month, int day, int *week_number, int *week_year);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment