SideBar    The Logical Puzzle, Share Your DATETIME Thoughts

In my February and March columns, I talked about challenges related to datetime calculations. I presented techniques for separating the date and time parts of a datetime value, returning the first/last day of the month, and calculating a language-independent weekday number. Now, I want to offer some techniques for calculating the date of the last/next occurrences of a weekday related to a specific date (call it the event date)—for example, calculating the date of the most recent Monday related to today.

All the techniques I present in this article will be based on the calculations of a language-independent weekday number, which I discussed last month. As a reminder, I presented two techniques to calculate a language-independent weekday number. One technique was based on calculating the offset in terms of days between a base date and the event date (call it diff). The base date had the same weekday as the one you wanted to set as the logical first day of the week (e.g., the string 19000101 or the integer 0 for Monday). When converting the integer 0 to a datetime value, you get the base date January 1, 1900, which happens to be a Monday. So 0 represents a date that falls on a Monday, 1 represents a date that falls on a Tuesday, and so on. The expression diff % 7 + 1 produced the weekday number. For example, to calculate the weekday of today's date, assuming Monday as the first day of the week, you use the expression

SELECT DATEDIFF(day, 0, GETDATE()) % 7 + 1;

If today happens to be a Tuesday, the above expression would return 2. In this article, I refer to this technique as datediff-based.

Another technique I presented was based on neutralizing the impact of the DATEFIRST setting on the DATEPART calculation. The expression I used added @@DATEFIRST days to the event date and subtracted a constant representing the logical first day of the week that you want to use. For example, to calculate the weekday of today's date, assuming Monday as the first day of the week, you use the expression

SELECT DATEPART(weekday, GETDATE() + @@DATEFIRST - 1); 

Again, if today happens to be a Tuesday, the above expression would return 2. In this article, I refer to this technique as datepart-based.

Last Occurrence of a Weekday
As long as the datediff-based and datepart-based expressions are completely clear to you, we can proceed. You're now equipped to write expressions that calculate the last or next occurrence of a weekday.

Datediff. Suppose today's date is December 19, 2006 (a Tuesday), and you want to calculate the last occurrence of Monday, which might be today (that is, the calculation is inclusive). So if today is a Monday, the expression would return today's date. Because I'm assuming in my example that today's date is December 19, 2006 (Tuesday), the expression should return the most recent occurrence of Monday, which is yesterday. Here's the datediff-based expression that returns the date of the most recent Monday:

-- Last Monday (inclusive)
  SELECT DATEADD(day, DATEDIFF
  (day, 0, -- Base Monday date
  GETDATE()) /7*7, 0); -- Base
  Monday date

The DATEDIFF function calculates the difference in terms of days between a base date, which is a Monday, and today's date (call it diff). The expression then divides diff by 7 and multiplies by 7, practically subtracting the number of days that passed since the most recent Monday (call the result floored_diff). Finally, the expression adds floored_diff days to the base date, returning the date of last Monday.

As expected, the expression returns last Monday's date, assuming today is December 19, 2006:

2006-12-18 00:00:00.000

As a side note, to validate the expressions in this article, you can explicitly specify 20061219 as the date instead of GETDATE().

To return the most recent Tuesday's date, provide a date that falls on a Tuesday as the base date (represented by the integer 1 or the string 19000102):

SELECT DATEADD(day, DATEDIFF(day,
1, GETDATE())/7*7, 1);

You'll get today's date (December 19, 2006) as output because the calculation is inclusive:

2006-12-19 00:00:00.000

To return the most recent Sunday's date, provide a date that falls on a Sunday as the base date (represented by the integer 6 or the string 19000107):

SELECT DATEADD(day, DATEDIFF(day,
  6, GETDATE()) /7*7, 6);

You get the output

2006-12-17 00:00:00.000

Another flooring technique that you can use instead of dividing diff by 7 and then multiplying it by 7 is to subtract from diff a number representing the offset (in terms of days) of "today's day of the week" from "the day of the week you need." For example, if the requested day of the week is Tuesday (represented by the integer 1 or the string 19000102), this offset would be expressed as

DATEDIFF(day, 1 /* Base Tuesday
  Date */, GETDATE()) % 7

Call this expression offset. Now, embed offset as part of an expression that calculates the most recent occurrence of a requested day of the week:

-- Don't run 
  SELECT DATEADD(day, DATEDIFF(day,
  0 /* Base Date */, GETDATE())
  - diff - offset, 0 /* Base Date
  */);
   Prev. page   [1] 2 3     next page



You must log on before posting a comment.

If you don't have a username & password, please register now.

Reader Comments

dear Itzik, it would be nice to learn from you how to manage datetime information in UTC format. I'm currently involved with the consolidation of a system, where datetime fields are expressed in local format (time zone) and have to be transformed consistently in UTC form. Good matter for a new article of yours!

Thank you marco buttazzoni

mbuttazzoni

Article Rating 5 out of 5

The following feedback was sent to Izik by reader Peter Larsson ... Diana

I read your third article today about DATETIMEs. I recently too wrote an article about DATEDIFF function here http://www.sqlteam.com/item.asp?ItemID=26922

In the following conversation, I put together a little test script for getting last day of a period, such as MONTH, QUARTER and YEAR. This without your extra substractions. See http://www.sqlteam.com/forums/topic.asp?TOPIC_ID=80880

The trick is to use the date -1, which incidentally is December 31, 1899. The trick is the 31 days in December.

SELECT CURRENT_TIMESTAMP AS Now, Status,

DATEADD(MONTH, DATEDIFF(MONTH, StartingPoint1, CURRENT_TIMESTAMP), StartingPoint2) AS [MONTH],

DATEADD(QUARTER, DATEDIFF(QUARTER, StartingPoint1, CURRENT_TIMESTAMP), StartingPoint2) AS [QUARTER], DATEADD(YEAR, DATEDIFF(YEAR, StartingPoint1, CURRENT_TIMESTAMP), StartingPoint2) AS [YEAR]

FROM (

SELECT 'End Previous' AS Status, 0 AS StartingPoint1, -1 AS StartingPoint2 UNION ALL

SELECT 'Start Current', 0, 0 UNION ALL SELECT 'End Current', -1, -1 UNION ALL SELECT 'Start Next', -1, 0 ) AS x

ORDER BY 5 I like your Black-belt articles, but I thought this time your calculations could be made simpler. Peter Larsson peso@developerworkshop.net

DianaMay

Article Rating 5 out of 5

Nice idea, Peter! Thanks for sharing… Regards, Itzik

DianaMay

Article Rating 5 out of 5