Skrypt lub funkcja zwracająca liczbę dni od teraz do określonej daty


28

Chciałbym napisać skrypt lub funkcję, która powie mi, ile dni od teraz do określonej daty w przyszłości. To, co próbuję wypracować, to jak przetworzyć podaną datę i porównać ją z bieżącą datą ... Wyobrażam sobie coś takiego

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

A potem zakładam, że mam ciąg, który nie ma znaczenia dla powłoki i muszę dokonać pewnych ocen, takich jak ... ?? (To tylko przykład; myślę, bcże będzie potrzebny)

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

A potem nie wiem, co zrobić z tymi liczbami ... ??

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

Z pewnością utrudniam to sobie; prawdopodobnie istnieje narzędzie do przetwarzania tekstu, które rozumie daty i może je porównywać.

W jaki sposób mogę to zrobić?


Prawdopodobnie nie ma pytona? W każdym razie przekonwertuj czas na epokę (może być w dowolnym formacie), to jest łatwe :)
Jacob Vlijm,

@JobobVlijm rozwiązanie python całkowicie mile widziane - to pomoże mi, gdy w końcu zacznę uczyć się python XD i chcę, żeby też działało :)
Zanna

AHA, chwila ...
Jacob Vlijm,

Odpowiedzi:


29

Czas epoki

Zasadniczo obliczenia czasowe są najłatwiejsze, jeśli najpierw przekonwertujemy czas na czas (uniksowy) epoki (sekundy od 1-1-1970). W Pythonie mamy narzędzia do konwersji czasu na epokę i powrotu do dowolnego preferowanego formatu daty.

Możemy po prostu ustawić format, na przykład:

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

... i zdefiniuj dzisiaj:

today = "2016-12-07"

a następnie napisz funkcję wykonującą zadanie:

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

Następnie dane wyjściowe:

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

> 1481065200

... co jest, jak wspomniano, liczbą sekund od 1-1-1970

Obliczanie dni między dwiema datami

Jeśli zrobimy to zarówno dzisiaj, jak i w przyszłości, oblicz różnicę:

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

Dane wyjściowe zostaną obliczone według daty , ponieważ używamy formatu %Y-%m-%d. Zaokrąglanie sekund może być przyczyną nieprawidłowej różnicy dat, jeśli na przykład jesteśmy blisko 24 godzin.

Wersja terminalowa

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

wprowadź opis zdjęcia tutaj

... i opcja 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))
         ])

wprowadź opis zdjęcia tutaj

wprowadź opis zdjęcia tutaj

I dla zabawy ...

Mała aplikacja. Dodaj go do skrótu, jeśli często go używasz.

wprowadź opis zdjęcia tutaj

Scenariusz:

#!/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()
  • Skopiuj go do pustego pliku i zapisz jako orangedays.py
  • Uruchom:

    python3 /path/to/orangedays.py

Podsumowując

Użyj dla małego skryptu aplikacji nad następującym .desktopplikiem:

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

wprowadź opis zdjęcia tutaj

  • Skopiuj kod do pustego pliku, zapisać go jako orangedays.desktopw~/.local/share/applications
  • W linii

    Exec=/path/to/orangedays.py

    ustaw rzeczywistą ścieżkę do skryptu ...


23

GNU datenarzędzie jest dość dobry na tego typu rzeczy. Jest w stanie przeanalizować wiele różnych formatów dat, a następnie wyprowadzać je w innym formacie. Tutaj używamy %sdo wyświetlania liczby sekund od epoki. Odejmowanie $nowod $futurei dzielenie przez 86400 sekund / dzień to prosta kwestia arytmetyczna :

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

oprócz nieprawidłowego zaokrąglenia (wydaje się), działa to dobrze! Głupio czuję, że wątpię w moc GNU :) Dzięki :)
Zanna

1
@Zanna - Myślę, że rozwiązaniem problemu zaokrąglania jest po prostu podzielenie liczb całkowitych obu znaczników czasu przez 86400, zanim przejmiesz różnicę. Ale mogę tu znaleźć pewne szczegóły. Czy chcesz także, aby wprowadzoną datą był czas lokalny lub UTC? Jeśli jest to UTC, dodaj -uparametr do date.
Cyfrowy uraz

