给定星期几名称、一个月中的第几周和一年,如何查找日期

给定星期几名称、一个月中的第几周和一年,如何查找日期

我可以使用找到最后一个星期一date -d 'last-monday' +%F,但给定工作日名称、一个月中的第几周和一年,如何找到日期?

例如给出:Monday, Week 4, June, 2022 输出:2022-06-20

给定:Friday, Week 1, July, 2022 输出:2022-07-01

答案1

以下 perl 脚本使用日期::解析日期格式模块来自时间日期收藏。还有一些基本的算术。

不幸的是,这些模块不是核心 perl 模块,因此它们不包含在 perl 中。您需要自己安装它们。它们针对大​​多数发行版进行了打包 - 例如在 debian 上,运行sudo apt-get install libtimedate-perl.否则用 perl 安装它们效用(其中包含在 Perl 中)。

$ cat decode-week.pl 
#!/usr/bin/perl -l

use strict;
use Date::Parse;
use Date::Format;

sub reformat_date {
  # parse date from first arg, and tidy it up.
  my ($day, $week, $month, $year) = split /\s*,\s*/, shift;
  $week =~ s/^Week //;  # Don't need that word.
  $day =~ s/^(...).*/$1/; # Only need first three letters.

  # some useful constants
  use constant secs_per_day => 86400;        # close enough
  use constant one_week => secs_per_day * 7;

  # Assumes English. Change as required for other languages.
  my %day_names = (Sun => 0, Mon => 1, Tue => 2, Wed => 3,
                   Thu => 4, Fri => 5, Sat => 6);
  # Alternatively, if you want Monday to be the zeroth weekday.
  #my %day_names = (Mon => 0, Tue => 1, Wed => 2, Thu => 3,
  #                 Fri => 4, Sat => 5, Sun => 6);

  # get time_t for first day of $month $year
  my $t = str2time("1 $month $year");
  # get abbreviated day name for that 1st day
  my $dn = time2str("%a",$t);

  # calculate difference in days
  my $diff = $day_names{$day} - $day_names{$dn};

  if ($week == 1 && $diff < 0) {
      return "Invalid week";
  };

  my $new_t = $t + ($diff * secs_per_day) + (($week-1) * one_week);

  # Date::Format doesn't do %F, use identical %Y-%m-%d instead.
  return time2str("%Y-%m-%d",$new_t);
};


foreach (@ARGV) {
  print "$_ ===> ", reformat_date($_);
};

这是作为一个函数编写的,如果您想从日志文件或其他文件中提取要解析的日期,则可以更轻松地重复使用。目前,它仅从命令行获取 args。

示例输出:

$ ./decode-week.pl "Monday, Week 1, June, 2022" "Monday, Week 4, June, 2022" "Friday, Week 1, July, 2022"
Monday, Week 1, June, 2022 ===> Invalid week
Monday, Week 4, June, 2022 ===> 2022-06-20
Friday, Week 1, July, 2022 ===> 2022-07-01

可以通过一些数据验证/检查来改进子例程,以确保它传递的是有效日期,或者至少是正确格式的日期。这是一个相当简单的例子,作为留给读者的练习。

另外,主循环可能应该检查函数返回的值是否为“无效周”,而不是只打印它收到的任何内容。

答案2

您可以告诉ncal您给定月份的日历,然后使用 gawk 提取相应行和列中的数字(并将月份和日期名称翻译为数字):

#! /bin/sh -
wday=$1 week=$2 month=$3 year=$4
{
  echo "$wday $week $month $year"
  ncal -Shb "$month" "$year"
} | gawk '
  NR == 1 {
    wday = $1; week = $2; month = $3; year = $4
    for (i = 1; i <= 7; i++)
      nday[strftime("%A", (i+2)*86400, 1)] = i
    for (i = 1; i <= 12; i++)
      nmonth[strftime("%B", (i-0.5)*86400*30, 1)] = i
    FIELDWIDTHS = "3 3 3 3 3 3 3"
    next
  }
  NR == 3 + week {
    day = $ nday[wday]
    if (+day) {
      printf "%04d-%02d-%02d\n", year, nmonth[month], day
      ok = 1
    }
    exit
  }
  END {
    if (!ok) {
      print "There is no "wday" in week "week" of "month" "year > "/dev/stderr"
      exit 1
    }
  }'

上述月份名称根据用户的区域设置进行解释。

答案3

使用 GNU awk 来实现时间函数和 gensub():

$ cat tst.awk
BEGIN { FS="[, ]+" }
{
    day   = $1
    week  = $3
    month = $4
    year  = $5

    mkMap(year,month)

    key = year SUBSEP month SUBSEP week SUBSEP day
    print $0, "=>", (key in map ? map[key] : "invalid date")
}

