flags = (int) $flags; $this->modifyTime = $modifyTime; $this->accessTime = $accessTime; $this->createTime = $createTime; } /** * @param int|null $modifyTime * @param int|null $accessTime * @param int|null $createTime * * @return ExtendedTimestampExtraField */ public static function create($modifyTime, $accessTime, $createTime) { $flags = 0; if ($modifyTime !== null) { $modifyTime = (int) $modifyTime; $flags |= self::MODIFY_TIME_BIT; } if ($accessTime !== null) { $accessTime = (int) $accessTime; $flags |= self::ACCESS_TIME_BIT; } if ($createTime !== null) { $createTime = (int) $createTime; $flags |= self::CREATE_TIME_BIT; } return new self($flags, $modifyTime, $accessTime, $createTime); } /** * Returns the Header ID (type) of this Extra Field. * The Header ID is an unsigned short integer (two bytes) * which must be constant during the life cycle of this object. * * @return int */ public function getHeaderId() { return self::HEADER_ID; } /** * Populate data from this array as if it was in local file data. * * @param string $buffer the buffer to read data from * @param ZipEntry|null $entry * * @return ExtendedTimestampExtraField */ public static function unpackLocalFileData($buffer, ZipEntry $entry = null) { $length = \strlen($buffer); $flags = unpack('C', $buffer)[1]; $offset = 1; $modifyTime = null; $accessTime = null; $createTime = null; if (($flags & self::MODIFY_TIME_BIT) === self::MODIFY_TIME_BIT) { $modifyTime = unpack('V', substr($buffer, $offset, 4))[1]; $offset += 4; } // Notice the extra length check in case we are parsing the shorter // central data field (for both access and create timestamps). if ((($flags & self::ACCESS_TIME_BIT) === self::ACCESS_TIME_BIT) && $offset + 4 <= $length) { $accessTime = unpack('V', substr($buffer, $offset, 4))[1]; $offset += 4; } if ((($flags & self::CREATE_TIME_BIT) === self::CREATE_TIME_BIT) && $offset + 4 <= $length) { $createTime = unpack('V', substr($buffer, $offset, 4))[1]; } return new self($flags, $modifyTime, $accessTime, $createTime); } /** * Populate data from this array as if it was in central directory data. * * @param string $buffer the buffer to read data from * @param ZipEntry|null $entry * * @return ExtendedTimestampExtraField */ public static function unpackCentralDirData($buffer, ZipEntry $entry = null) { return self::unpackLocalFileData($buffer, $entry); } /** * The actual data to put into local file data - without Header-ID * or length specifier. * * @return string the data */ public function packLocalFileData() { $data = ''; if (($this->flags & self::MODIFY_TIME_BIT) === self::MODIFY_TIME_BIT && $this->modifyTime !== null) { $data .= pack('V', $this->modifyTime); } if (($this->flags & self::ACCESS_TIME_BIT) === self::ACCESS_TIME_BIT && $this->accessTime !== null) { $data .= pack('V', $this->accessTime); } if (($this->flags & self::CREATE_TIME_BIT) === self::CREATE_TIME_BIT && $this->createTime !== null) { $data .= pack('V', $this->createTime); } return pack('C', $this->flags) . $data; } /** * The actual data to put into central directory - without Header-ID or * length specifier. * * Note: even if bit1 and bit2 are set, the Central data will still * not contain access/create fields: only local data ever holds those! * * @return string the data */ public function packCentralDirData() { $cdLength = 1 + ($this->modifyTime !== null ? 4 : 0); return substr($this->packLocalFileData(), 0, $cdLength); } /** * Gets flags byte. * * The flags byte tells us which of the three datestamp fields are * present in the data: * bit0 - modify time * bit1 - access time * bit2 - create time * * Only first 3 bits of flags are used according to the * latest version of the spec (December 2012). * * @return int flags byte indicating which of the * three datestamp fields are present */ public function getFlags() { return $this->flags; } /** * Returns the modify time (seconds since epoch) of this zip entry, * or null if no such timestamp exists in the zip entry. * * @return int|null modify time (seconds since epoch) or null */ public function getModifyTime() { return $this->modifyTime; } /** * Returns the access time (seconds since epoch) of this zip entry, * or null if no such timestamp exists in the zip entry. * * @return int|null access time (seconds since epoch) or null */ public function getAccessTime() { return $this->accessTime; } /** * Returns the create time (seconds since epoch) of this zip entry, * or null if no such timestamp exists in the zip entry. * * Note: modern linux file systems (e.g., ext2) * do not appear to store a "create time" value, and so * it's usually omitted altogether in the zip extra * field. Perhaps other unix systems track this. * * @return int|null create time (seconds since epoch) or null */ public function getCreateTime() { return $this->createTime; } /** * Returns the modify time as a \DateTimeInterface * of this zip entry, or null if no such timestamp exists in the zip entry. * The milliseconds are always zeroed out, since the underlying data * offers only per-second precision. * * @return \DateTimeInterface|null modify time as \DateTimeInterface or null */ public function getModifyDateTime() { return self::timestampToDateTime($this->modifyTime); } /** * Returns the access time as a \DateTimeInterface * of this zip entry, or null if no such timestamp exists in the zip entry. * The milliseconds are always zeroed out, since the underlying data * offers only per-second precision. * * @return \DateTimeInterface|null access time as \DateTimeInterface or null */ public function getAccessDateTime() { return self::timestampToDateTime($this->accessTime); } /** * Returns the create time as a a \DateTimeInterface * of this zip entry, or null if no such timestamp exists in the zip entry. * The milliseconds are always zeroed out, since the underlying data * offers only per-second precision. * * Note: modern linux file systems (e.g., ext2) * do not appear to store a "create time" value, and so * it's usually omitted altogether in the zip extra * field. Perhaps other unix systems track $this->. * * @return \DateTimeInterface|null create time as \DateTimeInterface or null */ public function getCreateDateTime() { return self::timestampToDateTime($this->createTime); } /** * Sets the modify time (seconds since epoch) of this zip entry * using a integer. * * @param int|null $unixTime unix time of the modify time (seconds per epoch) or null */ public function setModifyTime($unixTime) { $this->modifyTime = $unixTime; $this->updateFlags(); } private function updateFlags() { $flags = 0; if ($this->modifyTime !== null) { $flags |= self::MODIFY_TIME_BIT; } if ($this->accessTime !== null) { $flags |= self::ACCESS_TIME_BIT; } if ($this->createTime !== null) { $flags |= self::CREATE_TIME_BIT; } $this->flags = $flags; } /** * Sets the access time (seconds since epoch) of this zip entry * using a integer. * * @param int|null $unixTime Unix time of the access time (seconds per epoch) or null */ public function setAccessTime($unixTime) { $this->accessTime = $unixTime; $this->updateFlags(); } /** * Sets the create time (seconds since epoch) of this zip entry * using a integer. * * @param int|null $unixTime Unix time of the create time (seconds per epoch) or null */ public function setCreateTime($unixTime) { $this->createTime = $unixTime; $this->updateFlags(); } /** * @param int|null $timestamp * * @return \DateTimeInterface|null */ private static function timestampToDateTime($timestamp) { try { return $timestamp !== null ? new \DateTimeImmutable('@' . $timestamp) : null; } catch (\Exception $e) { return null; } } /** * @return string */ public function __toString() { $args = [self::HEADER_ID]; $format = '0x%04x ExtendedTimestamp:'; if ($this->modifyTime !== null) { $format .= ' Modify:[%s]'; $args[] = date(\DATE_W3C, $this->modifyTime); } if ($this->accessTime !== null) { $format .= ' Access:[%s]'; $args[] = date(\DATE_W3C, $this->accessTime); } if ($this->createTime !== null) { $format .= ' Create:[%s]'; $args[] = date(\DATE_W3C, $this->createTime); } return vsprintf($format, $args); } }