Looping através de datas unix

3

Eu tenho uma tarefa básica, mas não consegui encontrar uma solução adequada para ela. Eu quero iterar intervalo de data desde 2008 para atual e preciso de valores de época para cada iteração do loop. Estou interessado em repetir os anos, metades e meses também. Eu escrevi esse script

#!/bin/bash
initial_date='date -d "2008-02-04 00:00:00 UTC" +%s'
end_date='date +%s'
n=0
until [ $initial_date -gt $end_date ]; do
 echo $initial_date
 let n+=1
 readable_date='date +"%Y-%m-%d %T" -d "1970-01-01 $initial_date sec"'
 echo $readable_date
 readable_date='date -d "$readable_date + $n year"'
 initial_date='date -d "$readable_date" +%s'
done

mas sua saída é bastante estranha para mim:

1202083200
2008-02-04 00:00:00
1233702000
2009-02-03 23:00:00
1265230800
2010-02-03 21:00:00
1296756000
2011-02-03 18:00:00
1328277600
2012-02-03 15:00:00
1359885600
2013-02-03 11:00:00
1391403600
2014-02-03 06:00:00
1422918000
2015-02-02 23:00:00
1454425200
2016-02-02 15:00:00

Por que o ano não é incrementado corretamente? As horas deslocadas me parecem um efeito colateral da conversão unix > > UTC > > unix. Existe algum método direto (sem reconversão) para fazer isso?

PS
E sim, verifiquei this , this e esta pergunta e não encontrei uma maneira clara de fazer isso. Todos eles são baseados no número incremental e na conversão de data com a ajuda dela, o que não parece preciso para mim.

E sim, pensei em adicionar 60*60*24*30*365 à data inicial, mas seria correto? Essa abordagem não considera anos bissextos, meses compostos por 31 dias e assim por diante.

    
por Suncatcher 06.08.2016 / 16:28

2 respostas

3

Data usada:

$ dateA="2008-02-04 00:00:00 UTC"

O primeiro motivo para obter datas com horários alterados é solicitar datas "locais":

$ date -d "$dateA"
Sun Feb  3 19:00:00 EST 2008

O que será corrigido se você especificar um horário UTC:

$ TZ=UTC0 date -d "$dateA"
Mon Feb  4 00:00:00 UTC 2008

Ou melhor (crie o hábito de usá-lo) use a opção -u da data:

$ date -ud "$dateA"
Mon Feb  4 00:00:00 UTC 2008

Um segundo motivo é solicitar "itens relativos" com um sinal:

$ date -ud "$(date +"%Y-%m-%d %T" -ud "$dateA") +1 year"  ### wrong
Tue Feb  3 23:00:00 UTC 2009

$ date -ud "$(date +"%Y-%m-%d %T" -ud "$dateA") 1 year"  ### better
Mon Feb  4 00:00:00 UTC 2008

Mas todos os problemas do tempo (geralmente) desaparecem quando um "fuso horário" (-z) é usado:

$ date -ud "$(date +"%Y-%m-%d %T %z" -ud "$dateA") +1 year"  ### Best
Wed Feb  4 00:00:00 UTC 2009

$ date -ud "$(date +"%Y-%m-%d %T %z" -ud "$dateA") 1 year"  ### Preferred
Wed Feb  4 00:00:00 UTC 2009

O problema está relacionado à maneira como uma string de data é analisada, se o valor de TZ estiver ausente, o +1 pode ser interpretado como um valor de fuso horário:

$ date -ud "2008-02-04 00:00:00 +3 year"
Tue Feb  3 21:00:00 UTC 2009

Mesmo que um valor de ambiente para TZ tenha sido definido:

$ TZ=UTC0 date -ud "2008-02-04 00:00:00 +3 year"
Tue Feb  3 21:00:00 UTC 2009

Sim, o ano foi aumentado uma vez (2009 em vez de 2008), mas o +3 foi usado para uma alteração do tempo apresentado (não o pretendido).

Repito: o problema é que +3 pode ser analisado como um valor de fuso horário.

Além disso, para converter (época) segundos em datas, a data GNU pode usar "@"

$ dateAsec="$( date -ud "$dateA" +"%s" )

$ date -ud @"$dateAsec"
Mon Feb  4 00:00:00 UTC 2008

O script (usando uma função (newdate)) pode ser escrito assim:

#!/bin/bash
dateI="$(date -ud "2008-02-04 00:00:00 UTC" +%s)"
dateF="$(date -u +%s)"

newdate(){ date -ud "$(date +"%Y-%m-%d %T %z" -ud @"$dateI") $1 year" +"%s"; }

n=0
while 
    dateN="$( newdate "$n" )"
    (( dateN < dateF ));
do
    dateNhuman="$(date +"%Y-%m-%d %T %z" -ud @"$dateN")"
    echo "dateN=$dateN --- $dateNhuman ---> $dateF $(( $dateF - $dateN )) $n"
    (( n++ ))
done

Resultados:

dateN=1202083200 --- 2008-02-04 00:00:00 +0000 ---> 1470543626 268460426 0
dateN=1233705600 --- 2009-02-04 00:00:00 +0000 ---> 1470543626 236838026 1
dateN=1265241600 --- 2010-02-04 00:00:00 +0000 ---> 1470543626 205302026 2
dateN=1296777600 --- 2011-02-04 00:00:00 +0000 ---> 1470543626 173766026 3
dateN=1328313600 --- 2012-02-04 00:00:00 +0000 ---> 1470543626 142230026 4
dateN=1359936000 --- 2013-02-04 00:00:00 +0000 ---> 1470543626 110607626 5
dateN=1391472000 --- 2014-02-04 00:00:00 +0000 ---> 1470543626 79071626 6
dateN=1423008000 --- 2015-02-04 00:00:00 +0000 ---> 1470543626 47535626 7
dateN=1454544000 --- 2016-02-04 00:00:00 +0000 ---> 1470543626 15999626 8

Todos os anos aparecem e tudo parece correto.

    
por 07.08.2016 / 06:23
3

Um iterador em um data relativa do ponto de partida pode fazer mais sentido:

#!/usr/bin/env bash

START='2008-02-04 00:00:00 UTC'
END=$( date +%s )

for yearnum in $( seq 1 999 ); do
  NUDATE=$( date -d "$START + $yearnum year" +%s )
  PUNY_HUMAN_DATE=$( date -d "@$NUDATE" "+%Y-%m-%d %T %Z" )
  if [[ $NUDATE -gt $END ]]; then
    break
  fi
  echo $NUDATE $PUNY_HUMAN_DATE
done

Que resulta em algo como:

-bash-4.1$ bash iter
1233705600 2009-02-04 00:00:00 UTC
1265241600 2010-02-04 00:00:00 UTC
1296777600 2011-02-04 00:00:00 UTC
1328313600 2012-02-04 00:00:00 UTC
1359936000 2013-02-04 00:00:00 UTC
1391472000 2014-02-04 00:00:00 UTC
1423008000 2015-02-04 00:00:00 UTC
1454544000 2016-02-04 00:00:00 UTC
-bash-4.1$ 
    
por 06.08.2016 / 16:42