Linux date command, finding seconds to next hour - linux

How do I find the seconds to the next hour using date? I know I can do
date -d "next hour"
but that just adds 1 hour to the present time. I want it to show the seconds to the next full hour. For example if the current time is 9:39am I want to find the number of seconds to 10am

The epoch timestamp of right now is
now=$(date '+%s')
That of the next hour is
next=$(date -d $(date -d 'next hour' '+%H:00:00') '+%s')
The number of seconds until the next hour is
echo $(( next - now ))
For a continuous solution, use functions:
now() { date +%s; }
next() { date -d $(date -d "next ${1- hour}" '+%H:00:00') '+%s'; }
And now you have
echo $(( $(next) - $(now) ))
and even
echo $(( $(next day) - $(now) ))
Another way
Another slightly mathier approach still uses the epoch timestamp. We know it started on an hour, so the timestamp mod 3600 only equals zero on the hour. Thus
$(( $(date +%s) % 3600 ))
is the number of seconds since the last hour, and
$(( 3600 - $(date +%s) % 3600 ))
is the number of seconds until the next.

Well, there's always the straightforward mathy way:
read min sec <<<$(date +'%M %S')
echo $(( 3600 - 10#$min*60 - 10#$sec ))
EDIT: removed race condition, added explicit radix. Thanks, #rici and #gniourf_gniourf.

With Bash≥4.2 you can use printf with the %(...)T modifier to access dates (current date corresponds to argument -1, or empty since version 4.3):
printf -v left '%(3600-60*10#%M-10#%S)T' -1
echo "$((left))"
Pure Bash and no subshells!
The 10# is here to ensure that Bash's arithmetic (expanded in the $((...))) treats the following number in radix 10. Without this, you'd get an error if the minute or second is 08 or 09.

Try doing this :
LANG=C
now=$(date +%s)
next="$(date |
perl -pe 's/(\d{2}):\d{2}:\d{2}/sprintf "%.2d:00:00", $1 + 1/e')"
next=$(date -d "$next" +%s)
echo $(( next - now ))
OUTPUT :
2422

#!/usr/bin/env bash
is=`date +%S`
im=`date +%M`
echo $((((60-$im)*60)+(60-$is)))

Related

Calculate Timeout Value to Time

I have script that I kill using the timeout command. Right now I set a timeout value by hand so that it stops before 9 AM. I am trying to come up with a bash command using date that will when run calculate how many hours left till 9 AM. I can't find the syntax for date -d next 09:00, command may run before 9AM same day or after 9AM so it runs till next day?
Updated answer, for my original answer, please check the history and comments.
Based on this linked post, lets add some logic to check the next 09AM is today, or tomorrow to get the desired output:
#!/bin/bash
# Current hour
H=$(date +%-H)
# If today is before 9am
# Set target to 'upcoming 9am'
# Otherwise, set to 'tomorrow 9'
(( $H < 9 )) && target=$(date -d '9' '+%s') || target=$(date -d 'tomorrow 9' '+%s')
# Get diffs in hours
current=$(date +%s)
hours=$(((target - current) / 60 / 60))
# Result
echo "Current GMT Time: $(date '+%Y-%m-%d %H:%M')"
echo "$hours hours until tomorrow 09:00:00"
Current GMT Time: 2021-05-27 13:20
19 hours until tomorrow 09:00:00
Try it online!
This one-liner worked for me to return seconds -
echo $(( $(date +%s -d "9am tomorrow") - $(date +%s) ))
so you could use that value for a sleep, and it's pretty easy to calculate hours/minutes/seconds with modulo division if you need it.
At 2021-05-27,08:15:53 -
$: secsTill9amTomorrow=$(( $(date +%s -d "9am tomorrow") - $(date +%s) ))
$: echo $secsTill9amTomorrow
88814
$: hrsAsSecs=$(( secsTill9amTomorrow - ( secsTill9amTomorrow % 3600 ) ))
$: echo $hrsAsSecs
86400
$: hrsTo9=$(( hrsAsSecs / 3600 ))
$: echo $hrsTo9
24
$: minsAsSecs=$(( secsTill9amTomorrow - hrsAsSecs ))
$: echo $minsAsSecs
2414
$: mins=$(( minsAsSecs / 60 ))
$: echo $mins
40
$: secs=$(( minsAsSecs % 60 ))
$: echo $secs
14
$: echo "H:M:S - $hrsTo9:$mins:$secs"
H:M:S - 24:40:14

Time since last update of a file in human readable format

I want to get the time since the last update of a file in a human readable format. On the Internet I found:
now=$(date +%s) # to get timestamp of now
last=$(stat -c %Y /var/cache/apt/) # to get the timestamp of the last update
But now I want to divide now with last and print it in a human readable format in Debian shell?
You can write a function:
time_since() {
file=$1
now=$(date +%s)
file_time=$(stat -c %Y "$file")
seconds=$((now - file_time))
hh=$(( $seconds / 3600 ))
mm=$(( ($seconds - $hh * 3600) / 60 ))
ss=$(( $seconds - $hh * 3600 - $mm * 60 ))
printf '%d:%02d:%02d\n' $hh $mm $ss
}
time_since /var/cache/apt
# output => 332:17:07
Using the bash & printf:
time_since() {
eval "$(stat -s "$1")" # st_mtime=1490029104
TZ=UTC printf "%(%T)T\n" $(( $(printf "%(%s)T") - st_mtime ))
}
time_since file.txt
#out -> 02:07:02
Some duration value (time difference in seconds) is the same as the epoch time. (e.g. the difference in seconds from the epoch (UTC))

Iterating over dates with a bash loop in bash -- for (( ... )) not working correctly

I am trying to create a list of dates. This script below works with simple dates with no spaces.
datestart=20130601
dateend=20130705
for (( date1="$datestart"; date1 != dateend; )); do
date1="$(date --date="$date1 + 1 days" +'%Y%m%d')";
echo $date1;
done
When I use a data string like (which contains WHITE SPACE) datestart="2013-06-01 00:00:00" and a date format like +'Y-%m-%d %H:%M:%S'
datestart="2013-06-01 00:00:00"
dateend="2013-07-05 00:00:00"
for (( date1="$datestart"; date1 != "$dateend"; )); do
date1="$(date --date=""$date1" + 1 days" +'%Y-%d-%m %H:%M:%S')";
echo "$date1";
done
I get the following error:
-bash: ((: date1=2013-06-01 00:00:00: syntax error in expression (error token is "00:00:00")
I think I am NOT quoting my variables correctly. I have twiddled and fiddled, and now I am here. How do I quote the variables correctly in a for loop?
This should work for you:
datestart="2013-06-01 00:00:00"
dateend="2013-07-05 00:00:00"
date1="$datestart"
while [[ "$date1" != "$dateend" ]]; do
date1="$(date -u --date="$date1 tomorrow" '+%Y-%m-%d %H:%M:%S')"
echo "$date1"
done
Working Demo
((...)) is used for arithmetic operations only. Use while loop instead.
No need to use nested quotes for $date
Use tomorrow to get next date
Use correct year-month-date format while assigning next date to date1
No way, bash three-expression for loop expects aritmetic expressions. See this link:
http://wiki.bash-hackers.org/syntax/ccmd/c_for
use a classic "while" loop instead.
Addendum
You can use seconds from epoch to allow arithmetics, like in:
datestart=$(date --date="2013-06-01 00:00:00" +%s)
dateend=$(date --date="2013-07-05 00:00:00" +%s)
for (( date1=$datestart; date1 != $dateend; date1+=86400 )); do
date --date=#$date1
done
but care with days that has not 86400 seconds.
You're right about your quotes being off. You had:
date1="$(date --date=""$date1" + 1 days" +'%Y-%d-%m %H:%M:%S')";
but you're doing in and out of your double quotes in very weird ways with this. A quick solution might be to switch to single quotes, which do not isolate variables if they are inside double quotes:
date1="$(date --date="'$date1' + 1 days" +'%Y-%d-%m %H:%M:%S')";
As for the other part ... let's just review what's on the bash man page:
for (( expr1 ; expr2 ; expr3 )) ; do list ; done
First, the arithmetic expression expr1 is evaluated according to
the rules described below under ARITHMETIC EVALUATION. ...
And if you check the ARITHMETIC EVALUATION section of the man page, you'll see that it does not include the sorts of tests that /bin/test or [[ ... ]] can run. Those are covered in the next section of the man page, CONDITIONAL EXPRESSIONS.
If you want to use a for loop, then #pasaba's suggestion to use epoch seconds is what I'd go with also, in order to stick with arithmetic. Something like this:
#!/bin/bash
datestart="2013-06-01 00:00:00"
dateend="2013-07-05 00:00:00"
e_start=$(date -d "$datestart" '+%s')
e_end=$(date -d "$dateend" '+%s')
for (( date1=$e_start; date1 < $e_end; date1+=86400 )); do
echo -n "$date1 "; date -d "#$date1" '+%Y-%m-%d'
done
To account for leap years and leap seconds and the like, you can put your trust in the Linux date command, and evaluate $date1 in the loop as you originally did:
for (( date1=$e_start; date1 < $e_end; )); do
date1=$(date -d "$(date -d "#$date1") + 1 day" '+%s')
echo -n "$date1 "; date -d "#$date1" '+%Y-%m-%d'
done
The nested date commands are required because Linux's date command doesn't allow you to use relative dates ("+1day" or "tomorrow") when the origin date is specified as an epoch with #. (I'd love to know if I'm wrong about that.)
I realize that your question is tagged "Linux", but I'll note for future searches that this is a non-portable (Linux-only) use of the date command, so if you want this script to run in FreeBSD, NetBSD, OSX, etc, you'll need to review their usage. The following works in FreeBSD:
#!/usr/bin/env bash
datestart="2015-06-01 00:00:00"
dateend="2015-07-05 00:00:00"
e_start="$(date -jf '%Y-%m-%d %T' "$datestart" '+%s')"
e_end="$(date -jf '%Y-%m-%d %T' "$dateend" '+%s')"
for (( date1 = $e_start; date1 < $e_end; )); do
date1=$(date -j -v+1d -f '%s' "$date1" '+%s')
echo -n "$date1 "; date -jf '%s' "$date1" '+%Y-%m-%d'
done

Get the number of days since file is last modified

I want to get the number of days since file last modified date to today's date.
I use this $ ls -l uname.txt | awk '{print $6 , "", $7}' but it gives me the last modified date. I want to know the number of days from a last modified date to today's date.
Any way to do this?
Instead of using ls, you can use date -r to tell you the modification date of the file. In addition to that, date's %s specifier, which formats the date in seconds since the epoch, is useful for calculations. Combining the two easily results in the desired number of days:
mod=$(date -r uname.txt +%s)
now=$(date +%s)
days=$(expr \( $now - $mod \) / 86400)
echo $days
Try creating a script:
#!/bin/bash
ftime=`stat -c %Y uname.txt`
ctime=`date +%s`
diff=$(( (ctime - ftime) / 86400 ))
echo $diff
You could wrap up the differences of GNU and BSD stat with some BASH math and basic readable API:
since_last_modified() {
local modified
local now=$(date +%s)
local period=$2
stat -f %m $1 > /dev/null 2>&1 && modified=$(stat -f %m $1) # BSD stat
stat -c %Y $1 > /dev/null 2>&1 && modified=$(stat -c %Y $1) # GNU stat
case $period in
day|days) period=86400 ;; # 1 day in seconds
hour|hours) period=1440 ;; # 1 hour in seconds
minute|minutes) period=60 ;; # 1 minute in seconds
*) period= ;; # default to seconds
esac
if [[ $period > 0 ]]; then
echo "$(( (now - modified) / period ))"
else
echo "$(( now - modified ))"
fi
}
Basic usage of seconds since last modification:
since_last_modified uname.txt
or minutes saved to a variable
minutes_since=$(since_last_modified uname.txt minutes)

