Как сократить первое воскресенье и субботу каждого месяца в году? - PullRequest
8 голосов
/ 05 мая 2020

У нас есть зеленая зона logi c, где задание должно выполняться только с первого воскресенья по субботу, то есть 7 дней, начиная с первого воскресенья каждого месяца. Я использую приведенную ниже команду awk, чтобы получить это, но где-то она ломается. Я просто пытаюсь в течение первых 3 месяцев, т.е. с января по март

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 }  '

Мой результат ниже, что неверно:

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

Ожидаемый результат:

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

Как мне настроить команду awk для получения ожидаемого результата? Также приветствуются любые другие решения, использующие другие инструменты bash.

Ответы [ 7 ]

6 голосов
/ 05 мая 2020

Я предлагаю следующую альтернативу 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

Скрипт не очень эффективен, но выполняет свою работу. Для каждого месяца мы просто печатаем даты 13 первых дней этого месяца. Мы знаем, что зеленая зона должна находиться в этой области, поэтому нам не нужны оставшиеся дни месяца.
Формат даты - Weekday YYYY-MM-DD. Мы используем grep, чтобы найти и распечатать первое воскресенье, распечатать 6 дней после этого воскресенья (-A6) и выйти, потому что мы ограничили поиск одним совпадением (-m1).
Процедура, описанная выше, выполнена для каждого месяца с 1 по 3.

4 голосов
/ 05 мая 2020

Вот простой способ заставить GNU awk создать список дат и названий дней для любого заданного года:

$ 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

Я начинаю в полдень и цикл от 0 до 400 дней и прерывание при изменении года, чтобы мне не приходилось пытаться учесть летнее время или високосные годы или секунды прыжка при определении дней в году для более точного расчета.

Просто добавьте код, чтобы проверить, отличается ли текущий месяц от предыдущего, а название текущего дня - воскресенье, и выведите 7 дней, начиная с этого места, например:

$ 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

Есть несколько более эффективных способов сделать это, но все вышеперечисленное ясно и просто и будет выполнено в мгновение ока.

4 голосов
/ 05 мая 2020

С Perl, используя DateTime

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

use DateTime;

my $dt = DateTime->new(year => 2020, month => 1, day => 1); 

my $first_sunday = 7 - $dt->day_of_week + 1;  # day of month for first Sun

while (1) { 
    my $day = $dt->day; 
    if ($day >= $first_sunday and $day < $first_sunday + 7) { 
        say $dt->ymd, " (", $dt->day_abbr, ")";
    }
} 
continue { 
    $dt->add(days => 1); 
    if ($dt->day == 1) {  # new month
        last if $dt->month > 3;
        $first_sunday = 7 - $dt->day_of_week + 1;
    }   
}

Это сохраняет состояние (первое за месяц в определяет, какой день первое воскресенье), что довольно подходит, если программа предназначена для генерации и go по всем датам из интересующего диапазона.

С другой стороны, программе может потребоваться проверка для данного дня; возможно, он работает ежедневно и на этот день нужно проверять. Тогда проще увидеть, находится ли день между первым и вторым воскресеньями месяца

my $dt = DateTime->today;

while ( $dt->add(days => 1)->month <= 3) {

    if ($dt->day_of_week == 7) {           # it's a Sunday
        if ($dt->weekday_of_month == 1) {  # first Sunday in the month
            say $dt->ymd, " (", $dt->day_abbr, ")";
        }
    } 
    else {
        my $sdt = $dt->clone;                # preserve $dt
        $sdt->subtract( $dt->day_of_week );  # drop to previous Sunday
        if ($sdt->weekday_of_month == 1) {   # was first Sunday in the month
            say $dt->ymd, " (", $dt->day_abbr, ")";
        }
    }
}

while l oop вокруг кода используется для облегчения проверки.

Для других дней, кроме воскресенья, мы переходим к прошлому воскресенью, чтобы проверить, было ли это первое воскресенье месяца. Если так, то наш день находится в нужном интервале. Если день равен воскресенью, нам нужно только проверить, является ли он первым в месяце.

Код можно сделать немного более эффективным и кратким, если это имеет значение

if ( (my $dow = $dt->day_of_week) == 7) { 
    if ($dt->weekday_of_month == 1) {
        say $dt->ymd, " (", $dt->day_abbr, ")";
    }
}   
elsif ( $dt->clone->subtract(days => $dow)->weekday_of_month == 1 ) { 
    say $dt->ymd, " (", $dt->day_abbr, ")";
}

... за счет читаемости.

3 голосов
/ 05 мая 2020

A (довольно многословно - у меня нет времени делать короче :-)) Perl решение:

#!/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;
  }
}
3 голосов
/ 05 мая 2020

Попробуйте это

for i in $(seq 12); do  cal ${i} 2020 | awk -v month=${i} 'NF==7 && !/^Su/{ for (j=0;j<=6;j++){print "2020-"month"-"$1+j;}exit}'

РЕДАКТИРОВАТЬ: Обновленный код для дня печати

