Script or function to return how many days from now until a given date

I would like to write a script or function to tell me how many days from now until a given date in the future. What I'm struggling to work out is how to process the given date and compare it with the current date... I'm imagining something like

read -p "enter the date in the format YYYY-MM-DD "

And then I'm assuming I have a string that's meaningless to the shell and I have to do some evaluations like...?? (This is just an example; I guess bc would be needed)

i=$(($(date +%Y)-${REPLY%%-*}))
j=$(($(date +%m)-${REPLY:5:2}))
k=$(($(date +%d)-${REPLY##*-}))

And then I don't know what to do with those numbers...??

if $i > 1 then assign l=$((i*365)) and else what?? # what about leap years?
Using $j somehow assign m   # confused before I've started
Using $k somehow assign n   # just as bad
echo $((l+m+n))   

I'm surely making it too hard for myself; probably there's a text processing tool that understands dates and can compare them.

How can I do this?


Solution 1:

Epoch time

In general, calculations on time are easiest if we first convert time into (Unix) epoch time (seconds from 1-1-1970). In python, we have tools to convert time into epoch time, and back into any date format we prefer.

We can simply set a format, like:

pattern = "%Y-%m-%d"

...and define today:

today = "2016-12-07"

and subsequently write a function to do the job:

def convert_toepoch(pattern, stamp):
    return int(time.mktime(time.strptime(stamp, pattern)))

Then the output of:

nowepoch = convert_toepoch(pattern, today)
print(nowepoch)

> 1481065200

...which is, as mentioned, the number of seconds since 1-1-1970

Calculating the days between two dates

If we do this on both today and our future date, subsequently calculate the difference:

#!/usr/bin/env python3
import time

# set our date pattern
pattern = "%Y-%m-%d" 

def convert_toepoch(pattern, stamp):
    return int(time.mktime(time.strptime(stamp, pattern)))

# automatically get today's date 
today = time.strftime(pattern); future = "2016-12-28"

nowepoch = convert_toepoch(pattern, today)
future_epoch = convert_toepoch(pattern, future)

print(int((future_epoch - nowepoch)/86400))

The output will be calculated by date, since we use the format %Y-%m-%d. Rounding on seconds would possibly give an incorrect date difference, if we are near 24 hrs for example.

Terminal version

#!/usr/bin/env python3
import time

# set our date pattern
pattern = "%Y-%m-%d" 

def convert_toepoch(pattern, stamp):
    return int(time.mktime(time.strptime(stamp, pattern)))

# automatically get today's date 
today = time.strftime(pattern)
# set future date
future = input("Please enter the future date (yyyy-mm-dd): ")
nowepoch = convert_toepoch(pattern, today)
future_epoch = convert_toepoch(pattern, future)
print(int((future_epoch - nowepoch)/86400))

enter image description here

...And the Zenity option

#!/usr/bin/env python3
import time
import subprocess

# set our date pattern
pattern = "%Y-%m-%d" 

def convert_toepoch(pattern, stamp):
    return int(time.mktime(time.strptime(stamp, pattern)))

# automatically get today's date 
today = time.strftime(pattern)
# set future date
try:
    future = subprocess.check_output(
        ["zenity", "--entry", "--text=Enter a date (yyyy-mm-dd)"]
        ).decode("utf-8").strip()
except subprocess.CalledProcessError:
    pass
else:     
    nowepoch = convert_toepoch(pattern, today)
    future_epoch = convert_toepoch(pattern, future)
    subprocess.call(
        ["zenity", "--info",
         "--text="+str(int((future_epoch - nowepoch)/86400))
         ])

enter image description here

enter image description here

And just for fun...

A tiny application. Add it to a shortcut if you use it often.

enter image description here

The script:

#!/usr/bin/env python3
import time
import subprocess
import gi
gi.require_version('Gtk', '3.0')
from gi.repository import Gtk, Pango, Gdk

class OrangDays(Gtk.Window):

    def __init__(self):

        self.pattern = "%Y-%m-%d" 
        self.currdate = time.strftime(self.pattern)
        big_font = "Ubuntu bold 45"
        self.firstchar = True

        Gtk.Window.__init__(self, title="OrangeDays")
        maingrid = Gtk.Grid()
        maingrid.set_border_width(10)
        self.add(maingrid)

        datelabel = Gtk.Label("Enter date")
        maingrid.attach(datelabel, 0, 0, 1, 1)

        self.datentry = Gtk.Entry()
        self.datentry.set_max_width_chars(12)
        self.datentry.set_width_chars(12)
        self.datentry.set_placeholder_text("yyyy-mm-dd")
        maingrid.attach(self.datentry, 2, 0, 1, 1)

        sep1 = Gtk.Grid()
        sep1.set_border_width(10)
        maingrid.attach(sep1, 0, 1, 3, 1)

        buttongrid = Gtk.Grid()
        buttongrid.set_column_homogeneous(True)
        maingrid.attach(buttongrid, 0, 2, 3, 1)

        fakebutton = Gtk.Grid()
        buttongrid.attach(fakebutton, 0, 0, 1, 1)

        calcbutton = Gtk.Button("Calculate")
        calcbutton.connect("clicked", self.showtime)
        calcbutton.set_size_request(80,10)
        buttongrid.attach(calcbutton, 1, 0, 1, 1)

        fakebutton2 = Gtk.Grid()
        buttongrid.attach(fakebutton2, 2, 0, 1, 1)

        sep2 = Gtk.Grid()
        sep2.set_border_width(5)
        buttongrid.attach(sep2, 0, 1, 1, 1)

        self.span = Gtk.Label("0")
        self.span.modify_font(Pango.FontDescription(big_font))
        self.span.set_alignment(xalign=0.5, yalign=0.5)
        self.span.modify_fg(Gtk.StateFlags.NORMAL, Gdk.color_parse("#FF7F2A"))
        maingrid.attach(self.span, 0, 4, 100, 1)

        sep3 = Gtk.Grid()
        sep3.set_border_width(5)
        maingrid.attach(sep3, 0, 5, 1, 1)

        buttonbox = Gtk.Box()
        maingrid.attach(buttonbox, 0, 6, 3, 1)
        quitbutton = Gtk.Button("Quit")
        quitbutton.connect("clicked", Gtk.main_quit)
        quitbutton.set_size_request(80,10)
        buttonbox.pack_end(quitbutton, False, False, 0)

    def convert_toepoch(self, pattern, stamp):
        return int(time.mktime(time.strptime(stamp, self.pattern)))

    def showtime(self, button):
        otherday = self.datentry.get_text()
        try:
            nextepoch = self.convert_toepoch(self.pattern, otherday)
        except ValueError:
            self.span.set_text("?")
        else:
            todayepoch = self.convert_toepoch(self.pattern, self.currdate)
            days = str(int(round((nextepoch-todayepoch)/86400)))
            self.span.set_text(days)


def run_gui():
    window = OrangDays()
    window.connect("delete-event", Gtk.main_quit)
    window.set_resizable(True)
    window.show_all()
    Gtk.main()

run_gui()
  • Copy it into an empty file, save it as orangedays.py
  • Run it:

    python3 /path/to/orangedays.py
    

To wrap it up

Use for the tiny application script above the following .desktop file:

[Desktop Entry]
Exec=/path/to/orangedays.py
Type=Application
Name=Orange Days
Icon=org.gnome.Calendar

enter image description here

  • Copy the code into an empty file, save it as orangedays.desktop in ~/.local/share/applications
  • In the line

    Exec=/path/to/orangedays.py
    

    set the actual path to the script...

Solution 2:

The GNU date utility is quite good at this sort of thing. It is able to parse a good variety of date formats and then output in another format. Here we use %s to output the number of seconds since the epoch. It is then a simple matter of arithmetic to subtract $now from the $future and divide by 86400 seconds/day:

#!/bin/bash

read -p "enter the date in the format YYYY-MM-DD "

future=$(date -d "$REPLY" "+%s")
now=$(date "+%s")
echo "$(( ( $future / 86400 ) - ( $now / 86400 ) )) days"

Solution 3:

You could try doing something in awk, using the mktime function

awk '{print (mktime($0) - systime())/86400}'

The awk expects to read date from standard input in the format "YYYY MM DD HH MM SS" and then prints the difference between the time specified and the current time in days.

mktime simply converts a time (in the specified format) to the number of seconds from a reference time (1970-01-01 00:00:00 UTC); systime simple specifies the current time in the same format. Subtract one from the other and you get how far apart they are in seconds. Divide by 86400 (24 * 60 * 60) to convert to days.

Solution 4:

Here is a Ruby version

require 'date'

puts "Enter a future date in format YYYY-MM-DD"
answer = gets.chomp

difference = (Date.parse(answer) - Date.today).numerator

puts difference > 1 ? "That day will come after #{difference} days" :
  (difference < 0) ? "That day passed #{difference.abs} days ago" :
 "Hey! That is today!"

Example Run:

Example run of the script ruby ./day-difference.rb is given below (assuming you have saved it as day-difference.rb)

With a future date

$ ruby day-difference.rb
Enter a future date in format YYYY-MM-DD
2021-12-30
That day will come after 1848 days

With a passed date

$ ruby day-difference.rb
Enter a future date in format YYYY-MM-DD
2007-11-12
That day passed 3314 days ago

When passed today's date

$ ruby day-difference.rb
Enter a future date in format YYYY-MM-DD
2016-12-8
Hey! That is today!

Here is an nice website to check the differences of date http://www.timeanddate.com/date/duration.html

Solution 5:

There is a dateutils package which is very convenient for dealing with dates. Read more about it here github:dateutils

Install it by

sudo apt install dateutils

For your problem, simply,

dateutils.ddiff <start date> <end date> -f "%d days"

where the output can be chosen as seconds, minues, hours, days, weeks, months or years. It can be conveniently used in scripts where the output can be used for other tasks.


For example,

dateutils.ddiff 2016-12-26  2017-05-12 -f "%m month and %d days"
4 month and 16 days

dateutils.ddiff 2016-12-26  2017-05-12 -f "%d days"
137 days