Get the next time occurance with linux date - linux

Linux date utility can understand a lot of strings including for instance:
$ date -d '8:30'
Fri Jan 2 08:30:00 CET 2015
I'm looking for a way to get the next 8:30, thus:
in case it is Fri Jan 2 before 8:30, the result above should be returned;
otherwise it should print Sat Jan 3 08:30:00 CET 2015.
As one can see next 8:30 doesn't result in the correct answer:
$ date -d 'next 8:30'
date: invalid date ‘next 8:30’
Is there a single expression to calculate this?
Handling it in the shell oneself is of course an option, but makes things more complicates because of daylight save time regulation etc.
In case the clock is adapted to daylight save time, next 8:30 should be parsed to 8:30 according to the settings of the next day.
Testcase:
Given it is Fri Jan 2 12:01:01 CET 2015, the result should be:
$ date -d 'next 8:30'
Sat Jan 3 08:30:00 CET 2015
$ date -d 'next 15:30'
Fri Jan 2 15:30:00 CET 2015

Just use something like:
if [[ $(date -d '8:30 today' +%s) -lt $(date +%s) ]] ; then
next830="$(date -d '8:30 tomorrow')"
else
next830="$(date -d '8:30 today')"
fi
The %s format string gives you seconds since the epoch so the if statement is basically:
if 8:30-today is before now:
use 8:30-tomorrow
else
use 8:30-today

I researched and it does not seem to be possible to do so.
What you can probably do is to compare the hour and minute with 830 and print accordingly:
[ $(date '+%H%M') -le 830 ] && date -d '8:30' || date -d '8:30 + 1 day'
In case you want to work with this easily, create a function to do these calculations.
Test
$ [ $(date '+%H%M') -le 830 ] && date '8:30' || date -d '8:30 + 1 day'
Sat Jan 3 08:30:00 CET 2015

Related

How to add an interval to a date/time stored in a variable