Dni, które przełączają się między czasem normalnym a czasem letnim, mogą się różnić o +/- 1 godzinę, a w niektórych dniach rzadko pojawiają się sekundy korekcyjne. Ale w praktyce może to być nieistotne w większości przypadków.
użytkownik nieznany

10

Możesz spróbować coś zrobić awk, korzystając z mktimefunkcji

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

Awk oczekuje odczytania daty ze standardowego wejścia w formacie „RRRR MM DD GG MM SS”, a następnie wydrukuje różnicę między podanym czasem a bieżącym czasem w dniach.

mktimepo prostu konwertuje czas (w określonym formacie) na liczbę sekund z czasu odniesienia (1970-01-01 00:00:00 UTC); systime simple określa aktualny czas w tym samym formacie. Odejmij jeden od drugiego, a otrzymasz odległość od siebie w kilka sekund. Podziel przez 86400 (24 * 60 * 60), aby przeliczyć na dni.


1
Fajnie, jest jednak jeden problem: myślę, że nie chcesz liczby dni jako liczby zmiennoprzecinkowej, wtedy po prostu dzielenie przez 86400 nie zadziała, możliwe zaokrąglenie jako rozwiązanie daje niepoprawne wyjście, jeśli jesteś blisko 24 godzin
Jacob Vlijm

Uwaga Funkcje czasu Awk nie są POSIX
Steven Penny

10

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

Przykładowy przebieg:

Przykład uruchomienia skryptu ruby ./day-difference.rbpodano poniżej (zakładając, że został zapisany jako day-difference.rb)

Z przyszłą datą

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

Z upływem daty

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

Kiedy minęła dzisiejsza data

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

Oto fajna strona internetowa, na której można sprawdzić różnice dat http://www.timeanddate.com/date/duration.html


Niesamowite! Tak proste i jasne. Ruby wydaje się świetnym językiem :)
Zanna,

Świetnie Świetnie! Witamy w Ruby :)
Jacob Vlijm,

1
@Zanna dzięki. Naprawdę jest. spróbuj tutaj, jeśli masz 15 minut. :)
Anwar,

@JacobVlijm Dziękujemy za zachętę. Chociaż wciąż jestem studentem :)
Anwar,

6

Istnieje dateutilspakiet, który jest bardzo wygodny do obsługi dat. Przeczytaj więcej na ten temat tutaj github: dateutils

Zainstaluj przez

sudo apt install dateutils

W przypadku twojego problemu po prostu

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

gdzie dane wyjściowe można wybrać jako sekundy, minuty, godziny, dni, tygodnie, miesiące lub lata. Można go wygodnie używać w skryptach, w których dane wyjściowe można wykorzystać do innych zadań.


Na przykład,

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

Doskonale :) Dobrze wiedzieć o tym pakiecie.
Zanna,

2

Możesz użyć biblioteki awk Velor :

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

Lub:

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

0

Krótkim rozwiązaniem, jeśli obie daty należą do tego samego roku, jest:

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

za pomocą formatu „% j”, który zwraca pozycję daty w dniach w roku, tj. 135 dla bieżącej daty. Pozwala to uniknąć problemów z zaokrąglaniem i obsługuje daty w przeszłości, dając negatywne wyniki.

Jednak przekroczenie granic roku nie powiedzie się. Możesz dodać (lub odjąć) 365 ręcznie dla każdego roku lub 366 dla każdego roku przestępnego, jeśli upłynie ostatni luty, ale będzie to prawie tak szczegółowe, jak inne rozwiązania.

Oto czyste rozwiązanie 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 sugeruje wiele podwójnych cytatów, ale w dniach przekraczających rok 9999 należy rozważyć inne podejście. W przeszłości nie powiedzie się po cichu dla dat sprzed 1970.01.01. Odkażanie danych wejściowych użytkownika pozostaje ćwiczeniem dla użytkownika.

Dwie funkcje można przekształcić w jedną, ale może to utrudnić zrozumienie.

Pamiętaj, że skrypt wymaga wyczerpujących testów, aby poprawnie obsługiwać lata przestępne w przeszłości. Nie założę się, że to prawda.

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.