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

时间:2008-10-05 15:05:41

标签: perl datetime date rfc822

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

4 个答案:

答案 0 :(得分:30)

use POSIX qw(strftime);
print strftime("%a, %d %b %Y %H:%M:%S %z", localtime(time())) . "\n";

答案 1 :(得分: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参数 到现在():

DateTime->now( time_zone => $ENV{'TZ'}, ... )

答案 2 :(得分:5)

可以使用strftime完成,但%a(日)和%b(月)以当前语言环境的语言表示。

来自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";

答案 3 :(得分:0)

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

  • 根据RFC822对于%z转换规范的要求,它不能与MS-DOS aka Windows一起使用,该Windows生成诸如“ W. Europe Standard Time”而不是“ +0200”这样的字符串。
  • 它将以RFC822再次在当前语言环境中打印缩写的月份和日期名称,而不是英语。

分别将语言环境切换为“ POSIX”。 “ C”解决了后一个问题,但可能会很昂贵,对于行为良好的代码(后来又切换回以前的语言环境)而言,甚至更高。

但是它也不是完全线程安全的。虽然暂时切换语言环境可以在Perl解释器线程内正常工作,但是当Perl解释器本身在内核线程内运行时会发生竞争。将Perl解释器嵌入到服务器中(例如,在线程化Apache MPM中运行的mod_perl)时,可能就是这种情况。

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

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而是本地时间时更是如此。后者需要检查zoneinfo数据,其中包含当前和历史DST以及当前时区的时区设置。由于这些参数受may be reverted in the future的政治决策影响,因此也容易出错。因此,当不定期更新系统时,依赖于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,您可以避免上面提到的所有问题,同时仍然完全符合标准,因此可以更快地处理。当性能可能对您来说是一个问题时,请使用该解决方案。当您的用户抱怨该软件报告“错误”时区时,请采用第一种解决方案。