initial proper of seasons

This commit is contained in:
2025-11-05 19:59:37 -05:00
commit 2a9d851918
8 changed files with 730 additions and 0 deletions

505
src/builder.c Normal file
View File

@@ -0,0 +1,505 @@
#include "builder.h"
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
//helper to add days to a date
static struct tm add_days(struct tm date, int days) {
date.tm_mday += days;
mktime(&date);
return date;
}
//helper to compare dates (return negative if d1 < d2, 0 if equal, positive if d1 > d2)
static int compare_dates(struct tm d1, struct tm d2) {
if (d1.tm_year != d2.tm_year) return d1.tm_year - d2.tm_year;
if (d1.tm_mon != d2.tm_mon) return d1.tm_mon - d2.tm_mon;
return d1.tm_mday - d2.tm_mday;
}
/// Computus algorithm to calculate Easter Sunday
static ChurchDay easter_for_year(int year) {
int a = year % 19;
int b = year / 100;
int c = year % 100;
int d = b / 4;
int e = b % 4;
int f = (b + 8) / 25;
int g = (b - f + 1) / 3;
int h = (19 * a + b - d - g + 15) % 30;
int i = c / 4;
int k = c % 4;
int l = (32 + 2 * e + 2 * i - h - k) % 7;
int m = (a + 11 * h + 22 * l) / 451;
int month = (h + l - 7 * m + 114) / 31;
int day = (h + l - 7 * m + 114) % 31 + 1;
return church_day_new(year, month, day, SOLEMNITY, EASTER,
"Easter Sunday of the Resurrection of the Lord");
}
static ChurchDay ash_wednesday(const ChurchDay *easter) {
struct tm date = add_days(easter->date, -46);
ChurchDay cd;
cd.date = date;
cd.class = SOLEMNITY;
cd.season = LENT;
strcpy(cd.description, "Ash Wednesday");
return cd;
}
static ChurchDay first_sunday_advent(int year) {
struct tm dec_first = {0};
dec_first.tm_year = year - 1900;
dec_first.tm_mon = 11; // December
dec_first.tm_mday = 1;
mktime(&dec_first);
int days_to_subtract;
switch (dec_first.tm_wday) {
case 0: days_to_subtract = 0; break; // Sunday
case 1: days_to_subtract = 1; break; // Monday
case 2: days_to_subtract = 2; break; // Tuesday
case 3: days_to_subtract = 3; break; // Wednesday
case 4: days_to_subtract = 4; break; // Thursday
case 5: days_to_subtract = -2; break; // Friday
case 6: days_to_subtract = -1; break; // Saturday
default: days_to_subtract = 0; break;
}
struct tm date = add_days(dec_first, -days_to_subtract);
ChurchDay cd;
cd.date = date;
cd.class = SUNDAY;
cd.season = ADVENT;
strcpy(cd.description, "1st Sunday of Advent");
return cd;
}
static ChurchDay epiphany(int year) {
for (int day = 2; day <= 8; day++) {
struct tm date = {0};
date.tm_year = year - 1900;
date.tm_mon = 0; // January
date.tm_mday = day;
mktime(&date);
if (date.tm_wday == 0) { // Sunday
return church_day_new(year, 1, day, SOLEMNITY, CHRISTMAS,
"The Epiphany of the Lord");
}
}
// Fallback (shouldn't happen)
return church_day_new(year, 1, 6, SOLEMNITY, CHRISTMAS, "The Epiphany of the Lord");
}
static ChurchDay baptism(ChurchDay epiphany_day) {
struct tm date = add_days(epiphany_day.date, 7);
ChurchDay cd;
cd.date = date;
cd.class = FEAST;
cd.season = CHRISTMAS;
strcpy(cd.description, "The Baptism of the Lord");
return cd;
}
static ChurchDay pentecost(ChurchDay easter) {
struct tm date = add_days(easter.date, 49);
ChurchDay cd;
cd.date = date;
cd.class = SOLEMNITY;
cd.season = EASTER;
strcpy(cd.description, "Pentecost Sunday");
return cd;
}
static const char* ordinal_suffix(int number) {
char str[16];
snprintf(str, sizeof(str), "%d", number);
if (strstr(str, "11") == str + strlen(str) - 2 ||
strstr(str, "12") == str + strlen(str) - 2 ||
strstr(str, "13") == str + strlen(str) - 2) {
return "th";
}
switch (str[strlen(str) - 1]) {
case '1': return "st";
case '2': return "nd";
case '3': return "rd";
default: return "th";
}
}
static const char* day_string(int wday) {
switch (wday) {
case 0: return "Sunday";
case 1: return "Monday";
case 2: return "Tuesday";
case 3: return "Wednesday";
case 4: return "Thursday";
case 5: return "Friday";
case 6: return "Saturday";
default: return "Unknown";
}
}
static void calculate_advent(ChurchYear *cy, const ChurchDay *start) {
ChurchDay current = *start;
int sunday_counter = 1;
church_year_add(cy, current);
while (1) {
if (current.date.tm_mon == 11 && current.date.tm_mday == 24) {
break;
}
current.date = add_days(current.date, 1);
if (current.date.tm_wday == 0) {
sunday_counter++;
}
current.season = ADVENT;
current.class = (current.date.tm_wday == 0) ? SUNDAY : SEASONAL_WEEKDAY;
if (current.date.tm_mday >= 17 && current.date.tm_mon == 11) {
snprintf(current.description, sizeof(current.description),
"December %d%s", current.date.tm_mday,
ordinal_suffix(current.date.tm_mday));
} else if (current.date.tm_wday == 0) {
snprintf(current.description, sizeof(current.description),
"%d%s Sunday of Advent", sunday_counter,
ordinal_suffix(sunday_counter));
} else {
snprintf(current.description, sizeof(current.description),
"%s of the %d%s week of Advent",
day_string(current.date.tm_wday), sunday_counter,
ordinal_suffix(sunday_counter));
}
church_year_add(cy, current);
}
}
static void calculate_christmas(ChurchYear *cy, int year) {
ChurchDay epiphany_day = epiphany(year + 1);
ChurchDay baptism_day = baptism(epiphany_day);
ChurchDay christmas = church_day_new(year, 12, 25, SOLEMNITY, CHRISTMAS,
"The Nativity of the Lord (Christmas)");
church_year_add(cy, christmas);
ChurchDay current = christmas;
while (1) {
struct tm next_date = add_days(current.date, 1);
if (next_date.tm_mon == epiphany_day.date.tm_mon &&
next_date.tm_mday == epiphany_day.date.tm_mday) {
break;
}
current.date = next_date;
current.season = CHRISTMAS;
current.class = (current.date.tm_wday == 0) ? FEAST : SEASONAL_WEEKDAY;
if (current.date.tm_wday == 0) {
strcpy(current.description, "The Holy Family of Jesus, Mary, and Joseph");
} else {
switch (current.date.tm_mday) {
case 26: strcpy(current.description, "Saint Stephen, First Martyr"); break;
case 27: strcpy(current.description, "Saint John, Apostle and Evangelist"); break;
case 28: strcpy(current.description, "Holy Innocents"); break;
case 29: strcpy(current.description, "Fifth Day within the Octave of the Nativity of the Lord"); break;
case 30: strcpy(current.description, "Sixth Day within the Octave of the Nativity of the Lord"); break;
case 31: strcpy(current.description, "Seventh Day within the Octave of the Nativity of the Lord"); break;
default: strcpy(current.description, "Christmas Weekday"); break;
}
}
church_year_add(cy, current);
}
church_year_add(cy, epiphany_day);
current = epiphany_day;
while (1) {
struct tm next_date = add_days(current.date, 1);
if (next_date.tm_mon == baptism_day.date.tm_mon &&
next_date.tm_mday == baptism_day.date.tm_mday) {
break;
}
current.date = next_date;
current.season = CHRISTMAS;
current.class = SEASONAL_WEEKDAY;
snprintf(current.description, sizeof(current.description),
"%s between Epiphany and Baptism of the Lord",
day_string(current.date.tm_wday));
church_year_add(cy, current);
}
church_year_add(cy, baptism_day);
}
static void calculate_lent(ChurchYear *cy, ChurchDay ash_wed) {
ChurchDay easter = easter_for_year(ash_wed.date.tm_year + 1900);
struct tm stop_date = add_days(easter.date, -8);
ChurchDay current = ash_wed;
int sunday_counter = 0;
church_year_add(cy, current);
while (compare_dates(current.date, stop_date) != 0) {
current.date = add_days(current.date, 1);
if (current.date.tm_wday == 0) {
sunday_counter++;
}
current.season = LENT;
current.class = (current.date.tm_wday == 0) ? SUNDAY : SEASONAL_WEEKDAY;
if (sunday_counter == 0) {
snprintf(current.description, sizeof(current.description),
"%s after Ash Wednesday", day_string(current.date.tm_wday));
} else if (current.date.tm_wday == 0) {
snprintf(current.description, sizeof(current.description),
"%d%s Sunday of Lent", sunday_counter,
ordinal_suffix(sunday_counter));
} else {
snprintf(current.description, sizeof(current.description),
"%s of the %d%s week of Lent",
day_string(current.date.tm_wday), sunday_counter,
ordinal_suffix(sunday_counter));
}
church_year_add(cy, current);
}
}
static void calculate_easter(ChurchYear *cy, ChurchDay sat_before) {
ChurchDay easter = easter_for_year(sat_before.date.tm_year + 1900);
struct tm ps_date = add_days(easter.date, -7);
ChurchDay palm_sunday;
palm_sunday.date = ps_date;
palm_sunday.class = SUNDAY;
palm_sunday.season = LENT;
strcpy(palm_sunday.description, "Palm Sunday of the Passion of the Lord");
church_year_add(cy, palm_sunday);
const char* holy_week_names[] = {
"",
"Monday of Holy Week",
"Tuesday of Holy Week",
"Wednesday of Holy Week",
"Holy Thursday",
"Friday of the Passion of the Lord",
"Holy Saturday"
};
for (int days_after = 1; days_after <= 6; days_after++) {
ChurchDay day;
day.date = add_days(ps_date, days_after);
day.class = (days_after < 4) ? SEASONAL_WEEKDAY : SOLEMNITY;
day.season = (days_after < 4) ? LENT : TRIDUUM;
strcpy(day.description, holy_week_names[days_after]);
church_year_add(cy, day);
}
church_year_add(cy, easter);
const char* octave_names[] = {
"",
"Monday within the Octave of Easter",
"Tuesday within the Octave of Easter",
"Wednesday within the Octave of Easter",
"Thursday within the Octave of Easter",
"Friday within the Octave of Easter",
"Saturday within the Octave of Easter",
"Sunday of Divine Mercy"
};
for (int octave_days = 1; octave_days <= 7; octave_days++) {
ChurchDay day;
day.date = add_days(easter.date, octave_days);
day.class = SOLEMNITY;
day.season = EASTER;
strcpy(day.description, octave_names[octave_days]);
church_year_add(cy, day);
}
ChurchDay pentecost_day = pentecost(easter);
struct tm stop_date = add_days(pentecost_day.date, -1);
ChurchDay current = cy->days[cy->count - 1];
int sunday_counter = 2;
while (compare_dates(current.date, stop_date) != 0) {
current.date = add_days(current.date, 1);
if (current.date.tm_wday == 0) {
sunday_counter++;
}
current.season = EASTER;
current.class = (current.date.tm_wday == 0) ? SUNDAY : SEASONAL_WEEKDAY;
if (current.date.tm_wday == 0) {
if (sunday_counter == 7) {
strcpy(current.description, "The Ascension of the Lord");
} else {
snprintf(current.description, sizeof(current.description),
"%d%s Sunday of Easter", sunday_counter,
ordinal_suffix(sunday_counter));
}
} else {
snprintf(current.description, sizeof(current.description),
"%s of the %d%s week of Easter",
day_string(current.date.tm_wday), sunday_counter,
ordinal_suffix(sunday_counter));
}
church_year_add(cy, current);
}
church_year_add(cy, pentecost_day);
}
static void calculate_otime_prelent(ChurchYear *cy, ChurchDay baptism_day) {
ChurchDay easter = easter_for_year(baptism_day.date.tm_year + 1900);
ChurchDay ash_wed = ash_wednesday(&easter);
struct tm stop_date = add_days(ash_wed.date, -1);
ChurchDay current;
current.date = add_days(baptism_day.date, 1);
current.class = FERIAL_WEEKDAY;
current.season = ORDINARY_TIME;
strcpy(current.description, "Monday of the 1st week in Ordinary Time");
int sunday_counter = 1;
church_year_add(cy, current);
while (compare_dates(current.date, stop_date) != 0) {
current.date = add_days(current.date, 1);
if (current.date.tm_wday == 0) {
sunday_counter++;
}
current.season = ORDINARY_TIME;
current.class = (current.date.tm_wday == 0) ? SUNDAY : FERIAL_WEEKDAY;
if (current.date.tm_wday == 0) {
snprintf(current.description, sizeof(current.description),
"%d%s Sunday of Ordinary Time", sunday_counter,
ordinal_suffix(sunday_counter));
} else {
snprintf(current.description, sizeof(current.description),
"%s of the %d%s week of Ordinary Time",
day_string(current.date.tm_wday), sunday_counter,
ordinal_suffix(sunday_counter));
}
church_year_add(cy, current);
}
}
static void calculate_otime_posteaster(ChurchYear *cy, ChurchDay pentecost_day) {
ChurchDay first_advent = first_sunday_advent(pentecost_day.date.tm_year + 1900);
struct tm last_day = add_days(first_advent.date, -1);
struct tm trinity_sunday = add_days(pentecost_day.date, 7);
struct tm corpus_christi = add_days(trinity_sunday, 7);
// Build backwards
ChurchDay *temp_days = malloc(366 * sizeof(ChurchDay));
size_t temp_count = 0;
ChurchDay current;
current.date = last_day;
current.class = FERIAL_WEEKDAY;
current.season = ORDINARY_TIME;
int sunday_counter = 34;
snprintf(current.description, sizeof(current.description),
"%s of the %d%s Week in Ordinary Time",
day_string(current.date.tm_wday), sunday_counter,
ordinal_suffix(sunday_counter));
temp_days[temp_count++] = current;
struct tm stop_date = add_days(pentecost_day.date, 1);
while (compare_dates(current.date, stop_date) != 0) {
current.date = add_days(current.date, -1);
if (current.date.tm_wday == 0) {
sunday_counter--;
}
current.season = ORDINARY_TIME;
if (current.date.tm_wday == 0) {
if (compare_dates(current.date, trinity_sunday) == 0) {
current.class = SOLEMNITY;
strcpy(current.description, "The Most Holy Trinity");
} else if (compare_dates(current.date, corpus_christi) == 0) {
current.class = SOLEMNITY;
strcpy(current.description, "The Most Holy Body and Blood of Christ");
} else if (sunday_counter == 33) {
current.class = SOLEMNITY;
strcpy(current.description, "Our Lord Jesus Christ, King of the Universe");
} else {
current.class = SUNDAY;
snprintf(current.description, sizeof(current.description),
"%d%s Sunday of Ordinary Time", sunday_counter,
ordinal_suffix(sunday_counter));
}
} else {
current.class = FERIAL_WEEKDAY;
snprintf(current.description, sizeof(current.description),
"%s of the %d%s week of Ordinary Time",
day_string(current.date.tm_wday), sunday_counter,
ordinal_suffix(sunday_counter));
}
temp_days[temp_count++] = current;
}
// Reverse and add to main list
for (size_t i = temp_count; i > 0; i--) {
church_year_add(cy, temp_days[i - 1]);
}
free(temp_days);
}
ChurchYear proper_of_seasons(struct tm today) {
ChurchYear cy = church_year_new();
ChurchDay first_advent = first_sunday_advent(today.tm_year + 1900);
if (compare_dates(today, first_advent.date) < 0) {
first_advent = first_sunday_advent(today.tm_year + 1900 - 1);
}
calculate_advent(&cy, &first_advent);
calculate_christmas(&cy, first_advent.date.tm_year + 1900);
ChurchDay baptism_day = cy.days[cy.count - 1];
ChurchDay easter = easter_for_year(baptism_day.date.tm_year + 1900);
ChurchDay ash_wed = ash_wednesday(&easter);
calculate_otime_prelent(&cy, baptism_day);
calculate_lent(&cy, ash_wed);
ChurchDay sat_before = cy.days[cy.count - 1];
calculate_easter(&cy, sat_before);
ChurchDay pentecost_day = cy.days[cy.count - 1];
calculate_otime_posteaster(&cy, pentecost_day);
return cy;
}

