Как вычесть дату в формате: ‘xyz/[int][int]/[int][int][int][int]’ из текущей даты?

Вопрос или проблема

Сценарий

У меня есть контейнеры, даты последнего развертывания в которых имеют следующий формат:

месяц/дата/год или февр/11/2024.

Я не могу изменить способ, которым они выводят дату последнего развертывания.

Я хотел бы вычесть дату последнего развертывания из текущей даты, а затем записать в файл, если дата последнего развертывания больше 2 месяцев.

Пример container1-last-deploy.txt:

последнее развертывание: янв/01/2024

У меня есть несколько таких файлов.

Проблема в том, что месяц указан буквами. Я мог бы использовать регулярные выражения, чтобы получить дату.

Одно из решений, которое я придумал, – иметь месяцы в словаре, например:

dict=(
  ['янв']=1
  ['фев']=2
)

Но, вероятно, есть и лучший способ.

Можно ли преобразовать названия месяцев в цифры?

Это для bash на macos, предпочтительно решение для bash 3.

MacOS date – конвертировать строку даты в секунды с начала Эпохи

Команда date может распознавать месяцы в формате строк благодаря функции strptime(3) библиотеки libc. У меня нет macos, поэтому я не могу протестировать свой ответ, но согласно странице man для date в macos:

date [-jRu] -f input_fmt new_date [+output_fmt]

[…]

   -f      Используйте input_fmt в качестве форматной строки для разбора new_date, вместо использования
           формата по умолчанию [[[mm]dd]HH]MM[[cc]yy][.ss]. Разбор производится с помощью strptime(3).

Теперь вам просто нужно понять, как форматировать вашу входную дату на странице man strptime(3):

       %b или %B или %h
              Название месяца в соответствии с текущей локалью, в сокращенной
              форме или полное название.

       %d или %e
              День месяца (1-31).

       %Y     Год, включая век (например, 1991).

Таким образом, в соответствии с этим, входной формат должен быть: "%b/%e/%Y".

Далее, вам нужно использовать следующий флаг для date:

   -j      Не пытайтесь установить дату. Это позволяет использовать флаг -f в дополнение к
           опции +, чтобы преобразовать один формат даты в другой. Обратите внимание, что любые даты
           или временные компоненты, не указанные в строке формата -f, получают свои значения от текущего времени.

И, наконец, поскольку вы хотите иметь возможность вычесть текущее время из вашего результата, вам нужно убедиться, что ваш +output_fmt будет:

       %s     Количество секунд с начала Эпохи, 1970-01-01 00:00:00
              +0000 (UTC). У високосных секунд нет учета, если поддержка високосных
              секунд недоступна.

Таким образом, команда результата, которая преобразует ваш формат даты в эпоху, должна быть:

$ DATE="янв/01/2024"
$ date -jf "%b/%e/%Y" $DATE +%s
1704060000

Снова, поскольку у меня нет macos, я не могу протестировать это для подтверждения. Я также не уверен, примет ли это месяц в нижнем регистре, но если вам нужно сделать первую букву месяца заглавной, вы можете просто заменить $DATE на ${DATE^}.

Вместо этого для полноты я также покажу, как выполнить ту же операцию с:

GNU date – конвертировать строку даты в секунды с начала Эпохи

Это немного легче, так как GNU-версия date дает больше свободы.

Согласно его странице man:

       --date=STRING - это в основном свободный формат читаемой человеком даты
       строки, такой как "Вс, 29 Фев 2004 16:21:42 -0800" или "2004-02-29
       16:21:42" или даже "в следующий четверг". Строка даты может содержать
       элементы, указывающие на календарную дату, время суток, часовой пояс, день
       недели, относительное время, относительную дату и числа. Пустая строка
       указывает на начало дня. Формат строки даты
       более сложен, чем это легко задокументировать здесь, но полностью
       описан в документации info.

Итак, согласно моему тесту на моем компьютере, вам просто нужно убрать слэши из вашего ввода, чтобы команда date смогла его понять:

