File Coverage

blib/lib/Date/Parser.pm
Criterion Covered Total %
statement 63 73 86.3
branch 14 24 58.3
condition 3 9 33.3
subroutine 12 13 92.3
pod 1 2 50.0
total 93 121 76.8


line stmt bran cond sub pod time code
1             #!/usr/bin/perl
2              
3             package Date::Parser;
4              
5 1     1   45441 use strict;
  1         2  
  1         48  
6 1     1   6 use warnings;
  1         2  
  1         31  
7              
8 1     1   754 use Date::Parser::Date;
  1         3  
  1         35  
9              
10 1     1   12 use Date::Format;
  1         1  
  1         97  
11 1         2321 use I18N::Langinfo qw( langinfo
12             ABDAY_1 ABDAY_2 ABDAY_3 ABDAY_4 ABDAY_5 ABDAY_6 ABDAY_7
13             ABMON_1 ABMON_2 ABMON_3 ABMON_4 ABMON_5 ABMON_6
14             ABMON_7 ABMON_8 ABMON_9 ABMON_10 ABMON_11 ABMON_12
15             DAY_1 DAY_2 DAY_3 DAY_4 DAY_5 DAY_6 DAY_7
16             MON_1 MON_2 MON_3 MON_4 MON_5 MON_6
17             MON_7 MON_8 MON_9 MON_10 MON_11 MON_12
18 1     1   1049 );
  1         897  