I'm writing a bash script on a Fedora 27 machine. This script runs a Python program at intervals, displaying occasional progress messages. It's working except for a line that adds an interval to a time stored in a variable.
Does anyone know how to accomplish this?
Here's a minimal version of the script.
#!/usr/bin/env bash
next_run_dat=${1}
echo -n 'Next run will be at: ';echo ${next_run_dat}
now_dat=`date +'%Y-%m-%d %H:%M:%S'`
echo -n 'The time is now: ';echo ${now_dat}
while [[ ${now_dat} < ${next_run_dat} ]]
do
sleep 10
now_dat=`date +'%Y-%m-%d %H:%M:%S'`
echo -n 'The time is now: ';echo ${now_dat}
done
while true
do
echo 'This line represents a run.'
sleep 5
# ==> PROBLEM LINE BELOW <==
next_run_dat=$(date -d "${next_run_dat} + 25 seconds" +'Y-%m-%d %H:%M:%S')
echo -n 'Next run will be at: ';echo ${next_run_dat}
now_dat=`date +'%Y-%m-%d %H:%M:%S'`
echo -n 'The time is now: ';echo ${now_dat}
while [[ ${now_dat} < ${next_run_dat} ]]
do
sleep 5
now_dat=`date +'%Y-%m-%d %H:%M:%S'`
echo -n 'The time is now: ';echo ${now_dat}
done
done
And here's the output from running the minimal version:
$ bash control_bash_minimal.sh '2018-07-23 09:38:00'
Next run will be at: 2018-07-23 09:38:00
The time is now: 2018-07-23 09:37:36
The time is now: 2018-07-23 09:37:46
The time is now: 2018-07-23 09:37:56
The time is now: 2018-07-23 09:38:06
This line represents a run.
date: invalid date ‘2018-07-23 09:38:00 + 25 seconds’
Next run will be at:
The time is now: 2018-07-23 09:38:11
This line represents a run.
Next run will be at: Y-07-23 09:38:41
The time is now: 2018-07-23 09:38:16
^C
Thanks very much in advance for any help with this problem.
The input format uses + for two different purposes: to specify a timezone in a timestamp, and to specify a relative increase to a timestamp. In your case, date is trying to parse 25 seconds as a timezone. If you specify an explicit timezone, then you can add an offset:
$ date -d "2018-07-23 09:38:00"
Mon Jul 23 09:38:00 EDT 2018
$ date -d "2018-07-23 09:38:00 + 25 seconds"
date: invalid date ‘2018-07-23 09:38:00 + 25 seconds’
# I used a minus sign here to make the output match
# the first example...
$ date -d "2018-07-23 09:38:00-0400 + 25 seconds"
Mon Jul 23 09:38:25 EDT 2018
# ... but positive timezone offsets work too
# (Note that date uses the timezone to parse the
# input, but converts the result to your local
# timezone.)
$ date -d "2018-07-23 09:38:00+0100 + 25 seconds"
Mon Jul 23 04:38:25 EDT 2018
There may be a way to disambiguate without adding an explicit timezone that I am unaware of.
note: this is more an extended comment to chepner's answer
From the manual of GNU coreutils:
Combined date and time of day items
The ISO 8601 date and time of day extended format consists of an ISO 8601 date, a ‘T’ character separator, and an ISO 8601 time of day. This format is also recognized if the ‘T’ is replaced by a space.
In this format, the time of day should use 24-hour notation. Fractional seconds are allowed, with either comma or period preceding the fraction. ISO 8601 fractional minutes and hours are not supported. Typically, hosts support nanosecond timestamp resolution; excess precision is silently discarded.
Sadly enough they make no mention of whether or not a time-zone should be included.
Trying to disentangle the source code and the used GNUlib gives me the feeling that chepner is correct. The double usage of the sign brakes the date parser. To be more correct, it assumes that the first number after + or - is a time-zone offset in hours. Normally, time zones have the format +HH:MM or -HH:MM, but a single number implements it as +HH:00. Evidently, the number has to be smaller than or equal to 24. Example:
$ TZ=UTC date -d "2018-07-23T09:38:00 + 9 seconds"
Mon 23 Jul 00:38:01 UTC 2018
$ TZ=UTC date -d "2018-07-23T09:38:00 + 9 2 seconds"
Mon 23 Jul 00:38:02 UTC 2018
Here, the date is assumed to be in UTC+09:00 and converted to UTC and incremented with a single second and in the second case two seconds.
The example of the OP fails because + 25 seconds is assumed to be UTC+25:00, but this is an invalid time zone:
$ date -d "2018-07-23T09:38:00 + 25 seconds"
date: invalid date ‘2018-07-23T09:38:00 + 25 seconds’
So, how can we add relative times without falling into the TZ-trap?
The date parser expects a signed or unsigned number for relative times. Hence we don't really need to add the plus sign and thus we can exploit this for adding time by removing the + sign:
$ date -d "2018-07-23T09:38:00 25 seconds"
Mon 23 Jul 09:38:25 UTC 2018
This however only works when you add relative time and not when you subtract it. But again, we can trick the parser by adding first ZERO seconds or hours or days or whatever to it:
$ date -d "2018-07-23T09:38:00 - 25 seconds"
date: invalid date ‘2018-07-23T09:38:00 - 25 seconds’
$ date -d "2018-07-23T09:38:00 0 hours - 25 seconds"
Mon 23 Jul 09:37:35 UTC 2018
You can also make use of the keywords next and prev:
$ date -d "2018-07-23T09:38:00 next 25 seconds"
Mon 23 Jul 09:38:25 UTC 2018
$ date -d "2018-07-23T09:38:00 prev 25 seconds"
Mon 23 Jul 09:37:35 UTC 2018
If the time zone if not really of importance, simply work in UTC, just add a Z to the end of the string.
$ date -d "2018-07-23T09:38:00Z + 25 seconds"
Mon 23 Jul 09:38:25 UTC 2018
But the easiest of all is to use float-numbers. As time-zones timezones are given as HH:MM a float cannot be interpreted as a time-zone and thus
$ date -d "2018-07-23T09:38:00 + 25.0 seconds"
Mon 23 Jul 09:38:25 UTC 2018
$ date -d "2018-07-23T09:38:00 - 25.0 seconds"
Mon 23 Jul 09:37:35 UTC 2018
You can convert date into EPOCH seconds and add no of seconds using BASH arithmetic like this:
dt='2018-07-23 09:38:00'
newdt=$(date -d "#$(( $(date -d "$dt" +%s) + 25))" +'%Y-%m-%d %H:%M:%S')
echo "$newdt"
2018-07-23 09:38:25

