+sub RFC2616 {
+ my $self = shift;
+ my %args = ( Date => 1, Time => 1,
+ @_,
+ Timezone => 'utc',
+ Seconds => 1, DayOfWeek => 1,
+ );
+
+ my $res = $self->RFC2822( %args );
+ $res =~ s/\s*[+-]\d\d\d\d$/ GMT/ if $args{'Time'};
+ return $res;
+}
+
+=head4 iCal
+
+Returns the object's date and time in iCalendar format.
+If only date requested then user's timezone is used, otherwise
+it's UTC.
+
+Supports arguments: C<Date> and C<Time>.
+See L</Output formatters> for description of arguments.
+
+=cut
+
+sub iCal {
+ my $self = shift;
+ my %args = (
+ Date => 1, Time => 1,
+ @_,
+ );
+
+ my $res;
+ if ( $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 {
+ 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;
+}
+
+# it's been added by mistake in 3.8.0
+sub iCalDate { return (shift)->iCal( Time => 0, @_ ) }
+
+sub _SplitOffset {
+ my ($self, $offset) = @_;
+ my $sign = $offset < 0? '-': '+';
+ $offset = int( (abs $offset) / 60 + 0.001 );
+ my $mins = $offset % 60;
+ my $hours = int( $offset/60 + 0.001 );
+ return $sign, $hours, $mins;
+}
+
+=head2 Timezones handling
+
+=head3 Localtime $context [$time]
+
+Takes one mandatory argument C<$context>, which determines whether
+we want "user local", "system" or "UTC" time. Also, takes optional
+argument unix C<$time>, default value is the current unix time.
+
+Returns object's date and time in the format provided by perl's
+builtin functions C<localtime> and C<gmtime> with two exceptions:
+
+=over
+
+=item 1)
+
+"Year" is a four-digit year, rather than "years since 1900"
+
+=item 2)
+
+The last element of the array returned is C<offset>, which
+represents timezone offset against C<UTC> in seconds.
+
+=back
+
+=cut
+
+sub Localtime
+{
+ my $self = shift;
+ my $tz = $self->Timezone(shift);
+
+ my $unix = shift || $self->Unix;
+ $unix = 0 unless $unix >= 0;
+
+ my @local;
+ if ($tz eq 'UTC') {
+ @local = gmtime($unix);
+ } else {
+ {
+ local $ENV{'TZ'} = $tz;
+ ## Using POSIX::tzset fixes a bug where the TZ environment variable
+ ## is cached.
+ POSIX::tzset();
+ @local = localtime($unix);
+ }
+ POSIX::tzset(); # return back previous value
+ }
+ $local[5] += 1900; # change year to 4+ digits format
+ my $offset = Time::Local::timegm_nocheck(@local) - $unix;
+ return @local, $offset;
+}
+
+=head3 Timelocal $context @time
+
+Takes argument C<$context>, which determines whether we should
+treat C<@time> as "user local", "system" or "UTC" time.
+
+C<@time> is array returned by L</Localtime> functions. Only first
+six elements are mandatory - $sec, $min, $hour, $mday, $mon and $year.
+You may pass $wday, $yday and $isdst, these are ignored.
+
+If you pass C<$offset> as ninth argument, it's used instead of
+C<$context>. It's done such way as code
+C<< $self->Timelocal('utc', $self->Localtime('server')) >> doesn't
+make much sense and most probably would produce unexpected
+results, so the method ignores 'utc' context and uses the offset
+returned by the L</Localtime> method.
+
+=cut
+
+sub Timelocal {
+ my $self = shift;
+ my $tz = shift;
+ if ( defined $_[9] ) {
+ return timegm(@_[0..5]) - $_[9];
+ } else {
+ $tz = $self->Timezone( $tz );
+ if ( $tz eq 'UTC' ) {
+ return Time::Local::timegm(@_[0..5]);
+ } else {
+ my $rv;
+ {
+ local $ENV{'TZ'} = $tz;
+ ## Using POSIX::tzset fixes a bug where the TZ environment variable
+ ## is cached.
+ POSIX::tzset();
+ $rv = Time::Local::timelocal(@_[0..5]);
+ };
+ POSIX::tzset(); # switch back to previouse value
+ return $rv;
+ }
+ }
+}
+
+
+=head3 Timezone $context
+
+Returns the timezone name for the specified context. C<$context>
+should be one of these values:
+
+=over
+
+=item C<user>
+
+The current user's Timezone value will be returned.
+
+=item C<server>
+
+The value of the C<Timezone> RT config option will be returned.
+
+=back
+
+For any other value of C<$context>, or if the specified context has no
+defined timezone, C<UTC> is returned.
+
+=cut
+
+sub Timezone {
+ my $self = shift;
+
+ if (@_ == 0) {
+ Carp::carp 'RT::Date->Timezone requires a context argument';
+ return undef;
+ }
+
+ my $context = lc(shift);
+
+ my $tz;
+ if( $context eq 'user' ) {
+ $tz = $self->CurrentUser->UserObj->Timezone;
+ } elsif( $context eq 'server') {
+ $tz = RT->Config->Get('Timezone');
+ } else {
+ $tz = 'UTC';
+ }
+ $tz ||= RT->Config->Get('Timezone') || 'UTC';
+ $tz = 'UTC' if lc $tz eq 'gmt';
+ return $tz;
+}
+
+=head3 IsSet
+
+Returns true if this Date is set in the database, otherwise returns a false value.
+
+This avoids needing to compare to 1970-01-01 in any of your code.
+
+=cut
+
+sub IsSet {
+ my $self = shift;
+ return $self->Unix ? 1 : 0;