19              
20             our $VERSION = 0.4;
21              
22             my @days = map { langinfo $_ } (
23             DAY_1, DAY_2, DAY_3, DAY_4, DAY_5, DAY_6, DAY_7
24             );
25             my @days_ab = map { langinfo $_ } (
26             ABDAY_1, ABDAY_2, ABDAY_3, ABDAY_4, ABDAY_5, ABDAY_6, ABDAY_7
27             );
28             my @months = map { langinfo $_ } (
29             MON_1, MON_2, MON_3, MON_4, MON_5, MON_6,
30             MON_7, MON_8, MON_9, MON_10, MON_11, MON_12
31             );
32             my @months_ab = map { langinfo $_ } (
33             ABMON_1, ABMON_2, ABMON_3, ABMON_4, ABMON_5, ABMON_6,
34             ABMON_7, ABMON_8, ABMON_9, ABMON_10, ABMON_11, ABMON_12
35             );
36              
37             my $format_chars = {
38             '%' => { # PERCENT
39             regexp => '%',
40             },
41             'a' => { # day of the week abbr
42             regexp => '('.join("|", @days_ab).')',
43             type => 'dow',
44             parser => sub { for (0..$#days_ab) { return $_ if ($_[0] eq $days_ab[$_]); } },
45             },
46             'A' => { # day of the week
47             regexp => '('.join("|", @days).')',
48             type => 'dow',
49             parser => sub { for (0..$#days) { return $_ if ($_[0] eq $days[$_]); } },
50             },
51             'b' => { # month abbr
52             regexp => '('.join("|", @months_ab).')',
53             type => 'month',
54             parser => sub { for (0..$#months_ab) { return $_ if ($_[0] eq $months_ab[$_]); } },
55             },
56             'B' => { # month
57             regexp => '('.join("|", @months).')',
58             type => 'month',
59             parser => sub { for (0..$#months) { return $_ if ($_[0] eq $months[$_]); } },
60             },
61             'd' => { # numeric day of the month, with leading zeros (eg 01..31)
62             regexp => '(0[1-9]|[1-2][0-9]|3[01])',
63             type => 'day',
64             parser => \&_strip_zero,
65             },
66             'e' => { # like %d, but a leading zero is replaced by a space (eg 1..31)
67             regexp => '( [1-9]|[1-2][0-9]|3[01])',
68             type => 'day',
69             parser => \&_strip_leading_space,
70             },
71             'h' => { # month abbr
72             regexp => '('.join("|", @months_ab).')',
73             type => 'month',
74             parser => sub { for (0..$#months_ab) { return $_ if ($_[0] eq $months_ab[$_]); } },
75             },
76             'H' => { # hour, 24 hour clock, leading 0's)
77             regexp => '([0-1][0-9]|2[0-3])',
78             type => 'hour24',
79             parser => \&_strip_leading_space,
80             },
81             'I' => { # hour, 12 hour clock, leading 0's)
82             regexp => '(0[1-9]|1[0-2])',
83             type => 'hour12',
84             parser => \&_strip_leading_space,
85             },
86             'j' => { # day of the year
87             regexp => '(0[0-9][0-9]|[1-2][0-9][0-9]|3[0-5][0-9]|36[0-6])',
88             type => 'doy',
89             parser => \&_strip_zeros,
90             },
91             'k' => { # hour
92             regexp => '( [0-9]|1[0-9]|2[0-3])',
93             type => 'hour24',
94             parser => \&_strip_leading_space,
95             },
96             'l' => { # hour, 12 hour clock
97             regexp => '( [0-9]|1[0-2])',
98             type => 'hour12',
99             parser => \&_strip_leading_space,
100             },
101             'L' => { # month number, starting with 1
102             regexp => '( [0-9]|1[0-2])',
103             type => 'month',
104             parser => sub { $_[0] = _strip_leading_space($_[0]); --$_[0]; $_[0] },
105             },
106             'm' => { # month number, starting with 01
107             regexp => '(0[0-9]|1[0-2])',
108             type => 'month',
109             parser => sub { $_[0] = _strip_zero($_[0]); --$_[0]; $_[0] },
110             },
111             'M' => { # minute, leading 0's
112             regexp => '([0-5][0-9])',
113             type => 'min',
114             parser => \&_strip_zero,
115             },
116             'o' => { # ornate day of month -- "1st", "2nd", "25th", etc.
117             regexp => '([1-2][0-9]|3[0-1])\S+', # TODO: localized st, dn, rd, th?
118             type => 'day',
119             },
120             'p' => { # AM or PM
121             regexp => '(AM|PM)',
122             type => 'hour12_ind',
123             },
124             'P' => { # am or pm (Yes %p and %P are backwards :)
125             regexp => '(am|pm)',
126             type => 'hour12_ind',
127             },
128             'q' => { # Quarter number, starting with 1
129             regexp => '([1-4])',
130             type => 'quarter',
131             },
132             's' => { # seconds since the Epoch, UCT
133             regexp => '(\d+)',
134             type => 'unixtime',
135             },
136             'S' => { # seconds, leading 0's
137             regexp => '([0-5][0-9])',
138             type => 'sec',
139             parser => \&_strip_zero,
140             },
141             't' => { # TAB
142             regexp => '\\t',
143             },
144             'U' => { # week number, Sunday as first day of week
145             regexp => '(0[1-9]|[1-4][0-9]|5[0-2])',
146             type => 'week',
147             parser => \&_strip_zero,
148             },
149             'w' => { # day of the week, numerically, Sunday == 0
150             regexp => '([0-7])',
151             type => 'dow',
152             },
153             'W' => { # week number, Monday as first day of week
154             regexp => '(0[1-9]|[1-4][0-9]|5[0-2])',
155             type => 'week',
156             parser => \&_strip_zero,
157             },
158             'y' => { # year (2 digits)
159             regexp => '([0-9][0-9])',
160             type => 'year2',
161             },
162             'Y' => { # year (4 digits)
163             regexp => '([0-9][0-9][0-9][0-9])',
164             type => 'year4',
165             },
166             # TODO
167             'Z' => { # timezone in ascii. eg: PST
168             regexp => '(\S+)', # TODO: localized values?
169             type => 'timezone',
170             },
171             'z' => { # timezone in format -/+0000
172             regexp => '(?:-|+)(0[0-9][0-9][0-9]|1[0-1][0-9][0-9]|1200)',
173             type => 'timezone',
174             },
175             };
176              
177             sub new {
178 1     1 0 772 my ($class, %opts) = @_;
179              
180 1         4 my $self = bless {}, $class;
181              
182 1         3 return $self;
183             }
184              
185             sub _get_regexp {
186 3     3   6 my ($format) = @_;
187              
188 3         3 my @capture_order;
189 3         5 my $flag = 0;
190             CHAR:
191 3         16 for my $c (split(//, $format)) {
192 54 100       81 if ($flag) {
193 19 50       43 unless (defined $format_chars->{$c}) {
194 0         0 warn "no such format character: $c";
195 0         0 next(CHAR);
196             }
197 19         18 my $regexp;
198 19         34 $regexp = $format_chars->{$c}->{regexp};
199 19         187 $format =~ s/%$c/$regexp/;
200 19         31 push(@capture_order, $c);
201 19         25 $flag = 0;
202             }
203 54 100       93 if ($c eq "%") {
204             # next one is format character
205 19         20 $flag = 1;
206             }
207             }
208 3         15 my %reg = (
209             format => $format,
210             capture_order => \@capture_order,
211             );
212              
213 3         8 return \%reg;
214             }
215              
216             sub parse_data {
217 3     3 1 386 my ($self, $format, $data) = @_;
218              
219             # return undef if either is missing
220 3 50 33     18 return unless (defined $data && defined $format);
221              
222 3         8 my $regexp = _get_regexp($format);
223 3         5 $format = $regexp->{format};
224 3         4 my @capture_order = @{$regexp->{capture_order}};
  3         10  
225              
226 3         225 my (@items) = $data =~ /$format/;
227 3         11 my %data;
228 3         18 for my $i (0..$#items) {
229 19         28 my $type = $format_chars->{$capture_order[$i]}->{type};
230 19 100       38 if (defined(my $parser = $format_chars->{$capture_order[$i]}->{parser})) {
231 17         25 $data{$type} = $parser->($items[$i]);
232             } else {
233 2         6 $data{$type} = $items[$i];
234             }
235             }
236              
237 3         5 my %opts;
238 3   66     10 $opts{year} = _parse_year(%data) || time2str("%Y", time);
239 3         96 $opts{hour} = _parse_hour(%data);
240 3         6 foreach my $key (qw/month day min sec unixtime/) {
241 15 100       33 $opts{$key} = $data{$key} if (defined $data{$key});
242             }
243              
244 3         19 my $date = Date::Parser::Date->new(%opts);
245              
246 3         22 return $date;
247             }
248              
249             sub _parse_hour {
250 3     3   16 my (%opts) = @_;
251              
252 3 50       13 return $opts{hour24} if (defined $opts{hour24});
253              
254 0 0 0     0 if (defined $opts{hour12} && defined $opts{hour12_ind}) {
255 0 0       0 if (lc($opts{hour12_ind}) eq "pm") {
256 0 0       0 return 0 if ($opts{hour12} == 12);
257 0         0 return $opts{hour12} + 12;
258             } else {
259 0         0 return $opts{hour12};
260             }
261             }
262              
263 0         0 return;
264             }
265              
266             sub _parse_year {
267 3     3   10 my (%opts) = @_;
268              
269 3 100       14 return $opts{year4} if (defined $opts{year4});
270 1 50       4 return "20.$opts{year2}" if (defined $opts{year2});
271              
272 1         14 return;
273             }
274              
275 9     9   29 sub _strip_zero { $_[0] =~ s/^0//; return $_[0] };
  9         43  
276 0     0   0 sub _strip_zeros { $_[0] =~ s/^0//; return $_[0] };
  0         0  
277 3     3   5 sub _strip_leading_space { $_[0] =~ s/^ //; return $_[0] };
  3         7  
278              
279             1;
280              
281             __END__