How to get the date of the 3. wednesday in the month - bash

I need a code sample getting the date of the 3. wednesday of the month.
I simply can't wrap my head around how to do that - the last wednesday is simple:
cal | awk '/^ *[0-9]/ { d=$4 } END { print d }'
Ultimately I need the script to return the "next 3. wednesday" - as in if we have passed the 3. wednesday of this month, return the 3. wednesday of the next month.

Here's a simple approach which would work properly in most regions:
#!/bin/bash
export LC_TIME=C
thismonth=$(date +%m) # this month like "10"
thisyear=$(date +%Y) # this year like "2018"
firstdayofweek=$(date -d "${thisyear}-${thismonth}-1" +%w)
# calculates the day of week of the 1st day of the month
# returns the number between 0 and 6, where 0=Sun, 1=Mon, ...
wed1=$(( (10 - $firstdayofweek) % 7 + 1 ))
# calculates the day of month of the 1st Wednesday
wed3=$(( $wed1 + 14 ))
# the day of the 3rd Wednsday
echo $wed3
which yields "17" as of today.
It would be easy to modify the code above to achieve your next goal.

if you have ncal
$ ncal | awk '/We/{print $4}'
ncal format is transpose of cal and working on rows is easier. 3rd Wednesday is the fourth field.
$ ncal
October 2018
Su 7 14 21 28
Mo 1 8 15 22 29
Tu 2 9 16 23 30
We 3 10 17 24 31
Th 4 11 18 25
Fr 5 12 19 26
Sa 6 13 20 27
for example for November
$ ncal -m 11
November 2018
Su 4 11 18 25
Mo 5 12 19 26
Tu 6 13 20 27
We 7 14 21 28
Th 1 8 15 22 29
Fr 2 9 16 23 30
Sa 3 10 17 24
$ ncal -m 11 | awk '/We/{print $4}'
21

It's not pretty, but on a GNU/Linux system you could loop over each day of the month and count:
#!/bin/bash
month="$1" # e.g. 2018-10
number="$2" # e.g. 3
weekday="$3" # e.g "Wednesday"
export LC_ALL=C TZ=UTC
date -d today > /dev/null 2>&1 || {
echo "You are not using GNU date"
exit 1
}
n="$number"
for day in {1..31}
do
wd=$(date -d "$month-$day" +"%A" 2>&1) || continue
if [[ $wd == "$weekday" ]]
then
(( --n )) || break
fi
done
if ! (( n ))
then
echo "The $weekday number $number in $month is $month-$day"
else
echo "$month does not have a $weekday number $number"
fi
However, note that dates are hard, and this simple approach may not correctly account for various curiosities like Samoa skipping Friday 2011-12-30 due to a time zone shift.
Here are some examples on a GNU system:
$ ./finddate 2018-10 3 Wednesday
The Wednesday number 3 in 2018-10 is 2018-10-17
$ ./finddate 2018-10 5 Thursday
2018-10 does not have a Thursday number 5
Here is an example from a non-GNU system like MacOS:
$ ./finddate 2018-10 3 Wednesday
You are not using GNU date

