Script ou fonction pour renvoyer le nombre de jours d'ici à une date donnée


28

Je voudrais écrire un script ou une fonction pour me dire combien de jours d'ici à une date donnée dans le futur. Ce que j'ai du mal à comprendre, c'est comment traiter la date donnée et la comparer avec la date actuelle ... J'imagine quelque chose comme

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

Et puis je suppose que j'ai une chaîne qui n'a pas de sens pour le shell et je dois faire des évaluations comme ... ?? (Ceci est juste un exemple; je suppose que ce bcserait nécessaire)

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

Et puis je ne sais pas quoi faire avec ces chiffres ... ??

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))   

Je fais sûrement trop dur pour moi; il existe probablement un outil de traitement de texte qui comprend les dates et peut les comparer.

Comment puis-je faire ceci?


Probablement pas de python? Quoi qu'il en soit, convertissez le temps en temps d'époque (peut être dans n'importe quel format), alors c'est facile :)
Jacob Vlijm

@JacobVlijm solution python totalement bienvenue - cela m'aidera quand j'arriverai enfin à apprendre python XD et je veux juste que ça marche aussi :)
Zanna

AHA, un instant ...
Jacob Vlijm

Réponses:


29

Temps d'époque

En général, les calculs sur le temps sont plus faciles si nous avons le temps de conversion en (Unix) époque le temps (secondes de 01.01.1970). En python, nous avons des outils pour convertir l'heure en temps d'époque, et de nouveau dans n'importe quel format de date que nous préférons.

Nous pouvons simplement définir un format, comme:

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

... et définissez aujourd'hui:

today = "2016-12-07"

et ensuite écrire une fonction pour faire le travail:

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

Ensuite, la sortie de:

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

> 1481065200

... qui est, comme mentionné, le nombre de secondes depuis le 1-1-1970

Calcul des jours entre deux dates

Si nous le faisons à la fois aujourd'hui et à notre date future, calculons ensuite la différence:

#!/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))

La sortie sera calculée par date , puisque nous utilisons le format %Y-%m-%d. Arrondir les secondes donnerait probablement une différence de date incorrecte, si nous sommes près de 24 heures par exemple.

Version du terminal

#!/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))

entrez la description de l'image ici

... et l'option Zenity

#!/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))
         ])

entrez la description de l'image ici

entrez la description de l'image ici

Et juste pour le plaisir ...

Une toute petite application. Ajoutez-le à un raccourci si vous l'utilisez souvent.

entrez la description de l'image ici

Le 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()
  • Copiez-le dans un fichier vide, enregistrez-le sous orangedays.py
  • Exécuter:

    python3 /path/to/orangedays.py

Pour conclure

Utilisez pour le petit script d'application au-dessus du .desktopfichier suivant :

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

entrez la description de l'image ici

  • Copiez le code dans un fichier vide, enregistrez-le comme orangedays.desktopdans~/.local/share/applications
  • Dans la ligne

    Exec=/path/to/orangedays.py

    définir le chemin réel vers le script ...


23

L' utilitaire GNUdate est assez bon dans ce genre de choses. Il est capable d'analyser une bonne variété de formats de date, puis de produire dans un autre format. Ici, nous utilisons %spour afficher le nombre de secondes depuis l'époque. C'est alors une simple question d'arithmétique à soustraire $nowde la $futureet à diviser par 86400 secondes / jour:

#!/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"

à part un arrondi incorrect (il semble), cela fonctionne bien! Je me sens stupide de douter des pouvoirs de la date GNU :) Merci :)
Zanna

1
@Zanna - Je pense que la solution au problème d'arrondi est simplement de diviser les deux horodatages par 86400, avant de prendre la différence. Mais il y a peut-être un détail qui me manque ici. Voulez-vous également que la date saisie soit l'heure locale ou UTC? Si son UTC, alors ajoutez le -uparamètre à date.
Digital Trauma

Les jours qui basculent entre l'heure normale et l'heure d'été peuvent différer de +/- 1 heure et il y a rarement des secondes de correction placées certains jours. Mais en pratique, cela peut être sans importance dans la plupart des cas.
utilisateur inconnu

10

Vous pouvez essayer de faire quelque chose en awkutilisant la mktimefonction

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

L'awk s'attend à lire la date à partir de l'entrée standard au format "AAAA MM JJ HH MM SS" puis imprime la différence entre l'heure spécifiée et l'heure actuelle en jours.

mktimeconvertit simplement une heure (dans le format spécifié) en nombre de secondes à partir d'une heure de référence (1970-01-01 00:00:00 UTC); systime simple spécifie l'heure actuelle dans le même format. Soustrayez l'un de l'autre et vous obtenez à quelle distance ils sont séparés en quelques secondes. Divisez par 86400 (24 * 60 * 60) pour convertir en jours.


1
Bien, il y a cependant un problème: je suppose que vous ne voulez pas le nombre de jours comme un flottant, alors simplement diviser par 86400 ne fonctionnera pas, l'arrondi possible comme solution donne une sortie incorrecte si vous êtes près de 24 heures
Jacob Vlijm

note Les fonctions de temps Awk ne sont pas POSIX
Steven Penny

10

Voici une version Ruby

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!"

Exemple d'exécution:

Un exemple d'exécution du script ruby ./day-difference.rbest donné ci-dessous (en supposant que vous l'ayez enregistré sous day-difference.rb)

Avec une date future

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

Avec une date passée

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

Quand est passé la date d'aujourd'hui

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

Voici un joli site internet pour vérifier les différences de date http://www.timeanddate.com/date/duration.html


Impressionnant! Si simple et clair. Ruby semble être un super langage :)
Zanna

Bien bien! Bienvenue chez Ruby :)
Jacob Vlijm

1
@Zanna merci. Ça l'est vraiment. tryruby ici si vous avez 15 minutes. :)
Anwar

@JacobVlijm Merci pour vos encouragements. Bien que je sois encore étudiant :)
Anwar

6

Il existe un dateutilspackage très pratique pour gérer les dates. En savoir plus ici github: dateutils

Installez-le par

sudo apt install dateutils

Pour votre problème, simplement,

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

où la sortie peut être choisie en secondes, minutes, heures, jours, semaines, mois ou années. Il peut être facilement utilisé dans des scripts où la sortie peut être utilisée pour d'autres tâches.


Par exemple,

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

Excellent :) Bon à savoir sur ce package.
Zanna

2

Vous pouvez utiliser la bibliothèque awk Velour :

$ velour -n 'print t_secday(t_utc(2018, 7, 1) - t_now())'
7.16478

Ou:

$ velour -n 'print t_secday(t_utc(ARGV[1], ARGV[2], ARGV[3]) - t_now())' 2018 7 1
7.16477

0

Une solution courte, si les deux dates appartiennent à la même année, est:

echo $((1$(date -d 2019-04-14 +%j) - 1$(date +%j)))

en utilisant le format "% j", qui renvoie la position de la date en jours dans l'année, soit 135 pour la date actuelle. Il évite les problèmes d'arrondi et gère les dates dans le passé, donnant des résultats négatifs.

Cependant, traverser les frontières de l'année, cela échouera. Vous pouvez ajouter (ou soustraire) 365 manuellement pour chaque année ou 366 pour chaque année bissextile, si le dernier février est franchi, mais ce sera presque aussi détaillé que les autres solutions.

Voici la solution pure bash:

#!/bin/bash
#
# Input sanitizing and asking for user input, if no date was given, is left as an exercise
# Suitable only for dates from 1.1.1970 to 31.12.9999
#
# Get date as parameter (in format yyyy-MM-dd
#
date2=$1
# for testing, more convenient:
# date2=2019-04-14
#
year2=${date2:0:4}
year1=$(date +%Y)
#
# difference in days, ignoring years:
# since %j may lead to values like 080..099, 
# which get interpreted as invalid octal numbers, 
# I prefix them with "1" each (leads to 1080..1099) 
daydiff=$((1$(date -d 1$date2 +%j)- $(date +%j)))
#
yeardiff=$((year2-year1))
# echo yeardiff $yeardiff
#
#
# summarize days per year, except for the last year:
#
daysPerYearFromTo () {
    year1=$1
    year2=$2
    days=0
    for y in $(seq $year1 $((year2-1)))
    do
        ((days+=$(date -d $y-12-31 +"%j")))
    done
    echo $days
}
# summarize days per year in the past, except for the last year:
#
daysPerYearReverse () {
    year1=$1
    year2=$2
    days=0
    for y in $(seq $((year1-1)) -1 $year2)
    do
        ((days+=$(date -d $y-12-31 +"%j")))
    done
    echo $days
}

case $yeardiff in
    0) echo $daydiff
        ;;
    # date in one of previous years:
    -[0-9]*) echo $((daydiff-$(daysPerYearReverse $year1 $year2)))
        ;;
    # date in one of future years:
    [0-9]*) echo $((daydiff+$(daysPerYearFromTo $year1 $year2)))
        ;;
esac

Shellcheck propose de nombreuses citations doubles, mais pour les jours dépassant l'année 9999, vous devriez envisager une approche différente. Pour le passé, il échouera silencieusement pour les dates antérieures au 01.01.1970. La désinfection de l'entrée utilisateur est laissée à l'utilisateur.

Les deux fonctions peuvent être refondues en une seule, mais cela pourrait la rendre plus difficile à comprendre.

Veuillez noter que le script nécessite des tests exhaustifs pour gérer correctement les années bissextiles dans le passé. Je ne parierais pas que c'est vrai.

En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.