X-Git-Url: http://git.freeside.biz/gitweb/?a=blobdiff_plain;f=rt%2Flib%2FRT%2FDate.pm;h=52bdc01dfe1416309efc42ff82380cca8cadfe55;hb=0ea23112cfa0d82738b0f08d60d90579721b7524;hp=e51a775408cc245237961a96fa949eadb8735a12;hpb=fc6209f398899f0211cfcedeb81a3cd65e04a941;p=freeside.git diff --git a/rt/lib/RT/Date.pm b/rt/lib/RT/Date.pm index e51a77540..52bdc01df 100644 --- a/rt/lib/RT/Date.pm +++ b/rt/lib/RT/Date.pm @@ -2,7 +2,7 @@ # # COPYRIGHT: # -# This software is Copyright (c) 1996-2011 Best Practical Solutions, LLC +# This software is Copyright (c) 1996-2014 Best Practical Solutions, LLC # # # (Except where explicitly superseded by other copyright notices) @@ -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,7 +164,7 @@ sub Set { @_ ); - return $self->Unix(0) unless $args{'Value'}; + return $self->Unix(0) unless $args{'Value'} && $args{'Value'} =~ /\S/; if ( $args{'Format'} =~ /^unix$/i ) { return $self->Unix( $args{'Value'} ); @@ -208,7 +208,7 @@ sub Set { # 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 +216,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]; @@ -273,6 +280,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, @@ -467,7 +509,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 +522,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. @@ -545,6 +612,10 @@ sub Get my $self = shift; my %args = (Format => 'ISO', @_); my $formatter = $args{'Format'}; + unless ( $self->ValidFormatter($formatter) ) { + RT->Logger->warning("Invalid date formatter '$formatter', falling back to ISO"); + $formatter = 'ISO'; + } $formatter = 'ISO' unless $self->can($formatter); return $self->$formatter( %args ); } @@ -583,6 +654,20 @@ sub Formatters return @FORMATTERS; } +=head3 ValidFormatter FORMAT + +Returns a true value if C is a known formatter. Otherwise returns +false. + +=cut + +sub ValidFormatter { + my $self = shift; + my $format = shift; + return (grep { $_ eq $format } $self->Formatters and $self->can($format)) + ? 1 : 0; +} + =head3 DefaultFormat =cut @@ -626,17 +711,33 @@ sub DefaultFormat } } +=head2 LocaleObj + +Returns the L object representing the current user's locale. + +=cut + +sub LocaleObj { + my $self = shift; + + my $lang = $self->CurrentUser->UserObj->Lang; + unless ($lang) { + require I18N::LangTags::Detect; + $lang = ( I18N::LangTags::Detect::detect(), 'en' )[0]; + } + + return DateTime::Locale->load($lang); +} + =head3 LocalizedDateTime Returns date and time as string, with user localization. Supports arguments: C and C which may contains date and -time format as specified in DateTime::Locale (default to full_date_format and +time format as specified in L (default to full_date_format and medium_time_format), C and C which may be set to 0 if you want full Day/Month names instead of abbreviated ones. -Require optionnal DateTime::Locale module. - =cut sub LocalizedDateTime @@ -645,32 +746,21 @@ sub LocalizedDateTime my %args = ( Date => 1, Time => 1, Timezone => '', - DateFormat => 'date_format_full', - TimeFormat => 'time_format_medium', + DateFormat => '', + TimeFormat => '', AbbrDay => 1, AbbrMonth => 1, @_, ); - return $self->loc("DateTime module missing") unless ( eval 'use DateTime qw(); 1;' ); - return $self->loc("DateTime::Locale module missing") unless ( eval 'use DateTime::Locale qw(); 1;' ); - return $self->loc("DateTime doesn't support format_cldr, you must upgrade to use this feature") - unless can DateTime::('format_cldr'); - - - my $date_format = $args{'DateFormat'}; - my $time_format = $args{'TimeFormat'}; + # Require valid names for the format methods + my $date_format = $args{DateFormat} =~ /^\w+$/ + ? $args{DateFormat} : 'date_format_full'; - my $lang = $self->CurrentUser->UserObj->Lang; - unless ($lang) { - require I18N::LangTags::Detect; - $lang = ( I18N::LangTags::Detect::detect(), 'en' )[0]; - } - + my $time_format = $args{TimeFormat} =~ /^\w+$/ + ? $args{TimeFormat} : 'time_format_medium'; - my $formatter = DateTime::Locale->load($lang); - return $self->loc("DateTime::Locale doesn't support date_format_full, you must upgrade to use this feature") - unless $formatter->can('date_format_full'); + my $formatter = $self->LocaleObj; $date_format = $formatter->$date_format; $time_format = $formatter->$time_format; $date_format =~ s/EEEE/EEE/g if ( $args{'AbbrDay'} ); @@ -683,7 +773,7 @@ sub LocalizedDateTime # FIXME : another way to call this module without conflict with local # DateTime method? - my $dt = new DateTime::( locale => $lang, + my $dt = DateTime::->new( locale => $formatter, time_zone => $tz, year => $year, month => $mon, @@ -813,7 +903,7 @@ sub RFC2822 { my ($date, $time) = ('',''); $date .= "$DAYS_OF_WEEK[$wday], " if $args{'DayOfWeek'} && $args{'Date'}; - $date .= "$mday $MONTHS[$mon] $year" if $args{'Date'}; + $date .= sprintf("%02d %s %04d", $mday, $MONTHS[$mon], $year) if $args{'Date'}; if ( $args{'Time'} ) { $time .= sprintf("%02d:%02d", $hour, $min); @@ -878,21 +968,20 @@ sub iCal { Date => 1, Time => 1, @_, ); - my ($sec,$min,$hour,$mday,$mon,$year,$wday,$ydaym,$isdst,$offset) = - $self->Localtime( 'utc' ); - - #the month needs incrementing, as gmtime returns 0-11 - $mon++; my $res; if ( $args{'Date'} && !$args{'Time'} ) { - $res = sprintf( '%04d%02d%02d', $year, $mon, $mday ); - } - elsif ( !$args{'Date'} && $args{'Time'} ) { + my (undef, undef, undef, $mday, $mon, $year) = + $self->Localtime( 'user' ); + $res = sprintf( '%04d%02d%02d', $year, $mon+1, $mday ); + } elsif ( !$args{'Date'} && $args{'Time'} ) { + my ($sec, $min, $hour) = + $self->Localtime( 'utc' ); $res = sprintf( 'T%02d%02d%02dZ', $hour, $min, $sec ); - } - else { - $res = sprintf( '%04d%02d%02dT%02d%02d%02dZ', $year, $mon, $mday, $hour, $min, $sec ); + } else { + my ($sec, $min, $hour, $mday, $mon, $year) = + $self->Localtime( 'utc' ); + $res = sprintf( '%04d%02d%02dT%02d%02d%02dZ', $year, $mon+1, $mday, $hour, $min, $sec ); } return $res; } @@ -1022,6 +1111,12 @@ If both server's and user's timezone names are undefined returns 'UTC'. sub Timezone { my $self = shift; + + if (@_ == 0) { + Carp::carp "RT::Date->Timezone is a setter only"; + return undef; + } + my $context = lc(shift); $context = 'utc' unless $context =~ /^(?:utc|server|user)$/i; @@ -1040,9 +1135,6 @@ sub Timezone { } -eval "require RT::Date_Vendor"; -die $@ if ($@ && $@ !~ qr{^Can't locate RT/Date_Vendor.pm}); -eval "require RT::Date_Local"; -die $@ if ($@ && $@ !~ qr{^Can't locate RT/Date_Local.pm}); +RT::Base->_ImportOverlays(); 1;