Resta el tiempo usando date y bash


18

Todas las otras preguntas en la red SE tratan con escenarios en los que se supone que la fecha es now( Q ) o donde solo se especifica una fecha ( Q ).

Lo que quiero hacer es proporcionar una fecha y hora, y luego restar una hora de eso.
Esto es lo que probé primero:

date -d "2018-12-10 00:00:00 - 5 hours - 20 minutes - 5 seconds"

Esto da como resultado 2018-12-10 06:39:55: agregó 7 horas. Luego restado 20:05 minutos.

Después de leer la página many infode date, pensé que lo había solucionado con esto:

date -d "2018-12-10T00:00:00 - 5 hours - 20 minutes - 5 seconds"

Pero, el mismo resultado. ¿De dónde obtiene las 7 horas?

Intenté otras fechas también porque pensé que tal vez teníamos 7200 segundos bisiestos ese día, quién sabe jajaja. Pero los mismos resultados.

Algunos ejemplos más:

$ date -d "2018-12-16T00:00:00 - 24 hours" +%Y-%m-%d_%H:%M:%S
2018-12-17_02:00:00

$ date -d "2019-01-19T05:00:00 - 2 hours - 5 minutes" +%Y-%m-%d_%H:%M:%S
2019-01-19_08:55:00

Pero aquí se vuelve interesante. Si omito el tiempo de entrada, funciona bien:

$ date -d "2018-12-16 - 24 hours" +%Y-%m-%d_%H:%M:%S
2018-12-15_00:00:00

$ date -d "2019-01-19 - 2 hours - 5 minutes" +%Y-%m-%d_%H:%M:%S
2019-01-18_21:55:00

$ date --version
date (GNU coreutils) 8.30

¿Qué me estoy perdiendo?

Actualización: agregué un Zal final, y cambió el comportamiento:

$ date -d "2019-01-19T05:00:00Z - 2 hours" +%Y-%m-%d_%H:%M:%S
2019-01-19_04:00:00

Aunque todavía estoy confundido. No hay mucho sobre esto en la página de información de GNU sobre la fecha.

Supongo que esto es un problema de zona horaria, pero citando The Calendar Wiki en ISO 8601 :

Si no se proporciona información de relación UTC con una representación de hora, se supone que la hora es en hora local.

Que es lo que quiero. Mi hora local también está configurada correctamente. No estoy seguro de por qué la fecha interferiría con la zona horaria en este caso simple de que yo proporcione una fecha y hora y desee restarle algo. ¿No debería restar primero las horas de la cadena de fecha? Incluso si primero lo convierte en una fecha y luego hace la resta, si omito cualquier resta obtengo exactamente lo que quiero:

$ date -d "2019-01-19T05:00:00" +%Y-%m-%d_%H:%M:%S
2019-01-19_05:00:00

Así SI esto realmente es un problema de zona horaria, ¿de dónde viene esa locura viene?


Respuestas:


20

Ese último ejemplo debería haber aclarado cosas para usted: zonas horarias .

$ TZ=UTC date -d "2019-01-19T05:00:00Z - 2 hours" +%Y-%m-%d_%H:%M:%S
2019-01-19_03:00:00
$ TZ=Asia/Colombo date -d "2019-01-19T05:00:00Z - 2 hours" +%Y-%m-%d_%H:%M:%S 
2019-01-19_08:30:00

Como la salida varía claramente según la zona horaria, sospecharía que se toma un valor predeterminado no obvio para una cadena de tiempo sin una zona horaria especificada. Al probar un par de valores, parece ser UTC-05: 00 , aunque no estoy seguro de qué es eso.

$ TZ=UTC date -d "2019-01-19T05:00:00 - 2 hours" +%Y-%m-%d_%H:%M:%S%Z
2019-01-19_08:00:00UTC
$ TZ=UTC date -d "2019-01-19T05:00:00Z - 2 hours" +%Y-%m-%d_%H:%M:%S%Z
2019-01-19_03:00:00UTC
$ TZ=UTC date -d "2019-01-19T05:00:00" +%Y-%m-%d_%H:%M:%S%Z           
2019-01-19_05:00:00UTC

Solo se usa cuando se realiza la aritmética de fechas.


Al parecer, el problema aquí es que - 2 hoursse no se toma como la aritmética, sino como un especificador de zona horaria :

