UNIX date: How to convert week number (date +%W) to a date range (Mon-Sun)?

Monday is the first day of week, ISO week numbers:

function week2date () {
  local year=$1
  local week=$2
  local dayofweek=$3
  date -d "$year-01-01 +$(( $week * 7 + 1 - $(date -d "$year-01-04" +%u ) - 3 )) days -2 days + $dayofweek days" +"%Y-%m-%d"
}

week2date 2017 35 1
week2date 2017 35 7

Output:

2017-08-28
2017-09-03

Everything depends on the definition of week numbers you are used too.

European (ISO 8601)

This ISO 8601 standard is widely used in the world: EU and most of other European countries, most of Asia, and Oceania

The ISO 8601 standard states the following:

  • There are 7 days in a week
  • The first day of the week is a Monday
  • The first week is the first week of the year which contains a Thursday. This means it is the first week with 4 days or more in January.

With this definition, it is possible to have a week number 53. These occur with the first of January is on a Friday (E.g. 2016-01-01, 2010-01-01). Or, if the year before was a leap year, also a Saturday. (E.g. 2005-01-01)

   December 2015               January 2016        
 Mo Tu We Th Fr Sa Su CW    Mo Tu We Th Fr Sa Su CW
     1  2  3  4  5  6 49                 1  2  3 53
  7  8  9 10 11 12 13 50     4  5  6  7  8  9 10 01
 14 15 16 17 18 19 20 51    11 12 13 14 15 16 17 02
 21 22 23 24 25 26 27 52    18 19 20 21 22 23 24 03
 28 29 30 31          53    25 26 27 28 29 30 31 04

function week_range() {
    local _u _F _V
    # dow Jan 01 (Mon 01 ... Sun 07)
    _u="$(date -d "$1-01-01" "+%u")"
    # First Monday
    _F="$(date -d "$1-01-01 + $(( (8 - _u) % 7)) days" "+%F")"
    # Week number of first Monday
    _V="$(date -d "$_F" "+%V")"
    printf -- "%s-%s\n" "$(date -d "$_F + $(( 7*($2 - _V) )) days" "+%F")"       \
                        "$(date -d "$_F + $(( 7*($2 - _V) + 6 )) days" "+%F")"
}

$ week_range 2016 1; done
2016-01-04 - 2016-01-10
$ week_range 2020 1; done
2019-12-30 - 2020-01-05     << week one starts in the previous year
$ week_range 2020 20
2020-05-11 - 2020-05-17

American or Islamic (Not ISO 8601)

Not all countries use the ISO 8601 system. They use a more absolute approach. The American system is used in Canada, United States, New Zealand, India, Japan,... The Islamic system is generally used in the middle east. Both systems are very similar.

American:

  • There are 7 days in a week
  • The first day of the week is a Sunday
  • The first week starts on the 1st of January

With this definition, it is possible to have partial weeks at the beginning and the end of a year. Hence the first and last week of the year could not contain all weekdays.

    December 2015                January 2016       
 Su Mo Tu We Th Fr Sa CW     Su Mo Tu We Th Fr Sa CW
        1  2  3  4  5 49                     1  2 01
  6  7  8  9 10 11 12 50      3  4  5  6  7  8  9 02
 13 14 15 16 17 18 19 51     10 11 12 13 14 15 16 03
 20 21 22 23 24 25 26 52     17 18 19 20 21 22 23 04
 27 28 29 30 31       53     24 25 26 27 28 29 30 05
                             31                   06

function week_range() {
    local _w _F _V _d1 _d2
    # dow Jan 01 (Sun 01 ... Sat 07)
    _w="$(date -d "$1-01-01" "+%w")"
    (( _w = _w + 1 ))
    # First Saturday
    _F="$(date -d "$1-01-01 + $(( (8 - _w) % 7)) days" "+%F")"
    # Week number of first Sunday
    [[ "$_F" == "$1-01-01" ]] && _V=1 || _V=2
    # Start and end
    _d1="$(date -d "$_F + $(( 7*($2 - _V) )) days" "+%F")"
    _d2="$(date -d "$_F + $(( 7*($2 - _V) + 6 )) days" "+%F")"
    [[ "$_d1" < "$1-01-01" ]] && _d1="$1-01-01"
    [[ "$_d2" > "$1-12-31" ]] && _d2="$1-12-31"
    [[ "$_d1" > "$1-12-31" ]] && echo "invalid week number" > /dev/stderr && return
    printf -- "%s - %s\n"               \
        "$(date -d "$_d1" "+%m/%d/%Y")" \
        "$(date -d "$_d2" "+%m/%d/%Y")"
}
$ week_range 2015 53
12/27/2015 - 12/31/2015
$ week_range 2016 1
01/01/2016 - 01/02/2016
$ week_range 2020 20
05/10/2020 - 05/16/2020

