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   563088 use strict;
  11         16  
  11         255  
4 11     11   33 use warnings;
  11         12  
  11         213  
5 11     11   33 use Carp;
  11         15  
  11         598  
6              
7             our $VERSION = '0.08';
8              
9 11     11   8390 use DateTime;
  11         3220928  
  11         432  
10 11     11   73 use DateTime::Locale;
  11         12  
  11         172  
11 11     11   34 use DateTime::TimeZone;
  11         12  
  11         151  
12 11     11   6701 use DateTime::Format::Strptime;
  11         140536  
  11         60  
13 11     11   6133 use DateTime::Format::Mail;
  11         17606  
  11         334  
14 11     11   4391 use DateTime::Format::W3CDTF;
  11         6126  
  11         314  
15 11     11   4554 use DateTime::Format::MySQL;
  11         140653  
  11         343  
16 11     11   4686 use DateTime::Format::HTTP;
  11         41306  
  11         311  
17 11     11   56 use Scalar::Util qw( blessed );
  11         12  
  11         5395  
18              
19             sub _parse_options {
20 83     83   86 my $self = shift;
21              
22 83 100       184 if ( @_ == 1 ) {
23 7 100       20 return %{$_[0]} if ref $_[0] eq 'HASH';
  3         12  
24 4 100       35 return @{$_[0]} if ref $_[0] eq 'ARRAY';
  2         7  
25             }
26 78 100       389 croak "Odd number of elements in hash assignment" if @_ % 2;
27 76         165 return @_;
28             }
29              
30             sub new {
31 31     31 1 13017 my $class = shift;
32              
33 31         88 my %config = $class->_parse_options(@_);
34              
35 31   100     276 $config{on_error} ||= 'croak';
36              
37 31         162 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     2648 $self->time_zone( $config{time_zone} || delete $config{timezone} || 'UTC' );
49 31   100     160 $self->locale( $config{locale} || 'en-US' );
50              
51 31         71 $self;
52             }
53              
54             sub format {
55 43     43 1 683 my ($self, $name, $package) = @_;
56              
57 43 100       86 if ( $package ) {
58 5 100       8 if ( ref $package ) {
59 1         2 $self->{format}->{lc $name} = $package;
60             }
61             else {
62 4 100       12 unless ( $package =~ s/^\+// ) {
63 3         3 $package =~ s/^DateTime::Format:://;
64 3         6 $package = "DateTime::Format\::$package";
65             }
66 4         190 eval "require $package;";
67 4 50       99 croak $@ if $@;
68 4 100       33 $self->{format}->{lc $name} =
69             ( $package->can('new') ) ? $package->new : $package;
70             }
71             }
72 43         191 $self->{format}->{lc $name};
73             }
74              
75             sub time_zone {
76 49     49 1 10009 my ($self, $zone) = @_;
77              
78 49 100       95 if ( $zone ) {
79             $self->{config}->{time_zone} =
80 37 100 66     272 ( blessed $zone && $zone->isa('DateTime::TimeZone') )
81             ? $zone
82             : DateTime::TimeZone->new( name => $zone );
83             }
84 46         2340 $self->{config}->{time_zone};
85             }
86              
87             sub locale {
88 42     42 1 8657 my ($self, $locale) = @_;
89              
90 42 100       85 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         1010 $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   3850 my $self = shift;
104              
105 13         34 my %options = $self->_parse_options(@_);
106              
107 13         31 $self->_merge_config( \%options );
108              
109 13         18 my $dt = eval { DateTime->$method( %options ) };
  13         74  
110 13 50       5584 $self->_error( $@ ) if $@;
111 13         32 return $dt;
112             };
113              
114 11     11   53 no strict 'refs';
  11         13  
  11         9535  