# TZ=UTC date -d "2019-01-19T05:00:00 - 2 hours" +%Y-%m-%d_%H:%M:%S%Z --debug
date: parsed datetime part: (Y-M-D) 2019-01-19 05:00:00 UTC-02
date: parsed relative part: +1 hour(s)
date: input timezone: parsed date/time string (-02)
date: using specified time as starting value: '05:00:00'
date: starting date/time: '(Y-M-D) 2019-01-19 05:00:00 TZ=-02'
date: '(Y-M-D) 2019-01-19 05:00:00 TZ=-02' = 1547881200 epoch-seconds
date: after time adjustment (+1 hours, +0 minutes, +0 seconds, +0 ns),
date:     new time = 1547884800 epoch-seconds
date: timezone: TZ="UTC" environment value
date: final: 1547884800.000000000 (epoch-seconds)
date: final: (Y-M-D) 2019-01-19 08:00:00 (UTC)
date: final: (Y-M-D) 2019-01-19 08:00:00 (UTC+00)
2019-01-19_08:00:00UTC

Por lo tanto, no solo no se está haciendo aritmética, parece que hay un ajuste de 1 hora en el horario de verano , lo que nos lleva a un tiempo un poco absurdo.

Esto también es válido para la adición:

# TZ=UTC date -d "2019-01-19T05:00:00 + 5:30 hours" +%Y-%m-%d_%H:%M:%S%Z --debug
date: parsed datetime part: (Y-M-D) 2019-01-19 05:00:00 UTC+05:30
date: parsed relative part: +1 hour(s)
date: input timezone: parsed date/time string (+05:30)
date: using specified time as starting value: '05:00:00'
date: starting date/time: '(Y-M-D) 2019-01-19 05:00:00 TZ=+05:30'
date: '(Y-M-D) 2019-01-19 05:00:00 TZ=+05:30' = 1547854200 epoch-seconds
date: after time adjustment (+1 hours, +0 minutes, +0 seconds, +0 ns),
date:     new time = 1547857800 epoch-seconds
date: timezone: TZ="UTC" environment value
date: final: 1547857800.000000000 (epoch-seconds)
date: final: (Y-M-D) 2019-01-19 00:30:00 (UTC)
date: final: (Y-M-D) 2019-01-19 00:30:00 (UTC+00)
2019-01-19_00:30:00UTC

Depuración un poco más, el análisis parece ser: 2019-01-19T05:00:00 - 2( -2siendo la zona horaria), y hours(= 1 hora), con una adición implícita. Se vuelve más fácil ver si usas minutos en su lugar:

# TZ=UTC date -d "2019-01-19T05:00:00 - 2 minutes" +%Y-%m-%d_%H:%M:%S%Z --debug
date: parsed datetime part: (Y-M-D) 2019-01-19 05:00:00 UTC-02
date: parsed relative part: +1 minutes
date: input timezone: parsed date/time string (-02)
date: using specified time as starting value: '05:00:00'
date: starting date/time: '(Y-M-D) 2019-01-19 05:00:00 TZ=-02'
date: '(Y-M-D) 2019-01-19 05:00:00 TZ=-02' = 1547881200 epoch-seconds
date: after time adjustment (+0 hours, +1 minutes, +0 seconds, +0 ns),
date:     new time = 1547881260 epoch-seconds
date: timezone: TZ="UTC" environment value
date: final: 1547881260.000000000 (epoch-seconds)
date: final: (Y-M-D) 2019-01-19 07:01:00 (UTC)
date: final: (Y-M-D) 2019-01-19 07:01:00 (UTC+00)
2019-01-19_07:01:00UTC

Entonces, bueno, se está haciendo la aritmética de fechas, pero no la que pedimos. ¯ \ (ツ) / ¯


1
@confetti lo hace de hecho; Creo que esta zona horaria predeterminada se usa solo al sumar / restar (comparar TZ=UTC date -d "2019-01-19T05:00:00Z - 2 hours" +%Y-%m-%d_%H:%M:%Svs TZ=UTC date -d "2019-01-19T05:00:00 - 2 hours" +%Y-%m-%d_%H:%M:%S)
Olorin

2
Podría ser un posible error date, según el estándar ISO 8601, si no se proporciona una zona horaria, se debe suponer la hora local (zona), que en el caso de una operación aritmética, no lo es. Sin embargo, es un problema muy extraño para mí.
confeti

1
@confetti encontró el problema: aquí - 2 hoursse toma como el especificador de zona horaria.
Olorin

2
Guau. Ahora que de alguna manera tiene sentido. Bueno, al menos lo explica. Muchas gracias por resolver eso. Sin embargo, para mí parece que su analizador necesita una actualización, ya que claramente con un formato y un espacio en blanco como este no desea especificar la zona horaria por el '- 2 horas' y seguramente está causando confusión. Si no quieren actualizar el analizador, al menos el manual debería recibir una nota al respecto.
confeti

