Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to cut the first Sunday to Saturday of each month in a year?

Tags:

bash

awk

perl

We have green zone logic where the job has to run only between first Sunday to Saturday, i.e. 7 days starting from first Sunday of every month. I'm using the below awk command to get that, but somewhere it is breaking. I'm just trying for first 3 months i.e Jan to March

seq 75  | awk ' BEGIN {ti=" 0 0 0"} 
function dtf(fmt,dy) { return strftime(fmt,mktime("2020 1 " dy ti)) } 
{ day=dtf("%A %F",$0);mm=dtf("%m",$0);if(day~/Sunday/ || a[mm]) a[mm]++ ; if(a[mm]<8) print day }  '

My output is below, which is incorrect:

Wednesday 2020-01-01
Thursday 2020-01-02
Friday 2020-01-03
Saturday 2020-01-04
Sunday 2020-01-05
Monday 2020-01-06
Tuesday 2020-01-07
Wednesday 2020-01-08
Thursday 2020-01-09
Friday 2020-01-10
Saturday 2020-01-11
Saturday 2020-02-01
Sunday 2020-02-02
Monday 2020-02-03
Tuesday 2020-02-04
Wednesday 2020-02-05
Thursday 2020-02-06
Friday 2020-02-07
Saturday 2020-02-08
Sunday 2020-03-01
Monday 2020-03-02
Tuesday 2020-03-03
Wednesday 2020-03-04
Thursday 2020-03-05
Friday 2020-03-06
Saturday 2020-03-07

Expected output:

Sunday 2020-01-05
Monday 2020-01-06
Tuesday 2020-01-07
Wednesday 2020-01-08
Thursday 2020-01-09
Friday 2020-01-10
Saturday 2020-01-11
Sunday 2020-02-02
Monday 2020-02-03
Tuesday 2020-02-04
Wednesday 2020-02-05
Thursday 2020-02-06
Friday 2020-02-07
Saturday 2020-02-08
Sunday 2020-03-01
Monday 2020-03-02
Tuesday 2020-03-03
Wednesday 2020-03-04
Thursday 2020-03-05
Friday 2020-03-06
Saturday 2020-03-07

How can I adjust the awk command to get the expected output? Any other solutions using other bash tools are also welcome.

like image 371
stack0114106 Avatar asked May 05 '20 07:05

stack0114106


3 Answers

I suggest the following alternative to awk:

#! /usr/bin/env bash
for month in {01..03}; do
  for day in {01..13}; do
    date -d "2020-$month-$day" '+%A %F'
  done |
  grep -A6 -m1 -F Sunday
done

The script is not very efficient, but does the job. For each month, we simply print the dates of the 13 first days in that month. We know that the green zone has to be in that area, therefore we do not need the remaining days of the month.
The date format is Weekday YYYY-MM-DD. We use grep to find and print the first Sunday, print the 6 days behind that Sunday (-A6) and exit because we limited the search to one match (-m1).
The procedure described above is done for each of the months 1 to 3.

like image 95
Socowi Avatar answered Nov 01 '22 12:11

Socowi


Here's a simple way to get GNU awk to create a list of dates and day names for any given year:

$ cat tst.awk
BEGIN {
    year = (year == "" ? 2020 : year)
    beg = mktime(year " 1 1 12 0 0")
    for (i=0; i<=400; i++) {
        dateday = strftime("%F %A", beg+24*60*60*i)
        split(dateday,d,/[ -]/)
        if ( d[1] != year ) {
            break
        }
        print d[1], d[2], d[3], d[4]
    }
}

.

$ awk -f tst.awk | head -20
2020 01 01 Wednesday
2020 01 02 Thursday
2020 01 03 Friday
2020 01 04 Saturday
2020 01 05 Sunday
2020 01 06 Monday
2020 01 07 Tuesday
2020 01 08 Wednesday
2020 01 09 Thursday
2020 01 10 Friday
2020 01 11 Saturday
2020 01 12 Sunday
2020 01 13 Monday
2020 01 14 Tuesday
2020 01 15 Wednesday
2020 01 16 Thursday
2020 01 17 Friday
2020 01 18 Saturday
2020 01 19 Sunday
2020 01 20 Monday

I'm starting at noon and looping from 0 to 400 days and breaking when the year changes just so I don't have to try to accommodate DST or leap years or leap seconds in the determination of days in the year in a more accurate calculation.

Just add some code to test for the current month being different from the previous and the current day name being a Sunday and print 7 days starting there, e.g.:

$ cat tst.awk
BEGIN {
    year = (year == "" ? 2020 : year)
    beg = mktime(year " 1 1 12 0 0")
    for (i=0; i<=400; i++) {
        dateday = strftime("%F %A", beg+24*60*60*i)
        split(dateday,d,/[ -]/)
        if ( d[1] != year ) {
            break
        }
        dayName[d[2]+0][d[3]+0] = d[4]
    }
    for (monthNr=1; monthNr<=3; monthNr++) {
        for (dayNr=1; dayNr in dayName[monthNr]; dayNr++) {
            if (dayName[monthNr][dayNr] == "Sunday") {
                for (i=0; i<7; i++) {
                    printf "%s %04d-%02d-%02d\n", dayName[monthNr][dayNr+i], year, monthNr, dayNr+i
                }
                break
            }
        }
    }
}

.

$ awk -f tst.awk
Sunday 2020-01-05
Monday 2020-01-06
Tuesday 2020-01-07
Wednesday 2020-01-08
Thursday 2020-01-09
Friday 2020-01-10
Saturday 2020-01-11
Sunday 2020-02-02
Monday 2020-02-03
Tuesday 2020-02-04
Wednesday 2020-02-05
Thursday 2020-02-06
Friday 2020-02-07
Saturday 2020-02-08
Sunday 2020-03-01
Monday 2020-03-02
Tuesday 2020-03-03
Wednesday 2020-03-04
Thursday 2020-03-05
Friday 2020-03-06
Saturday 2020-03-07

There are slightly more efficient ways to do it but the above is clear and simple and will run in the blink of an eye.

like image 4
Ed Morton Avatar answered Nov 01 '22 11:11

Ed Morton


A (rather wordy - I don't have time to make it shorter:-) ) Perl solution:

#!/usr/bin/perl

use strict;
use warnings;
use feature 'say';

use Time::Piece;
use Time::Seconds;

my $year = shift || localtime->year;

first_week($year, $_) for 1 ..12;


sub first_week {
  my ($yr, $mn) = @_;

  $mn = sprintf '%02d', $mn;

  # Use midday to avoid DST issues
  my $start = Time::Piece->strptime(
    "$year-$mn-01 12:00:00",
    '%Y-%m-%d %H:%M:%S'
  );

  $start += ONE_DAY while $start->day ne 'Sun';

  for (1 .. 7) {
    say $start->strftime('%A %Y-%m-%d');
    $start += ONE_DAY;
  }
}
like image 3
Dave Cross Avatar answered Nov 01 '22 10:11

Dave Cross