10
src/builder.h Normal file
View File

@@ -0,0 +1,10 @@
#ifndef BUILDER_H
#define BUILDER_H
#include "church_day.h"
#include <time.h>
//calculate proper of seasons for a given date
ChurchYear proper_of_seasons(struct tm today);
#endif //BUILDER_H

81
src/church_day.c Normal file
View File

@@ -0,0 +1,81 @@
#include "church_day.h"
#include <string.h>
#include <stdlib.h>
#include <stdio.h>
ChurchDay church_day_new(int year, int month, int day, DayClass class, Season season, const char *description) {
ChurchDay cd;
memset(&cd.date, 0, sizeof(struct tm));
cd.date.tm_year = year - 1900;
cd.date.tm_mon = month - 1;
cd.date.tm_mday = day;
mktime(&cd.date);
cd.class = class;
cd.season = season;
strncpy(cd.description, description, sizeof(cd.description) - 1);
cd.description[sizeof(cd.description) - 1] = '\0';
return cd;
}
const char* church_day_month_name(const ChurchDay *day) {
static char buffer[32];
strftime(buffer, sizeof(buffer), "%B", &day->date);
return buffer;
}
const char* church_day_weekday_string(const ChurchDay *day) {
static char buffer[32];
strftime(buffer, sizeof(buffer), "%A", &day->date);
return buffer;
}
const char* day_class_to_string(DayClass class) {
switch (class) {
case SOLEMNITY: return "Solemnity";
case SUNDAY: return "Sunday";
case FEAST: return "Feast";
case MEMORIAL: return "Memorial";
case SEASONAL_WEEKDAY: return "Seasonal Weekday";
case FERIAL_WEEKDAY: return "Ferial";
default: return "Unknown";
}
}
const char* season_to_string(Season season) {
switch(season) {
case ADVENT: return "Advent";
case CHRISTMAS: return "Christmas";
case ORDINARY_TIME: return "Ordinary Time";
case LENT: return "Lent";
case TRIDUUM: return "Sacred Triduum";
case EASTER: return "Easter";
default: return "Unknown";
}
}
ChurchYear church_year_new(void) {
ChurchYear cy;
cy.capacity = 366;
cy.count = 0;
cy.days = malloc(cy.capacity * sizeof(ChurchDay));
return cy;
}
void church_year_add(ChurchYear *year, ChurchDay day) {
if (year->count >= year->capacity) {
year->capacity *= 2;
year->days = realloc(year->days, year->capacity * sizeof(ChurchDay));
}
year->days[year->count++] = day;
}
void church_year_free(ChurchYear *year) {
free(year->days);
year->days = NULL;
year->count = 0;
year->capacity = 0;
}

