2

I have two dates in forms like: YYYYMMDDHH and want to calculate the differences (in hours) between these two dates. For example

start_date=1996010100
end_date=1996010122

which stands for two dates: 1996-01-01 00:00:00 and 1996-01-01 22:00:00. I want to use date to calculate the difference in hours, the result shall be 22 hours. I tried with

START=$(date -d "$start_date" +"%s")
END=$(date -d "$end_date" +"%s")
HOURS=$(bc -l <<< "($END - $START) / 3600")

but it failed... So how can I do this? Thanks!

Xu Shan
  • 175
  • 3
  • 11
  • please update the question to explain *'it failed'*; also update the question with the expected results for the sample inputs – markp-fuso Apr 06 '22 at 14:39

5 Answers5

2

For performance reasons we want to limit the number of sub-process calls we need to invoke:

  • use bash substring functionality to convert inputs into usable date/time strings
  • use bash math to replace bc call

bash substring functionality to break the inputs into a usable date/time format, eg:

# convert to usable date/time format:

$ start_date=1996010100
$ echo "${start_date:0:4}-${start_date:4:2}-${start_date:6:2} ${start_date:8:2}:00:00"
1996-01-01 00:00:00

# convert to epoch/seconds:

$ start=$(date -d "${start_date:0:4}-${start_date:4:2}-${start_date:6:2} ${start_date:8:2}:00:00" +"%s")
$ echo $start
820476000

Applying to ${end_date} and using bash math:

$ end_date=1996010122
$ end=$(date -d "${end_date:0:4}-${end_date:4:2}-${end_date:6:2} ${end_date:8:2}:00:00" +"%s")
$ echo $end
820555200

$ hours=$(( (end - start) / 3600))
$ echo $hours
22

This leaves us with 2 sub-process calls ($(date ...)). While other languages/tools (awk, perl, etc) can likely speed this up a bit, if you need to store the result in a bash variable then you're looking at needing at least 1 sub-process call (ie, hours=$(awk/perl/??? ...)).

If performance is really important (eg, needing to perform this 1000's of times) take a look at this SO answer that uses a fifo, background date process and io redirection ... yeah, a bit more coding and a bit more convoluted but also a bit faster for large volumes of operations.

markp-fuso
  • 28,790
  • 4
  • 16
  • 36
2

busybox date can do the trick

start_date=1996010100
end_date=1996010122

START=$(busybox date -D "%Y%m%d%H" -d "$start_date" +"%s")
END=$(busybox date -D "%Y%m%d%H" -d "$end_date" +"%s")
HOURS=$(bc -l <<< "scale=0;($END - $START) / 3600")
echo $HOURS
lojza
  • 1,823
  • 2
  • 13
  • 23
2

If it's possible for you to use a more fully-featured scripting language like Python, it'll provide a much more pleasant and understandable date parsing experience, and is probably installed by default (datetime is also a standard Python library)

Structured with shell vars

start_date=1996010100
end_date=1996010122
python -c "import datetime ; td = datetime.datetime.strptime('${end_date}', '%Y%m%d%H') - datetime.datetime.strptime('${start_date}', '%Y%m%d%H') ; print(int(td.total_seconds() / 3600))"

Structured to read dates and format code from stdin

echo '%Y%m%d%H' 1996010100 1996010122 | python -c "import datetime,sys ; fmt, date_start, date_end = sys.stdin.read().strip().split() ; td = datetime.datetime.strptime(date_end, fmt) - datetime.datetime.strptime(date_start, fmt) ; print(int(td.total_seconds() / 3600))"

Should work with both Python 3 and Python 2.7

format codes available here (1989 C standard)
https://docs.python.org/3/library/datetime.html#strftime-and-strptime-format-codes

ti7
  • 16,375
  • 6
  • 40
  • 68
1

which stands for two dates: 1996-01-01 00:00:00

So convert it to that form if it stands for it.

start_date=1996010100
start_date=$(sed -E 's/(....)(..)(..)(..)/\1-\2-\3 \4:00:00/' <<<"$start_date")
start=$(date -d "$start_date" +"%s")

and the same with end.

KamilCuk
  • 120,984
  • 8
  • 59
  • 111
  • Hi @KamilCuk, thanks for your quick answer! But is there a faster way to convert the ```start_date``` from YYYYMMDDHH to the format which we want? I see that this command costs ~1 second...by the way, I saw the final result is ```22.00000000000000000000```, can we put it in an int form? Thanks! – Xu Shan Apr 06 '22 at 14:34
  • 1
    Yes, move to python or perl. For even faster, move to C++ or Rust. Bash is slow. `this command costs ~1 second` Note that `date` is _very_ slow, it does a lot behind scenes. `sed` should be fast. Still 1 second to spawn some process is ridiculously high, I would suggest inspecting your system for problems. `can we put it in an int form?` `bc` has `scale` option, but just use shell arithemtic. – KamilCuk Apr 06 '22 at 15:24
  • Hi @KamilCuk, can I ask, what if I want to add "UTC" to calculate it in the UTC time zone? – Xu Shan Apr 22 '22 at 08:54
  • ? Then add UTC to do that. – KamilCuk Apr 22 '22 at 09:03
  • So just ```start=$(date -d "$start_date UTC" +"%s")```? – Xu Shan Apr 22 '22 at 09:07
  • Yes, sure. ____ – KamilCuk Apr 22 '22 at 09:55
1

the most simple way is to install "dateutils" using this command

sudo apt-get install dateutils

Run these commands to get the difference in seconds:

dateutils.ddiff -i '%Y%m%d%H%M%S' 20200817040001 20210817040101

output:

31536060s

next step: Simply divide by 86400 to get the number of days or similarly for hours and minutes :)

Aqeel
  • 188
  • 1
  • 11