UNIX日期:如何将周数(date +%W)转换为日期范围(星期一至星期日)?

13

我有一个从巨大的日志文件中提取出来的星期数列表,它们是使用以下语法提取的:

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

我想创建一个简单的bash函数,可以将这些周数转换为日期范围。 我认为函数应该接受2个参数:$number和$year,例如:

$ week() { ......... }
$ number=12; year=2012
$ week $number $year
"Mon Mar 19 2012" - "Sun Mar 25 2012"
6个回答

16

使用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"
$

注意:

正如原帖中提到的,获取周数的命令是date +%W。根据GNU date的手册

%W:一年中的周数,以星期一作为一周的第一天(00..53)

所以:

  1. 每周从星期一开始计算。
  2. 如果一月一日是星期一,则第一周第1周
  3. 如果一月一日不是星期一,则前几天属于第0周第1周从第一个星期一开始。

我尝试了你的函数。但是它返回的日期范围是当前周日期范围的1周后。请使用第20周和2020年进行尝试。它将返回“2020年5月18日星期一”至“2020年5月24日星期日”。 - Nishant Kansal
这个答案是不正确的:weekof 1 2019 得到了错误的周数。这个算法没有考虑到 ISO 格式中的第一周是指包含星期四的第一周。这意味着一年的第一周的星期一可能在前一年的十二月份。 - kvantour
2
OP的周数来自于date +%W。你可以尝试:date --date="2020-05-17 01:00:00" +%W输出19,而date --date="2020-05-18 01:00:00" +%W则输出20 - pynexj
2
GNU date 的手册:%W - 一年中的周数,以星期一作为一周的第一天(00..53)。也就是说,每周都会从星期一开始,就像我的函数输出一样。 - pynexj

9

周一是一周的第一天,ISO周数

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

输出:

2017-08-28
2017-09-03

2

一切都取决于你所使用的周数定义。

欧洲(ISO 8601)

这个ISO 8601标准在世界范围内广泛使用:欧盟和大多数其他欧洲国家、大多数亚洲和大洋洲国家。

ISO 8601标准规定如下:

  • 一周有7天
  • 一周的第一天是星期一
  • 第一周是包含星期四的年份中的第一周。这意味着它是1月份拥有4天或更多天的第一周。

按照这个定义,可以有第53周。这种情况发生在1月1日是星期五时(例如2016-01-01、2010-01-01)。或者,如果前一年是闰年,也会出现第53周。(例如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

美国或伊斯兰教(非ISO 8601)

并非所有国家都使用ISO 8601系统。他们采用了一种更为绝对的方法。 美国系统在加拿大、美国、新西兰、印度、日本等地使用, 伊斯兰教系统通常在中东使用。 两个系统非常相似。

美国:

  • 一周有7天
  • 一周的第一天是星期日
  • 第一周从1月1日开始

根据这个定义,一年的开始和结束可能会出现部分周。因此,一年的第一周和最后一周可能不包含所有工作日。

    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

伊斯兰教:

  • 一周有7天
  • 一周的第一天是星期六
  • 第一周从1月1日开始

根据此定义,年初和年末可能存在不完整的一周。因此,一年中的第一周和最后一周可能不包含所有工作日。

   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

注意:还有其他定义周数的方法,不过方法原理是一样的。


0
如果有人需要的话:我找到了一种更短的方法(不确定是否更容易):
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    }

我取得一年中的第一天,向前推n周,以便在正确的周数中。然后我根据星期几往前或往后推算,以达到周一和周日。


1
你的 weekof 12 2012 输出为 Mon Mar 12 2012 - Sun Mar 18 2012。应该是 Mon Mar 19 2012 - Sun Mar 25 2012 - pynexj

0

如果一周的开始是星期日,您可以使用这个版本的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"
}

0

借助Python,您可以创建简单的Bash转换函数:

> 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')将字符串 str 转换为日期
  • %G- ISO 年份,%V- ISO 周数,%u- 星期几
  • 或者 %Y- 年份,%W- 周数,%u- 星期几
  • strftime('%Y-%m-%d') - 以指定格式打印日期

然后就可以简单地使用它了:

> 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

附言:当然,输出格式可以通过修改 strftime("%Y-%m-%d") 部分来轻松调整。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接