File Coverage

lib/DateTimeX/Web.pm
Criterion Covered Total %
statement 170 175 97.1
branch 52 72 72.2
condition 14 21 66.6
subroutine 38 38 100.0
pod 18 18 100.0
total 292 324 90.1


line stmt bran cond sub pod time code
1             package DateTimeX::Web;
2              
3 11     11   1090937 use strict;
  11         113  
  11         324  
4 11     11   60 use warnings;
  11         21  
  11         260  
5 11     11   55 use Carp;
  11         22  
  11         784  
6              
7             our $VERSION = '0.09';
8              
9 11     11   9801 use DateTime;
  11         5480748  
  11         610  
10 11     11   112 use DateTime::Locale;
  11         24  
  11         234  
11 11     11   76 use DateTime::TimeZone;
  11         28  
  11         205  
12 11     11   7477 use DateTime::Format::Strptime;
  11         600281  
  11         55  
13 11     11   7697 use DateTime::Format::Mail;
  11         60345  
  11         432  
14 11     11   5047 use DateTime::Format::W3CDTF;
  11         8631  
  11         354  
15 11     11   4905 use DateTime::Format::MySQL;
  11         163036  
  11         417  
16 11     11   4975 use DateTime::Format::HTTP;
  11         56300  
  11         405  
17 11     11   87 use Scalar::Util qw( blessed );
  11         27  
  11         7681  
