Jak parsować daty ISO8601 za pomocą polecenia daty Linuksa

15

Próbuję użyć polecenia date, aby wygenerować znacznik czasu pliku, który samo polecenie date może zinterpretować. Wydaje się jednak, że komenda date nie lubi własnych wyników i nie jestem pewien, jak to obejść. Przykładem:

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

data wydaje się interpretować ciąg z przesunięciem 15 godzin. Czy istnieją jakieś znane obejścia tego problemu?

Edycja: to nie jest problem z wyświetlaniem:

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

Nadal jest wyłączony o 15 godzin, gdy jest wyświetlany jako znacznik czasu unix.

EDYCJA 1

Może powinienem postawić to pytanie nieco inaczej. Powiedzmy, że mam listę podstawowych znaczników czasu ISO8601 formularza:

  • RRRRMMDDGmm
  • RRRRMMDDGgmmmm

Jaki jest najprostszy sposób przekonwertowania ich na odpowiednie znaczniki czasu Unix?

Na przykład:

- 20140103T1422   = 1388787720
- 20140103T142233 = 1388787753
alex.forencich
źródło
1
@drewbenn Nie mogę mieć żadnych znaków specjalnych na znaczniku czasu. Tylko cyfry i litery. Więc nie, nie mogę tego zrobić niestety.
alex.forencich
@sim TZ nie jest ustawiony, ale / etc / localtime jest połączony.
alex.forencich
Zabijasz mnie, czy to twoje ostatnie pytanie? 8-)
slm
20140103T1518nie jest zgodny z ISO 8601, brakuje części strefy czasowej
Ferrybig,

Odpowiedzi:

9

Pytasz o „znane obejścia”. Oto prosty:

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

Służy seddo zastąpienia litery „T” spacją. Rezultatem jest format, który daterozumie.

Jeśli dodamy sekundy do daty ISO8601, datewymaga to dalszych zmian:

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

Powyżej sedzastępuje „T” spacją, a także dzieli HHMMSS na HH: MM: SS.

John1024
źródło
Działa dla mnie, jeśli + zostanie usunięty. Jednak nie działa w przypadku znaczników czasowych drugiej precyzji, a jedynie drobną precyzję.
alex.forencich
@ alex.forencich Odpowiedź zaktualizowana z precyzją sekund. Daj mi znać, jeśli wybrany format sekund nie jest tym, którego potrzebujesz.
John1024
8

Dokumenty Coreutils informują, że obsługiwany jest „rozszerzony format” ISO 8601.

Musisz dodać łączniki, dwukropki i a, +%zaby to działało.

$ 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

Aby odpowiedzieć na drugą część pytania ...

Ponieważ format daty zawiera tylko cyfry i symbole, każdy symbol można zastąpić unikalną literą, np. Używając 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

Możesz też parsować go za pomocą Ti -oraz +jako separatorów, np. Używając powłoki ${var%word}i ${var#word}rozszerzenia

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

lub używając bashdopasowania wyrażeń regularnych

$ 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

lub Perl, Python itp. itd.

Mikel
źródło
Znacznik czasu nie może zawierać żadnych znaków specjalnych. Czy znasz dobry sposób na automatyczne dodanie ich z powrotem?
alex.forencich
6

Coreutils GNU obsługuje tylko daty ISO 8601 jako dane wejściowe od wersji 8.13 (wydanej 08.08.2011). Musisz używać starszej wersji.

W starszych wersjach należy zastąpić Tspacją. W przeciwnym razie jest to interpretowane jako amerykańska wojskowa strefa czasowa .

Nawet w najnowszych wersjach rozpoznawana jest tylko w pełni interpunkcyjna forma, a nie podstawowy format zawierający tylko cyfry i Tśrodek.

# 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- przestań być zły”
źródło
2

Zauważyłem tę notatkę na stronie podręcznika dla 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.

Nie jest to rozstrzygające, ale nie pokazuje jawnie ciągu formatu czasu, który zawiera Tpróbę, dla [ISO 8601]. Jak wskazała odpowiedź @Gilles , obsługa ISO 8601 w GNU CoreUtils jest stosunkowo nowa.

Ponowne formatowanie ciągu

Możesz użyć Perla do przeformułowania łańcucha.

Przykład:

$ 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

Możesz ustawić tę obsługę zarówno ciągów zawierających sekundy, jak i tych, które tego nie robią.

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
źródło
@ alex.forencich - alternatywne polecenie, które obsłuży oba formaty czasu. Wyświadcz mi przysługę i usuń powyższe komentarze, które nie są już istotne.
slm
1

Według strony podręcznika daty format, który wyprowadzasz, nie jest taki sam, jak dateoczekiwany jako dane wejściowe. Oto, co mówi strona podręcznika:

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

Możesz to zrobić w ten sposób:

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

Ponieważ w zmiennych, które są używane do zdefiniowania ciągu wyjściowego, +%m%d%H%M%Ybyłby równy temu, czego oczekuje jako dane wejściowe.

powtórna rozgrywka
źródło
Czy możesz zatem podać polecenie odwzorowania daty w formacie ISO8601 na wymaganą datę? Rzeczywiste przechowywane znaczniki czasu muszą być w formacie ISO8601, aby można je było sortować według daty.
alex.forencich