使用 perl 在数据流中将 UNIX 纪元时间转换为 ISO8601

convert UNIX epoch time to ISO8601 in a stream of data with perl

我正在使用 API 和 curl --write-out '\n%{http_code}\t=%{time_total}s\n',它提供有关字段的日期信息 UNIX 纪元时间 而不是 ISO8601,这让人很难理解是怎么回事。

示例输入:

{"message":"Domains list","list":[{"domain":"example.org","created":"1443042000","regtill":"1632430800"}]}
200 =0.126406s
{"list":[{"d":"abc","c":"1443042000"},{"d":"xyz","c":"1000000000"}]}
200 =0.126406s

有没有办法在这个数据流中找到任何看起来像 UNIX 纪元时间的东西(例如,代表最近的时间(引号中的任何 10 位数字都应该这样做(10000000002001-09-09 根据 env TZ=GMT-3 date -r1000000000 +%Y-%m-%dT%H%M%S%zBSD date(1)))),并将其全部转换为类似 ISO8601 的日期,在 perl 或 BSD awk 没有任何广泛的依赖关系来进行转换?

期望的输出(有或没有时区偏移):

{"message":"Domains list","list":[
    {"domain":"example.org","created":"2015-09-24T000000+0300","regtill":"2021-09-24T000000+0300"}]}
200 =0.126406s
{"list":[
    {"d":"abc","c":"2015-09-24T000000+0300"},
    {"d":"xyz","c":"2001-09-09T044640+0300"}]}
200 =0.126406s

使用日期命令。这个命令:
date -d '@1443042000'
产生这个答案:
Wed 23 Sep 2015 03:00:00 PM MDT

您可以使用 jq,这是一个很好的操作工具 JSON。

jq '
   ( .. | select(type == "string") | select(try tonumber | . > 1000000000) ) |=
      ( tonumber | todateiso8601 )
'

Demo

在标准输入上读取您的 JSON 的 Perl 脚本,因此它可以将 curl 输出传送到它:

#!/usr/bin/env perl
use strict;
use warnings;
use feature qw/say/;
use experimental qw/postderef/;
use Time::Piece;
# Install through your OS package manager if provided or favorite
# CPAN client. Or use a different JSON module you like better; perl
# has quite a few available. Worst case if you can't install any
# extra modules is to use core JSON::PP
use JSON::MaybeXS; 

my $json = JSON::MaybeXS->new->utf8;

my $rawdata = do { local $/; <STDIN> };
my $data = $json->decode($rawdata);

for my $domain ($data->{list}->@*) {
    $domain->{created} = localtime($domain->{created})->datetime;
    $domain->{regtill} = localtime($domain->{regtill})->datetime;
}

say $json->encode($data);

示例:

$ ./convtimes < input.json
{"message":"Domains list","list":[{"created":"2015-09-23T14:00:00","regtill":"2021-09-23T14:00:00","domain":"example.org"}]}

simbabque 在评论中提供了迄今为止最好的片段:

$ perl -MTime::Piece -pe 's/(\d{10,})/{localtime()->datetime}/ge' <<<'{"message":"Domains list","list":[{"domain":"example.org","created":"1443042000","regtill":"1632430800"}]}' - you can pipe your curl through this. – simbabque 1 hour ago


我进一步改编如下:

  • 修改正则表达式以使用正向后视和前视,对于 \w":"(单词、引号、冒号、引号)和 "\W{2}(两次引用,而不是单词),(?<=\w":")(?="\W{2}),分别将 UNIX 纪元时间匹配为某个关键字的 JSON 值,并在随机其他数据流入脚本时避免任何可能的误报;
  • 将 UNIX 纪元时间限制为 10 位数字,这应该涵盖 2001 年到 2286 年之间的日期时间段:
    • env TZ=GMT perl -MTime::Piece -e 'print localtime(1000000000)->datetime, "Z/", localtime(9999999999)->datetime, "Z\n"'
    • 2001-09-09T01:46:40Z/2286-11-20T17:46:39Z
  • 使用一些额外的正则表达式为域数组的每个条目插入换行符;

最简单的片段:

curl ... \ 
  | perl -MTime::Piece -pe's#(\d{10})#localtime()->datetime#ge'

仅针对 opening/closing 引号添加 lookbehind/lookahead:

curl ... \ 
  | perl -MTime::Piece -pe's#(?<=")(\d{10})(?=")#localtime()->datetime#ge'

指定输入数据的时区,更严格 JSON-特定 lookbehind/lookahead,并为列表中的每个域插入换行符,以使其更易于用户阅读:

curl ... \ 
  | env TZ=GMT-3 perl -MTime::Piece -p \
    -e 's#(?<=":\[|["\d]},)(?={")#\n\t#g;' \
    -e 's#(?<=\w":")(\d{10})(?="\W{2})#localtime()->datetime#ge;'

整体转换最终解决方案的示例测试 运行 -- 适用于任何数据流,忽略任何不是 JSON 的内容,永远不会在无效 [=64] 上给出任何错误=]:

% printf '{"list":[{"d":"abc","c":"1443042000"},{"d":"xyz","c":"1000000000"}]}\n' \
    | env TZ=GMT-3 perl -MTime::Piece -p \
    -e 's#(?<=":\[|["\d]},)(?={")#\n\t#g;' \
    -e 's#(?<=\w":")(\d{10})(?="\W{2})#localtime()->datetime#ge;'
{"list":[
    {"d":"abc","c":"2015-09-24T00:00:00"},
    {"d":"xyz","c":"2001-09-09T04:46:40"}]}
% 

此解决方案比其他假定输入有效 JSON 的解决方案更灵活,因为它也可用于使用单个 curl 命令进行更多操作的情况而不是单个请求,这不是有效的 JSON,因为 JSON 只能有一个根元素。它也适用于 curl --write-out(例如,curl -w '\n%{http_code}\t=%{time_total}s\n'),同样,JSON 也是无效的。它也更灵活,因为它甚至不假设输入数据是任何特定格式——任何在 \w":""\W{2} 之间的 10 位数字都将自动从 UNIX Epoch timeISO8601 完全按照问题中的指定。

这个 awk 脚本不是很好,但它确实产生了预期的输出(可能有一些缺陷?)。

#!/usr/bin/env awk -f

BEGIN {
    FS=":|,"; OFS=":"; RS="["
} NR==1 {
    $NF="["
}  NR>1 {
    gsub(/"/,"");
    =strftime("%Y-%m-%dT%H%M%S%z",);
    =strftime("%Y-%m-%dT%H%M%S%z}]}",);
    gsub(/:/,"\":\"");
    sub(/{/,"{\"");
    sub(/}/,"}\"")
} 1

或作为一个班轮

awk 'BEGIN {FS=":|,"; OFS=":"; RS="["} NR==1 {$NF="["}  NR>1 {gsub(/"/,""); =strftime("%Y-%m-%dT%H%M%S%z",);=strftime("%Y-%m-%dT%H%M%S%z}]}",); gsub(/:/,"\":\""); sub(/{/,"{\""); sub(/}/,"}\"")}1' input_file

输出

{"message":"Domains list":"list":[
{"domain":"example.org":"created":"2015-09-23T220000+0100":"regtill":"2021-09-23T220000+0100}"]}