$ DATE="янв/01/2024"
$ echo ${DATE//\// }
янв 01 2024
$ date --date "${DATE//\// }"  +%s
1704060000

Вычитание из вашей текущей даты

Теперь у вас есть время Эпохи вашей первоначальной даты. Просто сохраните его в переменной. Например, в MacOS:

$ ORIG_DATE=$(date -jf "%b/%e/%Y" $DATE +%s)

Чтобы увидеть текущую дату в секундах с начала эпохи:

$ date +%s
1704965965

Теперь, чтобы вычислить разницу в секундах, это тривиально:

$ echo $(( $(date +%s ) - $ORIG_DATE ))
905967

Вам следует попытаться получить точные требования (возможно, у вас они есть, но вы не указали их здесь).

  • Одно из них – это локаль, которая предполагается для сокращений месяцев. Например, в немецкой локали несколько названий месяцев могут вызвать проблемы при разборе дат, напечатанных английским приложением (например, май – это mai на немецком, и окт – это okt).

    Если имя месяца, которое используется, соответствует текущей локали, простая команда date -d "$(echo янв/01/2024 | tr / ' ')" распарсит дату развертывания.

  • Другой вопрос – как вы хотите вычислить разницу дат:

    • Если вы ориентируетесь на количество дней (60 дней как приближение), вы можете преобразовать дату развертывания и текущую дату в секунды, применяя форматный аргумент +%s, вычитая эти числа и проверяя, больше ли результат 60*86400.

    • Если вы хотите придерживаться какого-то более сложного правила (например, сравнение дня месяца), вы также можете сделать это в bash, но это будет сложнее.

Это, с использованием любого date и любого awk, выполнит то, что вы описали:

$ awk -v now="$(date +%m/%Y)" -v dif=2 -F'[ /]' '
    BEGIN {
        split(now,n)
        now = n[1] + (n[2] * 12)
    }
    {
        mth = ( index("янвфевмарапрмайюнjulавгсепоктно...",$3) + 2 ) / 3
        cur = mth + ($5 * 12)
    }
    (now - cur) > dif
' file
последнее развертывание: окт/01/2023

Вышеуказанное было выполнено на этом входном файле:

$ cat file
последнее развертывание: окт/01/2023
последнее развертывание: ноя/01/2023
последнее развертывание: дек/01/2023
последнее развертывание: янв/01/2024

Если названия месяцев в вашем вводе не все в нижнем регистре, например, Янв или ЯНВ, просто измените $3 на tolower($3) в скрипте.

Используя Raku (ранее известный как Perl_6)

ОП спрашивает: “Можно ли преобразовать месяцы, указанные буквами, в числа?” В Raku вы можете начать так:

~$ raku -e 'my %months = [Z=>] <Янв Фев Мар Апр Май Июн Июл Авг Сен Окт Ноя Дек>, 1..12; %months.sort(*.value).say;'
(Янв => 1 Фев => 2 Мар => 3 Апр => 4 Май => 5 Июн => 6 Июл => 7 Авг => 8 Сен => 9 Окт => 10 Ноя => 11 Дек => 12)

Теперь вы можете преобразовать дату в валидную ISO 8601 дату:

~$ raku -pe 'BEGIN my %months = [Z=>] <Янв Фев Мар Апр Май Июн Июл Авг Сен Окт Ноя Дек>, 1..12;  \
             s{ ^ .+? <( ( <.alpha>**3 ) \/  (\d**1..2)  \/ (\d**4) } = "$2-{sprintf q[%02d], %months{$0.tclc}}-$1".Date;'  file
последнее развертывание: 2024-01-01

Не переживайте, мы всегда можем переформатировать с помощью mm-dd-yyyy рутины Raku:

~$ raku -pe 'BEGIN my %months = [Z=>] <Янв Фев Мар Апр Май Июн Июл Авг Сен Окт Ноя Дек>, 1..12;  \
             s{ ^ .+? <( ( <.alpha>**3 ) \/  (\d**1..2)  \/ (\d**4) } = "$2-{sprintf q[%02d], %months{$0.tclc}}-$1".Date.mm-dd-yyyy("/");'  file
последнее развертывание: 01/01/2024

Поскольку этот элемент Raku теперь является объектом Date, вы можете производить вычисления с ним:

~$ raku -pe 'BEGIN my %months = [Z=>] <Янв Фев Мар Апр Май Июн Июл Авг Сен Окт Ноя Дек>, 1..12;  \
             s{ ^ .+? <( ( <.alpha>**3 ) \/  (\d**1..2)  \/ (\d**4) } = Date.new(now) - ""$2-{sprintf q[%02d], %months{$0.tclc}}-$1".Date ~ " дней назад";'  file
последнее развертывание: 46 дней назад  

Смотрите ссылки ниже для дальнейших примеров кода.

https://docs.raku.org/language/temporal.html
https://github.com/Raku/examples/tree/master/categories/cookbook/03dates-and-times
https://raku.org

macos поставляется с устаревшей версией bash, но с текущими версиями zsh, которая является намного более удобной оболочкой. zsh имеет strftime и strftime -r как встроенные интерфейсы для strftime() и strptime(), чтобы форматировать и pарсить временные метки, так что вы можете делать

#! /bin/zsh -
zmodload zsh/datetime || exit

date=янв/01/2024

now=$EPOCHSECONDS
LC_ALL=C strftime -rs then %b/%d/%Y $date || exit

(( month = 30 * (day = 24 * (hour = 60 * (minute = 60 * (second = 1)))) ))

printf '%s в полночь утром было %d секунд (%d дней) назад.\n' \
   $date \
  'сейчас - тогда' \
  '(сейчас - тогда) / день'

if (( now - then > 2 * month )) print Это было больше 2 месяцев назад.

Обратите внимание на LC_ALL=C, чтобы работать с названиями месяцев на английском независимо от локали пользователя. Вам также потребуется это с большинством реализаций date, которые поддерживают разбор временных меток.

Обратите внимание на приближения:

  • один день – это 24 часа по 3600 секунд, в то время как на практике в часовых поясах, которые реализуют переход на летнее/зимнее время, есть дни продолжительностью 23 часа и дни продолжительностью 25 часов.
  • один месяц – это 30 дней, в то время как некоторые месяцы имеют 28, 29 или 31 день.

Ответ или решение

Чтобы вычесть дату, представленную в формате ‘месяц/день/год’ (например, ‘янв/01/2024’), из текущей даты в MacOS с использованием bash, следуйте пошаговым инструкциям, описанным ниже. Эта задача включает в себя преобразование названия месяца из буквенного формата в числовой, использование утилиты date для работы с датами и проверку временного интервала.

Шаг 1: Подготовка данных

Предположим, у нас есть файл с последней датой развертывания контейнера в формате, например:

last deployed: jan/01/2024

Для начала важно прочитать данные из файла и извлечь дату. Вы можете использовать команду cat для чтения содержимого файла:

DATE=$(cat container1-last-deploy.txt | grep 'last deployed:' | awk '{ print $3 }')

Шаг 2: Преобразование даты в секунды

Теперь, имея дату в строковом формате, давайте преобразуем ее в количество секунд с начала эпохи (Epoch time) с помощью команды date. Сначала нужно указать формат, в котором будет произведено чтение даты:

ORIG_DATE=$(date -jf "%b/%d/%Y" "$DATE" +%s)

Флаги:

  • -j — не пытаться установить дату системы.
  • -f — использовать указанный формат для анализа даты.
  • +%s — выводит дату в виде секунд с начала эпохи.

Шаг 3: Получение текущей даты в секундах

Получим текущее время в секундах:

CURRENT_DATE=$(date +%s)

Шаг 4: Вычисление разницы

Теперь, когда мы имеем обе даты в формате секунды, мы можем вычислить разницу:

DIFF=$(($CURRENT_DATE - $ORIG_DATE))

Шаг 5: Проверка условия

Чтобы определить, превышает ли разница две месяца, необходимо преобразовать месяцы в секунды. В среднем месяц составляет 30,44 дня (учитывая високосные годы). Значит, два месяца — это примерно:

TWO_MONTHS=$((2 * 30 * 24 * 60 * 60)) # 2 месяца в секундах

Теперь выполните проверку:

if [ "$DIFF" -gt "$TWO_MONTHS" ]; then
    echo "Last deployed date is greater than 2 months ago."
    echo "$DATE" >> output.txt # Запишите результат в файл
fi

Полный скрипт

Соберем весь процесс в один скрипт:

#!/bin/bash

# Чтение даты из файла
DATE=$(cat container1-last-deploy.txt | grep 'last deployed:' | awk '{ print $3 }')

# Преобразование даты в эпоху
ORIG_DATE=$(date -jf "%b/%d/%Y" "$DATE" +%s)
CURRENT_DATE=$(date +%s)

# Вычисление разницы в секундах
DIFF=$(($CURRENT_DATE - $ORIG_DATE))
TWO_MONTHS=$((2 * 30 * 24 * 60 * 60)) # 2 месяца в секундах

# Проверка условия
if [ "$DIFF" -gt "$TWO_MONTHS" ]; then
    echo "Last deployed date is greater than 2 months ago."
    echo "$DATE" >> output.txt # Записать в файл
fi

Заключение

Следуя приведенным выше шагам, вы сможете легко вычесть дату, записанную в текстовом формате, из текущей даты, проверив, превышает ли она два месяца, и, при необходимости, записать результат в файл. Этот процесс использует стандартные инструменты Linux, доступные в MacOS, и даст надежный результат.

Оцените материал
Добавить комментарий

Капча загружается...