for i in $(seq 2); do  cal ${i} 2020 | awk -v month=${i} 'NF==7 && !/^Su/{for (j=0;j<=6;j++){print strftime("%A %F", mktime("2020 " month " " $1+j " 0 0 0"))}exit}'; done;

Демо для января и февраля

$for i in $(seq 2); do  cal ${i} 2020 | awk -v month=${i} 'NF==7 && !/^Su/{a[0]="Sunday";a[1]="Monday";a[2]="Tuesday";a[3]="Wednesday";a[4]="Thursday";a[5]="Friday";a[6]="Saturday";for (j=0;j<=6;j++){print a[j]" " "2020-"month"-"$1+j}exit}'; done;
Sunday 2020-1-5
Monday 2020-1-6
Tuesday 2020-1-7
Wednesday 2020-1-8
Thursday 2020-1-9
Friday 2020-1-10
Saturday 2020-1-11
Sunday 2020-2-2
Monday 2020-2-3
Tuesday 2020-2-4
Wednesday 2020-2-5
Thursday 2020-2-6
Friday 2020-2-7
Saturday 2020-2-8
$
2 голосов
/ 05 мая 2020
$ printf "%s\n" 2020-{01..03}-01                 \
  | xargs -I{} date -d "{}" "+{} %u"             \
  | join -j3 - <(seq 0 6)                        \
  | xargs -n3 sh -c 'date -d "$1 + 7 days - $2 days + $3 days" "+%A %F"' --

Здесь есть несколько неприятных вещей, но я постараюсь объяснить. Идея состоит в том, чтобы вычислить день недели первого дня месяца (допустим, u). Если вы это знаете, вы знаете, какой день первое воскресенье (7-u дней спустя). Таким образом, с этого момента вам нужно вычислить только следующие 6 дней.

  1. Используйте расширение скобок, чтобы сгенерировать интересующие вас месяцы
  2. Используйте xargs, чтобы вычислить день неделю и вывести его как YYYY-MM-DD u
  3. . В день мы хотим создать список из 7 строк YYYY-MM-DD u d, где d работает от 0 до 6. Для этого мы используем неприятный join хак . Сообщая join присоединиться к файлам в несуществующем поле, мы создаем внешний продукт .
  4. Используйте xargs в сочетании с sh, чтобы создать команду, которая принимает 3 аргумента и выполните вычисление.

Этот метод теперь легко расширить на другие месяцы и годы:

$ printf "%s\n" 20{20..30}-{01..12}-01  | xargs ...

Вышеупомянутое выглядит немного беспорядочно, и вы можете быть более заинтересованы в версии l oop:

for yyyymm in {2020..2030}-{01..03}; do 
  u=$(date -d "$yyyymm-01" "+%u");
  for ((dd=7-u;dd<14-u;++dd)); do
     date -d "$yyyymm-01 + $dd days" "+%A %F"
  done
done

Предыдущее решение:

Это для первых 3 месяцев 2020 года:

$ printf "%s\n" 2020-{01..03}-{01..13}           \
  | xargs -n1 -I{} date -d '{}' '+%A %F'         \
  | awk -F"[- ]" '/Sun/{a[$3]++} a[$3]==1'

Это для первых лет с 2020 по 2030 год

$ printf "%s\n" 20{20..30}-{01..12}-{01..13}     \
  | xargs -n1 -I{} date -d '{}' '+%A %F'         \
  | awk -F"[- ]" '/Sun/{a[$2,$3]++} a[$2,$3]==1'

Это понимается в 3 шага:

  1. Используйте фигурные скобки для создания списка первые 13 дней месяцев и лет, которые вам интересны. Это прекрасно работает, потому что bash начинает расширяться слева направо. Это означает, что день - это быстрорастущий индекс. Мы просим первые 13 дней, потому что знаем, что первое воскресенье должно приходиться на первые 7 дней.

  2. Преобразуйте дни в ожидаемый формат, используя xargs и date

  3. Используйте awk для фильтрации.

0 голосов
/ 06 мая 2020

Добавив еще одно условие, я могу заставить его работать. a[mm]<8 && a[mm]>0

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 && a[mm]>0 )  print day 
 }'

Вывод:

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

В качестве дополнительного примечания, хотя я жестко запрограммировал 1 для месяца, когда параметр дня> 31, mktime () просто перемещается в в следующем месяце. Таким образом, вы можете передать julian day в mktime с месяцем, установленным на 1.

echo -e "1\n31\n32\n60\n61\n366" | awk ' 
 BEGIN { ti=" 0 0 0" } 
 function dtf(fmt,dy) { 
  return strftime(fmt,mktime("2020 1 " dy ti)) 
 } 
{  
   day=dtf("%A %F",$0);
   j=dtf("%j",$0); 
   print j,day 
 }'

Вывод:

001 Wednesday 2020-01-01
031 Friday 2020-01-31
032 Saturday 2020-02-01
060 Saturday 2020-02-29
061 Sunday 2020-03-01
366 Thursday 2020-12-31
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...