File Coverage

blib/lib/Regexp/Common/time.pm
Criterion Covered Total %
statement 153 167 91.6
branch 51 68 75.0
condition 20 39 51.2
subroutine 18 19 94.7
pod 0 7 0.0
total 242 300 80.6


line stmt bran cond sub pod time code
1             =head1 NAME
2              
3             Regexp::Common::time - Date and time regexps.
4              
5             =cut
6              
7 14     14   730919 use strict;
  14         48  
  14         480  
8 14     14   92 use warnings;
  14         37  
  14         780  
9              
10             package Regexp::Common::time;
11             $Regexp::Common::time::VERSION = '0.14';
12 14     14   1334 use Regexp::Common qw(pattern);
  14         17294  
  14         147  
13              
14 0     0   0 sub _croak { require Carp; goto &Carp::croak}
  0         0  
15              
16             my $can_locale;
17             my $can_posix;
18             BEGIN
19             {
20             eval
21 14     14   2333251 {
22 14         49 $can_posix = 0;
23 14         4440 require POSIX;
24 14         72841 $can_posix = 1;
25             };
26             eval
27 14         49 {
28 14         43 $can_locale = 0;
29 14         2283 require I18N::Langinfo;
30 14         5479 I18N::Langinfo->import(qw(langinfo));
31 14         70982 $can_locale = 1;
32             };
33             }
34              
35             # Master list of patterns
36             our %master
37             = (
38             c2 => q/\d{2}/, # Century, 2 digits
39             yr2 => q/\d{2}/, # Year, 2 digits
40             yr4 => q/\d{4}/, # Year, 4 digits
41             yr24 => q/(?:\d{2}(?:\d{2})?)/, # Year, 2 or 4 digits
42             mo2 => q/(?:(?=[01])(?:0[1-9]|1[012]))/, # Month, 2 digits
43             mo12 => q/(?:0[1-9]|1[012]|(?
44             mo_2 => q/(?:(?=[ 1])(?: [1-9]|1[012]))/, # Month, 2 places, leading space
45             dy2 => q/(?:(?=[0123])(?:0[1-9]|[12]\d|3[01]))/, # Day, 2 digits
46             dy12 => q/(?:0[1-9]|[12]\d|3[01]|(?
47             dy_2 => q/(?:(?=[ 123])(?: [1-9]|[12]\d|3[01]))/, # Day, 2 places, leading space
48             doy3 => q/(?:(?=[0-3])(?:00[1-9]|0[1-9]\d|[12]\d\d|3(?:[0-5]\d|6[0-6])))/, # Day of year, 3 digits
49             hr2 => q/(?:(?=[012])(?:[01]\d|2[0123]))/, # Hour, 00-23, 2 digits
50             hr12 => q/(?:(?=\d)(?:[01]\d|2[0123]|(?
51             hr_2 => q/(?:(?=[ 12])(?:[ 1]\d|2[0123]))/, # Hour, 0-23, 2 places, ld sp
52             hx2 => q/(?:(?=[01])(?:0[1-9]|1[012]))/, # Hour, 01-12, 2 digits
53             hx12 => q/(?:(?=\d)(?:0[1-9]|1[012]|(?
54             hx_2 => q/(?:(?=[ 1])(?: [1-9]|1[012]))/, # Hour, 1-12, 2 places, ld sp
55             mi2 => q/(?:[0-5]\d)/, # Minute, 2 digits
56             mi12 => q/(?:[0-5]\d|(?
57             mi_2 => q/(?:[ 1-5]\d)/, # Minute, 2 places, leading sp
58             sc2 => q/(?:(?=[0-6])(?:[0-5]\d|6[01]))/, # Second, 2 digits, 00-61
59             sc12 => q/(?:(?=[0-6])(?:[0-5]\d|6[01]|(?
60             sc_2 => q/(?:(?=[ 1-6])(?:[ 1-5]\d|6[01]))/, # Second, 2 places, 0-61, ld sp
61             wn2 => q/(?:(?=[0-5])(?:0[1-9]|[1-4]\d|5[0-3]))/, # Week number, 2 digits, 01-53
62             wnx2 => q/(?:(?=[0-5])(?:[0-4]\d|5[0-3]))/, # Week number, 2 digits, 00-53
63             wd1 => q/[0-6]/, # Weekday number, 1 digit, 0-6
64             wdx1 => q/[1-7]/, # Weekday number, 1 digit, 1-7
65             msec => q/\d{3}/, # millisecond
66             usec => q/\d{6}/, # microsecond
67             ampm => q/(?:(?=[AaPp])(?:[ap](?:m|\.m\.)?|[AP](?:M|\.M\.)?))/, # am/pm indicator
68             th => q/(?:(?=[SNRTsnrt])(?:st|ST|nd|ND|rd|RD|th|TH))/, # ordinal suffix
69             tz => q/(?:[-+](?:[01]\d|2[0-4])(?::?[0-5]\d)?|Z|GMT|UTC?|[ECMP][DS]T)/, # Time zone
70             ema => _get_abbr_month_pattern(1), # English month abbreviation
71              
72             # The following are locale-specific, and will be populated later
73             mname => q/TBD/, # Full month name
74             mabbr => q/TBD/, # Month abbreviation
75             dname => q/TBD/, # Full weekday name
76             dabbr => q/TBD/, # Weekday abbreviation
77             axpx => q/TBD/, # locale-specific AM/PM indicator
78             );
79              
80             my $npd = q/(?
81             my $nfd = q/(?!\d)/; # "No following digit"
82             my $sdig = $npd . q/[1-9]/ . $nfd; # One single digit (used for months and days)
83              
84             sub _nospace
85             {
86 132     132   274 my $s = shift;
87 132         608 $s =~ s/([\x20\x09])/sprintf '\\x%02x', ord $1/eg;
  212         1161  
88 132         476 return $s;
89             }
90              
91             my $anymon; # general-purpose month capture. Set in _setup_locale().
92              
93             my $d = qq/$sdig|$master{dy2}/;
94             my $dcap = qq/(?k:$d)/;
95              
96             # Separator pattern: allows for certain punctuation, or none, plus optional space.
97             my $dsep = _nospace q{[-/. ]};
98              
99             # "Middle" day. Must be surrounded by matching separators
100             my $dmiddle = _nospace qq{(?=(?>/$master{dy12}/|-$master{dy12}-| $master{dy12},? |\\.$master{dy12}\\.|(?!$dsep)$master{dy12}(?!$dsep)))$dsep?(?k:$master{dy12}),?$dsep?};
101             my $d2middle = _nospace qq{(?=(?>/$master{dy2}/|-$master{dy2}-| $master{dy2},? |\\.$master{dy2}\\.|(?!$dsep)$master{dy2}(?!$dsep)))$dsep?(?k:$master{dy2}),?$dsep?};
102              
103             # "Middle" month. Must be surrounded by matching separators
104             my $mFULLmiddle; # Full month pattern, in middle (ymd and dmy). Set in _setup_locale().
105             my $m2middle = _nospace qq{(?=(?>/$master{mo2}/|-$master{mo2}-| $master{mo2} |\\.$master{mo2}\\.|$master{mo2}(?!$dsep)))$dsep?(?k:$master{mo2})$dsep?};
106              
107             # "Middle" minute. Must be surrounded by matching separators
108             my $tsep = _nospace q/[:. ]/;
109             my $min2middle = _nospace qq{(?=(?>:$master{mi2}:|\\.$master{mi2}\\.| $master{mi2} |$master{mi2}(?!$tsep)))$tsep?(?k:$master{mi2})$tsep?};
110              
111              
112             # YMD builder
113             sub ymd
114             {
115 1017     1017 0 777743 my ($self, $flags_hr, $keys_ar) = @_;
116 1017         2323 my $pattern = $keys_ar->[1];
117 1017         3570 _setup_locale();
118              
119             # The second separator character is REQUIRED to be the same as the
120             # first for YMD patterns. Otherwise, "2005/10/21" is ambiguous:
121             # it matches "(20)(05)/(10)" and "(2005)/(10)/(21)".
122              
123             # 'ymd' is the most flexible: year: 2/4 digits; month 1/2 digits or name; day 1/2 digits.
124 1017 100 66     3973 if ($pattern eq 'ymd')
    100          
    100          
    100          
    50          
125             {
126 226         1964 return qq/(?k:$npd(?k:$master{yr24})$mFULLmiddle$dcap$nfd)/;
127             }
128             # 'y4md': 4-digit year; 1 or 2 digit month and day. Or named month.
129             elsif ($pattern eq 'y4md')
130             {
131 214         1287 return qq/(?k:(?k:$master{yr4})$mFULLmiddle$dcap$nfd)/;
132             }
133             # 'y2md': 2-digit year; 1 or 2 digit month and day.
134             elsif ($pattern eq 'y2md')
135             {
136 228         1868 return qq/(?k:(?k:$master{yr2})$mFULLmiddle$dcap$nfd)/;
137             }
138             elsif ($pattern eq 'y4m2d2' || $pattern eq 'YMD')
139             {
140 233         1406 return qq/(?k:(?k:$master{yr4})$m2middle(?k:$master{dy2}))/;
141             }
142             elsif ($pattern eq 'y2m2d2')
143             {
144 116         763 return qq/(?k:(?k:$master{yr2})$m2middle(?k:$master{dy2}))/;
145             }
146              
147             # Probably the only way to get here is if I goof up and specify this subroutine
148             # for a YMD pattern that is not handled above.
149 0         0 die "Programming error: Unknown y-m-d pattern '$pattern'. Contact Regexp::Common::time author.";
150             }
151              
152             # MDY builder
153             sub mdy
154             {
155 1240     1240 0 764635 my ($self, $flags_hr, $keys_ar) = @_;
156 1240         2397 my $pattern = $keys_ar->[1];
157 1240         3094 _setup_locale();
158              
159             # The second separator character is REQUIRED to be the same as the
160             # first for YMD patterns, for the STRICT versions of these patterns
161             # (the ones containing "m2d2").
162              
163             # 'mdy' is the most flexible: year: 2/4 digits; month 1/2 digits or named; day 1/2 digits.
164 1240 100 66     4386 if ($pattern eq 'mdy')
    100          
    100          
    100          
    50          
165             {
166 234         1389 return qq/(?k:$npd(?k:$anymon)$dmiddle(?k:$master{yr24})$nfd)/;
167             }
168             # 'mdy4': 4-digit year; 1 or 2 digit month and day.
169             elsif ($pattern eq 'mdy4')
170             {
171 212         1087 return qq/(?k:$npd(?k:$anymon)$dmiddle(?k:$master{yr4}))/;
172             }
173             # 'mdy2': 2-digit year; 1 or 2 digit month and day.
174             elsif ($pattern eq 'mdy2')
175             {
176 208         1097 return qq/(?k:$npd(?k:$anymon)$dmiddle(?k:$master{yr2}))/;
177             }
178             elsif ($pattern eq 'm2d2y4' || $pattern eq 'MDY')
179             {
180 392         1814 return qq/(?k:(?k:$master{mo2})$d2middle(?k:$master{yr4}))/;
181             }
182             elsif ($pattern eq 'm2d2y2')
183             {
184 194         948 return qq/(?k:(?k:$master{mo2})$d2middle(?k:$master{yr2}))/;
185             }
186              
187             # Probably the only way to get here is if I goof up and specify this subroutine
188             # for a YMD pattern that is not handled above.
189 0         0 die "Programming error: Unknown m-d-y pattern '$pattern'. Contact Regexp::Common::time author.";
190             }
191              
192             # DMY builder
193             sub dmy
194             {
195 986     986 0 895594 my ($self, $flags_hr, $keys_ar) = @_;
196 986         2541 my $pattern = $keys_ar->[1];
197 986         3693 _setup_locale();
198              
199             # The second separator character is REQUIRED to be the same as the
200             # first for YMD patterns, for the STRICT versions of these patterns
201             # (the ones containing "d2m2").
202              
203             # 'dmy' is the most flexible: year: 2/4 digits; month 1/2 digits; day 1/2 digits.
204 986 100 66     4383 if ($pattern eq 'dmy')
    100          
    100          
    100          
    50          
205             {
206 218         2480 return qq/(?k:$npd$dcap$mFULLmiddle(?k:$master{yr24})$nfd)/;
207             }
208             # 'mdy4': 4-digit year; 1 or 2 digit month and day.
209             elsif ($pattern eq 'dmy4')
210             {
211 208         2080 return qq/(?k:$npd$dcap$mFULLmiddle(?k:$master{yr4}))/;
212             }
213             # 'y2md': 2-digit year; 1 or 2 digit month and day.
214             elsif ($pattern eq 'dmy2')
215             {
216 208         2012 return qq/(?k:$npd$dcap$mFULLmiddle(?k:$master{yr2}))/;
217             }
218             elsif ($pattern eq 'd2m2y4' || $pattern eq 'DMY')
219             {
220 228         1612 return qq/(?k:(?k:$master{dy2})$m2middle(?k:$master{yr4}))/;
221             }
222             elsif ($pattern eq 'd2m2y2')
223             {
224 124         1265 return qq/(?k:(?k:$master{dy2})$m2middle(?k:$master{yr2}))/;
225             }
226              
227             # Probably the only way to get here is if I goof up and specify this subroutine
228             # for a YMD pattern that is not handled above.
229 0         0 die "Programming error: Unknown d-m-y pattern '$pattern'. Contact Regexp::Common::time author.";
230             }
231              
232             # HMS builder
233             sub hms
234             {
235 85     85 0 96650 my $hr = $npd . q/[01]\d|2[0-4]|\d/;
236 85         222 my $sec = q/\d\d/; # Can't limit it to 00-59! Because it's optional, and out-of-range = no match.
237              
238             # my ($self, $flags_hr, $keys_ar) = @_;
239 85         666 return qq/(?k:$npd(?k:$master{hr12})$tsep/ # hour
240             . qq/(?k:$master{mi2})/ # minute
241             . qq/(?:$tsep(?k:$sec))?/ # second
242             . qq/(?:\\s?(?k:$master{ampm}))?)/; # am/pm
243             }
244              
245             # Time::Format-like builder
246              
247             my %tf =
248             (
249             yyyy => $master{yr4},
250             yy => $master{yr2},
251             'm{on}' => $master{mo12},
252             'mm{on}'=> $master{mo2},
253             '?m{on}'=> $master{mo_2},
254             d => $master{dy12},
255             dd => $master{dy2},
256             '?d' => $master{dy_2},
257             h => $master{hr12},
258             hh => $master{hr2},
259             '?h' => $master{hr_2},
260             H => $master{hx12},
261             HH => $master{hx2},
262             '?H' => $master{hx_2},
263             'm{in}' => $master{mi12},
264             'mm{in}'=> $master{mi2},
265             '?m{in}'=> $master{mi_2},
266             s => $master{sc12},
267             ss => $master{sc2},
268             '?s' => $master{sc_2},
269             mmm => $master{msec},
270             uuuuuu => $master{usec},
271             am => $master{ampm},
272             AM => $master{ampm},
273             'a.m.' => $master{ampm},
274             'A.M.' => $master{ampm},
275             pm => $master{ampm},
276             PM => $master{ampm},
277             'p.m.' => $master{ampm},
278             'P.M.' => $master{ampm},
279             th => $master{th},
280             TH => $master{th},
281             tz => $master{tz},
282             );
283              
284             my %disam; # Disambiguator for 'm' format.
285             $disam{$_} = "{on}" foreach qw/yy d dd ?d/; # If year or day is nearby, it's 'month'
286             $disam{$_} = "{in}" foreach qw/h hh ?h H HH ?H s ss ?s/; # If hour or second is nearby, it's 'minute'
287             my $disambiguate_pat_1 = qr/
288             (?
289             (?=[ydhH]) # Must start with one of these
290             ( # $1 begins
291             ( # $2 begins. Capture:
292             yy # a year
293             | [dhH] # a day or hour
294             )
295             [^?m\\]* # Followed by something that's not part of a month
296             )
297             (?![?m]?m\{[io]n\}) # make sure it's not already unambiguous
298             (?!mon) # don't confuse "mon" with "m" "on"
299             ([?m]?m) # $3 is a month code
300             /x;
301              
302             my $disambiguate_pat_2 = qr/
303             (?
304             ([?m]?m) # $1 is a month code
305             ( # $2 begins.
306             [^a-zA-Z]* # any number of non-alphas
307             (?
308             (?=[?dsy]) # Next char must be one of these
309             ( # $3 begins. Capture:
310             \??[ds] # a day or a second
311             | yy # or a year
312             )
313             )/x;
314              
315             # The Big Date/Time Pattern
316             my $bigpat = qr/
317             (?
318             (?=[dDy?hHsaApPMmWwutT]) # Jump to one of these characters
319             (
320             [Dd]ay|DAY # Weekday abbreviation
321             | yy(?:yy)? # Year
322             | [?m]?m\{[oi]n\} # Unambiguous month-minute codes
323             | th | TH # day suffix
324             | [?d]?d # Day
325             | [?h]?h # Hour (24)
326             | [?H]?H # Hour (12)
327             | [?s]?s # Second
328             | [apAP]\.?[mM]\.? # am and pm strings
329             | [Mm]on(?:th)?|MON(?:TH)? # Month names and abbrev
330             | [Ww]eekday|WEEKDAY # Weekday names
331             | mmm|uuuuuu # millisecond and microsecond
332             | tz # time zone
333             )/x;
334              
335             sub tf_builder
336             {
337 636     636 0 581265 my ($self, $flags_hr, $keys_ar) = @_;
338              
339             # User must specify *something* as the pattern
340             _croak q{Mandatory "-pat" flag missing in tf pattern}
341 636 50       2035 if !exists $flags_hr->{-pat};
342              
343 636         1396 my $pattern = $flags_hr->{-pat};
344              
345             # Localize
346 636         1801 _setup_locale();
347              
348             # Copying from Time::Format...
349             # "Guess" how to interpret ambiguous 'm'
350 636         4099 $pattern =~ s/$disambiguate_pat_1/$1$3$disam{$2}/gx;
351 636         2983 $pattern =~ s/$disambiguate_pat_2/$1$disam{$3}$2/gx;
352              
353             # If the pattern contains any parentheses, then the caller is
354             # responsible for doing all the captures.
355 636 100       1839 if ($pattern =~ /(?
356             {
357 2         68 $pattern =~ s/$bigpat/$tf{$1}/gx;
358             }
359             else # we'll handle the capturing
360             {
361 634         7730 $pattern =~ s/$bigpat/(?k:$tf{$1})/gx;
362 634         2091 $pattern = "(?k:$pattern)";
363             }
364              
365 636         2607 return $pattern;
366             }
367              
368             # strftime builder
369             my %strftime =
370             (
371             C => $master{c2}, # two-digit century
372             D =>"$master{mo2}/$master{dy2}/$master{yr2}",
373             d => $master{dy2}, # two-digit day
374             e => $master{dy_2}, # 1 or 2-digit day, leading space
375             H => $master{hr2}, # hour, 00-23
376             I => $master{hx2}, # hour, 01-12
377             j => $master{doy3}, # day-of-year, 001-366
378             m => $master{mo2}, # month, 01-12
379             M => $master{mi2}, # minute, 00-59
380             n => "\n",
381             R =>"$master{hr2}:$master{mi2}",
382             S => $master{sc2}, # Second, 00-61
383             T =>"$master{hr2}:$master{mi2}:$master{sc2}",
384             t => "\t",
385             u => $master{wdx1}, # Weekday number, 1-7
386             U => $master{wnx2}, # Week number, 00-53
387             V => $master{wn2}, # Week number, 01-53
388             w => $master{wd1}, # Weekday number, 0-6
389             W => $master{wnx2}, # Week number, 00-53
390             y => $master{yr2}, # two-digit year
391             Y => $master{yr4}, # four-digit year
392             Z => $master{tz}, # time zone
393             '%' => '%',
394              
395             # additional useful patterns not specified by strftime
396             _d => $master{dy12}, # 1- or 2-digit day number
397             _H => $master{hr12}, # 1- or 2-digit 24-hour hour
398             _I => $master{hx12}, # 1- or 2-digit 12-hour hour
399             _m => $master{mo12}, # 1- or 2-digit month number
400             _M => $master{mi12}, # 1- or 2-digit minute
401             );
402              
403             sub strftime_builder
404             {
405 689     689 0 612891 my ($self, $flags_hr, $keys_ar) = @_;
406              
407             # User must specify *something* as the pattern
408             _croak q{Mandatory "-pat" flag missing in strftime pattern}
409 689 50       2007 if !exists $flags_hr->{-pat};
410              
411 689         1416 my $pattern = $flags_hr->{-pat};
412              
413             # Localize
414 689         1907 _setup_locale();
415              
416             # If the pattern contains any parentheses, then the caller is
417             # responsible for doing all the captures.
418 689 100       1927 if ($pattern =~ /(?
419             {
420 3         26 $pattern =~ s/(?
421             }
422             else # we'll handle the capturing
423             {
424             # If the pattern consists of a single pattern, then
425             # the enclosing (?k:) is redundant and annoying.
426 686         3516 my $solo = $pattern =~ /\A # Start of user's pattern
427             (?:
428             \\b # a word break
429             |
430             \\A # Start of string
431             |
432             \^ # Start of string
433             |
434             \(\?[^\)]*\) # Some other zero-width assertion
435             )* # (any number of such assertions)
436             %_?. # The meat of the user's actual pattern
437             (?:
438             \\b # word break
439             |
440             \\z # REAL end of string
441             |
442             \\Z # end of string
443             |
444             \$ # end of line or string
445             |
446             \(\?[^\)]*\) # some other assertion
447             )*
448             \z # Actual end of user's pattern
449             /x;
450 686         5733 $pattern =~ s/(?
451 686 100       2116 $pattern = "(?k:$pattern)" unless $solo;
452             }
453              
454 689         2386 return $pattern;
455             }
456              
457             sub american
458             {
459 92     92 0 89579 my ($self, $flags_hr, $keys_ar) = @_;
460 92         278 _setup_locale();
461              
462 92         759 return join '',
463             qq/(?k:\\b/, # must start on word boundary
464             qq/(?k:$master{mname}|$master{mabbr})/, # Month name or abbr
465             qq/ {1,2}/, # one or two spaces
466             qq/(?k:$master{dy12})/, # one- or two-digit day
467             qq/(?:,| |, )/, # Comma or space or both
468             qq/(?k:'$master{yr2}|$master{yr24})/, # Year: 'yy or yyyy or yy
469             qq/$nfd)/; # No following digits
470             }
471              
472              
473              
474             # Localization.
475             # Bug: This is bulky and inefficient, and sets up many patterns that may never be used.
476             # On the other hand, it's generally only ever called once.
477             my $latest_setup_locale;
478             sub _setup_locale
479             {
480             # Do nothing if locale has not changed since we set it up
481 4660     4660   7706 my $current_locale;
482 4660 50       23044 $current_locale = $can_posix? POSIX::setlocale(POSIX::LC_TIME()) : q{};
483 4660 50       13034 $current_locale = q{} if !defined $current_locale;
484              
485             # No changes needed
486 4660 100 66     24806 return if defined $latest_setup_locale
487             && $latest_setup_locale eq $current_locale;
488              
489 8         22 $latest_setup_locale = $current_locale;
490              
491 8         84 my $dt_fmt; # locale-specific date/time format
492             my $d_fmt; # locale-specific date format
493 8         0 my $t_fmt; # locale-specific time format
494 8         0 my $t_ap_fmt; # locale-specific time with am/pm format
495 8         0 my $am_str; # locale-specific ante-meridian string
496 8         0 my $pm_str; # locale-specific post-meridian string
497              
498 8 50       34 if ($can_locale)
499             {
500             eval
501 8         25 {
502 8         118 ($dt_fmt, $d_fmt, $t_fmt, $t_ap_fmt) = map langinfo($_),
503             (
504             I18N::Langinfo::D_T_FMT(),
505             I18N::Langinfo::D_FMT(),
506             I18N::Langinfo::T_FMT(),
507             I18N::Langinfo::T_FMT_AMPM(),
508             );
509 8         56 ($am_str, $pm_str) = map langinfo($_),
510             (
511             I18N::Langinfo::AM_STR(),
512             I18N::Langinfo::PM_STR(),
513             );
514              
515             };
516             }
517 8 50 33     81 if (!$can_locale || $@) # Internationalization didn't work for some reason
518             {
519 0         0 $dt_fmt = q{%a %b %e %H:%M:%S %Y};
520 0         0 $d_fmt = q{%m/%d/%y};
521 0         0 $t_fmt = q{%H:%M:%S};
522 0         0 $t_ap_fmt = q{%I:%M:%S %p};
523 0         0 $am_str = q{AM};
524 0         0 $pm_str = q{PM};
525             }
526              
527             # Update master patterns
528 8         39 $master{dname} = _get_full_weekday_pattern();
529 8         38 $master{dabbr} = _get_abbr_weekday_pattern();
530 8         40 $master{mname} = _get_full_month_pattern();
531 8         36 $master{mabbr} = _get_abbr_month_pattern();
532 8         51 $master{axpx} = qq/(?:\Q$am_str\E|\Q$pm_str\E)/;
533              
534             # Pattern variables for dmy-mdy-ymd patterns
535 8         86 $anymon = _nospace qq/(?>(?i)$master{mo2}|$sdig|$master{mname}|$master{mabbr})/;
536 8         93 $mFULLmiddle = _nospace qq{(?=(?>/$anymon/|-$anymon-| $anymon |\\.$anymon\\.|(?!$dsep)$anymon(?!$dsep)))$dsep?(?k:$anymon)$dsep?};
537              
538             # Pattern variables for Time::Format
539 8         48 $tf{Weekday} = $tf{WEEKDAY} = $tf{weekday} = $master{dname};
540 8         43 $tf{Day} = $tf{DAY} = $tf{day} = $master{dabbr};
541 8         43 $tf{Month} = $tf{MONTH} = $tf{month} = $master{mname};
542 8         37 $tf{Mon} = $tf{MON} = $tf{mon} = $master{mabbr};
543              
544             # Pattern variables for strftime
545 8         35 $strftime{A} = $master{dname};
546 8         30 $strftime{a} = $master{dabbr};
547 8         28 $strftime{B} = $master{mname};
548 8         128 $strftime{b} = $master{mabbr};
549 8         26 $strftime{h} = $strftime{b}; # defined synonym
550             $strftime{r} ="$master{hx2}:$master{mi2}:$master{sc2} (?:$am_str|$pm_str)",
551              
552             # Set up locale-dependent strftime patterns
553 8         78 $strftime{p} = $master{axpx};
554 8         33 foreach ($dt_fmt, $d_fmt, $t_fmt, $t_ap_fmt)
555             {
556             # the "|| q{}" below is to avoid "uninitialized" warnings.
557 32 50       147 s/%(.)/$strftime{$1} || q{}/eg;
  136         657  
558             }
559 8         38 $strftime{c} = _nospace $dt_fmt;
560 8         31 $strftime{r} = _nospace $t_ap_fmt;
561 8         35 $strftime{x} = _nospace $d_fmt;
562 8         26 $strftime{X} = _nospace $t_fmt;
563             }
564              
565             sub _first_chars
566             {
567 46     46   150 my %uniq = map {substr ($_,0,1) => 1} @_;
  472         1587  
568 46         540 return join q{}, map quotemeta, keys %uniq;
569             }
570              
571             sub _get_full_month_pattern
572             {
573 8     8   30 my @Mon_Name;
574 8 50       31 if ($can_locale)
575             {
576             eval
577 8         22 {
578 8         113 @Mon_Name = map langinfo($_),
579             (
580             I18N::Langinfo::MON_1(),
581             I18N::Langinfo::MON_2(),
582             I18N::Langinfo::MON_3(),
583             I18N::Langinfo::MON_4(),
584             I18N::Langinfo::MON_5(),
585             I18N::Langinfo::MON_6(),
586             I18N::Langinfo::MON_7(),
587             I18N::Langinfo::MON_8(),
588             I18N::Langinfo::MON_9(),
589             I18N::Langinfo::MON_10(),
590             I18N::Langinfo::MON_11(),
591             I18N::Langinfo::MON_12(),
592             );
593             };
594             }
595 8 50 33     91 if (!$can_locale || $@)
596             {
597 0         0 @Mon_Name = qw(January February March April May June July August September October November December);
598             }
599              
600 8         31 my $prematch = _first_chars(@Mon_Name);
601 8         115 my $alternat = join '|', map quotemeta, @Mon_Name;
602 8         72 return qq/(?=[$prematch])(?>$alternat)/;
603             }
604              
605             sub _get_abbr_month_pattern
606             {
607 22     22   64 my $english_only = shift;
608 22         56 my @Mon_Abbr;
609 22 100 66     158 if (!$english_only && $can_locale)
610             {
611             eval
612 8         22 {
613 8         101 @Mon_Abbr = map langinfo($_),
614             (
615             I18N::Langinfo::ABMON_1(),
616             I18N::Langinfo::ABMON_2(),
617             I18N::Langinfo::ABMON_3(),
618             I18N::Langinfo::ABMON_4(),
619             I18N::Langinfo::ABMON_5(),
620             I18N::Langinfo::ABMON_6(),
621             I18N::Langinfo::ABMON_7(),
622             I18N::Langinfo::ABMON_8(),
623             I18N::Langinfo::ABMON_9(),
624             I18N::Langinfo::ABMON_10(),
625             I18N::Langinfo::ABMON_11(),
626             I18N::Langinfo::ABMON_12(),
627             );
628             };
629             }
630 22 50 66     164 if ($english_only || !$can_locale || $@)
      66        
631             {
632 14         140 @Mon_Abbr = qw(Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec);
633             }
634              
635 22         76 my $prematch = _first_chars(@Mon_Abbr);
636 22         186 my $alternat = join '|', map quotemeta, @Mon_Abbr;
637 22         377 return qq/(?=[$prematch])(?>$alternat)/;
638             }
639              
640             sub _get_full_weekday_pattern
641             {
642 8     8   26 my @Day_Name;
643 8 50       32 if ($can_locale)
644             {
645             eval
646 8         21 {
647 8         84 @Day_Name = map langinfo($_),
648             (
649             I18N::Langinfo::DAY_1(),
650             I18N::Langinfo::DAY_2(),
651             I18N::Langinfo::DAY_3(),
652             I18N::Langinfo::DAY_4(),
653             I18N::Langinfo::DAY_5(),
654             I18N::Langinfo::DAY_6(),
655             I18N::Langinfo::DAY_7(),
656             );
657             };
658             }
659 8 50 33     71 if (!$can_locale || $@)
660             {
661 0         0 @Day_Name = qw(Sunday Monday Tuesday Wednesday Thursday Friday Saturday);
662             }
663              
664 8         58 my $prematch = _first_chars(@Day_Name);
665 8         78 my $alternat = join '|', map quotemeta, @Day_Name;
666 8         69 return qq/(?=[$prematch])(?>$alternat)/;
667             }
668              
669             sub _get_abbr_weekday_pattern
670             {
671 8     8   26 my $english_only = shift;
672 8         18 my @Day_Abbr;
673 8 50 33     62 if (!$english_only && $can_locale)
674             {
675             eval
676 8         23 {
677 8         78 @Day_Abbr = map langinfo($_),
678             (
679             I18N::Langinfo::ABDAY_1(),
680             I18N::Langinfo::ABDAY_2(),
681             I18N::Langinfo::ABDAY_3(),
682             I18N::Langinfo::ABDAY_4(),
683             I18N::Langinfo::ABDAY_5(),
684             I18N::Langinfo::ABDAY_6(),
685             I18N::Langinfo::ABDAY_7(),
686             );
687             };
688             }
689 8 50 33     95 if ($english_only || !$can_locale || $@)
      33        
690             {
691 0         0 @Day_Abbr = qw(Sun Mon Tue Wed Thu Fri Sat);
692             }
693              
694 8         35 my $prematch = _first_chars(@Day_Abbr);
695 8         74 my $alternat = join '|', map quotemeta, @Day_Abbr;
696 8         59 return qq/(?=[$prematch])(?>$alternat)/;
697             }
698              
699             # Set up all the patterns
700              
701             for (qw(ymd y4md y2md y4m2d2 y2m2d2 YMD))
702             {
703             pattern name => ['time', $_],
704             create => \&ymd,
705             }
706              
707             for (qw(mdy mdy4 mdy2 m2d2y4 m2d2y2 MDY))
708             {
709             pattern name => ['time', $_],
710             create => \&mdy,
711             }
712              
713             for (qw(dmy dmy4 dmy2 d2m2y4 d2m2y2 DMY))
714             {
715             pattern name => ['time', $_],
716             create => \&dmy,
717             }
718              
719             for (qw(hms))
720             {
721             pattern name => ['time', $_],
722             create => \&hms,
723             }
724              
725             for (qw(strftime))
726             {
727             pattern name => ['time', $_],
728             create => \&strftime_builder,
729             }
730              
731             for (qw(tf))
732             {
733             pattern name => ['time', $_],
734             create => \&tf_builder,
735             }
736             for (qw(american))
737             {
738             pattern name => ['time', $_],
739             create => \&american,
740             }
741              
742              
743             my $dt_sep = q/(?:(?<=\\d)[T_ ](?=\\d))?/;
744             pattern name => ['time', 'iso'],
745             create => join '',
746             qq/(?k:/,
747             qq/(?=\\d)/, # Expect a digit
748             qq/(?:/, # Begin optional date portion
749             qq/(?k:$master{yr4})/, $m2middle, qq/(?k:$master{dy2})/,
750             qq/)?/, # End optional date portion
751             $dt_sep,
752             qq/(?:/, # Begin optional time portion
753             qq/(?k:$master{hr2})/, $min2middle, qq/(?k:$master{sc2})/,
754             qq/)?)/; # End optional time portion
755              
756             pattern name => ['time', 'mail'],
757             create => join '',
758             qq/(?k:$npd/, # No preceeding digit
759             qq/(?=\\d)/, # Expect a digit
760             qq/(?k:$master{dy12})\\s*/, # Day
761             qq/(?k:$master{ema})\\s*/, # Month (english name abbreviation)
762             qq/(?k:$master{yr24})\\s+/, # Year
763             qq/(?k:$master{hr2}):/, # Hour
764             qq/(?k:$master{mi2}):/, # Minute
765             qq/(?k:$master{sc2})\\s*/, # Second
766             qq/(?k:$master{tz})/, # Time zone
767             qq/$nfd)/; # No trailing digit
768              
769             pattern name => ['time', 'MAIL'],
770             create => join '',
771             qq/(?k:$npd/, # No preceeding digit
772             qq/(?=\\d)/, # Expect a digit
773             qq/(?k:$master{dy12})\\s*/, # Day
774             qq/(?k:$master{ema})\\s*/, # Month (english name abbreviation)
775             qq/(?k:$master{yr4})\\s+/, # Year
776             qq/(?k:$master{hr2}):/, # Hour
777             qq/(?k:$master{mi2}):/, # Minute
778             qq/(?k:$master{sc2})\\s*/, # Second
779             qq/(?k:[-+]\\d{4})/, # Time zone
780             qq/$nfd)/; # No trailing digit
781              
782              
783             1;
784             __END__