Como analisar datas ISO8601 com o comando linux date

15

Estou tentando usar o comando date para gerar um registro de data e hora do arquivo que o próprio comando date possa interpretar. No entanto, o comando date não parece gostar de sua própria saída e não tenho certeza de como contornar isso. Caso em questão:

sh-4.2$ date
Fri Jan  3 14:22:19 PST 2014
sh-4.2$ date +%Y%m%dT%H%M
20140103T1422
sh-4.2$ date -d "20140103T1422"
Thu Jan  2 23:22:00 PST 2014

A data parece interpretar a sequência com um deslocamento de 15 horas. Existem soluções alternativas conhecidas para isso?

Edit: este não é um problema de exibição:

sh-4.2$ date +%s
1388791096
sh-4.2$ date +%Y%m%dT%H%M
20140103T1518
sh-4.2$ date -d 20140103T1518 +%s
1388737080
sh-4.2$ python
Python 3.3.3 (default, Nov 26 2013, 13:33:18) 
[GCC 4.8.2] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> 1388737080 - 1388791096
-54016
>>> 54016/3600
15.004444444444445
>>> 

Ainda está desativado por 15 horas quando exibido como um carimbo de data / hora unix.

EDIT # 1

Talvez eu deva fazer essa pergunta de maneira um pouco diferente. Digamos que eu tenha uma lista dos carimbos de data / hora básicos ISO8601 do formulário:

  • AAAAMMDDThhmm
  • AAAAMMDDThhmmss

Qual é a maneira mais simples de convertê-los nos carimbos de data e hora Unix correspondentes?

Por exemplo:

- 20140103T1422   = 1388787720
- 20140103T142233 = 1388787753
alex.forencich
fonte
1
@drewbenn Não posso ter caracteres especiais no carimbo de data e hora. Apenas números e letras. Então não, infelizmente não posso fazer isso.
alex.forencich
@sim TZ não está definido, mas / etc / localtime está vinculado.
alex.forencich
Você está me matando, esta é sua pergunta final? 8-)
slm
20140103T1518não é válido ISO 8601, ele perde a parte do fuso horário #
Ferrybig 19/12/16

Respostas:

9

Você solicita "soluções alternativas conhecidas". Aqui está um simples:

$ date -d "$(echo 20140103T1422 | sed 's/T/ /')"
Fri Jan  3 14:22:00 PST 2014

Isso usa sedpara substituir "T" por um espaço. O resultado é um formato que dateentende.

Se adicionarmos segundos à data ISO8601, serão datenecessárias mais alterações:

$ date -d "$(echo 20140103T142211 | sed -r 's/(.*)T(..)(..)(..)/\1 \2:\3:\4/')"
Fri Jan  3 14:22:11 PST 2014

Acima, sedsubstitui o "T" por um espaço e também separa o HHMMSS em HH: MM: SS.

John1024
fonte
Funciona para mim se o + for excluído. No entanto, ele não funciona para carimbos de data e hora de segunda precisão, apenas precisão minuciosa.
alex.forencich
@ alex.forencich Resposta atualizada com segundos de precisão. Deixe-me saber se o formato de segundos que eu escolhi não é o que você precisa.
precisa saber é o seguinte
8

A documentação do coreutils informa que o "formato estendido" ISO 8601 é suportado.

Você precisará adicionar hífens, dois pontos e um +%zpara fazê-lo funcionar.

$ date +"%Y-%m-%dT%H:%M:%S%z"
2014-01-03T16:08:23-0800
$ date -d 2014-01-03T16:08:23-0800
Fri Jan  3 16:08:23 PST 2014

Para responder sua segunda parte da pergunta ...

Como o formato da data contém apenas números e símbolos, você pode substituir cada símbolo por uma letra única, por exemplo, usando tr

$ ts="$(date +"%Y-%m-%dT%H:%M:%S%z" | tr -- '-:+' 'hcp')"; echo "$ts"
2014h01h03T16c18c04h0800
$ date -d "$(echo "$ts" | tr -- 'hcp' '-:+')"
Fri Jan  3 16:18:04 PST 2014