function mkMap(year,month,      mthAbbr,mthNr,wkNr,dayNr,date,secs,d) {
    if ( !seen[year,month]++ ) {
        mthAbbr = substr(month,1,3)
        mthNr = (index("JanFebMarAprMayJunJulAugSepOctNovDec",mthAbbr)+2)/3
        wkNr = 1
        for ( dayNr=1; dayNr<=31; dayNr++ ) {
            date = sprintf("%04d-%02d-%02d", year, mthNr, dayNr)
            secs = mktime(gensub(/-/," ","g",date) " 12 0 0")
            split(strftime("%F,%A",secs),d,",")
            if ( d[1] == date ) {
                # date -> secs -> same date means this is a valid date
                map[year,month,wkNr,d[2]] = date
                wkNr += ( d[2] == "Saturday" ? 1 : 0 )
            }
        }
    }
}

$ awk -f tst.awk file
Monday, Week 4, June, 2022 (should output: 2022-06-20) => 2022-06-20
Friday, Week 1, July, 2022 (should output: 2022-07-01) => 2022-07-01
Monday, Week 1, June, 2022 (should output: invalid date) => invalid date

以上是针对此输入文件运行的:

$ cat file
Monday, Week 4, June, 2022 (should output: 2022-06-20)
Friday, Week 1, July, 2022 (should output: 2022-07-01)
Monday, Week 1, June, 2022 (should output: invalid date)

答案4

使用(以前称为 Perl_6)

raku -e 'my %months = (Jan => 1, Feb => 2, Mar => 3, Apr => 4, May => 5, Jun => 6, Jul => 7, Aug => 8, Sep => 9, Oct => 10, Nov =>  11, Dec => 12); my %antimonths = %months.antipairs; \
      my %days = (Monday => 1, Tuesday => 2, Wednesday => 3, Thursday => 4, Friday => 5, Saturday => 6, Sunday => 7); my @a; my %antidays = %days.antipairs; \
      my %ordinals = (1 => "st", 2 => "nd", 3 => "rd", 4 => "th", 5 => "th"); \
      for lines.map: *.split(", ") { @a.push( .[3], sprintf( "%02u", %months{.[2].substr(0,3)} ), .[1].substr(*-1,1), %days{.[0]} ) };  \
      my @b = do for @a.rotor(4) { (.[0], .[1], "01").join("-").Date};  \
      my @week-desired = @a[2,6,10...*]; my @DOW-desired = @a[3,7,11...*];  \
      my @offset = do for @b>>.day-of-week Z @DOW-desired -> ($first-of-month_DOW, $DOW-desired) { ($DOW-desired - $first-of-month_DOW) mod 7}; \
      for ([Z] @b, @week-desired, @DOW-desired, @offset) -> ($a,$b,$c,$d) { \
      say "For %antimonths{$a.month}_{$a.year}, the $b%ordinals{$b} %antidays{$c} is:  " ~ $a + $d + 7*($b - 1) };'  

输入示例:

Saturday, Occurrence 1, January, 2022, #output: 2022-01-01
Monday, Occurrence 1, January, 2022, #output: 2022-01-03
Monday, Occurrence 2, January, 2022, #output: 2022-01-10
Monday, Occurrence 4, June, 2022, #output: 2022-06-27
Friday, Occurrence 1, July, 2022, #output: 2022-07-01
Monday, Occurrence 1, August, 2022, #output: 2022-08-01

示例输出:

For Jan_2022, the 1st Saturday is:  2022-01-01
For Jan_2022, the 1st Monday is:  2022-01-03
For Jan_2022, the 2nd Monday is:  2022-01-10
For Jun_2022, the 4th Monday is:  2022-06-27
For Jul_2022, the 1st Friday is:  2022-07-01
For Aug_2022, the 1st Monday is:  2022-08-01

OP 发布了一个有趣的问题,我尝试回答它 1)使用 Raku 和 2)不使用任何外部模块。

然而,阅读评论后,似乎对“每月一周”的计算方式存在一些不确定性。显然,名义上从星期日开始的“周”将与名义上从星期一开始的“周”产生不同的结果。在不深入“周”计算的细节的情况下,这个答案将“月中的周”计算简化为“该月的第 n 个工作日”。相应地,示例输入已更改以反映Occurrence所需月份中的特定工作日。希望这段代码对OP(和其他人)有用。

在前 2 个语句(第一行)中,散列%months并被%antimonths声明。在第二个语句(第二行)中,散列%days并被%antidays声明。在第三个语句(第三行)中%ordinals声明了哈希值。在第四条语句(第四行)中lines,从文件中读取,分割", "(逗号空格),然后push写入@a数组,通常从最大的时间单位(例如年)到最小的(或变量)最后。在第五条语句(第五行)中,月份和年份被提取出来@a并组合起来填充@b由有效的ISO-8601“第一天”日期组成的数组。

在第六/第七条语句(第六行)中,@a元素被拉出以创建@week-desired数组@DOW-desired。在第七行中使用modan@offset进行计算,即从该月的第一天到所需工作日的天数该月的第一周。最后,在最终计算中使用偏移量(以天为单位)和所需周(所需周乘以 7 以获得天数)以获得ISO-8601打印的日期结果。

[注意,没有显式的错误检查:任何错误检查都将由 Raku 的Date类执行,这可能确实会尝试用户不需要的转换。最后,本文发布的“一行”风格的 Raku 代码可以重写为脚本,(希望)类似于 @cas 发布的格式精美的 Perl 答案]。

https://docs.raku.org/type/Date
https://raku.org

相关内容