Bash script to calculate time elapsed

I am writing a script in bash to calculate the time elapsed for the execution of my commands, consider:
STARTTIME=$(date +%s)
#command block that takes time to complete...
#........
ENDTIME=$(date +%s)
echo "It takes $($ENDTIME - $STARTTIME) seconds to complete this task..."
I guess my logic is correct however I end up with the following print out:
"It takes seconds to complete this task..."
Anything wrong with my string evaluation?
I believe bash variables are untyped, I would love if there is a "string to integer" method in bash nevertheless.
I find it very clean to use the internal variable "$SECONDS"
SECONDS=0 ; sleep 10 ; echo $SECONDS
Either $(()) or $[] will work for computing the result of an arithmetic operation. You're using $() which is simply taking the string and evaluating it as a command. It's a bit of a subtle distinction.
As tink pointed out in the comments on this answer, $[] is deprecated, and $(()) should be favored.
You are trying to execute the number in the ENDTIME as a command. You should also see an error like 1370306857: command not found. Instead use the arithmetic expansion:
echo "It takes $((ENDTIME - STARTTIME)) seconds to complete this task..."
You could also save the commands in a separate script, commands.sh, and use time command:
time commands.sh
You can use Bash's time keyword here with an appropriate format string
TIMEFORMAT='It takes %R seconds to complete this task...'
time {
#command block that takes time to complete...
#........
}
Here's what the reference says about TIMEFORMAT:
The value of this parameter is used as a format string specifying how the timing information for pipelines prefixed with the time
reserved word should be displayed. The ‘%’ character introduces an
escape sequence that is expanded to a time value or other information.
The escape sequences and their meanings are as follows; the braces
denote optional portions.
%%
A literal ‘%’.
%[p][l]R
The elapsed time in seconds.
%[p][l]U
The number of CPU seconds spent in user mode.
%[p][l]S
The number of CPU seconds spent in system mode.
%P
The CPU percentage, computed as (%U + %S) / %R.
The optional p is a digit specifying the precision, the number of fractional digits after a decimal point. A value of 0 causes no
decimal point or fraction to be output. At most three places after the
decimal point may be specified; values of p greater than 3 are changed
to 3. If p is not specified, the value 3 is used.
The optional l specifies a longer format, including minutes, of the form MMmSS.FFs. The value of p determines whether or not the
fraction is included.
If this variable is not set, Bash acts as if it had the value
$'\nreal\t%3lR\nuser\t%3lU\nsys\t%3lS'
If the value is null, no timing information is displayed. A trailing newline is added when the format string is displayed.
For larger numbers we may want to print in a more readable format. The example below does same as other but also prints in "human" format:
secs_to_human() {
if [[ -z ${1} || ${1} -lt 60 ]] ;then
min=0 ; secs="${1}"
else
time_mins=$(echo "scale=2; ${1}/60" | bc)
min=$(echo ${time_mins} | cut -d'.' -f1)
secs="0.$(echo ${time_mins} | cut -d'.' -f2)"
secs=$(echo ${secs}*60|bc|awk '{print int($1+0.5)}')
fi
echo "Time Elapsed : ${min} minutes and ${secs} seconds."
}
Simple testing:
secs_to_human "300"
secs_to_human "305"
secs_to_human "59"
secs_to_human "60"
secs_to_human "660"
secs_to_human "3000"
Output:
Time Elapsed : 5 minutes and 0 seconds.
Time Elapsed : 5 minutes and 5 seconds.
Time Elapsed : 0 minutes and 59 seconds.
Time Elapsed : 1 minutes and 0 seconds.
Time Elapsed : 11 minutes and 0 seconds.
Time Elapsed : 50 minutes and 0 seconds.
To use in a script as described in other posts (capture start point then call the function with the finish time:
start=$(date +%s)
# << performs some task here >>
secs_to_human "$(($(date +%s) - ${start}))"
Try the following code:
start=$(date +'%s') && sleep 5 && echo "It took $(($(date +'%s') - $start)) seconds"
This is a one-liner alternative to Mike Q's function:
secs_to_human() {
echo "$(( ${1} / 3600 ))h $(( (${1} / 60) % 60 ))m $(( ${1} % 60 ))s"
}
try using time with the elapsed seconds option:
/usr/bin/time -f%e sleep 1 under bash.
or \time -f%e sleep 1 in interactive bash.
see the time man page:
Users of the bash shell need to use an explicit path in order to run
the external time command and not the shell builtin variant. On system
where time is installed in /usr/bin, the first example would become
/usr/bin/time wc /etc/hosts
and
FORMATTING THE OUTPUT
...
% A literal '%'.
e Elapsed real (wall clock) time used by the process, in
seconds.
Combining internal variable "$SECONDS" in Lon Kaut's answer with ssc's one-liner under Internal Server Error's answer:
SECONDS=0
sleep 2s
echo "Elapsed time: $((SECONDS/3600))h $(((SECONDS/60)%60))m $((SECONDS%60))s"
($/${} is unnecessary on arithmetic variables)
This is an old post and it seems that everybody like writing too much code here. But actually, you only need 3 lines of simple bash code in order to you can show properly the elapsed time in a well-formatted way.
START_TIME=$(date +%s)
# put your code here
ELAPSED=$(($(date +%s) - START_TIME))
printf "elapsed: %s\n\n" "$(date -d#$ELAPSED -u +%H\ hours\ %M\ min\ %S\ sec)"
This produces the following result:
elapsed: 0 hours 2 min 19 sec
Test the code:
START_TIME=$(date +%s)
ELAPSED=86399
printf "elapsed: %s\n\n" "$(date -d#$ELAPSED -u +%H\ hour\ %M\ min\ %S\ sec)"
elapsed: 23 hour 59 min 59 sec
If you would like to show the days as well, then you need to do a little trick in order for the day will be displayed properly:
ELAPSED=86399
printf "elapsed: %s\n\n" "$(date -d#$ELAPSED -u +%d\ days\ %H\ hour\ %M\ min\ %S\ sec)"
elapsed: 01 days 23 hour 59 min 59 sec
ELAPSED=86400
elapsed: 02 days 00 hour 00 min 00 sec
As you can see the day is calculated on a wrong way, so you need to do a math operation in bash this way:
printf "elapsed: %s day %s\n\n" "$(($(date -d#$ELAPSED -u +%d)-1))" "$(date -d#$ELAPSED -u +%H\ hour\ %M\ min\ %S\ sec)"
elapsed: 1 day 00 hour 00 min 00 sec
The \ character in the date-time pattern escapes the whitespace.
I hope that it helps you.
start=$(date +%Y%m%d%H%M%S);
for x in {1..5};
do echo $x;
sleep 1; done;
end=$(date +%Y%m%d%H%M%S);
elapsed=$(($end-$start));
ftime=$(for((i=1;i<=$((${#end}-${#elapsed}));i++));
do echo -n "-";
done;
echo ${elapsed});
echo -e "Start : ${start}\nStop : ${end}\nElapsed: ${ftime}"
Start : 20171108005304
Stop : 20171108005310
Elapsed: -------------6
#!/bin/bash
time_elapsed(){
appstop=$1; appstart=$2
ss_strt=${appstart:12:2} ;ss_stop=${appstop:12:2}
mm_strt=${appstart:10:2} ;mm_stop=${appstop:10:2}
hh_strt=${appstart:8:2} ; hh_stop=${appstop:8:2}
dd_strt=${appstart:6:2} ; dd_stop=${appstop:6:2}
mh_strt=${appstart:4:2} ; mh_stop=${appstop:4:2}
yy_strt=${appstart:0:4} ; yy_stop=${appstop:0:4}
if [ "${ss_stop}" -lt "${ss_strt}" ]; then ss_stop=$((ss_stop+60)); mm_stop=$((mm_stop-1)); fi
if [ "${mm_stop}" -lt "0" ]; then mm_stop=$((mm_stop+60)); hh_stop=$((hh_stop-1)); fi
if [ "${mm_stop}" -lt "${mm_strt}" ]; then mm_stop=$((mm_stop+60)); hh_stop=$((hh_stop-1)); fi
if [ "${hh_stop}" -lt "0" ]; then hh_stop=$((hh_stop+24)); dd_stop=$((dd_stop-1)); fi
if [ "${hh_stop}" -lt "${hh_strt}" ]; then hh_stop=$((hh_stop+24)); dd_stop=$((dd_stop-1)); fi
if [ "${dd_stop}" -lt "0" ]; then dd_stop=$((dd_stop+$(mh_days $mh_stop $yy_stop))); mh_stop=$((mh_stop-1)); fi
if [ "${dd_stop}" -lt "${dd_strt}" ]; then dd_stop=$((dd_stop+$(mh_days $mh_stop $yy_stop))); mh_stop=$((mh_stop-1)); fi
if [ "${mh_stop}" -lt "0" ]; then mh_stop=$((mh_stop+12)); yy_stop=$((yy_stop-1)); fi
if [ "${mh_stop}" -lt "${mh_strt}" ]; then mh_stop=$((mh_stop+12)); yy_stop=$((yy_stop-1)); fi
ss_espd=$((10#${ss_stop}-10#${ss_strt})); if [ "${#ss_espd}" -le "1" ]; then ss_espd=$(for((i=1;i<=$((${#ss_stop}-${#ss_espd}));i++)); do echo -n "0"; done; echo ${ss_espd}); fi
mm_espd=$((10#${mm_stop}-10#${mm_strt})); if [ "${#mm_espd}" -le "1" ]; then mm_espd=$(for((i=1;i<=$((${#mm_stop}-${#mm_espd}));i++)); do echo -n "0"; done; echo ${mm_espd}); fi
hh_espd=$((10#${hh_stop}-10#${hh_strt})); if [ "${#hh_espd}" -le "1" ]; then hh_espd=$(for((i=1;i<=$((${#hh_stop}-${#hh_espd}));i++)); do echo -n "0"; done; echo ${hh_espd}); fi
dd_espd=$((10#${dd_stop}-10#${dd_strt})); if [ "${#dd_espd}" -le "1" ]; then dd_espd=$(for((i=1;i<=$((${#dd_stop}-${#dd_espd}));i++)); do echo -n "0"; done; echo ${dd_espd}); fi
mh_espd=$((10#${mh_stop}-10#${mh_strt})); if [ "${#mh_espd}" -le "1" ]; then mh_espd=$(for((i=1;i<=$((${#mh_stop}-${#mh_espd}));i++)); do echo -n "0"; done; echo ${mh_espd}); fi
yy_espd=$((10#${yy_stop}-10#${yy_strt})); if [ "${#yy_espd}" -le "1" ]; then yy_espd=$(for((i=1;i<=$((${#yy_stop}-${#yy_espd}));i++)); do echo -n "0"; done; echo ${yy_espd}); fi
echo -e "${yy_espd}-${mh_espd}-${dd_espd} ${hh_espd}:${mm_espd}:${ss_espd}"
#return $(echo -e "${yy_espd}-${mh_espd}-${dd_espd} ${hh_espd}:${mm_espd}:${ss_espd}")
}
mh_days(){
mh_stop=$1; yy_stop=$2; #also checks if it's leap year or not
case $mh_stop in
[1,3,5,7,8,10,12]) mh_stop=31
;;
2) (( !(yy_stop % 4) && (yy_stop % 100 || !(yy_stop % 400) ) )) && mh_stop=29 || mh_stop=28
;;
[4,6,9,11]) mh_stop=30
;;
esac
return ${mh_stop}
}
appstart=$(date +%Y%m%d%H%M%S); read -p "Wait some time, then press nay-key..." key; appstop=$(date +%Y%m%d%H%M%S); elapsed=$(time_elapsed $appstop $appstart); echo -e "Start...: ${appstart:0:4}-${appstart:4:2}-${appstart:6:2} ${appstart:8:2}:${appstart:10:2}:${appstart:12:2}\nStop....: ${appstop:0:4}-${appstop:4:2}-${appstop:6:2} ${appstop:8:2}:${appstop:10:2}:${appstop:12:2}\n$(printf '%0.1s' "="{1..30})\nElapsed.: ${elapsed}"
exit 0
-------------------------------------------- return
Wait some time, then press nay-key...
Start...: 2017-11-09 03:22:17
Stop....: 2017-11-09 03:22:18
==============================
Elapsed.: 0000-00-00 00:00:01

Resources