18

如何在 Perl 中以 RFC822 格式优雅地打印日期?

4

4 回答 4

30
use POSIX qw(strftime);
print strftime("%a, %d %b %Y %H:%M:%S %z", localtime(time())) . "\n";
于 2008-10-05T15:12:41.027 回答
15

DateTime 套件为您提供了多种不同的方式,例如:

use DateTime;
print DateTime->now()->strftime("%a, %d %b %Y %H:%M:%S %z");

use DateTime::Format::Mail;
print DateTime::Format::Mail->format_datetime( DateTime->now() );

print DateTime->now( formatter => DateTime::Format::Mail->new() );

更新:要为某个特定时区提供时间,请将 time_zone 参数添加到 now():

DateTime->now( time_zone => $ENV{'TZ'}, ... )
于 2008-10-05T17:40:51.853 回答
5

它可以用 来完成strftime,但它的%a(day) 和%b(month) 用当前语言环境的语言表示。

来自man strftime

%a 根据当前语言环境的缩写工作日名称。
%b 根据当前语言环境的缩写月份名称。

邮件中的日期字段只能使用这些名称(来自rfc2822 DATE AND TIME SPECIFICATION):

day         =  "Mon"  / "Tue" /  "Wed"  / "Thu" /  "Fri"  / "Sat" /  "Sun"

month       =  "Jan"  /  "Feb" /  "Mar"  /  "Apr" /  "May"  /  "Jun" /
               "Jul"  /  "Aug" /  "Sep"  /  "Oct" /  "Nov"  /  "Dec"

因此可移植代码应该切换到C语言环境:

use POSIX qw(strftime locale_h);

my $old_locale = setlocale(LC_TIME, "C");
my $date_rfc822 = strftime("%a, %d %b %Y %H:%M:%S %z", localtime(time()));
setlocale(LC_TIME, $old_locale);

print "$date_rfc822\n";
于 2016-10-20T08:40:42.300 回答
0

仅使用POSIX::strftime()已在其他答案和评论中指出的问题:

  • 它不适用于 MS-DOS aka Windows,它产生像“W. Europe Standard Time”这样的字符串,而不是RFC822%z转换规范所要求的“+0200”。
  • 它将以当前语言环境而不是英语打印缩写的月份和日期名称,这也是RFC822所要求的。

将语言环境切换为“POSIX”。“C”修复了后一个问题,但可能很昂贵,对于后来切换回以前语言环境的行为良好的代码来说更是如此。

但它也不是完全线程安全的。虽然临时切换语言环境在 Perl 解释器线程中不会出现问题,但当 Perl 解释器本身在内核线程中运行时会出现竞争。当 Perl 解释器嵌入到服务器中时(例如mod_perl在线程化的 Apache MPM 中运行),就会出现这种情况。

以下版本不受任何此类限制,因为它不使用任何依赖于语言环境的函数:

sub rfc822_local {
    my ($epoch) = @_;

    my @time = localtime $epoch;

    use integer;

    my $tz_offset = (Time::Local::timegm(@time) - $now) / 60;
    my $tz = sprintf('%s%02u%02u',
                     $tz_offset < 0 ? '-' : '+',
                     $tz_offset / 60, $tz_offset % 60);

    my @month_names = qw(Jan Feb Mar Apr May Jun
                         Jul Aug Sep Oct Nov Dec);
    my @day_names = qw(Sun Mon Tue Wed Thu Fri Sat Sun);

    return sprintf('%s, %02u %s %04u %02u:%02u:%02u %s',
                   $day_names[$time[6]], $time[3], $month_names[$time[4]],
                   $time[5] + 1900, $time[2], $time[1], $time[0], $tz);
}

但应该注意的是,从纪元以来的秒数转换为故障时间(反之亦然)是相当复杂和昂贵的操作,在不处理 GMT/UTC 而是处理本地时间时更是如此。后者需要检查包含当前和历史 DST 以及当前时区的时区设置的 zoneinfo 数据。它也容易出错,因为这些参数受制于未来可能会恢复的政治决策。因此,当系统没有定期更新时,依赖 zoneinfo 数据的代码很脆弱并且可能会中断。

但是,符合 RFC822 的日期和时间规范的目的不是通知其他服务器有关“您的”服务器的时区设置,而是以独立于时区的方式给出当前日期和时间的概念。通过简单地使用 UTC 而不是本地时间,您可以在发送端和接收端节省大量 CPU 周期(它们可以用 CO2 排放量来衡量):

sub rfc822_gm {
    my ($epoch) = @_;

    my @time = gmtime $epoch;

    my @month_names = qw(Jan Feb Mar Apr May Jun
                         Jul Aug Sep Oct Nov Dec);
    my @day_names = qw(Sun Mon Tue Wed Thu Fri Sat Sun);

    return sprintf('%s, %02u %s %04u %02u:%02u:%02u +0000',
                   $day_names[$time[6]], $time[3], $month_names[$time[4]],
                   $time[5] + 1900, $time[2], $time[1], $time[0]);
}

通过对时区进行硬编码,+0000您可以避免上述所有问题,同时仍然完全符合标准,更快地离开。当性能可能对您来说是个问题时,请使用该解决方案。当您的用户抱怨软件报告“错误”时区时,请使用第一个解决方案。

于 2018-10-12T21:23:42.760 回答