在 Perl 中将纪元时间转换为标准约定的问题

Posted

技术标签:

【中文标题】在 Perl 中将纪元时间转换为标准约定的问题【英文标题】:Issue converting epoch time to standard convention in Perl 【发布时间】:2012-02-04 14:03:45 【问题描述】:

在 Perl 中,我尝试使用以下代码将包含毫秒纪元时间变量 $adjSendTime 的变量转换为标准约定:

$timeLabel = sprintf("%02d:%02d:%02d", (($adjSendTime/3600000) % 24), (($adjSendTime/60000) % 60), $currentSecond);

问题在于,只要到达第 59 秒,时间的分钟部分就会比应有的时间高一分钟。输出看起来像

11:58:57
11:58:58
11:59:59
11:59:00
11:59:01

$adjSendTime的计算如下:

# Determine number of hours between local time and UTC.
# This code is needed to compare timestamps in local time with those in epoch time.
# This code only works in time zones with current time less than UTC time.
@timeArray = gmtime();
$utcHour = $timeArray[2];
@timeArray = localtime();
$localHour = $timeArray[2];
# calculate number of milliseconds between current time and UTC time
$utcShift = ($utcHour - $localHour + 24) % 24;
$utcShift = $utcShift*60*60*1000;

...

if ($field[$i] =~ /^\[29997]=/) 
  $sendTimestamp = $field[$i];
  $sendTimestamp =~ s/.*=(\d*).*/$1/;
  # convert send time to local time.
  $adjSendTime = $sendTimestamp - $utcShift;

$currentSecond 的计算在代码的两个不同部分中。第一个片段在$FIRST = 1; 时第一次出现在循环中。在执行此 if 语句后,$FIRST 永远不会再次重置为 1。

$second = ($adjSendTime/1000) % 60;
if ($FIRST) 
  $currentSecond = $second;
  $prevSeqId = $seqId;
  $FIRST = 0;

在子例程 resetCounters 中,脚本中计算的每个值都重新初始化为 0。在输入日志文件中的每一秒开始时调用此子例程。

sub resetCounters
# -----------------------------------------------------------
# resets all metrics counters
# -----------------------------------------------------------

  $tps = 0;
  $mps = 0;
  $batch = 0;
  $maxBatch = 0;
  $avgBatch = 0;
  $latency = 0;
  $latencySum = 0;
  $maxLatency = 0;
  $avgLatency = 0;
  $overThreshold = 0;
  $percentOver = 0;
  $zeroLatencyCount = 0;
  $currentSecond = $second;
  @latencies = ();

谁能帮我弄清楚为什么 Perl 会这样做,因为当我通过长手除法找到余数时,我没有这个问题?我意识到我可以使用 datetime 来找到这个计算的小时和分钟,但我仍然有兴趣确定我的计算出了什么问题,这样我希望将来在使用 % 时可以避免这样的问题Perl。

【问题讨论】:

你从哪里得到$currentSecond?看起来你正在以某种方式增加它而不改变$adjSendTime 请显示完整循环的代码。看起来错误出现在您显示的语句之前的语句中。 我必须为我缺乏 Perl 经验向所有人道歉,而且我不能把我所有的代码都放给你们看。公司政策规定我只能在网上发布所需的部分代码来解决问题,而且越少越好.. 【参考方案1】:

既然可以使用DateTime,为什么还要重新发明***?

use strict;
use warnings;
use DateTime;

my $epoch=1254121251.352329;

my $dt = DateTime->from_epoch(epoch=>$epoch,time_zone => "America/Chicago");

print $dt->ymd . " " . $dt->hms . "\n";

输出是:

2009-09-28 02:00:51

【讨论】:

绝对正确。始终使用现有的、经过测试的库例程,尤其是日期/时间编码。 time_zone => 'local' 为当地时间。 他现有的代码暗示他散列$epoch=12541212513523;而不是$epoch=1254121251.3523; @ikegami:my $dt = DateTime->from_epoch(epoch=>($epoch/1000),time_zone => "America/Chicago"); @Matt Pascoe,在拒绝来自 CPAN 的代码的同时接受代码 *** 是无稽之谈。【参考方案2】:

这可以简单地使用核心工具来实现:POSIX's strftimelocaltime 结合使用。

use POSIX qw( strftime );
say strftime('%H:%M:%S', localtime( $mstime/1000 ));

DateTime 是一个很棒的模块,但是对于这项工作来说它是不必要的慢。而且更加冗长。

use DateTime qw( );
my $dt = DateTime->from_epoch(epoch => $mstime/1000, time_zone => 'local');
say $dt->strftime('%H:%M:%S');

【讨论】:

【参考方案3】:

这是一个使用两个内置 perl 函数的简单版本:

my $timeLabel = sprintf "%02d:%02d:%02d", (localtime($adjSendTime/1000))[2,1,0];

对于比这复杂得多的东西,我可能会使用外部模块,但我认为这样做对于这种特殊需求没有多大意义。

【讨论】:

这是最好的答案,因为每个人总是假设您能够使用像 DateTime 这样的外部模块。不幸的是,我的公司对在某些环境中允许使用的外部 API 和代码非常严格。在我工作的环境中,我只能使用 Perl 内置的模块,例如 localtime。

以上是关于在 Perl 中将纪元时间转换为标准约定的问题的主要内容,如果未能解决你的问题,请参考以下文章

如何在 Perl 中将日期转换为纪元时间?

在 Windows 上的 perl 中将纪元转换为本地时间

如何在 Perl 中将 dd/mm/yyyy 转换为纪元时间?

在perl中将日期转换为纪元的最佳方法[关闭]

在文本文件中将纪元转换为标准的bash脚本[重复]

Perl 将自纪元以来的微秒转换为本地时间