Hack 23. Second Tuesday of the Month
You can find "floating" calendar dates, such as the second Tuesday of the month, with modular arithmetic and brute force reasoning.
The formula to calculate the second Tuesday of the month depends on the day of the week of the first day of the month. But which month? Obviously, all you need to know are the year and month, but it's easier if you start with a date: the date of the first day of that month. For testing purposes, use the following table:
CREATE TABLE monthdates(monthdate DATE NOT NULL PRIMARY KEY); INSERT INTO monthdates(monthdate) VALUES (DATE '2007-04-01'); INSERT INTO monthdates(monthdate) VALUES (DATE '2007-05-01'); INSERT INTO monthdates(monthdate) VALUES (DATE '2007-06-01');
So, given a date, the first day of some month, what date is the second Tuesday of that month? The process for obtaining the solution begins by calculating the day of the week for the first day of the month.
4.5.1. Day-of-Week Function
Although standard SQL does not provide a function to give the day of the week for any date, most database systems do. Table 4-5 shows some of the functions that can accomplish this.
Database | Function |
---|---|
MySQL | DAYOFWEEK(date) |
Oracle | TO_CHAR(date,'D') |
SQL Server | DATEPART(DW,date) |
PostgreSQL | EXTRACT(dow FROM date) |
Access | DatePart("w", date) |
The essence of these functions is that they will return a number between 0 and 6, or between 1 and 7. Sometimes 0 (or 1) is Sunday and 6 (or 7) is Saturday, and sometimes 0 (or 1) is Monday and 6 (or 7) is Sunday.
|
The following formula uses the range 1 (Sunday) through 7 (Saturday). If your database system has no easy way to produce this range, but you can produce some other similar range, then you can alter the formula easily once you understand how it works.
4.5.2. The Formula
Converting the first day of the month into the second Tuesday of the month simply involves manipulating the day of the week of the first day with an arithmetic formula. Before you see the formula, you should review what happens when the first day of the month falls on each day of the week, from Sunday through Saturday.
If the first day of the month is:
- A Sunday, the third is a Tuesday, so the tenth is the second Tuesday.
- A Monday, the second is a Tuesday, so the ninth is the second Tuesday.
- A Tuesday, the eighth is the second Tuesday.
- A Wednesday, the seventh is the next Tuesday, so the fourteenth is the second Tuesday.
- A Thursday, the sixth is the next Tuesday, so the thirteenth is the second Tuesday.
- A Friday, the fifth is the next Tuesday, so the twelfth is the second Tuesday.
- A Saturday, the fourth is the next Tuesday, so the eleventh is the second Tuesday.
This exhausts all possibilities. So the challenge now is simply to reduce these facts into a formula. With the aid of an underappreciated technological methodology called brute force, you can verify the correctness of the following manipulation of the day of the week of the first day of the month, as shown in Table 4-6.
A1st | Bwkday | C10B | DC mod 7 | ED+7 |
---|---|---|---|---|
sun | 1 | 9 | 2 | 9 |
mon | 2 | 8 | 1 | 8 |
tue | 3 | 7 | 0 | 7 |
wed | 4 | 6 | 6 | 13 |
thu | 5 | 5 | 5 | 12 |
fri | 6 | 4 | 4 | 11 |
sat | 7 | 3 | 3 | 10 |
The first column (A) is the day of the week of the first day of the month, and the second column is the numerical equivalent of this, using the range 1 (Sunday) through 7 (Saturday).
The important data in Table 4-6 is in the last column, which is the number of days to add to the date of the first day of the month.
So in a nutshell, the formula is:
- Find B, the day of the week of the first day of the month, using:
- 1=Sunday ... 7=Saturday.
- Subtract this number from 10 to get C:
- With Sunday=1 ... Saturday=7, Tuesday would be 3.
- The number 3 B is the offset (relative to the first of the month) for a Tuesday, but it might be in the current month or the previous month; 10 B is also a Tuesday, and so are 17 B and 24 B.
- You should choose to subtract from 10 because you want C to be positive for all inputs. This is because you need D to be positive in the next step, but a negative value for C would result in a negative value for D. This is because 1 % 7 gives 1 on most systems.
- Divide by 7 and keep the remainder to get D.
- D is also the offset for a Tuesday, and D is in the range 0 to 6. Every day in the first week has an offset between 0 and 6. So D is the first Tuesday of the month.
- Add 7 to get E.
- That takes the range of E from 7 to 13. Every day in the second week has an offset in the range 713.
- Take the result and add that number of days to the date of the first day of the month.
In practical terms, to implement this formula you will need to use the specific date and arithmetic functions of your database system. Here are some examples.
4.5.2.1. MySQL
In MySQL:
SELECT monthdate AS first_day_of_month , DATE_ADD(monthdate , INTERVAL ( ( 10 - DAYOFWEEK(monthdate) ) % 7 ) + 7 DAY ) AS second_tuesday_of_month FROM monthdates
4.5.2.2. Oracle
In Oracle:
SELECT monthdate AS first_day_of_month , monthdate + MOD( ( 10 TO_CHAR(monthdate,'d') ), 7 ) + 7 AS second_tuesday_of_month FROM monthdates
4.5.2.3. SQL Server
With SQL Server:
SELECT monthdate AS first_day_of_month , DATEADD(day , ( ( 10 - DATEPART(dw,monthdate) ) % 7 ) + 7 , monthdate ) AS second_tuesday_of_month FROM monthdates
4.5.2.4. PostgreSQL
PostgreSQL gives 0 for Sunday, so you must add 1. Also, the output from EXtrACT is a floating-point number, so you must CAST it before you attempt modular arithmetic:
SELECT monthdate AS first_day_of_month , monthdate + ((10 - CAST(EXTRACT(dow FROM monthdate) + 1 AS INT)) % 7) + 7 AS second_tuesday_of_month FROM monthdates
Here are the results:
first_day_of_month second_tuesday_of_month 2007-04-01 2007-04-10 2007-05-01 2007-05-08 2007-06-01 2007-06-12
4.5.3. Hacking the Hack: The Last Thursday of the Month
You can use a similar technique to calculate the last Thursday of the month. Just find the first Thursday of next month and subtract seven days.
The formula for the offset for the first Thursday of the month beginning with monthdate is:
(12-DAYOFWEEK(monthdate) ) % 7
Subtract from 12 because Thursday is represented by 5 and 5 + 7 = 12.
The query to get the first day of next month is:
mysql> SELECT monthdate AS first_day_of_month -> ,DATE_ADD(monthdate,INTERVAL 1 MONTH) -> AS first_day_of_next_month -> FROM monthdates; +--------------------+-------------------------+ | first_day_of_month | first_day_of_next_month | +--------------------+-------------------------+ | 2007-04-01 | 2007-05-01 | | 2007-05-01 | 2007-06-01 | | 2007-06-01 | 2007-07-01 | +--------------------+-------------------------+
You can use this result to find the first Thursday of next month and subtract 7 to get the last Thursday of this month:
mysql> SELECT first_day_of_month -> ,DATE_ADD(first_day_of_next_month -> ,INTERVAL -> ((12 - DAYOFWEEK(first_day_of_next_month)) % 7) -> - 7 DAY) AS last_thursday_of_month -> FROM -> (SELECT monthdate AS first_day_of_month -> ,DATE_ADD(monthdate,INTERVAL 1 MONTH) -> AS first_day_of_next_month -> FROM monthdates) t; +--------------------+------------------------+ | first_day_of_month | last_thursday_of_month | +--------------------+------------------------+ | 2007-04-01 | 2007-04-26 | | 2007-05-01 | 2007-05-31 | | 2007-06-01 | 2007-06-28 | +--------------------+------------------------+
4.5.4. See Also
- "Generate a Calendar" [Hack #87]
Rudy Limeback