Search code examples
awk

Sum date using awk


I'm trying to add up days in a date using awk this way:

awk 'BEGIN { print date -d $(date -d "20181222" "+%Y%m%d") + 30}'

But my return is

030

I was expecting

20190121

The date I have is a string.


Solution

  • This is interesting to describe why you get the result you get.

    In awk, an unquoted word (that is not a function or command) is a variable:

    awk 'BEGIN { print date -d $(date -d "20181222" "+%Y%m%d") + 30}'
    

    Here, you have the "date" variable twice, the "d" variable twice, 2 quoted strings and a numeric literal.

    Since "date" and "d" as variables have not been initialized, they are treated as either the empty string or as the number zero, depending on the context. Because you use - between "date" and "d", you are putting them into a numeric context.

    Also note that:

    • awk string contatenation is to simply put 2 strings side-by-side: there is no explicit operator
    • the $ symbol can be described as a "field-value dereferencing" operator: the word on the right-hand side of $ is the field number to use.
    • in the BEGIN block, awk has not yet processed any records, so any $x will become the empty string

    awk essentially sees this:

    awk 'BEGIN { print date -d $(date -d "20181222" "+%Y%m%d") + 30}'
    awk 'BEGIN { print (0 - 0) $((0 - 0) "20181222" "+%Y%m%d") + 30}'  # variable values
    awk 'BEGIN { print 0 $(0 "20181222" "+%Y%m%d") + 30}'              # arithmetic
    awk 'BEGIN { print 0 $("020181222+%Y%m%d") + 30}'                  # string concat
    awk 'BEGIN { print 0 $(20181222) + 30}'                            # field is a number
    awk 'BEGIN { print 0 "" + 30}'                                     # field value
    awk 'BEGIN { print 0 (0 + 30)}'                                    # numeric context
    awk 'BEGIN { print 0 30}'                                          # arithmetic
    awk 'BEGIN { print "030"}'                                         # string concat
    

    You can do date arithmetic natively in GNU awk: see https://www.gnu.org/software/gawk/manual/html_node/Time-Functions.html

    gawk -v date=20181222 -v days=30 'BEGIN {
      datespec = substr(date, 1, 4) " " substr(date, 5, 2) " " (substr(date, 7, 2) + days) " 0 0 0"
      time = mktime(datespec)
      print strftime("%Y%m%d", time)
    }'
    
    20190121
    

    Fortunately, GNU awk's datetime library recognises "December 52nd, 2018" as "January 21st, 2019"


    If you don't have GNU awk and can't install it, perl should suffice:

    perl -sE '
        use Time::Piece;
        use Time::Seconds;
        $t = Time::Piece->strptime($date, "%Y%m%d");
        $t += $days * ONE_DAY;
        say $t->ymd("");          # or, say $t->strftime("%Y%m%d")
    ' -- -date=20181222 -days=30