Here's what I came up with:
#!/bin/bash -xv
CURRENTDAY=$(date +%e)
CURRENTMONTH=$(date +%m)
NEXTMONTH=$(( ${CURRENTMONTH}+1 ))
CURRENTYEAR=$(date +%Y)
WEDNESDAYSFORTHEMONTH=$(cal ${CURRENTMONTH} ${CURRENTYEAR} | awk 'NF <= 7 { print $4 }' | grep -v "^$"| grep -v We)
# I would normally start at 0
COUNTER=1
#Get this months 3rd Wednesday
for WEDNESDAY in ${WEDNESDAYSFORTHEMONTH}
do
if [[ ${COUNTER} -eq 3 ]]
then
echo "This months third wednesday is: ${WEDNESDAY}"
THISMONTHSTHIRDWEDNESDAY=${WEDNESDAY}
fi
COUNTER=$(( ${COUNTER}+1 ))
done
# Get the 3rd Wednesday for the next month
WEDNESDAYSFORTHEMONTH=$(cal ${NEXTMONTH} ${CURRENTYEAR} | awk 'NF <= 7 { print $4 }' | grep -v "^$"| grep -v We)
COUNTER=1
for WEDNESDAY in ${WEDNESDAYSFORTHEMONTH}
do
if [[ ${COUNTER} -eq 3 ]]
then
echo "Next months third wednesday is: ${WEDNESDAY}"
NEXTMONTHSTHIRDWEDNESDAY=${WEDNESDAY}
fi
COUNTER=$(( ${COUNTER}+1 ))
done
#Compare the current date with this months 3rd Wednesday
if [[ ${CURRENTDAY} -le ${THISMONTHSTHIRDWEDNESDAY} ]]
then
echo "Wednesday to use in your script is: ${THISMONTHSTHIRDWEDNESDAY} ${CURRENTMONTH} ${CURRENTYEAR}"
else
echo "Wednesday to use in your script is: ${NEXTMONTHSTHIRDWEDNESDAY} ${NEXTMONTH} ${CURRENTYEAR}"
fi

# Third Wednesday of this month
thirdWednesday=$( cal | cut -c 10-12 | sed '/^\s*$/d' | sed -n '5p' )
# Today
today=$( date +%d )
# If already passed
if [ $today -gt $thirdWednesday ]; then
# Next month
nextMonth=$( date +"%m %Y" -d "next month" )
# Get third Wednesday of next month
thirdWednesday=$( cal $nextMonth | cut -c 10-12 | sed '/^\s*$/d' | sed -n '5p' )
fi
# Result
echo $thirdWednesday

Related

How to get second sunday in a Month given a date parameter in bash script