1
¡Hoy aprendí sobre la --debugopción de fecha ! Gran explicación
Jeff Schaller

6

Funciona correctamente cuando convierte la fecha de entrada a ISO 8601 primero:

$ date -d "$(date -Iseconds -d "2018-12-10 00:00:00") - 5 hours - 20 minutes - 5 seconds"
So 9. Dez 18:39:55 CET 2018

Gracias, eso sí funciona, pero ¿hay alguna explicación para esto? Agregué más información sobre la cuestión ISO 8601 a mi pregunta, y realmente no veo dónde datepodría estropear esto, ya que sin ninguna resta suministrada, la zona horaria se deja intacta y todo es como se esperaba sin tener que suministrar ningún información de zona horaria o conversión.
confeti

No puedo decir, lo siento. Si alguien más pudiera responder esto, estaría feliz porque también me lo estaba preguntando.
pLumo

¡También me alegraría, pero aceptaré esto por ahora porque soluciona mi problema!
confeti

3
Esto funciona porque date -Itambién genera el especificador de zona horaria (p 2018-12-10T00:00:00+02:00. Ej. ) Que soluciona el problema descrito en la respuesta de
Olorin

6

TLDR: Esto no es un error. Acabas de encontrar uno de los comportamientos sutiles pero documentados de date. Al realizar operaciones aritméticas con el tiempo date, use un formato independiente de la zona horaria (como el tiempo Unix) o lea con mucha atención la documentación para saber cómo usar este comando correctamente.


GNU dateutiliza la configuración de su sistema (la TZvariable de entorno o, si no está configurada, la predeterminada del sistema) para determinar la zona horaria de la fecha alimentada con la opción -d/ --datey la fecha informada por el +formatargumento. La --dateopción también le permite anular la zona horaria para su propio argumento de opción, pero no anula la zona horaria de +format. Esa es la raíz de la confusión, en mi humilde opinión.

Teniendo en cuenta que mi zona horaria es UTC-6, compare los siguientes comandos:

$ date -d '1970-01-01 00:00:00' '+Normal: %F %T %:z%nUnix: %s'
Normal: 1970-01-01 00:00:00 -06:00
Unix: 21600
$ date -d '1970-01-01 00:00:00 UTC' '+Normal: %F %T %:z%nUnix: %s'
Normal: 1969-12-31 18:00:00 -06:00
Unix: 0
$ TZ='UTC0' date -d '1970-01-01 00:00:00 UTC' '+Normal: %F %T %:z%nUnix: %s'
Normal: 1970-01-01 00:00:00 +00:00
Unix: 0

El primero usa mi zona horaria para ambos -dy +format. El segundo usa UTC para -dpero mi zona horaria para +format. El tercero usa UTC para ambos.

Ahora, compare las siguientes operaciones simples:

$ date -d '1970-01-01 00:00:00 UTC +1 day' '+Normal: %F %T %:z%nUnix: %s'
Normal: 1970-01-01 18:00:00 -06:00
Unix: 86400
$ TZ='UTC0' date -d '1970-01-01 00:00:00 UTC +1 day' '+Normal: %F %T %:z%nUnix: %s'
Normal: 1970-01-02 00:00:00 +00:00
Unix: 86400

Incluso si la hora de Unix me dice lo mismo, la hora "Normal" difiere debido a mi propia zona horaria.

Si quisiera hacer la misma operación pero usando exclusivamente mi zona horaria:

$ TZ='CST+6' date -d '1970-01-01 00:00:00 -06:00 +1 day' '+Normal: %F %T %:z%nUnix: %s'
Normal: 1970-01-02 00:00:00 -06:00
Unix: 108000

44
Votado por mencionar el tiempo de época / Unix, que es la única forma sensata de hacer aritmética del tiempo
Rui F Ribeiro

1
TL; DR Si solo conoce su hora local compensada de la hora zulú (por ejemplo, la costa oeste de EE. UU. Es -8 horas o -08:00), simplemente agregue eso para ingresar la fecha-hora ... nada más para jugar. Ejemplo: date -d '2019-02-28 14:05:36-08:00 +2 days 4 hours 3 seconds' +'local: %F %T'da local: 02/03/2018 18:05:36
Capa B

1
@BLayer Tienes razón, funciona como se esperaba en ese escenario. Pero imagine la situación en la que un script determinado que usa ese enfoque se ejecuta en un lugar con una zona horaria diferente. La salida, aunque técnicamente correcta, puede verse mal dependiendo de la información proporcionada por el +formatargumento. Por ejemplo, en mi sistema, las salidas mismo comando: local: 2019-03-02 20:05:39(si añado %:zque +format, se hace evidente que la información es correcta y la discrepancia se debe a las zonas horarias).
nxnev

