How do I elegantly print the date in RFC822 format in Perl?

How can I elegantly print the date in RFC822 format in Perl?

Answers


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

The DateTime suite gives you a number of different ways, e.g.:

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() );

Update: to give time for some particular timezone, add a time_zone argument to now():

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

It can be done with strftime, but its %a (day) and %b (month) are expressed in the language of the current locale.

From man strftime:

%a The abbreviated weekday name according to the current locale. %b The abbreviated month name according to the current locale.

The Date field in mail must use only these names (from 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"

Therefore portable code should switch to the C locale:

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

Just using POSIX::strftime() has issues that have already been pointed out in other answers and comments on them:

  • It will not work with MS-DOS aka Windows which produces strings like "W. Europe Standard Time" instead of "+0200" as required by RFC822 for the %z conversion specification.
  • It will print the abbreviated month and day names in the current locale instead of English, again required by RFC822.

Switching the locale to "POSIX" resp. "C" fixes the latter problem but is potentially expensive, even more for well-behaving code that later switches back to the previous locale.

But it's also not completely thread-safe. While temporarily switching locale will work without issues inside Perl interpreter threads, there are races when the Perl interpreter itself runs inside a kernel thread. This can be the case, when the Perl interpreter is embedded into a server (for example mod_perl running in a threaded Apache MPM).

The following version doesn't suffer from any such limitations because it doesn't use any locale dependent functions:

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);
}

But it should be noted that converting from seconds since the epoch to a broken down time and vice versa are quite complex and expensive operations, even more when not dealing with GMT/UTC but local time. The latter requires the inspection of zoneinfo data that contains the current and historical DST and time zone settings for the current time zone. It's also error-prone because these parameters are subject to political decisions that may be reverted in the future. Because of that, code relying on the zoneinfo data is brittle and may break, when the system is not regulary updated.

However, the purpose of RFC822 compliant date and time specifications is not to inform other servers about the timezone settings of "your" server but to give its notion of the current date and time in a timezone indepent manner. You can save a lot of CPU cycles (they can be measured in CO2 emission) on both the sending and receiving end by simply using UTC instead of localtime:

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]);
}

By hard-coding the timezone to +0000 you avoid all of the above mentioned problems, while still being perfectly standards compliant, leave alone faster. Go with that solution, when performance could be an issue for you. Go with the first solution, when your users complain about the software reporting the "wrong" timezone.


Need Your Help

Which jar and spring version does <mvc:resource> come?

java spring

In my application, I am using the tag &lt;mvc:resource&gt; but I don't know which jar/version/taglib it comes from:

Django admin - limiting access to objects based on the user logged in

django django-models django-admin

I'm working on creating a simple website for an exhibition. It's intended to use django with django CMS as much as possible - so Django admin site will be used.