23

Suppose I have a date, i.e. year, month and day, as integers. What's a good (correct), concise and fairly readable algorithm for computing the ISO 8601 week number of the week the given date falls into? I have come across some truly horrendous code that makes me think surely there must be a better way.

I'm looking to do this in Java but psuedocode for any kind of object-oriented language is fine.

Basil Bourque
  • 303,325
  • 100
  • 852
  • 1,154
user9511
  • 231
  • 1
  • 2
  • 3

8 Answers8

26

tl;dr

LocalDate.of( 2015 , 12 , 30 )
         .get ( 
             IsoFields.WEEK_OF_WEEK_BASED_YEAR 
         )

53

…or…

org.threeten.extra.YearWeek.from (
    LocalDate.of( 2015 , 12 , 30 )
)

2015-W53

java.time

Support for the ISO 8601 week is now built into Java 8 and later, in the java.time framework. Avoid the old and notoriously troublesome java.util.Date/.Calendar classes as they have been supplanted by java.time.

These new java.time classes include LocalDate for date-only value without time-of-day or time zone. Note that you must specify a time zone to determine ‘today’ as the date is not simultaneously the same around the world.

ZoneId zoneId = ZoneId.of ( "America/Montreal" );
ZonedDateTime now = ZonedDateTime.now ( zoneId );

Or specify the year, month, and day-of-month as suggested in the Question.

LocalDate localDate = LocalDate.of( year , month , dayOfMonth );

The IsoFields class provides info according to the ISO 8601 standard including the week-of-year for a week-based year.

int calendarYear = now.getYear();
int weekNumber = now.get ( IsoFields.WEEK_OF_WEEK_BASED_YEAR );
int weekYear = now.get ( IsoFields.WEEK_BASED_YEAR ); 

Near the beginning/ending of a year, the week-based-year may be ±1 different than the calendar-year. For example, notice the difference between the Gregorian and ISO 8601 calendars for the end of 2015: Weeks 52 & 1 become 52 & 53.

enter image description here

enter image description here

ThreeTen-Extra — YearWeek

The YearWeek class represents both the ISO 8601 week-based year number and the week number together as a single object. This class is found in the ThreeTen-Extra project. The project adds functionality to the java.time classes built into Java.

ZoneId zoneId = ZoneId.of ( "America/Montreal" );
YearWeek yw = YearWeek.now( zoneId ) ;

Generate a YearWeek from a date.

YearWeek yw = YearWeek.from (
    LocalDate.of( 2015 , 12 , 30 )
)

This class can generate and parse strings in standard ISO 8601 format.

String output = yw.toString() ;

2015-W53

YearWeek yw = YearWeek.parse( "2015-W53" ) ;

You can extract the week number or the week-based-year number.

int weekNumber = yw.getWeek() ;
int weekBasedYearNumber = yw.getYear() ;

You can generate a particular date (LocalDate) by specifying a desired day-of-week to be found within that week. To specify the day-of-week, use the DayOfWeek enum built into Java 8 and later.

LocalDate ld = yw.atDay( DayOfWeek.WEDNESDAY ) ;

About java.time

The java.time framework is built into Java 8 and later. These classes supplant the troublesome old legacy date-time classes such as java.util.Date, Calendar, & SimpleDateFormat.

To learn more, see the Oracle Tutorial. And search Stack Overflow for many examples and explanations. Specification is JSR 310.

The Joda-Time project, now in maintenance mode, advises migration to the java.time classes.

You may exchange java.time objects directly with your database. Use a JDBC driver compliant with JDBC 4.2 or later. No need for strings, no need for java.sql.* classes.

Where to obtain the java.time classes?

The ThreeTen-Extra project extends java.time with additional classes. This project is a proving ground for possible future additions to java.time. You may find some useful classes here such as Interval, YearWeek, YearQuarter, and more.

Basil Bourque
  • 303,325
  • 100
  • 852
  • 1,154
13

