을 사용하여 마지막 월요일을 찾을 수 있지만 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에 포함되지 않습니다. 직접 설치해야 합니다. 대부분의 배포판에 대해 패키지되어 있습니다 sudo apt-get install libtimedate-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
. 두 번째 문(라인 2)에서는 해시가 선언 %days
됩니다 %antidays
. %ordinals
해시 값은 세 번째 문(3행)에서 선언됩니다. 네 번째 명령문(라인 4)에서는 lines
파일을 읽고 분할 ", "
(쉼표 공백)한 다음 일반적으로 가장 큰 시간 단위(예: 연도)부터 가장 작은 시간 단위(또는 변수)까지 배열 push
에 기록합니다 . @a
다섯 번째 명령문(5행)에서는 월과 연도를 추출 @a
하고 결합하여 @b
유효한 ISO-8601
"첫 번째" 날짜 배열을 채웁니다.
여섯 번째/일곱 번째 문(6번째 줄)에서는 @a
요소를 꺼내어 @week-desired
배열을 만듭니다 @DOW-desired
. 일곱 번째 줄에 mod
an을 사용하여 @offset
해당 월의 1일부터 원하는 근무일까지의 일수를 계산합니다.매월 첫째 주. 마지막으로, 최종 계산에서 오프셋(일 수)과 필요한 주(일 수를 얻기 위해 필요한 주에 7을 곱함)를 사용하여 ISO-8601
인쇄된 날짜 결과를 얻습니다.
[명시적인 오류 검사는 없습니다. 모든 오류 검사는 Date
Raku의 클래스에 의해 수행되며 실제로 사용자가 원하지 않는 변환을 시도할 수 있습니다. 마지막으로, 이 기사에 게시된 "한 줄" 스타일의 Raku 코드는 @cas]가 게시한 아름다운 형식의 Perl 답변과 유사하게 스크립트로 다시 작성할 수 있습니다.