diff --git a/src/main/php/img/io/ImageMetaData.class.php b/src/main/php/img/io/ImageMetaData.class.php index 2c03cd84..c9af9ee7 100755 --- a/src/main/php/img/io/ImageMetaData.class.php +++ b/src/main/php/img/io/ImageMetaData.class.php @@ -85,9 +85,10 @@ public function imageDimensions() { * Returns an IptcData instance or NULL if this image does not contain * IPTC Data * + * @param ?util.TimeZone $tz * @return ?img.util.IptcData */ - public function iptcData() { + public function iptcData($tz= null) { if (!($seg= $this->segmentsOf(IptcSegment::class))) return null; $data= new IptcData(); @@ -95,7 +96,12 @@ public function iptcData() { // Parse creation date if (3 === sscanf($iptc['2#055'][0] ?? '', '%4d%2d%d', $year, $month, $day)) { - $created= Date::create($year, $month, $day, 0, 0, 0); + if (isset($iptc['2#060'])) { + sscanf($iptc['2#060'][0], '%2d%2d%2d', $hour, $minute, $second); + } else { + $hour= $minute= $second= 0; + } + $created= Date::create($year, $month, $day, $hour, $minute, $second, $tz); } else { $created= null; } @@ -141,9 +147,10 @@ protected static function lookup($exif, ... $keys) { * Returns an ExifData instance or NULL if this image does not contain * EXIF Data * + * @param ?util.TimeZone $tz * @return ?img.util.ExifData */ - public function exifData() { + public function exifData($tz= null) { if (!($seg= $this->segmentsOf(ExifSegment::class))) return null; // Populate ExifData instance from ExifSegment's raw data @@ -205,8 +212,7 @@ public function exifData() { $data->withFlash(self::lookup($exif, 'Flash')); if (null !== ($date= self::lookup($exif, 'DateTimeOriginal', 'DateTimeDigitized', 'DateTime'))) { - $t= sscanf($date, '%4d:%2d:%2d %2d:%2d:%2d'); - $data->withDateTime(new Date(mktime($t[3], $t[4], $t[5], $t[1], $t[2], $t[0]))); + $data->withDateTime(new Date($date, $tz)); } if (null !== ($o= self::lookup($exif, 'Orientation'))) { diff --git a/src/test/php/img/unittest/MetaDataReaderTest.class.php b/src/test/php/img/unittest/MetaDataReaderTest.class.php index a6af9067..9015fda2 100755 --- a/src/test/php/img/unittest/MetaDataReaderTest.class.php +++ b/src/test/php/img/unittest/MetaDataReaderTest.class.php @@ -4,48 +4,30 @@ use img\ImagingException; use img\io\{Segment, CommentSegment, MetaDataReader, SOFNSegment, XMPSegment, ExifSegment, IptcSegment}; use img\util\{ExifData, IptcData}; -use lang\ArrayType; -use test\{Assert, Before, Expect, Test}; -use util\Date; +use test\{Assert, Before, Expect, Test, Values}; +use util\{Date, TimeZone}; class MetaDataReaderTest { private $fixture; /** - * Returns a file for a classloader resource + * Extract meta data from a given resource * * @param string $name - * @param string $sub default NULL subpackage - * @return io.File + * @param ?string $sub + * @return img.io.ImageMetaData */ - protected function resourceAsFile($name, $sub= null) { + private function extractFromFile($name, $sub= null) { $package= typeof($this)->getPackage(); $container= $sub ? $package->getPackage($sub) : $package; - return $container->getResourceAsStream($name); + return $this->fixture->read($container->getResourceAsStream($name)->in(), $name); } - /** - * Extract from file and return the instance - * - * @param string $name - * @param string $sub default NULL subpackage - * @return lang.Generic the instance - */ - protected function extractFromFile($name, $sub= null) { - return $this->fixture->read($this->resourceAsFile($name, $sub)->in(), $name); - } - - /** - * Assertion helper - * - * @param string $type The expected type - * @param int $size The expected size - * @param var $value The value - * @throws unittest.AssertionFailedError - */ - protected function assertArrayOf($type, $size, $value) { - Assert::instance(new ArrayType($type), $value); - Assert::equals($size, sizeof($value)); + /** @return iterable */ + private function timezones() { + yield null; + yield TimeZone::getByName('UTC'); + yield TimeZone::getByName('Europe/Berlin'); } #[Before] @@ -65,12 +47,18 @@ public function empty_file() { #[Test] public function all_segments() { - $this->assertArrayOf(Segment::class, 9, $this->extractFromFile('1x1.jpg')->allSegments()); + $segments= $this->extractFromFile('1x1.jpg')->allSegments(); + + Assert::instance('img.io.Segment[]', $segments); + Assert::equals(9, sizeof($segments)); } #[Test] public function segments_named_dqt() { - $this->assertArrayOf(Segment::class, 2, $this->extractFromFile('1x1.jpg')->segmentsNamed('DQT')); + $segments= $this->extractFromFile('1x1.jpg')->segmentsNamed('DQT'); + + Assert::instance('img.io.Segment[]', $segments); + Assert::equals(2, sizeof($segments)); } #[Test] @@ -100,8 +88,8 @@ public function com_segment() { #[Test] public function xmp_segment() { $segments= $this->extractFromFile('xmp.jpg')->segmentsOf(XMPSegment::class); - $this->assertArrayOf(XMPSegment::class, 1, $segments); + Assert::instance('img.io.XMPSegment[]', $segments); Assert::matches('/^<.+/', $segments[0]->source); Assert::instance(DOMDocument::class, $segments[0]->document()); } @@ -138,16 +126,16 @@ public function no_iptc_data_in_1x1() { #[Test] public function exif_data_segments() { - $this->assertArrayOf( - ExifSegment::class, 1, + Assert::instance( + 'img.io.ExifSegment[]', $this->extractFromFile('exif-only.jpg')->segmentsOf(ExifSegment::class) ); } #[Test] public function iptc_data_segments() { - $this->assertArrayOf( - IptcSegment::class, 1, + Assert::instance( + 'img.io.IptcSegment[]', $this->extractFromFile('iptc-only.jpg')->segmentsOf(IptcSegment::class) ); } @@ -155,8 +143,8 @@ public function iptc_data_segments() { #[Test] public function exif_and_iptc_data_segments() { $meta= $this->extractFromFile('exif-and-iptc.jpg'); - $this->assertArrayOf(ExifSegment::class, 1, $meta->segmentsOf(ExifSegment::class)); - $this->assertArrayOf(IptcSegment::class, 1, $meta->segmentsOf(IptcSegment::class)); + Assert::instance('img.io.ExifSegment[]', $meta->segmentsOf(ExifSegment::class)); + Assert::instance('img.io.IptcSegment[]', $meta->segmentsOf(IptcSegment::class)); } #[Test] @@ -519,7 +507,7 @@ public function detailed_iptc_data() { ->withUrgency(null) ->withCategory(null) ->withKeywords(null) - ->withDateCreated(new Date('2011-12-07 00:00:00')) + ->withDateCreated(new Date('2011-12-07 14:08:24')) ->withAuthor(null) ->withAuthorPosition(null) ->withCity(null) @@ -559,4 +547,20 @@ public function gps_data() { ] ); } + + #[Test, Values(from: 'timezones')] + public function iptc_with_timezone($tz) { + Assert::equals( + new Date('2011-12-07 14:08:24', $tz), + $this->extractFromFile('detailed-iptc-embedded.jpg')->iptcData($tz)->dateCreated + ); + } + + #[Test, Values(from: 'timezones')] + public function exif_with_timezone($tz) { + Assert::equals( + new Date('2001:06:09 15:17:32', $tz), + $this->extractFromFile('canon-ixus.jpg', 'exif_org')->exifData($tz)->dateTime + ); + } } \ No newline at end of file