Perl Tk::Date - toggle by week - perl

I am in the process of writing a GUI which will monitor if various measurements have been made on a weekly basis. I have written various other GUIs for updating measurements results each day onto a database. These GUIs use Tk::Date datewidget that allows me to toggle by days
my $datewidget = $f_filter->Date(-choices=>'today', -datefmt=>'%2d %2m %4y',
-fields=>'date', -varfmt=>'datehash',
-monthmenu=>1, -allarrows=>1,
-value=>'now', -command=>\&populate)->pack(-side=>'left');
This lets me use the up and down arrows to increment/decrement days, change months and year.
What I desire to do in the weekly GUI is have an up and down arrow that will toggle by week only. Eg this week would be 'Mon Nov 4 - Fri Nov 8', next week 'Mon Nov 11 to Fri Nov 15'
I would like to be able to go forwards and backwards several years.
Is there a simple way to do this in perl-Tk::Date or Date::Entry?

Tk::Date and Tk::DateEntry cannot do this out of the box. With Tk::Date, I can propose the following approach:
use -varfmt => 'unixtime' instead of datehash, because the latter does not work well with the ->configure(-value => ...) call used later
set -editable=>0 to remove all arrow buttons created by Tk::Date
create the inc/dec buttons yourself
and make the date calculations using DateTime (see the incweek subroutine here)
Something like the following could work:
use strict;
use Tk;
use Tk::Date;
my $mw = tkinit;
my $datewidget = $mw->Date(-choices=>'today', -datefmt=>'%2d %2m %4y',
-fields=>'date', -varfmt=>'unixtime',
-editable=>0,
-monthmenu=>1,
-value=>'now',
-command=>sub { warn "populate #_" })->pack(-side=>'left');
my $arrowframe = $mw->Frame->pack(-side => 'left');
{
no warnings 'once'; # because of INCBITMAP/DECBITMAP
$arrowframe->FireButton(-bitmap => $Tk::FireButton::INCBITMAP, -command => sub { incweek(+1) })->pack(-side => 'top');
$arrowframe->FireButton(-bitmap => $Tk::FireButton::DECBITMAP, -command => sub { incweek(-1) })->pack(-side => 'top');
}
MainLoop;
sub incweek {
my($inc) = #_;
use DateTime;
my $epoch = $datewidget->get;
my $dt = DateTime->from_epoch(epoch => $epoch);
$dt = $dt->add(weeks => $inc);
$datewidget->configure(-value => $dt->epoch);
}
__END__
Note that $datewidget->get returrns now the epoch time, but using DateTime you can easily convert this into y/m/d values.

Related

perl Data::Dumper to extract key values

