0

I'm trying to calculate the previous and next months (and year but that was trivial) for a given date. I'm using this for the next month:

$ awk 'BEGIN{m=12; print m%12+1}'
1

which is pretty tight IMHO and for the previous:

$ awk 'BEGIN{m=1; print (m+10)%12+1}'
12

̲w̲h̲i̲c̲h̲ ̲d̲o̲e̲s̲ ̲t̲h̲e̲ ̲j̲o̲b̲ ̲b̲u̲t̲ ̲w̲h̲i̲c̲h̲ ̲I̲'̲m̲ ̲n̲o̲t̲ ̲h̲a̲p̲p̲y̲ ̲w̲i̲t̲h̲, I feel it could be in tighter form but just can't figure it out ATM.

So, in which ways would you calculate the previous and next months (and years)? Here's a test template:

$ awk '{
    y=substr($1,1,4)             # extract yyyy from yyyymmdd
    m=substr($1,5,2)+0           # extract mm from yyyymmdd
    print "this:", m, y, 
          "next:", m%12+1,
                   y+(m==12),
          "prev;", (m+10)%12+1,  # optimize me
                   y-(m==1)
}' <(echo -e 20190116 \\n 20181231)
this: 1 2019 next: 2 2019 prev; 12 2018
this: 12 2018 next: 1 2019 prev; 11 2018
James Brown
  • 36,089
  • 7
  • 43
  • 59
  • 2
    According to https://stackoverflow.com/a/39740009/2229272 `((x-1) + k) % k`. – jas Jan 16 '19 at 14:12
  • Hmm, if `x==1` that yields 0: `echo 20190116 | awk '{y=substr($1,1,4);m=substr($1,5,2)+0;print ((m-1)+12)%12}'` -> 0. Did I miss something? – James Brown Jan 16 '19 at 14:18
  • 1
    No, I missed something. That solution counts 11 down to 0 and repeats, rather than 12 down to 1. – jas Jan 16 '19 at 14:27
  • Oh, it's [0,11] in which case it needs some more calculation added to it to convert to [1,12]. Lol, my brain is toast for the day. :D But nice find, I only found php and java solutions and they were in their own domain. – James Brown Jan 16 '19 at 14:27
  • 1
    I think it's going to be hard to beat `(m+10)%12+1` for tightness, tbh. You're always going to have to add 1 after the mod, and if the thing to mod is not `m` you'll have to manipulate it somehow, and what could be a smaller manipulation than adding a constant? – jas Jan 16 '19 at 14:32

2 Answers2

1

Using Ternary operator and covering the edge cases

$ awk '{  y=substr($1,1,4); m=substr($1,5,2) ; mn=m==12?1:m+1;yn=mn==1?y+1:y; mp=m+0==1?12:m-1;yp=mp==12?y-1:y; print "next ",mn,yn,m,y; print "prev ",mp,yp,m,y } ' <(echo -e 20190116 \\n 20181231)
next  2 2019 01 2019
prev  12 2018 01 2019
next  1 2019 12 2018
prev  11 2018 12 2018
$
stack0114106
  • 8,534
  • 3
  • 13
  • 38
1

I'd encapsulate it in functions:

awk '
    function next_month(year, month) {
        month++;
        if (month == 13) {
            year++;
            month = 1;
        }
        return sprintf "%d-%02d", year, month;
    }

    function previous_month(year, month) {
        month--;
        if (month == 0) {
            year--;
            month = 12;
        }
        return sprintf "%d-%02d", year, month;
    }

    BEGIN {
        year = 2019; month = 1;
        printf "%d-%d\t%s\t%s\n",
            year, month,
            next_month(year, month),
            previous_month(year, month);

        year = 2018; month = 12;
        printf "%d-%d\t%s\t%s\n",
            year, month,
            next_month(year, month),
            previous_month(year, month);
    }
'
2019-1  2019-02 2018-12
2018-12 2019-01 2018-11

I'm assuming you are providing valid input to the functions. Validation left as an exercise.

Remove the function parameters to mutate the variables in the global scope.

glenn jackman
  • 238,783
  • 38
  • 220
  • 352