/[suikacvs]/messaging/manakai/lib/Message/Field/Date.pm
Suika

Contents of /messaging/manakai/lib/Message/Field/Date.pm

Parent Directory Parent Directory | Revision Log Revision Log


Revision 1.1 - (hide annotations) (download)
Sat Mar 16 01:26:30 2002 UTC (22 years, 8 months ago) by wakaba
Branch: MAIN
2002-03-15  wakaba <w@suika.fam.cx>

	* Date.pm: New module.

1 wakaba 1.1
2     =head1 NAME
3    
4     Message::Field::Date Perl module
5    
6     =head1 DESCRIPTION
7    
8     Perl module for RFC 822/2822 date style C<field>s.
9    
10     =cut
11    
12     package Message::Field::Date;
13     require 5.6.0;
14     use strict;
15     use re 'eval';
16     use vars qw(%DEFAULT %MONTH %REG $VERSION %ZONE);
17     $VERSION = '1.00';
18    
19     use Time::Local 'timegm_nocheck';
20     use overload '""' => sub {shift->stringify};
21    
22     $REG{comment} = qr/\x28(?:\x5C[\x00-\xFF]|[\x00-\x0C\x0E-\x27\x2A-\x5B\x5D-\xFF]|(??{$REG{comment}}))*\x29/;
23     $REG{quoted_string} = qr/\x22(?:\x5C[\x00-\xFF]|[\x00-\x0C\x0E-\x21\x23-\x5B\x5D-\xFF])*\x22/;
24    
25     $REG{WSP} = qr/[\x20\x09]+/;
26     $REG{FWS} = qr/[\x20\x09]*/;
27     $REG{M_quoted_string} = qr/\x22((?:\x5C[\x00-\xFF]|[\x00-\x0C\x0E-\x21\x23-\x5B\x5D-\xFF])*)\x22/;
28     $REG{M_rfc2822_date_time} = qr/([0-9]+)$REG{FWS}([A-Za-z]+)$REG{FWS}([0-9]+)$REG{WSP}+([0-9]+)$REG{FWS}:$REG{FWS}([0-9]+)(?:$REG{FWS}:$REG{FWS}([0-9]+))?$REG{FWS}([A-Za-z]+|[+-]$REG{WSP}*[0-9]+)/;
29     $REG{M_rfc733_date_time} = qr/([0-9]{1,2})$REG{FWS}(?:-$REG{FWS})?([A-Za-z]+)$REG{FWS}(?:-$REG{FWS})?([0-9]+)$REG{WSP}+([0-9][0-9])$REG{FWS}(?::$REG{FWS})?([0-9][0-9])(?:$REG{FWS}(?::$REG{FWS})?([0-9][0-9]))?$REG{FWS}((?:-$REG{FWS})?[A-Za-z]+|[+-]$REG{WSP}*[0-9]+)/;
30    
31     %DEFAULT = (
32     format => 'rfc2822',
33     output_day_of_week => 1,
34     zone => [+1, 0, 0],
35     zone_letter => +1,
36     );
37    
38     ## format rfc733 [RFC733]
39     ## rfc2822 [RFC822] + [RFC1123], [RFC2822]
40    
41     %MONTH = (
42     JAN => 1, JANUARY => 1,
43     FEB => 2, FEBRUARY => 2,
44     MAR => 3, MARCH => 3,
45     APR => 4, APRIL => 4,
46     MAY => 5,
47     JUN => 6, JUNE => 6,
48     JUL => 7, JULY => 7,
49     AUG => 8, AUGUST => 8,
50     SEP => 9, SEPTEMBER => 9,
51     OCT => 10, OCTOBER => 10,
52     NOV => 11, NOVEMBER => 11,
53     DEC => 12, DECEMBER => 12,
54     );
55    
56     %ZONE = (
57     ADT => [-1, 3, 0], ## 733
58     AST => [-1, 4, 0], ## 733
59     BDT => [-1, 10, 0], ## 733
60     BST => [-1, 11, 0], ## 733
61     CDT => [-1, 5, 0], ## 733, 822, 2822
62     CST => [-1, 6, 0], ## 733, 822, 2822
63     EDT => [-1, 4, 0], ## 733, 822, 2822
64     EST => [-1, 5, 0], ## 733, 822, 2822
65     ## GDT 724
66     GMT => [+1, 0, 0], ## 733, 822, 2822
67     HDT => [-1, 9, 0], ## 733
68     HST => [-1, 10, 0], ## 733
69     MDT => [-1, 6, 0], ## 733, 822, 2822
70     MST => [-1, 7, 0], ## 733, 822, 2822
71     NST => [-1, 3, 30], ## 733
72     PDT => [-1, 7, 0], ## 733, 822, 2822
73     PST => [-1, 8, 0], ## 733, 822, 2822
74     YDT => [-1, 8, 0], ## 733
75     YST => [-1, 9, 0], ## 733
76     UT => [+1, 0, 0], ## 822, 2822
77     );
78    
79     =head2 $self->_option_zone_letter (-1/0/+1)
80    
81     Set convertion rule between one letter zone name
82     (military format) and time.
83    
84     C<+1> set it as standard value. (For exmaple, 'A' means
85     '+0100'.) C<-1> reverses their sign, for example, 'A'
86     means '-0100'. BNF comment of RFC 733 and 822 has typo
87     so quite a few implemention takes these values incorrectly.
88     As a result, these zone names carry no worthful information.
89     RFC 2822 recommends these names be taken as '-0000' (i.e.
90     unknown zone). C<-2> means it.
91    
92     =cut
93    
94     sub _option_zone_letter ($$) {
95     my $self = shift;
96     my $mode = shift;
97     my $i = 0;
98     if ($mode == -2) {
99     for my $letter ('A'..'Z') {$ZONE{$letter} = [-1, 0, 0]} return $self;
100     }
101     for my $letter ('Z', 'A'..'I', 'K'..'M') {
102     $ZONE{$letter} = [+1*$mode, $i++, 0];
103     } $i = 1;
104     for my $letter ('N'..'Y') {
105     $ZONE{$letter} = [-1*$mode, $i++, 0];
106     }
107     $self;
108     }
109    
110     =head2 Message::Field::Date->new ()
111    
112     Return empty Message::Field::Date object.
113    
114     =cut
115    
116     sub new ($;$) {
117     my $class = shift;
118     my $self = bless {option => {@_}, date_time => shift||time}, $class;
119     for (keys %DEFAULT) {$self->{option}->{$_} ||= $DEFAULT{$_}}
120     $self->_option_zone_letter ($self->{option}->{zone_letter});
121     $self;
122     }
123    
124     =head2 Message::Field::Date->parse ($unfolded_field_body)
125    
126     Parse date style C<field-body>.
127    
128     =cut
129    
130     sub parse ($$;%) {
131     my $class = shift; my $field_body = shift;
132     my $self = bless {option => {@_}}, $class;
133     for (keys %DEFAULT) {$self->{option}->{$_} ||= $DEFAULT{$_}}
134     $self->_option_zone_letter ($self->{option}->{zone_letter});
135     $field_body = $self->delete_comment ($field_body);
136     if ($field_body =~ /$REG{M_rfc2822_date_time}/) {
137     my ($day, $month, $year, $hour, $minute, $second, $zone)
138     = ($1, uc $2, $3, $4, $5, $6, uc $7);
139     $month = $MONTH{$month} || 1;
140     if ( 0 < $year && $year < 49) {$year += 2000}
141     elsif (50 < $year && $year < 1000) {$year += 1900}
142     my ($zone_sign, $zone_hour, $zone_minute) = $self->_zone_string_to_array ($zone);
143     eval '$self->{date_time} = timegm_nocheck
144     ($second, $minute-($zone_sign*$zone_minute), $hour-($zone_sign*$zone_hour),
145     $day, $month-1, $year);';
146     $self->{option}->{zone} = [$zone_sign, $zone_hour, $zone_minute];
147     } elsif ($field_body =~ /$REG{M_rfc733_date_time}/) {
148     my ($day, $month, $year, $hour, $minute, $second, $zone)
149     = ($1, uc $2, $3, $4, $5, $6, uc $7);
150     $month = $MONTH{$month} || 1;
151     if ( 0 < $year && $year < 49) {$year += 2000}
152     elsif (50 < $year && $year < 1000) {$year += 1900}
153     my ($zone_sign, $zone_hour, $zone_minute) = $self->_zone_string_to_array ($zone);
154     eval '$self->{date_time} = timegm_nocheck
155     ($second, $minute-($zone_sign*$zone_minute), $hour-($zone_sign*$zone_hour),
156     $day, $month-1, $year);';
157     $self->{option}->{zone} = [$zone_sign, $zone_hour, $zone_minute];
158     } else {
159     $self->{date_time} = 0;
160     }
161     $self;
162     }
163    
164     =head2 $self->stringify ()
165    
166     Returns C<field-body> as a string.
167    
168     =cut
169    
170     sub stringify ($) {
171     my $self = shift;
172     #} else { #if ($self->{option}->{format} eq 'rfc2822') {
173     $self->as_rfc2822_time ();
174     #}
175     }
176    
177     sub as_plain_string ($) {
178     my $self = shift;
179     $self->stringify (@_);
180     }
181    
182     sub as_unix_time ($) {
183     my $self = shift;
184     $self->{date_time};
185     }
186    
187     sub as_rfc2822_time ($;%) {
188     my $self = shift;
189     my %option = @_;
190     my $time = $self->{date_time};
191     my @zone = [+1, 0, 0];
192     if (ref $option{zone}) {@zone = @{$option{zone}}}
193     elsif ($option{zone}) {@zone = $self->_zone_string_to_array ($option{zone})}
194     elsif (ref $self->{option}->{zone}) {@zone = @{$self->{option}->{zone}}}
195     elsif ($self->{option}->{zone}) {@zone = $self->{option}->_zone_string_to_array ($self->{option}->{zone})}
196     $option{output_day_of_week} ||= $DEFAULT{output_day_of_week};
197    
198     $time += $zone[0] * ($zone[1] * 60 + $zone[2]) * 60;
199     my ($sec,$min,$hour,$day,$month,$year,$day_of_week) = gmtime ($time);
200     $month = (qw(Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec))[$month];
201     $year += 1900 if $year < 1900;
202     $day_of_week = (qw(Sun Mon Tue Wed Thr Fri Sat))[$day_of_week] .', ';
203    
204     ($option{output_day_of_week}? $day_of_week: '').
205     sprintf('%02d %s %s %02d:%02d:%02d %s%02d%02d',
206     $day,$month,$year,$hour,$min,$sec,$zone[0]>0?'+':'-',@zone[1,2]);
207     }
208    
209     =head2 $self->delete_comment ($field_body)
210    
211     Remove all C<comment> in given strictured C<field-body>.
212     This method is intended for internal use.
213    
214     =cut
215    
216     sub delete_comment ($$) {
217     my $self = shift;
218     my $body = shift;
219     $body =~ s{($REG{quoted_string})|$REG{comment}}{
220     my $o = $1; $o? $o : ' ';
221     }gex;
222     $body;
223     }
224    
225     sub _zone_string_to_array ($$;$) {
226     my $self = shift;
227     my $zone = shift;
228     my $format = shift;
229     my @azone = [+1, 0, 0];
230     ## if $format eq rfc2822
231     if ($zone =~ /([+-])$REG{FWS}([0-9][0-9])([0-9][0-9])/) {
232     @azone = ("${1}1", $2, $3);
233     } else { $zone =~ tr/-//d;
234     if (ref $ZONE{$zone}) {@azone = @{$ZONE{$zone}}}
235     elsif ($zone) {@azone = (-1, 0, 0)}
236     }
237     # }
238     @azone;
239     }
240    
241     =head1 EXAMPLE
242    
243     use Message::Field::Structured;
244    
245     my $field_body = '"This is an example of <\"> (quotation mark)."
246     (Comment within \q\u\o\t\e\d\-\p\a\i\r\(\s\))';
247     my $field = Message::Field::Structured->parse ($field_body);
248    
249     print $field->as_plain_string;
250    
251     =head1 LICENSE
252    
253     Copyright 2002 wakaba E<lt>w@suika.fam.cxE<gt>.
254    
255     This program is free software; you can redistribute it and/or modify
256     it under the terms of the GNU General Public License as published by
257     the Free Software Foundation; either version 2 of the License, or
258     (at your option) any later version.
259    
260     This program is distributed in the hope that it will be useful,
261     but WITHOUT ANY WARRANTY; without even the implied warranty of
262     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
263     GNU General Public License for more details.
264    
265     You should have received a copy of the GNU General Public License
266     along with this program; see the file COPYING. If not, write to
267     the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
268     Boston, MA 02111-1307, USA.
269    
270     =head1 CHANGE
271    
272     See F<ChangeLog>.
273    
274     =cut
275    
276     1;

admin@suikawiki.org
ViewVC Help
Powered by ViewVC 1.1.24