0

Scenario

I have containers which have last deploy dates in the format:

month/date/year or feb/11/2024.

I cant change the way they output the last deploy date.

I'd like to subtract the last deploy date, from the current date and then write to a file if the last deploy date is greater than 2 months.

Example container1-last-deploy.txt:

last deployed: jan/01/2024

I have several of these files.

The challenge is that the month is given in letters. I could use regex to get the date.

One solution I've thought of is to have the months in a dictionary like:

dict=(
  ['jan']=1
  ['feb']=2
)

But there is bound to be a better way.

Is it possible to convert the lettered month to integers?

This is for bash on macos, preferably a bash 3 solution.

Nickotine
  • 467
  • 2
    [edit] your question to include multiple lines of concise, testable sample input and expected output that demonstrates your needs and we can copy/paste to test a potential solution with. Without that we're just guessing at what you might need, e.g. does "write to a file if the last deploy date is greater than 2 months" mean write the input file to a new file or write the "deployed" line to a new file or write something else to it? Do the input files have more than 1 line? Should the output be to 1 or many files? Do we just compare the month numbers or do the days of the month matter too, etc. – Ed Morton Jan 11 '24 at 17:43

4 Answers4

2

MacOS date - convert date string to seconds since Epoch

date command could understand months in the format of strings through the strptime(3) libc function. I don't have macos so I can't test my answer, but according to macos date man page:

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

[...]

   -f      Use input_fmt as the format string to parse the new_date provided rather than using the
           default [[[mm]dd]HH]MM[[cc]yy][.ss] format.  Parsing is done using strptime(3).

Now you just need to understand how to format your input date from the man page of strptime(3):

       %b or %B or %h
              The month name according to the current locale,  in  abbreviated
              form or the full name.
   %d or %e
          The day of month (1-31).

   %Y     The year, including century (for example, 1991).

So according to this, the input format should be: "%b/%e/%Y".

Next, you need to use the following flag to date:

   -j      Do not try to set the date.  This allows you to use the -f flag in addition to the + option
           to convert one date format to another.  Note that any date or time components unspecified
           by the -f format string take their values from the current time.

And last, since you want to be able to substract the current time from your result, you need to ensure that your +output_fmt would be:

       %s     The number of seconds since the Epoch, 1970-01-01 00:00:00
              +0000 (UTC).  Leap seconds are not counted unless leap
              second support is available.

So the result command that would transform your date format to epoch should be:

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

Again, since I don't have macos I cannot test this to confirm. I'm also not sure if it would accept the month in lowercase, but if you have to capitalize the month, you could just replace $DATE with ${DATE^}

Just for the sake of completeness, I'll also show how to perform this same operation with:

GNU date - convert date string to seconds since Epoch

This is a bit easier, since GNU version of date give more freedom

From its man page:

       The --date=STRING is a mostly free format human readable date
       string such as "Sun, 29 Feb 2004 16:21:42 -0800" or "2004-02-29
       16:21:42" or even "next Thursday".  A date string may contain
       items indicating calendar date, time of day, time zone, day of
       week, relative time, relative date, and numbers.  An empty string
       indicates the beginning of the day.  The date string format is
       more complex than is easily documented here but is fully
       described in the info documentation.

So from my test on my machine, you should just remove the slashes from your input for date command to understand it:

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

Substract from your current date

Now you have the Epoch time of your original date. Just store it into a variable. For instance, in MacOS:

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

To see the current date in seconds since epoch:

$ date +%s
1704965965

So now to calculate the difference in seconds, it's trivial:

$ echo $(( $(date +%s ) - $ORIG_DATE ))
905967
aviro
  • 5,532
1

You should try to get exact requirements (perhaps you have them but you did not state them here).

  • One is the locale to be assumed for the month abbreviations. For example, in a german locale a few month names may cause issues when parsing dates printed by an english-only application (for example, may is mai in german, and oct is okt).

    If the month name used matches the current locale, a simple date -d "$(echo jan/01/2024 | tr / ' ')" would parse the deploy date.

  • The other issue is how you want to compute date differences:

    • If you're going for number of days (60 days as an approximation) you can convert the deploy date and current date into seconds by using a +%s format argument, subtract those numbers, and check whether the result is greater than 60*86400.

    • If you want to adhere to some more complex rule (for example, comparing the day of month) you can still do it in bash but it gets more difficult.

0

This, using any date and any awk would do what you describe:

$ awk -v now="$(date +%m/%Y)" -v dif=2 -F'[ /]' '
    BEGIN {
        split(now,n)
        now = n[1] + (n[2] * 12)
    }
    {
        mth = ( index("janfebmaraprmayjunjulaugsepoctnovdec",$3) + 2 ) / 3
        cur = mth + ($5 * 12)
    }
    (now - cur) > dif
' file
last deployed: oct/01/2023

The above was run on this input file:

$ cat file
last deployed: oct/01/2023
last deployed: nov/01/2023
last deployed: dec/01/2023
last deployed: jan/01/2024

If the month names in your input aren't all lower case, e.g. Jan or JAN, then just change $3 to tolower($3) in the script.

Ed Morton
  • 31,617
  • Any reason for ignoring the day of the month? – aviro Jan 14 '24 at 09:19
  • 2
    @aviro the OP didn't ask for it, didn't include an example using it, and didn't reply to my question where I asked if it had to be part of the comparison so I didn't feel the need to complicate the answer trying to include it in the computation. – Ed Morton Jan 14 '24 at 11:42
  • exactly the day is not required, which can easily be seen from the title and example in the question, ignore him @EdMorton he clearly didn't read the question, although this answer is a lot of work. the mac date cmd is easier and the linux date cmd is even more easier as it doesn't care about the order of date month, year. I appreciate the use of awk though, nice. – Nickotine Feb 17 '24 at 20:34
  • If you have just 1 timestamp to work on then using Unix date is fine, but if you have to loop through multiple of them just be aware that using any Unix date command will be orders of magnitude slower than the awk command. We're talking seconds vs hours for large files. See why-is-using-a-shell-loop-to-process-text-considered-bad-practice for why that is. – Ed Morton Feb 17 '24 at 21:59
0

Using Raku (formerly known as Perl_6)

Start by converting the date to a valid ISO 8601 Date:

~$ raku -pe 'BEGIN my %months = [Z=>] <Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec>, 1..12;  \
             s{ ^ .+? <( ( <.alpha>**3 ) (\/)  (\d**1..2)  (\/) (\d**4) } = "$4-{sprintf q[%02d], %months{$0.tclc}}-$2".Date;'  file
last deployed: 2024-01-01

No worries, we can always re-format it:

~$ raku -pe 'BEGIN my %months = [Z=>] <Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec>, 1..12;  \
             s{ ^ .+? <( ( <.alpha>**3 ) (\/)  (\d**1..2)  (\/) (\d**4) } = "$4-{sprintf q[%02d], %months{$0.tclc}}-$2".Date.mm-dd-yyyy("/");'  file
last deployed: 01/01/2024

Since this Raku element is now a Raku Date object, you can do math on it:

~$ raku -pe 'BEGIN my %months = [Z=>] <Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec>, 1..12;  \
             s{ ^ .+? <( ( <.alpha>**3 ) (\/)  (\d**1..2)  (\/) (\d**4) } = Date.new(now) - "$4-{sprintf q[%02d], %months{$0.tclc}}-$2".Date ~ " days ago";'  file
last deployed: 46 days ago  

See links below for further code examples.

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

jubilatious1
  • 3,195
  • 8
  • 17