I have some perl code which I've written to get weather data/alerts from NOAA.
My code is pretty simple:
use Weather::NOAA::Alert;
use Data::Dumper;
$alert = Weather::NOAA::Alert->new(['TXC301']);
$events = $alert->get_events();
$alert->poll_events();
print Dumper($events);
# #url = (keys %{$VAR1->{'TXC301'}});
# $url = $VAR1->{'TXC301'};
$url = $events->{'TXC301'};
print "URL is $url\n";
# $expires= $events->{'TXC301'}->{$url}->{'expires'};
$expires= $events->{'TXC301'}->{'http://alerts.weather.gov/cap/wwacapget.php?x=TX12516CBE9400.FloodWarning.12516CC068C0TX.MAFFLWMAF.f21e7ce7cf8e930ab73a110c4d912576'}->{'expires'};
print "Expires: $expires\n";
The output:
$VAR1 = {
'TXC301' => {
'http://alerts.weather.gov/cap/wwacapget.php?x=TX12516CBE9400.FloodWarning.12516CC068C0TX.MAFFLWMAF.f21e7ce7cf8e930ab73a110c4d912576' => {
'certainty' => 'Likely',
'senderName' => 'NWS Midland-Odessa (Western Texas and Southeastern New Mexico)',
'urgency' => 'Expected',
'instruction' => 'A FLOOD WARNING MEANS THAT FLOODING IS IMMINENT OR HAS BEEN REPORTED.
STREAM RISES WILL BE SLOW AND FLASH FLOODING IS NOT EXPECTED.
HOWEVER... ALL INTERESTED PARTIES SHOULD TAKE NECESSARY PRECAUTIONS
IMMEDIATELY.
DO NOT DRIVE YOUR VEHICLE INTO AREAS WHERE THE WATER COVERS THE
ROADWAY. THE WATER DEPTH MAY BE TOO GREAT TO ALLOW YOUR CAR TO CROSS
SAFELY.',
'description' => 'THE NATIONAL WEATHER SERVICE IN MIDLAND HAS ISSUED A
FLOOD WARNING FOR...
SOUTHWESTERN LOVING COUNTY IN WEST TEXAS...
NORTHWESTERN WARD COUNTY IN WEST TEXAS...
NORTH CENTRAL REEVES COUNTY IN SOUTHWEST TEXAS...
UNTIL 300 PM CDT FRIDAY
AT 259 AM CDT...ROADS REMAIN CLOSED NEAR THE PECOS RIVER BETWEEN
RED BLUFF AND INTERSTATE 20 BECAUSE OF ELEVATED RIVER LEVELS DUE
TO RECENT RAINS. FLOODING WILL ALSO IMPACT THE CITY OF PECOS.',
'event' => 'Flood Warning',
'delete' => 0,
'category' => 'Met',
'severity' => 'Moderate',
'effective' => '2014-09-26T03:00:00-05:00',
'headline' => 'Flood Warning issued September 26 at 3:00AM CDT until September 26 at 3:00PM CDT by NWS Midland-Odessa',
'expires' => '2014-09-26T15:00:00-05:00'
}
}
};
URL is HASH(0x26384c0)
Expires: 2014-09-26T15:00:00-05:00
The TXC301 is a report identifier.
The output of the script will print all the values fetched from NOAA.
The goal is to store/return the 'expires' value.
I have lines commented out, which were attempts at achieving my goal.
The problem I'm having is getting the $url variable. I need this value in order to get my $expires value. The 2nd to last line in my code will correctly get the $expires value, but in order to do this I needed to hard code the URL into the line.
I'm trying to get the line directly above that (3rd to last) to work:
$expires= $events->{'TXC301'}->{$url}->{'expires'};
But this depends on the $url value to be stored.
I can't seem to figure out how to get the $url value.
My guesses:
#url = (keys %{$VAR1->{'TXC301'}});
$url = $VAR1->{'TXC301'};
$url = $events->{'TXC301'};
None of which work.
Any help would be great.
Thanks!
Regards,
Joseph Spenner
Given there is only one value for that level of hash ref, you could use values:
print +( values %{ $VAR1->{TXC301} } )[0]{expires}, "\n";
Outputs:
2014-09-26T15:00:00-05:00
Alternative to Data::Dumper
Also, on a separate issue, I would like to recommend the use of Data::Dump over the core library Data::Dumper.
The default settings and features for this alternative give superior output and enable one to analyze a data structure a lot quicker as demonstrated below:
$VAR1 = {
'TXC301' => {
'http://alerts.weather.gov/cap/wwacapget.php?x=TX12516CBE9400.FloodWarning.12516CC068C0TX.MAFFLWMAF.f21e7ce7cf8e930ab73a110c4d912576' => {
'certainty' => 'Likely',
'senderName' => 'NWS Midland-Odessa (Western Texas and Southeastern New Mexico)',
'urgency' => 'Expected',
'instruction' => 'A FLOOD WARNING MEANS THAT FLOODING IS IMMINENT OR HAS BEEN REPORTED.
STREAM RISES WILL BE SLOW AND FLASH FLOODING IS NOT EXPECTED.
HOWEVER... ALL INTERESTED PARTIES SHOULD TAKE NECESSARY PRECAUTIONS
IMMEDIATELY.
DO NOT DRIVE YOUR VEHICLE INTO AREAS WHERE THE WATER COVERS THE
ROADWAY. THE WATER DEPTH MAY BE TOO GREAT TO ALLOW YOUR CAR TO CROSS
SAFELY.',
'description' => 'THE NATIONAL WEATHER SERVICE IN MIDLAND HAS ISSUED A
FLOOD WARNING FOR...
SOUTHWESTERN LOVING COUNTY IN WEST TEXAS...
NORTHWESTERN WARD COUNTY IN WEST TEXAS...
NORTH CENTRAL REEVES COUNTY IN SOUTHWEST TEXAS...
UNTIL 300 PM CDT FRIDAY
AT 259 AM CDT...ROADS REMAIN CLOSED NEAR THE PECOS RIVER BETWEEN
RED BLUFF AND INTERSTATE 20 BECAUSE OF ELEVATED RIVER LEVELS DUE
TO RECENT RAINS. FLOODING WILL ALSO IMPACT THE CITY OF PECOS.',
'event' => 'Flood Warning',
'delete' => 0,
'category' => 'Met',
'severity' => 'Moderate',
'effective' => '2014-09-26T03:00:00-05:00',
'headline' => 'Flood Warning issued September 26 at 3:00AM CDT until September 26 at 3:00PM CDT by NWS Midland-Odessa',
'expires' => '2014-09-26T15:00:00-05:00'
}
}
};
use Data::Dump;
dd $VAR1;
Outputs:
{
TXC301 => {
"http://alerts.weather.gov/cap/wwacapget.php?x=TX12516CBE9400.FloodWarning.12516CC068C0TX.MAFFLWMAF.f21e7ce7cf8e930ab73a110c4d912576" => {
category => "Met",
certainty => "Likely",
delete => 0,
description => "THE NATIONAL WEATHER SERVICE IN MIDLAND HAS ISSUED A\n FLOOD WARNING FOR...\nSOUTHWESTERN LOVING COUNTY IN WEST TEXAS...\nNORTHWESTERN WARD COUNTY IN WEST TEXAS...\nNORTH CENTRAL REEVES COUNTY IN SOUTHWEST TEXAS...\n UNTIL 300 PM CDT FRIDAY\n AT 259 AM CDT...ROADS REMAIN CLOSED NEAR THE PECOS RIVER BETWEEN\nRED BLUFF AND INTERSTATE 20 BECAUSE OF ELEVATED RIVER LEVELS DUE\nTO RECENT RAINS. FLOODING WILL ALSO IMPACT THE CITY OF PECOS.",
effective => "2014-09-26T03:00:00-05:00",
event => "Flood Warning",
expires => "2014-09-26T15:00:00-05:00",
headline => "Flood Warning issued September 26 at 3:00AM CDT until September 26 at 3:00PM CDT by NWS Midland-Odessa",
instruction => "A FLOOD WARNING MEANS THAT FLOODING IS IMMINENT OR HAS BEEN REPORTED.\nSTREAM RISES WILL BE SLOW AND FLASH FLOODING IS NOT EXPECTED.\nHOWEVER... ALL INTERESTED PARTIES SHOULD TAKE NECESSARY PRECAUTIONS\nIMMEDIATELY.\nDO NOT DRIVE YOUR VEHICLE INTO AREAS WHERE THE WATER COVERS THE\nROADWAY. THE WATER DEPTH MAY BE TOO GREAT TO ALLOW YOUR CAR TO CROSS\nSAFELY.",
senderName => "NWS Midland-Odessa (Western Texas and Southeastern New Mexico)",
severity => "Moderate",
urgency => "Expected",
},
},
}
Ok, I was able to piece something together which worked:
use Weather::NOAA::Alert;
use Data::Dumper;
$alert = Weather::NOAA::Alert->new(['TXC301']);
$events = $alert->get_events();
$alert->poll_events();
Dumper($events);
print +( values %{ $events->{TXC301} } )[0]{expires}, "\n";
By changing $VAR1 to $events in the last line, I got rid of the error and got the exact output I needed.
Thanks for all the quick replies!
Regards,
Joseph Spenner

