Perl 通过支持非标准时区缩写,将ISO 8601 datetime标准化为任何时区

Perl 通过支持非标准时区缩写,将ISO 8601 datetime标准化为任何时区,perl,datetime,timezone,iso8601,Perl,Datetime,Timezone,Iso8601,拥有ISO 8601格式的时间(例如,2019-09-17T16:15:20Z),我如何将这个时间从一个时区转换/标准化到另一个时区(例如,ET=美国东部时间,CT=美国中部时间,PT=美国太平洋时间) 理想的解决方案应接受任何标准和非标准缩写 Perl子程序 注意问题和标题在发布和编辑后被更改,以坚持要求“支持非标准”缩写 但是,一般建议不要使用短名称,因为本答案的第二部分已经详细讨论过了。更重要的是,在这个问题的背景下,这显然是不可能的,因为没有任何程序可以知道任意的缩写(而且也没有任何“

拥有ISO 8601格式的时间(例如,
2019-09-17T16:15:20Z
),我如何将这个时间从一个时区转换/标准化到另一个时区(例如,
ET
=美国东部时间,
CT
=美国中部时间,
PT
=美国太平洋时间)

理想的解决方案应接受任何标准和非标准缩写


Perl子程序


注意问题和标题在发布和编辑后被更改,以坚持要求“支持非标准”缩写

但是,一般建议不要使用短名称,因为本答案的第二部分已经详细讨论过了。更重要的是,在这个问题的背景下,这显然是不可能的,因为没有任何程序可以知道任意的缩写(而且也没有任何“标准”来定义)

一旦提供了到可接受名称的映射,那么这就不再是问题,答案中也考虑到了这一点。所以我保留这个答案,只是做了一些小的修改


用于从字符串构建
DateTime
对象,或者通常是
DateTime::Format::strtime
。然后根据需要使用它

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

use DateTime::Format::ISO8601;
use DateTime;

my $dt_string = shift or die "Usage: $0 datetime-ISO8601\n";

my $fmt = DateTime::Format::ISO8601->new(); 
my $dt = $fmt->parse_datetime($dt_string); 
say $dt->time_zone->name; 

$dt->set_time_zone("America/Chicago"); 
say $dt->time_zone->name;
这使用
DateTime::set_time_zone
转换(更改)对象上的时区


该问题要求将时区缩写名称用于转换。但这也有一个问题:缩写名称不在任何标准中,可能只是本地约定,不验证/使用来自解析器的方法。。。甚至可能模棱两可

这在许多地方都有讨论。在方法
short\u name\u for\u datetime
下,中有一个简短的摘要,表示“short name”(缩写,如请求的缩写)

强烈建议您不要将这些名称用于显示以外的任何内容。这些名称不是官方的,其中许多只是奥尔森数据库维护者的发明。此外,这些名称不是唯一的。例如,在-0500和+1000/+1100处都有一个“EST”

(原来的重点)

仍然可以尝试处理用户身上出现的缩写的一种方法是使用
DateTime::TimeZone
中的
all_name
,以及
grep
输出感兴趣的缩写。比如说,

grep { /P(?:S|D)?T/ } DateTime::TimeZone->all_names
返回(包含)唯一字符串的列表
PST8PDT
。这个字符串在我尝试的所有方法下都有效,并且可以正确地设置
DateTime
对象上的时区。但是,尽管正确,对于
/E(?:S | D)T/
这将返回列表
CET EET EST 5edt MET WET
;不容易使用

很明显,这既不系统也不可靠——正如缩写词不可靠一样

最好是建立某种本地查找,将您的短名称转换为适当的名称,以便您知道它在您的工作中是正确的。然后,可以将添加到OP(后来更改)的存根填充到

use DateTime;
use DateTime::Format::ISO8601;

sub convert_time_zone_for_ISO8601
{
  my ($iso, $tz) = @_;
  # Provide a lookup/mapping that knows locally used abbreviations
  #my $tz_name = convert_local_short_name($tz); 
  my $tz_name = 'America/New_York';             # for a working example

  # Returns a DateTime object (or generate a string in a desired format) 
  return DateTime::Format::ISO8601->new
      -> parse_datetime($iso)
      -> set_time_zone($tz_name);
}

my $dt = convert_time_zone_for_ISO8601('2019-09-17T16:15:20Z', 'ET');

# Sole stringification doesn't include timezone but there are other methods
say $dt->time_zone_short_name;
say $dt->time_zone_long_name;
say $dt->strftime("%F %T %{time_zone_short_name}");
say $dt->strftime("%a, %d %b %Y %H:%M:%S %z");       # RFC822-conformant
(请参阅各种打印方法的文档说明。)

构建返回对象的链式方法使用可接受的时区名称提供解析和时区更改

为了使用缩写,代码显然需要将本地使用的感兴趣的缩写转换(映射)为用户提供的正确时区名称


在上面的代码片段中,有一个占位符子例程,例如,它可以在模块中使用一个哈希值,并在其中正确指定映射,或者更好地使用JSON文件,这样也可以由其他软件管理;或者通过查询数据库表或本地服务或某种方式。

我们可以使用基于
DateTime
的库

use DateTime::TimeZone;
use DateTime::Format::ISO8601;
use DateTime::TimeZone::Alias;
并将所需的非标准缩写设置为别名

DateTime::TimeZone::Alias->set('ET' => 'America/New_York');
DateTime::TimeZone::Alias->set('CT' => 'America/Chicago');
DateTime::TimeZone::Alias->set('PT' => 'America/Los_Angeles');

因此,我们可以直接使用这些时区名称作为有效时区:

print normalizeDateTime('2019-09-17T16:15:20Z', 'ET');
2019-09-17T12:15:20-04:00

在解析部分非常好,但是需要一些帮助来转换为任意时区,这是我提供的


DateTime::TimeZone::Olson
只是
DateTime::TimeZone
的一个替代方案,后者对于命名区域来说速度更快<代码>日期时间::时区对象也可以工作。根据您的缩写确定实际使用的时区已在其他答案中涵盖。

请注意,如果您知道偏移,但不知道政治时区,则时区也可以是偏移(例如
-0500
)。请注意,对以这种方式创建的dt执行任何运算都可能使时区无效。例如,
-0500
可能在冬季在多伦多有效,但多伦多在夏季使用
-0400
。使用
America/Toronto
将全年有效。另一个复杂的问题是,短名称并不唯一。例如,有三个不同的时区可以使用缩写BST,具有非常不同的偏移量。唯一的解决办法是保持你所关心的时区的地图。@grinz好的,是的,我在这个答案中讨论了这一点。我建议用户向读者提供一个映射(“本地查找”到“翻译”):在额外的检查之后,不知道-1是用来做什么的。
sub normalizeDateTime
{
  my $dt = DateTime::Format::ISO8601
             ->new()
             ->parse_datetime($_[0])
             ->set_time_zone($_ = DateTime::TimeZone->new(name => $_[1]));

  $dt . DateTime::TimeZone::offset_as_string($_->offset_for_datetime($dt))
          =~ s/^[+-]00:?00$/Z/r
          =~ s/^([+-]\d{2})(\d{2})$/$1:$2/r;
}
print normalizeDateTime('2019-09-17T16:15:20Z', 'ET');
use strict;
use warnings;
use Time::Moment;
use Role::Tiny ();
use DateTime::TimeZone::Olson 'olson_tz';

my $class = Role::Tiny->create_class_with_roles('Time::Moment', 'Time::Moment::Role::TimeZone');
my $mt = $class->from_string('2019-09-17T16:15:20Z');
my $tz = olson_tz 'America/New_York';
my $in_eastern = $mt->with_time_zone_offset_same_instant($tz);