Commit ac9090d0 authored by Max Kanat-Alexander's avatar Max Kanat-Alexander Committed by Justin Wood

Bug 545299, XML-RPC WebService should take and return dates and times in UTC. POD

parents b30aeba0 9a9405da
...@@ -21,6 +21,8 @@ package Bugzilla::WebService; ...@@ -21,6 +21,8 @@ package Bugzilla::WebService;
use strict; use strict;
use Date::Parse; use Date::Parse;
use XMLRPC::Lite; use XMLRPC::Lite;
use Bugzilla::Util qw(datetime_from);
use Scalar::Util qw(blessed)
# Used by the JSON-RPC server to convert incoming date fields apprpriately. # Used by the JSON-RPC server to convert incoming date fields apprpriately.
use constant DATE_FIELDS => {}; use constant DATE_FIELDS => {};
...@@ -36,21 +38,24 @@ sub login_exempt { ...@@ -36,21 +38,24 @@ sub login_exempt {
sub type { sub type {
my ($self, $type, $value) = @_; my ($self, $type, $value) = @_;
if ($type eq 'dateTime') { if ($type eq 'dateTime') {
$value = datetime_format($value); $value = $self->datetime_format_outbound($value);
} }
return XMLRPC::Data->type($type)->value($value); return XMLRPC::Data->type($type)->value($value);
} }
sub datetime_format { sub datetime_format_outbound {
my ($date_string) = @_; my ($self, $date) = @_;
my $time = str2time($date_string); my $time = $date;
my ($sec, $min, $hour, $mday, $mon, $year) = localtime $time; if (blessed($date)) {
# This format string was stolen from SOAP::Utils->format_datetime, # We expect this to mean we were sent a datetime object
# which doesn't work but which has almost the right format string. $time->set_time_zone('UTC');
my $iso_datetime = sprintf('%d%02d%02dT%02d:%02d:%02d', } else {
$year + 1900, $mon + 1, $mday, $hour, $min, $sec); # We always send our time in UTC, for consistency.
return $iso_datetime; # passed in value is likely a string, create a datetime object
$time = datetime_from($date, 'UTC');
}
return $iso_datetime = $time->iso8601();
} }
......
...@@ -21,6 +21,7 @@ package Bugzilla::WebService::Bugzilla; ...@@ -21,6 +21,7 @@ package Bugzilla::WebService::Bugzilla;
use strict; use strict;
use base qw(Bugzilla::WebService); use base qw(Bugzilla::WebService);
use Bugzilla::Constants; use Bugzilla::Constants;
use Bugzilla::Util qw(datetime_from);
use DateTime; use DateTime;
...@@ -49,32 +50,27 @@ sub extensions { ...@@ -49,32 +50,27 @@ sub extensions {
sub timezone { sub timezone {
my $self = shift; my $self = shift;
my $offset = Bugzilla->local_timezone->offset_for_datetime(DateTime->now()); # All Webservices return times in UTC; Use UTC here for backwards compat.
$offset = (($offset / 60) / 60) * 100; return { timezone => $self->type('string', "+0000") };
$offset = sprintf('%+05d', $offset);
return { timezone => $self->type('string', $offset) };
} }
sub time { sub time {
my ($self) = @_; my ($self) = @_;
# All Webservices return times in UTC; Use UTC here for backwards compat.
# Hardcode values where appropriate
my $dbh = Bugzilla->dbh; my $dbh = Bugzilla->dbh;
my $db_time = $dbh->selectrow_array('SELECT LOCALTIMESTAMP(0)'); my $db_time = $dbh->selectrow_array('SELECT LOCALTIMESTAMP(0)');
$db_time = datetime_from($db_time, 'UTC');
my $now_utc = DateTime->now(); my $now_utc = DateTime->now();
my $tz = Bugzilla->local_timezone;
my $now_local = $now_utc->clone->set_time_zone($tz);
my $tz_offset = $tz->offset_for_datetime($now_local);
return { return {
db_time => $self->type('dateTime', $db_time), db_time => $self->type('dateTime', $db_time),
web_time => $self->type('dateTime', $now_local), web_time => $self->type('dateTime', $now_utc),
web_time_utc => $self->type('dateTime', $now_utc), web_time_utc => $self->type('dateTime', $now_utc),
tz_name => $self->type('string', $tz->name), tz_name => $self->type('string', 'UTC'),
tz_offset => $self->type('string', tz_offset => $self->type('string', '+0000'),
$tz->offset_as_string($tz_offset)), tz_short_name => $self->type('string', 'UTC'),
tz_short_name => $self->type('string',
$now_local->time_zone_short_name),
}; };
} }
...@@ -172,9 +168,7 @@ Use L</time> instead. ...@@ -172,9 +168,7 @@ Use L</time> instead.
=item B<Description> =item B<Description>
Returns the timezone of the server Bugzilla is running on. This is Returns the timezone that Bugzilla expects dates and times in.
important because all dates/times that the webservice interface
returns will be in this timezone.
=item B<Params> (none) =item B<Params> (none)
...@@ -183,12 +177,21 @@ returns will be in this timezone. ...@@ -183,12 +177,21 @@ returns will be in this timezone.
A hash with a single item, C<timezone>, that is the timezone offset as a A hash with a single item, C<timezone>, that is the timezone offset as a
string in (+/-)XXXX (RFC 2822) format. string in (+/-)XXXX (RFC 2822) format.
=item B<History>
=over
=item As of Bugzilla B<3.6>, the timezone returned is always C<+0000>
(the UTC timezone).
=back
=back =back
=item C<time> =item C<time>
B<UNSTABLE> B<EXPERIMENTAL>
=over =over
...@@ -207,8 +210,8 @@ A struct with the following items: ...@@ -207,8 +210,8 @@ A struct with the following items:
=item C<db_time> =item C<db_time>
C<dateTime> The current time in Bugzilla's B<local time zone>, according C<dateTime> The current time in UTC, according to the Bugzilla
to the Bugzilla I<database server>. I<database server>.
Note that Bugzilla assumes that the database and the webserver are running Note that Bugzilla assumes that the database and the webserver are running
in the same time zone. However, if the web server and the database server in the same time zone. However, if the web server and the database server
...@@ -217,8 +220,8 @@ rely on for doing searches and other input to the WebService. ...@@ -217,8 +220,8 @@ rely on for doing searches and other input to the WebService.
=item C<web_time> =item C<web_time>
C<dateTime> This is the current time in Bugzilla's B<local time zone>, C<dateTime> This is the current time in UTC, according to Bugzilla's
according to Bugzilla's I<web server>. I<web server>.
This might be different by a second from C<db_time> since this comes from This might be different by a second from C<db_time> since this comes from
a different source. If it's any more different than a second, then there is a different source. If it's any more different than a second, then there is
...@@ -227,26 +230,23 @@ rely on the C<db_time>, not the C<web_time>. ...@@ -227,26 +230,23 @@ rely on the C<db_time>, not the C<web_time>.
=item C<web_time_utc> =item C<web_time_utc>
The same as C<web_time>, but in the B<UTC> time zone instead of the local Identical to C<web_time>. (Exists only for backwards-compatibility with
time zone. versions of Bugzilla before 3.6.)
=item C<tz_name> =item C<tz_name>
C<string> The long name of the time zone that the Bugzilla web server is C<string> The literal string C<UTC>. (Exists only for backwards-compatibility
in. Will usually look something like: C<America/Los Angeles> with versions of Bugzilla before 3.6.)
=item C<tz_short_name> =item C<tz_short_name>
C<string> The "short name" of the time zone that the Bugzilla web server C<string> The literal string C<UTC>. (Exists only for backwards-compatibility
is in. This should only be used for display, and not relied on for your with versions of Bugzilla before 3.6.)
programs, because different time zones can have the same short name.
(For example, there are two C<EST>s.)
This will look something like: C<PST>.
=item C<tz_offset> =item C<tz_offset>
C<string> The timezone offset as a string in (+/-)XXXX (RFC 2822) format. C<string> The literal string C<+0000>. (Exists only for backwards-compatibility
with versions of Bugzilla before 3.6.)
=back =back
...@@ -256,6 +256,10 @@ C<string> The timezone offset as a string in (+/-)XXXX (RFC 2822) format. ...@@ -256,6 +256,10 @@ C<string> The timezone offset as a string in (+/-)XXXX (RFC 2822) format.
=item Added in Bugzilla B<3.4>. =item Added in Bugzilla B<3.4>.
=item As of Bugzilla B<3.6>, this method returns all data as though the server
were in the UTC timezone, instead of returning information in the server's
local timezone.
=back =back
=back =back
......
...@@ -19,6 +19,7 @@ package Bugzilla::WebService::Server; ...@@ -19,6 +19,7 @@ package Bugzilla::WebService::Server;
use strict; use strict;
use Bugzilla::Error; use Bugzilla::Error;
use Bugzilla::Util qw(datetime_from);
sub handle_login { sub handle_login {
my ($self, $class, $method, $full_method) = @_; my ($self, $class, $method, $full_method) = @_;
...@@ -29,4 +30,12 @@ sub handle_login { ...@@ -29,4 +30,12 @@ sub handle_login {
Bugzilla->login(); Bugzilla->login();
} }
sub datetime_format_inbound {
my ($self, $time) = @_;
my $converted = datetime_from($time, Bugzilla->local_timezone);
$time = $converted->ymd() . ' ' . $converted->hms();
return $time
}
1; 1;
...@@ -27,7 +27,6 @@ use base qw(JSON::RPC::Server::CGI Bugzilla::WebService::Server); ...@@ -27,7 +27,6 @@ use base qw(JSON::RPC::Server::CGI Bugzilla::WebService::Server);
use Bugzilla::Error; use Bugzilla::Error;
use Bugzilla::WebService::Constants; use Bugzilla::WebService::Constants;
use Bugzilla::WebService::Util qw(taint_data); use Bugzilla::WebService::Util qw(taint_data);
use Bugzilla::Util qw(datetime_from);
sub new { sub new {
my $class = shift; my $class = shift;
...@@ -77,20 +76,17 @@ sub type { ...@@ -77,20 +76,17 @@ sub type {
} }
elsif ($type eq 'dateTime') { elsif ($type eq 'dateTime') {
# ISO-8601 "YYYYMMDDTHH:MM:SS" with a literal T # ISO-8601 "YYYYMMDDTHH:MM:SS" with a literal T
$retval = $self->datetime_format($value); $retval = $self->datetime_format_outbound($value);
} }
# XXX Will have to implement base64 if Bugzilla starts using it. # XXX Will have to implement base64 if Bugzilla starts using it.
return $retval; return $retval;
} }
sub datetime_format { sub datetime_format_outbound {
my ($self, $date_string) = @_; my $self = shift;
# YUI expects ISO8601 in UTC time; including TZ specifier
# YUI expects ISO8601 in UTC time; uncluding TZ specifier return $self->SUPER::datetime_format_outbound(@_) . 'Z';
my $time = datetime_from($date_string, 'UTC');
my $iso_datetime = $time->iso8601() . 'Z';
return $iso_datetime;
} }
...@@ -192,10 +188,10 @@ sub _argument_type_check { ...@@ -192,10 +188,10 @@ sub _argument_type_check {
my $value = $params->{$field}; my $value = $params->{$field};
if (ref $value eq 'ARRAY') { if (ref $value eq 'ARRAY') {
$params->{$field} = $params->{$field} =
[ map { $self->_bz_convert_datetime($_) } @$value ]; [ map { $self->datetime_format_inbound($_) } @$value ];
} }
else { else {
$params->{$field} = $self->_bz_convert_datetime($value); $params->{$field} = $self->datetime_format_inbound($value);
} }
} }
} }
...@@ -220,14 +216,6 @@ sub _argument_type_check { ...@@ -220,14 +216,6 @@ sub _argument_type_check {
return $params; return $params;
} }
sub _bz_convert_datetime {
my ($self, $time) = @_;
my $converted = datetime_from($time, Bugzilla->local_timezone);
$time = $converted->ymd() . ' ' . $converted->hms();
return $time
}
sub handle_login { sub handle_login {
my $self = shift; my $self = shift;
......
...@@ -106,10 +106,12 @@ sub decode_value { ...@@ -106,10 +106,12 @@ sub decode_value {
# We convert dateTimes to a DB-friendly date format. # We convert dateTimes to a DB-friendly date format.
if ($type eq 'dateTime.iso8601') { if ($type eq 'dateTime.iso8601') {
# We leave off the $ from the end of this regex to allow for possible if ($value !~ /T.*[\-+Z]/i) {
# extensions to the XML-RPC date standard. # The caller did not specify a timezone, so we assume UTC.
$value =~ /^(\d{4})(\d{2})(\d{2})T(\d{2}):(\d{2}):(\d{2})/; # pass 'Z' specifier to datetime_from to force it
$value = "$1-$2-$3 $4:$5:$6"; $value = $value . 'Z';
}
$value = $self->datetime_format_inbound($value);
} }
return $value; return $value;
...@@ -288,7 +290,9 @@ API via: C<http://bugzilla.yourdomain.com/xmlrpc.cgi> ...@@ -288,7 +290,9 @@ API via: C<http://bugzilla.yourdomain.com/xmlrpc.cgi>
=head1 PARAMETERS =head1 PARAMETERS
C<dateTime> fields are the standard C<dateTime.iso8601> XML-RPC field. They C<dateTime> fields are the standard C<dateTime.iso8601> XML-RPC field. They
should be in C<YYYY-MM-DDTHH:MM:SS> format (where C<T> is a literal T). should be in C<YYYY-MM-DDTHH:MM:SS> format (where C<T> is a literal T). As
of Bugzilla B<3.6>, Bugzilla always expects C<dateTime> fields to be in the
UTC timezone, and all returned C<dateTime> values are in the UTC timezone.
All other fields are standard XML-RPC types. All other fields are standard XML-RPC types.
...@@ -306,6 +310,14 @@ Normally, XML-RPC does not allow empty values for C<int>, C<double>, or ...@@ -306,6 +310,14 @@ Normally, XML-RPC does not allow empty values for C<int>, C<double>, or
C<dateTime.iso8601> fields. Bugzilla does--it treats empty values as C<dateTime.iso8601> fields. Bugzilla does--it treats empty values as
C<undef> (called C<NULL> or C<None> in some programming languages). C<undef> (called C<NULL> or C<None> in some programming languages).
Bugzilla accepts a timezone specifier at the end of C<dateTime.iso8601>
fields that are specified as method arguments. The format of the timezone
specifier is specified in the ISO-8601 standard. If no timezone specifier
is included, the passed-in time is assumed to be in the UTC timezone.
Bugzilla will never output a timezone specifier on returned data, because
doing so would violate the XML-RPC specification. All returned times are in
the UTC timezone.
Bugzilla also accepts an element called C<< <nil> >>, as specified by the Bugzilla also accepts an element called C<< <nil> >>, as specified by the
XML-RPC extension here: L<http://ontosys.com/xml-rpc/extensions.php>, which XML-RPC extension here: L<http://ontosys.com/xml-rpc/extensions.php>, which
is always considered to be C<undef>, no matter what it contains. is always considered to be C<undef>, no matter what it contains.
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment