Script o funzione per restituire quanti giorni da oggi a una determinata data


28

Vorrei scrivere una sceneggiatura o una funzione per dirmi quanti giorni da oggi a una data determinata in futuro. Quello che sto lottando per capire è come elaborare la data specificata e confrontarla con la data corrente ... Sto immaginando qualcosa del genere

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

E poi suppongo di avere una stringa che non ha senso per la shell e devo fare alcune valutazioni come ... ?? (Questo è solo un esempio; immagino che bcsarebbe necessario)

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

E poi non so cosa fare di quei numeri ... ??

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

Lo sto sicuramente rendendo troppo difficile per me stesso; probabilmente c'è uno strumento di elaborazione del testo che capisce le date e può confrontarle.

Come posso fare questo?


Probabilmente nessun pitone? Ad ogni modo, converti il ​​tempo in tempo di epoca (può essere in qualsiasi formato), quindi è facile :)
Jacob Vlijm

Soluzione python @JacobVlijm completamente il benvenuto - che mi aiuterà quando finalmente andare in giro per l'apprendimento python XD e voglio solo farlo funzionare troppo :)
Zanna

AHA, un momento ...
Jacob Vlijm,

Risposte:


29

Tempo di epoca

In generale, i calcoli puntuali sono più semplici se convertiamo prima il tempo in tempo di epoca (Unix) (secondi da 1-1-1970). In Python abbiamo strumenti per convertire il tempo in tempo di epoca, e di nuovo in qualsiasi formato di data che preferiamo.

Possiamo semplicemente impostare un formato, come:

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

... e definisci oggi:

today = "2016-12-07"

e successivamente scrivere una funzione per fare il lavoro:

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

Quindi l'output di:

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

> 1481065200

... che è, come detto, il numero di secondi dall'1-1 al 1970

Calcolo dei giorni tra due date

Se lo facciamo sia oggi che la nostra data futura, successivamente calcoliamo la differenza:

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

L'output verrà calcolato per data , poiché utilizziamo il formato %Y-%m-%d. L'arrotondamento dei secondi potrebbe dare una differenza di data errata, ad esempio se siamo vicini alle 24 ore.

Versione terminale

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

inserisci qui la descrizione dell'immagine

... E l'opzione 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))
         ])

inserisci qui la descrizione dell'immagine

inserisci qui la descrizione dell'immagine

E solo per divertimento ...

Una piccola applicazione. Aggiungilo a un collegamento se lo usi spesso.

inserisci qui la descrizione dell'immagine

Il copione:

#!/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()
  • Copialo in un file vuoto, salvalo come orangedays.py
  • Eseguirlo:

    python3 /path/to/orangedays.py

Per concludere

Utilizzare per il minuscolo script dell'applicazione sopra il seguente .desktopfile:

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

inserisci qui la descrizione dell'immagine

  • Copia il codice in un file vuoto, salvalo come orangedays.desktopin~/.local/share/applications
  • In linea

    Exec=/path/to/orangedays.py

    imposta il percorso effettivo dello script ...


23

L' utilità GNUdate è abbastanza brava in questo genere di cose. È in grado di analizzare una buona varietà di formati di data e quindi di riprodurli in un altro formato. Qui usiamo %sper generare il numero di secondi dall'epoca. Si tratta quindi di una semplice questione di aritmetica per sottrarre $nowdalla $futuree dividere per 86,4 mila secondo / giorno:

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

a parte l'arrotondamento errato (sembra), questo funziona bene! Mi sento sciocco a dubitare dei poteri della data GNU :) Grazie :)
Zanna

1
@Zanna - Penso che la soluzione al problema dell'arrotondamento sia semplicemente quella di dividere tutti e due i timestamp per 86400, prima di fare la differenza. Ma potrebbero esserci alcuni dettagli che mi mancano qui. Vuoi anche che la data inserita sia l'ora locale o UTC? Se è UTC, quindi aggiungere il -uparametro a date.
Digital Trauma

I giorni che cambiano tra l'ora normale e l'ora legale, possono differire di +/- 1 ora e raramente ci sono secondi di correzione posizionati in determinati giorni. Ma in pratica, questo potrebbe non essere importante nella maggior parte dei casi.
utente sconosciuto

10

Potresti provare a fare qualcosa dentro awk, usando la mktimefunzione

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

Awk prevede di leggere la data dall'input standard nel formato "AAAA MM GG HH MM SS" e quindi stampa la differenza tra l'ora specificata e l'ora corrente in giorni.

mktimeconverte semplicemente un tempo (nel formato specificato) nel numero di secondi da un tempo di riferimento (1970-01-01 00:00:00 UTC); systime simple specifica l'ora corrente nello stesso formato. Sottrai l'uno dall'altro e otterrai quanto sono distanti in pochi secondi. Dividi per 86400 (24 * 60 * 60) per convertire in giorni.


1
Bene, c'è comunque un problema: suppongo che tu non voglia il numero di giorni come float, quindi semplicemente la divisione per 86400 non funzionerà, l'eventuale arrotondamento come soluzione fornisce un output errato se sei vicino alle 24 ore
Jacob Vlijm

note Le funzioni dell'orario Awk non sono POSIX
Steven Penny,

10

Ecco una versione di 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!"

Esempio di esecuzione:

L'esempio di esecuzione dello script ruby ./day-difference.rbè riportato di seguito (supponendo che tu l'abbia salvato come day-difference.rb)

Con una data futura

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

Con una data passata

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

Quando è passata la data odierna

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

Ecco un bel sito Web per verificare le differenze di data http://www.timeanddate.com/date/duration.html


Eccezionale! Così semplice e chiaro. Ruby sembra un'ottima lingua :)
Zanna

Bene bene! Benvenuto in Ruby :)
Jacob Vlijm,

1
@Zanna grazie. È davvero. tryruby qui se hai 15 minuti. :)
Anwar

@JacobVlijm Grazie per l'incoraggiamento. Anche se sono ancora uno studente :)
Anwar l'

6

C'è un dateutilspacchetto che è molto conveniente per gestire le date. Leggi di più qui github: dateutils

Installalo da

sudo apt install dateutils

Per il tuo problema, semplicemente,

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

dove l'output può essere scelto come secondi, minuti, ore, giorni, settimane, mesi o anni. Può essere comodamente utilizzato negli script in cui l'output può essere utilizzato per altre attività.


Per esempio,

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

Eccellente :) Buono a sapersi su questo pacchetto.
Zanna,

2

Puoi usare la libreria awk Velour :

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

O:

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

0

Una soluzione breve, se entrambe le date appartengono allo stesso anno, è:

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

utilizzando il formato "% j", che restituisce la posizione della data in giorni dell'anno, ovvero 135 per la data corrente. Evita problemi di arrotondamento e gestisce le date in passato, dando risultati negativi.

Tuttavia, attraversando i confini dell'anno, questo fallirà. È possibile aggiungere (o sottrarre) 365 manualmente per ogni anno o 366 per ogni anno bisestile, se l'ultimo di febbraio viene attraversato, ma sarà quasi dettagliato come altre soluzioni.

Ecco la pura soluzione 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 suggerisce molte doppie quotazioni, ma per giorni superiori all'anno 9999 dovresti considerare un approccio diverso. Per il passato, fallirà silenziosamente per le date precedenti al 1970.01.01. La sanificazione dell'input dell'utente viene lasciata come esercizio all'utente.

Le due funzioni possono essere refactored in una, ma ciò potrebbe rendere più difficile la comprensione.

Si noti che lo script necessita di test approfonditi per gestire correttamente gli anni bisestili nel passato. Non scommetterei che sia giusto.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.