I am trying to write a bash script, to merge 24 files in a given day. The requirement changes during Day light saving time changes, where I get 23 or 25 files.
So, with further research I realized that day-light savings begins on the second Sunday of March(23) of every year and ends on first sunday of Novemeber(25).
I need more inputs to get second sunday in a given month to do the check of finding 23 or 25 files for March and November respectively.
Any inputs to help me with this will be really appreciated.
Thank you
Here is the sample code to find 24 files in a day-
if [ -z "$1" ];then
now=$(date -d "-1 days" +%Y-%m-%d);
else now=$1;
fi
load_date='load_date='$now
singlePath="$newPath/$load_date"
fileCount=$(hdfs dfs -ls -R $hdfsPath/$load_date/ | grep -E '^-' | wc -l)
path=$hdfsPath/$load_date
if [ $fileCount -eq 24 ]; then
echo "All files are available for "$load_date;
hadoop fs -cat $path/* | hadoop fs -put - $singlePath/messages.txt
else echo $fileCount" files are available for "$load_date"! Please note, few files are being missed";
fi
I wouldn't hardcode the dates of DST transistions. I would just count "how many hours did today have":
a "normal" day:
$ diff=$(( $(date -d now +%s) - $(date -d yesterday +%s) ))
$ echo $(( diff / 3600 ))
24
"spring forward"
$ diff=$(( $(date -d "2019-03-10 23:59:59" +%s) - $(date -d "2019-03-09 23:59:59" +%s) ))
$ echo $(( diff / 3600 ))
23
"fall back"
$ diff=$(( $(date -d "2019-11-03 23:59:59" +%s) - $(date -d "2019-11-02 23:59:59" +%s) ))
$ echo $(( diff / 3600 ))
25
One thing to note: since bash only does integer arithmetic, if the difference is not 86400 but 86399, you get:
$ echo $((86399 / 3600))
23
So, better to query yesterday's time first in the tiny-but-non-zero chance that the seconds tick over between the 2 date calls:
diff=$(( -$(date -d yesterday +%s) + $(date -d now +%s) ))
Here, $diff will be 86400 or 86401 (for non DST transition days), and dividing by 3600 will give 24 not 23.

Date comparison with EPOCH to find an expiry in bash script

#!/bin/bash
for ADDR in `netstat -plant|grep LISTEN|grep http|awk '{print $4}'|egrep -v ':80$|:5555$'|sort -u`; do
EXPDATE=`openssl s_time 2>/dev/null | openssl s_client -connect $ADDR 2>/dev/null | openssl x509 -dates 2>/dev/null | grep ^notA | cut -f2 -d= | sed -e "s/ GMT//"`
printf "\t\t\t|%s\t|%s\t|\t%s\t|\n" "$ADDR" "$EXPDATE"
done
EXPDATES="$(echo "$EXPDATE" | awk '{print $1,$2,$4,$3}')"
CURREPOCH="$(date +%s)"
for i in "$EXPDATES"; do
CREXPEPOCH="$(date +%s -d "$i")"
if [[ "$CURREPOCH" -gt "$CREXPEPOCH" ]]; then
echo "No Expiry Found."
else
echo "Cert expired"
fi
done
Here, I'm getting dates from EXPDATE which has multiple date values as shown below,
Jul 12 12:00:00 2019
Jun 18 12:00:00 2019
May 8 00:00:00 2018
Nov 14 00:00:00 2017
And, converting to EPOCH time for better comparison with current EPOCH..
If any past date found, script should return "expired", else "no expiry found"..
I tried above script which is not working..
How I can do that? Any help?
The below tracks contents in an array rather than trying to abuse strings as iterable.
#!/usr/bin/env bash
# return all addresses that smell like HTTP
get_addrs() {
netstat -plant \
| awk '/LISTEN/ && /http/ && ! ($4 ~ /:(80|5555)$/) { print $4; }' \
| sort -u
}
# Given a local server address, return a usable client address
# converts wildcard addresses to loopback ones.
clientAddr() {
local addr=$1
case $addr in
0.0.0.0:*) addr=127.0.0.1:${addr#0.0.0.0:} ;;
:::*) addr='localhost:'"${addr#:::}" ;;
esac
printf '%s\n' "$addr"
}
# Given a local address that runs a HTTPS server, return the last valid date for its certificate
endDateForAddr() {
local addr endDate
addr=$(clientAddr "$1") || return
endDate=$(openssl s_client -connect "${addr}" </dev/null 2>/dev/null \
| openssl x509 -dates \
| awk -F= '/^notAfter/ { print $2; exit }')
[[ $endDate ]] && printf '%s\n' "$endDate"
}
# Loop over our local HTTPS services...
expDates=( )
while read -r addr; do
# find an address we can use to refer to each...
addr=$(clientAddr "$addr") || continue
# ...and use that to find its certificate expirey date.
result=$(endDateForAddr "$addr") || continue
# then add that to our array.
expDates+=( "$result" )
done < <(get_addrs)
# in bash 4.3, this is more efficiently written: printf -v curr_epoch '%(%s)T' -1
curr_epoch="$(date +%s)"
for expdate in "${expDates[#]}"; do
exp_epoch=$(date +%s -d "$expdate")
if (( curr_epoch > exp_epoch )); then
echo "$expdate is in the past"
else
echo "$expdate is in the future"
fi
done
...its output (correct as of this writing):
Jul 12 12:00:00 2019 is in the future
Jun 18 12:00:00 2019 is in the future
May 8 00:00:00 2018 is in the future
Nov 14 00:00:00 2017 is in the future

Unix - get next saturday using cal command

I need get te next saturday from starting the current date for create a file. For example, on linux:
date +%Y%m%d -d saturday
This return 20160430, the next saturday with current date 4/25/2016. That's I want.
I need the same result (or something like that) on Unix (SunOS 5.10). If I execute the same command, the return is the current date 20160425
There's another way to do that, for example using command cal? I tried but just return the first Saturday. The command:
cal | grep -v 2016 |grep -v Th | head -3 | head -1 | sed 's|^.*\([0-9]\)$|\1|g'
So, there's a way to get the next Saturday, starting from current date, using cal command?
Thanks for help!
You can obtain the day using following command:
cal -s | tail -n +2 | grep `date +%d` | cut -d' ' -f 7
Prints calendar of current month, starting weeks with Sunday, strips the year line, greps the line with today's date and gets its 7-th column, which is your Saturday month day number.
Now here is a little script that calculates the rest.
#!/bin/sh
TODAY=$(date +%d)
DAY=$(cal -s | tail -n +2 | grep ${TODAY} | cut -d' ' -f 7)
MONTH=$(date +%m)
YEAR=$(date +%Y)
if [[ $DAY -lt $TODAY ]]; then
# Saturday is next month
let MONTH=MONTH+1
if [[ $MONTH -eq 13 ]]; then
# Saturday is next year
let YEAR=YEAR+1
fi
fi
echo $YEAR$MONTH$DAY

How to get the latest date for a specific day of the week in Bash [duplicate]

This question already has answers here:
How to find out the date of the last Saturday in Linux shell script or python?
(3 answers)
Closed 6 years ago.
Q: How can the latest date for a given_day_of_the_week?
Example: For example if today is the 2016-04-21, I just want to get the date for a given_day_of_the_week for example Monday which would be the 2016-04-18. This date will be still returned if the code was run tomorrow, up until the day before the following given_day_of_the_week.
Explanation: The code should return the latest given_day_of_the_week's (Monday) date until the Sunday 2016-04-24, then the same bit of code running will return the 2016-04-25 next week.
Requires GNU date:
today_dow=$(date +%w)
days=(Sunday Monday Tuesday Wednesday Thursday Friday Saturday)
for ((dow=0; dow<7; dow++)); do
if ((dow < today_dow)); then
date -d "last ${days[dow]}"
else
date -d "${days[dow]}"
fi
done
Sun Apr 17 00:00:00 EDT 2016
Mon Apr 18 00:00:00 EDT 2016
Tue Apr 19 00:00:00 EDT 2016
Wed Apr 20 00:00:00 EDT 2016
Thu Apr 21 00:00:00 EDT 2016
Fri Apr 22 00:00:00 EDT 2016
Sat Apr 23 00:00:00 EDT 2016
So we can do:
given_day_of_the_week() {
local days=(Sunday Monday Tuesday Wednesday Thursday Friday Saturday)
local today_dow=$(date +%w)
local dow datestr
for ((dow=0; dow<7; dow++)); do
if [[ "${days[dow],,}" == "${1,,}" ]]; then
if ((dow < today_dow)); then
datestr="last ${days[dow]}"
else
datestr="${days[dow]}"
fi
date -d "$datestr" "+%F"
fi
done
}
Resulting in:
$ given_day_of_the_week tuesday
2016-04-19
$ given_day_of_the_week friday
2016-04-22
Hardcoding the weekday names like that will give you problems if you're in a different locale
Responding to #ryenus's comment:
$ given_day_of_the_week() {
local -A days=([sunday]=0 [monday]=1 [tuesday]=2 [wednesday]=3 [thursday]=4 [friday]=5 [saturday]=6)
local today_dow=$(date +%w)
local datestr=${1,,}
local dow=${days[$datestr]}
[[ -z "$dow" ]] && { echo "error: unknown day: '$1'" >&2; return 1; }
(( dow < today_dow )) && datestr="last $datestr"
date -d "$datestr" "+%F"
}
$ given_day_of_the_week friday
2016-04-22
$ given_day_of_the_week monday
2016-04-18
$ given_day_of_the_week FOO
error: unknown day: 'FOO'
Inspired by #glenn-jackman's answer, but simpler:
given_day_of_the_week=$1
f='%A %Y-%m-%d'
tomorrow=`date -d 'tomorrow' +"$f"`
today=`date -d 'today' +"$f"`
last5=`seq 1 5 | xargs -I{} date -d '{} day ago' +"$f"`
echo -e "$tomorrow\n$today\n$last5" |
grep -i $given_day_of_the_week |
cut -d' ' -f2
Note: this answer does give tomorrow's date for Monday if used on a Sunday. It also gives tomorrow's date for Tuesday if used on a Monday. Not 100% sure if that's what you had in mind.
If instead the idea is to always go with the current Sunday - Saturday, this will do it:
given_day_of_the_week=$1
f='%A %Y-%m-%d'
[ `date +%A` = 'Sunday' ] && first=`date -d 'today' +"$f"` ||
first=`date -d 'last Sunday' +"$f"`
rest=`seq 1 6 | xargs -I{} date -d "$first +{} day" +"$f"`
echo -e "$first\n$rest" |
grep -i $given_day_of_the_week |
cut -d' ' -f2
# requires bash 4.x and GNU date
last_kday() {
local kday=$1
local -A numbers=([sunday]=0 [monday]=1 [tuesday]=2 [wednesday]=3
[thursday]=4 [friday]=5 [saturday]=6)
if [[ $kday == *day ]]; then
kday=${numbers[${kday,,}]}
elif [[ $kday != [0-6] ]]; then
echo >&2 "Usage: last_kday weekday"
return 1
fi
local today=$(date +%w)
local days_ago=$(( today - kday ))
if (( days_ago < 0 )); then let days_ago+=7; fi
date -d "$days_ago days ago" +%F
}

Sort a list of files based on numeric value in name

I have this list of files and I want to sort them and increment their names by an integer value, my code works fine until the list hits 10. The linux 'sort' command then interprets the first '1' in '10' and thinks it is a smaller number than 9. Is there any way to make this work ?
This is the code I have written to loop over a folder and increment file names:
#!/bin/bash
#set -x
ROOT=~/testing/
FILE_COUNT=$(ls -1 $ROOT | wc -l | awk '{print $1}')
COUNT=5
if [[ ${FILE_COUNT} -eq $COUNT ]]; then
echo $COUNT backup files are there
FILE_LIST=$(ls -1 $ROOT | sort -n -r)
for file in $FILE_LIST; do
echo $file
file_new=`basename $file .zip`
if [[ -e $ROOT$file ]]; then
#mv $ROOT$file $ROOT${file_new%?}$COUNT.zip
FILENUM=${file_new:${#file_new}-1}
#echo "This is file # $FILENUM" next one is $(( FILENUM + 1 ))
echo mv $ROOT$file $ROOT${file_new%?}$(( FILENUM + 1 )).zip
mv $ROOT$file $ROOT${file_new%?}$(( FILENUM + 1 )).zip
fi
((COUNT--))
done
else
echo Not $COUNT files, there are $FILE_COUNT
COUNT=$FILE_COUNT
fi
And these are the results of the sort line:
macbookair:~ ilium007$ ls -l testing/ | sort -n -r -t "_"
total 40
-rw-r--r-- 1 ilium007 staff 15 16 Nov 21:24 backup_9.zip
-rw-r--r-- 1 ilium007 staff 15 16 Nov 21:24 backup_8.zip
-rw-r--r-- 1 ilium007 staff 15 16 Nov 21:24 backup_7.zip
-rw-r--r-- 1 ilium007 staff 15 16 Nov 21:24 backup_6.zip
-rw-r--r-- 1 ilium007 staff 15 16 Nov 21:24 backup_10.zip
How do I create this list of files:
backup_10.zip
backup_9.zip
backup_8.zip
backup_7.zip
backup_6.zip
Any help appreciated.
You need to specify the key to sort on, in this case -k2:
ls | sort -n -r -t "_" -k2
This code ended up working:
#!/bin/bash
set -x
ROOT=~/testing/
FILE_COUNT=$(ls -1 $ROOT | wc -l | awk '{print $1}')
COUNT=5
FILENAME=("backup_19.zip"
"backup_2.zip
"backup_29.zip
"backup_38.zip")
for i in ${FILENAME[#]}; do
BASE_FILE_NAME=`basename $i .zip`
FILENUM=${BASE_FILE_NAME##*_}
NEW_FILE_NUM=$(( FILENUM + 1 ))
NEW_FILE_SUFFIX=$(( FILENUM + 1 )).zip
TEST=${BASE_FILE_NAME%%_*}_${NEW_FILE_SUFFIX}
done
exit

Resources