File Coverage

blib/lib/DateTimeX/Lite/Strftime.pm
Criterion Covered Total %
statement 34 41 82.9
branch 22 26 84.6
condition n/a
subroutine 5 6 83.3
pod 0 2 0.0
total 61 75 81.3


line stmt bran cond sub pod time code
1             package DateTimeX::Lite;
2              
3             {
4             my %strftime_patterns =
5             ( 'a' => sub { $_[0]->day_abbr },
6             'A' => sub { $_[0]->day_name },
7             'b' => sub { $_[0]->month_abbr },
8             'B' => sub { $_[0]->month_name },
9             'c' => sub { $_[0]->format_cldr( $_[0]->{locale}->datetime_format_default() ) },
10             'C' => sub { int( $_[0]->year / 100 ) },
11             'd' => sub { sprintf( '%02d', $_[0]->day ) },
12             'D' => sub { $_[0]->strftime( '%m/%d/%y' ) },
13             'e' => sub { sprintf( '%2d', $_[0]->day ) },
14             'F' => sub { $_[0]->ymd('-') },
15             'g' => sub { substr( $_[0]->week_year, -2 ) },
16             'G' => sub { $_[0]->week_year },
17             'H' => sub { sprintf( '%02d', $_[0]->hour ) },
18             'I' => sub { sprintf( '%02d', $_[0]->hour_12 ) },
19             'j' => sub { $_[0]->day_of_year },
20             'k' => sub { sprintf( '%2d', $_[0]->hour ) },
21             'l' => sub { sprintf( '%2d', $_[0]->hour_12 ) },
22             'm' => sub { sprintf( '%02d', $_[0]->month ) },
23             'M' => sub { sprintf( '%02d', $_[0]->minute ) },
24             'n' => sub { "\n" }, # should this be OS-sensitive?
25             'N' => \&_format_nanosecs,
26             'p' => sub { $_[0]->am_or_pm() },
27             'P' => sub { lc $_[0]->am_or_pm() },
28             'r' => sub { $_[0]->strftime( '%I:%M:%S %p' ) },
29             'R' => sub { $_[0]->strftime( '%H:%M' ) },
30             's' => sub { $_[0]->epoch },
31             'S' => sub { sprintf( '%02d', $_[0]->second ) },
32             't' => sub { "\t" },
33             'T' => sub { $_[0]->strftime( '%H:%M:%S' ) },
34             'u' => sub { $_[0]->day_of_week },
35             # algorithm from Date::Format::wkyr
36             'U' => sub { my $dow = $_[0]->day_of_week;
37             $dow = 0 if $dow == 7; # convert to 0-6, Sun-Sat
38             my $doy = $_[0]->day_of_year - 1;
39             return sprintf( '%02d', int( ( $doy - $dow + 13 ) / 7 - 1 ) )
40             },
41             'V' => sub { sprintf( '%02d', $_[0]->week_number ) },
42             'w' => sub { my $dow = $_[0]->day_of_week;
43             return $dow % 7;
44             },
45             'W' => sub { my $dow = $_[0]->day_of_week;
46             my $doy = $_[0]->day_of_year - 1;
47             return sprintf( '%02d', int( ( $doy - $dow + 13 ) / 7 - 1 ) )
48             },
49             'x' => sub { $_[0]->format_cldr( $_[0]->{locale}->date_format_default() ) },
50             'X' => sub { $_[0]->format_cldr( $_[0]->{locale}->time_format_default() ) },
51             'y' => sub { sprintf( '%02d', substr( $_[0]->year, -2 ) ) },
52             'Y' => sub { return $_[0]->year },
53             'z' => sub { DateTimeX::Lite::TimeZone->offset_as_string( $_[0]->offset ) },
54             'Z' => sub { $_[0]->{tz}->short_name_for_datetime( $_[0] ) },
55             '%' => sub { '%' },
56             );
57              
58             $strftime_patterns{h} = $strftime_patterns{b};
59              
60             sub strftime
61             {
62 186     186 0 66426 my $self = shift;
63             # make a copy or caller's scalars get munged
64 186         442 my @patterns = @_;
65              
66 186         241 my @r;
67 186         350 foreach my $p (@patterns)
68             {
69 187         1401 $p =~ s/
70             (?:
71             %{(\w+)} # method name like %{day_name}
72             |
73             %([%a-zA-Z]) # single character specifier like %d
74             |
75             %(\d+)N # special case for %N
76             )
77             /
78 405 100       7147 ( $1
    100          
    50          
    100          
    100          
79             ? ( $self->can($1) ? $self->$1() : "\%{$1}" )
80             : $2
81             ? ( $strftime_patterns{$2} ? $strftime_patterns{$2}->($self) : "\%$2" )
82             : $3
83             ? $strftime_patterns{N}->($self, $3)
84             : '' # this won't happen
85             )
86             /sgex;
87              
88 187 100       1436 return $p unless wantarray;
89              
90 2         7 push @r, $p;
91             }
92              
93 1         5 return @r;
94             }
95             }
96              
97             {
98             # It's an array because the order in which the regexes are checked
99             # is important. These patterns are similar to the ones Java uses,
100             # but not quite the same. See
101             # http://www.unicode.org/reports/tr35/tr35-9.html#Date_Format_Patterns.
102             my @patterns =
103             ( qr/GGGGG/ => sub { $_[0]->{locale}->era_narrow->[ $_[0]->_era_index() ] },
104             qr/GGGG/ => 'era_name',
105             qr/G{1,3}/ => 'era_abbr',
106              
107             qr/(y{3,5})/ => sub { $_[0]->_zero_padded_number( $1, $_[0]->year() ) },
108             # yy is a weird special case, where it must be exactly 2 digits
109             qr/yy/ => sub { my $year = $_[0]->year();
110             $year = substr( $year, -2, 2 ) if length $year > 2;
111             $_[0]->_zero_padded_number( 'yy', $year ) },
112             qr/y/ => sub { $_[0]->year() },
113             qr/(u+)/ => sub { $_[0]->_zero_padded_number( $1, $_[0]->year() ) },
114             qr/(Y+)/ => sub { $_[0]->_zero_padded_number( $1, $_[0]->week_year() ) },
115              
116             qr/QQQQ/ => 'quarter_name',
117             qr/QQQ/ => 'quarter_abbr',
118             qr/(QQ?)/ => sub { $_[0]->_zero_padded_number( $1, $_[0]->quarter() ) },
119              
120             qr/MMMMM/ => sub { $_[0]->{locale}->month_format_narrow->[ $_[0]->month() - 1 ] },
121             qr/MMMM/ => 'month_name',
122             qr/MMM/ => 'month_abbr',
123             qr/(MM?)/ => sub { $_[0]->_zero_padded_number( $1, $_[0]->month() ) },
124              
125             qr/LLLLL/ => sub { $_[0]->{locale}->month_stand_alone_narrow->[ $_[0]->month() - 1] },
126             qr/LLLL/ => sub { $_[0]->{locale}->month_stand_alone_wide->[ $_[0]->month() - 1 ] },
127             qr/LLL/ => sub { $_[0]->{locale}->month_stand_alone_abbreviated->[ $_[0]->month() - 1] },
128             qr/(LL?)/ => sub { $_[0]->_zero_padded_number( $1, $_[0]->month() ) },
129              
130             qr/(ww?)/ => sub { $_[0]->_zero_padded_number( $1, $_[0]->week_number() ) },
131             qr/W/ => 'week_of_month',
132              
133             qr/(dd?)/ => sub { $_[0]->_zero_padded_number( $1, $_[0]->day() ) },
134             qr/(D{1,3})/ => sub { $_[0]->_zero_padded_number( $1, $_[0]->day_of_year() ) },
135              
136             qr/F/ => 'weekday_of_month',
137             qr/(g+)/ => sub { $_[0]->_zero_padded_number( $1, $_[0]->mjd() ) },
138              
139             qr/EEEEE/ => sub { $_[0]->{locale}->day_format_narrow->[ $_[0]->day_of_week() - 1] },
140             qr/EEEE/ => 'day_name',
141             qr/E{1,3}/ => 'day_abbr',
142              
143             qr/eeeee/ => sub { $_[0]->{locale}->day_format_narrow->[ $_[0]->day_of_week() - 1] },
144             qr/eeee/ => 'day_name',
145             qr/eee/ => 'day_abbr',
146             qr/(ee?)/ => sub { $_[0]->_zero_padded_number( $1, $_[0]->local_day_of_week() ) },
147              
148             qr/ccccc/ => sub { $_[0]->{locale}->day_stand_alone_narrow->[ $_[0]->day_of_week() - 1] },
149             qr/cccc/ => sub { $_[0]->{locale}->day_stand_alone_wide->[ $_[0]->day_of_week() - 1] },
150             qr/ccc/ => sub { $_[0]->{locale}->day_stand_alone_abbreviated->[ $_[0]->day_of_week() - 1] },
151             qr/(cc?)/ => sub { $_[0]->_zero_padded_number( $1, $_[0]->local_day_of_week() ) },
152              
153             qr/a/ => 'am_or_pm',
154              
155             qr/(hh?)/ => sub { $_[0]->_zero_padded_number( $1, $_[0]->hour_12() ) },
156             qr/(HH?)/ => sub { $_[0]->_zero_padded_number( $1, $_[0]->hour() ) },
157             qr/(KK?)/ => sub { $_[0]->_zero_padded_number( $1, $_[0]->hour() % 12 ) },
158             qr/(kk?)/ => sub { $_[0]->_zero_padded_number( $1, $_[0]->hour_1() ) },
159             qr/(jj?)/ => sub { my $h = $_[0]->{locale}->prefers_24_hour_time() ? $_[0]->hour_12() : $_[0]->hour();
160             $_[0]->_zero_padded_number( $1, $h ) },
161              
162             qr/(mm?)/ => sub { $_[0]->_zero_padded_number( $1, $_[0]->minute() ) },
163              
164             qr/(ss?)/ => sub { $_[0]->_zero_padded_number( $1, $_[0]->second() ) },
165             # I'm not sure this is what is wanted (notably the trailing
166             # and leading zeros it can produce), but once again the LDML
167             # spec is not all that clear.
168             qr/(S+)/ => sub { my $l = length $1;
169             my $val = sprintf( "%.${l}f", $_[0]->fractional_second() - $_[0]->second() );
170             $val =~ s/^0\.//;
171             $val || 0 },
172             qr/A+/ => sub { ( $_[0]->{local_rd_secs} * 1000 ) + $_[0]->millisecond() },
173              
174             qr/zzzz/ => sub { $_[0]->time_zone_long_name() },
175             qr/z{1,3}/ => sub { $_[0]->time_zone_short_name() },
176             qr/ZZZZ/ => sub { $_[0]->time_zone_short_name()
177             . DateTimeX::Lite::TimeZone->offset_as_string( $_[0]->offset() ) },
178             qr/Z{1,3}/ => sub { DateTimeX::Lite::TimeZone->offset_as_string( $_[0]->offset() ) },
179             qr/vvvv/ => sub { $_[0]->time_zone_long_name() },
180             qr/v{1,3}/ => sub { $_[0]->time_zone_short_name() },
181             qr/VVVV/ => sub { $_[0]->time_zone_long_name() },
182             qr/V{1,3}/ => sub { $_[0]->time_zone_short_name() },
183             );
184              
185             sub _zero_padded_number
186             {
187 61     61   91 my $self = shift;
188 61         113 my $size = length shift;
189 61         65 my $val = shift;
190              
191 61         439 return sprintf( "%0${size}d", $val );
192             }
193              
194             sub _space_padded_string
195             {
196 0     0   0 my $self = shift;
197 0         0 my $size = length shift;
198 0         0 my $val = shift;
199              
200 0         0 return sprintf( "% ${size}s", $val );
201             }
202              
203             sub format_cldr
204             {
205 94     94 0 67662 my $self = shift;
206             # make a copy or caller's scalars get munged
207 94         319 my @patterns = @_;
208              
209 94         115 my @r;
210 94         169 foreach my $p (@patterns)
211             {
212 94         628 $p =~ s/\G
213             (?:
214             '((?:[^']|'')*)' # quote escaped bit of text
215             # it needs to end with one
216             # quote not followed by
217             # another
218             |
219             (([a-zA-Z])\3*) # could be a pattern
220             |
221             (.) # anything else
222             )
223             /
224 125 50       625 defined $1
    100          
    100          
