Cómo analizar las fechas ISO8601 con el comando de fecha de Linux

15

Estoy tratando de usar el comando de fecha para generar una marca de tiempo de archivo que el comando de fecha en sí mismo puede interpretar. Sin embargo, al comando date no parece gustarle su propio resultado, y no estoy seguro de cómo solucionarlo. Caso en punto:

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

La fecha parece estar interpretando la cadena con un desplazamiento de 15 horas. ¿Hay alguna solución conocida para esto?

Editar: este no es un problema de visualización:

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
>>> 

Todavía está apagado por 15 horas cuando se muestra como una marca de tiempo de Unix.

EDITAR # 1

Tal vez debería plantear esta pregunta un poco diferente. Digamos que tengo una lista de marcas de tiempo básicas ISO8601 del formulario:

  • AAAAMMDDThhmm
  • AAAAMMDDThhmmss

¿Cuál es la forma más sencilla de convertirlos a las marcas de tiempo Unix correspondientes?

Por ejemplo:

- 20140103T1422   = 1388787720
- 20140103T142233 = 1388787753
alex.forencich
fuente
1
@drewbenn No puedo tener caracteres especiales en la marca de tiempo. Solo números y letras. Entonces no, no puedo hacer eso, desafortunadamente.
alex.forencich
@sim TZ no está configurado, pero / etc / localtime está vinculado.
alex.forencich
Me estás matando, ¿es esta tu pregunta final? 8-)
slm
20140103T1518no es válida ISO 8601, pierde la parte de zona horaria
Ferrybig

Respuestas:

9

Pide "soluciones conocidas". Aquí hay uno simple:

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

Esto utiliza sedpara reemplazar "T" con un espacio. El resultado es un formato que datecomprende.

Si agregamos segundos a la fecha ISO8601, entonces se daterequieren más cambios:

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

En lo anterior, sedreemplaza la "T" con un espacio y también separa HHMMSS en HH: MM: SS.

John1024
fuente
Funciona para mí si se elimina el +. Sin embargo, no funciona para marcas de tiempo de segunda precisión, solo precisión de minutos.
alex.forencich
@ alex.forencich Respuesta actualizada con segundos de precisión. Avíseme si el formato de segundos que elegí no es el que necesita.
John1024
8

Los documentos de información de coreutils dicen que el "formato extendido" ISO 8601 es compatible.

Deberá agregar guiones, dos puntos y una +%zpara que funcione.

$ 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 a su segunda parte de la pregunta ...

Dado que el formato de fecha solo contiene números y símbolos, puede reemplazar cada símbolo con una letra única, por ejemplo, utilizando 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

O podría analizarlo usando los separadores Ty el -o +como, por ejemplo, usando shell ${var%word}y ${var#word}expansión

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

o usando bashla coincidencia de expresiones regulares

$ 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

o Perl, Python, etc. etc.

Mikel
fuente
La marca de tiempo no puede tener caracteres especiales. ¿Conoces una buena manera de volver a agregarlos automáticamente?
alex.forencich
6

Los coreutils de GNU solo han admitido fechas ISO 8601 como entrada desde la versión 8.13 (lanzada el 2011-09-08). Debes estar usando una versión anterior.

En versiones anteriores, debe reemplazarlo Tpor un espacio. De lo contrario, se interpreta como una zona horaria militar de los EE . UU .

Incluso en versiones recientes, solo se reconoce la forma totalmente puntuada, no el formato básico con solo dígitos y una Ten el medio.

# 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- deja de ser malvado'
fuente
2

Noté esta nota en la página del 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.

No es concluyente, pero no muestra explícitamente una cadena de formato de hora que incluya lo Tque está intentando, para [ISO 8601]. Como indicó la respuesta de @Gilles , el soporte de ISO 8601 en GNU CoreUtils es relativamente nuevo.

Volver a formatear la cadena

Puedes usar Perl para reformular tu cadena.

Ejemplo:

$ 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

Puede hacer que esto maneje tanto las cadenas que incluyen segundos como las que no.

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
fuente
@ alex.forencich: un comando alternativo que manejará ambos formatos de hora. Hazme un favor y elimina los comentarios anteriores que ya no son relevantes.
slm
1

De acuerdo con la página de manual de fecha, el formato que genera no es el mismo que el que se dateespera como entrada. Esto es lo que dice la página del manual:

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

Entonces podrías hacerlo así:

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

Porque en las variables que se utilizan para definir la cadena de salida, +%m%d%H%M%Ysería igual a lo que espera como entrada.

repetición
fuente
Entonces, ¿puede proporcionar un comando para asignar una fecha de formato ISO8601 en qué fecha requiere? Las marcas de tiempo almacenadas reales deben estar en formato ISO8601 para que se puedan ordenar por fecha.
alex.forencich