Requests for "a function to return the week number of a date" are
common. It is essential
• to check which definition is to be used;
• and, for some definitions, to return also the
proper year number, because of the overlaps.
Such a function can also return a Day-of-Week number.
Algorithms here are shown mostly in JavaScript; note that JavaScript stores date/time as milliseconds from 1970.0 GMT.. If translating to VBScript (see via VBScript Date and Time Index), DateSerial can be useful. For Pascal/Delphi, see my Borland Pascal Time and Date and unit dateprox.pas.
Here, when using JavaScript, Week Number Dates are commonly held in arrays [y,w,d] and years are assumed to be AD 100 or later. The possibility of a Date Object representing a time other than 00:00h has largely been disregarded. JavaScript's Math.floor(X/Y) and ((X/Y)|0) are equivalent, the latter being faster, and match VBScript/C's X\Y amd Pascal's X div Y.
Each function giving [y, w, d] here should have a corresponding reverse function.
It should generally be easy enough to convert a week-of-year algorithm to a corresponding week-of-month one.
Refer to the corresponding parts of Calendar Weeks for the associated descriptive material.
It is possible that some code may inadvertently assume either that local time is GMT or that local time is less than 12 hours away from GMT. RSVP if you see signs of that here, unless specific warning is given.
The conversion routines are believed to give correct results, and the JavaScript ones can be tested on this page.
The underlying algorithms are reasonably efficient, They go directly to the result, without looping; except that some have a single recursion.
The implementation of some of the JavaScript algorithms that run on this page may not be fully optimum. In particular, I have noticed that using UTC date object functions internally is much faster, and have been converting more routines. Also, code converting via DayCount is comparatively fast. See also in JavaScript Date and Time 1 : Date Arithmetic.
Pascal/Delphi code in my dateprox.pas etc. is mostly older, and may be less efficient.
Day-of-Week of a given date in early January of a known Gregorian year number is often needed; that can be rapidly determined with expressions such as used by Zeller.
For 1901-2099, to get Day-of-Week of January 1st, using Sun=0 :- (5*(y-1) div 4) mod 7 or Trunc(1.25*(y-1)) mod 7
Note that Appendix B of a draft Internet standard, dated in the 1990's, has a C routine (based on Zeller's) for getting day-of-week from Y M D in which the left operand of a mod operator goes negative for some dates early in a century, giving a wrong result. Uncorrected copies are still on the Net.
For a discussion of ISO 8601 week numbers in general, see in Calendar Weeks.
All ISO weeks are Monday = 1 to Sunday = 7, and week 01 of the numbering year is the one containing the first Thursday of the Gregorian year. Thus up to three days of the first and last weeks for a year number can be in the adjacent Gregorian year; December 29 to January 03 can have a numbering year differing from the Gregorian.
A complete ISO Week Number function must also return the Year Number. It is easy enough also to return the Day-of-Week number, DoW.
Some methods give the Year directly. Otherwise, if the Calendar Month Number is large and the ISO Week Number is small then increment the Year, and vice versa, or otherwise.
String forms such as "yyyy-Www" or "yyyy-Www-d" or "yyyy-ww" or "yyyy-ww-d" can be used, depending on context; or arrays [Y, W, D].
This returns a string "yyyy-Www-d" from an array [y, w, d], to represent ISO 8601 week numbers.
In practice, the best method of implementation will depend on the nature and speed of the available primitive operations, and on whether the date is represented as a string, as Y-M-D, as Y-D, or as a count of time or whatever.
If using a [milli-]seconds-based method for DayCount, consider carefully Summer Time, remembering that it can span a New Year. So rounding the time from January 4th is better than truncating the time from January 1st.
It can be useful to know that CJD 0 was a Monday, and that 1970-01-01 was a Thursday.
When calculating ISO 8601 Week Number, giving the Year Number is essential. The Day Number will normally be easy to find en passant.
If a DayCount is known, then
(DayCount+K) div 7 gives an Absolute Week
Number (AWN),
1 + (DayCount+K) mod 7 gives the Day-of-Week (DoW).
where K allows for the Day-of-Week of DayCount zero, and
ensures that the arguments of mod & div are
never negative.
Using JavaScript,
Manually tested above :-
Here, JavaScript Date Objects are used only in testing. The methods therefore can be implemented in any language.
The following determines ISO Week Number by explicit arithmetic from IsLeapYear, Month, Day, and Day-of-Week, and vice versa, without going explicitly via a day-count.
It shows the possibility of calculating ISO Week Number Www-d from IsLeapYear, Month, Day, and day-of-week-of-January-1st, since those suffice to determine the current Day-of-Week, and vice versa.
Algorithm of function NewWN :-
Algorithm of function RevWN :-
For Day-of-Year, see also in Date Object Information.
Function MathWNo is differently derived and independent of imported functions and Date Objects. It is somewhat optimised in function WNoMath.
Some languages handle dates as DayCounts, often with Day 0 being 1899-12-30 and Day 25569 being 1970-01-01. Conversion of DayCount to/from Y W D is easier than conversion to/from Y M D, and does not need system date routines.
In January 2008 I derived, now in unit programs/dateutys.pas, efficient Pascal/Delphi routines for DateTime to/from YWD, covering the (extendable) range AD 0001 to 9999. The following JavaScript routines mimic them.
Comparison results which can be used by testers, showing Weeks 1, 52 & 53 for over 28 years, are in wknotest.txt.
The following checks should suffice - all weeks have 7 days, Monday is Day 1 of 1..7, January 4th is in Week Number 1 of 1..52/53, Year usually matches, numbers YYYYWWD are in increasing order, testing over 28 years with quadrennial Leap Years (or at least over all 14 year types). That does not fully test the effects of the 100 and 400 year rules introduced by Pope Gregory XIII.
Test cases should include those tabled in Calendar Weeks.
To test a function that generates Week Numbering dates from an ordinary date, or vice versa, one can easily generate an independent sequence of known-good Week Numbering dates.
ISO 8601:2004(E) §3.2.2 "The week calendar" includes The reference point of the time scale assigns Saturday to 1 January 2000; and, since 2000 (being Leap) had 366 = 52×7+2 days, 2001-01-01 was a Monday, and was 2001-W01-1.
Then, starting for example with Y=2001, W=1, D=1, one can increment D in 1..7, carrying into W in 1..52/53, carrying into Y. To choose between 52 and 53, it is sufficient to note that the first day of a Week 53 can only be the 28th or 27th of its month, or that carry into Y can only occur before the 29th to before the 4th.
One can also step Thursdays (D=4) a week at a time, get D=1 to D=7 from them (in the same year and week), and reset W=1 whenever either W=53 or both W=52 and Y has changed.
JavaScript is placed first merely for the convenience of contiguity with the demonstration above.
Where Math.round(X) is used, X|0 may be faster, and is equivalent for 0≤X<2^{31}; but beware operator precedences.
Code not using Date Objects is currently above.
These should be evidently the same algorithms as demonstrated. It shows errors in Opera 9; see in JavaScript Date and Time Troubles :-
The next routine is in inc-date.js; YMD2YWD uses a different method and parameters to the above.
NOTE the change of 2007-04-03 in YMD2YWD, to allow for a problem with the browser Firefox 2.0.0.3.
First Validate. Then save Day-of-Week, get nearest Thursday, save
Time; go to January 4th, subtract from Time, round the Week difference.
Basically, put Year Jan 4th into a date object, getDay(), then
use setDate() to move back 0..6 days to Monday and to add (WW-1)*7 days
for the start of the week.
Contents moved upwards.
A direct routine for CMJD to Year-Week-Day, and a new direct reverse routine, with CMJD to/from Year-Month-Day and Year-Month-Day to/from Year-Week-Day using them :-
Note that routines using CMJD are comparatively fast.
Given the straightforward handling of the Week and Day components in conversions from YWD to ordinary date, it seemed sufficient to test just over one day per year for over 400 years.
I have some more JavaScript for date conversions, and a little VBScript.
In unaided DOS..Win98/ME, date calculations cannot reasonably be done.
My NOWMINUS and ENVICALC are batch-enhancing programs for date/time work and for integer arithmetic; get Pascal source and executable via programs.
NOWMINUS F5 J0 L6 Eisoweekno ; set env. ISOWEEKNO to ISO YYYYWW NOWMINUS F33 J0 L6 Etaxweekno ; set env. TAXWEEKNO to UK HMRC YYYYWW For an absolute week count, from BC 4713, NOWMINUS F9 EDN ; set CJD in env. DN, then ENVICALC CDN 3 - 7 / B6 EWN ; set Local AbsWkNo in env. WN Adjust the value of 3 by up to ±3 to get the desired first day of the week (..., Mon, ...); put N - after / to alter the zero week by N.
See also in MS-DOS Batch Files, and WSH below this.
In Windows NT, XP, etc., batch, it should be possible to code most or all common date/time work. In particular, CMD.EXE FAQ ZIP #149. But it will often be easier to use WSH with VBS or JS.
Batch file iso-wkno.bat contains and tests CMD.EXE code for obtaining y w d and yyyy-Www-d from yyyy mm dd, for 1900-03-01 to 2100-02-28 inclusive.
See my code in VBScript Date and Time 2 - for all valid years.
Note that DatePart("ww", Date, vbMonday, vbFirstFourDays) is not always satisfactory.
WSH can execute JavaScript and VBScript; see in MS-DOS Batch Files.
See ISO 8601 Week of Year format in Microsoft .Net.
Apparently, the general algorithm above should be easy to implement using only basic DateTime routines.
Code by Joel Bennett is at poshcode; I understand that its results agree with mine, though the algorithm seems to differ.
Also, the thread with this article, by Kiron, dated Sat, 21 Mar 2009 10:38:41 -0600, looks good.
My new (2012-11-05) page weekpshl.ps1, written independently of the above and subsequently reconsidered, contains code to convert between a PowerShell Datetime object and ISO 8601 week-numbering form, etc.
My Pascal unit dateprox.pas, tested by program mjd_date.pas with included version.pas (BP7; Delphi 3 at least) can calculate week number (ISO, Tax & otherwise) from date, by first principles. Program mjd_date.exe was used for :-
Peter Haas has written Delphi Week Number code, ISO & otherwise (ZIP).
Recent Delphi has WeekOfTheYear to give ISO Week Numbers (NOTE: verified over full 9999 years; wrong within Mondays before 1900). It is longer than necessary, as I can do the job correctly , better and slightly faster, in five commented one-line statements. I can do the reverse in four one-line statements. Routines are now in dateprox.pas and demonstrated in mjd_date.pas; versions in unit programs/dateutys.pas may be even better.
GAWK code for ISO week number, valid for 1970-2099, independent of strftime(), is tested in programs/iso-wkno.awk. Where different, the result of strftime (which is then expected to be 53) is appended.
Presumably C code would be similar.
See also Week Numbers in UNIX & C Utilities.
I've been sent the following code to determine the start of the ISO Week-Numbering Year; I think it's for SQL Server.
Truncate(date_0, 'IY') = Truncate(Truncate(Truncate(date_0, 'IW') + 3, 'YEAR') + 3, 'IW')
"Truncate(date, 'IW') truncates an input date to the nearest Monday (ISO Week start), Truncate(date, 'YEAR') returns calendar year start, and '+ 3' means 'plus 3 days'". That amounts to "go to the nearest Thursday, go to January 4th of that calendar year, go to the start of that week", which is appropriate.
I don't know, or have, Perl.
Revised Perl code contributed by Robert Urban (2002-08-13), which I have not been able to test, is in programs/what-cw.pl. It uses only basic arithmetic operations. Check it before use.
However, Harry Broomhall wrote (2002-08-18) :-
There are many time/date-based modules/libraries for Perl, but, IMHO, the one known as Date::Calc is the most comprehensive, and the most mature. Quoting this name is generally the standard response to questions about date-handling in newsgroups/lists.
Date::Calc can be retrieved from CPAN (the major repository of Perl libraries). The module includes a *lot* of utilities to cover most requirements for 'normal' civil uses (i.e. it doesn't include astro requirements, or odd calendars such as Mayan).
A working example (weekno.pl) to get week-numbers from a date (passed on the command line) is as follows :-
#!/usr/bin/perl -w use strict; use Date::Calc qw(Week_of_Year); my ($wn, $yr); ($wn, $yr) = Week_of_Year($ARGV[0], $ARGV[1], $ARGV[2]); print "$wn of $yr\n";
The author of Date::Calc clearly recognises ISO 8601 as the standard to use.
DateTime code via Dave Rolsky may have ISO week numbers. Looks promising. May be part of The Perl DateTime Project.
I don't know, or have, Perl.
A function in GNU R, derived by Theodore Lytras of Athens from the algorithm which I use.
isoweek <- function(x) { x.date<-as.Date(x) x.weekday<-as.integer(format(x.date,"%w")) x.weekday[x.weekday==0]=7 x.nearest.thu<-x.date-x.weekday+4 x.isoyear<-as.integer(substring(x.nearest.thu,1,4)) x.isoweek<-(as.integer(x.nearest.thu-as.Date(paste(x.isoyear,"-1-1",sep="")))%/%7)+1 paste(x.isoweek,x.isoyear,sep="/") }
HTML 5 introduced various input control types to handle dates/times. So far, I have only seen these implemented in Opera 9.64+. Generally, they do not work, or work badly.
See also at JavaScript Date and Time 3 : Input and Lengths.
See in Calendar Weeks.
These examples are in JavaScript, but might be adapted for other languages. Adapting for Week-of-Month should be obvious.
Alas, Financial Years may not approximate to Calendar or ISO Week-Numbering Years; then, the code for Types 1 & 2 will need modification.
For specific uses, the fixed parameter values can be incorporated into the code.
In each case, the Date Object should represent a time near midnight, and is not altered; and Week Number is an array [Y, W, D] of Numbers.
This returns a string to represent non-standard week numbers.
Therefore, weeks 01 and 52 may be, and 53 will, be partial.
Week 1 = Sun-Sat row seems to be what Microsoft VBS datepart("ww", D) gives.
Therefore, the Year Number can differ from the Calendar Year, and must be returned. ISO 8601 Week Numbers are a specific case.
These examples are for numberings in which Week 1 is more-or-less at the start of the calendar year.
2009-01-18 : a somewhat ad hoc fix has been applied, on finding that "BBC" was a week out. See comment in View Source. Check the previous line too.
Therefore (unless that date is January 1st), the Year Number can differ from the Calendar Year, and Week 53 has one or two days.
UK HMRC (HM Revenue and Customs; was HM Inland Revenue) Tax Weeks are a particular case of Type 3. See in Calendar Weeks. For VBScript, Excel, etc. see in VBScript Date and Time 2.
These string functions are in inc-date.js, take different arguments, and return text.
Those can easily be generalised for Month 1 and Week 1 starting on any date of the calendar year; in the code, 134 is just 4×32+6 for Month 4 Day 6.
These start the week count on Monday, Julian BC 4713-01-01, 00:00:00 GMT or local time (which corresponds with CJD). Modification for other starts is easy. Subtracting two such numbers gives what might be called the difference in ISO Week Number.
See in Calendar Weeks.
Check the definitions carefully, and test the implementation well too. Consider the TZ environment variable, and determine whether it is fully used, including the transition dates. Check what day and what number begin each week, and what happens around the change in year number.
Any Week Number program whuch may use strftime .. %V for ISO 8601 Week Number should be tested for 2001-12-30 2001-12-31 2002-01-01 to see whether it shows an erroneous Week 53 for Mon 2001-12-31 only; cf. ISO Week Number Using DatePart.
A 1999 man page in unixhelp (*) includes ISO formats %G %g for the year of %V the week number, %u for Mon=1..Sun=7. Also, it has other versions of week data.
An older GAWK man page (FSF, 1994-11-24) implied that the corresponding GAWK strftime() followed the ANSI C standard; and tests on a particular older GAWK show that it includes non-ISO week numbering. Beware.
But Markus Kuhn has written :-
- ANSI C is obsolete. The current C standard is ISO C (1999),
which has %V in strftime() for
ISO 8601 week numbers.
Tests show that my old copy of GAWK (Gnu Awk (gawk) 2.15, patchlevel 6) does have %V but that it is implemented incorrectly for many year rollovers; and does not know %G which should give the proper year. I have code to fix this.