I want to write a bash script that determines the 10 day period (decade) has ended relative to the start date (in the format YYYY-MM-DD).
If the 10 day period is finished script has to output the 10 days period.
Im new in bash and has a lot syntax errors with code, help me pls.
#!/bin/bash
# GNU bash, version 4.3.46
CURRENT_DATE=$(date +%Y-%m-%d)
START_DATE=2019-01-01
IS_TODAY_DECADE_CALCULATION_DAY = (CURRENT_DATE - START_DATE) % 10
if [ $IS_TODAY_DECADE_CALCULATION_DAY -eq 0 ]
then
BEGIN_DATE = $("$CURRENT_DATE - 11 days" +%Y-%m-%d)"
END_DATE = $("$CURRENT_DATE - 1 day" +%Y-%m-%d)"
echo "Period is="$BEGIN_DATE":"$END_DATE"
else
echo "Decade is not finished."
fi
You should compare the unix time stamps. If the time stamp "now+10 days" is larger than the start date, the period is ended.
#! /bin/bash
DATE_OLD=$(date "+%F" -d "-11 days")
DATE_NOW=$(date "+%F")
TEST_DATE_NOW=$(date "+%s" -d ${DATE_NOW})
TEST_DATE_OLD=$(date "+%s" -d ${DATE_OLD})
DIVIDER=$(( (TEST_DATE_NOW - TEST_DATE_OLD) / (60*60*24) ))
REMAINING=$(( DIVIDER % 10 ))
echo "Days between ${DATE_OLD} and ${DATE_NOW} is $DIVIDER"
if [ ${DIVIDER} -gt 0 ]; then
echo "Date ${DATE_OLD} is in the past"
else
echo "Date ${DATE_OLD} is in the future"
fi
if [ $REMAINING -eq 0 ]; then
echo "Ten days period ended"
else
echo "Still in ten day period"
fi
exit 0;
The question implies that the code should identify each 10 day period starting on a specific START_DATE. Bash does not have date math - it can not calculate difference between dates (as expected by '(CURRENT_DATE - START_DATE)'). Two options
Convert date to seconds since Unix Epoch, and do the math on those values, OR
Use date utilities package, OR
using Python, awk, perl
Implementing #1 is simple. Notice few changes to assignments - in particular no spaces are allowed in assignments variable=expression, or let variable=expression
#! /bin/bash
CURRENT_DATE=$(date +%Y-%m-%d)
START_DATE=2019-01-01
# Instead of IS_TODAY_DECADE_CALCULATION_DAY = (CURRENT_DATE - START_DATE) % 10
SEC_IN_DAY=$((60*60*24))
let D1=$(date '+%s' -d "$CURRENT_DATE Z")/SEC_IN_DAY
let D2=$(date '+%s' -d "$START_DATE Z")/SEC_IN_DAY
let IS_TODAY_DECADE_CALCULATION_DAY=(CURRENT_DATE-START_DATE)%10
# Rest of script here
if [ $IS_TODAY_DECADE_CALCULATION_DAY -eq 0 ]
then
BEGIN_DATE=$(date -d "$CURRENT_DATE - 11 days" +%Y-%m-%d)
END_DATE=$(date -d "$CURRENT_DATE - 1 day" +%Y-%m-%d)
echo "Period is=$BEGIN_DATE:$END_DATE"
else
echo "Decade is not finished."
fi
I need to do date arithmetic in Unix shell scripts that I use to control the execution of third party programs.
I'm using a function to increment a day and another to decrement:
IncrementaDia(){
echo $1 | awk '
BEGIN {
diasDelMes[1] = 31
diasDelMes[2] = 28
diasDelMes[3] = 31
diasDelMes[4] = 30
diasDelMes[5] = 31
diasDelMes[6] = 30
diasDelMes[7] = 31
diasDelMes[8] = 31
diasDelMes[9] = 30
diasDelMes[10] = 31
diasDelMes[11] = 30
diasDelMes[12] = 31
}
{
anio=substr($1,1,4)
mes=substr($1,5,2)
dia=substr($1,7,2)
if((anio % 4 == 0 && anio % 100 != 0) || anio % 400 == 0)
{
diasDelMes[2] = 29;
}
if( dia == diasDelMes[int(mes)] ) {
if( int(mes) == 12 ) {
anio = anio + 1
mes = 1
dia = 1
} else {
mes = mes + 1
dia = 1
}
} else {
dia = dia + 1
}
}
END {
printf("%04d%02d%02d", anio, mes, dia)
}
'
}
if [ $# -eq 1 ]; then
tomorrow=$1
else
today=$(date +"%Y%m%d")
tomorrow=$(IncrementaDia $hoy)
fi
but now I need to do more complex arithmetic.
What it's the best and more compatible way to do this?
Assuming you have GNU date, like so:
date --date='1 days ago' '+%a'
And similar phrases.
Here is an easy way for doing date computations in shell scripting.
meetingDate='12/31/2011' # MM/DD/YYYY Format
reminderDate=`date --date=$meetingDate'-1 day' +'%m/%d/%Y'`
echo $reminderDate
Below are more variations of date computation that can be achieved using date utility.
http://www.cyberciti.biz/tips/linux-unix-get-yesterdays-tomorrows-date.html
http://www.cyberciti.biz/faq/linux-unix-formatting-dates-for-display/
This worked for me on RHEL.
I have written a bash script for converting dates expressed in English into conventional
mm/dd/yyyy dates. It is called ComputeDate.
Here are some examples of its use. For brevity I have placed the output of each invocation
on the same line as the invocation, separarted by a colon (:). The quotes shown below are not necessary when running ComputeDate:
$ ComputeDate 'yesterday': 03/19/2010
$ ComputeDate 'yes': 03/19/2010
$ ComputeDate 'today': 03/20/2010
$ ComputeDate 'tod': 03/20/2010
$ ComputeDate 'now': 03/20/2010
$ ComputeDate 'tomorrow': 03/21/2010
$ ComputeDate 'tom': 03/21/2010
$ ComputeDate '10/29/32': 10/29/2032
$ ComputeDate 'October 29': 10/1/2029
$ ComputeDate 'October 29, 2010': 10/29/2010
$ ComputeDate 'this monday': 'this monday' has passed. Did you mean 'next monday?'
$ ComputeDate 'a week after today': 03/27/2010
$ ComputeDate 'this satu': 03/20/2010
$ ComputeDate 'next monday': 03/22/2010
$ ComputeDate 'next thur': 03/25/2010
$ ComputeDate 'mon in 2 weeks': 03/28/2010
$ ComputeDate 'the last day of the month': 03/31/2010
$ ComputeDate 'the last day of feb': 2/28/2010
$ ComputeDate 'the last day of feb 2000': 2/29/2000
$ ComputeDate '1 week from yesterday': 03/26/2010
$ ComputeDate '1 week from today': 03/27/2010
$ ComputeDate '1 week from tomorrow': 03/28/2010
$ ComputeDate '2 weeks from yesterday': 4/2/2010
$ ComputeDate '2 weeks from today': 4/3/2010
$ ComputeDate '2 weeks from tomorrow': 4/4/2010
$ ComputeDate '1 week after the last day of march': 4/7/2010
$ ComputeDate '1 week after next Thursday': 4/1/2010
$ ComputeDate '2 weeks after the last day of march': 4/14/2010
$ ComputeDate '2 weeks after 1 day after the last day of march': 4/15/2010
$ ComputeDate '1 day after the last day of march': 4/1/2010
$ ComputeDate '1 day after 1 day after 1 day after 1 day after today': 03/24/2010
I have included this script as an answer to this problem because it illustrates how
to do date arithmetic via a set of bash functions and these functions may prove useful
for others. It handles leap years and leap centuries correctly:
#! /bin/bash
# ConvertDate -- convert a human-readable date to a MM/DD/YY date
#
# Date ::= Month/Day/Year
# | Month/Day
# | DayOfWeek
# | [this|next] DayOfWeek
# | DayofWeek [of|in] [Number|next] weeks[s]
# | Number [day|week][s] from Date
# | the last day of the month
# | the last day of Month
#
# Month ::= January | February | March | April | May | ... | December
# January ::= jan | january | 1
# February ::= feb | january | 2
# ...
# December ::= dec | december | 12
# Day ::= 1 | 2 | ... | 31
# DayOfWeek ::= today | Sunday | Monday | Tuesday | ... | Saturday
# Sunday ::= sun*
# ...
# Saturday ::= sat*
#
# Number ::= Day | a
#
# Author: Larry Morell
if [ $# = 0 ]; then
printdirections $0
exit
fi
# Request the value of a variable
GetVar () {
Var=$1
echo -n "$Var= [${!Var}]: "
local X
read X
if [ ! -z $X ]; then
eval $Var="$X"
fi
}
IsLeapYear () {
local Year=$1
if [ $[20$Year % 4] -eq 0 ]; then
echo yes
else
echo no
fi
}
# AddToDate -- compute another date within the same year
DayNames=(mon tue wed thu fri sat sun ) # To correspond with 'date' output
Day2Int () {
ErrorFlag=
case $1 in
-e )
ErrorFlag=-e; shift
;;
esac
local dow=$1
n=0
while [ $n -lt 7 -a $dow != "${DayNames[n]}" ]; do
let n++
done
if [ -z "$ErrorFlag" -a $n -eq 7 ]; then
echo Cannot convert $dow to a numeric day of wee
exit
fi
echo $[n+1]
}
Months=(31 28 31 30 31 30 31 31 30 31 30 31)
MonthNames=(jan feb mar apr may jun jul aug sep oct nov dec)
# Returns the month (1-12) from a date, or a month name
Month2Int () {
ErrorFlag=
case $1 in
-e )
ErrorFlag=-e; shift
;;
esac
M=$1
Month=${M%%/*} # Remove /...
case $Month in
[a-z]* )
Month=${Month:0:3}
M=0
while [ $M -lt 12 -a ${MonthNames[M]} != $Month ]; do
let M++
done
let M++
esac
if [ -z "$ErrorFlag" -a $M -gt 12 ]; then
echo "'$Month' Is not a valid month."
exit
fi
echo $M
}
# Retrieve month,day,year from a legal date
GetMonth() {
echo ${1%%/*}
}
GetDay() {
echo $1 | col / 2
}
GetYear() {
echo ${1##*/}
}
AddToDate() {
local Date=$1
local days=$2
local Month=`GetMonth $Date`
local Day=`echo $Date | col / 2` # Day of Date
local Year=`echo $Date | col / 3` # Year of Date
local LeapYear=`IsLeapYear $Year`
if [ $LeapYear = "yes" ]; then
let Months[1]++
fi
Day=$[Day+days]
while [ $Day -gt ${Months[$Month-1]} ]; do
Day=$[Day - ${Months[$Month-1]}]
let Month++
done
echo "$Month/$Day/$Year"
}
# Convert a date to normal form
NormalizeDate () {
Date=`echo "$*" | sed 'sX *X/Xg'`
local Day=`date +%d`
local Month=`date +%m`
local Year=`date +%Y`
#echo Normalizing Date=$Date > /dev/tty
case $Date in
*/*/* )
Month=`echo $Date | col / 1 `
Month=`Month2Int $Month`
Day=`echo $Date | col / 2`
Year=`echo $Date | col / 3`
;;
*/* )
Month=`echo $Date | col / 1 `
Month=`Month2Int $Month`
Day=1
Year=`echo $Date | col / 2 `
;;
[a-z]* ) # Better be a month or day of week
Exp=${Date:0:3}
case $Exp in
jan|feb|mar|apr|may|june|jul|aug|sep|oct|nov|dec )
Month=$Exp
Month=`Month2Int $Month`
Day=1
#Year stays the same
;;
mon|tue|wed|thu|fri|sat|sun )
# Compute the next such day
local DayOfWeek=`date +%u`
D=`Day2Int $Exp`
if [ $DayOfWeek -le $D ]; then
Date=`AddToDate $Month/$Day/$Year $[D-DayOfWeek]`
else
Date=`AddToDate $Month/$Day/$Year $[7+D-DayOfWeek]`
fi
# Reset Month/Day/Year
Month=`echo $Date | col / 1 `
Day=`echo $Date | col / 2`
Year=`echo $Date | col / 3`
;;
* ) echo "$Exp is not a valid month or day"
exit
;;
esac
;;
* ) echo "$Date is not a valid date"
exit
;;
esac
case $Day in
[0-9]* );; # Day must be numeric
* ) echo "$Date is not a valid date"
exit
;;
esac
[0-9][0-9][0-9][0-9] );; # Year must be 4 digits
[0-9][0-9] )
Year=20$Year
;;
esac
Date=$Month/$Day/$Year
echo $Date
}
# NormalizeDate jan
# NormalizeDate january
# NormalizeDate jan 2009
# NormalizeDate jan 22 1983
# NormalizeDate 1/22
# NormalizeDate 1 22
# NormalizeDate sat
# NormalizeDate sun
# NormalizeDate mon
ComputeExtension () {
local Date=$1; shift
local Month=`GetMonth $Date`
local Day=`echo $Date | col / 2`
local Year=`echo $Date | col / 3`
local ExtensionExp="$*"
case $ExtensionExp in
*w*d* ) # like 5 weeks 3 days or even 5w2d
ExtensionExp=`echo $ExtensionExp | sed 's/[a-z]/ /g'`
weeks=`echo $ExtensionExp | col 1`
days=`echo $ExtensionExp | col 2`
days=$[7*weeks+days]
Due=`AddToDate $Month/$Day/$Year $days`
;;
*d ) # Like 5 days or 5d
ExtensionExp=`echo $ExtensionExp | sed 's/[a-z]/ /g'`
days=$ExtensionExp
Due=`AddToDate $Month/$Day/$Year $days`
;;
* )
Due=$ExtensionExp
;;
esac
echo $Due
}
# Pop -- remove the first element from an array and shift left
Pop () {
Var=$1
eval "unset $Var[0]"
eval "$Var=(\${$Var[*]})"
}
ComputeDate () {
local Date=`NormalizeDate $1`; shift
local Expression=`echo $* | sed 's/^ *a /1 /;s/,/ /' | tr A-Z a-z `
local Exp=(`echo $Expression `)
local Token=$Exp # first one
local Ans=
#echo "Computing date for ${Exp[*]}" > /dev/tty
case $Token in
*/* ) # Regular date
M=`GetMonth $Token`
D=`GetDay $Token`
Y=`GetYear $Token`
if [ -z "$Y" ]; then
Y=$Year
elif [ ${#Y} -eq 2 ]; then
Y=20$Y
fi
Ans="$M/$D/$Y"
;;
yes* )
Ans=`AddToDate $Date -1`
;;
tod*|now )
Ans=$Date
;;
tom* )
Ans=`AddToDate $Date 1`
;;
the )
case $Expression in
*day*after* ) #the day after Date
Pop Exp; # Skip the
Pop Exp; # Skip day
Pop Exp; # Skip after
#echo Calling ComputeDate $Date ${Exp[*]} > /dev/tty
Date=`ComputeDate $Date ${Exp[*]}` #Recursive call
#echo "New date is " $Date > /dev/tty
Ans=`AddToDate $Date 1`
;;
*last*day*of*th*month|*end*of*th*month )
M=`date +%m`
Day=${Months[M-1]}
if [ $M -eq 2 -a `IsLeapYear $Year` = yes ]; then
let Day++
fi
Ans=$Month/$Day/$Year
;;
*last*day*of* )
D=${Expression##*of }
D=`NormalizeDate $D`
M=`GetMonth $D`
Y=`GetYear $D`
# echo M is $M > /dev/tty
Day=${Months[M-1]}
if [ $M -eq 2 -a `IsLeapYear $Y` = yes ]; then
let Day++
fi
Ans=$[M]/$Day/$Y
;;
* )
echo "Unknown expression: " $Expression
exit
;;
esac
;;
next* ) # next DayOfWeek
Pop Exp
dow=`Day2Int $DayOfWeek` # First 3 chars
tdow=`Day2Int ${Exp:0:3}` # First 3 chars
n=$[7-dow+tdow]
Ans=`AddToDate $Date $n`
;;
this* )
Pop Exp
dow=`Day2Int $DayOfWeek`
tdow=`Day2Int ${Exp:0:3}` # First 3 chars
if [ $dow -gt $tdow ]; then
echo "'this $Exp' has passed. Did you mean 'next $Exp?'"
exit
fi
n=$[tdow-dow]
Ans=`AddToDate $Date $n`
;;
[a-z]* ) # DayOfWeek ...
M=${Exp:0:3}
case $M in
jan|feb|mar|apr|may|june|jul|aug|sep|oct|nov|dec )
ND=`NormalizeDate ${Exp[*]}`
Ans=$ND
;;
mon|tue|wed|thu|fri|sat|sun )
dow=`Day2Int $DayOfWeek`
Ans=`NormalizeDate $Exp`
if [ ${#Exp[*]} -gt 1 ]; then # Just a DayOfWeek
#tdow=`GetDay $Exp` # First 3 chars
#if [ $dow -gt $tdow ]; then
#echo "'this $Exp' has passed. Did you mean 'next $Exp'?"
#exit
#fi
#n=$[tdow-dow]
#else # DayOfWeek in a future week
Pop Exp # toss monday
Pop Exp # toss in/off
if [ $Exp = next ]; then
Exp=2
fi
n=$[7*(Exp-1)] # number of weeks
n=$[n+7-dow+tdow]
Ans=`AddToDate $Date $n`
fi
;;
esac
;;
[0-9]* ) # Number weeks [from|after] Date
n=$Exp
Pop Exp;
case $Exp in
w* ) let n=7*n;;
esac
Pop Exp; Pop Exp
#echo Calling ComputeDate $Date ${Exp[*]} > /dev/tty
Date=`ComputeDate $Date ${Exp[*]}` #Recursive call
#echo "New date is " $Date > /dev/tty
Ans=`AddToDate $Date $n`
;;
esac
echo $Ans
}
Year=`date +%Y`
Month=`date +%m`
Day=`date +%d`
DayOfWeek=`date +%a |tr A-Z a-z`
Date="$Month/$Day/$Year"
ComputeDate $Date $*
This script makes extensive use of another script I wrote (called col ... many apologies to those who use the standard col supplied with Linux). This version of
col simplifies extracting columns from the stdin. Thus,
$ echo a b c d e | col 5 3 2
prints
e c b
Here it the col script:
#!/bin/sh
# col -- extract columns from a file
# Usage:
# col [-r] [c] col-1 col-2 ...
# where [c] if supplied defines the field separator
# where each col-i represents a column interpreted according to the presence of -r as follows:
# -r present : counting starts from the right end of the line
# -r absent : counting starts from the left side of the line
Separator=" "
Reverse=false
case "$1" in
-r ) Reverse=true; shift;
;;
[0-9]* )
;;
* )Separator="$1"; shift;
;;
esac
case "$1" in
-r ) Reverse=true; shift;
;;
[0-9]* )
;;
* )Separator="$1"; shift;
;;
esac
# Replace each col-i with $i
Cols=""
for f in $*
do
if [ $Reverse = true ]; then
Cols="$Cols \$(NF-$f+1),"
else
Cols="$Cols \$$f,"
fi
done
Cols=`echo "$Cols" | sed 's/,$//'`
#echo "Using column specifications of $Cols"
awk -F "$Separator" "{print $Cols}"
It also uses printdirections for printing out directions when the script is invoked improperly:
#!/bin/sh
#
# printdirections -- print header lines of a shell script
#
# Usage:
# printdirections path
# where
# path is a *full* path to the shell script in question
# beginning with '/'
#
# To use printdirections, you must include (as comments at the top
# of your shell script) documentation for running the shell script.
if [ $# -eq 0 -o "$*" = "-h" ]; then
printdirections $0
exit
fi
# Delete the command invocation at the top of the file, if any
# Delete from the place where printdirections occurs to the end of the file
# Remove the # comments
# There is a bizarre oddity here.
sed '/#!/d;/.*printdirections/,$d;/ *#/!d;s/# //;s/#//' $1 > /tmp/printdirections.$$
# Count the number of lines
numlines=`wc -l /tmp/printdirections.$$ | awk '{print $1}'`
# Remove the last line
numlines=`expr $numlines - 1`
head -n $numlines /tmp/printdirections.$$
rm /tmp/printdirections.$$
To use this place the three scripts in the files ComputeDate, col, and printdirections, respectively. Place the file in directory named by your PATH, typically, ~/bin. Then make them executable with:
$ chmod a+x ComputeDate col printdirections
Problems? Send me some emaiL: morell AT cs.atu.edu Place ComputeDate in the subject.
For BSD / OS X compatibility, you can also use the date utility with -j and -v to do date math. See the FreeBSD manpage for date. You could combine the previous Linux answers with this answer which might provide you with sufficient compatibility.
On BSD, as Linux, running date will give you the current date:
$ date
Wed 12 Nov 2014 13:36:00 AEDT
Now with BSD's date you can do math with -v, for example listing tomorrow's date (+1d is plus one day):
$ date -v +1d
Thu 13 Nov 2014 13:36:34 AEDT
You can use an existing date as the base, and optionally specify the parse format using strftime, and make sure you use -j so you don't change your system date:
$ date -j -f "%a %b %d %H:%M:%S %Y %z" "Sat Aug 09 13:37:14 2014 +1100"
Sat 9 Aug 2014 12:37:14 AEST
And you can use this as the base of date calculations:
$ date -v +1d -f "%a %b %d %H:%M:%S %Y %z" "Sat Aug 09 13:37:14 2014 +1100"
Sun 10 Aug 2014 12:37:14 AEST
Note that -v implies -j.
Multiple adjustments can be provided sequentially:
$ date -v +1m -v -1w
Fri 5 Dec 2014 13:40:07 AEDT
See the manpage for more details.
To do arithmetic with dates on UNIX you get the date as the number seconds since the UNIX epoch, do some calculation, then convert back to your printable date format. The date command should be able to both give you the seconds since the epoch and convert from that number back to a printable date. My local date command does this,
% date -n
1219371462
% date 1219371462
Thu Aug 21 22:17:42 EDT 2008
%
See your local date(1) man page.
To increment a day add 86400 seconds.
Why not write your scripts using a language like perl or python instead which more naturally supports complex date processing? Sure you can do it all in bash, but I think you will also get more consistency across platforms using python for example, so long as you can ensure that perl or python is installed.
I should add that it is quite easy to wire in python and perl scripts into a containing shell script.
date --date='1 days ago' '+%a'
It's not a very compatible solution. It will work only in Linux. At least, it didn't worked in Aix and Solaris.
It works in RHEL:
date --date='1 days ago' '+%Y%m%d'
20080807
I have bumped into this a couple of times. My thoughts are:
Date arithmetic is always a pain
It is a bit easier when using EPOCH date format
date on Linux converts to EPOCH, but not on Solaris
For a portable solution, you need to do one of the following:
Install gnu date on solaris (already
mentioned, needs human interaction
to complete)
Use perl for the date part (most unix installs include
perl, so I would generally assume
that this action does not
require additional work).
A sample script (checks for the age of certain user files to see if the account can be deleted):
#!/usr/local/bin/perl
$today = time();
$user = $ARGV[0];
$command="awk -F: '/$user/ {print \$6}' /etc/passwd";
chomp ($user_dir = `$command`);
if ( -f "$user_dir/.sh_history" ) {
#file_dates = stat("$user_dir/.sh_history");
$sh_file_date = $file_dates[8];
} else {
$sh_file_date = 0;
}
if ( -f "$user_dir/.bash_history" ) {
#file_dates = stat("$user_dir/.bash_history");
$bash_file_date = $file_dates[8];
} else {
$bash_file_date = 0;
}
if ( $sh_file_date > $bash_file_date ) {
$file_date = $sh_file_date;
} else {
$file_date = $bash_file_date;
}
$difference = $today - $file_date;
if ( $difference >= 3888000 ) {
print "User needs to be disabled, 45 days old or older!\n";
exit (1);
} else {
print "OK\n";
exit (0);
}
Looking into it further, I think you can simply use date.
I've tried the following on OpenBSD: I took the date of Feb. 29th 2008 and a random hour (in the form of 080229301535) and added +1 to the day part, like so:
$ date -j 0802301535
Sat Mar 1 15:35:00 EST 2008
As you can see, date formatted the time correctly...
HTH
If you want to continue with awk, then the mktime and strftime functions are useful:
BEGIN { dateinit }
{ newdate=daysadd(OldDate,DaysToAdd)}
# daynum: convert DD-MON-YYYY to day count
#-----------------------------------------
function daynum(date, d,m,y,i,n)
{
y=substr(date,8,4)
m=gmonths[toupper(substr(date,4,3))]
d=substr(date,1,2)
return mktime(y" "m" "d" 12 00 00")
}
#numday: convert day count to DD-MON-YYYY
#-------------------------------------------
function numday(n, y,m,d)
{
m=toupper(substr(strftime("%B",n),1,3))
return strftime("%d-"m"-%Y",n)
}
# daysadd: add (or subtract) days from date (DD-MON-YYYY), return new date (DD-MON-YYYY)
#------------------------------------------
function daysadd(date, days)
{
return numday(daynum(date)+(days*86400))
}
#init variables for date calcs
#-----------------------------------------
function dateinit( x,y,z)
{
# Stuff for date calcs
split("JAN:1,FEB:2,MAR:3,APR:4,MAY:5,JUN:6,JUL:7,AUG:8,SEP:9,OCT:10,NOV:11,DEC:12", z)
for (x in z)
{
split(z[x],y,":")
gmonths[y[1]]=y[2]
}
}
The book "Shell Script Recipes: A Problem Solution Approach" (ISBN: 978-1-59059-471-1) by Chris F.A. Johnson has a date functions library that might be helpful. The source code is available at http://apress.com/book/downloadfile/2146 (the date functions are in Chapter08/data-funcs-sh within the tar file).
If the GNU version of date works for you, why don't you grab the source and compile it on AIX and Solaris?
http://www.gnu.org/software/coreutils/
In any case, the source ought to help you get the date arithmetic correct if you are going to write you own code.
As an aside, comments like "that solution is good but surely you can note it's not as good as can be. It seems nobody thought of tinkering with dates when constructing Unix." don't really get us anywhere. I found each one of the suggestions so far to be very useful and on target.
Here are my two pennies worth - a script wrapper making use of date and grep.
Example Usage
> sh ./datecalc.sh "2012-08-04 19:43:00" + 1s
2012-08-04 19:43:00 + 0d0h0m1s
2012-08-04 19:43:01
> sh ./datecalc.sh "2012-08-04 19:43:00" - 1s1m1h1d
2012-08-04 19:43:00 - 1d1h1m1s
2012-08-03 18:41:59
> sh ./datecalc.sh "2012-08-04 19:43:00" - 1d2d1h2h1m2m1s2sblahblah
2012-08-04 19:43:00 - 1d1h1m1s
2012-08-03 18:41:59
> sh ./datecalc.sh "2012-08-04 19:43:00" x 1d
Bad operator :-(
> sh ./datecalc.sh "2012-08-04 19:43:00"
Missing arguments :-(
> sh ./datecalc.sh gibberish + 1h
date: invalid date `gibberish'
Invalid date :-(
Script
#!/bin/sh
# Usage:
#
# datecalc "<date>" <operator> <period>
#
# <date> ::= see "man date", section "DATE STRING"
# <operator> ::= + | -
# <period> ::= INTEGER<unit> | INTEGER<unit><period>
# <unit> ::= s | m | h | d
if [ $# -lt 3 ]; then
echo "Missing arguments :-("
exit; fi
date=`eval "date -d \"$1\" +%s"`
if [ -z $date ]; then
echo "Invalid date :-("
exit; fi
if ! ([ $2 == "-" ] || [ $2 == "+" ]); then
echo "Bad operator :-("
exit; fi
op=$2
minute=$[60]
hour=$[$minute*$minute]
day=$[24*$hour]
s=`echo $3 | grep -oe '[0-9]*s' | grep -m 1 -oe '[0-9]*'`
m=`echo $3 | grep -oe '[0-9]*m' | grep -m 1 -oe '[0-9]*'`
h=`echo $3 | grep -oe '[0-9]*h' | grep -m 1 -oe '[0-9]*'`
d=`echo $3 | grep -oe '[0-9]*d' | grep -m 1 -oe '[0-9]*'`
if [ -z $s ]; then s=0; fi
if [ -z $m ]; then m=0; fi
if [ -z $h ]; then h=0; fi
if [ -z $d ]; then d=0; fi
ms=$[$m*$minute]
hs=$[$h*$hour]
ds=$[$d*$day]
sum=$[$s+$ms+$hs+$ds]
out=$[$date$op$sum]
formattedout=`eval "date -d #$out +\"%Y-%m-%d %H:%M:%S\""`
echo $1 $2 $d"d"$h"h"$m"m"$s"s"
echo $formattedout
This works for me:
TZ=GMT+6;
export TZ
mes=`date --date='2 days ago' '+%m'`
dia=`date --date='2 days ago' '+%d'`
anio=`date --date='2 days ago' '+%Y'`
hora=`date --date='2 days ago' '+%H'`
In bash on macOS, I would like to write a small script with dates (or any other program that would do) that gives me a list of dates in the format yyyymmdd of every Saturday of a given year and saves it to a variable.
For example, if I wanted to have a list of dates for all Saturdays of the year 1850, it should somehow look like this:
var = [ 18500105, 18500112, 18500119, …, 18501228 ]
with the below code:
list=()
for month in `seq -w 1 12`; do
for day in `seq -w 1 31`; do
list=( $(gdate -d "1850$month$day" '+%A %Y%m%d' | grep 'Saturday' | egrep -o '[[:digit:]]{4}[[:digit:]]{2}[[:digit:]]{2}' | tee /dev/tty) )
done
done
However, the above command does not write anything in the array list although it gives me the right output with tee.
How can I fix these issues?
Modifying Dennis Williamson's answer slightly to suit your requirement and to add the results into the array. Works on the GNU date and not on FreeBSD's version.
#!/usr/bin/env bash
y=1850
for d in {0..6}
do
# Identify the first day of the year that is a Saturday and break out of
# the loop
if (( $(date -d "$y-1-1 + $d day" '+%u') == 6))
then
break
fi
done
array=()
# Loop until the last day of the year, increment 7 days at a
# time and append the results to the array
for ((w = d; w <= $(date -d "$y-12-31" '+%j'); w += 7))
do
array+=( $(date -d "$y-1-1 + $w day" '+%Y%m%d') )
done
Now you can just print the results as
printf '%s\n' "${array[#]}"
To set up the GNU date on MacOS you need to do brew install coreutils and access the command as gdate to distinguish it from the native version provided.
Argh, just realised you need it for MacOS date.
I will leave the answer for others that do not have that restriction, but it will not work for you.
This is not quite what you want, but close:
year=1850
firstsat=$(date -d $year-01-01-$(date -d $year-01-01 +%w)days+6days +%Y%m%d)
parset a 'date -d '$firstsat'+{=$_*=7=}days +%Y%m%d' ::: {0..52}
echo ${a[#]}
It has the bug, that it finds the next 53 Saturdays, and the last of those may not be in current year.
parset is part of GNU Parallel.
I didn't do much error checking, but here's another implementation.
Takes day of week and target year as arguments.
Gets the julian day of the first matching weekday requested -
gets the epoch seconds of noon on that day -
as long as the year matches what was requested, adds that date to the array and adds a week's worth of seconds to the tracking variable.
Lather, rinse, repeat until no longer in that year.
$: typeset -f alldays
alldays () { local dow=$1 year=$2 julian=1;
until [[ "$dow" == "$( date +%a -d $year-01-0$julian )" ]]; do (( julian++ )); done;
es=$( date +%s -d "12pm $year-01-0$julian" );
allhits=( $( while [[ $year == "$( date +%Y -d #$es )" ]]; do date +%Y%m%d -d #$es; (( es+=604800 )); done; ) )
}
$: time alldays Sat 1850
real 0m9.931s
user 0m1.025s
sys 0m6.695s
$: printf "%s\n" "${allhits[#]}"
18500105
18500112
18500119
18500126
18500202
18500209
18500216
18500223
18500302
18500309
18500316
18500323
18500330
18500406
18500413
18500420
18500427
18500504
18500511
18500518
18500525
18500601
18500608
18500615
18500622
18500629
18500706
18500713
18500720
18500727
18500803
18500810
18500817
18500824
18500831
18500907
18500914
18500921
18500928
18501005
18501012
18501019
18501026
18501102
18501109
18501116
18501123
18501130
18501207
18501214
18501221
18501228
I'm trying to the month number of the last Monday of this week. I got it to check what day of the week it is and if it's not Monday then subtract x days and set that new date as the variable value.
What I'm having trouble with is formatting this variable to only get the month. Everything works except the 2nd to last line below.
startDate=$(date)
weekDayNum=$(date +'%u') # 1 is Monday
# If today is NOT Monday
if [ weekDayNum > 1 ];
then
# Get the date for the last Monday
newWeekDayNum=$(($weekDayNum-1))
startDate=$(date -j -v-${newWeekDayNum}d)
fi
month=$(date -d "$startDate" +'%m')
echo $month```
[ weekDayNum > 1 ] doesn't test for numeric order. Use [ $weekDayNum -gt 10 ] (you also did not access the value of your weekDayNum variable).
It seems you have to supply the format string in the BSD variant of date:
This works for me:
#!/usr/bin/env bash
LANG=C
startDate=$(date)
weekDayNum=$(date +'%u') # 1 is Monday
# If today is NOT Monday
if [ $weekDayNum -gt 1 ];
then
startDate=$(date -j -v "-$(($weekDayNum - 1))d")
fi
month=$(date -j -f "%a %b %d %T %Z %Y" "${startDate}" +'%m')
echo $month
Use expr to convert it to number.
month=$(date -d "$startDate" +'%m')
month=$(expr $month + 0)
echo $month
Output:
8
This question already has answers here:
How can I calculate time elapsed in a Bash script?
(20 answers)
Closed 5 years ago.
I'm trying to compare a date given by a user to a date in a file, basically a text file with lots of dates and times listed.
for example the user would enter a date such as 22/08/2007 and a time of 1:00, what i need the script to do is count how many dates in the text file are after the date given by the user.
I’ve managed to accomplish this by converting each date in the text file to unix timestamp and then comparing the two. Is there no way of simply comparing two dates in bash?
Thanks in advance
The GNU date command can convert a date into the number of seconds since 1970. Try this script:
#! /bin/bash
DATE=$(date -d "$3-$2-$1 01" '+%s')
COUNT=0
tr '/' ' ' | {
while read D M Y ; do
THIS=$(date -d "$Y-$M-$D 01" '+%s')
if (( THIS > DATE )) ; then
COUNT=$((COUNT + 1))
fi
done
echo $COUNT
}
It expects three arguments and the raw dates in stdin:
for D in $(seq 19 25) ; do echo $D/08/2007 ; done | ./count.sh 22 08 2007
3
It will work till 2038. ;-)
If you don't mind an external helper tool look at my dateutils. Your use case is covered by
dgrep -i '%d/%m/%Y %H:%M' '>=2007-08-22 01:00:00' < FILE | wc -l
where FILE is your file with the dates, and -i specifies the date format used in the file (I assumed dates like 22/08/2007 01:00 here). Matching lines will be printed, hence counting them gives you the information you were after.
...why don't you simply cut out the single numbers, rearrange them from the most signifcant to the less significant, put them toghether to form a new big number and then compare the other one? :) Suppose you have a date in both $1 and $2 and suppose the date format is dd-mm-yyyy (adding hours and minutes is trivial):
d1=`echo "$1" | cut -d "-" -f 1`
m1=`echo "$1" | cut -d "-" -f 2`
y1=`echo "$1" | cut -d "-" -f 3`
date1="$y1$m1$d1"
d2=`echo "$2" | cut -d "-" -f 1`
m2=`echo "$2" | cut -d "-" -f 2`
y2=`echo "$2" | cut -d "-" -f 3`
date2="$y2$m2$d2"
if [ "$date1" -gt "$date2" ]; then
#date1 > date2
else
#date2 >= date1
fi
Note that you need zeros for 1-digit fields, for example, dates like this will work:
01-01-2013
and dates like this will NOT
1-1-2013
Cheers :-)
The problem is that dates are printed in such a way that, string-wise, "1/1/2050 1:00" < "2/1/1999 0:00". And since there's no way for a script to know that something is a datetime without you saying so, you essentially have to convert any date to something that can be compared - Either you have to order the elements so that the most important (year) are first, etc. (like ISO dates) or you convert to a number.
the above command compares the date in form of integer and would work fine until you are comparing the dates of same year.
better idea is to break the dates into 3 parts of dd, mm and yyyy and then do a comparison. just as below:
sysdate=`date +%d%m%Y`
sys_dd=`echo $sysdate|cut -c1,2`
sys_mm=`echo $sysdate|cut -c3,4`
sys_yyyy=`echo $sysdate|cut -c5-8`
cd $dir_source #moving in directory where report are placed
for i in *.* #reading all the files present in directory and comparing with current sysdate
do
filename=$i
filedate=`echo $filename| cut -d '_' -f1`
file_dd=`echo $filedate|cut -c1,2`
file_mm=`echo $filedate|cut -c3,4`
file_yyyy=`echo $filedate|cut -c5-8`
if [ $sys_yyyy -lt $file_yyyy ]
then
echo "future cob file, check for the error"elif [ $sys_yyyy -gt $file_yyyy ]
then
echo "prev cob file , to be removed"
else
if [ $sys_mm -lt $file_mm ]
then
echo "future cob file, check for the error"
elif [ $sys_mm -gt $file_mm ]
then
echo "prev cob file , to be removed"
else
if [ $sys_dd -lt $file_dd ]
then
echo "future cob file, check for the error"
elif [ $sys_dd -gt $file_dd ]
then
echo "prev cob file , to be removed"
else
echo "file date is same is cob date, retaining the file as it is"
fi
fi
fi