From 174c82d8cef27be5cb9d8491dd1e26d27898870b Mon Sep 17 00:00:00 2001 From: Jack Wakefield Date: Sun, 10 Sep 2017 23:17:50 +0100 Subject: Add well-known timestamps to JSON for PHP (#3564) --- php/src/Google/Protobuf/Internal/GPBUtil.php | 80 ++++++++++++++++++++++++++++ 1 file changed, 80 insertions(+) (limited to 'php/src/Google/Protobuf/Internal/GPBUtil.php') diff --git a/php/src/Google/Protobuf/Internal/GPBUtil.php b/php/src/Google/Protobuf/Internal/GPBUtil.php index 84e8ecf0..a27220a0 100644 --- a/php/src/Google/Protobuf/Internal/GPBUtil.php +++ b/php/src/Google/Protobuf/Internal/GPBUtil.php @@ -38,6 +38,9 @@ use Google\Protobuf\Internal\MapField; class GPBUtil { + const NANOS_PER_MILLISECOND = 1000000; + const NANOS_PER_MICROSECOND = 1000; + public static function divideInt64ToInt32($value, &$high, &$low, $trim = false) { $isNeg = (bccomp($value, 0) < 0); @@ -340,4 +343,81 @@ class GPBUtil } return $result; } + + public static function parseTimestamp($timestamp) + { + // prevent parsing timestamps containing with the non-existant year "0000" + // DateTime::createFromFormat parses without failing but as a nonsensical date + if (substr($timestamp, 0, 4) === "0000") { + throw new \Exception("Year cannot be zero."); + } + // prevent parsing timestamps ending with a lowercase z + if (substr($timestamp, -1, 1) === "z") { + throw new \Exception("Timezone cannot be a lowercase z."); + } + + $nanoseconds = 0; + $periodIndex = strpos($timestamp, "."); + if ($periodIndex !== false) { + $nanosecondsLength = 0; + // find the next non-numeric character in the timestamp to calculate + // the length of the nanoseconds text + for ($i = $periodIndex + 1, $length = strlen($timestamp); $i < $length; $i++) { + if (!is_numeric($timestamp[$i])) { + $nanosecondsLength = $i - ($periodIndex + 1); + break; + } + } + if ($nanosecondsLength % 3 !== 0) { + throw new \Exception("Nanoseconds must be disible by 3."); + } + if ($nanosecondsLength > 9) { + throw new \Exception("Nanoseconds must be in the range of 0 to 999,999,999 nanoseconds."); + } + if ($nanosecondsLength > 0) { + $nanoseconds = substr($timestamp, $periodIndex + 1, $nanosecondsLength); + $nanoseconds = intval($nanoseconds); + + // remove the nanoseconds and preceding period from the timestamp + $date = substr($timestamp, 0, $periodIndex - 1); + $timezone = substr($timestamp, $periodIndex + $nanosecondsLength); + $timestamp = $date.$timezone; + } + } + + $date = \DateTime::createFromFormat(\DateTime::RFC3339, $timestamp, new \DateTimeZone("UTC")); + if ($date === false) { + throw new \Exception("Invalid RFC 3339 timestamp."); + } + + $value = new \Google\Protobuf\Timestamp(); + $seconds = $date->format("U"); + $value->setSeconds($seconds); + $value->setNanos($nanoseconds); + return $value; + } + + public static function formatTimestamp($value) + { + $nanoseconds = static::getNanosecondsForTimestamp($value->getNanos()); + if (!empty($nanoseconds)) { + $nanoseconds = ".".$nanoseconds; + } + $date = new \DateTime('@'.$value->getSeconds(), new \DateTimeZone("UTC")); + return $date->format("Y-m-d\TH:i:s".$nanoseconds."\Z"); + } + + public static function getNanosecondsForTimestamp($nanoseconds) + { + if ($nanoseconds == 0) { + return ''; + } + if ($nanoseconds % static::NANOS_PER_MILLISECOND == 0) { + return sprintf('%03d', $nanoseconds / static::NANOS_PER_MILLISECOND); + } + if ($nanoseconds % static::NANOS_PER_MICROSECOND == 0) { + return sprintf('%06d', $nanoseconds / static::NANOS_PER_MICROSECOND); + } + return sprintf('%09d', $nanoseconds); + } } -- cgit v1.2.3