Write a script to find all the years between
1900
and2100
which is aLong Year
.A year is Long if it has 53 weeks.For more information about Long Year, please refer to Wikipedia.
1903, 1908, 1914, 1920, 1925, 1931, 1936, 1942, 1948, 1953, 1959, 1964, 1970, 1976, 1981, 1987, 1992, 1998, 2004, 2009, 2015, 2020, 2026, 2032, 2037, 2043, 2048, 2054, 2060, 2065, 2071, 2076, 2082, 2088, 2093, 2099
No year contains 53 full weeks, and all years contain 52 weeks and some days. So, the one line definition of Long Year needs some clearification.
This refers to how ISO numbers weeks. ISO weeks start on Mondays, ending
on Sundays, and written as yyyyWnn
, where yyyy
is the year, and nn
the week number (W
is just the letter W
).
The first week of the year is the first week which has at least four days
in January. This means that the last days of December could be part of
week 01
of the next year, or that the first days of January could be
part of the last week of the previous year. (This means that at the
end or the beginning of the year, the yyyy
part of the ISO week number
the date falls into may not equal the current year).
Since years are slightly longer than 52 weeks, every now and then we have a year with a week 53.
In particular, a year has a 53rd week if one of the conditions is true:
The cycle of leap years in the Gregorian calendar repeats every 400 years. 400 years in the Gregorian calendar means 146097 days (400 times 365 days plus 97 leap days). And 146097 = 7 * 20871, so 400 years have an exact number of weeks.
This means that the cycle of Long Years repeats every 400 years. So, if we have the Long Years in a 400 year period, we can easily expand this to get all the Long Years. There are 71 Long Years in each 400 year period. For the period 2000 to 2399, we have the following Long Years:
2004 | 2009 | 2015 | 2020 | 2026 | 2032 | 2037 | 2043 | 2048 |
2054 | 2060 | 2065 | 2071 | 2076 | 2082 | 2088 | 2093 | 2099 |
2105 | 2111 | 2116 | 2122 | 2128 | 2133 | 2139 | 2144 | 2150 |
2156 | 2161 | 2167 | 2172 | 2178 | 2184 | 2189 | 2195 | |
2201 | 2207 | 2212 | 2218 | 2224 | 2229 | 2235 | 2240 | 2246 |
2252 | 2257 | 2263 | 2268 | 2274 | 2280 | 2285 | 2291 | 2296 |
2303 | 2308 | 2314 | 2320 | 2325 | 2331 | 2336 | 2342 | 2348 |
2353 | 2359 | 2364 | 2370 | 2376 | 2381 | 2387 | 2392 | 2398 |
To find out whether a year before 2000 or after 2400 is a Long Year, just add or substract the appropriate multiple of 400 and check the table.
Enter a year in the (proleptic) Gregorian calendar, and hit the Calculate button to check whether the year in a Long Year or not.
Enter a year:
Our main solution takes a list of offsets of Long Years for 400 year periods starting at years \(400 \cdot k\). We calculate all the Long Years in the 400 year periods starting in 1600 and 2000 (so we get all the Long Years from 1600 to 2400), and then selecting the years between 1900 and 2100.
For a select few languages, we have implemented an alternative solution. This solution calculates the day of the week of December 31 of the year. If that day is a Thursday, or if that day is a Friday, and the year is a leap year, the year is a Long Year.
my @start_years = qw [1600 2000];
my @long_year_offsets = qw [004 009 015 020 ... 387 392 398]; # All 71 offsets
say for grep {1900 <= $_ <= 2100}
map {my $fy = $_; map {$_ + $fy} @long_year_offsets} @start_years
This uses a nested map to get all the possible sums of a start year and an offset, then greps the ones between 1900 and 2100, which are printed.
Find the full program on GitHub.
First, we calculate the doomsday of the year. This is the day of the week certain days in a year all share: 4/4, 6/6, 8/8, 10/10, 12/12, 5/9, 9/5, 7/11, 11/7, Pi Day, and the last day of Februari, using an algorithm devised by John Conway:
sub doomsday ($year) {
use integer;
my $anchor = (2, 0, 5, 5) [($year / 100) % 4];
my $y = $year % 100;
my $doomsday = ((($y / 12) + ($y % 12) + (($y % 12) / 4)) + $anchor) % 7;
$doomsday;
}
The return value is a integer 0 to 6, with 0 indicating Sunday, and 6 a Saturday.
This gives us the day of the week of December 12. To calculate the day of the week of December 31, we subtract 12, add 31, and take this modulo 7. All we have to do is check if it's a Thursday, or a Friday and a leap year:
foreach my $year (1900 .. 2100) {
my $doomsday = doomsday ($year);
my $dec_31 = ($doomsday - 12 + 31) % 7;
say $year if $dec_31 == 4 || $dec_31 == 5 && is_leap $year;
}
with the following code to determine whether a year is a leap year:
sub is_leap ($year) {
($year % 400 == 0) || ($year % 4 == 0) && ($year % 100 != 0)
}
Find the full program on GitHub.
Our Go solution looks very similar to the main Perl solution, except that we use a nested for loop to calculate the Long Years:
func main () {
start_years := [] int {1600, 2000}
long_year_offsets := [] int {4, 9, 15, ..., 387, 392, 398};
for _, start_year := range start_years {
for _, offset := range long_year_offsets {
year := start_year + offset
if 1900 <= year && year <= 2100 {
fmt . Println (year)
}
}
}
}
Find the full program on GitHub.
The R solution uses nested for loops as well, but it uses a trick R has: if you add a number to a vector, the result is a vector with the number added to each element of the original vector. This gives us:
start_years <- c (1600, 2000)
long_year_offsets <- c (4, 9, 15, ..., 387, 392, 398)
for (start_year in start_years) {
for (year in start_year + long_year_offsets) {
if (1900 <= year && year <= 2100) {
cat (year, "\n")
}
}
}
Find the full program on GitHub.
Erlang is a functional language, so we have to write our loops in a functional way:
long_year ([], _) -> ok;
long_year (_, []) -> ok;
long_year ([Start], [Offset | Offsets]) ->
Year = Start + Offset,
if (1900 =< Year) and (Year =< 2100) -> io:fwrite ("~B~n", [Year]);
true -> ok
end,
long_year ([Start], Offsets);
long_year ([Start | Starts], Offsets) ->
long_year ([Start], Offsets),
long_year (Starts, Offsets).
Find the full program on GitHub.
We also have solutions in: AWK, Bash, bc, C, Java, Lua, Node.js, Pascal, Python, Ruby, Scheme, and Tcl.