Date calculation using GNU date - bash

Using the GNU date command line utility, I know:
how to substract 3 days from any given date:
date -d "20110405 -3 days" "+%Y%m%d"
20110402
how to get the last Friday from today:
date -d "last friday" "+%Y%m%d"
20110408
But I don't know how to get the last Friday from any given date:
date -d "20110405 last friday" "+%Y%m%d"
Simply returns the given date:
20110405
Any ideas on how to do this? If a one-liner is not possible a few lines of script would also be helpful.

Ugly, but one line:
date -d "20110405 -2 days -$(date -d '20110405' '+%w') days" "+%Y%m%d"
EDIT: See comments.
date -d "20110405 -$(date -d "20110405 +2 days" +%u) days" "+%Y%m%d"
Explanation:
%w returns day of the week. Friday = 5 so take off 2 more days to get the right offset.
Works out as "20110405 -x days", where x is the number of days back to last Friday.
I don't like that it repeats the date string, but hopefully it goes some way to helping.

Script example (based on the accepted answer)
DT="20170601"
# get the Friday before $DT
# expected value is 20170526
date -d "$DT -`date -d "$DT +2 days" +%u` days" "+%Y%m%d"
Further examples, using undocumented features of GNU date (from unix.com)
# assign a value to the variable DT for the examples below
DT="2006-10-01 06:55:55"
echo $DT
# add 2 days, one hour and 5 sec to any date
date --date "$DT 2 days 1 hour 5 sec"
# subtract from any date
date --date "$DT 3 days 5 hours 10 sec ago"
date --date "$DT -3 days -5 hours -10 sec"
# or any mix of +/-. What will be the date in 3 months less 5 days?
date --date "now +3 months -5 days"

Related

unix date util + bash get a "last monday" starting from a given date

I see that unix date util provides a way to create a date from a string with specified format.
And it can also create a date using such strings as "last monday" or X days ago.
Is it possible to get a last monday for any given date using just date util and bash.
Something akin
date -d "$(date -d '-24 day' +%Y%m%d) last monday"
This may not be your desired answer as it does not use the string such as last monday but how about:
given="Mar 16" # example
dow=$(date -d "$given" +%w) # day of week (0: Sun .. 6: Sat)
before=$(( (dow + 5) % 7 + 1 )) # days to go back
date -d "$given -$before days" +%Y%m%d # result

First and last days of month given a specific day of that month - shell

I can get the first and last days of the current month
#first
date -d "-0 month -$(($(date +%d)-1)) days" +%Y%m%d
#last
date -d "-$(date +%d) days +1 month" +%Y%m%d
Now, if I'm accepting a particular day of the month in YYYYMMDD format as an argument like indate="$1", how can I use this to get the first and last dates for that corresponding month?
You can do it like this:
indate='20160215'
# first day of given month
date -d "$indate -${indate:6} days +1 day" +%Y%m%d
20160201
# last day of given month
date -d "$indate +1 month -${indate:6} days" +%Y%m%d
20160229

Shell add day to date saved in parameter

