3

I occasionally use the date command to check the current or a future date's week number based on the ISO-8601 week-date calendar. To do this I simply use: date +%V to check the current ISO week number and date -d "2021-12-25" +%V to check the ISO week number of another date (Christmas day in this case).

However, it seems like it is not possible to do this in reverse. That is, get the date in "month-date" format given a date in "week number" format. For example, if I run date -d "2021-W50-3", it generates an error saying the format is not supported. Why is it not and is there any way to input or store dates using this format?

9
  • 1
    May we assume that you are using GNU date? Did you try date -d '2021-01-01 +51 weeks'?
    – Kusalananda
    Jul 31, 2021 at 15:45
  • 4
    @Kusalananda, I'm not sure that a '+51+ will always do. January first does not even have to belong to the first week of a given year. Jul 31, 2021 at 15:57
  • @EduardoTrápani Right you are.
    – Kusalananda
    Jul 31, 2021 at 16:25
  • It's unclear if you're asking "why does date not understand the special format date string that I've invented?" or "how do I calculate the dates of a particular week?"
    – Kusalananda
    Jul 31, 2021 at 16:26
  • @Kusalananda Sorry, I am not sure if it's the GNU date. I'm using the built-in date command in my distro (Fedora 33). I'm just curious as to why date doesn't support such standardized format.
    – Kosho E
    Jul 31, 2021 at 16:59

2 Answers 2

5

GNU date, with that non-standard -d option, only supports a limited set of date-time formats documented at info date 'Date input formats' (and online here).

For a date implementation that supports more date formats including the one you're referring to, you can use the date implementation from ast-open (which can also take an input date with -d like GNU date).

That can also be the date builtin of the ksh93 shell if built as part of ast-open (rarely the case though), or instead of date, you can use its printf builtin (which is always included) and its %T specifier (which bash copied though without the time parsing part):

$ builtin date
$ date -d '2021-W50-3' +%F
2021-12-15
$ printf '%(%F)T\n' 2021-W50-3
2021-12-15

From the bash shell (or any other shell), you can always do it as:

$ ksh93 -c '"${@:0}"' printf '%(%F)T\n' 2021-W50-3
2021-12-15

