将 GMT 日期时间字符串转换为 1000 多个大型 csv 文件中的 utc 纪元

Convert GMT datetime string to utc epoch in 1000s of large csv files

我有 1000 个 csv 文件,其中包含数百万行,这些行具有整数、浮点数、可为空整数和 2 种类型的 GMT 日期时间字符串格式。以下是其中一个文件中此类行的示例:

2/20/2016 3:25,3,,87,340.3456,5/18/2013,5/19/2014,4,6

我对将两种类型的 GMT 日期时间格式字符串转换(就地)为 UTC 纪元的最快方法很感兴趣。

例如,上面的行将被转换为:

1455938740,3,,87,340.3456,1368835200,1400457600,4,6

假设文件是​​隔离的,那么*.csv

都可以收集到

有什么方法可以用 linux 命令做到这一点吗?如果没有,那你有什么建议?

更新答案

感谢@Borodin 的见解,我现在最好的解决方案是这样的:

perl -MTime::Local -plne '
   s|(\d+)\/(\d+)\/(\d+) (\d+):(\d+)|timegm(0,,,,-1,)|ge ;
   s|(\d+)\/(\d+)\/(\d+)|timegm(0,0,0,,-1,)|ge' file.csv

如果可以调试并发现它可以工作,我会像这样将它合并到 GNU Parallel 中:

function doit(){
    tmp=temp_$$
    perl -MTime::Local -plne '
       s|(\d+)\/(\d+)\/(\d+) (\d+):(\d+)|timegm(0,,,,-1,)|ge;
       s|(\d+)\/(\d+)\/(\d+)|timegm(0,0,0,,-1,)|ge' "" >> $tmp && mv $tmp ""
}
export -f doit

find . -name \*.csv -print0 | parallel -0 doit {}

原答案

恐怕我会给你一根非常强大的钓鱼竿(更像是鱼叉)而不是现成的鱼晚餐,但我认为你可以很容易地解决它。

首先,如果你使用Perl中的Time::Local模块,你可以将秒、分、时、日、月、年传递给它,它会告诉你对应的Epoch秒数:

# So, for midnight on 02:10:01 AM 1st May 2016, you can do
perl -MTime::Local -e 'print timelocal(1,10,2,1,5,2016)'
1464743401

其次,如果您使用 -plne 开关启动 Perl,它将有效地将您提供的代码应用到输入文件的每一行并打印结果并为您整理出所有行结尾 - 有点类似于 awk 如何循环输入文件。因此,如果您的文件名为 file.csv 并且如下所示:

2/20/2016 3:25,3,,87,340.3456,5/18/2013,5/19/2014,4,6
2/21/2013 3:25,3,,87,340.3456,4/20/2013,6/20/2015,4,6

而你 运行 一个空程序,它只会回显输入文件:

perl -MTime::Local -plne '' file.csv
2/20/2016 3:25,3,,87,340.3456,5/18/2013,5/19/2014,4,6
2/21/2013 3:25,3,,87,340.3456,4/20/2013,6/20/2015,4,6

如果我们现在进行替换并将所有逗号替换为大象:

perl -MTime::Local -plne 's/,/elephant/g' file.csv
2/20/2016 3:25elephant3elephantelephant87elephant340.3456elephant5/18/2013elephant5/1      9/2014elephant4elephant6
2/21/2013 3:25elephant3elephantelephant87elephant340.3456elephant4/20/2013elephant6/20/2015elephant4elephant6

这似乎可行 - 现在您也可以执行我所说的 "computed replacement" - 我不知道真正的 Perl-folk 怎么称呼它。无论如何,您在替换后使用 e 修饰符标志来执行该代码并计算替换文本:

perl -MTime::Local -plne 's|(\d+)\/(\d+)\/(\d+)|timelocal(0,0,0,,,)|ge' file.csv
1458432000 3:25,3,,87,340.3456,1371510000,1403132400,4,6
1363824000 3:25,3,,87,340.3456,1369004400,1437346800,4,6

