使用Perl在不同的日期时间格式间进行转换

5

我们目前正在使用一个第三方API,它以以下格式提供日期时间:

Sat Mar 06 09:00:00 ICST 2010
Fri Feb 19 19:30:00 JDT 2010
Fri Feb 19 19:30:00 PST 2010

然而,我们希望将这些日期时间对象存储在MySQL中的标准日期时间字段中,该字段需要以下格式:

YYYY-MM-DD HH:MM:SS

目前我们正在使用以下代码,对于某些时区(如KDT、JDT和ICST)报告错误:

use Date::Manip;
use DateTime;
use DateTime::Format::DateManip;

my $date = ParseDate($time);
$date = DateTime::Format::DateManip->parse_datetime($date);
eval{ $time = $date->strftime("%Y-%m-%d %H:%M:%S"); };

你能推荐一个更好的Perl代码实现吗,将API中的日期时间对象转换为正确的格式和时间,以便插入到我们的MySQL日期时间字段中?

非常感谢您的帮助和建议!


3
一个好的起点:http://datetime.perl.org/?Modules - PP.
4个回答

6

将时间以格林威治标准时间(GMT)存储在内部。所有操作都在GMT中进行。然后,在最后一刻,就在你即将向用户显示结果时,转换为用户的本地时间。

我建议使用Date::Parse,但是您需要增加其时区偏移量,因为它目前没有印度支那夏令时和日本夏令时等。

#! /usr/bin/perl

use warnings;
use strict;

use Date::Format;
use Date::Parse;

# add timezone offsets
$Time::Zone::Zone{icst} = +7*3600;
$Time::Zone::Zone{jdt}  = +9*3600;

while (<DATA>) {
  chomp;
  warn("$0: failed conversion for $_\n"), next
    unless defined(my $time_t = str2time $_);

  my @t = gmtime($time_t);
  print $_, " => ", strftime("%Y-%m-%d %H:%M:%S", @t), "\n";
}

__DATA__
Sat Mar 06 09:00:00 ICST 2010
Fri Feb 19 19:30:00 JDT 2010
Fri Feb 19 19:30:00 PST 2010

输出:

Sat Mar 06 09:00:00 ICST 2010 => 2010-03-06 02:00:00
Fri Feb 19 19:30:00 JDT 2010 => 2010-02-19 10:30:00
Fri Feb 19 19:30:00 PST 2010 => 2010-02-20 03:30:00

为了支持您想要的查询,请将时间存储在GMT加上偏移量的格式中(即从API的GMT到本地时间)。请注意,下面的代码假设如果str2time可以解析给定的时间,则strptime也可以。将循环更改为

my @dates;
while (<DATA>) {
  chomp;
  warn("$0: failed conversion for $_\n"), next
    unless defined(my $time_t = str2time $_);

  my $zone = (strptime $_)[-1];
  my @t = gmtime($time_t);
  push @dates => [ strftime("%Y-%m-%d %H:%M:%S", @t)
                 , sprintf("%+03d:%02d",
                           int($zone / 3600),
                           int($zone % 3600) / 60)
                 , $_
                 ];
}

使用收集的时间,将其渲染为SQL语句:
print "DROP TABLE IF EXISTS dates;\n",
      "CREATE TABLE dates (date DATETIME, offset CHAR(6));\n",
      "INSERT INTO dates (date,offset) VALUES\n",
        join(",\n\n" =>
          map("  -- $_->[2]\n" .
              "  ('$_->[0]','$_->[1]')", @dates)),
        ";\n",
      "SELECT CONVERT_TZ(date,'+00:00',offset) FROM dates;\n"

输出结果为:
DROP TABLE IF EXISTS dates;
CREATE TABLE dates (date DATETIME, offset CHAR(6));
INSERT INTO dates (date,offset) VALUES
  -- Sat Mar 06 09:00:00 ICST 2010
  ('2010-03-06 02:00:00','+07:00'),

  -- Fri Feb 19 19:30:00 JDT 2010
  ('2010-02-19 10:30:00','+09:00'),

  -- Fri Feb 19 19:30:00 PST 2010
  ('2010-02-20 03:30:00','-08:00');
SELECT CONVERT_TZ(date,'+00:00',offset) FROM dates;

我们可以将其导入到mysql中:

$ ./prog.pl | mysql -u 用户名 -D 数据库名
CONVERT_TZ(date,'+00:00',offset)
2010-03-06 09:00:00
2010-02-19 19:30:00
2010-02-19 19:30:00

