Reputation: 8614
I am writing a C# program that extracts the EXIF DateTimeOriginal
field from a JPEG file, if that property is in the data, and I need to parse it into a DateTime
value.
The code I have is:
BitmapFrame src = decoder.Frames[ 0 ];
if ( src.Metadata != null ) {
BitmapMetadata metaData = (BitmapMetadata) src.Metadata;
if ( metaData.ContainsQuery( "/app1/{ushort=0}/{ushort=34665}/{ushort=36867}" ) ) {
object o = metaData.GetQuery( "/app1/{ushort=0}/{ushort=34665}/{ushort=36867}" );
if ( o != null && o is string ) {
string originalDate = Convert.ToString( o );
if ( originalDate != null ) {
if ( !DateTime.TryParseExact( originalDate.Trim(), "yyyy:MM:dd hh:mm:ss", CultureInfo.InvariantCulture, DateTimeStyles.AssumeLocal, out createDate ) ) {
// Sets createDate to a default value if the date doesn't parse.
}
}
}
}
}
However, the format string in the call to TryParseExact
doesn't work, as the code executes the code that is replaced with the comment.
What's the right way to write the format string? The value in the DateTimeOriginal
property is formatted as YYYY:MM:DD HH:MM:SS. It's the colons in between the YYYY, MM, and DD specifiers that are killing me. Why'd they use colons??
EDIT
I tried changing the format specifier string to "yyyy\:MM\dd hh\:mm:\ss" but that didn't work, either.
Upvotes: 10
Views: 14179
Reputation: 233347
I recently rewrote the application that I mention in my other answer. This time, I wrote it in F#. The relevant part doing the work is this:
[<Literal>]
let private exifDateTaken = 0x0132
[<Literal>]
let private exifDateTimeOriginal = 0x9003
let private tryParseDate s =
let res =
DateTime.TryParseExact(
s,
"yyyy:MM:dd HH:mm:ss",
CultureInfo.InvariantCulture,
DateTimeStyles.None)
match res with
| true, dt -> Some dt
| _ -> None
let extractDateTaken (fi : FileInfo) =
let extractExif (img : Image) exif =
if img.PropertyIdList |> Array.contains exif
then
let pi = img.GetPropertyItem exif
Some (Encoding.ASCII.GetString(pi.Value, 0, pi.Len - 1))
else None
use photo = Image.FromFile fi.FullName
[ exifDateTimeOriginal; exifDateTaken ]
|> Seq.choose (extractExif photo)
|> Seq.tryHead
|> Option.bind tryParseDate
Upvotes: 2
Reputation: 8614
Thanks to Mark Seemann & Markus, I got this figured out finally. The time in the EXIF data is in 24 hour / military time. The "hh" format specifier in the string is for 12 hour time with an AM/PM. The time I was passing was at 14:14, or 2:14 PM. In 12 hour time, "14" is an invalid time.
So the correct format specifier is "yyyy:MM:dd HH:mm:ss".
Upvotes: 4
Reputation: 22501
This link describes a way that parses the individual parts of the string instead of parsing it using DateTime.Parse:
/// <summary>
/// Returns the EXIF Image Data of the Date Taken.
/// </summary>
/// <param name="getImage">Image (If based on a file use Image.FromFile(f);)</param>
/// <returns>Date Taken or Null if Unavailable</returns>
public static DateTime? DateTaken(Image getImage)
{
int DateTakenValue = 0x9003; //36867;
if (!getImage.PropertyIdList.Contains(DateTakenValue))
return null;
string dateTakenTag = System.Text.Encoding.ASCII.GetString(getImage.GetPropertyItem(DateTakenValue).Value);
string[] parts = dateTakenTag.Split(':', ' ');
int year = int.Parse(parts[0]);
int month = int.Parse(parts[1]);
int day = int.Parse(parts[2]);
int hour = int.Parse(parts[3]);
int minute = int.Parse(parts[4]);
int second = int.Parse(parts[5]);
return new DateTime(year, month, day, hour, minute, second);
}
Upvotes: 2
Reputation: 233347
Here's a code snippet from an old program I have lying around that does something very similar to this:
string dateTakenText;
using (Image photo = Image.FromFile(file.Name))
{
PropertyItem pi = photo.GetPropertyItem(Program.propertyTagExifDTOrig_);
ASCIIEncoding enc = new ASCIIEncoding();
dateTakenText = enc.GetString(pi.Value, 0, pi.Len - 1);
}
if (string.IsNullOrEmpty(dateTakenText))
{
continue;
}
DateTime dateTaken;
if (!DateTime.TryParseExact(dateTakenText, "yyyy:MM:dd HH:mm:ss",
CultureInfo.CurrentCulture, DateTimeStyles.None, out dateTaken))
{
continue;
}
This code snippet sits inside a foreach
loop, which explains the use of the continue
keyword.
This is code from a program I wrote sometime back in 2002 or 2003, and I've been using it regularly since then; it works pretty reliably.
Upvotes: 14