Islamic:

  • There are 7 days in a week
  • The first day of the week is a Saturday
  • The first week starts on the 1st of January

With this definition, it is possible to have partial weeks at the beginning and the end of a year. Hence the first and last week of the year could not contain all weekdays.

   December 2015                 January 2016       
 Sa Su Mo Tu We Th Fr CW     Sa Su Mo Tu We Th Fr CW
           1  2  3  4 49                        1 01
  5  6  7  8  9 10 11 50      2  3  4  5  6  7  8 02
 12 13 14 15 16 17 18 51      9 10 11 12 13 14 15 03
 19 20 21 22 23 24 25 52     16 17 18 19 20 21 22 04
 26 27 28 29 30 31    53     23 24 25 26 27 28 29 05
                             30 31                06

function week_range() {
    local _w _F _V _d1 _d2
    # dow Jan 01 (Sat 01 ... Fri 07)
    _w="$(date -d "$1-01-01" "+%w")"
    (( _w = (_w + 8) % 7 + 1 ))
    # First Saturday
    _F="$(date -d "$1-01-01 + $(( (8 - _w) % 7)) days" "+%F")"
    # Week number of first Saturday
    [[ "$_F" == "$1-01-01" ]] && _V=1 || _V=2
    # Start and end
    _d1="$(date -d "$_F + $(( 7*($2 - _V) )) days" "+%F")"
    _d2="$(date -d "$_F + $(( 7*($2 - _V) + 6 )) days" "+%F")"
    [[ "$_d1" < "$1-01-01" ]] && _d1="$1-01-01"
    [[ "$_d2" > "$1-12-31" ]] && _d2="$1-12-31"
    [[ "$_d1" > "$1-12-31" ]] && echo "invalid week number" > /dev/stderr && return
    printf -- "%s - %s\n" "${_d1//-//}" "${_d2//-//}"
}

$ week_range 2015 53
2015/12/26 - 2015/12/31
$ week_range 2016 1
2016/01/01 - 2016/01/01
$ week_range 2020 20
2020/05/09 - 2020/05/15

Note: There are other methods of defining a week number. Nonetheless, the approach stays the same.


With GNU date:

$ cat weekof.sh
function weekof()
{
    local week=$1 year=$2
    local week_num_of_Jan_1 week_day_of_Jan_1
    local first_Mon
    local date_fmt="+%a %b %d %Y"
    local mon sun

    week_num_of_Jan_1=$(date -d $year-01-01 +%W)
    week_day_of_Jan_1=$(date -d $year-01-01 +%u)

    if ((week_num_of_Jan_1)); then
        first_Mon=$year-01-01
    else
        first_Mon=$year-01-$((01 + (7 - week_day_of_Jan_1 + 1) ))
    fi

    mon=$(date -d "$first_Mon +$((week - 1)) week" "$date_fmt")
    sun=$(date -d "$first_Mon +$((week - 1)) week + 6 day" "$date_fmt")
    echo "\"$mon\" - \"$sun\""
}

weekof $1 $2
$ bash weekof.sh 12 2012
"Mon Mar 19 2012" - "Sun Mar 25 2012"
$ bash weekof.sh 1 2018
"Mon Jan 01 2018" - "Sun Jan 07 2018"
$

NOTE:

As the OP mentions, the week number is got by date +%W. According to GNU date's manual:

%W: week number of year, with Monday as first day of week (00..53)

So:

  1. Each week starts from Mon.
  2. If Jan 1 is Mon, then the first week will be week #1.
  3. If Jan 1 is not Mon, then the first few days will be week #0 and the week #1 starts from the first Mon.