Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improve EXIF timestamp handling with timezone support #8148

Open
wants to merge 3 commits into
base: trunk
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
46 changes: 40 additions & 6 deletions src/wp-admin/includes/image.php
Original file line number Diff line number Diff line change
Expand Up @@ -805,6 +805,25 @@ function wp_exif_frac2dec( $str ) {
return $numerator / $denominator;
}

/**
* Convert the exif date format to DateTime object
Sukhendu2002 marked this conversation as resolved.
Show resolved Hide resolved
*
* @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
Sukhendu2002 marked this conversation as resolved.
Show resolved Hide resolved
*/
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.
*
Expand All @@ -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;
}

/**
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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'];
Expand Down
78 changes: 78 additions & 0 deletions tests/phpunit/tests/date/exifDate.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
<?php

/**
* @group date
* @group datetime
*/
class Tests_Date_Exif extends WP_UnitTestCase {

/**
* @dataProvider data_wp_exif_datetime
*
* @ticket 49413
Sukhendu2002 marked this conversation as resolved.
Show resolved Hide resolved
*
* @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.
*/
public function test_wp_exif_datetime( $date_string, $timezone, $expected ) {
$datetime = wp_exif_datetime( $date_string, $timezone );
if ( false === $expected ) {
$this->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
Sukhendu2002 marked this conversation as resolved.
Show resolved Hide resolved
*
* @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.
Sukhendu2002 marked this conversation as resolved.
Show resolved Hide resolved
*/
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,
),
);
}
}
2 changes: 2 additions & 0 deletions tests/phpunit/tests/image/meta.php
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand All @@ -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',
),
),
);
Expand Down
Loading