| Filename | /home/vagrant/kohaclone/Koha/DateUtils.pm |
| Statements | Executed 0 statements in 0s |
| Line | State ments |
Time on line |
Calls | Time in subs |
Code |
|---|---|---|---|---|---|
| 1 | package Koha::DateUtils; | ||||
| 2 | |||||
| 3 | # Copyright (c) 2011 PTFS-Europe Ltd. | ||||
| 4 | # This file is part of Koha. | ||||
| 5 | # | ||||
| 6 | # Koha is free software; you can redistribute it and/or modify it under the | ||||
| 7 | # terms of the GNU General Public License as published by the Free Software | ||||
| 8 | # Foundation; either version 2 of the License, or (at your option) any later | ||||
| 9 | # version. | ||||
| 10 | # | ||||
| 11 | # Koha is distributed in the hope that it will be useful, but WITHOUT ANY | ||||
| 12 | # WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR | ||||
| 13 | # A PARTICULAR PURPOSE. See the GNU General Public License for more details. | ||||
| 14 | # | ||||
| 15 | # You should have received a copy of the GNU General Public License along with | ||||
| 16 | # Koha; if not, write to the Free Software Foundation, Inc., | ||||
| 17 | # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. | ||||
| 18 | |||||
| 19 | use Modern::Perl; | ||||
| 20 | use DateTime; | ||||
| 21 | use C4::Context; | ||||
| 22 | use Carp; | ||||
| 23 | |||||
| 24 | use base 'Exporter'; | ||||
| 25 | use version; our $VERSION = qv('1.0.0'); | ||||
| 26 | |||||
| 27 | our @EXPORT = ( | ||||
| 28 | qw( dt_from_string output_pref format_sqldatetime ) | ||||
| 29 | ); | ||||
| 30 | |||||
| 31 | =head1 DateUtils | ||||
| 32 | |||||
| 33 | Koha::DateUtils - Transitional wrappers to ease use of DateTime | ||||
| 34 | |||||
| 35 | =head1 DESCRIPTION | ||||
| 36 | |||||
| 37 | Koha has historically only used dates not datetimes and been content to | ||||
| 38 | handle these as strings. It also has confused formatting with actual dates | ||||
| 39 | this is a temporary module for wrappers to hide the complexity of switch to DateTime | ||||
| 40 | |||||
| 41 | =cut | ||||
| 42 | |||||
| 43 | =head2 dt_ftom_string | ||||
| 44 | |||||
| 45 | $dt = dt_from_string($date_string, [$format, $timezone ]); | ||||
| 46 | |||||
| 47 | Passed a date string returns a DateTime object format and timezone default | ||||
| 48 | to the system preferences. If the date string is empty DateTime->now is returned | ||||
| 49 | |||||
| 50 | =cut | ||||
| 51 | |||||
| 52 | sub dt_from_string { | ||||
| 53 | my ( $date_string, $date_format, $tz ) = @_; | ||||
| 54 | |||||
| 55 | return if $date_string and $date_string =~ m|^0000-0|; | ||||
| 56 | |||||
| 57 | $tz = C4::Context->tz unless $tz;; | ||||
| 58 | |||||
| 59 | return DateTime->now( time_zone => $tz ) unless $date_string; | ||||
| 60 | |||||
| 61 | $date_format = C4::Context->preference('dateformat') unless $date_format; | ||||
| 62 | |||||
| 63 | if ( ref($date_string) eq 'DateTime' ) { # already a dt return it | ||||
| 64 | return $date_string; | ||||
| 65 | } | ||||
| 66 | |||||
| 67 | my $regex; | ||||
| 68 | |||||
| 69 | # The fallback format is sql/iso | ||||
| 70 | my $fallback_re = qr| | ||||
| 71 | (?<year>\d{4}) | ||||
| 72 | - | ||||
| 73 | (?<month>\d{2}) | ||||
| 74 | - | ||||
| 75 | (?<day>\d{2}) | ||||
| 76 | |xms; | ||||
| 77 | |||||
| 78 | if ( $date_format eq 'metric' ) { | ||||
| 79 | # metric format is "dd/mm/yyyy[ hh:mm:ss]" | ||||
| 80 | $regex = qr| | ||||
| 81 | (?<day>\d{2}) | ||||
| 82 | / | ||||
| 83 | (?<month>\d{2}) | ||||
| 84 | / | ||||
| 85 | (?<year>\d{4}) | ||||
| 86 | |xms; | ||||
| 87 | } | ||||
| 88 | elsif ( $date_format eq 'dmydot' ) { | ||||
| 89 | # dmydot format is "dd.mm.yyyy[ hh:mm:ss]" | ||||
| 90 | $regex = qr| | ||||
| 91 | (?<day>\d{2}) | ||||
| 92 | . | ||||
| 93 | (?<month>\d{2}) | ||||
| 94 | . | ||||
| 95 | (?<year>\d{4}) | ||||
| 96 | |xms; | ||||
| 97 | } | ||||
| 98 | elsif ( $date_format eq 'us' ) { | ||||
| 99 | # us format is "mm/dd/yyyy[ hh:mm:ss]" | ||||
| 100 | $regex = qr| | ||||
| 101 | (?<month>\d{2}) | ||||
| 102 | / | ||||
| 103 | (?<day>\d{2}) | ||||
| 104 | / | ||||
| 105 | (?<year>\d{4}) | ||||
| 106 | |xms; | ||||
| 107 | } | ||||
| 108 | elsif ( $date_format eq 'iso' or $date_format eq 'sql' ) { | ||||
| 109 | # iso or sql format are yyyy-dd-mm[ hh:mm:ss]" | ||||
| 110 | $regex = $fallback_re; | ||||
| 111 | } | ||||
| 112 | else { | ||||
| 113 | die "Invalid dateformat parameter ($date_format)"; | ||||
| 114 | } | ||||
| 115 | |||||
| 116 | # Add the faculative time part [hh:mm[:ss]] | ||||
| 117 | my $time_re .= qr| | ||||
| 118 | ( | ||||
| 119 | \s* | ||||
| 120 | (?<hour>\d{2}) | ||||
| 121 | : | ||||
| 122 | (?<minute>\d{2}) | ||||
| 123 | ( | ||||
| 124 | : | ||||
| 125 | (?<second>\d{2}) | ||||
| 126 | )? | ||||
| 127 | )? | ||||
| 128 | |xms; | ||||
| 129 | $regex .= $time_re; | ||||
| 130 | $fallback_re .= $time_re; | ||||
| 131 | |||||
| 132 | my %dt_params; | ||||
| 133 | 12 | 51µs | if ( $date_string =~ $regex ) { # spent 51µs making 12 calls to Tie::Hash::NamedCapture::FETCH, avg 4µs/call | ||
| 134 | %dt_params = ( | ||||
| 135 | year => $+{year}, | ||||
| 136 | month => $+{month}, | ||||
| 137 | day => $+{day}, | ||||
| 138 | hour => $+{hour}, | ||||
| 139 | minute => $+{minute}, | ||||
| 140 | second => $+{second}, | ||||
| 141 | ); | ||||
| 142 | } elsif ( $date_string =~ $fallback_re ) { | ||||
| 143 | %dt_params = ( | ||||
| 144 | year => $+{year}, | ||||
| 145 | month => $+{month}, | ||||
| 146 | day => $+{day}, | ||||
| 147 | hour => $+{hour}, | ||||
| 148 | minute => $+{minute}, | ||||
| 149 | second => $+{second}, | ||||
| 150 | ); | ||||
| 151 | } | ||||
| 152 | else { | ||||
| 153 | die "The given date ($date_string) does not match the date format ($date_format)"; | ||||
| 154 | } | ||||
| 155 | |||||
| 156 | # system allows the 0th of the month | ||||
| 157 | $dt_params{day} = '01' if $dt_params{day} eq '00'; | ||||
| 158 | |||||
| 159 | # Set default hh:mm:ss to 00:00:00 | ||||
| 160 | $dt_params{hour} = 00 unless defined $dt_params{hour}; | ||||
| 161 | $dt_params{minute} = 00 unless defined $dt_params{minute}; | ||||
| 162 | $dt_params{second} = 00 unless defined $dt_params{second}; | ||||
| 163 | |||||
| 164 | my $dt = eval { | ||||
| 165 | DateTime->new( | ||||
| 166 | %dt_params, | ||||
| 167 | # No TZ for dates 'infinite' => see bug 13242 | ||||
| 168 | ( $dt_params{year} < 9999 ? ( time_zone => $tz->name ) : () ), | ||||
| 169 | ); | ||||
| 170 | }; | ||||
| 171 | if ($@) { | ||||
| 172 | $tz = DateTime::TimeZone->new( name => 'floating' ); | ||||
| 173 | $dt = DateTime->new( | ||||
| 174 | %dt_params, | ||||
| 175 | # No TZ for dates 'infinite' => see bug 13242 | ||||
| 176 | ( $dt_params{year} < 9999 ? ( time_zone => $tz->name ) : () ), | ||||
| 177 | ); | ||||
| 178 | } | ||||
| 179 | return $dt; | ||||
| 180 | } | ||||
| 181 | |||||
| 182 | =head2 output_pref | ||||
| 183 | |||||
| 184 | $date_string = output_pref({ dt => $dt [, dateformat => $date_format, timeformat => $time_format, dateonly => 0|1, as_due_date => 0|1 ] }); | ||||
| 185 | $date_string = output_pref( $dt ); | ||||
| 186 | |||||
| 187 | Returns a string containing the time & date formatted as per the C4::Context setting, | ||||
| 188 | or C<undef> if C<undef> was provided. | ||||
| 189 | |||||
| 190 | This routine can either be passed a DateTime object or or a hashref. If it is | ||||
| 191 | passed a hashref, the expected keys are a mandatory 'dt' for the DateTime, | ||||
| 192 | an optional 'dateformat' to override the dateformat system preference, an | ||||
| 193 | optional 'timeformat' to override the TimeFormat system preference value, | ||||
| 194 | and an optional 'dateonly' to specify that only the formatted date string | ||||
| 195 | should be returned without the time. | ||||
| 196 | |||||
| 197 | =cut | ||||
| 198 | |||||
| 199 | sub output_pref { | ||||
| 200 | my $params = shift; | ||||
| 201 | my ( $dt, $str, $force_pref, $force_time, $dateonly, $as_due_date ); | ||||
| 202 | if ( ref $params eq 'HASH' ) { | ||||
| 203 | $dt = $params->{dt}; | ||||
| 204 | $str = $params->{str}; | ||||
| 205 | $force_pref = $params->{dateformat}; # if testing we want to override Context | ||||
| 206 | $force_time = $params->{timeformat}; | ||||
| 207 | $dateonly = $params->{dateonly} || 0; # if you don't want the hours and minutes | ||||
| 208 | $as_due_date = $params->{as_due_date} || 0; # don't display the hours and minutes if eq to 23:59 or 11:59 (depending the TimeFormat value) | ||||
| 209 | } else { | ||||
| 210 | $dt = $params; | ||||
| 211 | } | ||||
| 212 | |||||
| 213 | 2 | 42µs | carp "output_pref should not be called with both dt and str parameters" # spent 42µs making 2 calls to DateTime::_stringify, avg 21µs/call | ||
| 214 | and return | ||||
| 215 | if $dt and $str; | ||||
| 216 | |||||
| 217 | $dt = eval { dt_from_string( $str ) } if $str; | ||||
| 218 | carp "Invalid date '$str' passed to output_pref\n" if $@; | ||||
| 219 | |||||
| 220 | return unless defined $dt; | ||||
| 221 | |||||
| 222 | # FIXME: see bug 13242 => no TZ for dates 'infinite' | ||||
| 223 | if ( $dt->ymd !~ /^9999/ ) { | ||||
| 224 | my $tz = $dateonly ? DateTime::TimeZone->new(name => 'floating') : C4::Context->tz; | ||||
| 225 | $dt->set_time_zone( $tz ); | ||||
| 226 | } | ||||
| 227 | |||||
| 228 | my $pref = | ||||
| 229 | defined $force_pref ? $force_pref : C4::Context->preference('dateformat'); | ||||
| 230 | |||||
| 231 | my $time_format = $force_time || C4::Context->preference('TimeFormat') || q{}; | ||||
| 232 | my $time = ( $time_format eq '12hr' ) ? '%I:%M %p' : '%H:%M'; | ||||
| 233 | my $date; | ||||
| 234 | if ( $pref =~ m/^iso/ ) { | ||||
| 235 | $date = $dateonly | ||||
| 236 | ? $dt->strftime("%Y-%m-%d") | ||||
| 237 | : $dt->strftime("%Y-%m-%d $time"); | ||||
| 238 | } | ||||
| 239 | elsif ( $pref =~ m/^metric/ ) { | ||||
| 240 | $date = $dateonly | ||||
| 241 | ? $dt->strftime("%d/%m/%Y") | ||||
| 242 | : $dt->strftime("%d/%m/%Y $time"); | ||||
| 243 | } | ||||
| 244 | elsif ( $pref =~ m/^dmydot/ ) { | ||||
| 245 | $date = $dateonly | ||||
| 246 | ? $dt->strftime("%d.%m.%Y") | ||||
| 247 | : $dt->strftime("%d.%m.%Y $time"); | ||||
| 248 | } | ||||
| 249 | |||||
| 250 | elsif ( $pref =~ m/^us/ ) { | ||||
| 251 | $date = $dateonly | ||||
| 252 | ? $dt->strftime("%m/%d/%Y") | ||||
| 253 | : $dt->strftime("%m/%d/%Y $time"); | ||||
| 254 | } | ||||
| 255 | else { | ||||
| 256 | $date = $dateonly | ||||
| 257 | ? $dt->strftime("%Y-%m-%d") | ||||
| 258 | : $dt->strftime("%Y-%m-%d $time"); | ||||
| 259 | } | ||||
| 260 | |||||
| 261 | if ( $as_due_date ) { | ||||
| 262 | $time_format eq '12hr' | ||||
| 263 | ? $date =~ s| 11:59 PM$|| | ||||
| 264 | : $date =~ s| 23:59$||; | ||||
| 265 | } | ||||
| 266 | |||||
| 267 | return $date; | ||||
| 268 | } | ||||
| 269 | |||||
| 270 | =head2 format_sqldatetime | ||||
| 271 | |||||
| 272 | $string = format_sqldatetime( $string_as_returned_from_db ); | ||||
| 273 | |||||
| 274 | a convenience routine for calling dt_from_string and formatting the result | ||||
| 275 | with output_pref as it is a frequent activity in scripts | ||||
| 276 | |||||
| 277 | =cut | ||||
| 278 | |||||
| 279 | sub format_sqldatetime { | ||||
| 280 | my $str = shift; | ||||
| 281 | my $force_pref = shift; # if testing we want to override Context | ||||
| 282 | my $force_time = shift; | ||||
| 283 | my $dateonly = shift; | ||||
| 284 | |||||
| 285 | if ( defined $str && $str =~ m/^\d{4}-\d{2}-\d{2}/ ) { | ||||
| 286 | my $dt = dt_from_string( $str, 'sql' ); | ||||
| 287 | return q{} unless $dt; | ||||
| 288 | $dt->truncate( to => 'minute' ); | ||||
| 289 | return output_pref({ | ||||
| 290 | dt => $dt, | ||||
| 291 | dateformat => $force_pref, | ||||
| 292 | timeformat => $force_time, | ||||
| 293 | dateonly => $dateonly | ||||
| 294 | }); | ||||
| 295 | } | ||||
| 296 | return q{}; | ||||
| 297 | } | ||||
| 298 | |||||
| 299 | 1; |