diff --git a/Bugzilla.pm b/Bugzilla.pm index abba18924ada792d9538351d237f11f34ad353ac..ba3e75d892f4df032f2280309b27928d8168398a 100644 --- a/Bugzilla.pm +++ b/Bugzilla.pm @@ -51,6 +51,7 @@ use Bugzilla::Flag; use File::Basename; use File::Spec::Functions; +use DateTime::TimeZone; use Safe; # This creates the request cache for non-mod_perl installations. @@ -463,6 +464,16 @@ sub hook_args { return $class->request_cache->{hook_args}; } +sub local_timezone { + my $class = shift; + + if (!defined $class->request_cache->{local_timezone}) { + $class->request_cache->{local_timezone} = + DateTime::TimeZone->new(name => 'local'); + } + return $class->request_cache->{local_timezone}; +} + sub request_cache { if ($ENV{MOD_PERL}) { require Apache2::RequestUtil; @@ -699,4 +710,10 @@ is unreadable or is not valid perl, we C<die>. If you are running inside a code hook (see L<Bugzilla::Hook>) this is how you get the arguments passed to the hook. +=item C<local_timezone> + +Returns the local timezone of the Bugzilla installation, +as a DateTime::TimeZone object. This detection is very time +consuming, so we cache this information for future references. + =back diff --git a/Bugzilla/Install.pm b/Bugzilla/Install.pm index c70f8a8bc47fdead2a989cdb00f1b7a76ec671df..3d382add8b494eaaff5d21a40c514d5476d42a0e 100644 --- a/Bugzilla/Install.pm +++ b/Bugzilla/Install.pm @@ -62,7 +62,9 @@ sub SETTINGS { default => ${Bugzilla->languages}[0] }, # 2007-07-02 altlist@gmail.com -- Bug 225731 quote_replies => { options => ['quoted_reply', 'simple_reply', 'off'], - default => "quoted_reply" } + default => "quoted_reply" }, + # 2008-08-27 LpSolit@gmail.com -- Bug 182238 + timezone => { subclass => 'Timezone', default => 'local' }, } }; diff --git a/Bugzilla/Install/Requirements.pm b/Bugzilla/Install/Requirements.pm index 2216d963dd37f47aef7efa01c13f40d3c1c5aafa..9ff26246e723269f7b8f19b95a3fe46c9e0273be 100644 --- a/Bugzilla/Install/Requirements.pm +++ b/Bugzilla/Install/Requirements.pm @@ -69,6 +69,12 @@ sub REQUIRED_MODULES { module => 'Date::Format', version => '2.21' }, + # 0.28 fixed some important bugs in DateTime. + { + package => 'DateTime', + module => 'DateTime', + version => '0.28' + }, { package => 'PathTools', module => 'File::Spec', diff --git a/Bugzilla/Template.pm b/Bugzilla/Template.pm index 8a322fae58708bb49310ab588a7515eeea2b46b4..76ff9f11d1b38e4f1a0af7c253414f58c245b80e 100644 --- a/Bugzilla/Template.pm +++ b/Bugzilla/Template.pm @@ -604,7 +604,15 @@ sub create { }, # Format a time for display (more info in Bugzilla::Util) - time => \&Bugzilla::Util::format_time, + time => [ sub { + my ($context, $format) = @_; + return sub { + my $time = shift; + return format_time($time, $format); + }; + }, + 1 + ], # Bug 120030: Override html filter to obscure the '@' in user # visible strings. diff --git a/Bugzilla/User.pm b/Bugzilla/User.pm index ccee85755b50a2899e3644df4fdf4804f654d791..7f59b749c15467e53e2e8ee467a4fabf5ac27eba 100644 --- a/Bugzilla/User.pm +++ b/Bugzilla/User.pm @@ -50,6 +50,7 @@ use Bugzilla::Classification; use Bugzilla::Field; use Scalar::Util qw(blessed); +use DateTime::TimeZone; use base qw(Bugzilla::Object Exporter); @Bugzilla::User::EXPORT = qw(is_available_username @@ -349,6 +350,22 @@ sub settings { return $self->{'settings'}; } +sub timezone { + my $self = shift; + + if (!defined $self->{timezone}) { + my $tz = $self->settings->{timezone}->{value}; + if ($tz eq 'local') { + # The user wants the local timezone of the server. + $self->{timezone} = Bugzilla->local_timezone; + } + else { + $self->{timezone} = DateTime::TimeZone->new(name => $tz); + } + } + return $self->{timezone}; +} + sub flush_queries_cache { my $self = shift; @@ -1884,6 +1901,11 @@ value - the value of this setting for this user. Will be the same is_default - a boolean to indicate whether the user has chosen to make a preference for themself or use the site default. +=item C<timezone> + +Returns the timezone used to display dates and times to the user, +as a DateTime::TimeZone object. + =item C<groups> Returns an arrayref of L<Bugzilla::Group> objects representing diff --git a/Bugzilla/User/Setting/Timezone.pm b/Bugzilla/User/Setting/Timezone.pm new file mode 100644 index 0000000000000000000000000000000000000000..d75b1113b66105995af6ee636c52cb6235e2e37d --- /dev/null +++ b/Bugzilla/User/Setting/Timezone.pm @@ -0,0 +1,71 @@ +# -*- Mode: perl; indent-tabs-mode: nil -*- +# +# The contents of this file are subject to the Mozilla Public +# License Version 1.1 (the "License"); you may not use this file +# except in compliance with the License. You may obtain a copy of +# the License at http://www.mozilla.org/MPL/ +# +# Software distributed under the License is distributed on an "AS +# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or +# implied. See the License for the specific language governing +# rights and limitations under the License. +# +# The Original Code is the Bugzilla Bug Tracking System. +# +# The Initial Developer of the Original Code is FrГ©dГ©ric Buclin. +# Portions created by FrГ©dГ©ric Buclin are Copyright (c) 2008 FrГ©dГ©ric Buclin. +# All rights reserved. +# +# Contributor(s): FrГ©dГ©ric Buclin <LpSolit@gmail.com> + +package Bugzilla::User::Setting::Timezone; + +use strict; + +use DateTime::TimeZone; + +use base qw(Bugzilla::User::Setting); + +use Bugzilla::Constants; + +sub legal_values { + my ($self) = @_; + + return $self->{'legal_values'} if defined $self->{'legal_values'}; + + my @timezones = DateTime::TimeZone->all_names; + # Remove old formats, such as CST6CDT, EST, EST5EDT. + @timezones = grep { $_ =~ m#.+/.+#} @timezones; + # Append 'local' to the list, which will use the timezone + # given by the server. + push(@timezones, 'local'); + + return $self->{'legal_values'} = \@timezones; +} + +1; + +__END__ + +=head1 NAME + +Bugzilla::User::Setting::Timezone - Object for a user preference setting for desired timezone + +=head1 DESCRIPTION + +Timezone.pm extends Bugzilla::User::Setting and implements a class specialized for +setting the desired timezone. + +=head1 METHODS + +=over + +=item C<legal_values()> + +Description: Returns all legal timezones + +Params: none + +Returns: A reference to an array containing the names of all legal timezones + +=back diff --git a/Bugzilla/Util.pm b/Bugzilla/Util.pm index 1e7dbf8d1bf9e2c1edc6f7b986fdcfdedacc8901..a8c595a05ef675965e4f7b995508351e704794be 100644 --- a/Bugzilla/Util.pm +++ b/Bugzilla/Util.pm @@ -50,6 +50,7 @@ use Bugzilla::Constants; use Date::Parse; use Date::Format; +use DateTime; use Text::Wrap; # This is from the perlsec page, slightly modified to remove a warning @@ -398,36 +399,39 @@ sub wrap_hard { sub format_time { my ($date, $format) = @_; - # If $format is undefined, try to guess the correct date format. - my $show_timezone; + # If $format is undefined, try to guess the correct date format. if (!defined($format)) { if ($date =~ m/^(\d{4})[-\.](\d{2})[-\.](\d{2}) (\d{2}):(\d{2})(:(\d{2}))?$/) { my $sec = $7; if (defined $sec) { - $format = "%Y-%m-%d %T"; + $format = "%Y-%m-%d %T %Z"; } else { - $format = "%Y-%m-%d %R"; + $format = "%Y-%m-%d %R %Z"; } } else { - # Default date format. See Date::Format for other formats available. - $format = "%Y-%m-%d %R"; + # Default date format. See DateTime for other formats available. + $format = "%Y-%m-%d %R %Z"; } - # By default, we want the timezone to be displayed. - $show_timezone = 1; } - else { - # Search for %Z or %z, meaning we want the timezone to be displayed. - # Till bug 182238 gets fixed, we assume Bugzilla->params->{'timezone'} - # is used. - $show_timezone = ($format =~ s/\s?%Z$//i); - } - - # str2time($date) is undefined if $date has an invalid date format. - my $time = str2time($date); - if (defined $time) { - $date = time2str($format, $time); - $date .= " " . Bugzilla->params->{'timezone'} if $show_timezone; + # strptime($date) returns an empty array if $date has an invalid date format. + my @time = strptime($date); + + if (scalar @time) { + # strptime() counts years from 1900, and months from 0 (January). + # We have to fix both values. + my $dt = DateTime->new({year => 1900 + $time[5], + month => ++$time[4], + day => $time[3], + hour => $time[2], + minute => $time[1], + second => $time[0], + # Use the timezone specified by the server. + time_zone => Bugzilla->local_timezone}); + + # Now display the date using the user's timezone. + $dt->set_time_zone(Bugzilla->user->timezone); + $date = $dt->strftime($format); } else { # Don't let invalid (time) strings to be passed to templates! diff --git a/buglist.cgi b/buglist.cgi index c06d40d229e689a004afce892881e5432151adfb..3baeee291b33c5d53f338ca495ac6368ba00f18a 100755 --- a/buglist.cgi +++ b/buglist.cgi @@ -1183,7 +1183,7 @@ if (scalar(@bugowners) > 1 && Bugzilla->user->in_group('editbugs')) { $vars->{'splitheader'} = $cgi->cookie('SPLITHEADER') ? 1 : 0; $vars->{'quip'} = GetQuip(); -$vars->{'currenttime'} = time(); +$vars->{'currenttime'} = localtime(time()); # The following variables are used when the user is making changes to multiple bugs. if ($dotweak && scalar @bugs) { diff --git a/chart.cgi b/chart.cgi index 70eeb814a3c11504df511c4f8abf0f0a24a44ad1..25d5b446decb5cae211f8a92bf8be2f520cf5283 100755 --- a/chart.cgi +++ b/chart.cgi @@ -292,7 +292,7 @@ sub wrap { # We create a Chart object so we can validate the parameters my $chart = new Bugzilla::Chart($cgi); - $vars->{'time'} = time(); + $vars->{'time'} = localtime(time()); $vars->{'imagebase'} = $cgi->canonicalise_query( "action", "action-wrap", "ctype", "format", "width", "height"); diff --git a/report.cgi b/report.cgi index 2b9cb61ad00d6056367523bcf689ab8d5db59979..61670417cab798bf6b38d91193d0ab382005647e 100755 --- a/report.cgi +++ b/report.cgi @@ -226,7 +226,7 @@ foreach my $tbl (@tbl_names) { $vars->{'col_field'} = $col_field; $vars->{'row_field'} = $row_field; $vars->{'tbl_field'} = $tbl_field; -$vars->{'time'} = time(); +$vars->{'time'} = localtime(time()); $vars->{'col_names'} = \@col_names; $vars->{'row_names'} = \@row_names; diff --git a/t/007util.t b/t/007util.t index 18d58148bd3288012f5c035ac74cfd0e32acd799..5fc02d52ccb89f4905b014ce4c04c41c46c47657 100644 --- a/t/007util.t +++ b/t/007util.t @@ -33,11 +33,13 @@ BEGIN { use_ok(Bugzilla::Util); } -# We need to override Bugzilla->params so we can get an expected value when -# Bugzilla::Util::format_time() calls ask for the 'timezone' parameter. -# This will also prevent the tests from failing on site that do not have a -# data/params file containing 'timezone' yet. -Bugzilla->params->{'timezone'} = "TEST"; +# We need to override user preferences so we can get an expected value when +# Bugzilla::Util::format_time() calls ask for the 'timezone' user preference. +Bugzilla->user->settings->{'timezone'}->{'value'} = "local"; + +# We need to know the local timezone for the date chosen in our tests. +# Below, tests are run against Nov. 24, 2002. +my $tz = Bugzilla->local_timezone->short_name_for_datetime(DateTime->new(year => 2002, month => 11, day => 24)); # we don't test the taint functions since that's going to take some more work. # XXX: test taint functions @@ -58,7 +60,7 @@ is(lsearch(\@list,'kiwi'),-1,'lsearch 3 (missing item)'); is(trim(" fg<*\$%>+=~~ "),'fg<*$%>+=~~','trim()'); #format_time(); -is(format_time("2002.11.24 00:05"),'2002-11-24 00:05 TEST','format_time("2002.11.24 00:05")'); -is(format_time("2002.11.24 00:05:56"),'2002-11-24 00:05:56 TEST','format_time("2002.11.24 00:05:56")'); +is(format_time("2002.11.24 00:05"), "2002-11-24 00:05 $tz",'format_time("2002.11.24 00:05") is ' . format_time("2002.11.24 00:05")); +is(format_time("2002.11.24 00:05:56"), "2002-11-24 00:05:56 $tz",'format_time("2002.11.24 00:05:56")'); is(format_time("2002.11.24 00:05:56", "%Y-%m-%d %R"), '2002-11-24 00:05', 'format_time("2002.11.24 00:05:56", "%Y-%m-%d %R") (with no timezone)'); -is(format_time("2002.11.24 00:05:56", "%Y-%m-%d %R %Z"), '2002-11-24 00:05 TEST', 'format_time("2002.11.24 00:05:56", "%Y-%m-%d %R %Z") (with timezone)'); +is(format_time("2002.11.24 00:05:56", "%Y-%m-%d %R %Z"), "2002-11-24 00:05 $tz", 'format_time("2002.11.24 00:05:56", "%Y-%m-%d %R %Z") (with timezone)'); diff --git a/template/en/default/global/setting-descs.none.tmpl b/template/en/default/global/setting-descs.none.tmpl index 10290ad661076b5c9796eabc04e3724d47d8acaf..bcd476d04918df2a07fa9c731454fa19705a9090 100644 --- a/template/en/default/global/setting-descs.none.tmpl +++ b/template/en/default/global/setting-descs.none.tmpl @@ -43,5 +43,7 @@ "quote_replies" => "Quote the associated comment when you click on its reply link", "quoted_reply" => "Quote the full comment", "simple_reply" => "Reference the comment number only", + "timezone" => "Timezone used to display dates and times", + "local" => "Same as the server", } %] diff --git a/template/en/default/list/list.html.tmpl b/template/en/default/list/list.html.tmpl index 512201c27cbd4a809dbe67e296bca5e1f017d461..37fd0a638d4138bb0bcc6495ccca09522ff30ef8 100644 --- a/template/en/default/list/list.html.tmpl +++ b/template/en/default/list/list.html.tmpl @@ -54,11 +54,7 @@ <div class="bz_query_head" align="center"> <span class="bz_query_timestamp"> - [% IF Param('timezone') %] - <b>[% time2str("%a %b %e %Y %T %Z", currenttime, Param('timezone')) %]</b><br> - [% ELSE %] - <b>[% time2str("%a %b %e %Y %T", currenttime) %]</b><br> - [% END %] + <b>[% currenttime FILTER time('%a %b %e %Y %T %Z') FILTER html %]</b><br> </span> [% IF debug %] diff --git a/template/en/default/reports/chart.html.tmpl b/template/en/default/reports/chart.html.tmpl index 06a8d791fcdaf467b788d2fd62a608182db92555..e14744d311a3e57505f210c3b1714bf5827e05bb 100644 --- a/template/en/default/reports/chart.html.tmpl +++ b/template/en/default/reports/chart.html.tmpl @@ -25,9 +25,11 @@ height = 350 %] +[% time = time FILTER time('%Y-%m-%d %H:%M:%S') FILTER html %] + [% PROCESS global/header.html.tmpl title = "Chart" - header_addl_info = time2str("%Y-%m-%d %H:%M:%S", time) + header_addl_info = time %] <div align="center"> diff --git a/template/en/default/reports/report.html.tmpl b/template/en/default/reports/report.html.tmpl index b8e52219acdfeec0735f028447fea1f27f3e59d2..37af0b3006c0e9ad453c24a61f12f5eee2fbd848 100644 --- a/template/en/default/reports/report.html.tmpl +++ b/template/en/default/reports/report.html.tmpl @@ -66,6 +66,8 @@ [% col_field_disp FILTER html %] [% END %] +[% time = time FILTER time('%Y-%m-%d %H:%M:%S') FILTER html %] + [% PROCESS global/header.html.tmpl style = " .t1 { background-color: #ffffff } /* white */ @@ -74,7 +76,7 @@ .t4 { background-color: #c3d3ed } /* darker blue */ .ttotal { background-color: #cfffdf } /* light green */ " - header_addl_info = time2str("%Y-%m-%d %H:%M:%S", time) + header_addl_info = time %] [% IF debug %]