Сценарій або функція, щоб повернути скільки днів від дати до заданої дати


28

Я хотів би написати сценарій або функцію, щоб сказати, скільки днів відтепер до заданої дати в майбутньому. Що я намагаюся опрацювати - це як обробити дану дату і порівняти її з поточною датою ... Я уявляю собі щось подібне

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

І тоді я припускаю, що у мене є струнка, яка не має сенсу для оболонки, і я повинен робити деякі оцінки, як ... ?? (Це лише приклад; я думаю, bcце буде потрібно)

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

І тоді я не знаю, що робити з цими номерами ... ??

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

Я, звичайно, занадто важко для себе; ймовірно, є інструмент для обробки тексту, який розуміє дати та може їх порівняти.

Як я можу це зробити?


Напевно, немає пітона? У будь-якому випадку перетворіть час на епоху (можна в будь-якому форматі), тоді це легко :)
Яків Влійм

@JacobVlijm python рішення цілком вітається - це допоможе мені, коли я нарешті перейду до вивчення python XD, і я просто хочу, щоб він теж працював :)
Zanna

АХА, мить ...
Яків Влійм

Відповіді:


29

Епоха часу

Загалом, обчислення часу найлегше, якщо ми спочатку перетворимо час у (Unix) епоху (секунди від 1-1-1970). У python у нас є інструменти для перетворення часу в епоху і назад у будь-який формат дати, який ми надаємо перевагу.

Ми можемо просто встановити формат, наприклад:

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

... і визначте сьогодні:

today = "2016-12-07"

а згодом напишіть функцію для виконання завдання:

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

Тоді вихід:

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

> 1481065200

... що, як було сказано, кількість секунд з 1-1-1970 років

Розрахунок днів між двома датами

Якщо ми робимо це як сьогодні, так і на майбутню дату, згодом обчислимо різницю:

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

Вихід буде розрахований за датою , оскільки ми використовуємо формат %Y-%m-%d. Округлення на секунди , можливо, призведе до неправильної різниці дат, якщо ми, наприклад, близько 24 годин.

Термінальна версія

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

введіть тут опис зображення

... І варіант 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))
         ])

введіть тут опис зображення

введіть тут опис зображення

І просто для розваги ...

Крихітний додаток. Додайте його до ярлика, якщо ви використовуєте його часто.

введіть тут опис зображення

Сценарій:

#!/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()
  • Скопіюйте його в порожній файл, збережіть як orangedays.py
  • Виконати:

    python3 /path/to/orangedays.py

Щоб його обернути

Використовуйте для крихітного сценарію програми над наступним .desktopфайлом:

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

введіть тут опис зображення

  • Скопіюйте код у порожній файл, збережіть його як orangedays.desktopу~/.local/share/applications
  • У рядку

    Exec=/path/to/orangedays.py

    встановити фактичний шлях до сценарію ...


23

GNU dateутиліта досить добре такого роду речі. Він може проаналізувати велику кількість форматів дат, а потім вивести їх в іншому форматі. Тут ми використовуємо %sдля виведення кількості секунд з епохи. Тоді проста арифметична справа - відняти $nowвід $futureта поділити на 86400 секунд / день:

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

крім неправильного округлення (здається), це добре працює! Мені глупо за те, що сумніваються в повноваженнях дати GNU :) Дякую :)
Zanna

1
@Zanna - Я думаю, що рішення проблеми округлення полягає в тому, щоб просто розділити обидві часові позначки на 86400, перш ніж приймати різницю. Але тут може бути якась деталь, якої я пропускаю. Ви також хочете, щоб введена дата була місцевим часом або UTC? Якщо його UTC, то додайте -uпараметр до date.
Цифрова травма

Дні, які перемикаються між нормальним часом та літнім часом, можуть відрізнятися на +/- 1 годину, і в певні дні рідко трапляються секунди корекції. Але на практиці це може бути неважливим у більшості випадків.
користувач невідомий

10

Ви можете спробувати щось зробити awk, використовуючи mktimeфункцію

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

Awk очікує, що він прочитає дату зі стандартного введення у форматі "РРРР MM MM HH MM SS", а потім друкує різницю між вказаним часом та поточним часом у днях.

mktimeпросто перетворює час (у вказаному форматі) на кількість секунд від опорного часу (1970-01-01 00:00:00 UTC); systime simple вказує поточний час у тому ж форматі. Віднімайте одне від іншого, і ви отримуєте відстань один від одного за секунди. Розділіть на 86400 (24 * 60 * 60) для перерахунку на дні.


1
Приємно, проте є одне питання: я думаю, ви не хочете, щоб кількість днів як поплавок, тоді просто розділення на 86400 не вийде, можливе округлення як рішення дає неправильний вихід, якщо ви знаходитесь поблизу 24 годин
Якоб Влійм

Примітка Функції часу Awk не є POSIX
Стівен Пенні

10

Ось версія 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!"

Приклад виконання:

Приклад виконання сценарію ruby ./day-difference.rbнаведено нижче (при умові, що ви зберегли його якday-difference.rb )

З майбутньою датою

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

З минулою датою

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

Коли пройшла сьогоднішня дата

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

Ось приємний веб-сайт для перевірки відмінностей дати http://www.timeanddate.com/date/duration.html


Дивовижно! Так просто і зрозуміло. Рубі здається чудовою мовою :)
Zanna

Greatgreat! Ласкаво просимо до Рубі :)
Якоб Влійм

1
@Zanna спасибі Це дійсно так. Tryruby тут, якщо у вас 15 хвилин. :)
Анвар

@JacobVlijm Дякую за заохочення. Хоча я ще студентка :)
Анвар

6

Є dateutilsпакет, який дуже зручний для спілкування з датами. Детальніше про це читайте тут github: dateutils

Встановіть його

sudo apt install dateutils

Для вашої проблеми просто,

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

де вихід може бути обраний як секунди, мінуси, години, дні, тижні, місяці чи роки. Його можна зручно використовувати в сценаріях, де вихід може бути використаний для інших завдань.


Наприклад,

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

Відмінно :) Приємно знати про цей пакет.
Занна


0

Коротке рішення, якщо обидві дати належать до одного року, це:

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

використовуючи формат "% j", який повертає позицію дати у днях у році, тобто 135 для поточної дати. Це дозволяє уникнути проблем із округленням та обробляти дати в минулому, даючи негативні результати.

Однак, перетинаючи межі року, це не вдасться. Ви можете додати (або відняти) 365 вручну на кожен рік або 366 за кожен високосний рік, якщо останній лютий перекреслений, але це буде майже таким же багатослівним, як і інші рішення.

Ось чистий розчин башу:

#!/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 пропонує багато подвійного цитування, але для днів, що перевищують рік 9999, ви повинні розглянути інший підхід. Для минулого він не вдаватиметься мовчки для дат до 1970.01.01. Санітизація введення користувача залишається користувачеві як вправу.

Дві функції можна перетворити на одну, але це може ускладнити розуміння.

Зауважте, що сценарій потребує вичерпного тестування, щоб правильно обробити високосні роки в минулому. Я б не сумнівався, що це правильно.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.