Subtrair hora usando data e bash

18

Todas as outras perguntas na rede SE lidam com cenários em que a data é assumida como sendo now( Q ) ou onde apenas uma data é especificada ( Q ).

O que eu quero fazer é fornecer uma data e hora e depois subtrair uma hora.
Aqui está o que eu tentei primeiro:

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

Isso resulta em 2018-12-10 06:39:55- Adicionou 7 horas. Subtraído em 20:05 minutos.

Depois de ler a página mane , pensei em corrigi-lo com isso:infodate

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

Mas, mesmo resultado. De onde ele tira as 7 horas?

Eu tentei outras datas também porque pensei que talvez tivéssemos 7200 segundos bissextos naquele dia, quem sabe lol. Mas mesmos resultados.

Mais alguns exemplos:

$ 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

Mas aqui isso se torna interessante. Se eu omitir o tempo de entrada, ele funciona bem:

$ 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

o que estou perdendo?

Atualização: adicionei um Zno final e isso mudou o comportamento:

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

Ainda estou confuso. Não há muito sobre isso na página de informações do GNU sobre a data.

Suponho que esse é um problema de fuso horário, mas citando o Wiki do calendário na ISO 8601 :

Se nenhuma informação de relação UTC for fornecida com uma representação de hora, presume-se que a hora esteja na hora local.

Qual é o que eu quero. Minha hora local também está definida corretamente. Não sei ao certo por que o date iria mexer com o fuso horário, neste simples caso de eu fornecer um datetime e querer subtrair algo dele. Não deve subtrair as horas da cadeia de datas primeiro? Mesmo que ele a converta primeiro em uma data e depois faça a subtração, se eu deixar de fora quaisquer subtrações, obtenho exatamente o que quero:

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

Então, se esse é realmente um problema de fuso horário, de onde vem essa loucura?

confete
fonte

Respostas:

20

Esse último exemplo deve ter esclarecido as coisas para você: fusos horários .

$ 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 a saída varia claramente de acordo com o fuso horário, eu suspeitaria de algum padrão não óbvio adotado para uma sequência de tempo sem um fuso horário especificado. Testando alguns valores, parece ser UTC-05: 00 , embora não tenha certeza do que seja.

$ 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

É usado apenas ao executar aritmética de datas.


Parece que o problema aqui é que não- 2 hours é considerado aritmético, mas como um especificador de fuso horário :

# 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

Portanto, não apenas não está sendo feita nenhuma aritmética, parece haver um ajuste de 1 hora no horário de verão , levando a um tempo um tanto absurdo para nós.

Isso também vale para adição:

# 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

Depurando um pouco mais, a análise parece ser: 2019-01-19T05:00:00 - 2( -2sendo o fuso horário) e hours(= 1 hora), com uma adição implícita. Torna-se mais fácil ver se você usa minutos:

# 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

Então, bem, a aritmética da data está sendo feita, mas não a que pedimos. ¯ \ (ツ) / ¯