How do I set the cookie expiration for MojoX::Sessions?

Whatever expiration value that I give to expires() or expires_delta(), the cookie expiration is always one hour. How do I change it so that the session and the cookie expiration times match?
Although I like vti's work, that distribution looks outdated and was replaced in the past. Today the standard way to set session expire dates is explained in Mojolicious::Sessions:
default_expiration
my $time = $sessions->default_expiration;
$sessions = $sessions->default_expiration(3600);
Default time for sessions to expire in seconds from now, defaults to
3600. The expiration timeout gets refreshed for every request. Setting the value to 0 will allow sessions to persist until the browser window
is closed, this can have security implications though. For more
control you can also use the expiration and expires session values.
# Expiration date in epoch seconds from now (persists between requests)
$c->session(expiration => 604800);
# Expiration date as absolute epoch time (only valid for one request)
$c->session(expires => time + 604800);
# Delete whole session by setting an expiration date in the past
$c->session(expires => 1);
I wrote a small test script to make sure it works:
#!/usr/bin/env perl
use Mojolicious::Lite;
use Time::Local 'timegm';
# set some session variable
get '/test' => sub {
my $self = shift;
$self->session(
expires => timegm(0, 0, 0, 4, 4, 142), # star wars day '42
foo => 42,
);
$self->render_text('foo is set');
};
use Test::More;
use Test::Mojo;
use Mojo::Cookie::Response;
my $t = Test::Mojo->new;
$t->get_ok('/test')->status_is(200)->content_is('foo is set');
my $cookies = Mojo::Cookie::Response->parse($t->tx->res->headers->set_cookie);
is $cookies->[0]->expires, 'Sun, 04 May 2042 00:00:00 GMT', 'right expire time';
done_testing;
Output:
ok 1 - get /test
ok 2 - 200 OK
ok 3 - exact match for content
ok 4 - right expire time
1..4