225             ? $1
226             : defined $2
227             ? $self->_cldr_pattern($2)
228             : defined $4
229             ? $4
230             : undef # should never get here
231             /sgex;
232              
233 94         214 $p =~ s/\'\'/\'/g;
234              
235 94 50       798 return $p unless wantarray;
236              
237 0         0 push @r, $p;
238             }
239              
240 0         0 return @r;
241             }
242              
243             sub _cldr_pattern
244             {
245 104     104   136 my $self = shift;
246 104         182 my $pattern = shift;
247              
248 104         376 for ( my $i = 0; $i < @patterns; $i +=2 )
249             {
250 2578 100       19772 if ( $pattern =~ /$patterns[$i]/ )
251             {
252 104         168 my $sub = $patterns[ $i + 1 ];
253              
254 104         318 return $self->$sub();
255             }
256             }
257              
258 0         0 return $pattern;
259             }
260             }
261              
262             sub _format_nanosecs
263             {
264 4     4   6 my $self = shift;
265 4         9 my $precision = shift;
266              
267 4         19 my $ret = sprintf( "%09d", $self->{rd_nanosecs} );
268 4 100       15 return $ret unless $precision; # default = 9 digits
269              
270             # rd_nanosecs might contain a fractional separator
271 3         40 my ( $int, $frac ) = split /[.,]/, $self->{rd_nanosecs};
272 3 50       11 $ret .= $frac if $frac;
273              
274 3         13 return substr( $ret, 0, $precision );
275             }
276              
277             1;