File: | C4/Dates.pm |
Coverage: | 80.3% |
line | stmt | bran | cond | sub | time | code |
---|---|---|---|---|---|---|
1 | package C4::Dates; | |||||
2 | ||||||
3 | # This file is part of Koha. | |||||
4 | # | |||||
5 | # Koha is free software; you can redistribute it and/or modify it under the | |||||
6 | # terms of the GNU General Public License as published by the Free Software | |||||
7 | # Foundation; either version 2 of the License, or (at your option) any later | |||||
8 | # version. | |||||
9 | # | |||||
10 | # Koha is distributed in the hope that it will be useful, but WITHOUT ANY | |||||
11 | # WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR | |||||
12 | # A PARTICULAR PURPOSE. See the GNU General Public License for more details. | |||||
13 | # | |||||
14 | # You should have received a copy of the GNU General Public License along with | |||||
15 | # Koha; if not, write to the Free Software Foundation, Inc., 59 Temple Place, | |||||
16 | # Suite 330, Boston, MA 02111-1307 USA | |||||
17 | ||||||
18 | 56 56 56 | 118287 426 2050 | use strict; | |||
19 | 56 56 56 | 596 427 2426 | use warnings; | |||
20 | 56 56 56 | 561 346 4717 | use Carp; | |||
21 | 56 56 56 | 1072 346 1013 | use C4::Context; | |||
22 | 56 56 56 | 461 246 5642 | use C4::Debug; | |||
23 | 56 56 56 | 441 317 2504 | use Exporter; | |||
24 | 56 56 56 | 480 208 1004 | use POSIX qw(strftime); | |||
25 | 56 56 56 | 140329 192141 8656 | use Date::Calc qw(check_date check_time); | |||
26 | 56 56 56 | 759 270 4520 | use vars qw($VERSION @ISA @EXPORT @EXPORT_OK %EXPORT_TAGS); | |||
27 | 56 56 56 | 415 247 3813 | use vars qw($debug $cgi_debug); | |||
28 | ||||||
29 | BEGIN { | |||||
30 | 56 | 257 | $VERSION = 0.04; | |||
31 | 56 | 776 | @ISA = qw(Exporter); | |||
32 | 56 | 1503 | @EXPORT_OK = qw(format_date_in_iso format_date); | |||
33 | } | |||||
34 | ||||||
35 | 56 56 56 | 493 215 111984 | use vars qw($prefformat); | |||
36 | ||||||
37 | sub _prefformat { | |||||
38 | 286 | 730 | unless ( defined $prefformat ) { | |||
39 | 0 | 0 | $prefformat = C4::Context->preference('dateformat'); | |||
40 | } | |||||
41 | 286 | 865 | return $prefformat; | |||
42 | } | |||||
43 | ||||||
44 | sub reset_prefformat { # subroutine to clear the prefformat, called when we change it | |||||
45 | 0 | 0 | if (defined $prefformat){ | |||
46 | 0 | 0 | $prefformat = C4::Context->preference('dateformat'); | |||
47 | } | |||||
48 | } | |||||
49 | ||||||
50 | our %format_map = ( | |||||
51 | iso => 'yyyy-mm-dd', # plus " HH:MM:SS" | |||||
52 | metric => 'dd/mm/yyyy', # plus " HH:MM:SS" | |||||
53 | us => 'mm/dd/yyyy', # plus " HH:MM:SS" | |||||
54 | sql => 'yyyymmdd HHMMSS', | |||||
55 | rfc822 => 'a, dd b y HH:MM:SS z ', | |||||
56 | ); | |||||
57 | our %posix_map = ( | |||||
58 | iso => '%Y-%m-%d', # or %F, "Full Date" | |||||
59 | metric => '%d/%m/%Y', | |||||
60 | us => '%m/%d/%Y', | |||||
61 | sql => '%Y%m%d %H%M%S', | |||||
62 | rfc822 => '%a, %d %b %Y %H:%M:%S %z', | |||||
63 | ); | |||||
64 | ||||||
65 | our %dmy_subs = ( # strings to eval (after using regular expression returned by regexp below) | |||||
66 | # make arrays for POSIX::strftime() | |||||
67 | iso => '[(($6||0),($5||0),($4||0),$3, $2 - 1, $1 - 1900)]', | |||||
68 | metric => '[(($6||0),($5||0),($4||0),$1, $2 - 1, $3 - 1900)]', | |||||
69 | us => '[(($6||0),($5||0),($4||0),$2, $1 - 1, $3 - 1900)]', | |||||
70 | sql => '[(($6||0),($5||0),($4||0),$3, $2 - 1, $1 - 1900)]', | |||||
71 | rfc822 => '[($7, $6, $5, $2, $3, $4 - 1900, $8)]', | |||||
72 | ); | |||||
73 | ||||||
74 | our @months = qw(Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec); | |||||
75 | ||||||
76 | our @days = qw(Sun Mon Tue Wed Thu Fri Sat); | |||||
77 | ||||||
78 | sub regexp ($;$) { | |||||
79 | 140 | 4421 | my $self = shift; | |||
80 | 140 | 615 | my $delim = qr/:?\:|\/|-/; # "non memory" cluster: no backreference | |||
81 | 140 | 618 | my $format = (@_) ? _recognize_format(shift) : ( $self->{'dateformat'} || _prefformat() ); | |||
82 | ||||||
83 | # Extra layer of checking $self->{'dateformat'}. | |||||
84 | # Why? Because it is assumed you might want to check regexp against an *instantiated* Dates object as a | |||||
85 | # way of saying "does this string match *whatever* format that Dates object is?" | |||||
86 | ||||||
87 | 140 | 528 | ( $format eq 'sql' ) | |||
88 | and return qr/^(\d{4})(\d{1,2})(\d{1,2})(?:\s{4}(\d{2})(\d{2})(\d{2}))?/; | |||||
89 | 124 | 913 | ( $format eq 'iso' ) | |||
90 | and return qr/^(\d{4})$delim(\d{1,2})$delim(\d{1,2})(?:(?:\s{1}|T)(\d{2})\:?(\d{2})\:?(\d{2}))?Z?/; | |||||
91 | 76 | 278 | ( $format eq 'rfc822' ) | |||
92 | and return qr/^([a-zA-Z]{3}),\s{1}(\d{1,2})\s{1}([a-zA-Z]{3})\s{1}(\d{4})\s{1}(\d{1,2})\:(\d{1,2})\:(\d{1,2})\s{1}(([\-|\+]\d{4})|([A-Z]{3}))/; | |||||
93 | 68 | 779 | return qr/^(\d{1,2})$delim(\d{1,2})$delim(\d{4})(?:\s{1}(\d{1,2})\:?(\d{1,2})\:?(\d{1,2}))?/; # everything else | |||
94 | } | |||||
95 | ||||||
96 | sub dmy_map ($$) { | |||||
97 | 80 | 192 | my $self = shift; | |||
98 | 80 | 313 | my $val = shift or return undef; | |||
99 | 80 | 292 | my $dformat = $self->{'dateformat'} or return undef; | |||
100 | 80 | 257 | my $re = $self->regexp(); | |||
101 | 80 | 222 | my $xsub = $dmy_subs{$dformat}; | |||
102 | 80 | 252 | $debug and print STDERR "xsub: $xsub \n"; | |||
103 | 80 | 809 | if ( $val =~ /$re/ ) { | |||
104 | 80 | 6901 | my $aref = eval $xsub; | |||
105 | 80 | 437 | if ($dformat eq 'rfc822') { | |||
106 | 4 | 11 | $aref = _abbr_to_numeric($aref, $dformat); | |||
107 | 4 4 | 4 7 | pop(@{$aref}); #pop off tz offset because we are not setup to handle tz conversions just yet | |||
108 | } | |||||
109 | 80 | 468 | _check_date_and_time($aref); | |||
110 | 80 80 | 144 223 | push @{$aref}, (-1,-1,1); # for some reason unknown to me, setting isdst to -1 or undef causes strftime to fail to return the tz offset which is required in RFC822 format -chris_n | |||
111 | 80 80 | 146 1035 | return @{$aref}; | |||
112 | } | |||||
113 | ||||||
114 | # $debug and | |||||
115 | 0 | 0 | carp "Illegal Date '$val' does not match '$dformat' format: " . $self->visual(); | |||
116 | 0 | 0 | return 0; | |||
117 | } | |||||
118 | ||||||
119 | sub _abbr_to_numeric { | |||||
120 | 4 | 5 | my $aref = shift; | |||
121 | 4 | 8 | my $dformat = shift; | |||
122 | 4 | 16 | my ($month_abbr, $day_abbr) = ($aref->[4], $aref->[3]) if $dformat eq 'rfc822'; | |||
123 | ||||||
124 | for( my $i = 0; $i < scalar(@months); $i++ ) { | |||||
125 | 38 | 131 | if ( $months[$i] =~ /$month_abbr/ ) { | |||
126 | 4 | 7 | $aref->[4] = $i-1; | |||
127 | 4 | 6 | last; | |||
128 | } | |||||
129 | 4 | 6 | }; | |||
130 | ||||||
131 | for( my $i = 0; $i < scalar(@days); $i++ ) { | |||||
132 | 28 | 92 | if ( $days[$i] =~ /$day_abbr/ ) { | |||
133 | 0 | 0 | $aref->[3] = $i; | |||
134 | 0 | 0 | last; | |||
135 | } | |||||
136 | 4 | 5 | }; | |||
137 | 4 | 9 | return $aref; | |||
138 | } | |||||
139 | ||||||
140 | sub _check_date_and_time { | |||||
141 | 80 | 173 | my $chron_ref = shift; | |||
142 | 80 | 215 | my ( $year, $month, $day ) = _chron_to_ymd($chron_ref); | |||
143 | 80 | 600 | unless ( check_date( $year, $month, $day ) ) { | |||
144 | 18 | 1156 | carp "Illegal date specified (year = $year, month = $month, day = $day)"; | |||
145 | } | |||||
146 | 80 | 10971 | my ( $hour, $minute, $second ) = _chron_to_hms($chron_ref); | |||
147 | 80 | 409 | unless ( check_time( $hour, $minute, $second ) ) { | |||
148 | 0 | 0 | carp "Illegal time specified (hour = $hour, minute = $minute, second = $second)"; | |||
149 | } | |||||
150 | } | |||||
151 | ||||||
152 | sub _chron_to_ymd { | |||||
153 | 80 | 266 | my $chron_ref = shift; | |||
154 | 80 | 379 | return ( $chron_ref->[5] + 1900, $chron_ref->[4] + 1, $chron_ref->[3] ); | |||
155 | } | |||||
156 | ||||||
157 | sub _chron_to_hms { | |||||
158 | 80 | 207 | my $chron_ref = shift; | |||
159 | 80 | 262 | return ( $chron_ref->[2], $chron_ref->[1], $chron_ref->[0] ); | |||
160 | } | |||||
161 | ||||||
162 | sub new { | |||||
163 | 156 | 744072 | my $this = shift; | |||
164 | 156 | 1929 | my $class = ref($this) || $this; | |||
165 | 156 | 371 | my $self = {}; | |||
166 | 156 | 674 | bless $self, $class; | |||
167 | 156 | 469 | return $self->init(@_); | |||
168 | } | |||||
169 | ||||||
170 | sub init ($;$$) { | |||||
171 | 156 | 286 | my $self = shift; | |||
172 | 156 | 254 | my $dformat; | |||
173 | 156 | 598 | $self->{'dateformat'} = $dformat = ( scalar(@_) >= 2 ) ? $_[1] : _prefformat(); | |||
174 | 156 | 527 | ( $format_map{$dformat} ) or croak "Invalid date format '$dformat' from " . ( ( scalar(@_) >= 2 ) ? 'argument' : 'system preferences' ); | |||
175 | 156 | 1429 | $self->{'dmy_arrayref'} = [ ( (@_) ? $self->dmy_map(shift) : localtime ) ]; | |||
176 | 156 0 0 | 740 0 0 | if ($debug && $debug > 1) { warn "(during init) \@\$self->{'dmy_arrayref'}: " . join( ' ', @{ $self->{'dmy_arrayref'} } ) . "\n"; } | |||
177 | 156 | 1164 | return $self; | |||
178 | } | |||||
179 | ||||||
180 | sub output ($;$) { | |||||
181 | 414 | 149059 | my $self = shift; | |||
182 | 414 | 1294 | my $newformat = (@_) ? _recognize_format(shift) : _prefformat(); | |||
183 | 414 414 414 | 767 791 29592 | return ( eval { POSIX::strftime( $posix_map{$newformat}, @{ $self->{'dmy_arrayref'} } ) } || undef ); | |||
184 | } | |||||
185 | ||||||
186 | sub today ($;$) { # NOTE: sets date value to today (and returns it in the requested or current format) | |||||
187 | 64 | 25667 | my $class = shift; | |||
188 | 64 | 274 | $class = ref($class) || $class; | |||
189 | 64 | 202 | my $format = (@_) ? _recognize_format(shift) : _prefformat(); | |||
190 | 64 | 184 | return $class->new()->output($format); | |||
191 | } | |||||
192 | ||||||
193 | sub _recognize_format($) { | |||||
194 | 318 | 595 | my $incoming = shift; | |||
195 | 318 | 849 | ( $incoming eq 'syspref' ) and return _prefformat(); | |||
196 | 310 | 5499 | ( scalar grep ( /^$incoming$/, keys %format_map ) == 1 ) or croak "The format you asked for ('$incoming') is unrecognized."; | |||
197 | 310 | 1047 | return $incoming; | |||
198 | } | |||||
199 | ||||||
200 | sub DHTMLcalendar ($;$) { # interface to posix_map | |||||
201 | 0 | 0 | my $class = shift; | |||
202 | 0 | 0 | my $format = (@_) ? shift : _prefformat(); | |||
203 | 0 | 0 | return $posix_map{$format}; | |||
204 | } | |||||
205 | ||||||
206 | sub format { # get or set dateformat: iso, metric, us, etc. | |||||
207 | 12 | 29 | my $self = shift; | |||
208 | 12 | 62 | (@_) or return $self->{'dateformat'}; | |||
209 | 10 | 22 | $self->{'dateformat'} = _recognize_format(shift); | |||
210 | } | |||||
211 | ||||||
212 | sub visual { | |||||
213 | 10 | 34 | my $self = shift; | |||
214 | 10 | 37 | if (@_) { | |||
215 | 0 | 0 | return $format_map{ _recognize_format(shift) }; | |||
216 | } | |||||
217 | 10 | 60 | $self eq __PACKAGE__ and return $format_map{ _prefformat() }; | |||
218 | 10 10 | 28 125 | return $format_map{ eval { $self->{'dateformat'} } || _prefformat() }; | |||
219 | } | |||||
220 | ||||||
221 | # like the functions from the old C4::Date.pm | |||||
222 | sub format_date { | |||||
223 | 16 | 8892 | return __PACKAGE__->new( shift, 'iso' )->output( (@_) ? shift : _prefformat() ); | |||
224 | } | |||||
225 | ||||||
226 | sub format_date_in_iso { | |||||
227 | 8 | 3930 | return __PACKAGE__->new( shift, _prefformat() )->output('iso'); | |||
228 | } | |||||
229 | ||||||
230 | 1; |