Calculate the date from an ISO date
published: Fri, 24-Feb-2006 | updated: Fri, 24-Feb-2006
One of the most popular pages on my website is the one where I discuss how to calculate the ISO week number of a date. Today out of the blue I was asked, if given an ISO week, how would you do the reverse calculation and find the original date.
The thing is, my original code only calculated the week number and the year for a given date. Using those two pieces of information is insufficient to calculate the original date; indeed, you could only really return the date of the Monday of that week as a predictable answer.
So to properly answer the question I'd have to alter the original code so that the full ISO date was returned: the year, the week, and the day of the week. OK, no problem.
Then I started thinking. The original code I'd written was procedural (or, since it was written in C#, it used static methods): when presented with a date it would return the ISO week. Given that I'd been ranting recently about procedural code and that we should endeavor to design so as not to use it, I didn't want to just write yet another static method. No, it had to be a proper class. Much better. For fun I then decided to use Chrome instead of C# (Chrome is an Object Pascal derivative hosted in Visual Studio 2005).
So first of all, here's the original code, tidied up (some of my
original identifiers were not that good), and converted to Chrome as a
class. I also changed it so that the class deals with ISO dates (it
returns the ISO date as an integer in the form YYYYWWD, where YYYY is
the year, WW is the week number from 01 to 53, and D is the day of the
week with 1 for Monday, 2 for Tuesday, all the way to 7 for Sunday).
The ToString()
method returns the formatted version of
the ISO date with a constant character W in between the year and the
week (2005123, the Wednesday in the 12th week of 2005, would be
converted to '2005W123').
Anyway here's the code:
namespace IsoWeek; interface type IsoDate = public class private FValue : integer; method get_Day: integer; method get_Week: integer; method get_Year: integer; method getIsoDayNumber(date : DateTime) : integer; method getMondayOf1stWeek(year : integer) : DateTime; method convertDateToIsoDate(date : DateTime) : integer; protected public constructor(date : DateTime); constructor(year : Integer; month : Integer; day : integer); method ToString : string; override; property Value : integer read FValue; property Year : integer read get_Year; property Week : integer read get_Week; property Day : integer read get_Day; end; implementation {===IsoDate==========================================================} constructor IsoDate(date : DateTime); begin FValue := convertDateToIsoDate(date); end; {--------} constructor IsoDate(year : Integer; month : Integer; day : integer); begin FValue := convertDateToIsoDate(new DateTime(year, month, day)); end; {--------} method IsoDate.get_Year: integer; begin Result := FValue div 1000; end; {--------} method IsoDate.get_Week: integer; begin Result := (FValue div 10) mod 100; end; {--------} method IsoDate.get_Day: integer; begin Result := FValue mod 10; end; {--------} method IsoDate.getIsoDayNumber(date : DateTime) : integer; begin // the ISO day number has 1==Monday, ..., 7==Sunday Result := integer(date.DayOfWeek); // 0==Sunday, 6==Saturday if (Result = 0) then Result := Result + 7; end; {--------} method IsoDate.getMondayOf1stWeek(year : integer) : DateTime; var dt : DateTime; begin // get the date for the 4-Jan for this year dt := new DateTime(Year, 1, 4); // return the date of the Monday that is less than or equal // to this date Result := dt.AddDays(1 - getIsoDayNumber(dt)); end; {--------} method IsoDate.convertDateToIsoDate(date : DateTime) : integer; var mondayOf1stWeek : DateTime; isoYear : Integer; begin isoYear := date.Year; if (date >= new DateTime(isoYear, 12, 29)) then begin mondayOf1stWeek := getMondayOf1stWeek(isoYear + 1); if (date < mondayOf1stWeek) then mondayOf1stWeek := getMondayOf1stWeek(isoYear) else inc(IsoYear); end else begin mondayOf1stWeek := getMondayOf1stWeek(isoYear); if (date < mondayOf1stWeek) then begin dec(isoYear); mondayOf1stWeek := getMondayOf1stWeek(isoYear); end; end; Result := (isoYear * 1000) + (((date - mondayOf1stWeek).Days / 7 + 1) * 10) + getIsoDayNumber(date); end; {--------} method IsoDate.ToString : string; begin Result := string.Format('{0:0000}W{1:000}', FValue div 1000, FValue mod 1000); end; {====================================================================} end.
Now, to calculate the original date from an ISO date is simple: extract the year part, calculate the date of the Monday of the first week for that year, and then add the number of days in the week part of the ISO date and the day of the week.
namespace IsoWeek; interface type IsoDate = public class private ... method get_Date: DateTime; ... protected public ... property Date : DateTime read get_Date; end; implementation {===IsoDate==========================================================} ... method IsoDate.get_Date: DateTime; begin Result := getMondayOf1stWeek(Year).AddDays((Week - 1) * 7 + (Day - 1)); end; ... {====================================================================}
Not to hard, I think you'll agree. And it certainly looks better as a class.