Script o función para devolver cuántos días a partir de ahora hasta una fecha determinada

28

Me gustaría escribir un script o función para decirme cuántos días a partir de ahora hasta una fecha determinada en el futuro. Lo que me cuesta resolver es cómo procesar la fecha dada y compararla con la fecha actual ... Me estoy imaginando algo así como

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

Y luego asumo que tengo una cadena que no tiene sentido para el shell y tengo que hacer algunas evaluaciones como ... ?? (Esto es solo un ejemplo; supongo bcque sería necesario)

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

Y entonces no sé qué hacer con esos 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))   

Seguramente lo estoy haciendo muy difícil para mí; probablemente hay una herramienta de procesamiento de texto que comprende las fechas y puede compararlas.

¿Cómo puedo hacer esto?

Zanna
fuente
Probablemente no hay pitón? De todos modos, convierta el tiempo en tiempo de época (puede estar en cualquier formato), entonces es fácil :)
Jacob Vlijm
La solución Python @JacobVlijm es totalmente bienvenida, eso me ayudará cuando finalmente llegue a aprender Python XD y solo quiero que funcione también :)
Zanna
AHA, un momento ...
Jacob Vlijm

Respuestas:

29

Época de la época

En general, los cálculos sobre el tiempo son más fáciles si nos primera vez se convierten en (Unix) época de tiempo (segundos desde el 01/01/1970). En python, tenemos herramientas para convertir el tiempo en tiempo de época y volver a cualquier formato de fecha que prefieramos.

Simplemente podemos establecer un formato, como:

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

... y define hoy:

today = "2016-12-07"

y luego escribe una función para hacer el trabajo:

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

Entonces la salida de:

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

> 1481065200

... que es, como se mencionó, el número de segundos desde 1-1-1970

Calcular los días entre dos fechas

Si hacemos esto tanto hoy como en nuestra fecha futura, calcule posteriormente la diferencia:

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

La salida se calculará por fecha , ya que usamos el formato %Y-%m-%d. El redondeo en segundos posiblemente daría una diferencia de fecha incorrecta, si estamos cerca de las 24 horas, por ejemplo.

Versión 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))

ingrese la descripción de la imagen aquí

... Y la opción 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))
         ])

ingrese la descripción de la imagen aquí

ingrese la descripción de la imagen aquí

Y solo por diversión ...

Una pequeña aplicación. Agréguelo a un acceso directo si lo usa con frecuencia.

ingrese la descripción de la imagen aquí

La secuencia de comandos:

#!/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()
  • Cópielo en un archivo vacío, guárdelo como orangedays.py
  • Ejecutarlo:

    python3 /path/to/orangedays.py

Para envolverlo

Úselo para el pequeño script de aplicación sobre el siguiente .desktoparchivo:

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

ingrese la descripción de la imagen aquí

  • Copie el código en un archivo vacío, guárdelo como orangedays.desktopen~/.local/share/applications
  • En la linea

    Exec=/path/to/orangedays.py

    establecer la ruta real al script ...

Jacob Vlijm
fuente
23

La utilidad GNUdate es bastante buena en este tipo de cosas. Es capaz de analizar una buena variedad de formatos de fecha y luego generarlos en otro formato. Aquí usamos %spara generar el número de segundos desde la época. Entonces es una simple cuestión de aritmética restar $nowdel $futurey dividir por 86400 segundos / día:

#!/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
fuente
aparte del redondeo incorrecto (parece), ¡esto funciona bien! Me siento tonto por dudar de los poderes de la fecha GNU :) Gracias :)
Zanna
1
@Zanna: creo que la solución al problema de redondeo es simplemente dividir enteramente ambas marcas de tiempo entre 86400, antes de tomar la diferencia. Pero puede haber algún detalle que me falta aquí. ¿También desea que la fecha ingresada sea la hora local o UTC? Si es UTC, agregue el -uparámetro a date.
Trauma digital
Los días que cambian entre el horario normal y el horario de verano, pueden diferir durante +/- 1 hora y rara vez se colocan segundos de corrección en ciertos días. Pero en la práctica, esto podría no ser importante en la mayoría de los casos.
usuario desconocido
10

Podrías intentar hacer algo awkusando la mktimefunción

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

El awk espera leer la fecha de la entrada estándar en el formato "AAAA MM DD HH MM SS" y luego imprime la diferencia entre la hora especificada y la hora actual en días.

mktimesimplemente convierte una hora (en el formato especificado) a la cantidad de segundos de una hora de referencia (1970-01-01 00:00:00 UTC); systime simple especifica la hora actual en el mismo formato. Resta uno del otro y obtienes qué tan separados están en segundos. Divide entre 86400 (24 * 60 * 60) para convertir a días.

Nick Sillito
fuente
1
Agradable, sin embargo, hay un problema: supongo que no quieres la cantidad de días como flotante, simplemente dividir entre 86400 no funcionará, el posible redondeo como solución da un resultado incorrecto si estás cerca de las 24 horas
Jacob Vlijm
note Las funciones de tiempo Awk no son POSIX
Steven Penny
10

Aquí hay una versión de 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!"

Ejemplo de ejecución:

A ruby ./day-difference.rbcontinuación se muestra un ejemplo de ejecución del script (suponiendo que lo haya guardado como day-difference.rb)

Con una fecha futura

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

Con una fecha pasada

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

Cuando pasó la fecha de hoy

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

Aquí hay un buen sitio web para verificar las diferencias de fecha http://www.timeanddate.com/date/duration.html

Anwar
fuente
¡Increíble! Tan simple y claro. Ruby parece un gran idioma :)
Zanna
¡Bien bien! Bienvenido a Ruby :)
Jacob Vlijm
1
@ Zanna gracias. Realmente es. prueba aquí si tienes 15 minutos. :)
Anwar
@JacobVlijm Gracias por el aliento. Aunque todavía soy estudiante :)
Anwar
6

Hay un dateutilspaquete que es muy conveniente para manejar fechas. Lea más sobre esto aquí github: dateutils

Instalarlo por

sudo apt install dateutils

Para su problema, simplemente

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

donde la salida se puede elegir como segundos, minutos, horas, días, semanas, meses o años. Se puede usar convenientemente en scripts donde la salida se puede usar para otras tareas.


Por ejemplo,

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
fuente
Excelente :) Es bueno saber acerca de este paquete.
Zanna
2

Puede usar la biblioteca awk Velour :

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

O:

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

Una solución corta, si ambas fechas pertenecen al mismo año, es:

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

utilizando el formato "% j", que devuelve la posición de la fecha en días del año, es decir, 135 para la fecha actual. Evita problemas de redondeo y maneja fechas en el pasado, dando resultados negativos.

Sin embargo, cruzando las fronteras del año, esto fallará. Puede sumar (o restar) 365 manualmente para cada año o 366 para cada año bisiesto, si se cruza el último de febrero, pero eso será casi tan detallado como otras soluciones.

Aquí la solución de bash puro:

#!/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 sugiere muchas citas dobles, pero para los días que excedan el año 9999, debe considerar un enfoque diferente. En el pasado, fallará en silencio para las fechas anteriores a 1970.01.01. La desinfección de la entrada del usuario se deja como un ejercicio para el usuario.

Las dos funciones se pueden refactorizar en una, pero eso podría dificultar su comprensión.

Tenga en cuenta que el script necesita pruebas exhaustivas para manejar correctamente los años bisiestos en el pasado. No apostaría que es correcto.

usuario desconocido
fuente