X-Git-Url: http://git.freeside.biz/gitweb/?a=blobdiff_plain;f=rt%2Flib%2FRT%2FDate.pm;h=52bdc01dfe1416309efc42ff82380cca8cadfe55;hb=0ea23112cfa0d82738b0f08d60d90579721b7524;hp=d569971742fc641f1da209432e89977d54686ef2;hpb=0ebeec96313dd7edfca340f01f8fbbbac1f4aa1d;p=freeside.git diff --git a/rt/lib/RT/Date.pm b/rt/lib/RT/Date.pm index d56997174..52bdc01df 100644 --- a/rt/lib/RT/Date.pm +++ b/rt/lib/RT/Date.pm @@ -1,6 +1,50 @@ -#$Header: /home/cvs/cvsroot/freeside/rt/lib/RT/Date.pm,v 1.1 2002-08-12 06:17:07 ivan Exp $ -# (c) 1996-2000 Jesse Vincent -# This software is redistributable under the terms of the GNU GPL +# BEGIN BPS TAGGED BLOCK {{{ +# +# COPYRIGHT: +# +# This software is Copyright (c) 1996-2014 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 +# you are the copyright holder for those contributions and you grant +# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable, +# 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 @@ -16,11 +60,6 @@ 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. -=begin testing - -ok (require RT::Date); - -=end testing =head1 METHODS @@ -28,346 +67,486 @@ ok (require RT::Date); package RT::Date; + + +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; $HOUR = 60 * $MINUTE; $DAY = 24 * $HOUR; $WEEK = 7 * $DAY; -$MONTH = 4 * $WEEK; -$YEAR = 365 * $DAY; +$MONTH = 30.4375 * $DAY; +$YEAR = 365.25 * $DAY; + +our @MONTHS = ( + 'Jan', # loc + 'Feb', # loc + 'Mar', # loc + 'Apr', # loc + 'May', # loc + 'Jun', # loc + 'Jul', # loc + 'Aug', # loc + 'Sep', # loc + 'Oct', # loc + 'Nov', # loc + 'Dec', # loc +); + +our @DAYS_OF_WEEK = ( + 'Sun', # loc + 'Mon', # loc + 'Tue', # loc + 'Wed', # loc + 'Thu', # loc + 'Fri', # loc + 'Sat', # loc +); + +our @FORMATTERS = ( + 'DefaultFormat', # loc + 'ISO', # loc + 'W3CDTF', # loc + 'RFC2822', # loc + 'RFC2616', # loc + 'iCal', # loc + 'LocalizedDateTime', # loc +); + +=head2 new + +Object constructor takes one argument C object. -# {{{ sub new +=cut -sub new { - my $proto = shift; - my $class = ref($proto) || $proto; - my $self = {}; - bless ($self, $class); - $self->Unix(0); - return $self; +sub new { + my $proto = shift; + my $class = ref($proto) || $proto; + my $self = {}; + bless ($self, $class); + $self->CurrentUser(@_); + $self->Unix(0); + return $self; } -# }}} - -# {{{ sub Set +=head2 Set -=head2 sub Set +Takes a param hash with the fields C, C and C. -takes a param hash with the fields 'Format' and 'Value' - -if $args->{'Format'} is 'unix', takes the number of seconds since the epoch +If $args->{'Format'} is 'unix', takes the number of seconds since the epoch. If $args->{'Format'} is ISO, tries to parse an ISO date. -If $args->{'Format'} is 'unknown', require Date::Parse and make it figure things -out. This is a heavyweight operation that should never be called from within -RT's core. But it's really useful for something like the textbox date entry -where we let the user do whatever they want. - -If $args->{'Value'} is 0, assumes you mean never. +If $args->{'Format'} is 'unknown', require Time::ParseDate and make it figure +things out. This is a heavyweight operation that should never be called from +within RT's core. But it's really useful for something like the textbox date +entry where we let the user do whatever they want. +If $args->{'Value'} is 0, assumes you mean never. =cut sub Set { my $self = shift; - my %args = ( Format => 'unix', - Value => time, - @_); - if (($args{'Value'} =~ /^\d*$/) and ($args{'Value'} == 0)) { - $self->Unix(-1); - return($self->Unix()); + my %args = ( + Format => 'unix', + Value => time, + Timezone => 'user', + @_ + ); + + return $self->Unix(0) unless $args{'Value'} && $args{'Value'} =~ /\S/; + + if ( $args{'Format'} =~ /^unix$/i ) { + return $self->Unix( $args{'Value'} ); } + elsif ( $args{'Format'} =~ /^(sql|datemanip|iso)$/i ) { + $args{'Value'} =~ s!/!-!g; + + if ( ( $args{'Value'} =~ /^(\d{4})?(\d\d)(\d\d)(\d\d)(\d\d)(\d\d)$/ ) + || ( $args{'Value'} =~ /^(\d{4})?(\d\d)(\d\d)(\d\d):(\d\d):(\d\d)$/ ) + || ( $args{'Value'} =~ /^(?:(\d{4})-)?(\d\d)-(\d\d) (\d\d):(\d\d):(\d\d)$/ ) + || ( $args{'Value'} =~ /^(?:(\d{4})-)?(\d\d)-(\d\d) (\d\d):(\d\d):(\d\d)\+00$/ ) + ) { + + my ($year, $mon, $mday, $hours, $min, $sec) = ($1, $2, $3, $4, $5, $6); + + # use current year if string has no value + $year ||= (localtime time)[5] + 1900; + + #timegm expects month as 0->11 + $mon--; + + #now that we've parsed it, deal with the case where everything was 0 + return $self->Unix(0) if $mon < 0 || $mon > 11; - if ($args{'Format'} =~ /^unix$/i) { - $self->Unix($args{'Value'}); + my $tz = lc $args{'Format'} eq 'datemanip'? 'user': 'utc'; + $self->Unix( $self->Timelocal( $tz, $sec, $min, $hours, $mday, $mon, $year ) ); + + $self->Unix(0) unless $self->Unix > 0; + } + else { + $RT::Logger->warning( + "Couldn't parse date '$args{'Value'}' as a $args{'Format'} format" + ); + return $self->Unix(0); + } } - - elsif ($args{'Format'} =~ /^(sql|datemanip|iso)$/i) { - - if (($args{'Value'} =~ /^(\d{4}?)(\d\d)(\d\d)(\d\d)(\d\d)(\d\d)$/) || - ($args{'Value'} =~ /^(\d{4}?)-(\d\d)-(\d\d) (\d\d):(\d\d):(\d\d)$/) || - ($args{'Value'} =~ /^(\d{4}?)-(\d\d)-(\d\d) (\d\d):(\d\d):(\d\d)\+00$/) || - ($args{'Value'} =~ /^(\d{4}?)(\d\d)(\d\d)(\d\d):(\d\d):(\d\d)$/)) { - - my $year = $1; - my $mon = $2; - my $mday = $3; - my $hours = $4; - my $min = $5; - my $sec = $6; - - #timegm expects month as 0->11 - $mon--; - - #now that we've parsed it, deal with the case where everything - #was 0 - if ($mon == -1) { - $self->Unix(-1); - } else { - - #Dateamnip strings aren't in GMT. - if ($args{'Format'} =~ /^datemanip$/i) { - $self->Unix(timelocal($sec,$min,$hours,$mday,$mon,$year)); - } - #ISO and SQL dates are in GMT - else { - $self->Unix(timegm($sec,$min,$hours,$mday,$mon,$year)); - } - - $self->Unix(-1) unless $self->Unix; - } - } - else { - use Carp; - Carp::cluck; - $RT::Logger->debug( "Couldn't parse date $args{'Value'} as a $args{'Format'}"); - - } + elsif ( $args{'Format'} =~ /^unknown$/i ) { + 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, $error) = Time::ParseDate::parsedate( + $args{'Value'}, + GMT => 1, + NOW => $now, + UK => RT->Config->Get('DateDayBeforeMonth'), + 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]; + + $RT::Logger->debug( + "RT::Date used Time::ParseDate to make '$args{'Value'}' $date\n" + ); + + return $self->Set( Format => 'unix', Value => $date); } - elsif ($args{'Format'} =~ /^unknown$/i) { - require Date::Parse; - #Convert it to an ISO format string - - my $date = Date::Parse::str2time($args{'Value'}); - - #This date has now been set to a date in the _local_ timezone. - #since ISO dates are known to be in GMT (for RT's purposes); - - $RT::Logger->debug("RT::Date used date::parse to make ".$args{'Value'} . " $date\n"); - - - return ($self->Set( Format => 'unix', Value => "$date")); - } else { - die "Unknown Date format: ".$args{'Format'}."\n"; + $RT::Logger->error( + "Unknown Date format: $args{'Format'}\n" + ); + return $self->Unix(0); } - - return($self->Unix()); + + return $self->Unix; } -# }}} +=head2 SetToNow + +Set the object's time to the current time. Takes no arguments +and returns unix time. -# {{{ sub SetToMidnight +=cut + +sub SetToNow { + return $_[0]->Unix(time); +} -=head2 SetToMidnight +=head2 SetToMidnight [Timezone => 'utc'] -Sets the date to midnight (at the beginning of the day) GMT +Sets the date to midnight (at the beginning of the day). Returns the unixtime at midnight. +Arguments: + +=over 4 + +=item Timezone + +Timezone context C, C or C. See also L. + +=back + =cut sub SetToMidnight { my $self = shift; - - use Time::Local; - my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday) = gmtime($self->Unix); - $self->Unix(timegm (0,0,0,$mday,$mon,$year,$wday,$yday)); - - return ($self->Unix); - - + my %args = ( Timezone => '', @_ ); + my $new = $self->Timelocal( + $args{'Timezone'}, + 0,0,0,($self->Localtime( $args{'Timezone'} ))[3..9] + ); + 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". -# {{{ sub SetToNow -sub SetToNow { - my $self = shift; - return($self->Set(Format => 'unix', Value => time)) -} -# }}} +=cut -# {{{ sub Diff +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 RT::Date object or the date in unixtime format as a string +Takes either an C object or the date in unixtime format as a string, +if nothing is specified uses the current time. -Returns the differnce between $self and that time as a number of seconds +Returns the differnce between the time in the current object and that time +as a number of seconds. Returns C if any of two compared values is +incorrect or not set. =cut sub Diff { my $self = shift; my $other = shift; - - if (ref($other) eq 'RT::Date') { - $other=$other->Unix; + $other = time unless defined $other; + if ( UNIVERSAL::isa( $other, 'RT::Date' ) ) { + $other = $other->Unix; } - return ($self->Unix - $other); -} -# }}} + return undef unless $other=~ /^\d+$/ && $other > 0; + + my $unix = $self->Unix; + return undef unless $unix > 0; -# {{{ sub DiffAsString + return $unix - $other; +} -=head2 sub DiffAsString +=head2 DiffAsString -Takes either an RT::Date object or the date in unixtime format as a string +Takes either an C object or the date in unixtime format as a string, +if nothing is specified uses the current time. -Returns the differnce between $self and that time as a number of seconds as -as string fit for human consumption +Returns the differnce between C<$self> and that time as a number of seconds as +a localized string fit for human consumption. Returns empty string if any of +two compared values is incorrect or not set. =cut sub DiffAsString { my $self = shift; - my $other = shift; - + my $diff = $self->Diff( @_ ); + return '' unless defined $diff; - if ($other < 1) { - return (""); - } - if ($self->Unix < 1) { - return(""); - } - my $diff = $self->Diff($other); - - return ($self->DurationAsString($diff)); + return $self->DurationAsString( $diff ); } -# }}} - -# {{{ sub DurationAsString =head2 DurationAsString -Takes a number of seconds. returns a string describing that duration +Takes a number of seconds. Returns a localized string describing +that duration. =cut -sub DurationAsString{ +sub DurationAsString { + my $self = shift; + my $duration = int shift; - my $self=shift; - my $duration = shift; - - my ($negative, $s); - - $negative = 'ago' if ($duration < 0); - - $duration = abs($duration); - - if($duration < $MINUTE) { - $s=$duration; - $string="sec"; - } elsif($duration < (2 * $HOUR)) { - $s = int($duration/$MINUTE); - $string="min"; - } elsif($duration < (2 * $DAY)) { - $s = int($duration/$HOUR); - $string="hours"; - } elsif($duration < (2 * $WEEK)) { - $s = int($duration/$DAY); - $string="days"; - } elsif($duration < (2 * $MONTH)) { - $s = int($duration/$WEEK); - $string="weeks"; - } elsif($duration < $YEAR) { - $s = int($duration/$MONTH); - $string="months"; - } else { - $s = int($duration/$YEAR); - $string="years"; + my ( $negative, $s, $time_unit ); + $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"); + } + + if ( $negative ) { + return $self->loc( "[_1] [_2] ago", $s, $time_unit ); + } + else { + return $self->loc( "[_1] [_2]", $s, $time_unit ); } - - return ("$s $string $negative"); } -# }}} +=head2 AgeAsString -# {{{ sub AgeAsString +Takes nothing. Returns a string that's the differnce between the +time in the object and now. -=head2 sub AgeAsString +=cut -Takes nothing +sub AgeAsString { return $_[0]->DiffAsString } -Returns a string that's the differnce between the time in the object and now + + +=head2 AsString + +Returns the object's time as a localized string with curent user's prefered +format and timezone. + +If the current user didn't choose prefered format then system wide setting is +used or L if the latter is not specified. See config option +C. =cut -sub AgeAsString { +sub AsString { my $self = shift; - return ($self->DiffAsString(time)); - } -# }}} + my %args = (@_); + + return $self->loc("Not set") unless $self->Unix > 0; -# {{{ sub AsString + my $format = RT->Config->Get( 'DateTimeFormat', $self->CurrentUser ) || 'DefaultFormat'; + $format = { Format => $format } unless ref $format; + %args = (%$format, %args); + + return $self->Get( Timezone => 'user', %args ); +} -=head2 sub AsString +=head2 GetWeekday DAY -Returns the object\'s time as a string with the current timezone. +Takes an integer day of week and returns a localized string for +that day of week. Valid values are from range 0-6, Note that B<0 +is sunday>. =cut -sub AsString { +sub GetWeekday { my $self = shift; - return ("Not set") if ($self->Unix <= 0); - - return (scalar(localtime($self->Unix))); + my $dow = shift; + + return $self->loc($DAYS_OF_WEEK[$dow]) + if $DAYS_OF_WEEK[$dow]; + return ''; } -# }}} -# {{{ sub AddSeconds +=head2 GetMonth MONTH -=head2 sub AddSeconds +Takes an integer month and returns a localized string for that month. +Valid values are from from range 0-11. + +=cut + +sub GetMonth { + my $self = shift; + my $mon = shift; + + return $self->loc($MONTHS[$mon]) + if $MONTHS[$mon]; + return ''; +} -Takes a number of seconds as a string +=head2 AddSeconds SECONDS -Returns the new time +Takes a number of seconds and returns the new unix time. + +Negative value can be used to substract seconds. =cut sub AddSeconds { my $self = shift; - my $delta = shift; + my $delta = shift or return $self->Unix; $self->Set(Format => 'unix', Value => ($self->Unix + $delta)); - + return ($self->Unix); - - } -# }}} - -# {{{ sub AddDays +=head2 AddDays [DAYS] -=head2 AddDays $DAYS +Adds C<24 hours * DAYS> to the current time. Adds one day when +no argument is specified. Negative value can be used to substract +days. -Adds 24 hours * $DAYS to the current time +Returns new unix time. =cut sub AddDays { my $self = shift; my $days = shift; - $self->AddSeconds($days * $DAY); - + $days = 1 unless defined $days; + return $self->AddSeconds( $days * $DAY ); } -# }}} +=head2 AddDay -# {{{ sub AddDay +Adds 24 hours to the current time. Returns new unix time. -=head2 AddDay +=cut -Adds 24 hours to the current time +sub AddDay { return $_[0]->AddSeconds($DAY) } + +=head2 AddMonth + +Adds one month to the current time. Returns new +unix time. =cut -sub AddDay { +sub AddMonth { my $self = shift; - $self->AddSeconds($DAY); + 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 + } -# {{{ sub Unix + my $new = $self->Timelocal($args{'Timezone'}, @localtime); + return $self->Unix($new); +} -=head2 sub Unix [unixtime] +=head2 Unix [unixtime] Optionally takes a date in unix seconds since the epoch format. Returns the number of seconds since the epoch @@ -375,62 +554,587 @@ Returns the number of seconds since the epoch =cut sub Unix { + my $self = shift; + $self->{'time'} = int(shift || 0) if @_; + return $self->{'time'}; +} + +=head2 DateTime + +Alias for L method. Arguments C and