1<?php 2/* 3 * This file is part of the PHPASN1 library. 4 * 5 * Copyright © Friedrich Große <friedrich.grosse@gmail.com> 6 * 7 * For the full copyright and license information, please view the LICENSE 8 * file that was distributed with this source code. 9 */ 10 11namespace FG\ASN1\Universal; 12 13use FG\ASN1\AbstractTime; 14use FG\ASN1\Parsable; 15use FG\ASN1\Identifier; 16use FG\ASN1\Exception\ParserException; 17 18/** 19 * This ASN.1 universal type contains date and time information according to ISO 8601. 20 * 21 * The type consists of values representing: 22 * a) a calendar date, as defined in ISO 8601; and 23 * b) a time of day, to any of the precisions defined in ISO 8601, except for the hours value 24 which shall not be used; and 24 * c) the local time differential factor as defined in ISO 8601. 25 * 26 * Decoding of this type will accept the Basic Encoding Rules (BER) 27 * The encoding will comply with the Distinguished Encoding Rules (DER). 28 */ 29class GeneralizedTime extends AbstractTime implements Parsable 30{ 31 private $microseconds; 32 33 public function __construct($dateTime = null, $dateTimeZone = 'UTC') 34 { 35 parent::__construct($dateTime, $dateTimeZone); 36 $this->microseconds = $this->value->format('u'); 37 if ($this->containsFractionalSecondsElement()) { 38 // DER requires us to remove trailing zeros 39 $this->microseconds = preg_replace('/([1-9]+)0+$/', '$1', $this->microseconds); 40 } 41 } 42 43 public function getType() 44 { 45 return Identifier::GENERALIZED_TIME; 46 } 47 48 protected function calculateContentLength() 49 { 50 $contentSize = 15; // YYYYMMDDHHmmSSZ 51 52 if ($this->containsFractionalSecondsElement()) { 53 $contentSize += 1 + strlen($this->microseconds); 54 } 55 56 return $contentSize; 57 } 58 59 public function containsFractionalSecondsElement() 60 { 61 return intval($this->microseconds) > 0; 62 } 63 64 protected function getEncodedValue() 65 { 66 $encodedContent = $this->value->format('YmdHis'); 67 if ($this->containsFractionalSecondsElement()) { 68 $encodedContent .= ".{$this->microseconds}"; 69 } 70 71 return $encodedContent.'Z'; 72 } 73 74 public function __toString() 75 { 76 if ($this->containsFractionalSecondsElement()) { 77 return $this->value->format("Y-m-d\tH:i:s.uP"); 78 } else { 79 return $this->value->format("Y-m-d\tH:i:sP"); 80 } 81 } 82 83 public static function fromBinary(&$binaryData, &$offsetIndex = 0) 84 { 85 self::parseIdentifier($binaryData[$offsetIndex], Identifier::GENERALIZED_TIME, $offsetIndex++); 86 $lengthOfMinimumTimeString = 14; // YYYYMMDDHHmmSS 87 $contentLength = self::parseContentLength($binaryData, $offsetIndex, $lengthOfMinimumTimeString); 88 $maximumBytesToRead = $contentLength; 89 90 $format = 'YmdGis'; 91 $content = substr($binaryData, $offsetIndex, $contentLength); 92 $dateTimeString = substr($content, 0, $lengthOfMinimumTimeString); 93 $offsetIndex += $lengthOfMinimumTimeString; 94 $maximumBytesToRead -= $lengthOfMinimumTimeString; 95 96 if ($contentLength == $lengthOfMinimumTimeString) { 97 $localTimeZone = new \DateTimeZone(date_default_timezone_get()); 98 $dateTime = \DateTime::createFromFormat($format, $dateTimeString, $localTimeZone); 99 } else { 100 if ($binaryData[$offsetIndex] == '.') { 101 $maximumBytesToRead--; // account for the '.' 102 $nrOfFractionalSecondElements = 1; // account for the '.' 103 104 while ($maximumBytesToRead > 0 105 && $binaryData[$offsetIndex + $nrOfFractionalSecondElements] != '+' 106 && $binaryData[$offsetIndex + $nrOfFractionalSecondElements] != '-' 107 && $binaryData[$offsetIndex + $nrOfFractionalSecondElements] != 'Z') { 108 $nrOfFractionalSecondElements++; 109 $maximumBytesToRead--; 110 } 111 112 $dateTimeString .= substr($binaryData, $offsetIndex, $nrOfFractionalSecondElements); 113 $offsetIndex += $nrOfFractionalSecondElements; 114 $format .= '.u'; 115 } 116 117 $dateTime = \DateTime::createFromFormat($format, $dateTimeString, new \DateTimeZone('UTC')); 118 119 if ($maximumBytesToRead > 0) { 120 if ($binaryData[$offsetIndex] == '+' 121 || $binaryData[$offsetIndex] == '-') { 122 $dateTime = static::extractTimeZoneData($binaryData, $offsetIndex, $dateTime); 123 } elseif ($binaryData[$offsetIndex++] != 'Z') { 124 throw new ParserException('Invalid ISO 8601 Time String', $offsetIndex); 125 } 126 } 127 } 128 129 $parsedObject = new self($dateTime); 130 $parsedObject->setContentLength($contentLength); 131 132 return $parsedObject; 133 } 134} 135