Skript oder Funktion, um die Anzahl der Tage bis zu einem bestimmten Datum zurückzugeben


28

Ich möchte ein Skript oder eine Funktion schreiben, um mir mitzuteilen, wie viele Tage von jetzt bis zu einem bestimmten Datum in der Zukunft vergangen sind. Was ich kämpfe, um herauszufinden, ist, wie man das gegebene Datum verarbeitet und es mit dem gegenwärtigen Datum vergleicht ... Ich stelle mir so etwas vor

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

Und dann gehe ich davon aus, dass ich einen String habe, der für die Shell bedeutungslos ist, und ich muss einige Auswertungen durchführen wie ... ?? (Dies ist nur ein Beispiel; ich denke, es bcwäre nötig)

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

Und dann weiß ich nicht, was ich mit diesen Zahlen anfangen soll ... ??

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

Ich mache es mir sicher zu schwer; wahrscheinlich gibt es ein Textverarbeitungswerkzeug, das Daten versteht und sie vergleichen kann.

Wie kann ich das machen?


Wahrscheinlich keine Python? Wie auch immer, konvertiere die Zeit in die Epochenzeit (kann in jedem Format sein), dann ist es einfach :)
Jacob Vlijm

@JacobVlijm Python-Lösung total willkommen - das wird mir helfen, wenn ich endlich Python XD lerne und ich möchte, dass es auch funktioniert :)
Zanna

AHA, einen Moment ...
Jacob Vlijm

Antworten:


29

Epochenzeit

Im Allgemeinen sind Zeitberechnungen am einfachsten, wenn wir die Zeit zuerst in (Unix-) Epochenzeit (Sekunden von 1-1-1970) konvertieren. In Python haben wir Werkzeuge, um die Zeit in die Epochenzeit und zurück in ein beliebiges Datumsformat zu konvertieren.

Wir können einfach ein Format einstellen, wie:

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

... und heute definieren:

today = "2016-12-07"

und schreiben Sie anschließend eine Funktion, um die Arbeit zu erledigen:

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

Dann ist die Ausgabe von:

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

> 1481065200

... das ist, wie erwähnt, die Anzahl der Sekunden seit dem 1.1.1970

Berechnung der Tage zwischen zwei Daten

Wenn wir dies heute und zu einem späteren Zeitpunkt tun, berechnen Sie anschließend die Differenz:

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

Die Ausgabe wird nach Datum berechnet , da wir das Format verwenden %Y-%m-%d. Das Runden auf Sekunden würde möglicherweise einen falschen Datumsunterschied ergeben, wenn wir uns zum Beispiel 24 Stunden nähern.

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

Bildbeschreibung hier eingeben

... und die 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))
         ])

Bildbeschreibung hier eingeben

Bildbeschreibung hier eingeben

Und nur zum Spaß ...

Eine winzige Anwendung. Fügen Sie es zu einer Verknüpfung hinzu, wenn Sie es häufig verwenden.

Bildbeschreibung hier eingeben

Das Drehbuch:

#!/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()
  • Kopieren Sie es in eine leere Datei und speichern Sie es unter orangedays.py
  • Starte es:

    python3 /path/to/orangedays.py

Zum einpacken

Verwenden Sie für das winzige Anwendungsskript die folgende .desktopDatei:

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

Bildbeschreibung hier eingeben

  • Kopieren Sie den Code in eine leere Datei, speichern Sie es als orangedays.desktopin~/.local/share/applications
  • In der Schlange

    Exec=/path/to/orangedays.py

    Setze den tatsächlichen Pfad zum Skript ...


23

Das GNU- dateDienstprogramm ist in solchen Dingen ziemlich gut. Es ist in der Lage, eine Vielzahl von Datumsformaten zu analysieren und dann in einem anderen Format auszugeben. Hier geben wir %sdie Anzahl der Sekunden seit der Epoche aus. Es ist dann eine einfache Sache der Arithmetik, $nowvom zu subtrahieren $futureund durch 86400 Sekunden / Tag zu teilen:

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

Abgesehen von einer falschen Rundung (es scheint), funktioniert dies gut! Ich fühle mich dumm, weil ich an den Möglichkeiten von GNU zweifle :) Danke :)
Zanna

1
@Zanna - Ich denke, die Lösung für das Rundungsproblem besteht einfach darin, beide Zeitstempel durch 86400 zu dividieren, bevor die Differenz genommen wird. Aber es könnte einige Details geben, die ich hier vermisse. Soll das eingegebene Datum auch Ortszeit oder UTC sein? Wenn es sich um eine UTC handelt, fügen Sie den -uParameter zu hinzu date.
Digitales Trauma

Tage, an denen zwischen Normalzeit und Sommerzeit gewechselt wird, können sich um +/- 1 Stunde unterscheiden, und an bestimmten Tagen werden selten Korrektursekunden gesetzt. In der Praxis dürfte dies jedoch in den meisten Fällen unwichtig sein.
Benutzer unbekannt

10

Sie können versuchen awk, mit der mktimeFunktion etwas zu tun

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

Das awk erwartet das Lesen des Datums von der Standardeingabe im Format "JJJJ MM TT HH MM SS" und druckt dann die Differenz zwischen der angegebenen Zeit und der aktuellen Zeit in Tagen.

mktimekonvertiert einfach eine Zeit (im angegebenen Format) in die Anzahl der Sekunden von einer Referenzzeit (1970-01-01 00:00:00 UTC); systime simple gibt die aktuelle Uhrzeit im selben Format an. Subtrahieren Sie eine von der anderen und Sie erhalten in Sekunden, wie weit sie voneinander entfernt sind. Teilen Sie durch 86400 (24 * 60 * 60), um in Tage umzurechnen.


1
Nizza, es gibt jedoch ein Problem: Ich denke, Sie möchten nicht die Anzahl der Tage als Float, dann wird einfach durch 86400 dividieren nicht funktionieren, mögliche Rundung als Lösung gibt eine falsche Ausgabe, wenn Sie in der Nähe von 24 Stunden sind
Jacob Vlijm

note Awk-Zeitfunktionen sind nicht POSIX
Steven Penny

10

Hier ist eine 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!"

Beispiellauf:

Ein Beispiel für die Ausführung des Skripts finden Sie weiter ruby ./day-difference.rbunten (vorausgesetzt, Sie haben es gespeichert als day-difference.rb).

Mit einem zukünftigen Datum

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

Mit einem verstrichenen Datum

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

Wann ist das heutige Datum vergangen?

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

Hier ist eine schöne Website, um die Unterschiede des Datums zu überprüfen http://www.timeanddate.com/date/duration.html


Genial! So einfach und klar. Ruby scheint eine großartige Sprache zu sein :)
Zanna

Großartig! Willkommen bei Ruby :)
Jacob Vlijm

1
@ Zanna danke. Ist es wirklich. tryruby hier, wenn Sie 15 Minuten haben. :)
Anwar

@JacobVlijm Vielen Dank für die Ermutigung. Obwohl ich noch Student bin :)
Anwar

6

Es gibt ein dateutilsPaket, das für den Umgang mit Daten sehr praktisch ist. Lesen Sie mehr darüber hier github: dateutils

Installieren Sie es durch

sudo apt install dateutils

Für Ihr Problem einfach,

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

wobei die Ausgabe als Sekunden, Minuten, Stunden, Tage, Wochen, Monate oder Jahre gewählt werden kann. Es kann bequem in Skripten verwendet werden, in denen die Ausgabe für andere Aufgaben verwendet werden kann.


Beispielsweise,

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 :) Gut zu wissen, über dieses Paket.
Zanna

2

Sie können die awk Velour-Bibliothek verwenden :

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

Oder:

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

0

Eine kurze Lösung, wenn beide Daten zum selben Jahr gehören, ist:

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

unter Verwendung des "% j" -Formats, das die Position des Datums in Tagen im Jahr zurückgibt, dh 135 für das aktuelle Datum. Es vermeidet Rundungsprobleme und behandelt Datumsangaben in der Vergangenheit, was zu negativen Ergebnissen führt.

Das Überschreiten der Jahresgrenzen wird jedoch scheitern. Sie können 365 manuell für jedes Jahr oder 366 für jedes Schaltjahr addieren (oder subtrahieren), wenn der letzte Februar überschritten ist. Dies ist jedoch fast so ausführlich wie bei anderen Lösungen.

Hier die reine Bash-Lösung:

#!/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 schlägt viele doppelte Anführungszeichen vor, aber für Tage, die das Jahr 9999 überschreiten, sollten Sie einen anderen Ansatz in Betracht ziehen. In der Vergangenheit wird es für Daten vor dem 1970.01.01 stillschweigend fehlschlagen. Das Desinfizieren von Benutzereingaben wird dem Benutzer als Übung überlassen.

Die beiden Funktionen können in eine überarbeitet werden, was das Verständnis jedoch erschwert.

Bitte beachten Sie, dass das Skript umfangreiche Tests benötigt, um Schaltjahre in der Vergangenheit korrekt zu handhaben. Ich würde nicht wetten, dass es richtig ist.

Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.