Day of Week, Month, or Year
Problem
How do I determine the day of week for a given date?
Solution
Given the year, the month, and the day of the month we can use Zeller's Congruence to compute the day of the week:
J is the century
K is the year within the century
m is the month
q is the day of the month
The day of the week h is calculated by this formula:
h = (q + 26(m+1)/10 + K + K/4 + J/4 + 5J) mod 7
where all divisions are truncated.
h will be a number between 0 (Saturday) and 6 (Friday).
The formula considers January and February to be months 13 and 14 of the previous year. Consequently we compute a modified month value m and a modified year value y. We implement the formula as follows:
(defn zeller [year month day]
(let [m (+ (mod (+ month 9) 12) 3)
y (- year (quot (- m month) 12))
J (quot y 100)
K (mod y 100)
q day]
(mod (+ q
(quot (* 26 (inc m)) 10)
K
(quot K 4)
(quot J 4)
(* 5 J))
7)))
(zeller 1999 7 16) => 6
(zeller 2010 9 15) => 4
Of course, we may not be satisfied with this form of output. So let's first shift the range 0-6 (Saturday-Friday) to the range 1-7 (Sunday-Saturday).
The general solution for n elements 0, …, n-1 (7 elements in our case) where we want to shift m elements (1 here) is:
(defn shift0 [m n i]
(+ (mod (+ i (- n m)) n) m))
For example, suppose we want to map morning military time to a 12-hour clock, in other words the range 0-11 becomes 1-12. We have 12 elements and want to shift 1:
(shift0 1 12 0) => 12
(shift0 1 12 1) => 1
(shift0 1 12 2) => 2
This works for us here too:
(shift0 1 7 0) => 7
(shift0 1 7 1) => 1
(shift0 1 7 6) => 6
But let's use a more specialized form for this particular case:
(defn shift0 [i]
(+ (mod (+ i 6) 7) 1))
This is similar to what we did in the zeller function above shifting January and February to months 13 and 14. In effect, we took the range 1-12 and mapped it to the range 3-14:
(+ (mod (+ month 9) 12) 3)
The constants come out a little different though since that range started with 1 and our weekday range started with 0.
Now our simplified version works this way:
(zeller 2010 9 4) => 0
(shift0 (zeller 2010 9 4)) => 7
(zeller 2010 9 5) => 1
(shift0 (zeller 2010 9 5)) => 1
So Saturday comes out as 7 and Sunday as 1. Finally, let's turn those into strings. We can get the names of the days of the week from Java:
(seq (.getWeekdays (java.text.DateFormatSymbols.))) => ("" "Sunday" "Monday" "Tuesday" "Wednesday" "Thursday" "Friday" "Saturday")
Putting it all together we get:
(defn day-of-week [year month day]
(nth (.getWeekdays (java.text.DateFormatSymbols.))
(shift0 (zeller year month day))))
(day-of-week 1999 7 16) => "Friday"
(day-of-week 2010 9 15) => "Wednesday"
Determining Leap Years
Problem
How do I tell whether or not a year is a leap year?
Solution
Under the Julian calendar, a leap year was designated every four years. As a result, the average length of the year was 365.25 days. This is close but not exactly right. The Gregorian calendar attempts to fix this by modifying the above rule. Now we designate every fourth year as a leap year unless the year is also a multiple of one hundred. However, this rule also has an exception. If the year is a multiple of four hundred, then it is still designated as a leap year. Thus, 1900 was not a leap year, but 2000 was.
This means that every 400 years rather than having 100 leap years we have only 97. Consequently the average length of the year is 365.2425 days:
(/ (+ (* 303 365) (* 97 366)) 400.0) => 365.2425
Putting all of this information together we can test whether or not a year is a leap year:
(defn leap-year? [year]
(cond (zero? (mod year 400)) true
(zero? (mod year 100)) false
:else (zero? (mod year 4))))
(leap-year? 2010) => false
(leap-year? 2000) => true
(leap-year? 1900) => false
(leap-year? 2012) => true
Back to Clojure Cookbook: Table of Contents
I suggest decomposing day-of-week a bit. Since the day names are essentially constant, there's no need to look them up with Java calls every time. Just use a let around the defn to grab it once. Also, I suggest that you use a vector, which tells the reader that you want to look up by index. (The original Java array works fine. I just like to live in the Clojure world as much as possible.) My check for the day range (1-7) isn't strictly necessary, but I thought I'd throw it in for safety. Note the syntax for vector access (VEC INDEX) is the same as (nth VEC INDEX).
Did you know about Freelance writing job service? I wanted to tell that your ideas connecting to this good post is supreme! Thank you very much for creating it!
curt
Post preview:
Close preview