Bash conditional expression: `-o`. What does it mean?

I am trying to understand what dose the -o mean in the following bash if script.
looking at the results I can guess what it does but I do really need to get the concept of it.
i=1
for day in Mon Tue Wed Thu Fri Sat Sun
do
echo -n "Day $((i++)) : $day"
if [ $i -eq 7 -o $i -eq 8 ]; then
echo " (WEEKEND)"
continue;
fi
echo " (weekday)"
done
The results are as following:
$ ./for7.sh
Day 1 : Mon (weekday)
Day 2 : Tue (weekday)
Day 3 : Wed (weekday)
Day 4 : Thu (weekday)
Day 5 : Fri (weekday)
Day 6 : Sat (WEEKEND)
Day 7 : Sun (WEEKEND)
The -o symbolizes Logical OR here.
Do man test which explains this.
EXPRESSION1 -o EXPRESSION2
either EXPRESSION1 or EXPRESSION2 is true

need to split "date" command ouput in shell

I want to split date command output and extract time zone difference and represent that time difference in terms of 30 min.
I tried this:
date -R | awk 'NF>1{print $NF}'
I got the output -0530
This means 5 hours and 30 min difference from GMT. Now I want to convert this -0530 in to -11. So what logic should I use in shell command?
date -R | awk '{tz=$NF;ew=substr(tz,1,1);h=substr(tz,1,3)*2;m=ew substr(tz,4,2)/30;print h + m}'
https://en.wikipedia.org/wiki/ISO_8601#Time_offsets_from_UTC
Edited to work with omitted sign
date -R | awk '{tz=$NF;l=length(tz);m=substr(tz,l-1,2)/30;h=substr(tz,l-3,2)*2;ew=l>4?ew=substr(tz,1,1):"+";print (ew h)+(ew m)}'
No need to complicate things.
tz=$(date '+%z'); echo "${tz:0:1}$(( 2 * ${tz:1:2} + ${tz:3} ))"
I wouldn't, and instead use a different tool. In the perl module Time::Piece you have tzoffset which returns the current timezone skew in seconds.
Thus:
#!/usr/bin/env perl
use strict;
use warnings;
use Time::Piece;
$ENV{'TZ'}='GMT-6';
my $now = localtime;
#find number of 30m differences;
print $now -> tzoffset / 60 / 30;
You can also use variants of strftime to print time formatted to your liking:
print $now -> strftime("%c");
This can be cut down into a one liner if so desired:
perl -MTime::Piece 'print localtime -> tzoffset / 60 / 30'
With GNU date, you could perform math on time/date values (in UTC):
$ date -uR -d "now"; date -uR -d "now -60 min"
Sun, 27 Dec 2015 20:34:17 +0000
Sun, 27 Dec 2015 19:34:17 +0000
A time of -5:30 is -330 min:
$ date -uR -d "now"; date -uR -d "now -330 min"
Sun, 27 Dec 2015 20:34:55 +0000
Sun, 27 Dec 2015 15:04:55 +0000
That could be further moved by the time zone:
$ TZ=America/New_York date -R -d "now"; TZ=America/New_York date -R -d "now -330 min"
Sun, 27 Dec 2015 15:34:26 -0500
Sun, 27 Dec 2015 10:04:26 -0500
That is the local time (now) subtracting 5 hours and 30 minutes.
However, what is a time zone of -5:30?
I know that India is +5:30 and there are some (very few) places that are +11:00.
But I fail to find any -5:30 and -11:00 seems just odd.