Ou você pode analisá-lo usando oe Tos -ou +como separadores, por exemplo, usando shell ${var%word}e ${var#word}expansão

$ ts="$(date +"%Y%m%dT%H%M%S%z")"; echo "$ts"
20140103T162228-0800
$ date=${ts%T*}; time=${ts#*T}
etc.    

ou usando bashcorrespondência de expressão regular

$ ts="$(date +"%Y%m%dT%H%M%S%z")"; echo "$ts"
20140103T165611-0800
$ [[ "$ts" =~ (.*)(..)(..)T(..)(..)(..)(.....) ]]
$ match=("${BASH_REMATCH[@]}")
$ Y=${match[1]}; m=${match[2]}; d=${match[3]}; H=${match[4]}; M=${match[5]}; S=${match[6]}; z=${match[7]}
$ date -d "$Y-$m-$d"T"$H:$M:$S$z"
Fri Jan  3 16:56:11 PST 2014

ou Perl, Python, etc. etc.

Mikel
fonte
O registro de data e hora não pode conter caracteres especiais. Você conhece uma boa maneira de adicioná-los de volta automaticamente?
alex.forencich
6

Os coreutils do GNU suportam apenas as datas ISO 8601 como entrada desde a versão 8.13 (lançada em 08/09/2011). Você deve estar usando uma versão mais antiga.

Nas versões anteriores, você precisa substituir o Tpor um espaço. Caso contrário, é interpretado como um fuso horário militar dos EUA .

Mesmo nas versões recentes, apenas a forma totalmente pontuada é reconhecida, não o formato básico com apenas dígitos e a Tno meio.

# Given a possibly abbreviated ISO date $iso_date...
date_part=${iso_date%%T*}
if [ "$date_part" != "$iso_date" ]; then
  time_part=${abbreviated_iso_date#*T}
  case ${iso_date#*T} in
    [!0-9]*) :;;
    [0-9]|[0-9][0-9]) time_part=${time_part}:00;;
    *)
      hour=${time_part%${time_part#??}}
      minute=${time_part%${time_part#????}}; minute=${minute#??}
      time_part=${hour}:${minute}:${time_part#????};;
  esac
else
  time_part=
fi
date -d "$date_part $time_part"
Gilles 'SO- parar de ser mau'
fonte
2

Eu notei esta nota na página de manual para date.

DATE STRING
      The --date=STRING is a mostly free format human readable date string
      such as "Sun, 29 Feb 2004 16:21:42 -0800"  or  "2004-02-29
      16:21:42"  or  even  "next Thursday".  A date string may contain 
      items indicating calendar date, time of day, time zone, day of
      week, relative time, relative date, and numbers.  An empty string 
      indicates the beginning of the day.  The date  string  format
      is more complex than is easily documented here but is fully described 
      in the info documentation.

Não é conclusivo, mas não mostra explicitamente uma sequência de formato de hora que inclui o Tque você está tentando, para [ISO 8601]. Como a resposta do @Gilles indicou, o suporte da ISO 8601 no GNU CoreUtils é relativamente novo.

Re-formatar a sequência

Você pode usar o Perl para reformular sua string.

Exemplo:

$ date -d "$(perl -pe 's/(.*)T(\d{2})(\d{2})(\d{2})/$1 $2:$3:$4/' \
    <<<"20140103T142233")"
Fri Jan  3 14:22:33 EST 2014

Você pode fazer isso manipular as duas cadeias que incluem segundos e aquelas que não.

20140103T1422:

$ date -d "$(perl -pe 's/^(.*)T(\d{2})(\d{2})(\d{2})$/$1 $2:$3:$4/ || \
     s/^(.*)T(\d{2})(\d{2})$/$1 $2:$3:00/' <<<"20140103T1422")"
Fri Jan  3 14:22:00 EST 2014

20140103T142233:

$ date -d "$(perl -pe 's/^(.*)T(\d{2})(\d{2})(\d{2})$/$1 $2:$3:$4/ || \
     s/^(.*)T(\d{2})(\d{2})$/$1 $2:$3:00/' <<<"20140103T142233")"
Fri Jan  3 14:22:33 EST 2014
slm
fonte
@ alex.forencich - um comando alternativo que manipula os dois formatos de hora. Faça-me um favor e exclua os comentários acima que não são mais relevantes.
Slm
1

De acordo com a página do manual da data, o formato que você produz não é o mesmo que o dateesperado como entrada. Isto é o que a página de manual diz:

date [-u|--utc|--universal] [MMDDhhmm[[CC]YY][.ss]]

Então você poderia fazer assim:

# date +%m%d%H%M%Y
010402052014
# date 010402052014
Sat Jan  4 02:05:00 EAT 2014

Porque nas variáveis ​​usadas para definir a sequência de saída, +%m%d%H%M%Yseria igual ao que ela espera como entrada.

repetição
fonte
Em seguida, você pode fornecer um comando para mapear uma data no formato ISO8601 para qual data exige? Os registros de data e hora armazenados reais devem estar no formato ISO8601 para que possam ser classificados por data.
alex.forencich