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 %]