/[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.9 - (hide annotations) (download)
Thu May 16 11:43:40 2002 UTC (22 years, 6 months ago) by wakaba
Branch: MAIN
Changes since 1.8: +524 -288 lines
2002-05-16  wakaba <w@suika.fam.cx>

	* Date.pm: Remade.

1 wakaba 1.1
2     =head1 NAME
3    
4 wakaba 1.9 Message::Field::Date --- Perl module for various styles of
5     date-time used in Internet messages and so on
6 wakaba 1.1
7     =cut
8    
9     package Message::Field::Date;
10     use strict;
11 wakaba 1.9 use vars qw(%DEFAULT @ISA %MONTH %REG $VERSION %ZONE);
12     $VERSION=do{my @r=(q$Revision: 1.2 $=~/\d+/g);sprintf "%d."."%02d" x $#r,@r};
13     require Message::Field::Structured;
14     push @ISA, qw(Message::Field::Structured);
15 wakaba 1.1 use Time::Local 'timegm_nocheck';
16    
17 wakaba 1.9 %REG = %Message::Util::REG;
18     $REG{M_dt_rfc822} = qr!(?:[A-Za-z]+ ## Day of week
19     [\x09\x20,]*)? ([0-9]+) ## Day
20     [\x09\x20/-]* ([A-Za-z]+) ## Month
21     [\x09\x20/-]* ([0-9]+) ## Year
22     [\x09\x20:Tt-]+ ([0-9]+) ## Hour
23     [\x09\x20:]+ ([0-9]+) ## Minute
24     [\x09\x20:]* ([0-9]+)? ## Second
25     ([\x09\x20 0-9A-Za-z+-]+)!x; ## Zone
26     $REG{M_dt_iso8601} = qr! ([0-9]{4,}) ## Year
27     [\x09\x20.:/-]+ ([0-9]+) ## Month
28     [\x09\x20.:/-]+ ([0-9]+) ## Day
29     (?:[\x09\x20.:Tt-]+ ([0-9]+) ## Hour
30     [\x09\x20.:]+ ([0-9]+) ## Minute
31     (?:[\x09\x20.:]+ ([0-9]+) ## Second
32     (?:[\x09\x20.:]+ ([0-9]+))?)?)? ## frac.
33     ([\x09\x20 0-9A-Za-z:.+-]*) !x; ## Zone.
34     $REG{M_dt_rfc733} = qr!(?:[A-Za-z]+ ## Day of week
35     [\x09\x20,]*)? ([0-9]+) ## Day
36     [\x09\x20/-]* ([A-Za-z]+) ## Month
37     [\x09\x20/-]* ([0-9]+) ## Year
38     [\x09\x20:Tt-]+ ([0-9][0-9]) ## Hour
39     [\x09\x20:]* ([0-9][0-9]) ## Minute
40     [\x09\x20:]* ([0-9][0-9])? ## Second
41     ([\x09\x20 0-9A-Za-z+-]+)!x; ## Zone
42     $REG{M_dt_rfc724} = qr!(?:[A-Za-z]+ ## Day of week
43     [\x09\x20,]*)? ([0-9][0-9]?) ## Month
44     [\x09\x20/]+ ([0-9][0-9]?) ## Day
45     [\x09\x20/]+ ([0-9]{2,}) ## Year
46     [\x09\x20:Tt-]+ ([0-9][0-9]) ## Hour
47     [\x09\x20:]* ([0-9][0-9]) ## Minute
48     [\x09\x20:]* ([0-9][0-9])? ## Second
49     ([\x09\x20 0-9A-Za-z+-]+)!x; ## Zone
50    
51 wakaba 1.2 $REG{M_rfc724_slash_date} = qr#([0-9]+)$REG{FWS}/$REG{FWS}([0-9]+)$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]+)#;
52     $REG{M_asctime} = qr/[A-Za-z]+$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{WSP}+([0-9]+)/;
53     $REG{M_iso8601_date_time} = qr/([0-9]+)-([0-9]+)-([0-9]+)[Tt]([0-9]+):([0-9]+):([0-9]+)(?:.([0-9]+))?(?:[Zz]|([+-])([0-9]+):([0-9]+))/;
54 wakaba 1.1
55 wakaba 1.9 =head1 CONSTRUCTORS
56    
57     The following methods construct new objects:
58    
59     =over 4
60    
61     =cut
62    
63     ## Initialize of this class -- called by constructors
64 wakaba 1.1 %DEFAULT = (
65 wakaba 1.9 -_MEMBERS => [qw|date_time secfrac|],
66     -_METHODS => [qw|unix_time second_fraction
67     comment_add comment_count comment_item comment_delete|],
68     -date_format => 'string', ## 'unix' / 1*ALPHA
69     #field_param_name
70     #field_name
71     #format
72     #hook_encode_string
73     #hook_decode_string
74     -output_comment => 1,
75     -str2time => {
76     CC => sub { sprintf $_[2]->{_fmt}, ## Support AD only
77     (($_[1]->{$_[2]->{_prefix}.'tm'}->[5] + 1899) / 100) + 1 },
78     YYYY => sub { $_[2]->{_fmt} =~ tr/2/4/;
79     sprintf $_[2]->{_fmt},
80     $_[1]->{$_[2]->{_prefix}.'tm'}->[5] + 1900 },
81     YY => sub { sprintf $_[2]->{_fmt},
82     substr ($_[1]->{$_[2]->{_prefix}.'tm'}->[5], -2) },
83     MM => sub { sprintf $_[2]->{_fmt},
84     $_[1]->{$_[2]->{_prefix}.'tm'}->[4] + 1 },
85     Mon => sub { qw(Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec)
86     [$_[1]->{$_[2]->{_prefix}.'tm'}->[4]] },
87     Month => sub { qw(January February March April May June
88     July August September October November December)
89     [$_[1]->{$_[2]->{_prefix}.'tm'}->[4]] },
90     DD => sub { sprintf $_[2]->{_fmt}, $_[1]->{$_[2]->{_prefix}.'tm'}->[3] },
91     Wdy => sub { qw(Sun Mon Tue Wed Thu Fri Sat)
92     [$_[1]->{$_[2]->{_prefix}.'tm'}->[6]] },
93     Weekday => sub { qw(Sunday Monday Tuesday Wednesday Thursday Friday Saturday)
94     [$_[1]->{$_[2]->{_prefix}.'tm'}->[6]] },
95     shun => sub {
96     my @alphabet = split /:/, $_[2]->{alphabet} || 'abcc';
97     my $day = $_[1]->{$_[2]->{_prefix}.'tm'}->[3];
98     $day <= 10? $alphabet[0]: ## 1 - 10 joujun
99     $day <= 20? $alphabet[1]: ## 11 - 20 chuujun
100     $alphabet[2]; ## 21 - 31 gejun
101     },
102     HH => sub { sprintf $_[2]->{_fmt}, $_[1]->{$_[2]->{_prefix}.'tm'}->[2] },
103     TT => sub { sprintf $_[2]->{_fmt}, $_[1]->{$_[2]->{_prefix}.'tm'}->[1] },
104     SS => sub { sprintf $_[2]->{_fmt}, $_[1]->{$_[2]->{_prefix}.'tm'}->[0] },
105     unix => sub { $_[1]->{$_[2]->{_prefix}.'unix'} },
106     frac => sub { $_[0]->{secfrac} },
107     zsign => sub { $_[1]->{zone}->[0] > 0 ? '+' : '-' },
108     zHH => sub { sprintf $_[2]->{_fmt}, $_[1]->{zone}->[1] },
109     zTT => sub { sprintf $_[2]->{_fmt}, $_[1]->{zone}->[2] },
110     percent => '%',
111     },
112     -use_comment => 1,
113     -use_military_zone => +1, ## +1 / -1 / 0
114     -zone => [+1, 0, 0],
115     -zone_default_string => '-0000',
116 wakaba 1.1 );
117    
118     %MONTH = (
119     JAN => 1, JANUARY => 1,
120     FEB => 2, FEBRUARY => 2,
121     MAR => 3, MARCH => 3,
122     APR => 4, APRIL => 4,
123     MAY => 5,
124     JUN => 6, JUNE => 6,
125     JUL => 7, JULY => 7,
126     AUG => 8, AUGUST => 8,
127 wakaba 1.8 SEP => 9, SEPTEMBER => 9, SEPT => 9,
128 wakaba 1.1 OCT => 10, OCTOBER => 10,
129     NOV => 11, NOVEMBER => 11,
130     DEC => 12, DECEMBER => 12,
131     );
132    
133 wakaba 1.9 %ZONE = ( ## NA = Northern America
134     ADT => [-1, 3, 0], ## (NA)Atlantic Daylight 733
135     CHST => [-1, 10, 0], ## Alaska-Hawaii Standard
136     AST => [-1, 4, 0], ## (NA)Atlantic Standard 733
137     AT => [-1, 2, 0], ## Azores
138     BDT => [-1, 10, 0], ## 733
139     BST => [-1, 11, 0], ## 733
140     #BST => [+1, 1, 0], ## British Summer
141     #BST => [-1, 3, 0], ## Brazil Standard
142     BT => [+1, 3, 0], ## Baghdad
143     CADT => [+1, 10, 30], ## Central Australian Daylight
144     CAST => [+1, 9, 30], ## Central Australian Standard
145     CAT => [-1, 10, 0], ## Central Alaska
146     CCT => [+1, 8, 0], ## China Coast
147     CDT => [-1, 5, 0], ## (NA)Central Daylight 733, 822
148     CET => [+1, 1, 0], ## Central European
149     CEST => [+1, 2, 0], ## Central European Daylight
150     CST => [-1, 6, 0], ## (NA)Central Standard 733, 822
151     EADT => [+1, 11, 0], ## Eastern Australian Daylight
152     EADT => [+1, 10, 0], ## Eastern Australian Standard
153     ECT => [+1, 1, 0], ## Central European (French)
154     EDT => [-1, 4, 0], ## (NA)Eastern Daylight 733, 822
155     EEST => [+1, 3, 0], ## Eastern European Summer
156     EET => [+1, 2, 0], ## Eastern Europe 1947
157     EST => [-1, 5, 0], ## (NA)Eastern Standard 733, 822
158     EWT => [-1, 4, 0], ## U.S. Eastern War Time
159     FST => [+1, 2, 0], ## French Summer
160     FWT => [+1, 1, 0], ## French Winter
161     GDT => [+1, 1, 0], ## 724
162     GMT => [+1, 0, 0], ## Greenwich Mean 733, 822
163     #GST => [-1, 3, 0], ## Greenland Standard
164     GST => [+1, 10, 0], ## Guam Standard
165     HDT => [-1, 9, 0], ## Hawaii Daylight 733
166     HKT => [+1, 8, 0], ## Hong Kong
167     HST => [-1, 10, 0], ## Hawaii Standard 733
168     IDLE => [+1, 12, 0], ## International Date Line East
169     IDLW => [-1, 12, 0], ## International Date Line West
170 wakaba 1.2 IDT => [+1, 3, 0],
171 wakaba 1.9 IST => [+1, 2, 0], ## Israel standard
172     #IST => [+1, 5, 30], ## Indian standard
173     IT => [+1, 3, 30], ## Iran
174     JST => [+1, 9, 0], ## Japan Central Standard
175     JT => [+1, 7, 30], ## Java
176     KDT => [+1, 10, 0], ## Korean Daylight
177     KST => [+1, 9, 0], ## Korean Standard
178     MDT => [-1, 6, 0], ## (NA)Mountain Daylight 733, 822
179     MET => [+1, 0, 0], ## Middle European
180     METDST => [+1, 2, 0],
181     MEST => [+1, 2, 0], ## Middle European Summer
182     MEWT => [+1, 0, 0], ## Middle European Winter
183     MEZ => [+1, 0, 0], ## Central European (German)
184     MST => [-1, 7, 0], ## (NA)Mountain Standard 733, 822
185     NDT => [-1, 2, 30], ## Newfoundland Daylight
186     NFT => [-1, 3, 30], ## Newfoundland Standard
187     NST => [-1, 3, 30], ## Newfoundland Standard 733
188     #NST => [-1, 6, 30], ## North Sumatra
189     NT => [-1, 11, 0], ## Nome
190     NZD => [+1, 13, 0], ## New Zealand Daylight
191     NZT => [+1, 12, 0], ## New Zealand
192     NZDT => [+1, 13, 0], ## New Zealand Daylight
193     NZST => [+1, 12, 0], ## New Zealand Standard
194     PDT => [-1, 7, 0], ## (NA)Pacific Daylight 733, 822
195     PST => [-1, 8, 0], ## (NA)Pacific Standard 733, 822
196     SET => [+1, 1, 0], ## Seychelles
197     SST => [+1, 2, 0], ## Swedish Summer
198     #SST => [+1, 7, 0], ## South Sumatra
199     SWT => [+1, 1, 0], ## Swedish Winter
200     UKR => [+1, 2, 0], ## Ukraine
201     UT => [+1, 0, 0], ## 822
202     UTC => [+1, 0, 0],
203     WADT => [+1, 8, 0], ## West Australian Daylight
204     WAT => [-1, 0, 0], ## West Africa
205     WET => [+1, 0, 0], ## Western European
206     WST => [+1, 8, 0], ## West Australian Standard
207     YDT => [-1, 8, 0], ## Yukon Daylight 733
208     YST => [-1, 9, 0], ## Yukon Standard 733
209     Z => [+1, 0, 0], ## 822, ISO 8601
210     ZP4 => [+1, 4, 0], ## Z+4
211     ZP5 => [+1, 5, 0], ## Z+5
212     ZP6 => [+1, 6, 0], ## Z+6
213 wakaba 1.1 );
214    
215 wakaba 1.9 ## -use_military_zone => +1 / -1 / 0
216     ## Whether military zone names are understood or not.
217     ## +1 Admits them and treats as standard value. (eg. "A" = +0100)
218     ## -1 Admits them but treats as negative value. (eg. "A" = -0100)
219     ## 0 They are ignored and zone is set as -0000. (eg. "A" = -0000)
220     ## Because of typo in BNF comment of RFCs 733 and 822,
221     ## quite a few implemention use these values incorrectly.
222     ## As a result, these zone names carry no worthful information.
223     ## RFC 2822 recommends these names be taken as '-0000' (i.e.
224     ## unknown zone).
225 wakaba 1.1
226 wakaba 1.9 sub _set_military_zone_name ($) {
227 wakaba 1.1 my $self = shift;
228 wakaba 1.9 my $mode = $self->{option}->{use_military_zone};
229 wakaba 1.1 my $i = 0;
230 wakaba 1.9 if ($mode == 0) {
231     for my $letter ('A'..'Y') {$ZONE{$letter} = [-1, 0, 0]} return;
232 wakaba 1.1 }
233     for my $letter ('Z', 'A'..'I', 'K'..'M') {
234     $ZONE{$letter} = [+1*$mode, $i++, 0];
235     } $i = 1;
236     for my $letter ('N'..'Y') {
237     $ZONE{$letter} = [-1*$mode, $i++, 0];
238     }
239     }
240    
241 wakaba 1.9 sub _init ($;%) {
242     my $self = shift;
243     my $DEFAULT = Message::Util::make_clone (\%DEFAULT);
244     my %option = @_;
245     $self->SUPER::_init (%$DEFAULT, %option);
246     $self->{date_time} = $option{unix} if defined $option{unix};
247     $self->{secfrac} = $option{frac} if defined $option{frac};
248    
249     my $format = $self->{option}->{format};
250     if ($format =~ /rfc2822/) {
251     $self->{option}->{use_military_zone} = 0;
252     }
253    
254     $self->_set_military_zone_name;
255     }
256    
257     =item $date = Message::Field::Date->new ([%options])
258 wakaba 1.1
259 wakaba 1.9 Constructs a new object. You might pass some options as parameters
260     to the constructor.
261 wakaba 1.1
262     =cut
263    
264 wakaba 1.9 ## Inherited
265 wakaba 1.1
266 wakaba 1.9 =item $date = Message::Field::Date->parse ($field-body, [%options])
267 wakaba 1.1
268 wakaba 1.9 Constructs a new object with given field body. You might pass
269     some options as parameters to the constructor.
270 wakaba 1.1
271     =cut
272    
273     sub parse ($$;%) {
274 wakaba 1.9 my $class = shift;
275     my $self = bless {}, $class;
276     my $body = shift;
277     $self->_init (@_);
278     ($body, @{$self->{comment}})
279     = $self->Message::Util::delete_comment_to_array ($body)
280     if $self->{option}->{use_comment};
281     $body =~ s/^$REG{WSP}+//; $body =~ s/$REG{WSP}+$//;
282     if ($self->{option}->{date_format} eq 'unix') {
283     $self->{date_time} = int ($body);
284     } elsif ($body =~ /^$REG{M_dt_rfc822}$/x) {
285 wakaba 1.1 my ($day, $month, $year, $hour, $minute, $second, $zone)
286     = ($1, uc $2, $3, $4, $5, $6, uc $7);
287     $month = $MONTH{$month} || 1;
288 wakaba 1.9 $year = $self->_obvious_year ($year) if length($year)<4;
289 wakaba 1.1 my ($zone_sign, $zone_hour, $zone_minute) = $self->_zone_string_to_array ($zone);
290     eval '$self->{date_time} = timegm_nocheck
291     ($second, $minute-($zone_sign*$zone_minute), $hour-($zone_sign*$zone_hour),
292     $day, $month-1, $year);';
293 wakaba 1.2 $self->{secfrac} = '';
294     $self->{option}->{zone} = [$zone_sign, $zone_hour, $zone_minute];
295 wakaba 1.9 } elsif ($body =~ /^$REG{M_dt_iso8601}$/x) {
296     my ($year,$month,$day,$hour,$minute,$second,$secfrac,$zone)
297     = ($1, $2, $3, $4, $5, $6, $7, $8);
298     my ($zone_sign, $zone_hour, $zone_minute) = $self->_zone_string_to_array ($zone);
299 wakaba 1.2 eval '$self->{date_time} = timegm_nocheck
300     ($second, $minute-($zone_sign*$zone_minute), $hour-($zone_sign*$zone_hour),
301     $day, $month-1, $year);';
302     $self->{secfrac} = $secfrac;
303 wakaba 1.1 $self->{option}->{zone} = [$zone_sign, $zone_hour, $zone_minute];
304 wakaba 1.9 } elsif ($body =~ /^$REG{M_dt_rfc733}$/x) {
305 wakaba 1.1 my ($day, $month, $year, $hour, $minute, $second, $zone)
306     = ($1, uc $2, $3, $4, $5, $6, uc $7);
307     $month = $MONTH{$month} || 1;
308 wakaba 1.9 $year = $self->_obvious_year ($year) if length($year)<4;
309 wakaba 1.1 my ($zone_sign, $zone_hour, $zone_minute) = $self->_zone_string_to_array ($zone);
310     eval '$self->{date_time} = timegm_nocheck
311     ($second, $minute-($zone_sign*$zone_minute), $hour-($zone_sign*$zone_hour),
312     $day, $month-1, $year);';
313 wakaba 1.2 $self->{secfrac} = '';
314     $self->{option}->{zone} = [$zone_sign, $zone_hour, $zone_minute];
315 wakaba 1.9 } elsif ($body =~ /^$REG{M_dt_rfc724}$/x) {
316 wakaba 1.2 my ($month, $day, $year, $hour, $minute, $second, $zone)
317     = ($1, $2, $3, $4, $5, $6, uc $7);
318 wakaba 1.9 $year = $self->_obvious_year ($year) if length($year)<4;
319 wakaba 1.2 my ($zone_sign, $zone_hour, $zone_minute) = $self->_zone_string_to_array ($zone);
320     eval '$self->{date_time} = timegm_nocheck
321     ($second, $minute-($zone_sign*$zone_minute), $hour-($zone_sign*$zone_hour),
322     $day, $month-1, $year);';
323     $self->{secfrac} = '';
324 wakaba 1.1 $self->{option}->{zone} = [$zone_sign, $zone_hour, $zone_minute];
325 wakaba 1.9 #} elsif ($body =~ /^[0-9]+$/) {
326     # $self->{date_time} = $&;
327 wakaba 1.1 } else {
328 wakaba 1.9 ## From HTTP::Date (revision 1.40) by Gisle Aas
329     #$body =~ s/^\s+//; # kill leading space
330     $body =~ s/^(?:Sun|Mon|Tue|Wed|Thu|Fri|Sat)[a-z]*,?\s*//i; # Useless weekday
331     my ($day, $month, $year, $hour, $minute, $second);
332     my ($secfrac, $zone, $ampm) = ('', $self->{option}->{zone_default_string});
333    
334     # Then we are able to check for most of the formats with this regexp
335     (($day,$month,$year,$hour,$minute,$second,$zone) =
336     $body =~ m"^
337     (\d\d?) # day
338     (?:\s+|[-\/])
339     (\w+) # month
340     (?:\s+|[-\/])
341     (\d+) # year
342     (?:
343     (?:\s+|:) # separator before clock
344     (\d\d?):(\d\d) # hour:min
345     (?::(\d\d))? # optional seconds
346     )? # optional clock
347     \s*
348     ([-+]?\d{2,4}|(?![APap][Mm]\b)[A-Za-z]+)? # timezone
349     $"x)
350    
351     ||
352    
353     # Try the ctime and asctime format
354     (($month, $day, $hour, $minute, $second, $zone, $year) =
355     $body =~ m"^
356     (\w{1,3}) # month
357     \s+
358     (\d\d?) # day
359     \s+
360     (\d\d?):(\d\d) # hour:min
361     (?::(\d\d))? # optional seconds
362     \s+
363     (?:([A-Za-z]+)\s+)? # optional timezone
364     (\d+) # year
365     $"x)
366    
367     ||
368    
369     # Then the Unix 'ls -l' date format
370     (($month, $day, $year, $hour, $minute, $second) =
371     $body =~ m"^
372     (\w{3}) # month
373     \s+
374     (\d\d?) # day
375     \s+
376     (?:
377     (\d\d\d\d) | # year
378     (\d{1,2}):(\d{2}) # hour:min
379     (?::(\d\d))? # optional seconds
380     )
381     $"x)
382    
383     ||
384    
385     # ISO 8601 format '1996-02-29 12:00:00 -0100' and variants
386     (($year, $month, $day, $hour, $minute, $second, $secfrac, $zone) =
387     $body =~ m"^
388     (\d{4}) # year
389     [-\/]?
390     (\d\d?) # numerical month
391     [-\/]?
392     (\d\d?) # day
393     (?:
394     (?:\s+|[-:Tt]) # separator before clock
395     (\d\d?):?(\d\d) # hour:min
396     (?:
397     :?
398     (\d\d)
399     (?:\.?(\d+))? ## optional second frac.
400     )? # optional seconds
401     )? # optional clock
402     \s*
403     ([-+]?\d\d?:?(:?\d\d)?
404     |Z|z)? # timezone (Z is 'zero meridian', i.e. GMT)
405    
406     $"x)
407    
408     ||
409    
410     # ISO 8601 like format '96-02-29 2:0:0 -0100' and variants
411     (($year, $month, $day, $hour, $minute, $second, $secfrac, $zone) =
412     $body =~ m"^
413     (\d+) # year
414     [-/]
415     (\d\d?) # numerical month
416     [-/]
417     (\d\d?) # day
418     (?:
419     (?:\s+|[-:Tt]) # separator before clock
420     (\d\d?):(\d+) # hour:min
421     (?:
422     :
423     (\d+)
424     (?:\.(\d+)) ## optional second frac.
425     )? # optional seconds
426     )? # optional clock
427     \s*
428     ([-+]?\d+(:\d+)?
429     |Z|z)? # timezone (Z is 'zero meridian', i.e. GMT)
430    
431     $"x)
432    
433     ||
434    
435     # Windows 'dir' 11-12-96 03:52PM
436     (($month, $day, $year, $hour, $minute, $ampm) =
437     $body =~ m"^
438     (\d{2}) # numerical month
439     -
440     (\d{2}) # day
441     -
442     (\d{2}) # year
443     \s+
444     (\d\d?):(\d\d)([APap][Mm]) # hour:min AM or PM
445     $"x)
446    
447     #||
448     #return; # unrecognized format
449     ;
450    
451     $day ||= 1;
452     # Translate month name to number
453     $month = $MONTH{uc $month}
454     ##|| ($mon >= 1 && $mon <= 12 && int($mon))
455     || int ($month)
456     || 1;
457    
458     # If the year is missing, we assume first date before the current,
459     # because of the formats we support such dates are mostly present
460     # on "ls -l" listings.
461     unless (defined $year) {
462     my $cur_mon;
463     ($cur_mon, $year) = (localtime)[4, 5];
464     $year += 1900; $cur_mon++;
465     $year-- if $month > $cur_mon;
466     } elsif (length($year) < 3) {
467     $year = $self->_obvious_year ($year);
468     }
469    
470     # Make sure clock elements are defined
471     $hour = 0 unless defined($hour);
472     $minute = 0 unless defined($minute);
473     $second = 0 unless defined($second);
474    
475     # Compensate for AM/PM
476     if ($ampm) {
477     $ampm = uc $ampm;
478     $hour = 0 if $hour == 12 && $ampm eq 'AM';
479     $hour += 12 if $ampm eq 'PM' && $hour != 12;
480     }
481    
482     my ($zone_sign, $zone_hour, $zone_minute) = $self->_zone_string_to_array ($zone);
483     eval '$self->{date_time} = timegm_nocheck
484     ($second, $minute-($zone_sign*$zone_minute), $hour-($zone_sign*$zone_hour),
485     $day, $month-1, $year);';
486     $self->{secfrac} = $secfrac;
487     $self->{option}->{zone} = [$zone_sign, $zone_hour, $zone_minute];
488 wakaba 1.1 }
489     $self;
490     }
491    
492 wakaba 1.9 ## Find "obvious" year
493     sub _obvious_year ($$) {
494     my $self = shift;
495     my $year = shift;
496     if ($self->{option}->{format} =~ /mail|news/) {
497     ## RFC 2822
498     if ( 0 <=$year && $year < 50) {$year += 2000}
499     elsif (50 < $year && $year < 1000) {$year += 1900}
500     } else {
501     ## RFC 2616
502     my $cur_yr = (localtime)[5] + 1900;
503     my $m = $cur_yr % 100;
504     my $tmp = $year;
505     $year += $cur_yr - $m;
506     $m -= $tmp;
507     $year += ($m > 0) ? 100 : -100 if abs($m) > 50;
508     }
509     $year;
510     }
511    
512     =back
513    
514     =head1 METHODS
515    
516     =over 4
517    
518    
519 wakaba 1.4 =head2 $self->unix_time ([$new_time])
520    
521     Returns or set the unix-time (seconds from the Epoch).
522    
523     =cut
524    
525     sub unix_time ($;$) {
526     my $self = shift;
527     my $new_time = shift;
528     if (defined $new_time) {
529 wakaba 1.6 $self->{date_time} = $new_time + 0;
530 wakaba 1.4 }
531     $self->{date_time};
532     }
533    
534 wakaba 1.2 =head2 $self->second_fraction ([$new_fraction])
535    
536     Returns or set the decimal fraction of a second.
537     Value is a string containing of only [0-9]
538     or empty string.
539    
540     =cut
541    
542     sub second_fraction ($;$) {
543     my $self = shift;
544     my $new_fraction = shift;
545     if (defined $new_fraction) {
546     $self->{secfrac} = $new_fraction unless $new_fraction =~ /[^0-9]/;
547     }
548     $self->{secfrac};
549     }
550    
551 wakaba 1.9 =item $field-body = $date->stringify ()
552 wakaba 1.1
553     Returns C<field-body> as a string.
554    
555     =cut
556    
557 wakaba 1.2 sub stringify ($;%) {
558 wakaba 1.1 my $self = shift;
559 wakaba 1.9 my %o = @_;
560     my %option = %{$self->{option}};
561     for (grep {/^-/} keys %o) {$option{substr ($_, 1)} = $o{$_}}
562     unless ($option{format_template}) {
563     if ($option{format} =~ /rfc2822|rfc1123|son-of-rfc1036|usefor|mime/) {
564     $option{format_template} = '%Wdy(local);, %DD(local); %Mon(local); %YYYY(local); %HH(local);:%TT(local);:%SS(local); %zsign;%zHH;%zTT;';
565     } elsif ($option{format} =~ /http/) {
566     $option{format_template} = '%Wdy;, %DD; %Mon; %YYYY; %HH;:%TT;:%SS; GMT';
567     } elsif ($option{format} =~ /rfc822|rfc1036/) {
568     $option{format_template} = '%Wdy(local);, %DD(local); %Mon(local); %YY(local); (%YYYY(local);) %HH(local);:%TT(local);:%SS(local); %zsign;%zHH;%zTT;';
569     } elsif ($option{format} =~ /rfc850/) {
570     $option{format_template} = '%Weekday;, %DD;-%Mon;-%YY; %HH;:%TT;:%SS; GMT';
571     } elsif ($option{format} =~ /asctime/) {
572     $option{format_template} = '%Wdy; %Mon; %DD(pad=>SP); %HH;:%MM;:%SS; %YYYY;';
573     #} elsif ($option{format} =~ /date\(1\)/) {
574     # $option{format_template} = '%Wdy; %Mon; %DD(pad=>SP); %HH;:%MM;:%SS; GMT %YYYY;';
575     } elsif ($option{format} =~ /un[i*]x/) { ## :-)
576     $option{format_template} = '%unix;';
577     } else { ## ISO 8601 (IETF)
578     $option{format_template} = '%YYYY(local);-%MM(local);-%DD(local);T%HH(local);:%TT(local);:%SS(local);%frac(prefix=>.);%zsign;%zHH;:%zTT;';
579     }
580 wakaba 1.2 }
581 wakaba 1.9 $self->_date2str (\%option)
582     . (($option{output_comment} && @{$self->{comment}} > 0)?
583     ' ' . $self->_comment_stringify: '');
584     }
585     *as_string = \&stringify;
586     *as_plain_string = \&stringify;
587    
588     sub _date2str ($\%) {
589     my $self = shift;
590     my $option = shift;
591     my $template = $option->{format_template};
592     my $time = $self->{date_time};
593     my $zone = $option->{zone};
594     if (ref $zone) {}
595     elsif (length $zone) {$zone = [$self->_zone_string_to_array ($zone)]}
596     my $l_time = $time + $zone->[0] * ($zone->[1] * 60 + $zone->[2]) * 60;
597     my %time = (unix => $time,
598     tm => [gmtime ($time)],
599     l_unix => $l_time,
600     l_tm => [gmtime ($l_time)],
601     zone => $zone);
602     $template =~ s{%([A-Za-z0-9_]+)(?:\(([A-Za-z0-9,.:\x09\x20=>_-]*)\))?;}{
603     my ($f, $a) = ($1, $2);
604     my $function = $option->{str2time}->{$f};
605     if (ref $function) {
606     my %a;
607     for (split /[\x09\x20]*,[\x09\x20]*/, $a) {
608     if (/^([^=]+)=>(.+)$/) {$a{$1} = $2}
609     else {$a{$_} = 1}
610     }
611     $a{_prefix} = $a{local}? 'l_': '';
612     $a{_fmt} = $a{pad} eq 'SP'? '%2d':
613     $a{pad} eq 'none'? '%d':
614     '%02d';
615     my $r = &$function ($self, \%time, \%a);
616     length $r? $a{prefix}.$r.$a{suffix}: '';
617     } elsif (length $function) {
618     $function;
619     } else {
620     "[$f: undef]";
621     }
622     }gex;
623     $template;
624 wakaba 1.1 }
625    
626 wakaba 1.2
627 wakaba 1.1
628     sub _zone_string_to_array ($$;$) {
629     my $self = shift;
630     my $zone = shift;
631     my $format = shift;
632     my @azone = [+1, 0, 0];
633 wakaba 1.9 $zone =~ tr/\x09\x20//d;
634     if ($zone =~ /([+-])([0-9][0-9])([0-9][0-9])/) {
635     @azone = ("${1}1", $2, $3);
636     } elsif ($zone =~ /([+-]?)([0-9]+)(?:[:.-]([0-9]+))?/) {
637 wakaba 1.1 @azone = ("${1}1", $2, $3);
638     } else { $zone =~ tr/-//d;
639     if (ref $ZONE{$zone}) {@azone = @{$ZONE{$zone}}}
640     elsif ($zone) {@azone = (-1, 0, 0)}
641     }
642     # }
643     @azone;
644     }
645    
646 wakaba 1.9 =item $option-value = $date->option ($option-name)
647    
648     Gets option value.
649    
650     =item $date->option ($option-name, $option-value, ...)
651    
652     Set option value(s). You can pass multiple option name-value pair
653     as parameter when setting.
654    
655     =item $clone = $date->clone ()
656    
657     Returns a copy of the object.
658    
659     =cut
660    
661     ## option, clone, method_available: Inherited
662    
663 wakaba 1.1 =head1 EXAMPLE
664    
665 wakaba 1.2 use Message::Field::Date;
666 wakaba 1.1
667 wakaba 1.9 print Message::Field::Date->new (unix => time,
668     -zone => '+0900'),"\n"; ## Thu, 16 May 2002 17:53:44 +0900
669     print Message::Field::Date->new (unix => time,
670     -format_template => ## Century: 21, Year: 02, Month: 05
671     'Century: %CC;, Year: %YY;, Month: %MM;'),"\n";
672    
673 wakaba 1.2 my $field_body = '04 Feb 2002 00:12:33 CST';
674     my $field = Message::Field::Date->parse ($field_body);
675 wakaba 1.1
676 wakaba 1.9 print "RFC 2822:\t", $field->stringify (-format => 'mail-rfc2822'), "\n";
677     print "HTTP preferred:\t", $field->stringify (-format => 'http-1.1'), "\n";
678     print "ISO 8601:\t", $field->stringify (-format => 'mail-cpim'), "\n";
679     ## RFC 2822: Mon, 04 Feb 2002 00:12:33 -0600
680     ## HTTP preferred: Mon, 04 Feb 2002 06:12:33 GMT
681     ## ISO 8601: 2002-02-04T00:12:33-06:00
682 wakaba 1.1
683     =head1 LICENSE
684    
685     Copyright 2002 wakaba E<lt>w@suika.fam.cxE<gt>.
686    
687     This program is free software; you can redistribute it and/or modify
688     it under the terms of the GNU General Public License as published by
689     the Free Software Foundation; either version 2 of the License, or
690     (at your option) any later version.
691    
692     This program is distributed in the hope that it will be useful,
693     but WITHOUT ANY WARRANTY; without even the implied warranty of
694     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
695     GNU General Public License for more details.
696    
697     You should have received a copy of the GNU General Public License
698     along with this program; see the file COPYING. If not, write to
699     the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
700     Boston, MA 02111-1307, USA.
701    
702     =head1 CHANGE
703    
704     See F<ChangeLog>.
705 wakaba 1.9 $Date: 2002/05/08 09:11:31 $
706 wakaba 1.1
707     =cut
708    
709     1;

admin@suikawiki.org
ViewVC Help
Powered by ViewVC 1.1.24