Within a Linux shell script to be used as a cron job, how do I calculate the current date, the current date – n days, and the current date + n days ?
This script is to be used to partition an oracle database, and automatically drop old partitions (n+1) and create a new partition (n+1).
While I’m sure this will amaze some people, I am going to actually take the daring step of suggesting that date math is rather difficult to accomplish in many Unix and Linux shell script environments, and it’s much easier to actually write a brief C program to accomplish your task. Yes, I’ve written one of the most popular shell script programming books ever written (Wicked Cool Shell Scripts), but even I don’t think that you can solve every problem with a script! 🙂
There’s a version of the date command (commonly known as GNU date) that allows some rudimentary mathematical calculations, but that’s only useful if you have the more sophisticated version of this particular utility.
Instead, the key to working with date math on a Unix system is to remember that Unix (and Linux) work on what’s called epoch time, the number of seconds since a specific date back in 1970. As I write this entry, for example, the “epoch time” is 1131948327.
Once you have that, and a utility that turns epoch time into a nice, readable date, it’s easy enough to add or delete days: just add or subtract 60*60*24 seconds for each day in question. Here it is as a C utility:
/** DATEMATH - Demonstration program that shows how to do date mathematics by utilizing Unix procedures. This is missing some error checking, etc., but will show how to do "+n" and "-n" date math well enough. (C) Copyright 2005 by Dave Taylor. Free to redistribute if this copyright is left intact. Thanks. ***/ #include <stdio.h> #include <time.h> #define ONEDAY 60*60*24 time_t time(time_t *tloc); char *ctime(const time_t *clock); main(int argc, char **argv) { time_t theTime; int offset = 1; if (argc > 1) offset = atoi(argv[1]); theTime = time((time_t *) NULL); theTime += (long) (offset * ONEDAY); printf("Offset by %d, the date is: %s\n", offset, ctime(&theTime)); }
As you can see, the utility grabs the current epoch time by using the time() function, adds any days necessary by multiplying the requested offset by the seconds in a day ONEDAY, then uses the ctime() function to output a standard date string.
Here’s the utility at work:
$ ./a.out +41 Offset by 41, the date is: Sun Dec 25 06:16:44 2005 Yes, that means there are only 41 shopping days until Christmas! $ ./a.out -41 Offset by -41, the date is: Tue Oct 4 06:16:50 2005 $ ./a.out -5000 Offset by -5000, the date is: Sat Mar 7 06:16:55 1992 $ ./a.out -999 Offset by -999, the date is: Wed Feb 19 06:17:13 2003 $ ./a.out 365 Offset by 365, the date is: Tue Nov 14 06:17:28 2006
I hope that helps you out!
What a great thread! Thank you!
To find files older than a specific date and time, you can try the following. No Special date command needed.
touch -t CCYYmmddHHMM.SS /tmp/ref-file.txt
find /dir -type f -not -newer /tmp/ref-file.txt -print
To find files between a range of date+time, try following will work.
#– Start date
touch CCYYmmddHHMM.SS /tmp/ref-start-file.txt
#–
touch CCYYmmddHHMM.SS /tmp/ref-end-file.txt
find /dir -type f -print \
| while read FILE
do
if [ “$FILE” -nt /tmp/ref-start-file.txt -a “$FILE” -ot /tmp/ref-end-file.txt ] ; then
echo “$FILE”
fi
done
Here is a “find” script that removes all CSV files over 30 days old in directory /tss/live:
for i in `find /tss/live -name “*.csv” -type f -mtime +30 -exec ls {} \;`
do
echo $i
rm $i
done
If all you want is todo is to find files older than X date and to do somethign with them, then you might want to try the “find” command. This cmmand will remove core dumps for example:
find . -name “core” -type f -exec rm -f {} \;
For those that do not have a “smart” date utility — -you may have an alternative utility loaded on your system. Some AIX servers have a smart date utility loaded here:
/opt/freeware/bin/date
I can use that utility to determine which file is older like this:
#### compare date
integer now
now=`/opt/freeware/bin/date +%s`
integer expires
expires=`/opt/freeware/bin/date -d “$expidt” +%s`
if [[ $expires -lt $now ]] ; then
echo “expired”
fi
How do you write a script to change all the files on a Red Hat Linux System that have a 2012 timestamp to 2011 timestamp ? Something happen during the installation of a Linux
Server to set all files to 2012.
Thanks,
Mariano
In response to your emailed questions, a coworker whipped up something that takes the day and number input and prints out the last 25 years’ or so worth of possible matches. Probably you should either just go all the way back to the beginning of Unix time in 1970, or have options for range of years, or newest only, or else leave that for processing by piping to “head” or whatever.
% years4weekday.pl Fri Feb 25
Looking for the Day of Week Fri for the Month of Feb and Day of 25
FOUND Fri Feb 25, 2010
FOUND Fri Feb 25, 1999
FOUND Fri Feb 25, 1993
FOUND Fri Feb 25, 1988
–Bruce
In response to your emailed questions, a coworker whipped up something that takes the day and number input and prints out the last 25 years’ or so worth of possible matches. Probably you should either just go all the way back to the beginning of Unix time in 1970, or have options for range of years, or newest only, or else leave that for processing by piping to “head” or whatever.
% years4weekday.pl Fri Feb 25
Looking for the Day of Week Fri for the Month of Feb and Day of 25
FOUND Fri Feb 25, 2010
FOUND Fri Feb 25, 1999
FOUND Fri Feb 25, 1993
FOUND Fri Feb 25, 1988
–Bruce
Many Unix commands (e.g. “last”) and log files show brain-dead date strings such as “Thu Feb 24”. Does anybody out there have a script that will convert that to a year, given a 5-year interval and defaulting to the present?
Many Unix commands (e.g. “last”) and log files show brain-dead date strings such as “Thu Feb 24”. Does anybody out there have a script that will convert that to a year, given a 5-year interval and defaulting to the present?
any ideas on the pulling a range of dates?
I am trying to figure out how to create a bash script to pull from a range of dates from the directory listing using date modified (ex. 6:00 AM March 3 through 6:00 AM March 8 – not using hard coded date/time) and put those file names in a new file to process later.
# // If it can help you: sample of code to inquire the modification date of a file and then to show you this date plus 1 minute
# // “My timezone”
mytz=”$(date +%z)”
difference_with_GMT=0
difference_with_GMT${mytz:0:1}=3600*${mytz:1:2}+60*${mytz:3:2}
# “date” assumes the input is in the local timezone, however epoch time is given in GMT, so we have to add “$difference_with_GMT”
date_with_one_minute_added=”$(date -d “1970-01-01 $(($(stat -c %Y “$file”) + $difference_with_GMT + 60)) sec” “+%Y-%m-%d %H:%M:%S”)”
I want to obtain the number of seconds from the present time to say 2 AM the following day. Could you help me with a suitable script. I need this to set a sleep option to reboot my modem at the next 2 AM. My ISP provides free browsing between 2 AM and 8 AM but they insist that the subscriber should re-login.
thanks Dave
I am not able to use sqrt function in linux shell script .
Is there any way to use mathematical functions such as decimal calculation in linux scripting.
I am having difficulty trying to write a bash shell script to calculate the age of a user who inputs month and year of birth. Having trouble using the date command to calculate the age of user by using user input. Any help in regards to this issue would be greatly appreciated. Thanks
Write a script to list only the files in a directory which have been modified on or after a specified time. The script should take date and time (up to minutes) as input from command line in the format Mon dd min:sec, for example Oct 10 17:52 .
thanks Donovan Young..your code is very useful to learn how to develop my script. Thanks a lot!
Thanks a lot! I try them, Peter’s one is fine.
https://www.askdavetaylor.com/date_math_in_linux_shell_script.html
Ok, I guess this really never got posted then…
I found an EASIER SOLUTION. =)
Though I would like to do “Date math in Linux shell script”, my application is simply to test which file is newer than another based on “Access date”. Note that there bash has the “-nt” newer than conditional test for files (e.g. file1 -nt file2), but the bash man page says that is according to the modification date. [del] –When I append a line to a file (e.g. cat “# my final comment” >> file2) and run the “stat” command, I see that only the “Access date” has changed – not the modification date.–[/del] correction: The “Modification” and “Change” dates are updated!
Fortunately, there is an option in the “stat” command to return the MAC timestamps of a file in epoch time where Access/Modify/Change are given in format %X/%Y/%Z.
Usage:
$ stat -c %X file2
1200496284
Now you can easily perform mathematical operations in seconds and date time stamp comparisons (e.g. is file1 > 10 secs newer than file2?) without ANY parsing and extra computation! I’m not sure that the “stat” command is standard on every Unix/BSD/Linux platform – but it should be available as a package!
Oops! In my previous post I meant to say that the “Modification” and “Change” date time stamps change when new content is echoed to the end of the file. It is the “Access” date that remains unchanged. Nevertheless, if one wanted to measure “Access” time they could do it easily with the “stat” command.
I think any algorithm that does arithmetic on the epoch time will have problems crossing daylight savings boundary as days on these boundaries are not 24 hours (in terms of number of seconds from 2am to 2am). The TZ trick might also have issues with daylight savings. So you might want to use Hugh’s code, fixed up to handle month boundaries as suggested by Kevin.
Hugh: Your “cal” trick is nice but your math for the previous day and month breaks pretty badly around Jan 1st. I’d suggest using the TZ trick to set DAYLESS and YEAR properly for the target date and then you can use cal to count the number of days in that month.
Here is something I found that gets the job done. Using some shell commands in Linux or Unix to write a script that calculated the previous or future date or any date for that matter using the “date” and “cal” command.
Taking in account that the previous date to the first of any month is the last day of the previous month. “cal” does a good enough job to give you the days of any month for any year since Jan 1 1970.
I used the following command in my scripts to get the dates I need to use in variables:
Get the current numerical day
DAY=`date +%d`
Get the current numerical month
MTH=`date +%m`
Get the current numerical year
YEAR=`date +%Y`
Get the previous numerical date (or replace 1 with the number of day to count back)
DAYLESS=”$((`date +%d`-1))”
Get the previous numerical month (or replace 1 with the number of months to count back)
MTHLESS=”$((`date +%m`-1))”
Get the last numerical date of the previous month
MTHEND=”$((`cal $MTHLESS $YEAR|wc -w`-9))”
Thanx peter for the info … the command date -d “-1 day” .. was really helpful
A shell only possibility for calculating dates in the future or the past.
date –date @$(($(date +%s)-(3600*24*2))) +%Y/%m/%d
Tested with bash and zsh.
Regarding all questions about why the time is off by hour(s): date assumes the input is in the local timezone, however epoch time is given in GMT.
The fix is extremely simple. Assuming your epoch value is stored in $time
#First, we get our local timezone offset, i.e. +0130
mytz=”`date +%z`”
#Then we dissect, using the +/- in the adjustment to the epoch, and then the 01 and 30 pieces multiplied by 3600 and 60 seconds respectively to come up with the proper offset in seconds.
let time${mytz:0:1}=3600*${mytz:1:2}+60*${mytz:3:2}
Hey guys, what about this 😉 ?
———————————————-
#!/bin/bash
# Call me this way: epochtodate EPOCH FORMAT
# Where EPOCH is an epoch-formatted date
# and FORMAT is the +%… date format.
# If FORMAT=””, then +%c
# Quote space-containing FORMAT.
dateformat=$2
[ -z “$dateformat” ] && dateformat=”+%c”
date -d “1970-01-01 $1 sec” “$dateformat”
————————————————-
: w epochtodate
> epochtodate 1167001200
But why 1 hour late?
In the above example of date operation using EPOCH, the resultant date will be set as per GMT time zone. e.g. if we use epoch value, it will return seconds since 1970, Jan 01 GMT value. now if my machine time zone is different than GMT i.e. IST(+0530), then to set correct time I have to add addition seconds for that particular time zone. Is there any command which can convert or give extra seconds that I need to add or subtract to get actual time? Thanks…
#!/bin/bash
# function to reproduce UNIX time() value
second()
{
set — $(TZ=GMT date ‘+%Y %m %d %H %M %S’)
local y=$1 m=1$2 d=1$3 H=1$4 M=1$5 S=1$6
((m-=103, d-=101, H-=100, M-=100, S-=100, m<0 && (m+=12, –y)))
((d+=(m*153 + 2)/5+y*365+y/4-y/100+y/400-719468))
echo $((d*86400+H*3600+M*60+S))
}
Epoch time is the amount of time since a fixed point in the history of Unix itself, and that’s what’s displayed when you specify the ‘-r’ flag to date followed by a number of seconds offset. For example:
$ date -r 0
Wed Dec 31 17:00:00 MST 1969
Zero seconds after the epoch time is the date and time shown above, basically 0:00:00 GMT on 31 December 1969.
Why that time and date? Just … because. 🙂
A few more, for fun:
$ date -r 999999999
Sat Sep 8 19:46:39 MDT 2001
$ date -r 1999999999
Tue May 17 21:33:19 MDT 2033
This site is very Good.
Plz let me know what is epoch time in Linux
The following code will convert any given date to any offset using only GNU compliant ksh (e.g. bash) script tools.
Note: This is not 100% Unix portable since it only works if the date supports the non-RFC compliant “–d��? option. GNU does (i.e. Linux) but I doubt it will work on older HP-UX or Solaris systems (but hey, try it and see — ymmv)
The basic algorithm uses the fact that the ‘date’ command can output the “seconds since epoch��? as well as be provided a date other than “now��? with the –d option (see above disclaimer). So, if we give it a specific date, and request the output in %s we get the “seconds since the epoch��? for the date we’ve supplied. Now, we apply our offset (+ or -) and resubmit the date command with the modified epoch time.
—– Start of Script —–
#!/bin/bash
# The date we want to convert
# can be any valid date format – I like this one myself
ORIGINAL_DATE=”20060401″
# provide the original date instead of “now” and ask for “seconds since epoch”
ORIGINAL_EPOCH=$(date -d “${ORIGINAL_DATE}” “+%s”)
# Now apply our offset, in this example we subtract 1 day
# Remember that $((.)) does arithmetic in bash (aka ksh)
# and can be nested. *hug gnu*
NEW_EPOCH=$((${ORIGINAL_EPOCH} – $((60 * 60 * 24))))
# We now have our target date in EPOCH format, we need to get it back to our
# original format.
NEW_DATE=$(date -d “1970-01-01 ${NEW_EPOCH} sec” “+%Y%m%d”)
# And there we have it! $NEW_DATE contains “20060331”
# For those that like a tidyier script (like me)
# here’s a sample of the above in one line.
NEW_DATE=$(date -d “1970-01-01 $(($(date -d “${ORIGINAL_DATE}” “+%s”) – $((60 * 60 * 24)))) sec” “+%Y%m%d”)
echo “ORIGINAL_DATE = $ORIGINAL_DATE”
echo “NEW_DATE = $NEW_DATE”
—– End of Script —–
prints out:
ORIGINAL_DATE = 20060401
NEW_DATE = 20060331
There you have it. Enjoy!
Thanks, Peter, but realize that what you’re referencing isn’t a flag that’s available in all versions of Unix / Linux…
Try running these, see what you get:
date -d “-1 day”
date -d “+1 day”
date -d “1 year”
Sorry, that example should have been:
OFFSET_DATE=$(TZ=$TZ+$((DAYS * 24)) date)
The offset can be positive or negative. The $(…) is command substitution and the $((…)) is arithmetic substitution. You may have to quote the asterisk.
In a former job I once wrote scripts that did date arithmetic entirely in Korn shell, and I expect the method would translate into other shell languages as well. I no longer have access to the scripts and no convenient access to a Unix/Linux system to reconstruct them, but I will describe the method and leave the details up to the reader. This requires an understanding of certain facts about Unix (about shells, actually) and the “date” command.
First, the “date” command computes the human-readable date output based on the timezone specified in the environment (something like TZ= or TIMEZONE=). You can see which your system uses by looking in /etc/TIMEZONE or the like.
Second, the TZ= (or whatever) variable supports different formats for the timezone, including the ability to specify positive or negative offsets from some timezone (something like EDT-1 or GMT+6).
Third, you may already have realized that if the displacement is a multiple of 24 hours, then the date can be shifted by that number of days.
Next, it is very easy to specify a change in a command’s environment only for the duration of the command. When used with the “date” command, this allows you to change the timezone of the date returned; for example, “TZ=$TZ-2 date” (or the like; ignore the quotes) will show you the date and time two hours displaced.
Finally, shells can do arithmetic. Dumb old shells use the “expr” command, newer ones do it directly. I like the Korn shell method. So to pull it all together, if you treat the following as metacode and not gospel, something like this should work for you:
OFFSET_DATE=$(TZ=$TZ+$((TZ * 24)) date)
Of course you can use the entire range of “date” command options to pull out any particular item of a date you wish. And if you really like a challenge, you can even control the date shift down to the minute or second, allowing you to force outputs for some particular time of day, say noon for example. Have fun and happy scripting!