18              
19             sub _parse_options {
20 83     83   142 my $self = shift;
21              
22 83 100       224 if ( @_ == 1 ) {
23 7 100       22 return %{$_[0]} if ref $_[0] eq 'HASH';
  3         17  
24 4 100       9 return @{$_[0]} if ref $_[0] eq 'ARRAY';
  2         8  
25             }
26 78 100       455 croak "Odd number of elements in hash assignment" if @_ % 2;
27 76         200 return @_;
28             }
29              
30             sub new {
31 31     31 1 15886 my $class = shift;
32              
33 31         103 my %config = $class->_parse_options(@_);
34              
35 31   100     160 $config{on_error} ||= 'croak';
36              
37 31         205 my $self = bless {
38             config => \%config,
39             format => {
40             mail => DateTime::Format::Mail->new( loose => 1 ),
41             wwwc => DateTime::Format::W3CDTF->new,
42             mysql => DateTime::Format::MySQL->new,
43             http => 'DateTime::Format::HTTP', # ::HTTP has no 'new'
44             },
45             parser => {},
46             }, $class;
47              
48 31   100     3573 $self->time_zone( $config{time_zone} || delete $config{timezone} || 'UTC' );
49 31   100     187 $self->locale( $config{locale} || 'en-US' );
50              
51 31         96 $self;
52             }
53              
54             sub format {
55 43     43 1 1225 my ($self, $name, $package) = @_;
56              
57 43 100       90 if ( $package ) {
58 5 100       11 if ( ref $package ) {
59 1         4 $self->{format}->{lc $name} = $package;
60             }
61             else {
62 4 100       15 unless ( $package =~ s/^\+// ) {
63 3         9 $package =~ s/^DateTime::Format:://;
64 3         10 $package = "DateTime::Format\::$package";
65             }
66 4         234 eval "require $package;";
67 4 50       152 croak $@ if $@;
68 4 100       46 $self->{format}->{lc $name} =
69             ( $package->can('new') ) ? $package->new : $package;
70             }
71             }
72 43         274 $self->{format}->{lc $name};
73             }
74              
75             sub time_zone {
76 49     49 1 13759 my ($self, $zone) = @_;
77              
78 49 100       115 if ( $zone ) {
79             $self->{config}->{time_zone} =
80 37 100 66     359 ( blessed $zone && $zone->isa('DateTime::TimeZone') )
81             ? $zone
82             : DateTime::TimeZone->new( name => $zone );
83             }
84 46         3346 $self->{config}->{time_zone};
85             }
86              
87             sub locale {
88 42     42 1 10506 my ($self, $locale) = @_;
89              
90 42 100       102 if ( $locale ) {
91             $self->{config}->{locale} =
92 36 100 66     287 ( blessed $locale && ($locale->isa('DateTime::Locale::root') || $locale->isa('DateTime::Locale::FromData') ) )
93             ? $locale
94             : DateTime::Locale->load( $locale );
95             }
96 39         1365 $self->{config}->{locale};
97             }
98              
99             {
100             my @constructors = qw(now today last_day_of_month from_day_of_year);
101             for my $method (@constructors) {
102             my $code = sub {
103 13     13   5101 my $self = shift;
104              
105 13         40 my %options = $self->_parse_options(@_);
106              
107 13         48 $self->_merge_config( \%options );
108              
109 13         22 my $dt = eval { DateTime->$method( %options ) };
  13         85  
110 13 50       7837 $self->_error( $@ ) if $@;
111 13         43 return $dt;
112             };
113              
114 11     11   95 no strict 'refs';
  11         27  
  11         14697  
115             *{$method} = $code;
116             }
117             }
118              
119             sub from {
120 15     15 1 3082 my $self = shift;
121              
122 15         47 my %options = $self->_parse_options(@_);
123              
124 13 100       52 return $self->from_epoch( %options ) if $options{epoch};
125 8 100       27 return $self->from_object( %options ) if $options{object};
126              
127 7         29 $self->_merge_config( \%options );
128              
129 7         16 my $dt = eval { DateTime->new( %options ) };
  7         129  
130 7 50       2478 $self->_error( $@ ) if $@;
131 7         26 return $dt;
132             }
133              
134             sub from_epoch {
135 7     7 1 21 my $self = shift;
136 7         12 my $epoch = shift;
137 7 100       21 $epoch = shift if $epoch eq 'epoch';
138 7         17 my %options = $self->_parse_options(@_);
139              
140 7         24 $self->_merge_config( \%options );
141              
142 7         14 my $dt = eval { DateTime->from_epoch( epoch => $epoch, %options ) };
  7         32  
143 7 50       3513 $self->_error( $@ ) if $@;
144              
145 7         33 return $dt;
146             }
147              
148             sub from_object {
149 3     3 1 541 my $self = shift;
150 3         5 my $object = shift;
151 3 100       10 $object = shift if $object eq 'object';
152 3         74 my %options = $self->_parse_options(@_);
153              
154 3         11 $self->_merge_config( \%options );
155              
156 3         5 my $orig_time_zone;
157 3 50       10 if (my $time_zone = delete $options{time_zone}) {
158 3 50       15 if ($object->can('set_time_zone')) {
159 3         10 $orig_time_zone = $object->time_zone;
160 3         16 $object->set_time_zone($time_zone);
161             }
162             }
163              
164 3         27 my $dt = eval { DateTime->from_object( object => $object, %options ) };
  3         11  
165 3 50       1774 $self->_error( $@ ) if $@;
166              
167 3 50       8 if ($orig_time_zone) {
168 3         18 $object->set_time_zone($orig_time_zone);
169             }
170              
171 3         27 return $dt;
172             }
173              
174 3     3 1 4984 sub from_rss { shift->parse_as( wwwc => @_ ); }
175 6     6 1 4489 sub from_mail { shift->parse_as( mail => @_ ); }
176 2     2 1 4538 sub from_mysql { shift->parse_as( mysql => @_ ); }
177 2     2 1 4479 sub from_http { shift->parse_as( http => @_ ); }
178              
179             *from_wwwc = \&from_rss;
180             *from_rss20 = \&from_mail;
181              
182             sub parse_as {
183 13     13 1 40 my ($self, $formatter, $string, @args) = @_;
184              
185 13         36 my %options = $self->_parse_options(@args);
186              
187 13         62 $self->_load( $formatter );
188              
189 13         24 my $dt = eval { $self->format($formatter)->parse_datetime( $string ) };
  13         32  
190 13 100       10905 if ( $@ ) {
191 4         12179 $self->_error( $@ );
192             }
193             else {
194 9         49 $self->_merge_config( \%options );
195 9         35 $self->_set_config( $dt, \%options );
196 9         1146 return $dt;
197             }
198             }
199              
200             sub parse {
201 1     1 1 8 my ($self, $pattern, $string, @args) = @_;
202              
203 1         4 my %options = $self->_parse_options(@args);
204              
205 1 50       4 unless ( $self->{parser}->{$pattern} ) {
206 1         5 $self->_merge_config( \%options );
207 1         3 $options{pattern} = $pattern;
208 1         10 my $parser = DateTime::Format::Strptime->new( %options );
209 1         1941 $self->{parser}->{$pattern} = $parser;
210             }
211 1         3 my $dt = eval { $self->{parser}->{$pattern}->parse_datetime( $string ) };
  1         8  
212 1 50       1171 if ( $@ ) {
213 0         0 $self->_error( $@ );
214             }
215             else {
216 1         9 $self->_set_config( $dt, \%options );
217 1         124 return $dt;
218             }
219             }
220              
221             *strptime = \&parse;
222              
223 1     1 1 437 sub for_rss { shift->render_as( wwwc => @_ ); }
224 1     1 1 329 sub for_mail { shift->render_as( mail => @_ ); }
225 1     1 1 329 sub for_mysql { shift->render_as( mysql => @_ ); }
226 1     1 1 323 sub for_http { shift->render_as( http => @_ ); }
227              
228             *for_wwwc = \&for_rss;
229             *for_rss20 = \&for_mail;
230              
231             sub render_as {
232 4     4 1 22 my ($self, $formatter, @args) = @_;
233              
234 4         15 $self->_load( $formatter );
235              
236 4         19 my $dt = $self->_datetime( @args );
237              
238 4         10 my $str = eval { $self->format($formatter)->format_datetime( $dt ) };
  4         17  
239 4 50       937 $self->_error( $@ ) if $@;
240 4         28 return $str;
241             }
242              
243             sub _merge_config {
244 40     40   83 my ($self, $options) = @_;
245              
246 40         91 foreach my $key (qw( time_zone locale )) {
247 80 50       208 next unless defined $self->{config}->{$key};
248 80 50       163 next if defined $options->{$key};
249 80         195 $options->{$key} = $self->{config}->{$key};
250             }
251             }
252              
253             sub _datetime {
254 7     7   3148 my $self = shift;
255              
256 7 50       24 return $self->now unless @_;
257 7 50 66     39 return $_[0] if @_ == 1 && blessed $_[0] && $_[0]->isa('DateTime');
      33        
258 7         36 return $self->from( @_ );
259             }
260              
261             sub _load {
262 17     17   36 my ($self, $formatter) = @_;
263              
264 17 50       43 unless ( $self->format($formatter) ) {
265 0         0 $self->format( $formatter => "DateTime::Format\::$formatter" );
266             }
267             }
268              
269             sub _set_config {
270 10     10   33 my ($self, $dt, $options) = @_;
271              
272 10   33     31 $options ||= $self->{config};
273              
274 10         25 foreach my $key (qw( time_zone locale )) {
275 20         1326 my $func = "set_$key";
276 20 50       160 $dt->$func( $options->{$key} ) if $options->{$key};
277             }
278             }
279              
280             sub _error {
281 4     4   17 my ($self, $message) = @_;
282              
283 4         12 my $on_error = $self->{config}->{on_error};
284              
285 4 50       44 return if $on_error eq 'ignore';
286 0 0         return $on_error->( $message ) if ref $on_error eq 'CODE';
287              
288 0           local $Carp::CarpLevel = 1;
289 0           croak $message;
290             }
291              
292             1;
293              
294             __END__
295              
296             =head1 NAME
297              
298             DateTimeX::Web - DateTime factory for web apps
299              
300             =head1 SYNOPSIS
301              
302             use DateTimeX::Web
303              
304             # create a factory.
305             my $dtx = DateTimeX::Web->new(time_zone => 'Asia/Tokyo');
306              
307             # then, grab a DateTime object from there.
308             my $obj = $dtx->now;
309              
310             # with arguments for a DateTime constructor.
311             my $obj = $dtx->from(year => 2008, month => 2, day => 9);
312              
313             # or with epoch (you don't need 'epoch =>' as it's obvious).
314             my $obj = $dtx->from_epoch(time);
315              
316             # or with a WWWC datetime format string.
317             my $obj = $dtx->from_rss('2008-02-09T01:00:02');
318              
319             # actually you can use any Format plugins.
320             my $obj = $dtx->parse_as(MySQL => '2008-02-09 01:00:02');
321              
322             # of course you may need to parse with strptime.
323             my $obj = $dtx->parse('%Y-%m-%d', $string);
324              
325             # you may want to create a datetime string for HTTP headers.
326             my $str = $dtx->for_http;
327              
328             # or for emails (you can pass an arbitrary DateTime object).
329             my $str = $dtx->for_mail($dt);
330              
331             # or for database (with arguments for a DateTime constructor).
332             my $str = $dtx->for_mysql(year => 2007, month => 3, day => 3);
333              
334             # actually you can use any Format plugins.
335             my $str = $dtx->render_as(MySQL => $dt);
336              
337             # you want finer control?
338             my $str = $dtx->format('mysql')->format_date($dt);
339              
340             =head1 DESCRIPTION
341              
342             The DateTime framework is quite useful and complete. However, sometimes it's a bit too strict and cumbersome. Also, we usually need to load too many common DateTime components when we build a web application. That's not DRY.
343              
344             So, here's a factory to make it sweet. If you want more chocolate or cream, help yourself. The DateTime framework boasts a variety of flavors.
345              
346             =head1 METHODS
347              
348             =head2 new
349              
350             creates a factory object. If you pass a hash, or a hash reference, it will be passed to a DateTime constructor. You usually want to provide a sane "time_zone" option.
351              
352             Optionally, you can pass an "on_error" option ("ignore"/"croak"/some code reference) to the constructor. DateTimeX::Web croaks by default when DateTime spits an error. If "ignore" is set, DateTimeX::Web would ignore the error and return undef. If you want finer control, provide a code reference.
353              
354             =head2 format
355              
356             takes a formatter's base name and returns the corresponding DateTime::Format:: object. You can pass an optional formatter package name/object to replace the previous formatter (or to add a new one).
357              
358             =head2 time_zone, locale
359              
360             returns the current time zone/locale object of the factory, which would be passed to every DateTime object it creates. You can pass an optional time zone/locale string/object to replace.
361              
362             =head1 METHODS TO GET A DATETIME OBJECT
363              
364             =head2 now, today, from_epoch, from_object, from_day_of_year, last_day_of_month
365              
366             returns a DateTime object as you expect.
367              
368             =head2 from
369              
370             takes arguments for a DateTime constructor and returns a DateTime object. Also, You can pass (epoch => time) pair for convenience.
371              
372             =head2 from_rss, from_wwwc
373              
374             takes a W3CDTF (ISO 8601) datetime string used by RSS 1.0 etc, and returns a DateTime object.
375              
376             =head2 from_mail, from_rss20
377              
378             takes a RFC2822 compliant datetime string used by email, and returns a DateTime object.
379              
380             =head2 from_mysql
381              
382             takes a MySQL datetime string, and returns a DateTime object.
383              
384             =head2 from_http
385              
386             takes a HTTP datetime string, and returns a DateTime object.
387              
388             =head2 parse_as
389              
390             takes a name of DateTime::Format plugin and some arguments for it, and returns a DateTime object.
391              
392             =head2 parse, strptime
393              
394             takes a strptime format string and a datetime string, and returns a DateTime object.
395              
396             =head1 METHODS TO GET A DATETIME STRING
397              
398             =head2 for_rss, for_wwwc
399              
400             may or may not take a DateTime object (or arguments for a DateTime constructor), and returns a W3CDTF datetime string.
401              
402             =head2 for_mail, for_rss20
403              
404             the same as above but returns a RFC2822 datetime string.
405              
406             =head2 for_mysql
407              
408             the same as above but returns a MySQL datetime string.
409              
410             =head2 for_http
411              
412             the same as above but returns a HTTP datetime string.
413              
414             =head2 render_as
415              
416             takes a name of DateTime::Format plugin and the same thing(s) as above, and returns a formatted string.
417              
418             =head1 SEE ALSO
419              
420             L<DateTime>, L<DateTime::Format::Mail>, L<DateTime::Format::MySQL>, L<DateTime::Format::W3CDFT>, L<DateTime::Format::HTTP>, L<DateTime::Format::Strptime>, L<DateTime::TimeZone>, L<DateTime::Locale>
421              
422             =head1 AUTHOR
423              
424             Kenichi Ishigaki, E<lt>ishigaki@cpan.orgE<gt>
425              
426             =head1 COPYRIGHT AND LICENSE
427              
428             Copyright (C) 2008 by Kenichi Ishigaki.
429              
430             This program is free software; you can redistribute it and/or
431             modify it under the same terms as Perl itself.
432              
433             =cut