Todas as outras questões na rede SE tratam de cenários em que a data é assumida como now
( Q ) ou onde apenas uma data é especificada ( Q ).
O que eu quero fazer é fornecer uma data e hora e, em seguida, subtrair uma hora disso.
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. Em seguida, subtraiu 20:05 minutos.
Depois de ler a página man
e de , pensei ter corrigido com isso:info
date
date -d "2018-12-10T00:00:00 - 5 hours - 20 minutes - 5 seconds"
Mas, mesmo resultado. De onde ele tira as 7 horas?
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 torna-se interessante. Se eu omitir o tempo na entrada, 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 Z
no 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
Eu ainda estou confuso embora. Não há muito sobre isso na página de informações do GNU sobre data.
Suponho que este seja um problema de fuso horário, mas citando o The Calendar Wiki na ISO 8601 :
Se nenhuma informação de relação UTC for fornecida com uma representação de hora, assume-se que a hora está na hora local.
Que é o que eu quero. Minha hora local também está definida corretamente. Não sei por que date mexeria com o fuso horário neste caso simples de eu fornecer um datetime e querer subtrair algo dele. Não deveria subtrair as horas da string de data primeiro? Mesmo que ele converta para uma data primeiro e depois faça a subtração, se eu deixar de fora qualquer subtração, recebo 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 isso realmente for um problema de fuso horário, de onde vem essa loucura?
Esse último exemplo deve ter esclarecido as coisas para você: fusos horários .
Como a saída varia claramente de acordo com o fuso horário, suspeito que algum padrão não óbvio seja usado para uma string de tempo sem um fuso horário especificado. Testando alguns valores, parece ser UTC-05:00 , embora eu não tenha certeza do que é isso.
É usado apenas ao realizar aritmética de data.
Parece que o problema aqui é que não
- 2 hours
é tomado como aritmética, mas como um especificador de fuso horário :Portanto, não apenas não há aritmética sendo feita, mas parece haver um ajuste de 1 hora no
horário de verão , levando a um tempo um tanto sem sentido para nós.Isso também vale para adição:
Depurando um pouco mais, a análise parece ser:
2019-01-19T05:00:00 - 2
(-2
sendo o fuso horário) ehours
(= 1 hora), com uma adição implícita. Fica mais fácil ver se você usar minutos em vez disso:Então, bem, a aritmética de datas está sendo feita, mas não a que pedimos. ¯\(ツ)/¯
Funciona corretamente quando você converte a data de entrada para ISO 8601 primeiro:
O GNU
date
suporta aritmética de data simples, embora os cálculos de tempo de época, como mostrado na resposta do @sudodus, sejam às vezes 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 "-":
ou
(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 anexando
${TZ:-$(date +%z)}
ao carimbo de data/hora.)Adicionando termos de tempo extra destes formulários ajusta o tempo:
Muitos ajustes complexos, em qualquer ordem, podem ser usados (embora termos relativos e variáveis como "14 semanas a partir da última segunda-feira" estejam pedindo problemas ;-)
(Tem outra pequena armadilha de urso aqui também,
date
sempre dará uma data válida, entãodate -d "2019-01-31 1 month"
dá 2019-03-03, assim como "próximo mês")Dada a grande variedade de formatos de hora e data que são suportados, a análise de fuso horário é necessariamente desleixada: pode ser um sufixo de uma ou várias letras, um deslocamento de hora ou hora:minuto, um nome "América/Denver" (ou mesmo um nome de arquivo no caso da
TZ
variável).Sua
2018-12-10T00:00:00
versão não funciona porque "T" é apenas um delimitador, não um fuso horário, adicionar "Z" no final faz com que funcione (sujeito à correção da zona escolhida) conforme o esperado também.Veja: https://www.gnu.org/software/tar/manual/html_node/Date-input-formats.html e em particular a seção 7.7.
TLDR: Isso não é um bug. Você acabou de encontrar um dos comportamentos sutis, mas documentados do
date
. Ao realizar aritmética de tempo comdate
, use um formato independente de fuso horário (como a hora do Unix) ou leia com muito cuidado a documentação para saber como usar corretamente este comando.O GNU
date
usa as configurações do seu sistema (aTZ
variável de ambiente ou, se não definida, o padrão do sistema) para determinar o fuso horário da data alimentada com a opção-d
/--date
e a data relatada pelo+format
argumento. A--date
opção também permite que você substitua 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:
O primeiro usa meu fuso horário para
-d
e+format
. O segundo usa UTC para-d
mas meu fuso horário para+format
. O terceiro usa UTC para ambos.Agora, compare as seguintes operações simples:
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:
Esta solução é fácil de entender, mas um pouco mais complicada, então eu a mostro como um shellscript.
date
linha de comando finalShellscript: