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

6

14

I have list of week numbers extracted from huge log file, they were extracted using syntax:

$ date --date="Wed Mar 20 10:19:56 2012" +%W;
12

I want to create a simple bash function which can convert these week numbers to a date range. I suppose function should accept 2 arguments: $number and $year, example:

$ week() { ......... }
$ number=12; year=2012
$ week $number $year
"Mon Mar 19 2012" - "Sun Mar 25 2012"
Acariasis answered 25/3, 2013 at 2:8 Comment(0)
F
16

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.
Fakieh answered 25/3, 2013 at 2:56 Comment(4)
I tried your function. However, it is returning the date range as the 1 week ahead of current week date range. Try with week number 20 and the year 2020. It is returning "Mon May 18 2020" - "Sun May 24 2020".Marten
This answer is incorrect: weekof 1 2019 gives the wrong week. This algorithm does not take into account that the first week of the year in ISO format is the first week that has a Thursday. This implies that the monday of the first week of a year can be in December of the previous year.Hewes
the OP's week num is from date +%W. you can try: date --date="2020-05-17 01:00:00" +%W outputs 19 and date --date="2020-05-18 01:00:00" +%W outputs 20.Fakieh
GNU date's manual: %W - week number of year, with Monday as first day of week (00..53). that's to say, every week would start with Monday just as my func outputs.Fakieh
Z
9

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
Zeena answered 1/9, 2017 at 14:25 Comment(0)
H
4

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.

Hewes answered 11/5, 2020 at 15:36 Comment(0)
S
0

If anybody needs it: I found an even shorter way (not sure if easier):

function weekof() {
        local year=$2
        local week=`echo $1 | sed 's/^0*//'` # Fixes random bug
        local dateFormat="+%a %b %d %Y"
        # Offset is the day of week, so we can calculate back to monday
        local offset="`date -d "$year/01/01 +$((week - 1)) week" "+%u"`"
        echo -n "`date -d "$year/01/01 +$((week - 1)) week +$((1 - $offset)) day" "$dateFormat"`" # Monday
        echo -n " - "
        echo "`date -d "$year/01/01 +$((week - 1)) week +$((7 - $offset)) day" "$dateFormat"`" # Sunday    }

I take the first day of the year and go n weeks forward to be somewhere in the right week. Then I take my weekday and go back/forward to reach monday and sunday.

Strathspey answered 4/3, 2015 at 15:45 Comment(1)
Your weekof 12 2012 outputs Mon Mar 12 2012 - Sun Mar 18 2012. It should be Mon Mar 19 2012 - Sun Mar 25 2012.Fakieh
L
0

If the start of a week is Sunday, you can use this version of weekof:

function weekof()
{
    local week=$1 year=$2
    local week_num_of_Jan_1 week_day_of_Jan_1
    local first_Sun
    local date_fmt="+%Y-%m-%d"
    local sun sat

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

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

    sun=$(date -d "$first_Sun +$((week - 1)) week" "$date_fmt")
    sat=$(date -d "$first_Sun +$((week - 1)) week + 6 day" "$date_fmt")
    echo "$sun $sat"
}
Lizzettelizzie answered 27/1, 2017 at 19:12 Comment(0)
C
0

with help of python, you can create simple Bash conversion functions:

> iso_year_week_day() {  python3 -c "from datetime import datetime as d; print(d.strptime('$1 $2 $3', '%G %V %u').strftime('%Y-%m-%d'));"; }
> calendar_year_week_day() {  python3 -c "from datetime import datetime as d; print(d.strptime('$1 $2 $3', '%Y %W %u').strftime('%Y-%m-%d'));"; }

where:

  • strptime(str, '%G %V %u') converts str to date,
  • %G-ISO Year, %V-ISO Week, %u-day of week
  • or %Y- Year, %W- Week, %u-day of week
  • strftime('%Y-%m-%d') - prints date in specified format

and then its explicitly simple to use it then:

> year=2023
> week=35
> echo "ISO: `iso_year_week_day $year $week 1` to `iso_year_week_day $year $week 7`"
> ISO: 2023-08-28 to 2023-09-03
> echo "Calendar: $(iso_year_week_day $year $week 1) to $(iso_year_week_day $year $week 7)"
> Calendar: 2023-08-28 to 2023-09-03

P.S. of course output format can be easily adjusted by altering strftime("%Y-%m-%d") part

Cloison answered 31/8, 2023 at 13:30 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.