Repository: SixLabors/ImageSharp

Issue:
ExifTag.TimeZoneOffset is defined as uint[], but the TIFF spec defines it as signed short
### Prerequisites

- [X] I have written a descriptive issue title
- [X] I have verified that I am running the latest version of ImageSharp
- [X] I have verified if the problem exist in both `DEBUG` and `RELEASE` mode
- [X] I have searched [open](https://github.com/SixLabors/ImageSharp/issues) and [closed](https://github.com/SixLabors/ImageSharp/issues?q=is%3Aissue+is%3Aclosed) issues to ensure it has not already been reported

### ImageSharp version

3.0.1

### Other ImageSharp packages and versions

?

### Environment (Operating system, version and so on)

Windows 10 x64

### .NET Framework version

.NET 7

### Description

The TimeZoneOffset Exif tag is defined in the code as a uint[] (https://github.com/SixLabors/ImageSharp/blob/a49862a755fb24804043390f391839416bf4547a/src/ImageSharp/Metadata/Profiles/Exif/Tags/ExifTag.LongArray.cs#L62)

However, the specifications for TIFF/EP which defines that tag seem to require a signed short (optionally two signed shorts). See Page 19 and 40 of the TIFF/EP spec : https://web.archive.org/web/20060212163025/http://www.map.tu.chiba-u.ac.jp/IEC/100/TA2/recdoc/N4378.pdf

This requirement is also encoded in the TIFF verification code in JHove, which then flags the file as non-conformant (https://jhove.openpreservation.org/modules/tiff/tags/ , https://jhove.openpreservation.org/)

As a side note, since ExifTag<T> doesn't have a public constructor, I cannot create my own tags to work around this issue (or to add custom tags to the file).

### Steps to Reproduce

Set the DateTimeOffsetTag in a tiff file, then save the file

```
var image = Image.Load(sourceTifFile);
image.Frames[0].Metadata.ExifProfile = new ExifProfile();
image.Frames[0].Metadata.ExifProfile.SetValue(ExifTag.TimeZoneOffset, new uint[] {1});
image.Save(outputTifFile);
```

Now run the output tiff file through jhove verification

```
robin@ubuntu:~$ jhove -m TIFF-hul Output.tif 
Jhove (Rel. 1.20.0, 2019-01-19)
 Date: 2023-04-13 13:33:05 PDT
 RepresentationInformation: Output.tif
  ReportingModule: TIFF-hul, Rel. 1.8 (2017-05-11)
  LastModified: 2023-04-13 13:27:13 PDT
  Size: 343128
  Format: TIFF
  Status: Not well-formed
  SignatureMatches:
   TIFF-hul
  ErrorMessage: Type mismatch for tag 34858; expecting 8, saw 4
  MIMEtype: image/tiff

```

You can install jhove on an ubuntu box  with

`sudo apt-get install jhove default-jre`

Note: I ran the ImageSharp test code on Windows, just used Ubuntu for testing as the jhove install is easier




### Images

_No response_


Hints:
That looks like a mistake indeed but changing this would introduce a breaking change. But I do wonder if anyone is using this since we implemented this incorrectly. Maybe we could patch this in a minor release and make a note about this minor breaking change in the release notes?
To avoid a breaking change you could attribute the existing TimeZoneOffset property as [Obsolete] and add a new property with the correct definition.


While it affects the public API I don’t consider bug fixes like this to require minor versions. I’d be happy shipping a fix without one. 
I'm happy for this to be fixed in v3.1

Patch:
diff --git a/src/ImageSharp/Metadata/Profiles/Exif/Tags/ExifTag.LongArray.cs b/src/ImageSharp/Metadata/Profiles/Exif/Tags/ExifTag.LongArray.cs
index 741df50f2d..4767ca852e 100644
--- a/src/ImageSharp/Metadata/Profiles/Exif/Tags/ExifTag.LongArray.cs
+++ b/src/ImageSharp/Metadata/Profiles/Exif/Tags/ExifTag.LongArray.cs
@@ -56,11 +56,6 @@ public abstract partial class ExifTag
     /// </summary>
     public static ExifTag<uint[]> IntergraphRegisters { get; } = new ExifTag<uint[]>(ExifTagValue.IntergraphRegisters);
 
-    /// <summary>
-    /// Gets the TimeZoneOffset exif tag.
-    /// </summary>
-    public static ExifTag<uint[]> TimeZoneOffset { get; } = new ExifTag<uint[]>(ExifTagValue.TimeZoneOffset);
-
     /// <summary>
     /// Gets the offset to child IFDs exif tag.
     /// </summary>
diff --git a/src/ImageSharp/Metadata/Profiles/Exif/Tags/ExifTag.SignedShortArray.cs b/src/ImageSharp/Metadata/Profiles/Exif/Tags/ExifTag.SignedShortArray.cs
new file mode 100644
index 0000000000..d6a9205143
--- /dev/null
+++ b/src/ImageSharp/Metadata/Profiles/Exif/Tags/ExifTag.SignedShortArray.cs
@@ -0,0 +1,13 @@
+// Copyright (c) Six Labors.
+// Licensed under the Six Labors Split License.
+
+namespace SixLabors.ImageSharp.Metadata.Profiles.Exif;
+
+/// <content/>
+public abstract partial class ExifTag
+{
+    /// <summary>
+    /// Gets the TimeZoneOffset exif tag.
+    /// </summary>
+    public static ExifTag<short[]> TimeZoneOffset { get; } = new ExifTag<short[]>(ExifTagValue.TimeZoneOffset);
+}
diff --git a/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifSignedShortArray.cs b/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifSignedShortArray.cs
index 8023fb8bca..206417f667 100644
--- a/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifSignedShortArray.cs
+++ b/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifSignedShortArray.cs
@@ -5,6 +5,11 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Exif;
 
 internal sealed class ExifSignedShortArray : ExifArrayValue<short>
 {
+    public ExifSignedShortArray(ExifTag<short[]> tag)
+        : base(tag)
+    {
+    }
+
     public ExifSignedShortArray(ExifTagValue tag)
         : base(tag)
     {
diff --git a/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifValues.cs b/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifValues.cs
index 8aa54c3a40..a4217fe026 100644
--- a/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifValues.cs
+++ b/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifValues.cs
@@ -144,8 +144,6 @@ internal static partial class ExifValues
                 return new ExifLongArray(ExifTag.StripRowCounts);
             case ExifTagValue.IntergraphRegisters:
                 return new ExifLongArray(ExifTag.IntergraphRegisters);
-            case ExifTagValue.TimeZoneOffset:
-                return new ExifLongArray(ExifTag.TimeZoneOffset);
             case ExifTagValue.SubIFDs:
                 return new ExifLongArray(ExifTag.SubIFDs);
 
@@ -417,6 +415,9 @@ internal static partial class ExifValues
             case ExifTagValue.Decode:
                 return new ExifSignedRationalArray(ExifTag.Decode);
 
+            case ExifTagValue.TimeZoneOffset:
+                return new ExifSignedShortArray(ExifTag.TimeZoneOffset);
+
             case ExifTagValue.ImageDescription:
                 return new ExifString(ExifTag.ImageDescription);
             case ExifTagValue.Make:


Test patch:
diff --git a/tests/ImageSharp.Tests/Metadata/Profiles/Exif/Values/ExifValuesTests.cs b/tests/ImageSharp.Tests/Metadata/Profiles/Exif/Values/ExifValuesTests.cs
index 1adb7bd556..bf1ef1a9b0 100644
--- a/tests/ImageSharp.Tests/Metadata/Profiles/Exif/Values/ExifValuesTests.cs
+++ b/tests/ImageSharp.Tests/Metadata/Profiles/Exif/Values/ExifValuesTests.cs
@@ -70,8 +70,7 @@ public class ExifValuesTests
         { ExifTag.JPEGDCTables },
         { ExifTag.JPEGACTables },
         { ExifTag.StripRowCounts },
-        { ExifTag.IntergraphRegisters },
-        { ExifTag.TimeZoneOffset }
+        { ExifTag.IntergraphRegisters }
     };
 
     public static TheoryData<ExifTag> NumberTags => new TheoryData<ExifTag>
@@ -235,6 +234,11 @@ public class ExifValuesTests
         { ExifTag.Decode }
     };
 
+    public static TheoryData<ExifTag> SignedShortArrayTags => new TheoryData<ExifTag>
+    {
+        { ExifTag.TimeZoneOffset }
+    };
+
     public static TheoryData<ExifTag> StringTags => new TheoryData<ExifTag>
     {
         { ExifTag.ImageDescription },
@@ -559,6 +563,21 @@ public void ExifSignedRationalArrayTests(ExifTag tag)
         Assert.Equal(expected, typed.Value);
     }
 
+
+    [Theory]
+    [MemberData(nameof(SignedShortArrayTags))]
+    public void ExifSignedShortArrayTests(ExifTag tag)
+    {
+        short[] expected = new short[] { 21, 42 };
+        ExifValue value = ExifValues.Create(tag);
+
+        Assert.False(value.TrySetValue(expected.ToString()));
+        Assert.True(value.TrySetValue(expected));
+
+        var typed = (ExifSignedShortArray)value;
+        Assert.Equal(expected, typed.Value);
+    }
+
     [Theory]
     [MemberData(nameof(StringTags))]
     public void ExifStringTests(ExifTag tag)