@gbacon - 这正是我一直在寻找的。最后一个问题。我必须以与API原始提供的时区相同的时区显示项目的时间。您建议我将时区存储在MySQL表中使用哪种格式,以便于转换,而且我应该使用什么Perl代码来将GMT和时区中存储的日期时间转换为“2010年2月19日星期五下午2:30 JDT”这样的格式? - Russell C.
如果有一种方法可以在MySQL中存储时区,以使我能够使用仅SQL查询(无perl)将存储的日期时间(在GMT中)返回为本地日期时间,那么这可能是更好的解决方案。 - Russell C.
@Russell 是哪个本地时间?与API给出的时间相同还是与托管数据库的机器相同? - Greg Bacon
@gbacon - 与API给出的时间相同。 - Russell C.
@gbacon - 感谢您的帮助!虽然我们使用的最终结果有些不同,但它基于您的建议,并且似乎对我们通过API获取的所有项目都有效。 - Russell C.
@Russell 不用谢!我很高兴我的建议有所帮助。如果你能再接受一个建议,那就请编辑你的问题描述,让它成为对未来搜索者更有价值的资源。 - Greg Bacon

3

在存储日期时,应始终将其存储为UTC。这样,您可以从数据库中获取它们,并根据需要将它们转换为适当的时区以显示给用户。

要正确处理Perl中的日期时间,我强烈推荐使用DateTime套件,其中包含各种输入和输出格式的解析器和格式化程序。

我不确定您的OP中列出的日期是否为标准格式,但如果不是,构建一个DateTime格式会很容易:

my $str = 'Sat Mar 06 09:00:00 ICST 2010';
my ( $month, $day, $hour, $min, $sec, $tz, $year ) = ( $str =~ m{\w{3}\s(\w{3})\s(\d{2})\s(\d{2}):(\d{2}):(\d{2})\s(\w+)\s(\d{4})} );

my $dt = DateTime->new( year => $year, month => $month, day => $day, 
                        hour => $hour, minute => $min, second => $sec, 
                        time_zone => $tz );

然后,您可以使用DateTime::Format::MySQL将日期转换为与MySQL兼容的日期时间格式。


我们目前正在使用DateTime,但由于某些原因,它似乎无法处理某些时区,并且对于KDT、JDT、ICST等报告错误。我不知道是否有一个支持更广泛的模块? - Russell C.
我刚刚在原问题中添加了我们目前正在使用的代码。有什么想法为什么DateTime似乎无法处理某些时区? - Russell C.
1
很遗憾,我不知道如何使其支持这样的时区;它们不在DateTime::TimeZone模块列表中(http://search.cpan.org/~drolsky/DateTime-TimeZone-1.10/)。您可能需要将它们转换为Olson风格的名称,例如KDT => 'Asia/Seoul'。 - friedo
你能提供一个示例Perl代码,将API返回的datetime对象转换为UTC格式,正如你建议的那样吗?如果可能的话,我更喜欢使用一个Perl模块去解析datetime输入字符串,这样会更灵活一些。 - Russell C.
1
为了添加对其他时区的支持,看起来您需要修改zoneinfo文件以添加缺失的时区,并让DateTime-TimeZone工具编译所需的模块以进行自定义构建。http://cpansearch.perl.org/src/DROLSKY/DateTime-TimeZone-1.10/tools/compile-all-zones - daotoad

1

由于三字和四字代码不是唯一的,也不是标准化的,因此您可能需要明确告诉DateTime 构造函数所处理的时区。

我建议您从DateTime::TimeZone开始,如果您在列表中找不到想要的内容,则可能构建新类型以适应您的时区。您还可以找到符合您语法的DateTime格式化程序(有很多),但否则只需使用正则表达式提取日期和时间字段并将其传递给DateTime构造函数即可。

一旦您已经将字符串解析为DateTime对象,并正确设置了时间,将它们转换回MySQL时间戳就没有任何问题:只需使用 DateTime::Format::MySQL:

my $string = DateTime::Format::MySQL->format_datetime($dt);

@Ether - 谢谢!既然一致认为我需要以UTC格式存储这些日期,那么一旦我拥有正确的DateTime对象,你能提供一些Perl代码示例来告诉我该怎么做吗? - Russell C.
你不必将它们存储为UTC时间,尽管如果你需要对日期时间进行一些数学计算,那么存储为UTC时间会提高性能。然而,你需要做的是向DateTime构造函数传递正确的时区字符串,例如:$my $dt = DateTime->new(..., timezone => 'America/Los_Angeles'); - Ether

0

1
@vivin - 能否提供更多细节?我刚刚将我们一直在使用的代码添加到原始问题中。不知道为什么DateTime似乎无法转换某些时区? - Russell C.
我会将数据存储在UTC时间中。但我不确定我是否理解了你的问题 - 你想知道如何处理将日期插入到MySQL数据库中吗? - Vivin Paliath
我刚刚更新了问题以使其更清晰。我的问题基本上是,您能否提供一个更好的Perl实现来将API提供的日期时间转换为正确的日期时间格式,以插入到MySQL日期时间字段中。 - Russell C.
我会选择Friedo所说的,关于DateTime::Format::MySQL(以及时区)。 - Vivin Paliath

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接