How can I perform introspection in Perl?

In the Perl OOP, how can I dump all methods / fields in this class and its parent class.
my ($self) = #_;
I saw a lot of constructors as above. Then, what is #_?
I have Java / OOP background. If your explanation can follow Java domain, it will be much easier for me to understand.
What is #_? See perldoc -v #_
perldoc perlobj
mjd has an interesting article on introspection in Perl.
In addition, How do I list available methods on a given object or package in Perl? answers part of your question.
My preferred answer to that question uses Class::Inspector:
#!/usr/bin/env perl
use strict; use warnings;
use Class::Inspector;
use HTML::TokeParser::Simple;
my $methods = Class::Inspector->methods(
'HTML::TokeParser::Simple', 'full', 'public'
);
print "Methods:\n";
print "$_\n" for #$methods;
print "Superclasses\n";
use Class::ISA;
print join(", ", Class::ISA::super_path('HTML::TokeParser::Simple')), "\n";
Data::Printer is a quick way to get a list of available methods in the current class:
#!/usr/bin/env perl
use strict;
use warnings;
use DateTime;
use Data::Printer;
p( DateTime->now );
This will print something like:
DateTime {
public methods (134) : add, add_duration, am_or_pm, bootstrap, ce_year, christian_era, clone, compare, compare_ignore_floating, date, datetime, day, day_abbr, day_name, day_of_month, day_of_month_0, day_of_quarter, day_of_quarter_0, day_of_week, day_of_week_0, day_of_year, day_of_year_0, day_0, DefaultLanguage, DefaultLocale, delta_days, delta_md, delta_ms, dmy, doq, doq_0, dow, dow_0, doy, doy_0, duration_class, epoch, era, era_abbr, era_name, format_cldr, formatter, fractional_second, from_day_of_year, from_epoch, from_object, hires_epoch, hms, hour, hour_1, hour_12, hour_12_0, INFINITY, is_dst, is_finite, is_infinite, is_leap_year, iso8601, jd, language, last_day_of_month, leap_seconds, local_day_of_week, local_rd_as_seconds, local_rd_values, locale, MAX_NANOSECONDS, mday, mday_0, mdy, microsecond, millisecond, min, minute, mjd, mon, mon_0, month, month_abbr, month_name, month_0, NAN, nanosecond, NEG_INFINITY, new, now, offset, quarter, quarter_abbr, quarter_name, quarter_0, sec, second, SECONDS_PER_DAY, secular_era, set, set_day, set_formatter, set_hour, set_locale, set_minute, set_month, set_nanosecond, set_second, set_time_zone, set_year, STORABLE_freeze, STORABLE_thaw, strftime, subtract, subtract_datetime, subtract_datetime_absolute, subtract_duration, time, time_zone, time_zone_long_name, time_zone_short_name, today, truncate, utc_rd_as_seconds, utc_rd_values, utc_year, wday, wday_0, week, week_number, week_of_month, week_year, weekday_of_month, year, year_with_christian_era, year_with_era, year_with_secular_era, ymd
private methods (38) : _accumulated_leap_seconds, _add_overload, _adjust_for_positive_difference, _calc_local_components, _calc_local_rd, _calc_utc_components, _calc_utc_rd, _cldr_pattern, _compare, _compare_overload, _day_has_leap_second, _day_length, _era_index, _format_nanosecs, _handle_offset_modifier, _is_leap_year, _month_length, _new, _new_from_self, _normalize_leap_seconds, _normalize_nanoseconds, _normalize_seconds, _normalize_tai_seconds, _offset_for_local_datetime, _rd2ymd, _seconds_as_components, _space_padded_string, _string_compare_overload, _string_equals_overload, _string_not_equals_overload, _stringify, _subtract_overload, _time_as_seconds, _utc_hms, _utc_ymd, _weeks_in_year, _ymd2rd, _zero_padded_number
internals: {
formatter undef,
local_c {
day 13,
day_of_quarter 13,
day_of_week 5,
day_of_year 104,
hour 15,
minute 5,
month 4,
quarter 2,
second 16,
year 2012
},
local_rd_days 734606,
local_rd_secs 54316,
locale DateTime::Locale::en_US,
offset_modifier 0,
rd_nanosecs 0,
tz DateTime::TimeZone::UTC,
utc_rd_days 734606,
utc_rd_secs 54316,
utc_year 2013
}
}

Schema for opening hours MongoDB

How would you store opening hours on a document, say a Library, in mongoDB, that's easily queryable with Mongoid? I've read this thread, but I'm not sure how it would be implemented with my needs.
I need to have the ability to add multiple opening and closing times per day since the Library should be able to close some hours during the day and then reopen.
I need to be able to add exceptions to these opening hours. For example; close monday on a specific date.
Please share some best practices and experiences on how one could do this the most flexible way.
Thank you, Yeggeps, for the list of requirements.
This is a revised answer based on your requirements. Of course there is no holy grail to schemae, but I would like to motivate my answer before revision (keeping a flat structure is easy to query and maintain) with some sample data + queries based on your requirement list. I reiterate, I am not saying this is the best solution, but it is a solution which is straightforward to query and easy to maintain (imho).
Code is a little quick and dirty, apologies. The data:
[
# library "lib1" open on wednesdays from 8:00 until 17:00
{"lib_id" => "lib1", "type" => "hours", "opening" => 800, "closing" => 1700, "day_of_week" => 3},
# library "lib1" open on wednesdays from 19:00 until 22:15
{"lib_id" => "lib1", "type" => "hours", "opening" => 1900, "closing" => 2215, "day_of_week" => 3},
{"lib_id" => "lib1", "type" => "hours", "opening" => 800, "closing" => 1700, "day_of_week" => 4},
{"lib_id" => "lib2", "type" => "hours", "opening" => 1100, "closing" => 1700, "day_of_week" => 3},
{"lib_id" => "lib2", "type" => "hours", "opening" => 1400, "closing" => 1700, "day_of_week" => 4},
{"lib_id" => "lib2", "type" => "hours", "opening" => 1900, "closing" => 2100, "day_of_week" => 4},
# library lib1 closed on wednesday december 7th 2011
{"lib_id" => "lib1", "type" => "closed_on", "reason" => "Rearranging the shelves", "closed_date" => Time.utc(2011, 12, 8)},
{"lib_id" => "lib2", "type" => "closed_on", "reason" => "We are closed for the holidays", "closed_date" => Time.utc(2011, 12, 7)}
].each do |schedule|
coll.save(schedule)
end
Show opening hours and exceptional dates separately:
# List all the library id's distinctly
coll.distinct("lib_id").each do |lib_id|
puts "\nLibrary #{lib_id} opening hours:\n--- "
# I need to be able to show the opening hours in correlation with the Library
# Find all the opening hour information for current library
coll.find({"lib_id" => lib_id, "type" => "hours"}).each do |schedule|
puts " #{Date::DAYNAMES[schedule["day_of_week"]]}s: #{schedule["opening"]} - #{schedule["closing"]}" if schedule["type"] == "hours"
end
# I need to show an indication if it's open or closed in correlation with the Library.
puts "This library will be closed on: "
# Find all the exceptions for current lib_id -- introduce a time-period restriction using Date.utc (...)
coll.find({"lib_id" => lib_id, "type" => "closed_on"}).each do |closed|
puts " #{closed["closed_date"].strftime("%a %B%e, %Y")}: #{closed["reason"]}"
end
end
Which libraries are open today?
# I need to be able to query on what's open right now or some time in the future with minute granularity
# here I'll also need to be able to exclude the Librarys that has added exceptions for the given time/day
puts "---"
qtime = (Time.now.hour * 100) + Time.now.min # minute granularity
qwday = Time.now.wday # this example only shows today
qclosed = Time.utc(Time.now.year, Time.now.mon, Time.now.mday)
# Query for all library ids which have opening times for this weekday, at this hour (+minutes)
coll.find({"opening" => {"$lte" => qtime}, "closing" => {"$gte" => qtime}, "day_of_week" => qwday}, {:fields => "lib_id"}).each do |lib|
# Check whether current library has an exception for this specific day
closed = coll.find_one({"lib_id" => lib["lib_id"], "closed_date" => qclosed})
if closed
# If an exception record was encountered, print the reason
puts "Library #{lib["lib_id"]} is normally open right now, but is now closed: '#{closed["reason"]}'"
else
# Else: the library is open
puts "Library #{lib["lib_id"]} is open right now! (#{Time.now.strftime("%a %B%e %Y, %H:%M")})"
end
end
Produces output as follows:
Library lib1 opening hours:
---
Wednesdays: 800 - 1700
Wednesdays: 1900 - 2215
Thursdays: 800 - 1700
This library will be closed on:
Thu December 8, 2011: Rearranging the shelves
Library lib2 opening hours:
---
Wednesdays: 1100 - 1700
Thursdays: 1400 - 1700
Thursdays: 1900 - 2100
This library will be closed on:
Wed December 7, 2011: We are closed for the holidays
---
Library lib1 is open right now! (Wed December 7 2011, 13:12)
Library lib2 is normally open right now, but is now closed: 'We are closed for the holidays'
Admittedly, the downside to my proposed solution is that it does not capture every requirement in one query.
It's difficult to provide a good solution without knowing the exact queries you'd like to run. For instance, if you're asking "what businesses are open now (5:32 PM, 5/11/2011)?" you'd want a different schema than if you were asking "when is business XYZ open next?"
In the first case, you'll want to be able to efficiently pose range queries on the current hour, minute, and day -- as well as negative queries on an exception list. Alternatively, you can handle exceptions in client code.
Last, what is the level of granularity needed? What is the smallest exception possible? Minutes? Hours? Days?
I'd post the above as a comment but I just created a user account. With additional information, I'll update this to provide an actual answer.

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

How can I elegantly print the date in RFC822 format in Perl?
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.