diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index 625761a460..a0f66c6ac1 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -457,6 +457,7 @@ phutil_register_library_map(array( 'PhabricatorLintEngine' => 'infrastructure/lint/engine', 'PhabricatorLiskDAO' => 'applications/base/storage/lisk', 'PhabricatorLocalDiskFileStorageEngine' => 'applications/files/engine/localdisk', + 'PhabricatorLocalTimeTestCase' => 'view/utils/__tests__', 'PhabricatorLoginController' => 'applications/auth/controller/login', 'PhabricatorLogoutController' => 'applications/auth/controller/logout', 'PhabricatorMailImplementationAdapter' => 'applications/metamta/adapter/base', @@ -700,6 +701,7 @@ phutil_register_library_map(array( ), 'function' => array( + '__phabricator_format_local_time' => 'view/utils', '_qsprintf_check_scalar_type' => 'storage/qsprintf', '_qsprintf_check_type' => 'storage/qsprintf', 'celerity_generate_unique_node_id' => 'infrastructure/celerity/api', @@ -1073,6 +1075,7 @@ phutil_register_library_map(array( 'PhabricatorLintEngine' => 'PhutilLintEngine', 'PhabricatorLiskDAO' => 'LiskDAO', 'PhabricatorLocalDiskFileStorageEngine' => 'PhabricatorFileStorageEngine', + 'PhabricatorLocalTimeTestCase' => 'PhabricatorTestCase', 'PhabricatorLoginController' => 'PhabricatorAuthController', 'PhabricatorLogoutController' => 'PhabricatorAuthController', 'PhabricatorMailImplementationAmazonSESAdapter' => 'PhabricatorMailImplementationPHPMailerLiteAdapter', diff --git a/src/view/utils/__tests__/PhabricatorLocalTimeTestCase.php b/src/view/utils/__tests__/PhabricatorLocalTimeTestCase.php new file mode 100644 index 0000000000..4470eb1aca --- /dev/null +++ b/src/view/utils/__tests__/PhabricatorLocalTimeTestCase.php @@ -0,0 +1,52 @@ +setTimezoneIdentifier('America/Los_Angeles'); + + $utc = new PhabricatorUser(); + $utc->setTimezoneIdentifier('UTC'); + + $this->assertEqual( + 'Jan 1 2000, 12:00 AM', + phabricator_datetime(946684800, $utc), + 'Datetime formatting'); + $this->assertEqual( + 'Jan 1 2000', + phabricator_date(946684800, $utc), + 'Date formatting'); + $this->assertEqual( + '12:00 AM', + phabricator_time(946684800, $utc), + 'Time formatting'); + + $this->assertEqual( + 'Dec 31 1999, 4:00 PM', + phabricator_datetime(946684800, $user), + 'Localization'); + + $this->assertEqual( + '', + phabricator_datetime(0, $user), + 'Missing epoch should fail gracefully'); + } + +} diff --git a/src/view/utils/__tests__/__init__.php b/src/view/utils/__tests__/__init__.php new file mode 100644 index 0000000000..f3b3be7802 --- /dev/null +++ b/src/view/utils/__tests__/__init__.php @@ -0,0 +1,14 @@ +getTimezoneIdentifier()); - $date = new DateTime('@'.$epoch); - $date->setTimeZone($zone); - return $date->format('M j Y'); + return __phabricator_format_local_time( + $epoch, + $user, + 'M j Y'); } function phabricator_time($epoch, $user) { - $zone = new DateTimeZone($user->getTimezoneIdentifier()); - $date = new DateTime('@'.$epoch); - $date->setTimeZone($zone); - return $date->format('g:i A'); + return __phabricator_format_local_time( + $epoch, + $user, + 'g:i A'); } function phabricator_datetime($epoch, $user) { - $zone = new DateTimeZone($user->getTimezoneIdentifier()); + return __phabricator_format_local_time( + $epoch, + $user, + 'M j Y, g:i A'); +} + +/** + * Internal date rendering method. Do not call this directly; instead, call + * @{function:phabricator_date}, @{function:phabricator_time}, or + * @{function:phabricator_datetime}. + * + * @param int Unix epoch timestamp. + * @param PhabricatorUser User viewing the timestamp. + * @param string Date format, as per DateTime class. + * @return string Formatted, local date/time. + */ +function __phabricator_format_local_time($epoch, $user, $format) { + if (!$epoch) { + // If we're missing date information for something, the DateTime class will + // throw an exception when we try to construct an object. Since this is a + // display function, just return an empty string. + return ''; + } + + $user_zone = $user->getTimezoneIdentifier(); + + static $zones = array(); + if (empty($zones[$user_zone])) { + $zones[$user_zone] = new DateTimeZone($user_zone); + } + $zone = $zones[$user_zone]; + + // NOTE: Although DateTime takes a second DateTimeZone parameter to its + // constructor, it ignores it if the date string includes timezone + // information. Further, it treats epoch timestamps ("@946684800") as having + // a UTC timezone. Set the timezone explicitly after constructing the object. $date = new DateTime('@'.$epoch); $date->setTimeZone($zone); - return $date->format('M j Y, g:i A'); + + return $date->format($format); } function phabricator_format_relative_time($duration) {