Hoping someone can help me work out what on earth is happening here.
I've got a script which receives a date as a parameter in this format "2016-09-01 00:00:00" and should create another variable containing the date for one day in the future, code is below
currentDate=$1
currentDate=$(date +"%Y-%m-%d %H:%M:%S" -d "$currentDate")
nextDate=$(date +"%Y-%m-%d %H:%M:%S" -d "$currentDate + 1 day")
echo $currentDate
echo $nextDate
Sometimes this works perfectly fine for example
2016-09-01 00:00:00 - date given as parameter
2016-09-02 00:00:00 - output for next day
But sometimes it'll only add 23 hours depending on the date provided
2016-02-01 00:00:00 - date given as parameter
2016-02-01 23:00:00 - output for next day
if I change the nextDay variable to add three days as below
nextDate=$(date +"%Y-%m-%d %H:%M:%S" -d "$currentDate + 3 day")
it gives the output as below adding only 21 hours instead of 3 days
2016-02-01 00:00:00 - date given as parameter
2016-02-01 21:00:00 - output for next day
Could someone help me understand why this is happening, is it related to timezones?
The reason appears to be how difficult it is to do free-form date parsing:
A baseline date: (I'm in the America/Toronto time zone)
$ date -d "2016-11-06 01:00:00" "+%F %T%z"
2016-11-06 01:00:00-0400
Try adding a day
$ date -d "2016-11-06 01:00:00 + 1 day" "+%F %T%z"
2016-11-06 19:00:00-0500
Hmm, that's strange, looks like it's adding a day but then expressing it as midnight, then subtracting 5 hours.
Let's try adding a day to just the date part
$ date -d "2016-11-06 + 1 day 01:00:00" "+%F %T%z"
2016-11-07 01:00:00-0500
That looks better.
In your script try this:
read current_date current_time < <(date -d "$1" +"%F %T%z")
echo "$current_date $current_time"
next_day=$(date -d "$current_date + 1 day $current_time" +"%F %T%z")
echo "$next_day"
three_days=$(date -d "$current_date + 3 day $current_time" +"%F %T%z")
echo "$three_days"

bash loop comparing date strings

Given a start date, I set the date to the end of the month of the previous year, and then try to loop 12m using a bash while loop.
The following loop never exits, and the date eventually skips from the end of the month to the beginning of the next one.
startdate="2014-06-22"
cod=${startdate:0:7}-01
echo $cod
cod=$( date --date "$cod -1 year +1 month -1 day " +"%Y-%m-%d" )
echo "$cod , $startdate "
while [ "$cod" < "$startdate" ];
do
echo $cod
cod=$( date --date "$cod +1 month" +"%Y-%m-%d" )
echo $cod
done
What I'm expecting is
2013-06-30
2013-07-31
2013-08-31
2013-09-30
2013-10-31
2013-11-30
2013-12-31
2014-01-31
2014-02-28
2014-03-31
2014-04-30
2014-05-31
So what you want is the last day of each month, but you are trying to get it by taking the first "last day of the month" and adding one month each time. That doesn't work because you get January 30th and then March 2nd rather than February 28th, because February is weird and "+1 month" is weird :-)
$ date -d '2014-01-30'
Thu Jan 30 00:00:00 GMT 2014
$ date -d '2014-01-30 +1 month'
Sun Mar 2 00:00:00 GMT 2014
To get the last day of each month you need to repeat the trick you use on the first date - find the first day of the following month and then -1 day from it.
#!/bin/bash
startdate="2014-06-22"
cod=${startdate:0:7}-01
cod=$( date --date "$cod -1 year +1 month -1 day " +"%Y-%m-%d" )
for i in $(seq 1 12); do
echo $cod
cod=$( date --date "$(date --date "$cod +32 day" +"%Y-%m-01") -1 day" +"%Y-%m-%d" )
done
Which outputs:
$ ./twelve_months.sh
2013-06-30
2013-07-31
2013-08-31
2013-09-30
2013-10-31
2013-11-30
2013-12-31
2014-01-31
2014-02-28
2014-03-31
2014-04-30
2014-05-31
Note that I also changed the loop to a straight 1 .. 12 instead of a while loop with a logic check. The while loop seemed needlessly complicated when you know you always want twelve dates.
< is getting executed by the shell. You need to escape it or use the [[ keyword for lexicographic comparison.
while [ "$cod" \< "$startdate" ];
or
while [[ "$cod" < "$startdate" ]];
I don't think you can compare date strings, but I am sure you can convert date strings to epoch and compare them as integer
Epoch is time in seconds since 1/1/1970 00:00:00+000
example:
startdate="2014-06-22"
epoch_start=$(date -d "$startdate" +%s)
cod=$startdate
cod=$( date --date "$cod -1 year +1 month -1 day " +%s )
while [ "$cod" -lt "$epoch_start" ];
do
date=$(date -d "#${cod}" +%Y-%m-%d)
date -d "#${cod}" +%Y-%m-%d
cod=$(date --date "${date} +1 month" +%s)
done
date -d "#${cod}" +%Y-%m-%d
Output:
bash test4.sh
2013-07-21
2013-08-21
2013-09-21
2013-10-21
2013-11-21
2013-12-21
2014-01-21
2014-02-21
2014-03-21
2014-04-21
2014-05-21
2014-06-21
2014-07-21
I hope that helps you.

Today's date, minus X days in shell script

I need to create three variables, each for Year, Month, and Day for Today's date, minus X number of days. For this question I'll choose a random amount of days: 222.
So if:
TodayYear=`date +%Y`
TodayMonth=`date +%m`
TodayDay=`date +%d`
What I want is 222 days before this.
222days_before_TodayYear=???
222days_before_TodayMonth=???
222days_before_TodayDay=???
Edit: Need 222 working days instead 222 regular days.
For GNU date:
date_222days_before_TodayYear=$(date --date="222 days ago" +"%Y")
date_222days_before_TodayMonth=$(date --date="222 days ago" +"%m")
date_222days_before_TodayDay=$(date --date="222 days ago" +"%d")
For BSD date::
If you are using OS X or FreeBSD, use the following instead because BSD date is different from GNU date:
date_222days_before_TodayYear=$(date -j -v-222d +"%Y")
date_222days_before_TodayMonth=$(date -j -v-222d +"%m")
date_222days_before_TodayDay=$(date -j -v-222d +"%d")
Source: BSD date manual page
Note:
In bash and many other languages, you cannot start a variable name with a numerical character, so I prefixed them with date_ for you.
Second Update: New requirement - Using 222 Working days instead of 222 Regular days:
(Assumption: Not considering statutory holidays, because that just gets far beyond the scope of what I can help you with in a shell script:)
Consider 222 working days:
5 working days per week, that is floor(222/5) == 44 weeks
44 weeks * 7 days per week == 308 days
Extra days leftover: 222 % 5 == 2
Therefore 222 working days == 310 regular days
But, there is a catch! If the number of regular days is 308 or some multiple of 7, then we would have been fine, because any multiple of 7-days ago from a working day is still a working day. So we need to consider whether today is a Monday or a Tuesday:
If today is a Monday, we'd get Saturday where we wanted Thursday
If today is a Tuesday, we'd get Sunday where we wanted Friday
So you see we need an additional offset of 2 more days if today is either Monday or Tuesday; so let's find that out first before we proceed:
#!/bin/bash
# Use 310 days as offset instead of 222
offset=310
# Find locale's abbreviated weekday name (e.g., Sun)
today=$(date -j +"%a")
# Check for Mon/Tue
if [[ "$today" == "Mon" ]] || [[ "$today" == "Tue" ]]; then
offset=$((offset+2))
fi
date_222_working_days_before_TodayYear=$(date -j -v-${offset}d +"%Y")
date_222_working_days_before_TodayMonth=$(date -j -v-${offset}d +"%m")
date_222_working_days_before_TodayDay=$(date -j -v-${offset}d +"%d")
And that should do it =)
date '+%Y' --date='222 days ago'
You can get exact past date from the following in bash
Number=222
current_date=$(date +%Y%m%d)
past_date=$(date -d "$current_date - $Number days" +%Y%m%d)
echo "$current_date\t$past_date"
Hope this helps !
epoch=$(( `date '+%s'` - ( 24 * 60 * 60 * 222 ) ))
year=`date -d "#$epoch" '+%Y'`
month=`date -d "#$epoch" '+%m'`
day=`date -d "#$epoch" '+%d'`
Should do the trick.
I would say easier solution would be
222days_before_TodayYear = $(date -v -222d +%Y)
222days_before_TodayMonth = $(date -v -222d +%m)
222days_before_TodayDay = $(date -v -222d +%d)

Resources