X-Git-Url: http://git.freeside.biz/gitweb/?a=blobdiff_plain;f=rt%2Flib%2FRT%2FDate.pm;h=7f4e323e4f9c66998cfff3bd6fe5158677344d5d;hb=7322f2afedcc2f427e997d1535a503613a83f088;hp=fc4c43ce4cc798d5b70e25967cbe7db00cb8e43d;hpb=b4b0c7e72d7eaee2fbfc7022022c9698323203dd;p=freeside.git diff --git a/rt/lib/RT/Date.pm b/rt/lib/RT/Date.pm index fc4c43ce4..7f4e323e4 100644 --- a/rt/lib/RT/Date.pm +++ b/rt/lib/RT/Date.pm @@ -1,40 +1,40 @@ # BEGIN BPS TAGGED BLOCK {{{ -# +# # COPYRIGHT: -# -# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC -# -# +# +# This software is Copyright (c) 1996-2016 Best Practical Solutions, LLC +# +# # (Except where explicitly superseded by other copyright notices) -# -# +# +# # LICENSE: -# +# # This work is made available to you under the terms of Version 2 of # the GNU General Public License. A copy of that license should have # been provided with this software, but in any event can be snarfed # from www.gnu.org. -# +# # This work is distributed in the hope that it will be useful, but # WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # General Public License for more details. -# +# # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA # 02110-1301 or visit their web page on the internet at # http://www.gnu.org/licenses/old-licenses/gpl-2.0.html. -# -# +# +# # CONTRIBUTION SUBMISSION POLICY: -# +# # (The following paragraph is not intended to limit the rights granted # to you to modify and distribute this software under the terms of # the GNU General Public License and is only of importance to you if # you choose to contribute your changes and enhancements to the # community by submitting them to Best Practical Solutions, LLC.) -# +# # By intentionally submitting any modifications, corrections or # derivatives to this work, or any other work intended for use with # Request Tracker, to Best Practical Solutions, LLC, you confirm that @@ -43,7 +43,7 @@ # royalty-free, perpetual, license to use, copy, create derivative # works based on those contributions, and sublicense and distribute # those contributions and any derivatives thereof. -# +# # END BPS TAGGED BLOCK }}} =head1 NAME @@ -56,7 +56,7 @@ =head1 DESCRIPTION -RT Date is a simple Date Object designed to be speedy and easy for RT to use +RT Date is a simple Date Object designed to be speedy and easy for RT to use. The fact that it assumes that a time of 0 means "never" is probably a bug. @@ -68,13 +68,16 @@ The fact that it assumes that a time of 0 means "never" is probably a bug. package RT::Date; -use Time::Local; -use POSIX qw(tzset); use strict; use warnings; + use base qw/RT::Base/; +use DateTime; + +use Time::Local; +use POSIX qw(tzset); use vars qw($MINUTE $HOUR $DAY $WEEK $MONTH $YEAR); $MINUTE = 60; @@ -110,17 +113,14 @@ our @DAYS_OF_WEEK = ( ); our @FORMATTERS = ( - 'DefaultFormat', # loc - 'ISO', # loc - 'W3CDTF', # loc - 'RFC2822', # loc - 'RFC2616', # loc - 'iCal', # loc + 'DefaultFormat', # loc + 'ISO', # loc + 'W3CDTF', # loc + 'RFC2822', # loc + 'RFC2616', # loc + 'iCal', # loc + 'LocalizedDateTime', # loc ); -if ( eval 'use DateTime qw(); 1;' && eval 'use DateTime::Locale qw(); 1;' && - DateTime->can('format_cldr') && DateTime::Locale::root->can('date_format_full') ) { - push @FORMATTERS, 'LocalizedDateTime'; # loc -} =head2 new @@ -164,12 +164,23 @@ sub Set { @_ ); - return $self->Unix(0) unless $args{'Value'}; + return $self->Unix(0) unless $args{'Value'} && $args{'Value'} =~ /\S/; + + my $format = lc $args{'Format'}; - if ( $args{'Format'} =~ /^unix$/i ) { + if ( $format eq 'unix' ) { return $self->Unix( $args{'Value'} ); } - elsif ( $args{'Format'} =~ /^(sql|datemanip|iso)$/i ) { + elsif ( + ($format eq 'sql' || $format eq 'iso') + && $args{'Value'} =~ /^(\d{4})-(\d\d)-(\d\d) (\d\d):(\d\d):(\d\d)$/ + ) { + local $@; + my $u = eval { Time::Local::timegm($6, $5, $4, $3, $2-1, $1) } || 0; + $RT::Logger->warning("Invalid date $args{'Value'}: $@") if $@ && !$u; + return $self->Unix( $u > 0 ? $u : 0 ); + } + elsif ( $format =~ /^(sql|datemanip|iso)$/ ) { $args{'Value'} =~ s!/!-!g; if ( ( $args{'Value'} =~ /^(\d{4})?(\d\d)(\d\d)(\d\d)(\d\d)(\d\d)$/ ) @@ -201,14 +212,14 @@ sub Set { return $self->Unix(0); } } - elsif ( $args{'Format'} =~ /^unknown$/i ) { + elsif ( $format eq 'unknown' ) { require Time::ParseDate; # the module supports only legacy timezones like PDT or EST... # so we parse date as GMT and later apply offset, this only # should be applied to absolute times, so compensate shift in NOW my $now = time; $now += ($self->Localtime( $args{Timezone}, $now ))[9]; - my $date = Time::ParseDate::parsedate( + my ($date, $error) = Time::ParseDate::parsedate( $args{'Value'}, GMT => 1, NOW => $now, @@ -216,6 +227,13 @@ sub Set { PREFER_PAST => RT->Config->Get('AmbiguousDayInPast'), PREFER_FUTURE => RT->Config->Get('AmbiguousDayInFuture'), ); + unless ( defined $date ) { + $RT::Logger->warning( + "Couldn't parse date '$args{'Value'}' by Time::ParseDate" + ); + return $self->Unix(0); + } + # apply timezone offset $date -= ($self->Localtime( $args{Timezone}, $date ))[9]; @@ -223,7 +241,7 @@ sub Set { "RT::Date used Time::ParseDate to make '$args{'Value'}' $date\n" ); - return $self->Set( Format => 'unix', Value => $date); + return $self->Unix($date || 0); } else { $RT::Logger->error( @@ -273,6 +291,41 @@ sub SetToMidnight { return $self->Unix( $new ); } +=head2 SetToStart PERIOD[, Timezone => 'utc' ] + +Set to the beginning of the current PERIOD, which can be +"year", "month", "day", "hour", or "minute". + +=cut + +sub SetToStart { + my $self = shift; + my $p = uc(shift); + my %args = @_; + my $tz = $args{'Timezone'} || ''; + my @localtime = $self->Localtime($tz); + #remove 'offset' so that DST is figured based on the resulting time. + pop @localtime; + + # This is the cleanest way to implement it, I swear. + { + $localtime[0]=0; + last if ($p eq 'MINUTE'); + $localtime[1]=0; + last if ($p eq 'HOUR'); + $localtime[2]=0; + last if ($p eq 'DAY'); + $localtime[3]=1; + last if ($p eq 'MONTH'); + $localtime[4]=0; + last if ($p eq 'YEAR'); + $RT::Logger->warning("Couldn't find start date of '$p'."); + return; + } + my $new = $self->Timelocal($tz, @localtime); + return $self->Unix($new); +} + =head2 Diff Takes either an C object or the date in unixtime format as a string, @@ -323,56 +376,105 @@ sub DiffAsString { Takes a number of seconds. Returns a localized string describing that duration. +Takes optional named arguments: + +=over 4 + +=item * Show + +How many elements to show, how precise it should be. Default is 1, +most vague variant. + +=item * Short + +Turn on short notation with one character units, for example +"3M 2d 1m 10s". + +=back + =cut sub DurationAsString { my $self = shift; my $duration = int shift; + my %args = ( Show => 1, Short => 0, @_ ); + + unless ( $duration ) { + return $args{Short}? $self->loc("0s") : $self->loc("0 seconds"); + } - my ( $negative, $s, $time_unit ); + my $negative; $negative = 1 if $duration < 0; $duration = abs $duration; - if ( $duration < $MINUTE ) { - $s = $duration; - $time_unit = $self->loc("sec"); - } - elsif ( $duration < ( 2 * $HOUR ) ) { - $s = int( $duration / $MINUTE + 0.5 ); - $time_unit = $self->loc("min"); - } - elsif ( $duration < ( 2 * $DAY ) ) { - $s = int( $duration / $HOUR + 0.5 ); - $time_unit = $self->loc("hours"); - } - elsif ( $duration < ( 2 * $WEEK ) ) { - $s = int( $duration / $DAY + 0.5 ); - $time_unit = $self->loc("days"); - } - elsif ( $duration < ( 2 * $MONTH ) ) { - $s = int( $duration / $WEEK + 0.5 ); - $time_unit = $self->loc("weeks"); - } - elsif ( $duration < $YEAR ) { - $s = int( $duration / $MONTH + 0.5 ); - $time_unit = $self->loc("months"); - } - else { - $s = int( $duration / $YEAR + 0.5 ); - $time_unit = $self->loc("years"); + my @res; + + my $coef = 2; + my $i = 0; + while ( $duration > 0 && ++$i <= $args{'Show'} ) { + + my ($locstr, $unit); + if ( $duration < $MINUTE ) { + $locstr = $args{Short} + ? '[_1]s' # loc + : '[quant,_1,second,seconds]'; # loc + $unit = 1; + } + elsif ( $duration < ( $coef * $HOUR ) ) { + $locstr = $args{Short} + ? '[_1]m' # loc + : '[quant,_1,minute,minutes]'; # loc + $unit = $MINUTE; + } + elsif ( $duration < ( $coef * $DAY ) ) { + $locstr = $args{Short} + ? '[_1]h' # loc + : '[quant,_1,hour,hours]'; # loc + $unit = $HOUR; + } + elsif ( $duration < ( $coef * $WEEK ) ) { + $locstr = $args{Short} + ? '[_1]d' # loc + : '[quant,_1,day,days]'; # loc + $unit = $DAY; + } + elsif ( $duration < ( $coef * $MONTH ) ) { + $locstr = $args{Short} + ? '[_1]W' # loc + : '[quant,_1,week,weeks]'; # loc + $unit = $WEEK; + } + elsif ( $duration < $YEAR ) { + $locstr = $args{Short} + ? '[_1]M' # loc + : '[quant,_1,month,months]'; # loc + $unit = $MONTH; + } + else { + $locstr = $args{Short} + ? '[_1]Y' # loc + : '[quant,_1,year,years]'; # loc + $unit = $YEAR; + } + my $value = int( $duration / $unit + ($i < $args{'Show'}? 0 : 0.5) ); + $duration -= int( $value * $unit ); + + push @res, $self->loc($locstr, $value); + + $coef = 1; } if ( $negative ) { - return $self->loc( "[_1] [_2] ago", $s, $time_unit ); + return $self->loc( "[_1] ago", join ' ', @res ); } else { - return $self->loc( "[_1] [_2]", $s, $time_unit ); + return join ' ', @res; } } =head2 AgeAsString -Takes nothing. Returns a string that's the differnce between the +Takes nothing. Returns a string that's the difference between the time in the object and now. =cut @@ -383,10 +485,10 @@ sub AgeAsString { return $_[0]->DiffAsString } =head2 AsString -Returns the object's time as a localized string with curent user's prefered +Returns the object's time as a localized string with curent user's preferred format and timezone. -If the current user didn't choose prefered format then system wide setting is +If the current user didn't choose preferred format then system wide setting is used or L if the latter is not specified. See config option C. @@ -396,7 +498,7 @@ sub AsString { my $self = shift; my %args = (@_); - return $self->loc("Not set") unless $self->Unix > 0; + return $self->loc("Not set") unless $self->IsSet; my $format = RT->Config->Get( 'DateTimeFormat', $self->CurrentUser ) || 'DefaultFormat'; $format = { Format => $format } unless ref $format; @@ -467,7 +569,8 @@ Returns new unix time. sub AddDays { my $self = shift; - my $days = shift || 1; + my $days = shift; + $days = 1 unless defined $days; return $self->AddSeconds( $days * $DAY ); } @@ -479,6 +582,30 @@ Adds 24 hours to the current time. Returns new unix time. sub AddDay { return $_[0]->AddSeconds($DAY) } +=head2 AddMonth + +Adds one month to the current time. Returns new +unix time. + +=cut + +sub AddMonth { + my $self = shift; + my %args = @_; + my @localtime = $self->Localtime($args{'Timezone'}); + # remove offset, as with SetToStart + pop @localtime; + + $localtime[4]++; #month + if ( $localtime[4] == 12 ) { + $localtime[4] = 0; + $localtime[5]++; #year + } + + my $new = $self->Timelocal($args{'Timezone'}, @localtime); + return $self->Unix($new); +} + =head2 Unix [unixtime] Optionally takes a date in unix seconds since the epoch format. @@ -488,13 +615,21 @@ Returns the number of seconds since the epoch sub Unix { my $self = shift; - $self->{'time'} = int(shift || 0) if @_; + + if (@_) { + my $time = int(shift || 0); + if ($time < 0) { + RT->Logger->notice("Passed a unix time less than 0, forcing to 0: [$time]"); + $time = 0; + } + $self->{'time'} = int $time; + } return $self->{'time'}; } =head2 DateTime -Alias for L method. Arguments C and