Question

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.

Was it helpful?

Solution

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.

OTHER TIPS

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".

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);
}

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
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top