Вопрос или проблема
Я могу найти последний понедельник, используя date -d 'last-monday' +%F
, но как найти дату, если дано имя дня недели, неделя месяца и год?
например: Monday, Week 4, June, 2022
вывод: 2022-06-20
например: Friday, Week 1, July, 2022
вывод: 2022-07-01
Следующий скрипт на языке perl использует модули Date::Parse и
Date::Format из коллекции TimeDate. А также некоторые базовые арифметические операции.
К сожалению, эти модули не являются стандартными для perl, поэтому они не включены в поставку perl. Вам нужно будет установить их самостоятельно. Они упакованы для большинства дистрибутивов – например, в debian выполните sudo apt-get install libtimedate-perl
. Или установите их с помощью утилиты cpan для perl (которая включена в поставку perl).
$ cat decode-week.pl
#!/usr/bin/perl -l
use strict;
use Date::Parse;
use Date::Format;
sub reformat_date {
# разбор даты из первого аргумента и ее оформление.
my ($day, $week, $month, $year) = split /\s*,\s*/, shift;
$week =~ s/^Week //; # Слово "Week" не нужно.
$day =~ s/^(...).*/$1/; # Нужны только первые три буквы.
# некоторые полезные константы
use constant secs_per_day => 86400; # достаточно точно
use constant one_week => secs_per_day * 7;
# Допускается английский язык. Измените при необходимости для других языков.
my %day_names = (Sun => 0, Mon => 1, Tue => 2, Wed => 3,
Thu => 4, Fri => 5, Sat => 6);
# Альтернативно, если хотите, чтобы понедельник был нулевым днем недели.
#my %day_names = (Mon => 0, Tue => 1, Wed => 2, Thu => 3,
# Fri => 4, Sat => 5, Sun => 6);
# получение time_t для первого дня $month $year
my $t = str2time("1 $month $year");
# получение сокращенного названия дня для этого 1-го дня
my $dn = time2str("%a",$t);
# вычисление разницы в днях
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 не поддерживает %F, используйте идентичный %Y-%m-%d вместо него.
return time2str("%Y-%m-%d",$new_t);
};
foreach (@ARGV) {
print "$_ ===> ", reformat_date($_);
};
Это было написано в виде функции для удобства повторного использования, если вы хотите извлечь дату из файла журнала или чего-то подобного. Пока что она просто принимает аргументы из командной строки.
Пример вывода:
$ ./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
Подпрограмму можно улучшить, добавив проверку данных, чтобы убедиться, что передана корректная дата или, по крайней мере, дата в правильном формате. Оставлено в качестве упражнения для читателя, это довольно простой пример.
Также, основной цикл, вероятно, должен проверять, является ли возвращаемое значение функции “Invalid week” или нет, а не просто выводить все, что он получает.
Вы можете сказать 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
}
}'
Выше названия месяцев интерпретируются в соответствии с локалью пользователя.
Используя 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)
Используя Raku (ранее известный как 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 %antidays = %days.antipairs;
my %ordinals = (1 => "st", 2 => "nd", 3 => "rd", 4 => "th", 5 => "th");
#цикл по строкам:
my @a; 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 "Для %antimonths{$a.month}_{$a.year}, $b%ordinals{$b} %antidays{$c} это: " ~ $a + $d + 7*($b - 1)
};' file
Пример входных данных:
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
Пример вывода:
Для Jan_2022, 1-й Saturday это: 2022-01-01
Для Jan_2022, 1-й Monday это: 2022-01-03
Для Jan_2022, 2-й Monday это: 2022-01-10
Для Jun_2022, 4-й Monday это: 2022-06-27
Для Jul_2022, 1-й Friday это: 2022-07-01
Для Aug_2022, 1-й Monday это: 2022-08-01
Автор интерпретировал интересный вопрос и попытался ответить на него 1) используя Raku и 2) без использования любых внешних модулей.
Считая комментарии, кажется есть некоторая неопределенность относительно того, как должна рассчитываться “неделя-месяца”. Ясно, что насчитывая “неделю”, начинающуюся в воскресенье, получаются другие результаты, по сравнению с началом недели в понедельник. Не углубляясь в подробности подсчета “недели”, этот ответ упрощает расчет “неделя-месяца” до “nth-день недели в месяце”. Соответственно, пример входных данных был изменен для отражения Occurrence
конкретного дня недели в заданном месяце. Надеюсь, этот код окажется полезным для автора вопроса (и других).
В первых 2-х заявлениях (первой строке) объявляются хэши %months
и %antimonths
. Во вторых 2-х заявлениях (вторая строка) объявляются хэши %days
и %antidays
. В третьем заявлении (третья строка) объявляется хэш %ordinals
. В четвертом заявлении (четвертая строка) lines
читаются из файла, сплитятся на ", "
(запятая пробел), и push
добавляется в массив @a
, в общем плане от большего к меньшему временному измерению (например, года), за исключением переменной измерения.
В пятом заявлении (пятая строка) извлекаются месяц и год из @a
и объединяются для заполнения массива @b
, состоящего из действительных дат “первого-дня-месяца” ISO-8601
.
В шестом/седьмом заявлениях (шестая строка) из элементов @a
создаются массивы @week-desired
и @DOW-desired
. В седьмой строке с использованием mod
рассчитывается @offset
, который составляет количество дней от первого-в-месяце до желаемого дня недели в первую неделю месяца. Наконец, смещение (в днях) и нужная неделя (нужная неделя умноженная на 7, чтобы получить дни) используется в конечном расчете, чтобы получить результат даты ISO-8601
, который печатается.
[Обратите внимание, что явной проверки ошибок нет: любая проверка ошибок будет выполнена классом Date
Raku, который может действительно попытаться преобразовать данные не желательным для пользователя способом. Наконец, представленный здесь ‘в одну строку’ код Raku может быть переписан в виде скрипта, (надеюсь) похожего на прекрасно отформатированный ответ на Perl, представленный @cas].
.
Ответ или решение
Чтобы вычислить конкретную дату, зная название дня недели, номер недели в месяце и год, требуется применить несколько методов программирования и математических вычислений. Рассмотрим теоретическую часть, практические примеры и возможное применение.
Теория
В теоретическом аспекте, для решения задачи нам нужно разобраться с понятием «неделя месяца» и правильным расчетом даты для заданного дня недели. Одним из основных факторов является определение, какая именно неделя месяца учитывается: полная неделя или неполная, начиная с первого числа месяца или начиная с первого понедельника и так далее. Например, если месяц начинается с пятницы, то стоит определить, какая неделя будет считаться первой для упрощения нашего вычисления.
При вычислении даты определенного дня недели в месяце, следует помнить о различиях в продолжительности месяцев, а также о том, что начало недели может различаться в зависимости от местных стандартов.
Пример
Предположим, нам дано следующее: «Понедельник, 4 неделя, июнь, 2022 год». Для этой задачи нам нужно определить, какой будет дата для предложенного условия. Мы используем несколько языков программирования для демонстрации возможных решений.
- Использование Perl
Пользователи Perl могут применять модули Date::Parse и Date::Format для достижения результата. Пример программы, которая помогает в вычислениях, представлен выше. Она включает такие важные моменты, как преобразование даты, вычитание номеров дней недели, добавление недель и другие операции, которые по итогу возвращают нужную дату в формате ISO 8601.
- Использование GNU Awk
GNU Awk может быть также использован для обработки строк команд и вычислений даты, прибегая к функциям времени. Скрипт awk выполняет разбор строки, рассчитывает смещения и выдает результат после применения преобразований даты.
- Использование Raku
Raku (ранее Perl 6) также предоставляет мощные инструменты для работы с датами без использования внешних модулей. В Raku используется несколько хешей для хранения месяцев и дней, а затем вычисляется необходимая дата с учетом недели и дня.
Применение
Это задание может найти применение в различных сферах, включая планирование событий на основе стандартных графиков, подготовка отчетов, автоматизация процессов работы, где требуется интеграция конкретных дат в процессе. Работая с системами, где необходима точность в расчете дат, подобный сценарий может быть особенно полезен.
Заключение
Понимание и умение найти дату на основании предоставленной информации является важным навыком для IT-специалистов, работающих с автоматизацией и системами планирования. Выбор языка программирования, набора инструментов и методов зависит от конкретных требований задач и технического окружения. Независимо от выбранного инструмента, важно подкрепить решение строгой валидацией и тестированием, чтобы обеспечить корректность вычислений и избежать неверных интерпретаций.