Скрипт или функция для возврата через сколько дней до указанной даты


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 ожидает чтения даты из стандартного ввода в формате «ГГГГ ММ ДД ЧЧ ММ СС» и затем выводит разницу между указанным временем и текущим временем в днях.

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


Потрясающе! Так просто и понятно. Ruby кажется отличным языком :)
Zanna

Здорово-здорово! Добро пожаловать в Ruby :)
Джейкоб Влейм

1
@ Занна спасибо. Это действительно так. попробуй здесь, если у тебя есть 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 для каждого високосного года, если будет пересекать последний февраль, но это будет почти столь же многословно, как и другие решения.

Вот чистое решение 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 предлагает много двойных кавычек, но для дней, превышающих 9999 год, вы должны рассмотреть другой подход. В прошлом он молча провалится до дат до 1970.01.01. Санитарная обработка пользовательского ввода оставлена ​​пользователю в качестве упражнения.

Две функции могут быть преобразованы в одну, но это может усложнить понимание.

Обратите внимание, что сценарий нуждается в исчерпывающем тестировании для корректной обработки високосных лет в прошлом. Я бы не поспорил, что это правильно.

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.