【问题标题】:How do I elegantly print the date in RFC822 format in Perl?如何在 Perl 中以 RFC822 格式优雅地打印日期?
【发布时间】:2010-09-15 09:16:09
【问题描述】:

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

【问题讨论】:

    标签: perl datetime date rfc822


    【解决方案1】:
    use POSIX qw(strftime);
    print strftime("%a, %d %b %Y %H:%M:%S %z", localtime(time())) . "\n";
    

    【讨论】:

    • 哦,太好了,我不知道核心中有什么东西可以做到这一点。
    • 谢谢,这正是我要求优雅方式时所寻找的:)
    • 嗯,我认为这不再有效了。对于 ActivePerl 5.16,这个 strftime('%a, %d %b %Y %H:%M:%S %z', localtime(time())); 产生 Thu, 14 Nov 2013 09:46:00 E. Australia Standard Time 这绝对是错误的。请参阅 RFC 2822 3.3。日期和时间规范。
    • $ perl -v 这是 perl 5,版本 16,subversion 2 (v5.16.2) 为 darwin-thread-multi-2level $ perl -e 'use POSIX qw(strftime); print strftime("%a, %d %b %Y %H:%M:%S %z", localtime(time())) . "\n";' Sun,2013 年 11 月 24 日 20:02:53 -0500 构建
    • 我现在确实在 Windows 上对其进行了测试,问题实际上在于 Microsoft 的 strftime 实现。 msdn.microsoft.com/en-us/library/fe06s4ak.aspx 说:%z, %Z 时区名称或时区缩写,取决于注册表设置;如果时区未知,则无字符。以下分享了更多细节:social.msdn.microsoft.com/Forums/vstudio/en-US/… 我确实验证了设置环境变量 TZ Active perl 会遵守它。我没有检查实际的注册表设置
    【解决方案2】:

    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'}, ... )
    

    【讨论】:

    • @GuidoFlohr 你能解释一下线程安全问题吗?我不知道有这样的
    • 查看我自己的答案。如果 Perl 解释器在内核线程中运行,另一个线程可能会调用 setlocale()。那么 DateTime::Format::Mail 可以使用非英文的月份和日期名称。通常,临时切换语言环境在该上下文中不是线程安全的。通常不相关,但有时是。
    【解决方案3】:

    可以使用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";
    

    【讨论】:

    • 这个答案比排名最高的一个要好。但是,在 localtime() 内调用 time() 是不必要的(这是默认参数)。
    【解决方案4】:

    仅使用POSIX::strftime() 已经在其他答案和 cmets 中指出了问题:

    • 它不适用于 MS-DOS (也称为 Windows),它会根据 RFC822%z 转换规范的要求生成像“W. Europe Standard Time”而不是“+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 数据。这也容易出错,因为这些参数受制于may be reverted in the future 的政治决策。因此,依赖 zoneinfo 数据的代码很脆弱,并且在系统不定期更新时可能会崩溃。

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

    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,您可以避免上述所有问题,同时仍然完全符合标准,更不用说更快了。当性能可能对您来说是个问题时,请使用该解决方案。当您的用户抱怨软件报告“错误”时区时,请使用第一个解决方案。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2010-09-23
      • 2019-03-30
      • 2011-05-12
      • 2012-11-29
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多