115             *{$method} = $code;
116             }
117             }
118              
119             sub from {
120 15     15 1 2754 my $self = shift;
121              
122 15         40 my %options = $self->_parse_options(@_);
123              
124 13 100       54 return $self->from_epoch( %options ) if $options{epoch};
125 8 100       21 return $self->from_object( %options ) if $options{object};
126              
127 7         17 $self->_merge_config( \%options );
128              
129 7         10 my $dt = eval { DateTime->new( %options ) };
  7         130  
130 7 50       1837 $self->_error( $@ ) if $@;
131 7         20 return $dt;
132             }
133              
134             sub from_epoch {
135 7     7 1 15 my $self = shift;
136 7         8 my $epoch = shift;
137 7 100       16 $epoch = shift if $epoch eq 'epoch';
138 7         17 my %options = $self->_parse_options(@_);
139              
140 7         21 $self->_merge_config( \%options );
141              
142 7         8 my $dt = eval { DateTime->from_epoch( epoch => $epoch, %options ) };
  7         28  
143 7 50       2346 $self->_error( $@ ) if $@;
144              
145 7         33 return $dt;
146             }
147              
148             sub from_object {
149 3     3 1 346 my $self = shift;
150 3         3 my $object = shift;
151 3 100       7 $object = shift if $object eq 'object';
152 3         44 my %options = $self->_parse_options(@_);
153              
154 3         4 $self->_merge_config( \%options );
155              
156 3         2 my $orig_time_zone;
157 3 50       6 if (my $time_zone = delete $options{time_zone}) {
158 3 50       21 if ($object->can('set_time_zone')) {
159 3         7 $orig_time_zone = $object->time_zone;
160 3         11 $object->set_time_zone($time_zone);
161             }
162             }
163              
164 3         16 my $dt = eval { DateTime->from_object( object => $object, %options ) };
  3         9  
165 3 50       1040 $self->_error( $@ ) if $@;
166              
167 3 50       5 if ($orig_time_zone) {
168 3         7 $object->set_time_zone($orig_time_zone);
169             }
170              
171 3         20 return $dt;
172             }
173              
174 3     3 1 5883 sub from_rss { shift->parse_as( wwwc => @_ ); }
175 6     6 1 3675 sub from_mail { shift->parse_as( mail => @_ ); }
176 2     2 1 2709 sub from_mysql { shift->parse_as( mysql => @_ ); }
177 2     2 1 3858 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 25 my ($self, $formatter, $string, @args) = @_;
184              
185 13         28 my %options = $self->_parse_options(@args);
186              
187 13         31 $self->_load( $formatter );
188              
189 13         16 my $dt = eval { $self->format($formatter)->parse_datetime( $string ) };
  13         29  
190 13 100       6535 if ( $@ ) {
191 4         7808 $self->_error( $@ );
192             }
193             else {
194 9         30 $self->_merge_config( \%options );
195 9         26 $self->_set_config( $dt, \%options );
196 9         894 return $dt;
197             }
198             }
199              
200             sub parse {
201 1     1 1 6 my ($self, $pattern, $string, @args) = @_;
202              
203 1         3 my %options = $self->_parse_options(@args);
204              
205 1 50       3 unless ( $self->{parser}->{$pattern} ) {
206 1         5 $self->_merge_config( \%options );
207 1         2 $options{pattern} = $pattern;
208 1         8 my $parser = DateTime::Format::Strptime->new( %options );
209 1         1021 $self->{parser}->{$pattern} = $parser;
210             }
211 1         2 my $dt = eval { $self->{parser}->{$pattern}->parse_datetime( $string ) };
  1         4  
212 1 50       783 if ( $@ ) {
213 0         0 $self->_error( $@ );
214             }
215             else {
216 1         5 $self->_set_config( $dt, \%options );
217 1         81 return $dt;
218             }
219             }
220              
221             *strptime = \&parse;
222              
223 1     1 1 406 sub for_rss { shift->render_as( wwwc => @_ ); }
224 1     1 1 271 sub for_mail { shift->render_as( mail => @_ ); }
225 1     1 1 180 sub for_mysql { shift->render_as( mysql => @_ ); }
226 1     1 1 297 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 14 my ($self, $formatter, @args) = @_;
233              
234 4         11 $self->_load( $formatter );
235              
236 4         16 my $dt = $self->_datetime( @args );
237              
238 4         5 my $str = eval { $self->format($formatter)->format_datetime( $dt ) };
  4         12  
239 4 50       590 $self->_error( $@ ) if $@;
240 4         22 return $str;
241             }
242              
243             sub _merge_config {
244 40     40   47 my ($self, $options) = @_;
245              
246 40         64 foreach my $key (qw( time_zone locale )) {
247 80 50       168 next unless defined $self->{config}->{$key};
248 80 50       134 next if defined $options->{$key};
249 80         138 $options->{$key} = $self->{config}->{$key};
250             }
251             }
252              
253             sub _datetime {
254 7     7   3064 my $self = shift;
255              
256 7 50       21 return $self->now unless @_;
257 7 50 66     53 return $_[0] if @_ == 1 && blessed $_[0] && $_[0]->isa('DateTime');
      33        
258 7         29 return $self->from( @_ );
259             }
260              
261             sub _load {
262 17     17   17 my ($self, $formatter) = @_;
263              
264 17 50       36 unless ( $self->format($formatter) ) {
265 0         0 $self->format( $formatter => "DateTime::Format\::$formatter" );
266             }
267             }
268              
269             sub _set_config {
270 10     10   15 my ($self, $dt, $options) = @_;
271              
272 10   33     22 $options ||= $self->{config};
273              
274 10         18 foreach my $key (qw( time_zone locale )) {
275 20         860 my $func = "set_$key";
276 20 50       139 $dt->$func( $options->{$key} ) if $options->{$key};
277             }
278             }
279              
280             sub _error {
281 4     4   9 my ($self, $message) = @_;
282              
283 4         9 my $on_error = $self->{config}->{on_error};
284              
285 4 50       26 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