Can I grep for results compared to a timestamp?

I have a tab-delimited text file with three fields: TIMESTAMP, HOST, and STATUS. I need to find if a host was listed as down less than an hour ago. So far, I have this example:
grep "Down" thetextfile.txt | grep "thehostname"
That gives me a little list of all the times that a host was down in the log. Cool. Now I think I just need to get whether the latest TIMESTAMP is less than an hour ago. I am pretty new to Linux and Bash scripting, but in my other work with actual databases, this would be a relatively simple query.
Any ideas? Or is there a much better approach?
Here's an example of the log file:
TIMESTAMP HOST STATUS
Wed Oct 8 12:16:23 EDT 2014 aserver Alive
Wed Oct 8 12:16:23 EDT 2014 anotherserver Down
Thanks!
You can use this BASH script:
#!/bin/bash
# current date-time in seconds (epoch) value
now=$(date '+%s')
while read -r p; do
# ignore 1st row with headers
[[ "$p" == *TIMESTAMP* ]] && continue
# read 3 values in 3 variables t h s
IFS=$'\t' && read t h s <<< "$p"
# convert date string to epoch value
ts=$(date -d "$t" '+%s')
# if date from file is less than 1 hour ago and status is Down then print host name
[[ "$s" == "Down" ]] && (( (now-ts) < 3600 )) && echo "$h"
done < file
I'd use GNU awk:
gawk -v status=Down -v host=anotherserver '
BEGIN {
mo["Jan"]=1; mo["May"]=5; mo["Sep"]=9
mo["Feb"]=2; mo["Jun"]=6; mo["Oct"]=10
mo["Mar"]=3; mo["Jul"]=7; mo["Nov"]=11
mo["Apr"]=4; mo["Aug"]=8; mo["Dec"]=12
}
function elapsed(month, day, time, year) {
gsub(/:/, " ", time)
return systime() - mktime(sprintf("%d %02d %02d %s", year, mo[month], day, time));
}
$NF == status && $(NF-1) == host && elapsed($2,$3,$4,$6) < 3600
' <<DATA
TIMESTAMP HOST STATUS
Wed Oct 8 12:16:23 EDT 2014 aserver Alive
Wed Oct 8 12:16:23 EDT 2014 anotherserver Down
Wed Oct 16 10:16:23 EDT 2014 aserver Alive
Wed Oct 16 10:16:23 EDT 2014 anotherserver Down
Wed Oct 16 10:16:23 EDT 2014 aserver Down
Wed Oct 16 10:16:23 EDT 2014 anotherserver Up
DATA
Wed Oct 16 10:16:23 EDT 2014 anotherserver Down
Current date is Thu Oct 16 10:53:45 EDT 2014

How to make date command work in relation to custom specified date?

In Linux there is pretty awesome command date which can be used is ways like this:
# Get some cool date in relation to systems date:
date -d "last Sunday -7 days"
Sun Sep 15 00:00:00 PDT 2013
# Set systems date:
date --set="2013-03-04"
Mon Mar 4 00:00:00 PST 2013
Basically I want to be able to run this command like this:
date --date="last Sunday -7 days" +%Y-%m-%d
2013-09-15
But not in relation to today's system date but in relation to some date generated by another computation in the form of string (e.g. "2013-09-01") or something else.
Please help me to figure out how to do that.
Using a function:
function get_last_day {
local date=$1 day=$2 format=$3 a b i
for (( i = 0; i <= 6; ++i )); do
read -r a b < <(exec date -d "$date - $i days" "+%a $format")
if [[ $a == "$day" ]]; then
echo "$b"
return
fi
done
}
get_last_day '2013-09-18' Sun '%Y-%m-%d'
Output:
2013-09-15

Resources