I believe you can use the Calendar object (just set FirstDayOfWeek to Monday and MinimalDaysInFirstWeek to 4 to get it to comply with ISO 8601) and call get(Calendar.WEEK_OF_YEAR).

technophile
  • 3,556
  • 1
  • 20
  • 24
  • 1
    Your reasoning and/or evidence being...? – technophile Feb 16 '15 at 16:08
  • Works most of the time, but not always. The Problem is described [here](https://blogs.msdn.microsoft.com/shawnste/2006/01/24/iso-8601-week-of-year-format-in-microsoft-net/) (yes, it is .Net-Code, but same Problem). See Basil Bourque answere for correct solution. – VinZ Sep 26 '19 at 15:11
12
/* Build a calendar suitable to extract ISO8601 week numbers
 * (see http://en.wikipedia.org/wiki/ISO_8601_week_number) */
Calendar calendar = Calendar.getInstance();
calendar.setMinimalDaysInFirstWeek(4);
calendar.setFirstDayOfWeek(Calendar.MONDAY);

/* Set date */
calendar.setTime(date);

/* Get ISO8601 week number */
calendar.get(Calendar.WEEK_OF_YEAR);
DoctorBug
  • 470
  • 5
  • 9
  • In fact `setMinimalDaysInFirstWeek(4)` (at least half a week at year beginning), is default for the standard, and not needed in java - _I think_. – Joop Eggen Feb 10 '14 at 15:01
  • 3
    instead of `calendar.get(Calendar.YEAR)` use `calendar.getWeekYear()` to get week year consistent with ISO 8601 – Archit Jan 27 '15 at 06:09
  • 1
    @JoopEggen That may be true sometimes, but not always, because `GregorianCalendar` is locale-sensitive. So depending on the selected locale (or default locale) the value for _minimal days in first week_ and also _first day of week_ can vary. The Javadoc of `GregorianCalendar` provides some information about this. – Sky Aug 30 '16 at 18:28
  • How do you get the date in your example? The timezone is not known if there is only year, month and day. – Gustave May 12 '18 at 06:06
8

The joda-time library has an ISO8601 calendar, and provides this functionality:

http://joda-time.sourceforge.net/cal_iso.html

yyyy-Www-dTHH:MM:SS.SSS This format of ISO8601 has the following fields:

* four digit weekyear, see rules below
* two digit week of year, from 01 to 53
* one digit day of week, from 1 to 7 where 1 is Monday and 7 is Sunday
* two digit hour, from 00 to 23
* two digit minute, from 00 to 59
* two digit second, from 00 to 59
* three decimal places for milliseconds if required

Weeks are always complete, and the first week of a year is the one that includes the first Thursday of the year. This definition can mean that the first week of a year starts in the previous year, and the last week finishes in the next year. The weekyear field is defined to refer to the year that owns the week, which may differ from the actual year.

The upshot of all that is, that you create a DateTime object, and call the rather confusingly (but logically) named getWeekOfWeekyear(), where a weekyear is the particular week-based definition of a year used by ISO8601.

In general, joda-time is a fantastically useful API, I've stopped using java.util.Calendar and java.util.Date entirely, except for when I need to interface with an API that uses them.

skaffman
  • 398,947
  • 96
  • 818
  • 769
4

Just the Java.util.Calendar can do the trick: You can create a Calendar instance and set the First Day Of the Week and the Minimal Days In First Week

Calendar calendar = Calendar.getInstance();
calendar.setMinimalDaysInFirstWeek(4);
calendar.setFirstDayOfWeek(Calendar.MONDAY);
calendar.setTime(date);

// Now you are ready to take the week of year.
calendar.get(Calendar.WEEK_OF_YEAR);

This is provided by the javaDoc

The week determination is compatible with the ISO 8601 standard when getFirstDayOfWeek() is MONDAY and getMinimalDaysInFirstWeek() is 4, which values are used in locales where the standard is preferred. These values can explicitly be set by calling setFirstDayOfWeek() and setMinimalDaysInFirstWeek().

istovatis
  • 1,408
  • 14
  • 27
  • Good answer for those people who have access to neither the [java.time](http://docs.oracle.com/javase/8/docs/api/java/time/package-summary.html) framework in Java 8+ nor the [Joda-Time](http://www.joda.org/joda-time/) library. But otherwise, the old date-time classes such as java.util.Date/.Calendar that were bundled with the earliest versions of Java should be avoided. The old classes are notoriously troublesome. – Basil Bourque Feb 03 '16 at 21:49
  • How do you get the date in your example? The timezone is not known if there is only year, month and day. – Gustave May 12 '18 at 06:04
2

The Calendar class almost works, but the ISO week-based year does not coincide with what an "Olson's Timezone package" compliant system reports. This example from a Linux box shows how a week-based year value (2009) can differ from the actual year (2010):

$ TZ=UTC /usr/bin/date --date="2010-01-01 12:34:56" "+%a %b %d %T %Z  %%Y=%Y,%%G=%G  %%W=%W,%%V=%V  %s"
Fri Jan 01 12:34:56 UTC  %Y=2010,%G=2009  %W=00,%V=53  1262349296

But Java's Calendar class still reports 2010, although the week of the year is correct. The Joda-Time classes mentioned by skaffman do handle this correctly:

import java.util.Calendar;
import java.util.TimeZone;
import org.joda.time.DateTime;

Calendar cal = Calendar.getInstance(TimeZone.getTimeZone("GMT"));
cal.setTimeInMillis(1262349296 * 1000L);
cal.setMinimalDaysInFirstWeek(4);
cal.setFirstDayOfWeek(Calendar.MONDAY);
System.out.println(cal.get(Calendar.WEEK_OF_YEAR));     // %V
System.out.println(cal.get(Calendar.YEAR));             // %G

DateTime dt = new DateTime(1262349296 * 1000L);
System.out.println(dt.getWeekOfWeekyear());             // %V
System.out.println(dt.getWeekyear());                   // %G

Running that program shows:

53 2010 53 2009

So the ISO 8601 week number is correct from Calendar, but the week-based year is not.

The man page for strftime(3) reports:

  %G     The ISO 8601 week-based year (see NOTES) with century as  a  decimal  number.   The
         4-digit year corresponding to the ISO week number (see %V).  This has the same for‐
         mat and value as %Y, except that if the ISO week number belongs to the previous  or
         next year, that year is used instead. (TZ)
Mike Gleason
  • 3,439
  • 1
  • 14
  • 7
  • 3
    instead of `cal.get(Calendar.YEAR)` use `cal.getWeekYear()` to get week year consistent with ISO 8601 – Archit Jan 27 '15 at 06:11
1

If you want to be on the bleeding edge, you can take the latest drop of the JSR-310 codebase (Date Time API) which is led by Stephen Colebourne (of Joda Fame). Its a fluent interface and is effectively a bottom up re-design of Joda.

JodaStephen
  • 60,927
  • 15
  • 95
  • 117
Andrew Harmel-Law
  • 7,739
  • 12
  • 44
  • 55
-2

this is the reverse: gives you the date of the monday of the week (in perl)

use POSIX qw(mktime);
use Time::localtime;

sub monday_of_week {
    my $year=shift;
    my $week=shift;
    my $p_date=shift;

    my $seconds_1_jan=mktime(0,0,0,1,0,$year-1900,0,0,0);
    my $t1=localtime($seconds_1_jan);
    my $seconds_for_week;
    if (@$t1[6] < 5) {
#first of january is a thursday (or below)
        $seconds_for_week=$seconds_1_jan+3600*24*(7*($week-1)-@$t1[6]+1);
    } else {
        $seconds_for_week=$seconds_1_jan+3600*24*(7*($week-1)-@$t1[6]+8);
    }
    my $wt=localtime($seconds_for_week);
    $$p_date=sprintf("%02d/%02d/%04d",@$wt[3],@$wt[4]+1,@$wt[5]+1900);
}