如何削减一年中每个月的第一个星期日至星期六?

时间:2020-05-05 07:18:34

标签: bash perl awk

我们有绿色区域逻辑,其中作业仅在每个第一个星期日至星期六之间运行,即从每个月的第一个星期日开始7天。我正在使用下面的awk命令来实现这一点,但是它在某个地方坏了。我只是尝试前三个月,即一月至三月

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 个答案:

答案 0 :(得分:6)

我建议使用以下替代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个月的每个月都要完成。

答案 1 :(得分:4)

使用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;
    }   
}

这样可以保持状态(在一个月的第一个月中找出第一个星期日是星期几),如果该程序旨在生成并遍历感兴趣范围内的所有日期,则非常合适。

另一方面,程序可能需要检查给定的一天;也许它每天运行,并且需要检查这一天。这样一来,查看日期是否在该月的第一个和第二个星期日之间就更简单了

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循环可简化检查。

对于除星期日之外的其他日子,我们将放到过去的星期日,以检查那是否是该月的第一个星期日。如果是这样,那么我们的日子就在所要求的间隔内。如果一天,那么我们只需要检查它是否是该月中的第一个。

如果需要的话,可以使代码更加高效和简洁

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, ")";
}

...出于可读性考虑。

答案 2 :(得分:4)

这是让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天循环播放,并在年份变化时中断,所以我不必在确定年份中的日子时尝试适应DST或leap年或leap秒。计算更准确。

只需添加一些代码来测试当月与上个月不同,当前日期为星期日,并从该日期开始打印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

有一些更有效的方法来完成此操作,但是上面的操作非常简单明了,一眨眼就可以完成。

答案 3 :(得分:3)

尝试

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

EDIT:更新了打印日代码

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
$

答案 4 :(得分:3)

A(相当罗y-我没有时间使它更简短:-))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;
  }
}

答案 5 :(得分:2)

sample_chain

这里有些讨厌的东西,但是我会尽力解释。这个想法是计算每月第一天的星期几(假设$ 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天之后)。因此,从那时起,您只需要计算接下来的6天。

  1. 使用括号扩展来生成您感兴趣的月份
  2. 使用7-u计算星期几并将其输出为xargs
  3. 每天,我们要创建一个包含7个字符串的列表YYYY-MM-DD u,其中YYYY-MM-DD u d的范围是0到6。为此,我们使用了讨厌的join hack。通过告诉d加入到不存在的字段中的文件,我们创建了外部产品
  4. 结合使用joinxargs来创建一个接受3个参数并进行计算的命令。

此方法现在可以轻松扩展到其他月份和年份:

sh

上面的内容看起来有些混乱,您可能对循环版本更感兴趣:

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

以前的解决方案:

这是2020年的前三个月:

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

这是从2020年到2030年的第一年

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

这可以通过3个步骤来理解:

  1. 使用花括号扩展来创建感兴趣的月份和年份的前13天的列表。由于bash开始从左向右扩展,因此效果很好。这意味着一天是快速运行的索引。我们要求前13天,因为我们知道第一个星期日必须在前7天之内。

  2. 使用$ 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' xargs

  3. 将日期转换为预期格式
  4. 使用date进行过滤。

答案 6 :(得分:0)

通过添加一个条件,我可以使其工作。 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,但当day参数> 31时,mktime()会移至下个月。因此,您可以将julian day设置为mktime并将month设置为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
相关问题