Script ou função para retornar quantos dias a partir de agora até uma determinada data

28

Gostaria de escrever um script ou função para me dizer quantos dias a partir de agora até uma determinada data no futuro. O que estou lutando para descobrir é como processar a data especificada e compará-la com a data atual ... Estou imaginando algo como

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

E então estou assumindo que tenho uma string que não faz sentido para o shell e tenho que fazer algumas avaliações como ... ?? (Este é apenas um exemplo; acho bcque seria necessário)

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

E então eu não sei o que fazer com esses números ... ??

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

Eu certamente estou tornando muito difícil para mim; provavelmente existe uma ferramenta de processamento de texto que entende datas e pode compará-las.

Como posso fazer isso?

Zanna
fonte
Provavelmente nenhum python? De qualquer forma, o tempo convertido ao tempo de época (pode estar em qualquer formato), então é fácil :)
Jacob Vlijm
@JacobVlijm python solution totally welcome - that will help me when I finally get around to learning python XD and I just want it to work too :)
Zanna
AHA, um momento ...
Jacob Vlijm 07/12/16

Respostas:

29

Época

Em geral, os cálculos no tempo são mais fáceis se primeiro convertermos o tempo em (Unix) época (segundos de 1-1-1970). Em python, temos ferramentas para converter tempo em tempo de época e retornar a qualquer formato de data que preferirmos.

Podemos simplesmente definir um formato, como:

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

... e defina hoje:

today = "2016-12-07"

e subsequentemente escreva uma função para fazer o trabalho:

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

Então a saída de:

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

> 1481065200

... que é, como mencionado, o número de segundos desde 1-1-1970

Cálculo dos dias entre duas datas

Se fizermos isso hoje e em nossa data futura, calcule subsequentemente a diferença:

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

A saída será calculada por data , pois usamos o formato %Y-%m-%d. O arredondamento de segundos possivelmente daria uma diferença de data incorreta, se estivermos perto de 24 horas, por exemplo.

Versão do terminal

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

insira a descrição da imagem aqui

... E a opção 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))
         ])

insira a descrição da imagem aqui

insira a descrição da imagem aqui

E apenas por diversão ...

Uma pequena aplicação. Adicione-o a um atalho se você o usar com frequência.

insira a descrição da imagem aqui

O script:

#!/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()
  • Copie-o para um arquivo vazio, salve-o como orangedays.py
  • Executá-lo:

    python3 /path/to/orangedays.py

Para finalizar

Use para o pequeno script de aplicativo acima do seguinte .desktoparquivo:

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

insira a descrição da imagem aqui

  • Copie o código em um arquivo vazio, salve-o como orangedays.desktopem~/.local/share/applications
  • Na linha

    Exec=/path/to/orangedays.py

    defina o caminho real para o script ...

Jacob Vlijm
fonte
23

O utilitário GNUdate é muito bom nesse tipo de coisa. É capaz de analisar uma boa variedade de formatos de data e depois imprimir em outro formato. Aqui usamos %spara gerar o número de segundos desde a época. Em seguida, é uma simples questão de aritmética para subtrair $nowdo $futuree dividir por 86.400 segundo / dia:

#!/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"
Trauma Digital
fonte
além do arredondamento incorreto (ao que parece), isso funciona bem! Eu sinto idiota por duvidar dos poderes de data GNU :) Thanks :)
Zanna
1
@ Zanna - Eu acho que a solução para o problema de arredondamento é simplesmente dividir por inteiro os dois registros de data e hora por 86400, antes de fazer a diferença. Mas pode haver alguns detalhes que estou perdendo aqui. Você também deseja que a data inserida seja hora local ou UTC? Se for UTC, adicione o -uparâmetro a date.
Digital Trauma
Os dias que alternam entre o horário normal e o horário de verão podem diferir em +/- 1 hora e raramente há segundos de correção em determinados dias. Mas, na prática, isso pode não ser importante na maioria dos casos.
usuário desconhecido
10

Você pode tentar fazer algo awkusando a mktimefunção

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

O awk espera ler a data da entrada padrão no formato "AAAA MM DD HH MM SS" e depois imprime a diferença entre a hora especificada e a hora atual em dias.

mktimesimplesmente converte uma hora (no formato especificado) para o número de segundos de uma hora de referência (1970-01-01 00:00:00 UTC); systime simple especifica a hora atual no mesmo formato. Subtraia um do outro e você fica distante em segundos. Divida por 86400 (24 * 60 * 60) para converter em dias.

Nick Sillito
fonte
1
Bom, no entanto, há um problema: acho que você não deseja o número de dias como um flutuador; simplesmente dividir por 86400 não funcionará; o arredondamento possível como uma solução gera uma saída incorreta se você estiver perto de 24 horas
Jacob Vlijm
nota funções de tempo Awk não são POSIX
Steven Penny
10

Aqui está uma versão do 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!"

Exemplo de execução:

Exemplo de execução do script ruby ./day-difference.rbé fornecido abaixo (assumindo que você o salvou como day-difference.rb)

Com uma 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

Com uma data passada

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

Quando passou a data de hoje

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

Aqui está um bom site para verificar as diferenças de data http://www.timeanddate.com/date/duration.html

Anwar
fonte
Impressionante! Tão simples e claro. Rubi parece ser uma ótima linguagem :)
Zanna
Ótimo ótimo! Bem-vindo ao Ruby :)
Jacob Vlijm
1
@ Zanna obrigado. É realmente. tryruby aqui se você tem 15 mintues. :)
Anwar
@JacobVlijm Obrigado pelo incentivo. Embora eu ainda sou um estudante :)
Anwar
6

Existe um dateutilspacote que é muito conveniente para lidar com datas. Leia mais sobre isso aqui github: dateutils

Instale-o

sudo apt install dateutils

Para o seu problema, simplesmente,

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

onde a saída pode ser escolhida como segundos, minutos, horas, dias, semanas, meses ou anos. Pode ser usado convenientemente em scripts em que a saída pode ser usada para outras tarefas.


Por exemplo,

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
ankit7540
fonte
Excelente :) É bom saber sobre este pacote.
Zanna
2

Você pode usar o awk biblioteca Velour :

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

Ou:

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

Uma solução curta, se as duas datas pertencerem ao mesmo ano, é:

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

usando o formato "% j", que retorna a posição da data em dias no ano, ou seja, 135 para a data atual. Evita problemas de arredondamento e lida com datas no passado, fornecendo resultados negativos.

No entanto, cruzando as fronteiras do ano, isso irá falhar. Você pode adicionar (ou subtrair) 365 manualmente para cada ano ou 366 para cada ano bissexto, se o último mês de fevereiro for ultrapassado, mas isso será quase tão detalhado quanto as outras soluções.

Aqui a solução pura do 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

O Shellcheck sugere muitas aspas duplas, mas por dias que excedem o ano de 9999 você deve considerar uma abordagem diferente. No passado, ele falharia silenciosamente em datas anteriores a 1970.01.01. A limpeza da entrada do usuário é deixada como um exercício para o usuário.

As duas funções podem ser refatoradas em uma, mas isso pode dificultar a compreensão.

Observe que o script precisa de testes exaustivos para lidar corretamente com os anos bissextos no passado. Eu não apostaria que está certo.

Usuário desconhecido
fonte