From fa1c94da7683b463196589e3ea05bb7521781978 Mon Sep 17 00:00:00 2001 From: Sukhendu Sekhar Guria Date: Tue, 21 Jan 2025 01:20:00 +0530 Subject: [PATCH 1/3] Improve EXIF timestamp handling with timezone support --- src/wp-admin/includes/image.php | 46 +++++++++++++--- tests/phpunit/tests/date/exifDate.php | 78 +++++++++++++++++++++++++++ tests/phpunit/tests/image/meta.php | 2 + 3 files changed, 120 insertions(+), 6 deletions(-) create mode 100644 tests/phpunit/tests/date/exifDate.php diff --git a/src/wp-admin/includes/image.php b/src/wp-admin/includes/image.php index 8471c999c8ec5..093eb2ca58093 100644 --- a/src/wp-admin/includes/image.php +++ b/src/wp-admin/includes/image.php @@ -805,6 +805,25 @@ function wp_exif_frac2dec( $str ) { return $numerator / $denominator; } +/** + * Convert the exif date format to DateTime object + * + * @since 6.8.0 + * + * @param string $str A date string expected to be in Exif format (Y:m:d H:i:s). + * @param string $timezone Optional. Timezone or offset string. + * @return DateTimeImmutable|false Return false if not valid date + */ +function wp_exif_datetime( $str, $timezone = null ) { + try { + $timezone = ( $timezone ) ? new DateTimeZone( $timezone ) : wp_timezone(); + $datetime = new DateTimeImmutable( $str, $timezone ); + return $datetime; + } catch ( Exception $e ) { + return false; + } +} + /** * Converts the exif date format to a unix timestamp. * @@ -814,10 +833,11 @@ function wp_exif_frac2dec( $str ) { * @return int|false The unix timestamp, or false on failure. */ function wp_exif_date2ts( $str ) { - list( $date, $time ) = explode( ' ', trim( $str ) ); - list( $y, $m, $d ) = explode( ':', $date ); - - return strtotime( "{$y}-{$m}-{$d} {$time}" ); + $datetime = wp_exif_datetime( $str ); + if ( $datetime ) { + return $datetime->getTimestamp(); + } + return false; } /** @@ -921,7 +941,11 @@ function wp_read_image_metadata( $file ) { } if ( ! empty( $iptc['2#055'][0] ) && ! empty( $iptc['2#060'][0] ) ) { // Created date and time. - $meta['created_timestamp'] = strtotime( $iptc['2#055'][0] . ' ' . $iptc['2#060'][0] ); + $datetime = new DateTimeImmutable( $iptc['2#055'][0] . ' ' . $iptc['2#060'][0] ); + // Store as a RFC3339 formatted timestring as this includes both date, time, and timezone. + $meta['created'] = $datetime->format( DATE_RFC3339 ); + // Retain the original created timestamp for backcompat. + $meta['created_timestamp'] = $datetime->getTimestamp(); } if ( ! empty( $iptc['2#116'][0] ) ) { // Copyright. @@ -1029,7 +1053,17 @@ function wp_read_image_metadata( $file ) { $meta['camera'] = trim( $exif['Model'] ); } if ( empty( $meta['created_timestamp'] ) && ! empty( $exif['DateTimeDigitized'] ) ) { - $meta['created_timestamp'] = wp_exif_date2ts( $exif['DateTimeDigitized'] ); + $timezone = null; + if ( ! empty( $exif['UndefinedTag:0x9012'] ) ) { + $timezone = $exif['UndefinedTag:0x9012']; + } + + $datetime = wp_exif_datetime( $exif['DateTimeDigitized'], $timezone ); + + // Store as a RFC3339 formatted timestring as this includes both date, time, and timezone. + $meta['created'] = $datetime->format( DATE_RFC3339 ); + // Retain the original created timestamp for backcompat. + $meta['created_timestamp'] = $datetime->getTimestamp(); } if ( ! empty( $exif['FocalLength'] ) ) { $meta['focal_length'] = (string) $exif['FocalLength']; diff --git a/tests/phpunit/tests/date/exifDate.php b/tests/phpunit/tests/date/exifDate.php new file mode 100644 index 0000000000000..59f81e36e14d1 --- /dev/null +++ b/tests/phpunit/tests/date/exifDate.php @@ -0,0 +1,78 @@ +assertFalse( $datetime ); + } else { + $this->assertInstanceOf( 'DateTimeImmutable', $datetime ); + $this->assertSame( $expected, $datetime->format( DATE_RFC3339 ) ); + } + } + + public function data_wp_exif_datetime() { + return array( + 'valid date without timezone' => array( + '2004:07:22 17:14:35', + null, + '2004-07-22T17:14:35+00:00', + ), + 'valid date with timezone' => array( + '2004:07:22 17:14:35', + '+05:30', + '2004-07-22T17:14:35+05:30', + ), + 'invalid date format' => array( + 'not a date', + null, + false, + ), + 'invalid timezone' => array( + '2004:07:22 17:14:35', + 'Invalid/Timezone', + false, + ), + ); + } + + /** + * @dataProvider data_wp_exif_date2ts + * + * @ticket 49413 + * + * @param string $date_string Date string in EXIF format (Y:m:d H:i:s). + * @param int|bool $expected Expected Unix timestamp or false for invalid input. + */ + public function test_wp_exif_date2ts( $date_string, $expected ) { + $timestamp = wp_exif_date2ts( $date_string ); + $this->assertSame( $expected, $timestamp ); + } + + public function data_wp_exif_date2ts() { + return array( + 'valid date' => array( + '2004:07:22 17:14:35', + 1090516475, + ), + 'invalid date format' => array( + 'not a date', + false, + ), + ); + } +} diff --git a/tests/phpunit/tests/image/meta.php b/tests/phpunit/tests/image/meta.php index 88b2cbcef1e40..8fab7b93ae795 100644 --- a/tests/phpunit/tests/image/meta.php +++ b/tests/phpunit/tests/image/meta.php @@ -217,6 +217,7 @@ public function data_stream() { 'title' => 'IPTC Headline', 'orientation' => '0', 'keywords' => array(), + 'created' => '2004-07-22T17:14:35+00:00', ), ), 'Exif from a DMC-LX2 camera with keywords' => array( @@ -234,6 +235,7 @@ public function data_stream() { 'title' => 'Photoshop Document Ttitle', 'orientation' => '1', 'keywords' => array( 'beach', 'baywatch', 'LA', 'sunset' ), + 'created' => '2011-05-25T09:22:07+00:00', ), ), ); From 3bc15890a0a3b14e6ca2ffad2bd7af904a209c2b Mon Sep 17 00:00:00 2001 From: Sukhendu Sekhar Guria Date: Tue, 21 Jan 2025 01:25:26 +0530 Subject: [PATCH 2/3] Update indentation --- tests/phpunit/tests/image/meta.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/phpunit/tests/image/meta.php b/tests/phpunit/tests/image/meta.php index 8fab7b93ae795..aabf5e9b968c5 100644 --- a/tests/phpunit/tests/image/meta.php +++ b/tests/phpunit/tests/image/meta.php @@ -217,7 +217,7 @@ public function data_stream() { 'title' => 'IPTC Headline', 'orientation' => '0', 'keywords' => array(), - 'created' => '2004-07-22T17:14:35+00:00', + 'created' => '2004-07-22T17:14:35+00:00', ), ), 'Exif from a DMC-LX2 camera with keywords' => array( @@ -235,7 +235,7 @@ public function data_stream() { 'title' => 'Photoshop Document Ttitle', 'orientation' => '1', 'keywords' => array( 'beach', 'baywatch', 'LA', 'sunset' ), - 'created' => '2011-05-25T09:22:07+00:00', + 'created' => '2011-05-25T09:22:07+00:00', ), ), ); From 729e911137f5f467fb4003dfd3fa7b1c77be1ea3 Mon Sep 17 00:00:00 2001 From: Sukhendu Sekhar Guria Date: Tue, 21 Jan 2025 11:42:54 +0530 Subject: [PATCH 3/3] Update PHP DocBlock --- src/wp-admin/includes/image.php | 4 ++-- tests/phpunit/tests/date/exifDate.php | 12 ++++++------ 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/wp-admin/includes/image.php b/src/wp-admin/includes/image.php index 093eb2ca58093..8e5fe3ccfe095 100644 --- a/src/wp-admin/includes/image.php +++ b/src/wp-admin/includes/image.php @@ -806,13 +806,13 @@ function wp_exif_frac2dec( $str ) { } /** - * Convert the exif date format to DateTime object + * Convert the exif date format to DateTime object. * * @since 6.8.0 * * @param string $str A date string expected to be in Exif format (Y:m:d H:i:s). * @param string $timezone Optional. Timezone or offset string. - * @return DateTimeImmutable|false Return false if not valid date + * @return DateTimeImmutable|false Return Date and time object, otherwise false. */ function wp_exif_datetime( $str, $timezone = null ) { try { diff --git a/tests/phpunit/tests/date/exifDate.php b/tests/phpunit/tests/date/exifDate.php index 59f81e36e14d1..aab1b6464ac8d 100644 --- a/tests/phpunit/tests/date/exifDate.php +++ b/tests/phpunit/tests/date/exifDate.php @@ -7,10 +7,10 @@ class Tests_Date_Exif extends WP_UnitTestCase { /** - * @dataProvider data_wp_exif_datetime - * * @ticket 49413 * + * @dataProvider data_wp_exif_datetime + * * @param string $date_string Date string in EXIF format (Y:m:d H:i:s). * @param string|null $timezone Optional. Timezone or offset string. * @param string|bool $expected Expected RFC3339 formatted date string or false for invalid input. @@ -51,12 +51,12 @@ public function data_wp_exif_datetime() { } /** - * @dataProvider data_wp_exif_date2ts - * * @ticket 49413 * - * @param string $date_string Date string in EXIF format (Y:m:d H:i:s). - * @param int|bool $expected Expected Unix timestamp or false for invalid input. + * @dataProvider data_wp_exif_date2ts + * + * @param string $date_string Date string in EXIF format (Y:m:d H:i:s). + * @param int|bool $expected Expected Unix timestamp or false for invalid input. */ public function test_wp_exif_date2ts( $date_string, $expected ) { $timestamp = wp_exif_date2ts( $date_string );