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?


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. ¯ \ (ツ) / ¯


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

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

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.


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"
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.