57
src/church_day.h Normal file
View File

@@ -0,0 +1,57 @@
#ifndef CHURCH_DAY_H
#define CHURCH_DAY_H
#include <time.h>
typedef enum {
SOLEMNITY,
SUNDAY,
FEAST,
MEMORIAL,
SEASONAL_WEEKDAY,
FERIAL_WEEKDAY
} DayClass;
typedef enum {
ADVENT,
CHRISTMAS,
ORDINARY_TIME,
LENT,
TRIDUUM,
EASTER
} Season;
typedef struct {
struct tm date;
DayClass class;
Season season;
char description[256];
} ChurchDay;
typedef struct {
ChurchDay *days;
size_t count;
size_t capacity;
} ChurchYear;
//Create a new ChurchDay
ChurchDay church_day_new(int year, int month, int day, DayClass class, Season season, const char *description);
//Get month name from ChurchDay
const char* church_day_month_name(const ChurchDay *day);
//Get day of week string from ChurchDay
const char* church_day_weekday_string(const ChurchDay *day);
//Get string representation of DayClass
const char* day_class_to_string(DayClass class);
//Get string representation of Season
const char* season_to_string(Season season);
//ChurchYear functions
ChurchYear church_year_new(void);
void church_year_add(ChurchYear *year, ChurchDay day);
void church_year_free(ChurchYear *year);
#endif // CHURCH_DAY_H

26
src/main.c Normal file
View File

@@ -0,0 +1,26 @@
#include <stdio.h>
#include <time.h>
#include "church_day.h"
#include "builder.h"
int main(void) {
time_t now = time(NULL);
struct tm *today = localtime(&now);
ChurchYear seasons = proper_of_seasons(*today);
for (size_t i = 0; i < seasons.count; i++) {
ChurchDay *day = &seasons.days[i];
printf("%s, %s %d, %d - %s - %s in %s\n",
church_day_weekday_string(day),
church_day_month_name(day),
day->date.tm_mday,
day->date.tm_year + 1900,
day->description,
day_class_to_string(day->class),
season_to_string(day->season));
}
church_year_free(&seasons);
return 0;
}