though note that depending on the system, ksh93 may be known as ksh, ksh2020 (or even sh like on Solaris 11+; though it's also known as ksh there), or not at all, as ksh93, like bash or zsh is not installed by default on all systems.

Note that while standard strftime() can produce dates in that standard format with the %G-%V-%u specification, the corresponding standard function to parse timestamps (strptime()) doesn't support %G nor %V (nor does standard UNIX getdate()).

GNU strptime() is currently documented to understand them (as an extension over the standard) but ignore them (see info libc strptime). FreeBSD strptime() is documented to support the same directives as strftime() but in my test on FreeBSD 12.2 (using zsh's strftime -r builtin), it doesn't seem to work there.

POSIX, GNU and freebsd strptime() do support %W though.

  • zsh:

    $ strftime -rs t %Y-W%W-%u 2021-W50-3 && strftime %F $t
    2021-12-15
    
  • busybox:

    $ busybox date -D %Y-W%W-%u -d 2021-W50-3 +%F
    2021-12-15
    

%Y-%W is not the same as %G-%V though, it just happens to coincide in this case. %W on the first of January is 00 unless that's a Monday in which case it's 01. Whilst %V (the ISO week number) is either the first week of the year (01) or the last week of the previous year (52 or 53) depending on whether that week has more days in the previous or current year.

4
  • So it is a limit imposed by strptime() in C?
    – Kosho E
    Aug 1, 2021 at 20:07
  • @KoshoE, no, the POSIX or C standard don't prohibit strptime() implementations from supporting parsing those ISO week numbers. Standard date has no date parsing other than (optionally) with one specific format and only to set the date, and the GNU implementation doesn't use strptime(). It's more likely that no body bothered because nobody asked for it. Aug 1, 2021 at 20:12
  • Sorry for asking further clarification. So POSIX's strptime() and GNU's date implement date parsing differently, if I understand it correctly? With that, both of them doesn't have a standard support for parsing dates in the ISO-8601 format?
    – Kosho E
    Aug 1, 2021 at 20:37
  • 3
    I gave a link to the POSIX date specification. POSIX date can't be used for parsing dates, that's not what POSIX date does. There's no utility in the POSIX toolchest to parse dates. -d is an extension of the GNU implementation of date. -d is used for something completely different on BSDs for instance. busybox date and ast-open date did add a -d with a similar semantic as GNU date but with a different implementation and different supported formats. Aug 1, 2021 at 20:40
4

It is possible to get the precise ISO week date (including the flip into previous or next year) by using two executions of date: one to calibrate a known day (I chose 01-June), and one to adjust by the number of days for the ISO week/day difference. This is unaffected by leap years -- that's the whole point of using ISO weeks instead of months.

This is a function that converts 2021-W50-3 format to 2021-12-15, with tests that show the year roll-over.

#! /bin/bash

isoDate () {

    declare -a dR   #.. Required date
    declare -a dC   #.. Calibration date.
    printf 1>&2 '\nAsked %s\n' "${1}"   #debug
    IFS='-' read -a dR <<<"${1}"
    IFS='-' read -a dC <<<"$( date -d "${dR[0]}-06-01" '+%G-W%V-%u' )"
    dR[1]=${dR[1]#W}; dR[1]=${dR[1]#0}
    dC[1]=${dC[1]#W}; dC[1]=${dC[1]#0}
    declare Adj=$(( 7 * (${dR[1]} - ${dC[1]}) + ${dR[2]} - ${dC[2]} ))
    date -d "${dR[0]}-06-01 + ${Adj} days" '+%Y-%m-%d'
}

    Ymd=$( isoDate '2021-W50-3' )
    printf 'Found %s\n' "${Ymd}"
    date -d "${Ymd}" '+Check %G-W%V-%u'

    Ymd=$( isoDate '2020-W01-1' )
    printf 'Found %s\n' "${Ymd}"
    date -d "${Ymd}" '+Check %G-W%V-%u'

    Ymd=$( isoDate '2011-W52-7' )
    printf 'Found %s\n' "${Ymd}"
    date -d "${Ymd}" '+Check %G-W%V-%u'

And the results:

$ ./isoDate

Asked 2021-W50-3
Found 2021-12-15
Check 2021-W50-3

Asked 2020-W01-1
Found 2019-12-30
Check 2020-W01-1

Asked 2011-W52-7
Found 2012-01-01
Check 2011-W52-7
$ 
4
  • 1
    Date-time computation is one of the trickier programming domains, has this code been published (and thus reviewed) elsewhere?
    – Jasen
    Aug 1, 2021 at 10:40
  • @Jasen Fresh from my own imagination, guided by bitter experience. The declare stuff was to ensure all variables are local and don't mess with the rest of the user code. I was also mildly surprised that date -d is good with 2020-06-01 + -154 days. Tested on GNU bash, version 4.4.20(1), Linux Mint 19.3, but I cannot vouch for portability. I should possibly validate the original 2021-W50-3 format, but that's really the caller's problem. Aug 1, 2021 at 16:51
  • This is a very interesting approach. However, this looks somewhat hacky (if you know what I mean).
    – Kosho E
    Aug 1, 2021 at 20:13
  • @KoshoE I would be glad to see a better (tested) solution. The intent of providing a shell function is that you can save it in a script or .bashrc file, so you don't have to look at it again. My first idea was to call date -d ymd '+ISO-out' for every day in the year and grep the required ISO, so this is 99% less hacky than that concept. Happily, I saw the light after about 90 seconds: exploit the arithmetic regularity of 7-day weeks. Aug 3, 2021 at 17:02

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .