我可以使用找到最后一个星期一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
。在第七行中使用mod
an@offset
进行计算,即从该月的第一天到所需工作日的天数该月的第一周。最后,在最终计算中使用偏移量(以天为单位)和所需周(所需周乘以 7 以获得天数)以获得ISO-8601
打印的日期结果。
[注意,没有显式的错误检查:任何错误检查都将由 Raku 的Date
类执行,这可能确实会尝试用户不需要的转换。最后,本文发布的“一行”风格的 Raku 代码可以重写为脚本,(希望)类似于 @cas 发布的格式精美的 Perl 答案]。