patternjavaMinor
It's Friday! - Zeller's Congruence Revisited
Viewed 0 times
revisitedfridaycongruencezeller
Problem
A couple of date-related questions have come up recently, and on both occasions I have been taken back to my 'early' days where one of my first forays in to programming involved implementing Zeller's Congruence.
Zeller's congruence is a neat calculation because it involves a mathematical approach to date manipulation, and the math relies on both modular and integral arithmetic, combined with a smart way of visualizing time progression.
$$
\begin{align}
h =&\ \left(q + \left\lfloor \frac{13(m + 1)}{5} \right\rfloor + K + \left\lfloor \frac{K}{4} \right\rfloor + 5J + \left\lfloor \frac{J}{4} \right\rfloor \right) \mod7\\
where\\
h =&\ \text{the day of the week (0 is Saturday ... 6 is Friday)}\\
q =&\ \text{the day of the month}\\
m =&\ \text{the month (3 = March, 4 = April, ..., 13 = January, 14 = February)}\\
&\ \text{If Jan or Feb, then you have to adjust the year back by 1 year}\\
&\ \text{Jan 2001 is month 13 of year 2000}\\
K =&\ \text{the century count} \implies \left\lfloor\ \frac{\mathtt{adj.year}}{100}\right\rfloor\\
J =&\ \text{the adjusted year in the century} \implies (\ \mathtt{adj.year} \mod 100)\\
\end{align}
$$
The two recent questions that have inspired me to go back and re-implement Zeller's Congruence are:
As a secondary exercise, I tested the code using the new-in-Java8 time API.
Zeller's Congruence
Note: The code contains comments which go some way to explaining how the congruence works
```
// Used for a quick validation of days in a month.
// Note filler at MONTHDAYS[0] because months are 1-based.
private static final int[] MONTHDAYS =
{ 0, 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };
private static final boolean isLeap(final int year) {
return (year % 4) == 0 && (year % 100 != 0 || year % 400 == 0);
}
/**
* Calculate the day of the week (1=Monday, 7=Sunday) for the supplied
* (valid) date.
* @param year The year to calculate (year 1 or more recent)
* @p
Zeller's congruence is a neat calculation because it involves a mathematical approach to date manipulation, and the math relies on both modular and integral arithmetic, combined with a smart way of visualizing time progression.
$$
\begin{align}
h =&\ \left(q + \left\lfloor \frac{13(m + 1)}{5} \right\rfloor + K + \left\lfloor \frac{K}{4} \right\rfloor + 5J + \left\lfloor \frac{J}{4} \right\rfloor \right) \mod7\\
where\\
h =&\ \text{the day of the week (0 is Saturday ... 6 is Friday)}\\
q =&\ \text{the day of the month}\\
m =&\ \text{the month (3 = March, 4 = April, ..., 13 = January, 14 = February)}\\
&\ \text{If Jan or Feb, then you have to adjust the year back by 1 year}\\
&\ \text{Jan 2001 is month 13 of year 2000}\\
K =&\ \text{the century count} \implies \left\lfloor\ \frac{\mathtt{adj.year}}{100}\right\rfloor\\
J =&\ \text{the adjusted year in the century} \implies (\ \mathtt{adj.year} \mod 100)\\
\end{align}
$$
The two recent questions that have inspired me to go back and re-implement Zeller's Congruence are:
- Function for checking leap years
- Is it Friday yet?
As a secondary exercise, I tested the code using the new-in-Java8 time API.
Zeller's Congruence
Note: The code contains comments which go some way to explaining how the congruence works
```
// Used for a quick validation of days in a month.
// Note filler at MONTHDAYS[0] because months are 1-based.
private static final int[] MONTHDAYS =
{ 0, 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };
private static final boolean isLeap(final int year) {
return (year % 4) == 0 && (year % 100 != 0 || year % 400 == 0);
}
/**
* Calculate the day of the week (1=Monday, 7=Sunday) for the supplied
* (valid) date.
* @param year The year to calculate (year 1 or more recent)
* @p
Solution
final int q = day;
final int m = month + (month < 3 ? 12 : 0);
final int calcyear = year - (month < 3 ? 1 : 0);
final int K = calcyear % 100;
final int J = calcyear / 100;
int offset = 0;
offset += J * 5 + J / 4;
offset += K + K / 4;
offset += (13 * (m + 1)) / 5;
offset += q;
int h = offset % 7;
return ((h + 5) % 7) + 1;That's the code without any comments. I'm going to try to optimize it, although you'll have to do your own performance testing.
A couple things come to mind already:
final int m = month + (month < 3 ? 12 : 0);
final int calcyear = year - (month < 3 ? 1 : 0);Assign the
month < 3 to isJanOrFeb instead.final boolean isJanOrFeb = month < 3;
final int m = month + (isJanOrFeb ? 12 : 0);
final int calcyear = year - (isJanOrFeb ? 1 : 0);Don't do unnecessary assignment...
int offset = 0;
offset += J * 5 + J / 4;It's a waste.
int offset = J * 5 + J / 4;There's only 1 usage of
m...offset += (13 * (m + 1)) / 5;And since it's final only one set too
final int m = month + (month < 3 ? 12 : 0);So maybe combine the
+ 1?final int m = month + (isJanOrFeb ? 13 : 1);
offset += (13 * m) / 5;This bit of code can be collapsed easily...
int h = offset % 7;
return ((h + 5) % 7) + 1;To this
int h = (offset + 5) % 7;
return h + 1;But then again, one wonders why you don't declare
h as final... or whether you need it at all.return ((offset + 5) % 7) + 1;All I did was remove the
h line and replace h with offset.Final code:
final int q = day;
final boolean isJanOrFeb = month < 3;
final int m = month + (isJanOrFeb ? 12 : 0) + 1;
final int calcyear = year - (isJanOrFeb ? 1 : 0);
final int K = calcyear % 100;
final int J = calcyear / 100;
int offset = q;
offset += (13 * m) / 5;
offset += K + (K / 4);
offset += (J * 5) + (J / 4);
return ((offset + 5) % 7) + 1;I moved the statements around so it looks more like the original function.
Code Snippets
final int q = day;
final int m = month + (month < 3 ? 12 : 0);
final int calcyear = year - (month < 3 ? 1 : 0);
final int K = calcyear % 100;
final int J = calcyear / 100;
int offset = 0;
offset += J * 5 + J / 4;
offset += K + K / 4;
offset += (13 * (m + 1)) / 5;
offset += q;
int h = offset % 7;
return ((h + 5) % 7) + 1;final int m = month + (month < 3 ? 12 : 0);
final int calcyear = year - (month < 3 ? 1 : 0);final boolean isJanOrFeb = month < 3;
final int m = month + (isJanOrFeb ? 12 : 0);
final int calcyear = year - (isJanOrFeb ? 1 : 0);int offset = 0;
offset += J * 5 + J / 4;int offset = J * 5 + J / 4;Context
StackExchange Code Review Q#67722, answer score: 7
Revisions (0)
No revisions yet.