leapc
Loading...
Searching...
No Matches
leap.c
Go to the documentation of this file.
1/* SPDX-License-Identifier: MIT */
10#include "leap.h"
11#include "quo_mod.h"
12
13bool is_leap(int year) {
14 /*
15 * Allow C99's standard precedence to rule over operator ordering: modulo
16 * exceeds equality and inequality operators. No need for brackets except
17 * perhaps to meet some external standard that requires brackets
18 * pendantically.
19 *
20 * A year is a leap year if it is divisible by 4, except for years that
21 * are divisible by 100, unless they are also divisible by 400. Optimisation
22 * using the \c & operator is possible but reduces readability. Instead, rely
23 * on compiler optimisation.
24 */
25 return year % 4 == 0 && (year % 100 != 0 || year % 400 == 0);
26}
27
28int leap_add(int year) { return is_leap(year) ? 1 : 0; }
29
30int leap_thru(int year) {
31 /*
32 * Expand the quotient terms first for debugging. Make it easier to see the
33 * terms of the thru-sum.
34 */
35 const int q4 = quo_mod(year, 4).quo;
36 const int q100 = quo_mod(year, 100).quo;
37 const int q400 = quo_mod(year, 400).quo;
38 return q4 - q100 + q400;
39}
40
41/*
42 * The `+ 1` anchors the epoch: year 0 maps to day 0. The term `year * 365 +
43 * leap_thru(year - 1)` counts days up to (but not including) the target year.
44 * Without the `+ 1`, `leap_day(0)` would be `-1`. Adding `1` fixes this:
45 * `leap_day(0) == 0` and `leap_day(1) == 366`. Constant offsets cancel in
46 * subtractions, preserving year differences.
47 */
48int leap_day(int year) { return year * 365 + leap_thru(year - 1) + 1; }
49
50/*
51 * Normalises an arbitrary day offset relative to a given year into a canonical
52 * (year, day-of-year) pair where 0 <= day < days_in_year.
53 *
54 * Algorithm:
55 * - Compute the current year's length: `days = 365 + leap_add(year)`.
56 * - While `day` lies outside `[0, days)`:
57 * * Jump whole years using floor-like quotient semantics:
58 * `year0 = year + quo_mod(day, days).quo`.
59 * * Rebase the offset to the new year using absolute day counts:
60 * `day += leap_day(year) - leap_day(year0)`.
61 * * Update `year` to `year0` and recompute `days` for that year.
62 * - Return the resulting `(year, day)` which is within the year's bounds.
63 *
64 * Notes:
65 * - `quo_mod` uses Lua-style modulo semantics so negative offsets jump the
66 * correct number of whole years in the negative direction.
67 * - Differences of `leap_day(...)` cancel the constant epoch offset, ensuring
68 * exact rebasing regardless of the `+1` anchor in `leap_day`.
69 */
70struct leap_off leap_off(int year, int day_off) {
71 int days = 365 + leap_add(year);
72 while (day_off < 0 || day_off >= days) {
73 int year0 = year + quo_mod(day_off, days).quo;
74 day_off += leap_day(year) - leap_day(year0);
75 days = 365 + leap_add(year = year0);
76 }
77 return (struct leap_off){.year = year, .day = day_off};
78}
79
80int leap_mday(int year, int month) {
81 static const int MDAY[] = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
82 const struct quo_mod qm = quo_mod(month - 1, 12);
83 return MDAY[qm.mod] + (qm.mod == 1 ? leap_add(year + qm.quo) : 0);
84}
85
86int leap_yday(int year, int month) {
87 static const int YDAY[] = {0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334};
88 const struct quo_mod qm = quo_mod(month - 1, 12);
89 return YDAY[qm.mod] + (qm.mod > 1 ? leap_add(year + qm.quo) : 0);
90}
91
92/*
93 * Converts a (year, day-of-year) pair into a (year, month, day-of-month)
94 * triple.
95 *
96 * Algorithm:
97 * - Normalise the (year, day) pair using leap_off to ensure day is within
98 * year's bounds.
99 * - Iterate months from 1 to 12, subtracting the number of days in each month
100 * from day until day is less than the number of days in the current month.
101 * - The current month is the target month, and day + 1 is the target day of
102 * month (to convert from 0-based to 1-based).
103 */
104struct leap_date leap_date(int year, int day_off) {
105 struct leap_off off = leap_off(year, day_off);
106 int month = 1;
107 /*
108 * Iterate months, subtracting month days from day offset until the day offset
109 * is less than the number of days in the current month.
110 *
111 * Take care not to introduce an off-by-one error in the loop termination
112 * condition. The loop continues while month < 12 because by the time month is
113 * 12, any remaining day offset must belong to December.
114 */
115 for (; month < 12; ++month) {
116 const int mday = leap_mday(off.year, month);
117 if (off.day < mday) {
118 break;
119 }
120 off.day -= mday;
121 }
122 return (struct leap_date){
123 .year = off.year,
124 .month = month,
125 .day = off.day + 1,
126 };
127}
128
129struct leap_off leap_from(int year, int month, int day) {
130 const struct quo_mod qm = quo_mod(month - 1, 12);
131 year += qm.quo;
132 return leap_off(year, leap_yday(year, qm.mod + 1) + day - 1);
133}
134
135struct leap_date leap_abs_date(int day_off) { return leap_date(0, day_off); }
136
137int leap_abs_from(int year, int month, int day) {
138 const struct leap_off off = leap_from(year, month, day);
139 return leap_day(off.year) + off.day;
140}
int leap_thru(int year)
Leap years completed from year 0 up to but not including the first day of the specified year.
Definition leap.c:30
struct leap_off leap_from(int year, int month, int day)
Day from year, month and day of month.
Definition leap.c:129
int leap_mday(int year, int month)
Day of month from year and month.
Definition leap.c:80
bool is_leap(int year)
Determine if a year is a leap year.
Definition leap.c:13
struct leap_date leap_abs_date(int day_off)
Absolute date from day of year.
Definition leap.c:135
int leap_add(int year)
Adds one for a leap year otherwise zero.
Definition leap.c:28
int leap_day(int year)
Counts leap-adjusted days up to some year.
Definition leap.c:48
int leap_yday(int year, int month)
Day of year from year and month.
Definition leap.c:86
int leap_abs_from(int year, int month, int day)
Absolute date from year, month, and day of month.
Definition leap.c:137
Leap year function prototypes.
int leap_mday(int year, int month)
Day of month from year and month.
Definition leap.c:80
int leap_add(int year)
Adds one for a leap year otherwise zero.
Definition leap.c:28
int leap_day(int year)
Counts leap-adjusted days up to some year.
Definition leap.c:48
int leap_yday(int year, int month)
Day of year from year and month.
Definition leap.c:86
Quotient and modulus prototypes.
Leap year date structure.
Definition leap.h:163
int year
Year.
Definition leap.h:167
int month
Month of year starting from 1 for January.
Definition leap.h:174
Leap offset by year and day.
Definition leap.h:77
int day
Day of year offset.
Definition leap.h:85
int year
Year offset.
Definition leap.h:81
Quotient and remainder in integer space.
Definition quo_mod.h:22
int mod
Integer modulus.
Definition quo_mod.h:30
int quo
Integer quotient.
Definition quo_mod.h:26