// Original attic calendar code in JS by Xanthe Tynehorne (https://satyrs.eu) // Port to C and improvements by cidoku (https://cidoku.net) // Uses portions of SunCalc. See suncalc.c and suncalc.h for details. /* Copyright (c) 2024, cidoku, Xanthe Tynehorne All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include #include #include #include #include #include "suncalc.h" #define MIDNIGHT 0 #define SUNRISE 1 #define SUNSET 2 // Program settings #define LATITUDE 54.9738 #define LONGITUDE -1.6132 #define HEIGHT 0 #define LAT_ATHENS 37.9795 #define LNG_ATHENS 23.7162 #define HGT_ATHENS 0 #define USE_NORTHERN_NEW_YEAR 1 #define USE_ATHENS_TIMES 0 #define DAY_START SUNSET #define EPOCH -775 #if USE_ATHENS_TIMES #define GET_TIMES(date, s) get_sun_times(date, LAT_ATHENS, LNG_ATHENS, \ HGT_ATHENS, s) #else #define GET_TIMES(date, s) get_sun_times(date, LATITUDE, LONGITUDE, \ HEIGHT, s) #endif time_t noon(time_t date, struct s_data* s) { struct tm* local = localtime(&date); local->tm_hour = 12; local->tm_min = 0; local->tm_sec = 0; time_t noon = mktime(local); get_sun_times(noon, LATITUDE, LONGITUDE, HEIGHT, s); #if DAY_START == SUNSET if (s->sunset <= date) { noon += DAY_SEC; get_sun_times(noon, LATITUDE, LONGITUDE, HEIGHT, s); } #elif DAY_START == SUNRISE if (s->sunrise >= date) { noon -= DAY_SEC; get_sun_times(noon, LATITUDE, LONGITUDE, HEIGHT, s); } #endif return noon; } time_t add_days(time_t date, int days) { return date + days * DAY_SEC; } struct m_data { time_t last_new_moon; time_t next_new_moon; int day_of_month; int signed_date; }; void scan_nights(time_t date, struct m_data* m_data) { struct s_data s; time_t today = noon(date, &s); m_data->last_new_moon = -1; m_data->next_new_moon = -1; m_data->day_of_month = -1; m_data->signed_date = -1; struct m_light m_light; #if USE_ATHENS_TIMES get_sun_times(today, LAT_ATHENS, LNG_ATHENS, HGT_ATHENS, &s); #endif get_moon_illumination(s.dawn, &m_light); double current_next = m_light.phase; double current_last = m_light.phase; for (int i = 1; m_data->last_new_moon == -1 && i < 50; i++) { time_t yester = add_days(today, -i); GET_TIMES(yester, &s); get_moon_illumination(s.dawn, &m_light); if (m_light.phase > current_last) { m_data->last_new_moon = add_days(today, -i + 1); } current_last = m_light.phase; } for (int i = 1; m_data->next_new_moon == -1 && i < 50; i++) { time_t morrow = add_days(today, i); GET_TIMES(morrow, &s); get_moon_illumination(s.dawn, &m_light); if (m_light.phase < current_next) { m_data->next_new_moon = add_days(today, i); } current_next = m_light.phase; } m_data->day_of_month = round((double)(today - m_data->last_new_moon) / DAY_SEC) + 1; if (round((double)(m_data->next_new_moon - today) / DAY_SEC) == 1) { m_data->signed_date = 0; } else { m_data->signed_date = m_data->day_of_month > 20 ? m_data->day_of_month - 31 : m_data->day_of_month; } } struct y_data { time_t new_year; time_t midsummer; }; void solstice(int year, struct y_data* y_data) { time_t t = time(NULL); struct tm* tm = localtime(&t); struct s_data s; time_t dates[3]; time_t day_lengths[3]; tm->tm_year = year - 1900; tm->tm_hour = 12; tm->tm_min = 0; tm->tm_sec = 0; #if USE_NORTHERN_NEW_YEAR tm->tm_mon = 5; // June #else tm->tm_mon = 11; // December #endif for (int i = 0; i <= 2; i++) { tm->tm_mday = i + 20; dates[i] = mktime(tm); GET_TIMES(dates[i], &s); day_lengths[i] = s.length; } int max_length = 0; for (int i = 1; i <= 2; i++) { if (day_lengths[i] > max_length) max_length = i; } y_data->midsummer = noon(dates[max_length], &s); struct m_data m_data; scan_nights(y_data->midsummer, &m_data); y_data->new_year = m_data.next_new_moon; } void years(time_t date, time_t* prev, time_t* next) { struct tm* local = localtime(&date); int current_year = local->tm_year + 1900; struct y_data y_data; time_t surrounding_years[3]; for (int i = 0; i <= 2; i++) { solstice(current_year + (i - 1), &y_data); surrounding_years[i] = y_data.new_year; } if (date < surrounding_years[1]) { *prev = surrounding_years[0]; *next = surrounding_years[1]; } else { *prev = surrounding_years[1]; *next = surrounding_years[2]; } } struct attic_tm { int olympiad; int year; int month; int day; int signed_date; int leap; }; void attic(time_t date, struct attic_tm* attic_tm) { struct m_data scanned; scan_nights(date, &scanned); attic_tm->day = scanned.day_of_month; attic_tm->signed_date = scanned.signed_date; time_t noumenia = scanned.last_new_moon; time_t new_year; time_t next; years(noumenia, &new_year, &next); int is_leap_year = round((next - new_year) / MON_SEC) == 13; attic_tm->month = round((noumenia - new_year) / MON_SEC); attic_tm->leap = is_leap_year; // Month 6 (zero-indexing from Hecatombæon) is always Poseideon II, so we // bump the rest up if it’s not a leap year if (!is_leap_year && attic_tm->month >= 6) { attic_tm->month++; } struct tm* new_year_tm = localtime(&new_year); // 776 BC was the first olympiad int raw_year = new_year_tm->tm_year + 1900 - EPOCH; attic_tm->olympiad = (raw_year / 4) + 1; attic_tm->year = (raw_year % 4) + 1; } int main(int argc, char *argv[]) { time_t date; struct attic_tm attic_tm; // If last argument is a number interpret as unix timestamp and use as date if (argc > 1 && isdigit(argv[argc - 1][0])) date = atoi(argv[argc - 1]); else date = time(NULL); attic(date, &attic_tm); const char *months[] = { "Hekatombaion", "Metageitnion", "Boedromion", "Pyanepsion", "Maimakterion", "Poseideon", "Poseideon II", "Gamelion", "Anthesterion", "Elaphebolion", "Mounuchion", "Thargelion", "Skirophorion" }; /* const char *months[] = { "Hecatombeón", "Metagitnión", "Boedromión", "Pianepsión", "Memacterión", "Posideón", "Posideón II", "Gamelión", "Antesterión", "Elafebolión", "Muniquión", "Targelión", "Esciroforión" }; */ if (argc > 1 && argv[1][0] == '-') { switch (argv[1][1]) { case 'r': printf("%d %d %d %d\n", attic_tm.olympiad, attic_tm.year, attic_tm.month, attic_tm.day); break; case 'i': printf(attic_tm.leap && attic_tm.month == 6? "%d-%d-%02d.2-%02d\n" : "%d-%d-%02d-%02d\n", attic_tm.olympiad, attic_tm.year, attic_tm.month > 5 ? attic_tm.month : attic_tm.month + 1, attic_tm.day); break; case 'a': printf("Ol.%d.%d %c%c%c. %d\n", attic_tm.olympiad, attic_tm.year, months[attic_tm.month][0], months[attic_tm.month][1], months[attic_tm.month][2], attic_tm.day); break; } return 0; } printf("Ol.%d.%d %s %d\n", attic_tm.olympiad, attic_tm.year, months[attic_tm.month], attic_tm.day); return 0; }