.NET で Exif 情報の日時を変更する

# はじめに

僕は以前 Delphi でファイルの日時を変更する Windows アプリを作りました。
[簡単エクスプローラ拡張 EzExpEx]
(http://hp.vector.co.jp/authors/VA029585/EzExpEx/)
これは残念なことにユニコードの名称のファイルを扱えないんですよね。
最新の Delphi でビルドし直せばいいのですが、僕が持っている Delphi ではだめなんです。
これを VB.NET で作り直したいとずっと思っていました。

僕のアプリが扱えるファイルの日時は
・ファイル自体の作成日時、更新日時
・Exif 情報の撮影日時、更新日時
・PDF ファイルの作成日時、更新日時
・概要情報の作成日時、更新日時

ファイル自体の日時を変更するのは .NET の標準機能で対応できますが、Exif 情報の日時などはどうでしょう。

## Exif 情報の日時を変更する

Exif 情報は、撮影日時や撮影条件など画像ファイルに埋込された情報です。

ファイルの Exif 情報は .NET の Image オブジェクトで変更できます。
・[画像のExif情報を取得する、設定する - DOBON.NET]
(https://dobon.net/vb/dotnet/graphics/getexifinfo.html)
・[.NET TIPS デジカメ画像のExif情報を取得するには？ - C# - ＠IT]
(https://atmarkit.itmedia.co.jp/fdotnet/dotnettips/047exif/exif.html)
これは楽でいいのですが、この方法で Exif 情報を変更すると画像そのものが読込時点でデコードされ保存時点でエンコードし直されて、微妙に画質が落ちてしまいます。

僕が Delphi で作ったアプリは、メタデータだけ読取して変更するようにしていました。
これを VB.NET で書き直しました。C# のコードも用意しました。

# Exif 情報の日時を取得する

```vb:VB.NET
        Dim Path As String = "●●●●.jpg"    '対象とするファイルのパス
        Dim TimeUpdated As DateTime    '更新日時を返す
        Dim UpdatedValid As Boolean = False    '更新日時を取得できたか返す
        Dim TimeOriginal As DateTime    '撮影日時を返す
        Dim OriginalValid As Boolean = False     '撮影日時を取得できたか返す
        Dim TimeDigitized As DateTime    'デジタル化日時を返す
        Dim DigitizedValid As Boolean = False     'デジタル化日時を取得できたか返す

        If Not System.IO.File.Exists(Path) Then
            Exit Function
        End If
        Try
            'ファイルを読取
            Using fs As New System.IO.FileStream(Path, System.IO.FileMode.Open, System.IO.FileAccess.Read)
                'TiffHeader を読取
                Dim TiffHeaderPos As Long
                Dim IsLittleEndian As Boolean
                Dim IfdNum As Integer
                If Not ReadTiffHeader(fs, TiffHeaderPos, IsLittleEndian, IfdNum) Then
                    Exit Function
                End If
                Dim IfdPos As Long = fs.Position
                For n As Integer = 0 To IfdNum - 1
                    Dim WordBuff As Byte() = {0, 0}
                    'IFD を読取
                    Dim IfdBuff As Byte() = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}
                    fs.Seek(IfdPos + n * IfdBuff.Length, System.IO.SeekOrigin.Begin)
                    If fs.Read(IfdBuff, 0, IfdBuff.Length) <> IfdBuff.Length Then Exit Function
                    Dim Tag As UInt16 = BitConverter.ToUInt16(If(IsLittleEndian And BitConverter.IsLittleEndian, {IfdBuff(0), IfdBuff(1)}, {IfdBuff(1), IfdBuff(0)}), 0)
                    Select Case Tag
                        Case &H132
                            '最後に変更された日時
                            If GetTimeFromIFD(fs, TiffHeaderPos, IsLittleEndian, IfdBuff, TimeUpdated) Then
                                UpdatedValid = True
                            End If
                        Case &H8769    'Exif データが見つかった
                            'エントリ数を取得
                            Dim Offset As Long = BitConverter.ToUInt32(If(IsLittleEndian And BitConverter.IsLittleEndian, {IfdBuff(8), IfdBuff(9), IfdBuff(10), IfdBuff(11)}, {IfdBuff(11), IfdBuff(10), IfdBuff(9), IfdBuff(8)}), 0)
                            fs.Seek(TiffHeaderPos + Offset, System.IO.SeekOrigin.Begin)
                            If fs.Read(WordBuff, 0, WordBuff.Length) <> WordBuff.Length Then Exit Function
                            Dim EntryNum As Integer = BitConverter.ToUInt16(If(IsLittleEndian And BitConverter.IsLittleEndian, {WordBuff(0), WordBuff(1)}, {WordBuff(1), WordBuff(0)}), 0)
                            Dim EntryPos As Long = fs.Position
                            For i As Integer = 0 To EntryNum - 1
                                'エントリを読取
                                fs.Seek(EntryPos + i * IfdBuff.Length, System.IO.SeekOrigin.Begin)
                                If fs.Read(IfdBuff, 0, IfdBuff.Length) <> IfdBuff.Length Then Exit Function
                                Tag = BitConverter.ToUInt16(If(IsLittleEndian And BitConverter.IsLittleEndian, {IfdBuff(0), IfdBuff(1)}, {IfdBuff(1), IfdBuff(0)}), 0)
                                Select Case Tag
                                    Case &H9003
                                        '撮影された日時
                                        If GetTimeFromIFD(fs, TiffHeaderPos, IsLittleEndian, IfdBuff, TimeOriginal) Then
                                            OriginalValid = True
                                        End If
                                    Case &H9004
                                        'デジタル化された日時
                                        If GetTimeFromIFD(fs, TiffHeaderPos, IsLittleEndian, IfdBuff, TimeDigitized) Then
                                            DigitizedValid = True
                                        End If
                                End Select
                            Next
                    End Select
                Next
            End Using
        Catch ex As Exception
            Exit Function
        End Try
```
```csharp:C#
            string Path = "●●●●.jpg";    // 対象とするファイルのパス
            DateTime TimeUpdated;    // 更新日時を返す
            bool UpdatedValid = false;    // 更新日時を取得できたか返す
            DateTime TimeOrijinal;    // 撮影日時を返す
            bool OriginalValid = false;    // 撮影日時を取得できたか返す
            DateTime TimeDigitized;    // デジタル化日時を返す
            bool DigitizedValid = false;    // デジタル化日時を取得できたか返す

            if (!System.IO.File.Exists(Path)) { 
                return false;
            }
            try {
                // ファイルを読取
                using (System.IO.FileStream fs = new System.IO.FileStream(Path, System.IO.FileMode.Open, System.IO.FileAccess.Read)) {
                    // TiffHeader を読取
                    long TiffHeaderPos = 0;
                    bool isLittleEndian = true;
                    int IfdNum = 0;
                    if (!RaadTiffHeader(fs, ref TiffHeaderPos, ref isLittleEndian, ref IfdNum)) {
                        return false;
                    }
                    long IfdPos = fs.Position;
                    for (int n = 0; n < IfdNum; n++) {
                        byte[] WordBuff = new byte[2];
                        // IFD を読取
                        byte[] IfdBuff = new byte[12];
                        fs.Seek(IfdPos + n * IfdBuff.Length, System.IO.SeekOrigin.Begin);
                        if (fs.Read(IfdBuff, 0, IfdBuff.Length) != IfdBuff.Length) return false;
                        UInt16 Tag = BitConverter.ToUInt16(isLittleEndian && BitConverter.IsLittleEndian ? new byte[] {IfdBuff[0], IfdBuff[1]} : new byte[] {IfdBuff[1], IfdBuff[0]}, 0);
                        switch (Tag) {
                            case 0x0132:
                                // 最後に変更された日時
                                if (GetTimeFromIFD(fs, TiffHeaderPos, isLittleEndian, IfdBuff, ref TimeUpdated)) {
                                    UpdatedValid = true;
                                }
                                break;
                            case 0x8769:    // Exif データが見つかった
                                // エントリ数を取得
                                long Offset = BitConverter.ToUInt32(isLittleEndian && BitConverter.IsLittleEndian ? new byte[] {IfdBuff[8], IfdBuff[9], IfdBuff[10], IfdBuff[11]} : new byte[] {IfdBuff[11], IfdBuff[10], IfdBuff[9], IfdBuff[8]}, 0);
                                fs.Seek(TiffHeaderPos + Offset, System.IO.SeekOrigin.Begin);
                                if (fs.Read(WordBuff, 0, WordBuff.Length) != WordBuff.Length) return false;
                                int EntryNum = BitConverter.ToUInt16(isLittleEndian && BitConverter.IsLittleEndian ? new byte[] {WordBuff[0], WordBuff[1]} : new byte[] {WordBuff[1], WordBuff[0]}, 0);
                                long EntryPos = fs.Position;
                                for (int i = 0; i < EntryNum; i++) {
                                    // エントリを読取
                                    fs.Seek(EntryPos + i * IfdBuff.Length, System.IO.SeekOrigin.Begin);
                                    if (fs.Read(IfdBuff, 0, IfdBuff.Length) != IfdBuff.Length) return false;
                                    Tag = BitConverter.ToUInt16(isLittleEndian && BitConverter.IsLittleEndian ? new byte[] {IfdBuff[0], IfdBuff[1]} : new byte[] {IfdBuff[1], IfdBuff[0]}, 0);
                                    switch (Tag) {
                                        case 0x9003:
                                            // 撮影された日時
                                            if (GetTimeFromIFD(fs, TiffHeaderPos, isLittleEndian, IfdBuff, ref TimeOriginal))
                                            {
                                                OriginalValid = true;
                                            }
                                            break;
                                        case 9004:
                                            // デジタル化された日時
                                            if (GetTimeFromIFD(fs, TiffHeaderPos, isLittleEndian, IfdBuff, ref TimeDigitized))
                                            {
                                                DigitizedValid = true;
                                            }
                                            break;
                                    }
                                }
                                break;
                        }
                    }
                }
            }
            catch (Exception) {
                return false;
            }
```

以下はヘルパー関数です。変更するときも使用します。

```vb:VB.NET
    Imports System.Runtime.InteropServices

    Private Function ReadTiffHeader(fs As System.IO.FileStream, ByRef TiffHeaderPos As Long, ByRef isLittleEndian As Boolean, ByRef IfdNum As Integer)
        ReadTiffHeader = False
        Dim WordBuff As Byte() = {0, 0}
        'JPEG ヘッダを確認
        If fs.Read(WordBuff, 0, WordBuff.Length) <> WordBuff.Length Then Exit Function
        If WordBuff(0) <> &HFF Or WordBuff(1) <> &HD8 Then Exit Function
        'APP1 マーカーを確認
        If fs.Read(WordBuff, 0, WordBuff.Length) <> WordBuff.Length Then Exit Function
        If WordBuff(0) <> &HFF Or WordBuff(1) <> &HE1 Then Exit Function
        'EXIF ヘッダを確認
        fs.Seek(2, System.IO.SeekOrigin.Current)
        Dim ExifHeaderBuff As Byte() = {0, 0, 0, 0, 0, 0}
        If fs.Read(ExifHeaderBuff, 0, ExifHeaderBuff.Length) <> ExifHeaderBuff.Length Then Exit Function
        If System.Text.Encoding.ASCII.GetString(ExifHeaderBuff).ToUpper().TrimEnd(vbNullChar) <> "EXIF" Then Exit Function
        'TIFF ヘッダを読取
        TiffHeaderPos = fs.Position
        Dim TiffHeaderBuff As Byte() = {0, 0, 0, 0, 0, 0, 0, 0}
        If fs.Read(TiffHeaderBuff, 0, TiffHeaderBuff.Length) <> TiffHeaderBuff.Length Then Exit Function
        If (TiffHeaderBuff(0) <> &H49 Or TiffHeaderBuff(1) <> &H49) And (TiffHeaderBuff(0) <> &H4D Or TiffHeaderBuff(1) <> &H4D) Then Exit Function
        isLittleEndian = (TiffHeaderBuff(0) = &H49 And TiffHeaderBuff(1) = &H49)
        Dim Tag As UInt16 = BitConverter.ToUInt16(If(isLittleEndian And BitConverter.IsLittleEndian, {TiffHeaderBuff(2), TiffHeaderBuff(3)}, {TiffHeaderBuff(3), TiffHeaderBuff(2)}), 0)
        If Tag <> &H2A Then Exit Function
        fs.Seek(2, System.IO.SeekOrigin.Current)
        'IFD 数を取得
        Dim Offset As Long = BitConverter.ToUInt32(If(isLittleEndian And BitConverter.IsLittleEndian, {TiffHeaderBuff(4), TiffHeaderBuff(5), TiffHeaderBuff(6), TiffHeaderBuff(7)}, {TiffHeaderBuff(7), TiffHeaderBuff(6), TiffHeaderBuff(5), TiffHeaderBuff(4)}), 0)
        fs.Seek(TiffHeaderPos + Offset, System.IO.SeekOrigin.Begin)
        If fs.Read(WordBuff, 0, WordBuff.Length) <> WordBuff.Length Then Exit Function
        IfdNum = BitConverter.ToUInt16(If(isLittleEndian And BitConverter.IsLittleEndian, {WordBuff(0), WordBuff(1)}, {WordBuff(1), WordBuff(0)}), 0)
        ReadTiffHeader = True
    End Function

    Private Function GetTimeFromIFD(fs As System.IO.FileStream, TiffHeaderPos As Long, isLittleEndian As Boolean, IfdBuf As Byte(), ByRef Time As DateTime) As Boolean
        GetTimeFromIFD = False
        Dim DataType As UInt16 = BitConverter.ToUInt16(If(isLittleEndian And BitConverter.IsLittleEndian, {IfdBuf(2), IfdBuf(3)}, {IfdBuf(3), IfdBuf(2)}), 0)
        If DataType <> 2 Then Exit Function
        Dim Offset As Long = BitConverter.ToUInt32(If(isLittleEndian And BitConverter.IsLittleEndian, {IfdBuf(8), IfdBuf(9), IfdBuf(10), IfdBuf(11)}, {IfdBuf(11), IfdBuf(10), IfdBuf(9), IfdBuf(8)}), 0)
        fs.Seek(TiffHeaderPos + Offset, System.IO.SeekOrigin.Begin)
        Dim Buff As Byte() = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}
        If fs.Read(Buff, 0, Buff.Length) <> Buff.Length Then Exit Function
        Try
            Dim yy As Integer = CInt(System.Text.Encoding.ASCII.GetString({Buff(0), Buff(1), Buff(2), Buff(3)}))
            Dim mm As Integer = CInt(System.Text.Encoding.ASCII.GetString({Buff(5), Buff(6)}))
            Dim dd As Integer = CInt(System.Text.Encoding.ASCII.GetString({Buff(8), Buff(9)}))
            Dim hh As Integer = CInt(System.Text.Encoding.ASCII.GetString({Buff(11), Buff(12)}))
            Dim nn As Integer = CInt(System.Text.Encoding.ASCII.GetString({Buff(14), Buff(15)}))
            Dim ss As Integer = CInt(System.Text.Encoding.ASCII.GetString({Buff(17), Buff(18)}))
            Time = New DateTime(yy, mm, dd, hh, nn, ss)
        Catch ex As Exception
            Exit Function
        End Try
        GetTimeFromIFD = True
    End Function
```
```csharp:C#
        private bool RaadTiffHeader(System.IO.FileStream fs, ref long TiffHeaderPos, ref bool isLittleEndian, ref int IfdNum)
        {
            byte[] WordBuff = new byte[2];
            // JPEG ヘッダを確認
            if (fs.Read(WordBuff, 0, WordBuff.Length) != WordBuff.Length) return false;
            if (WordBuff[0] != 0xff || WordBuff[1] != 0xd8) return false;
            // APP1 マーカーを確認
            if (fs.Read(WordBuff, 0, WordBuff.Length) != WordBuff.Length) return false;
            if (WordBuff[0] != 0xff || WordBuff[1] != 0xe1) return false;
            // EXIF ヘッダを確認
            fs.Seek(2, System.IO.SeekOrigin.Current);
            byte[] ExifHeaderBuff = new byte[6];
            if (fs.Read(ExifHeaderBuff, 0, ExifHeaderBuff.Length) != ExifHeaderBuff.Length) return false;
            if (System.Text.Encoding.ASCII.GetString(ExifHeaderBuff).ToUpper().TrimEnd('\0') != "EXIF") return false;
            // TIFF ヘッダを読取
            TiffHeaderPos = fs.Position;
            byte[] TiffHeaderBuff = new byte[8];
            if (fs.Read(TiffHeaderBuff, 0, TiffHeaderBuff.Length) != TiffHeaderBuff.Length) return false;
            if ((TiffHeaderBuff[0] != 0x49 || TiffHeaderBuff[1] != 0x49) && (TiffHeaderBuff[0] != 0x4d || TiffHeaderBuff[1] != 0x4d)) return false;
            isLittleEndian = (TiffHeaderBuff[0] == 0x49 || TiffHeaderBuff[1] == 0x49);
            UInt16 Tag = BitConverter.ToUInt16(isLittleEndian && BitConverter.IsLittleEndian ? new byte[] {TiffHeaderBuff[2], TiffHeaderBuff[3]} : new byte[] {TiffHeaderBuff[3], TiffHeaderBuff[2]}, 0);
            if (Tag != 0x2a) return false;
            fs.Seek(2, System.IO.SeekOrigin.Current);
            // IFD 数を取得
            long Offset = BitConverter.ToUInt32(isLittleEndian && BitConverter.IsLittleEndian ? new byte[] {TiffHeaderBuff[4], TiffHeaderBuff[5], TiffHeaderBuff[6], TiffHeaderBuff[7]} : new byte[] {TiffHeaderBuff[7], TiffHeaderBuff[6], TiffHeaderBuff[5], TiffHeaderBuff[4]}, 0);
            fs.Seek(TiffHeaderPos + Offset, System.IO.SeekOrigin.Begin);
            if (fs.Read(WordBuff, 0, WordBuff.Length) != WordBuff.Length) return false;
            IfdNum = BitConverter.ToUInt16(isLittleEndian && BitConverter.IsLittleEndian ? new byte[] {WordBuff[0], WordBuff[1]} : new byte[] {WordBuff[1], WordBuff[0]}, 0);
            return true;
        }

        private bool GetTimeFromIFD(System.IO.FileStream fs, long TiffHeaderPos, bool isLittleEndian, byte[] IfdBuff, ref DateTime Time)
        {
            UInt16 DataType = BitConverter.ToUInt16(isLittleEndian && BitConverter.IsLittleEndian ? new byte[] {IfdBuff[2], IfdBuff[3]} : new byte[] {IfdBuff[3], IfdBuff[2]}, 0);
            if (DataType != 2) return false;
            long Offset = BitConverter.ToUInt32(isLittleEndian && BitConverter.IsLittleEndian ? new byte[] {IfdBuff[8], IfdBuff[9], IfdBuff[10], IfdBuff[11]} : new byte[] {IfdBuff[11], IfdBuff[10], IfdBuff[9], IfdBuff[8]}, 0);
            fs.Seek(TiffHeaderPos + Offset, System.IO.SeekOrigin.Begin);
            byte[] Buff = new byte[20];
            if (fs.Read(Buff, 0, Buff.Length) != Buff.Length) return false;
            try {
                int yy = int.Parse(System.Text.Encoding.ASCII.GetString(new byte[] {Buff[0], Buff[1], Buff[2], Buff[3]}));
                int mm = int.Parse(System.Text.Encoding.ASCII.GetString(new byte[] {Buff[5], Buff[6]}));
                int dd = int.Parse(System.Text.Encoding.ASCII.GetString(new byte[] {Buff[8], Buff[9]}));
                int hh = int.Parse(System.Text.Encoding.ASCII.GetString(new byte[] {Buff[11], Buff[12]}));
                int nn = int.Parse(System.Text.Encoding.ASCII.GetString(new byte[] {Buff[14], Buff[15]}));
                int ss = int.Parse(System.Text.Encoding.ASCII.GetString(new byte[] {Buff[17], Buff[18]}));
                Time = new DateTime(yy, mm, dd, hh, nn, ss);
            }
            catch {
                return false;
            }
            return true;
        }
```

Exif 情報の日時を取得するだけなら、.NET の `Image` オブジェクトを使えばいいかも知れません。

# Exif 情報の日時を変更する

```vb:VB.NET
        Dim Path As String = "●●●●.jpg"    '対象とするファイルのパス
        Dim TimeUpdated As DateTime    '変更する更新日時を指定する
        Dim UpdatedValid As Boolean = True    '更新日時を変更するか指定する
        Dim TimeOriginal As DateTime    '変更する撮影日時を指定する
        Dim OriginalValid As Boolean = True     '撮影日時を変更するか指定する
        Dim TimeDigitized As DateTime    '変更するデジタル化日時を指定する
        Dim DigitizedValid As Boolean = True     'デジタル化日時を変更するか指定する

        If Not System.IO.File.Exists(Path) Then
            Exit Function
        End If
        Try
            'ファイルを読取
            Using fs As New System.IO.FileStream(Path, System.IO.FileMode.Open, System.IO.FileAccess.ReadWrite)
                'TiffHeader を読取
                Dim TiffHeaderPos As Long
                Dim isLittleEndian As Boolean
                Dim IfdNum As Integer
                If Not ReadTiffHeader(fs, TiffHeaderPos, isLittleEndian, IfdNum) Then
                    Exit Function
                End If
                Dim IfdPos As Long = fs.Position
                For n As Integer = 0 To IfdNum - 1
                    Dim WordBuff As Byte() = {0, 0}
                    'IFD を読取
                    Dim IfdBuff As Byte() = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}
                    fs.Seek(IfdPos + n * IfdBuff.Length, System.IO.SeekOrigin.Begin)
                    If fs.Read(IfdBuff, 0, IfdBuff.Length) <> IfdBuff.Length Then Exit Function
                    Dim Tag As UInt16 = BitConverter.ToUInt16(If(isLittleEndian And BitConverter.IsLittleEndian, {IfdBuff(0), IfdBuff(1)}, {IfdBuff(1), IfdBuff(0)}), 0)
                    Select Case Tag
                        Case &H132
                            '最後に変更された日時
                            If Not UpdatedValid Then
                                Continue For
                            End If
                            If Not SetTimeToIFD(fs, TiffHeaderPos, isLittleEndian, IfdBuff, TimeUpdated) Then
                                Exit Function
                            End If
                        Case &H8769
                            'Exif データが見つかった
                            Dim Offset As Long = BitConverter.ToUInt32(If(isLittleEndian And BitConverter.IsLittleEndian, {IfdBuff(8), IfdBuff(9), IfdBuff(10), IfdBuff(11)}, {IfdBuff(11), IfdBuff(10), IfdBuff(9), IfdBuff(8)}), 0)
                            fs.Seek(TiffHeaderPos + Offset, System.IO.SeekOrigin.Begin)
                            'エントリ数を取得
                            If fs.Read(WordBuff, 0, WordBuff.Length) <> WordBuff.Length Then Exit Function
                            Dim EntryNum As Integer = BitConverter.ToUInt16(If(isLittleEndian And BitConverter.IsLittleEndian, {WordBuff(0), WordBuff(1)}, {WordBuff(1), WordBuff(0)}), 0)
                            Dim EntryPos As Long = fs.Position
                            For i As Integer = 0 To EntryNum - 1
                                'エントリを読取
                                fs.Seek(EntryPos + i * IfdBuff.Length, System.IO.SeekOrigin.Begin)
                                If fs.Read(IfdBuff, 0, IfdBuff.Length) <> IfdBuff.Length Then Exit Function
                                Tag = BitConverter.ToUInt16(If(isLittleEndian And BitConverter.IsLittleEndian, {IfdBuff(0), IfdBuff(1)}, {IfdBuff(1), IfdBuff(0)}), 0)
                                Select Case Tag
                                    Case &H9003
                                        '撮影された日時
                                        If Not OriginalValid Then
                                            Continue For
                                        End If
                                        If Not SetTimeToIFD(fs, TiffHeaderPos, isLittleEndian, IfdBuff, TimeOriginal) Then
                                            Exit Function
                                        End If
                                    Case &H9004
                                        'デジタル化された日時
                                        If Not DigitizedValid Then
                                            Continue For
                                        End If
                                        If Not SetTimeToIFD(fs, TiffHeaderPos, isLittleEndian, IfdBuff, TimeDigitized) Then
                                            Exit Function
                                        End If
                                End Select
                            Next
                    End Select
                Next
            End Using
        Catch ex As Exception
            Exit Function
        End Try
    End Function
```
```csharp:C#
            string Path = "●●●●.jpg";    // 対象とするファイルのパス
            DateTime TimeUpdated;    // 変更する更新日時を指定する
            bool UpdatedValid = true;    // 更新日時を変更するか指定する
            DateTime TimeOrijinal;    // 変更する撮影日時を指定する
            bool OriginalValid = true;    // 撮影日時を変更するか指定する
            DateTime TimeDigitized;    // 変更するデジタル化日時を指定する
            bool DigitizedValid = true;    // デジタル化日時を変更するか指定する

            if (!System.IO.File.Exists(Path)) {
                return false;
            }
            try {
                // ファイルを読取
                using (System.IO.FileStream fs = new System.IO.FileStream(Path, System.IO.FileMode.Open, System.IO.FileAccess.ReadWrite)) {
                    // TiffHeader を読取
                    long TiffHeaderPos = 0;
                    bool isLittleEndian = true;
                    int IfdNum = 0;
                    if (!RaadTiffHeader(fs, ref TiffHeaderPos, ref isLittleEndian, ref IfdNum))
                    {
                        return false;
                    }
                    long IfdPos = fs.Position;
                    for (int n = 0; n < IfdNum; n++) {
                        byte[] WordBuff = new byte[2];
                        // IFD を読取
                        byte[] IfdBuff = new byte[12];
                        fs.Seek(IfdPos + n * IfdBuff.Length, System.IO.SeekOrigin.Begin);
                        if (fs.Read(IfdBuff, 0, IfdBuff.Length) != IfdBuff.Length) return false;
                        UInt16 Tag = BitConverter.ToUInt16(isLittleEndian && BitConverter.IsLittleEndian ? new byte[] {IfdBuff[0], IfdBuff[1]} : new byte[] {IfdBuff[1], IfdBuff[0]}, 0);
                        switch (Tag) {
                            case 0x0132:
                                // 最後に変更された日時
                                if (!UpdatedValid) {
                                    continue;
                                }
                                if (!SetTimeToIFD(fs, TiffHeaderPos, isLittleEndian, IfdBuff, TimeUpdated)) {
                                    return false;
                                }
                                break;
                            case 0x8769:    // Exif データが見つかった
                                // エントリ数を取得
                                long Offset = BitConverter.ToUInt32(isLittleEndian && BitConverter.IsLittleEndian ? new byte[] {IfdBuff[8], IfdBuff[9], IfdBuff[10], IfdBuff[11]} : new byte[] {IfdBuff[11], IfdBuff[10], IfdBuff[9], IfdBuff[8]}, 0);
                                fs.Seek(TiffHeaderPos + Offset, System.IO.SeekOrigin.Begin);
                                if (fs.Read(WordBuff, 0, WordBuff.Length) != WordBuff.Length) return false;
                                int EntryNum = BitConverter.ToUInt16(isLittleEndian && BitConverter.IsLittleEndian ? new byte[] {WordBuff[0], WordBuff[1]} : new byte[] {WordBuff[1], WordBuff[0]}, 0);
                                long EntryPos = fs.Position;
                                for (int i = 0; i < EntryNum; i++) {
                                    // エントリを読取
                                    fs.Seek(EntryPos + i * IfdBuff.Length, System.IO.SeekOrigin.Begin);
                                    if (fs.Read(IfdBuff, 0, IfdBuff.Length) != IfdBuff.Length) return false;
                                    Tag = BitConverter.ToUInt16(isLittleEndian && BitConverter.IsLittleEndian ? new byte[] {IfdBuff[0], IfdBuff[1]} : new byte[] {IfdBuff[1], IfdBuff[0]}, 0);
                                    switch (Tag) {
                                        case 0x9003:
                                            // 撮影された日時
                                            if (!OriginalValid) {
                                                continue;
                                            }
                                            if (!SetTimeToIFD(fs, TiffHeaderPos, isLittleEndian, IfdBuff, TimeOriginal)) {
                                                return false;
                                            }
                                            break;
                                        case 9004:
                                            // デジタル化された日時
                                            if (!DigitizedValid) {
                                                continue;
                                            }
                                            if (!SetTimeToIFD(fs, TiffHeaderPos, isLittleEndian, IfdBuff, TimeDigitized)) {
                                                return false;
                                            }
                                            break;
                                    }
                                }
                                break;
                        }
                    }
                }
            }
            catch (Exception) {
                return false;
            }
```

以下はヘルパー関数です。

```vb:VB.NET
    Private Function SetTimeToIFD(fs As System.IO.FileStream, TiffHeaderPos As Long, isLittleEndian As Boolean, IfdBuff As Byte(), Time As DateTime) As Boolean
        SetTimeToIFD = False
        Dim DataType As UInt16 = BitConverter.ToUInt16(If(isLittleEndian And BitConverter.IsLittleEndian, {IfdBuff(2), IfdBuff(3)}, {IfdBuff(3), IfdBuff(2)}), 0)
        If DataType <> 2 Then Exit Function
        Try
            Dim Offset As Long = BitConverter.ToUInt32(If(isLittleEndian And BitConverter.IsLittleEndian, {IfdBuff(8), IfdBuff(9), IfdBuff(10), IfdBuff(11)}, {IfdBuff(11), IfdBuff(10), IfdBuff(9), IfdBuff(8)}), 0)
            fs.Seek(TiffHeaderPos + Offset, System.IO.SeekOrigin.Begin)
            Dim Buff As Byte() = System.Text.Encoding.ASCII.GetBytes(Format(Time, "yyyy:MM:dd HH:mm:ss"))
            fs.Write(Buff, 0, Buff.Length)
        Catch ex As Exception
            Exit Function
        End Try
        SetTimeToIFD = True
    End Function
```
```csharp:C#
        private bool SetTimeToIFD(System.IO.FileStream fs, long TiffHeaderPos, bool isLittleEndian, byte[] IfdBuff, DateTime Time)
        {
            UInt16 DataType = BitConverter.ToUInt16(isLittleEndian && BitConverter.IsLittleEndian ? new byte[] {IfdBuff[2], IfdBuff[3]} : new byte[] {IfdBuff[3], IfdBuff[2]}, 0);
            if (DataType != 2) return false;
            try {
                long Offset = BitConverter.ToUInt32(isLittleEndian && BitConverter.IsLittleEndian ? new byte[] {IfdBuff[8], IfdBuff[9], IfdBuff[10], IfdBuff[11]} : new byte[] {IfdBuff[11], IfdBuff[10], IfdBuff[9], IfdBuff[8]}, 0);
                fs.Seek(TiffHeaderPos + Offset, System.IO.SeekOrigin.Begin);
                byte[] Buff = System.Text.Encoding.ASCII.GetBytes(Time.ToString("yyyy:MM:dd HH:mm:ss"));
                fs.Write(Buff, 0, Buff.Length);
            }
            catch {
                return false;
            }
            return true;
        }
```


上記のコードは、Exif 情報にセットされている既存の日時を書換しています。既存の日時がセットされていないと書換できません。
そのときは、.NET の `Image` オブジェクトを使用するしかないですね。