Skip to content

Commit 2fc3acc

Browse files
author
epriestley
committed
Improve time localization code
Summary: - We throw on a missing date right now, in the DateTime constructor. This can happen in reasonable cases and this is display code, so handle it more gracefully (see T520). - This stuff is a little slow and we sometimes render many hundreds of dates per page. I've been seeing it in profiles on and off. Memoize timezones to improve performance. - Some minor code duplication that would have become less-minor with the constructor change, consolidate the logic. - Add some unit tests and a little documentation. Test Plan: - Ran unit tests. - Profiled 1,000 calls to phabricator_datetime(), cost dropped from ~49ms to ~19ms with addition of memoization. This is still slower than I'd like but I don't think there's an easy way to squeeze it down further. Reviewers: ajtrichards, jungejason, nh, tuomaspelkonen, aran Reviewed By: ajtrichards CC: aran, ajtrichards, epriestley Differential Revision: 966
1 parent 016b060 commit 2fc3acc

File tree

4 files changed

+115
-10
lines changed

4 files changed

+115
-10
lines changed

src/__phutil_library_map__.php

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -457,6 +457,7 @@
457457
'PhabricatorLintEngine' => 'infrastructure/lint/engine',
458458
'PhabricatorLiskDAO' => 'applications/base/storage/lisk',
459459
'PhabricatorLocalDiskFileStorageEngine' => 'applications/files/engine/localdisk',
460+
'PhabricatorLocalTimeTestCase' => 'view/utils/__tests__',
460461
'PhabricatorLoginController' => 'applications/auth/controller/login',
461462
'PhabricatorLogoutController' => 'applications/auth/controller/logout',
462463
'PhabricatorMailImplementationAdapter' => 'applications/metamta/adapter/base',
@@ -700,6 +701,7 @@
700701
),
701702
'function' =>
702703
array(
704+
'__phabricator_format_local_time' => 'view/utils',
703705
'_qsprintf_check_scalar_type' => 'storage/qsprintf',
704706
'_qsprintf_check_type' => 'storage/qsprintf',
705707
'celerity_generate_unique_node_id' => 'infrastructure/celerity/api',
@@ -1073,6 +1075,7 @@
10731075
'PhabricatorLintEngine' => 'PhutilLintEngine',
10741076
'PhabricatorLiskDAO' => 'LiskDAO',
10751077
'PhabricatorLocalDiskFileStorageEngine' => 'PhabricatorFileStorageEngine',
1078+
'PhabricatorLocalTimeTestCase' => 'PhabricatorTestCase',
10761079
'PhabricatorLoginController' => 'PhabricatorAuthController',
10771080
'PhabricatorLogoutController' => 'PhabricatorAuthController',
10781081
'PhabricatorMailImplementationAmazonSESAdapter' => 'PhabricatorMailImplementationPHPMailerLiteAdapter',
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
<?php
2+
3+
/*
4+
* Copyright 2011 Facebook, Inc.
5+
*
6+
* Licensed under the Apache License, Version 2.0 (the "License");
7+
* you may not use this file except in compliance with the License.
8+
* You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing, software
13+
* distributed under the License is distributed on an "AS IS" BASIS,
14+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15+
* See the License for the specific language governing permissions and
16+
* limitations under the License.
17+
*/
18+
19+
final class PhabricatorLocalTimeTestCase extends PhabricatorTestCase {
20+
21+
public function testLocalTimeFormatting() {
22+
$user = new PhabricatorUser();
23+
$user->setTimezoneIdentifier('America/Los_Angeles');
24+
25+
$utc = new PhabricatorUser();
26+
$utc->setTimezoneIdentifier('UTC');
27+
28+
$this->assertEqual(
29+
'Jan 1 2000, 12:00 AM',
30+
phabricator_datetime(946684800, $utc),
31+
'Datetime formatting');
32+
$this->assertEqual(
33+
'Jan 1 2000',
34+
phabricator_date(946684800, $utc),
35+
'Date formatting');
36+
$this->assertEqual(
37+
'12:00 AM',
38+
phabricator_time(946684800, $utc),
39+
'Time formatting');
40+
41+
$this->assertEqual(
42+
'Dec 31 1999, 4:00 PM',
43+
phabricator_datetime(946684800, $user),
44+
'Localization');
45+
46+
$this->assertEqual(
47+
'',
48+
phabricator_datetime(0, $user),
49+
'Missing epoch should fail gracefully');
50+
}
51+
52+
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
<?php
2+
/**
3+
* This file is automatically generated. Lint this module to rebuild it.
4+
* @generated
5+
*/
6+
7+
8+
9+
phutil_require_module('phabricator', 'applications/people/storage/user');
10+
phutil_require_module('phabricator', 'infrastructure/testing/testcase');
11+
phutil_require_module('phabricator', 'view/utils');
12+
13+
14+
phutil_require_source('PhabricatorLocalTimeTestCase.php');

src/view/utils/viewutils.php

Lines changed: 46 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -17,24 +17,60 @@
1717
*/
1818

1919
function phabricator_date($epoch, $user) {
20-
$zone = new DateTimeZone($user->getTimezoneIdentifier());
21-
$date = new DateTime('@'.$epoch);
22-
$date->setTimeZone($zone);
23-
return $date->format('M j Y');
20+
return __phabricator_format_local_time(
21+
$epoch,
22+
$user,
23+
'M j Y');
2424
}
2525

2626
function phabricator_time($epoch, $user) {
27-
$zone = new DateTimeZone($user->getTimezoneIdentifier());
28-
$date = new DateTime('@'.$epoch);
29-
$date->setTimeZone($zone);
30-
return $date->format('g:i A');
27+
return __phabricator_format_local_time(
28+
$epoch,
29+
$user,
30+
'g:i A');
3131
}
3232

3333
function phabricator_datetime($epoch, $user) {
34-
$zone = new DateTimeZone($user->getTimezoneIdentifier());
34+
return __phabricator_format_local_time(
35+
$epoch,
36+
$user,
37+
'M j Y, g:i A');
38+
}
39+
40+
/**
41+
* Internal date rendering method. Do not call this directly; instead, call
42+
* @{function:phabricator_date}, @{function:phabricator_time}, or
43+
* @{function:phabricator_datetime}.
44+
*
45+
* @param int Unix epoch timestamp.
46+
* @param PhabricatorUser User viewing the timestamp.
47+
* @param string Date format, as per DateTime class.
48+
* @return string Formatted, local date/time.
49+
*/
50+
function __phabricator_format_local_time($epoch, $user, $format) {
51+
if (!$epoch) {
52+
// If we're missing date information for something, the DateTime class will
53+
// throw an exception when we try to construct an object. Since this is a
54+
// display function, just return an empty string.
55+
return '';
56+
}
57+
58+
$user_zone = $user->getTimezoneIdentifier();
59+
60+
static $zones = array();
61+
if (empty($zones[$user_zone])) {
62+
$zones[$user_zone] = new DateTimeZone($user_zone);
63+
}
64+
$zone = $zones[$user_zone];
65+
66+
// NOTE: Although DateTime takes a second DateTimeZone parameter to its
67+
// constructor, it ignores it if the date string includes timezone
68+
// information. Further, it treats epoch timestamps ("@946684800") as having
69+
// a UTC timezone. Set the timezone explicitly after constructing the object.
3570
$date = new DateTime('@'.$epoch);
3671
$date->setTimeZone($zone);
37-
return $date->format('M j Y, g:i A');
72+
73+
return $date->format($format);
3874
}
3975

4076
function phabricator_format_relative_time($duration) {

0 commit comments

Comments
 (0)