+use Time::Local qw(timegm);
+my @MoY = qw(january february march april may june
+ july august september october november december);
+my %MoY;
+@MoY{@MoY} = (0..11);
+@MoY{map { substr($_, 0, 3) } @MoY} = (0..11);
+
+my %OBSOLETE_TZ = ( # RFC2822 4.3 (Obsolete Date and Time)
+ EST => '-0500', EDT => '-0400',
+ CST => '-0600', CDT => '-0500',
+ MST => '-0700', MDT => '-0600',
+ PST => '-0800', PDT => '-0700',
+ UT => '+0000', GMT => '+0000', Z => '+0000',
+
+ # RFC2822 states:
+ # The 1 character military time zones were defined in a non-standard
+ # way in [RFC822] and are therefore unpredictable in their meaning.
+);
+my $OBSOLETE_TZ = join('|', keys %OBSOLETE_TZ);
+
+sub str2date_zone ($) {
+ my ($date) = @_;
+ my ($ts, $zone);
+
+ # RFC822 is most likely for email, but we can tolerate an extra comma
+ # or punctuation as long as all the data is there.
+ # We'll use '\s' since Unicode spaces won't affect our parsing.
+ # SpamAssassin ignores commas and redundant spaces, too.
+ if ($date =~ /(?:[A-Za-z]+,?\s+)? # day-of-week
+ ([0-9]+),?\s+ # dd
+ ([A-Za-z]+)\s+ # mon
+ ([0-9]{2,4})\s+ # YYYY or YY (or YYY :P)
+ ([0-9]+)[:\.] # HH:
+ ((?:[0-9]{2})|(?:\s?[0-9])) # MM
+ (?:[:\.]((?:[0-9]{2})|(?:\s?[0-9])))? # :SS
+ \s+ # a TZ offset is required:
+ ([\+\-])? # TZ sign
+ [\+\-]* # I've seen extra "-" e.g. "--500"
+ ([0-9]+|$OBSOLETE_TZ)(?:\s|$) # TZ offset
+ /xo) {
+ my ($dd, $m, $yyyy, $hh, $mm, $ss, $sign, $tz) =
+ ($1, $2, $3, $4, $5, $6, $7, $8);
+ # don't accept non-English months
+ defined(my $mon = $MoY{lc($m)}) or return;
+
+ if (defined(my $off = $OBSOLETE_TZ{$tz})) {
+ $sign = substr($off, 0, 1);
+ $tz = substr($off, 1);
+ }
+
+ # Y2K problems: 3-digit years, follow RFC2822
+ if (length($yyyy) <= 3) {
+ $yyyy += 1900;
+
+ # and 2-digit years from '09 (2009) (0..49)
+ $yyyy += 100 if $yyyy < 1950;
+ }
+
+ $ts = timegm($ss // 0, $mm, $hh, $dd, $mon, $yyyy);