而且 - 如果您错过了 - 这就是答案。 (\d+) 是 "one or more digits" 的正则表达式,它在括号中的事实意味着它被捕获了。第一个这样的组被捕获为 </code>,第二个被捕获为 <code> 等等。所以,我基本上是在寻找我保存为 </code> 的一个或多个数字,然后是一个斜线,然后是我捕获为 $2 的 1 个或多个数字,然后是一个斜线和我捕获为 $3 的 1 个或多个数字。然后,在替换部分,我使用捕获的组来制定日期。 <code>g 修饰符意味着我在每一行上都出现了所有事件。

我会让你为 24 小时时间添加更多捕获组并将其放入 timelocal() 调用中。

我给出的捕获组也有点松散 - 你可能需要

\d{1,2}\/\d{1,2}\/\d{4}

或表示日期的 1 或 2 位数字,月份的 1 或 2 位数字以及年份的 4 位数字。你可以查一下!

当你开始工作时,如果你有数千个文件,我建议你使用 GNU Parallel 并行处理文件。试着看看我在这里的其他答案,或者 Ole Tange 写的答案,你会看到类似这样的内容:

function doit(){
    perl -plne '...'  ...
}
export -f doit

find . -name \*.csv -print0 | parallel -0 doit {}

关于就地执行,我认为您需要在 doit() 函数中使用这样的技术。基本上它会写入一个新文件,然后,仅当 Perl 部分有效(&& 执行该位)时,它才会用临时文件覆盖原始文件:

tmp=$(mktemp ...)
perl -plne '...' "" > $tmp && mv $tmp ""

我建议您在执行任何其他操作之前先进行备份 - 这里会出现很多错误。祝你好运!

P.S。如果您编辑问题下的标签并添加 perl,我想一些 Perl 专家会帮助您,也许会对我的建议进行最后润色,并启发 me/us 关于e"computed replacement".

的修饰符

更新

因为 the timegm function from Time::Local 可能比 Time::Piece 提供的字符串解析更快

这是我使用该模块的原始解决方案的重写。输出与原来的相同

use strict;
use warnings 'all';

use Time::Local 'timegm';

while ( <DATA> ) {
    chomp;

    my @fields = split /,/;

    for ( @fields ) {
        next unless m{/};

        my ($mn, $dy, $yr, $h, $m, $s) = (/\d+/g, 0, 0, 0);

        $_ = timegm($s, $m, $h, $dy, $mn-1, $yr);
    }

    print join(',', @fields), "\n";
}

__DATA__
2/20/2016 3:25,3,,87,340.3456,5/18/2013,5/19/2014,4,6

输出

1455938700,3,,87,340.3456,1368835200,1400457600,4,6



原版post

Time::Piece 模块很小而且速度很快。这是一个转换示例数据的示例程序

算法很简单。任何不包含斜杠 / 的字段都将保留,否则如果还有冒号 : 则假定为 date/time 字段,如果没有 : 则假定为日期字段

use strict;
use warnings 'all';
use feature 'say';

use Time::Piece ();

while ( <DATA> ) {
    chomp;

    my @fields = split /,/;

    for ( @fields ) {
        next unless m{/};

        my $fmt = /:/ ? '%m/%d/%Y %H:%M' : '%m/%d/%Y';
        $_ = Time::Piece->strptime($_, $fmt)->epoch;
    }

    print join(',', @fields), "\n";
}

__DATA__
2/20/2016 3:25,3,,87,340.3456,5/18/2013,5/19/2014,4,6

输出

1455938700,3,,87,340.3456,1368835200,1400457600,4,6

第一个字段 1455938700 与您自己的预期输出 1455938740 相差四十秒。这很奇怪,因为原始数据中没有秒值,1455938700 可以被 60 整除,而 1455938740 不能。所以我坚持我的计算