Olorin
fonte
11
@ confetti faz sim; Acho que este fuso horário padrão é utilizado apenas quando a adição / subtração (comparando 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
Pode ser um bug possível date, conforme o padrão ISO 8601, se nenhum fuso horário for fornecido, a hora local (fuso horário) deve ser assumida, o que, no caso de uma operação aritmética, não é. Problema muito estranho para mim.
Confetti
11
O @confetti encontrou o problema: - 2 hoursé considerado o especificador de fuso horário aqui.
Olorin
2
Uau. Agora que de alguma forma faz sentido. Bem, pelo menos, explica isso. Muito obrigado por descobrir isso. Para mim, parece que o analisador precisa de uma atualização, porém, com um formato e um espaço em branco assim, você não deseja especificar o fuso horário por `- 2 horas 'e certamente está causando confusão. Se eles não quiserem atualizar o analisador, pelo menos o manual deve receber uma observação sobre isso.
confetti
11
Hoje eu aprendi sobre a --debugopção de data ! Ótima explicação.
Jeff Schaller
6

Funciona corretamente quando você converte a data de entrada em ISO 8601 primeiro:

$ 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
pLumo
fonte
Obrigado, isso realmente funciona, mas há alguma explicação para isso? Adicionei mais algumas informações sobre a questão da ISO 8601 à minha pergunta e realmente não vejo onde isso dateiria atrapalhar, pois sem subtrações fornecidas, o fuso horário fica intocado e tudo é o esperado também, sem precisar fornecer nenhum informações ou conversão de fuso horário.
Confetti
Eu não posso dizer, desculpe. Se outra pessoa pudesse responder, eu ficaria feliz porque também estava pensando.
PLumo
Eu ficaria feliz também, mas vou aceitar isso por enquanto, porque ele corrige meu problema!
Confetti
3
Isso funciona porque date -Itambém emite o especificador de fuso horário (por exemplo 2018-12-10T00:00:00+02:00) que corrige o problema descrito na resposta de Olorin
ilkkachu
6

TLDR: Isso não é um bug. Você acabou de encontrar um dos comportamentos sutis, mas documentados, de date. Ao executar a aritmética da hora date, use um formato independente do fuso horário (como hora do Unix) ou leia com muita atenção a documentação para saber como usar corretamente este comando.


O GNU dateusa as configurações do sistema (a TZvariável de ambiente ou, se não estiver definido, o padrão do sistema) para determinar o fuso horário da data alimentada com a opção -d/ --datee a data relatada pelo +formatargumento. A --dateopção também permite substituir o fuso horário por seu próprio argumento de opção, mas não substitui o fuso horário de +format. Essa é a raiz da confusão, IMHO.

Considerando que meu fuso horário é UTC-6, compare os seguintes 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

O primeiro usa meu fuso horário para ambos -d e +format. O segundo usa o UTC para -dmas meu fuso horário para +format. O terceiro usa o UTC para ambos.

Agora, compare as seguintes operações 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

Mesmo que o horário do Unix esteja me dizendo a mesma coisa, o horário "Normal" difere por causa do meu próprio fuso horário.

Se eu quisesse fazer a mesma operação, mas usando exclusivamente meu fuso horário:

$ 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
nxnev
fonte
4
Promovido por mencionar época / época do Unix, que imo é a única maneira sensata de fazer a aritmética do tempo
Rui F Ribeiro
11
TL; DR Se você apenas sabe o seu horário local em relação ao horário do Zulu (por exemplo, a costa oeste dos EUA é de -8 horas ou -08:00), basta anexá-lo à data e hora ... nada mais para mexer. Exemplo: date -d '2019-02-28 14:05:36-08:00 +2 days 4 hours 3 seconds' +'local: %F %T'fornece local: 2019-03-02 18:05:36
Camada B
11
@BLayer Você está certo, funciona como esperado nesse cenário. Mas imagine a situação em que um determinado script usando essa abordagem é executado em um local com um fuso horário diferente. A saída, embora tecnicamente correta, pode parecer errada, dependendo das informações fornecidas pelo +formatargumento. Por exemplo, no meu sistema, as saídas mesmo comando: local: 2019-03-02 20:05:39(se eu adicionar %:za +format, torna-se óbvio que a informação está correta e a discrepância é devido aos fusos horários).
Nxnev 01/03/19
@BLayer IMHO, uma abordagem geral melhor seria usar o mesmo fuso horário para ambos -de para o +formatUnix, como eu disse na resposta, para evitar resultados inesperados.
Nxnev 01/03/19
11
@nxnev Concordo, se você estiver escrevendo um script para ser publicado / compartilhado. As palavras de abertura "se você conhece seu próprio fuso horário", além de ter um significado literal, devem implicar uso casual / pessoal. Alguém publicando um script confiando em algo tão frágil como saber apenas o tz de seu próprio sistema provavelmente não deveria estar no jogo de compartilhamento de scripts. :) Por outro lado, eu usaria / usaria meu próprio comando em todos os meus ambientes pessoais.
Camada B,
4

O GNU datesuporta aritmética simples de data, embora os cálculos de época, como mostrado na resposta do @sudodus, às vezes sejam mais claros (e mais portáteis).

O uso de +/- quando não há fuso horário especificado no carimbo de data / hora aciona uma tentativa de corresponder a um fuso horário a seguir, antes que qualquer outra coisa seja analisada.

Aqui está uma maneira de fazer isso, use "ago" em vez 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

ou

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

(Embora você não possa usar "Z" arbitrariamente, ele funciona na minha zona, mas isso o torna um carimbo de data / hora da zona UTC / GMT - use sua própria zona ou% z /% Z acrescentando ${TZ:-$(date +%z)}o carimbo de data / hora.)

A adição de termos de tempo extra desses formulários ajusta o tempo:

  • "5 hours ago" subtrai 5 horas
  • "4 horas" adicionar (implícito) 4 horas
  • "Daqui a 3 horas" adicione (explícito) 3 horas (não suportado em versões mais antigas)

Muitos ajustes complexos, em qualquer ordem, podem ser usados ​​(embora termos relativos e variáveis ​​como "14 semanas depois da segunda-feira" estejam pedindo problemas ;-)

(Há outra pequena armadilha aqui também, datesempre fornecerá uma data válida, assim date -d "2019-01-31 1 month"como 2019-03-03, assim como "no próximo mês")

Dada a grande variedade de formatos de data e hora suportados, a análise de fuso horário é necessariamente superficial: pode ser um sufixo único ou com várias letras, um deslocamento de hora ou hora: minuto, um nome "America / Denver" (ou até mesmo um nome de arquivo no caso da TZvariável).

Seu 2018-12-10T00:00:00 versão não funciona porque "T" é apenas um delimitador, não um fuso horário. A adição de "Z" ao final faz com que esse trabalho (sujeito à correção da zona escolhida) também seja esperado.

Veja: https://www.gnu.org/software/tar/manual/html_node/Date-input-formats.html e, em particular, a seção 7.7.

mr.spuratic
fonte
11
Outras opções incluem: date -d "2018-12-10 00:00:00 now -5 hoursou todayou 0 hourem vez de now, qualquer coisa que diz dateque o token após o tempo não é um deslocamento de fuso horário.
Stéphane Chazelas
11
Ou, por uma questão de rigor, não deixe nada após a data e hora reordenando argumentos: date -d "-5 hours 2018-12-10 00:00:00" `
Camada B
2

Essa solução é fácil de entender, mas um pouco mais complicada, por isso eu a mostro como um shellscript.

  • converter para 'segundos desde 1970-01-01 00:00:00 UTC'
  • adicionar ou subtrair a diferença
  • converter de volta para um formato legível por humanos com uma datelinha 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"
sudodus
fonte