@BLayer En mi humilde opinión, un mejor enfoque general sería utilizar la misma zona horaria para ambos -dy para +formatUnix, como dije en la respuesta, para evitar resultados inesperados.
nxnev

1
@nxnev De acuerdo, si está escribiendo un script para ser publicado / compartido. Se supone que las palabras iniciales "si conoce su propia zona horaria", además de tener un significado literal, implican un uso casual / personal. Alguien que publica un guión que se basa en algo tan endeble como saber solo el tz de su propio sistema probablemente no debería estar en el juego de intercambio de guiones. :) Por otro lado, uso / usaría mi propio comando en todos mis entornos personales.
B Layer

4

GNU dateadmite aritmética de fecha simple, aunque los cálculos de tiempo de época como se muestra en la respuesta de @sudodus a veces son más claros (y más portátiles).

El uso de +/- cuando no hay una zona horaria especificada en la marca de tiempo desencadena un intento de hacer coincidir una zona horaria a continuación, antes de analizar cualquier otra cosa.

Aquí hay una forma de hacerlo, use "ago" en lugar de "-":

$ date -d "2018-12-10 00:00:00 5 hours ago 20 minutes ago 5 seconds ago"
Sun Dec  9 18:39:55 GMT 2018

o

$ date -d "2018-12-10 00:00:00Z -5 hours -20 minutes -5 seconds"
Sun Dec  9 18:39:55 GMT 2018

(Aunque no puede utilizar arbitrariamente "Z", funciona en mi zona, pero eso lo convierte en una marca de tiempo de zona UTC / GMT; use su propia zona o% z /% Z agregando ${TZ:-$(date +%z)}en su lugar a la marca de tiempo).

Agregar términos de tiempo extra de estos formularios ajusta el tiempo:

  • "Hace 5 horas" resta 5 horas
  • "4 horas" agregar (implícito) 4 horas
  • "3 horas en adelante" agregar (explícito) 3 horas (no compatible con versiones anteriores)

Se pueden usar muchos ajustes complejos, en cualquier orden (aunque los términos relativos y variables como "14 semanas, por lo tanto, el lunes pasado" piden problemas ;-)

(También hay otro pequeño beartrap aquí, datesiempre dará una fecha válida, por lo que date -d "2019-01-31 1 month"da 2019-03-03, al igual que "el próximo mes")

Dada la gran variedad de formatos de hora y fecha admitidos, el análisis de la zona horaria es necesariamente descuidado: puede ser un sufijo de una o varias letras, una hora u hora: desplazamiento de minutos, un nombre "América / Denver" (o incluso un nombre de archivo en el caso de la TZvariable).

Su 2018-12-10T00:00:00versión no funciona porque "T" es solo un delimitador, no una zona horaria, agregar "Z" al final hace que el trabajo (sujeto a la corrección de la zona elegida) también se espere.

Ver: https://www.gnu.org/software/tar/manual/html_node/Date-input-formats.html y en particular la sección 7.7.


1
Otras opciones incluyen: date -d "2018-12-10 00:00:00 now -5 hourso today, o 0 houren lugar de now, cualquier cosa que se dice dateque el testigo después de que el tiempo no es un desplazamiento de zona horaria.
Stéphane Chazelas

1
O, en aras de la minuciosidad, no deje nada después de la fecha y hora reordenando args: fecha -d "-5 horas 2018-12-10 00:00:00" `
Capa B

2

Esta solución es fácil de entender, pero un poco más complicada, así que la muestro como un shellscript.

  • convertir a 'segundos desde 1970-01-01 00:00:00 UTC'
  • suma o resta la diferencia
  • convertir de nuevo a un formato legible por humanos con una datelínea de comando final

Shellscript:

#!/bin/bash

startdate="2018-12-10 00:00:00"

ddif="0"          # days
diff="-5:-20:-5"  # hours:minutes:seconds

#-----------------------------------------------------------------------------

ss1970in=$(date -d "$startdate" "+%s")  # seconds since 1970-01-01 00:00:00 UTC
printf "%11s\n" "$ss1970in"

h=${diff%%:*}
m=${diff#*:}
m=${m%:*}
s=${diff##*:}
difs=$(( (((ddif*24+h)*60)+m)*60+s ))
printf "%11s\n" "$difs"

ss1970ut=$((ss1970in + difs))  # add/subtract the time difference
printf "%11s\n" "$ss1970ut"

date -d "@$ss1970ut" "+%Y-%m-%d %H:%M:%S"
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.