Bash: Display Nano Seconds With Date - bash

With date it is possible to generate formatted time strings like
date +"%Y-%m-%d-%H:%M:%S.%N"
With date it is also possible to create unix timestamps in nano seconds. Using
NANO_TIMESTAMP=$(date +%s%N)
Is it possible to use date to read in a nano second timestamp to create a formatted date string?
How can I pass a nano second timestamp to date?
I tried:
date -d$NANO_TIMESTAMP +%H:%M:%S.%N
date: invalid date ‘1550736813798767689’
date -d#$NANO_TIMESTAMP +"%Y-%m-%d-%H:%M:%S.%N"
date: time 1550736813798767689 is out of range

You can do that by first by converting the nanosecond value in EPOCH to seconds value in EPOCH and then use that for conversion to human readable strings
nano=1550736813798767689
date -d#"$(( $nano / 1000000000 ))" +"%Y-%m-%d-%H:%M:%S.%N"
For even more accurate representation back, take the modulo of the nano second value
withNano="$(( $nano % 1000000000 ))"
withoutNano="$(date -d#"$(( $nano / 1000000000 ))" +"%Y-%m-%d-%H:%M:%S")"
echo "$withoutNano.$withNano"
So putting this together in a wrapper function
from_nano_to_readable() {
(( $# )) || { printf '%s\n' 'provide atleast one argument' >&2 ; }
input="$1"
withNano="$(( $input % 1000000000 ))"
withoutNano="$(date -d#"$(( $input / 1000000000 ))" +"%Y-%m-%d-%H:%M:%S")"
printf '%s\n' "$withoutNano.$withNano"
}
and call it as
from_nano_to_readable 1550736813798767690
2019-02-21-03:13:33.798767690

Yes, but you have to do the math (division and modulo) yourself.
> set -x
> NANO_TIMESTAMP=$(date +%s%N)
++ date +%s%N
+ NANO_TIMESTAMP=1550740150623261543
> date -d#$((NANO_TIMESTAMP/(1000*1000*1000))).$((NANO_TIMESTAMP%(1000*1000*1000))) +%Y-%m-%d-%H:%M:%S.%N
+ date -d#1550740150.623261543 +%Y-%m-%d-%H:%M:%S.%N
2019-02-21-10:09:10.623261543

Like this but without math.
NANO_TIMESTAMP=$(date +%s%N)
secs=$(printf "%1d\n" ${NANO_TIMESTAMP: 0 : -9})
nanos=${NANO_TIMESTAMP: -9 : 9 }
printf '\r%s' $(TZ=UTC date -d#$secs.$nanos +"%Y-%m-%d-%H:%M:%S.%N")
With printf "%1d" I want to make sure, that there is at least one zero in the secs variable.

Related

convert timestamp to date in bash

i have time logs in timestamp (epoch unix time) format :
1515365117236
1515365123162
1515365139963
i would like to convert it to a regular date like
2017-01-07 23:48:01
2017-01-07 23:48:02
2017-01-07 23:48:03
any ideas what approach would be the fastest?
cat ff1.csv | while read line ; do echo $line\;$(date -d +"%Y-%m-%d %H:%M:%S") ; done > somefile.csv
this takes awful lot of time and just appends the current time
Another approach that must be much faster , using printf of bash version >4.2 :
$ printf '%(datefmt)T\n' epoch
For datefmt you need a string accepted by strftime(3) - see man 3 strftime
Testing:
$ cat file10
1515365117236
1515365123162
1515365139963
$ printf '%(%F %H:%M:%S)T\n' $(cat file10)
49990-01-04 04:47:16
49990-01-04 06:26:02
49990-01-04 11:06:03
In this case , printf format string is:
%F Equivalent to %Y-%m-%d (the ISO 8601 date format). (C99)
%H The hour as a decimal number using a 24-hour clock (range 00 to 23).(Calculated from tm_hour.)
%M The minute as a decimal number (range 00 to 59). (Calculated from tm_min.)
%S The second as a decimal number (range 00 to 60). (The range is up to 60 to allow for occasional leap seconds.- Calculated from tm_sec.)
Update to remove milliseconds:
$ printf '%(%F %T)T\n' $(printf '%s/1000\n' $(<file10) |bc)
2018-01-08 00:45:17
2018-01-08 00:45:23
2018-01-08 00:45:39
The way to transform epoch to date is date -d #epochtime +format
An alternative way is to use date --file switch to read dates from a file directly.
$ cat file10
1515365117236
1515365123162
1515365139963
In order date to understand that these lines are epoch time you need to add # in the beginning of each line.
This can be done like bellow:
$ sed -i 's/^/#/g' file10 #caution - this will make changes in your file
$ date --file file10 +"%Y-%m-%d %H:%M:%S"
Alternativelly, you can do it on the fly without affecting the original file:
$ sed 's/^/#/g' file10 |date --file - +"%Y-%m-%d %H:%M:%S"
PS: in this case --file reads from - == stdin == pipe
In both cases, the result is
49990-01-04 04:47:16
49990-01-04 06:26:02
49990-01-04 11:06:03
PS: by the way, the timestamps you provide seems invalid, since it seems to refer at year 49990
Your input data aren't epoch unix time, it has miliseconds. If you wish to use any method on bash first you must convert to timestamp:
cat ff1.csv | while read LINE; do echo "#$(expr $LINE \/ 1000)" | date +"%Y-%m-%d %H:%M:%S" --file - ; done
First divide by 1000 to delete miliseconds parts, the rest is the same that explain George Vasiliou

Bash - convert time interval string to nr. of seconds

I'm trying to convert strings, describing a time interval, to the corresponding number of seconds.
After some experimenting I figured out that I can use date like this:
soon=$(date -d '5 minutes 10 seconds' +%s); now=$(date +%s)
echo $(( $soon-$now ))
but I think there should be an easier way to convert strings like "5 minutes 10 seconds" to the corresponding number of seconds, in this example 310. Is there a way to do this in one command?
Note: although portability would be useful, it isn't my top priority.
You could start at epoch
date -d"1970-01-01 00:00:00 UTC 5 minutes 10 seconds" "+%s"
310
You could also easily sub in times
Time="1 day"
date -d"1970-01-01 00:00:00 UTC $Time" "+%s"
86400
There is one way to do it, without using date command in pure bash (for portability)
Assuming you just have an input string to convert "5 minutes 10 seconds" in a bash variable with a : de-limiter as below.
$ convertString="00:05:10"
$ IFS=: read -r hour minute second <<< "$convertString"
$ secondsValue=$(((hour * 60 + minute) * 60 + second))
$ printf "%s\n" "$secondsValue"
310
You can run the above commands directly on the command-line without the $ mark.
This will do (add the epoch 19700101):
$ date -ud '19700101 5 minutes 10 seconds' +%s
310
It is important to add a -u to avoid local time (and DST) effects.
$ TZ=America/Los_Angeles date -d '19700101 5 minutes 10 seconds' +%s
29110
Note that date could do some math:
$ date -ud '19700101 +5 minutes 10 seconds -47 seconds -1 min' +%s
203
The previous suggestions didn't work properly on alpine linux, so here's a small helper function that is POSIX compliant, is easy to use and also supports calculations (just as a side effect of the implementation).
The function always returns an integer based on the provided parameters.
$ durationToSeconds '<value>' '<fallback>'
$ durationToSeconds "1h 30m"
5400
$ durationToSeconds "$someemptyvar" 1h
3600
$ durationToSeconds "$someemptyvar" "1h 30m"
5400
# Calculations also work
$ durationToSeconds "1h * 3"
10800
$ durationToSeconds "1h - 1h"
0
# And also supports long forms for year, day, hour, minute, second
$ durationToSeconds "3 days 1 hour"
262800
# It's also case insensitive
$ durationToSeconds "3 Days"
259200
function durationToSeconds () {
set -f
normalize () { echo $1 | tr '[:upper:]' '[:lower:]' | tr -d "\"\\\'" | sed 's/years\{0,1\}/y/g; s/months\{0,1\}/m/g; s/days\{0,1\}/d/g; s/hours\{0,1\}/h/g; s/minutes\{0,1\}/m/g; s/min/m/g; s/seconds\{0,1\}/s/g; s/sec/s/g; s/ //g;'; }
local value=$(normalize "$1")
local fallback=$(normalize "$2")
echo $value | grep -v '^[-+*/0-9ydhms]\{0,30\}$' > /dev/null 2>&1
if [ $? -eq 0 ]
then
>&2 echo Invalid duration pattern \"$value\"
else
if [ "$value" = "" ]; then
[ "$fallback" != "" ] && durationToSeconds "$fallback"
else
sedtmpl () { echo "s/\([0-9]\+\)$1/(0\1 * $2)/g;"; }
local template="$(sedtmpl '\( \|$\)' 1) $(sedtmpl y '365 * 86400') $(sedtmpl d 86400) $(sedtmpl h 3600) $(sedtmpl m 60) $(sedtmpl s 1) s/) *(/) + (/g;"
echo $value | sed "$template" | bc
fi
fi
set +f
}
Edit : Yes. I developed for OP after comment and checked on Mac OS X, CentOS and Ubuntu. One liner, POSIX compliant command for converting "X minutes Y seconds" format to seconds. That was the question.
echo $(($(echo "5 minutes 10 seconds" | cut -c1-2)*60 + $(echo "5 minutes 10 seconds" | cut -c1-12 | awk '{print substr($0,11)}')))
OP told me via comment that he wants for "X minutes Y seconds" format not for HH:MM:SS format. The command with date and "+%s" is throwing error on (my) Mac. OP wanted to grab the numerical values from "X minutes Y seconds" format and convert it to seconds. First I extracted the minute in digit (take it as equation A) :
echo "5 minutes 10 seconds" | cut -c1-2)
then I extracted the seconds part (take it as equation B) :
echo "5 minutes 10 seconds" | cut -c1-12 | awk '{print substr($0,11)}'
Now multiply minute by 60 then add with the other :
echo $((equation A)*60) + (equation B))
OP should ask the others to check my developmental version (but working) of command before using it for automatic repeated usage like we do with cron on a production server.
If we want to run this on a log file with values in "X minutes Y seconds" format, we have to change echo "5 minutes 10 seconds" to cat file | ... like command. I kept a gist of it too if I or others ever need we can use it with cat to run on server log files with x minutes y seconds like log format.
Although off-topic (what I understood, question has not much to do with current time), this is not working for POSIX-compliant OS to get current time in seconds :
date -d "1970-01-01 00:00:00 UTC 5 minutes 10 seconds" "+%s"
It will throw error on MacOS X but work on most GNU/Linux distro. That +%s part will throw error on POSIX-compliant OS upon complicated usage. These commands are mostly suitable to get current time in seconds on POSIX compliant to any kind of unix like OS :
awk 'BEGIN{srand(); print srand()}'
perl -le 'print time'
If OP needs can extend it by generating current time in seconds and subtract. I hope it will help.
---- OLD Answer before EDIT ----
You can get the current time without that date -- echo | awk '{print systime();}' or wget -qO- http://www.timeapi.org/utc/now?\\s. Other way to convert time to second is echo "00:20:40.25" | awk -F: '{ print ($1 * 3600) + ($2 * 60) + $3 }'.
The example with printf shown in another answer is near perfect.
That thing you want is always needed by the basic utilities of GNU/Linux - gnu.org/../../../../../Setting-an-Alarm.html
Way to approach really depends how much foolproof way you need.

How to subtract two different date formats to get days in bash?

I am working on bash. I have to subtract current date from a given date to get number of days as a difference. The given date is in format m/d/yyyy so instead of 09/26/2015 it is 9/26/2015. So even if I try to convert both dates into same format and subtract it says invalid date format.
date1=$(date +"%F")
date2=$(date -d 11/2/2015 +"%F")
diff=$(date "--date=${date2} -${date1}" +%F)
echo $diff days remaining
This is what I had tried with some variations, but doesn't work. What am I doing wrong? Thanks in advance.
Try this:
let diff=(`date +%s -d 11/2/2015` - `date +%s`)/86400
echo $diff days remaining
there are two problems: converting the user-provided date into a normalized form and calculcating the difference in days.
normalizing date
how date interprets a date-string depends on the current locale.
Try to find a locale that uses your special formatting (%m/%d/%Y):
$ LC_TIME=en_US.UTF-8 date -d 1/2/2015
Fri Jan 2 00:00:00 CET 2015
calculating the difference
bash only can only do integer arithmetic, so convert your date first to some integer representation, do the subtraction and convert the representation to days (if needed).
$ LC_TIME=en_US.UTF-8 \
echo $(( ( $(date -d 11/2/2015 +%s) - $(date +%s)) / (3600*24) ))
32
This uses $(...) instead of ... to function substitution.
It also uses $(( ... )) for evalution of math expression instead of the bashism let x=(), so you can use it in POSIX-conformant shell-scripts (e.g. interpreted by /bin/dash)

How can I echo minutes since midnight in any timezone using the date command on OS X?

Here's what isn't working:
> echo $(( ($(date +%s) - $(date +%s -d$(date +%Y-%m-%d))) / 60 ))
date: illegal time format
usage: date [-jnu] [-d dst] [-r seconds] [-t west] [-v[+|-]val[ymwdHMS]] ...
[-f fmt date | [[[mm]dd]HH]MM[[cc]yy][.ss]] [+format]
I've also tried:
> echo $(date +%s -f%Y-%m-%d $(date +%Y-%m-%d))
The following works, but only with a fixed UTC offset that will break during standard time:
> echo $[ ( ( $(date "+%s") - 28800 ) % 86400 ) / 60 ]
Reference: OS X date manual
The number of minutes (or seconds) since midnight can be computed directly from a time alone.
echo $(( $(date "+10#%H * 60 + 10#%M") )) # Minutes since midnight
echo $(( $(date "+(10#%H * 60 + 10#%M) * 60 + 10#%S") )) # Seconds since midnight
Note that this requires only minimal support from date, so will work with either GNU or BSD date.
This works by having date output a string which can be passed directly to the shell's arithmetic expression construct.
Thanks to Petesh for pointing out the need to force numbers with leading zeros to be treated as decimal.
The problem with your first attempt is that you're trying to apply a gnu date option to the BSD date that's on OSX. This has caught me out a lot as I've tried to make scripts compatible between both platforms.
One way is:
seconds_now=$(date +%s)
seconds_midnight=$(date -j -f'%Y-%m-%d %H:%M:%S' "$(date +%Y-%m-%d) 00:00:00" +%s)
minutes_now=$(((seconds_now - seconds_midnight) / 60))
You have to use the full format for the time, otherwise date takes the actual hours, minutes and seconds of the current time, which is not what you want.
Another way is:
Use date +%z to get the offset from UTC, and apply it to the number of minutes past midnight on the day.
offset=$(date +%z) # get TZ offset as [+-]<HH><MM> - for *now*
sign=${offset:0:1} # get sign
hours=${offset:1:2} # get hours
mins=${offset:3:2} # get minutes
minoff=$((10#$hours * 60 + 10#$mins)) # offset in minutes from UTC
from_midnight_utc_mins=$((($(date +%s) % 86400) / 60))
from_midnight_local=$(($from_midnight_utc_mins $sign $minoff))
It's seriously gack, though.
I use 10# for all the numbers in the minoff calculation to prevent the case where two digit numbers with a leading 0 are interpreted as octal, which can yield miscalculations/errors.

Compare Date-Time Stamps [duplicate]

This question already has an answer here:
How to compare two DateTime strings and return difference in hours? (bash shell)
(1 answer)
Closed 8 years ago.
I am looking to write a shell script that will compare the time between two date-time stamps in the format:
2013-12-10 13:25:30.123
2013-12-10 13:25:31.123
I can split the date and time if required (as the comparison should never be more than one second - I am looking at a reporting rate), so I can format the time as 13:25:30.123 / 13:25:31.123.
To just find the newer (or older) of the two timestamps, you could just use a string comparison operator:
time1="2013-12-10 13:25:30.123"
time2="2013-12-10 13:25:31.123"
if [ "$time1" > "$time2" ]; then
echo "the 2nd timestamp is newer"
else
echo "the 1st timestamp is newer"
fi
And, to find the time difference (tested):
ns1=$(date --date "$time1" +%s%N)
ns2=$(date --date "$time2" +%s%N)
echo "the difference in seconds is:" `bc <<< "scale=3; ($ns2 - $ns1) / 1000000000"` "seconds"
Which, in your case prints
the difference in seconds is: 1.000 seconds
Convert them into timestamps before comparing:
if [ $(date -d "2013-12-10 13:25:31.123" +%s) -gt $(date -d "2013-12-10 13:25:30.123" +%s) ]; then
echo "blub";
fi
With Perl using the included Time::Piece library:
perl -MTime::Piece -nE '
BEGIN {
$, = "\t";
sub to_seconds {
my ($dt, $frac) = (shift =~ /(.*)(\.\d*)$/);
return(Time::Piece->strptime($dt, "%Y-%m-%d %T")->epoch + $frac);
}
}
if ($. > 1) {
$a = to_seconds($_);
$b = to_seconds($prev);
say $a, $b, $a-$b
}
$prev = $_
'<<END
2013-12-10 13:25:30.123
2013-12-10 13:25:31.123
2013-12-10 13:25:42.042
END
1386681931.123 1386681930.123 1
1386681942.042 1386681931.123 10.9190001487732

Resources