From 4d87cf05b9d6eccd1fe6ddfc68775b9a610ecbb9 Mon Sep 17 00:00:00 2001
From: RudyTheDev <3857299+RudyTheDev@users.noreply.github.com>
Date: Thu, 4 Aug 2022 13:08:44 +0300
Subject: [PATCH] Read and write miliseconds in photo taken timestamp if
present or if manually requested to fix the full problem of #82
---
ExifLibrary/ExifBitConverter.cs | 56 +++++++++++++++++++++++----
ExifLibrary/ExifExtendedProperty.cs | 30 ++++++++++++--
ExifLibrary/ExifPropertyCollection.cs | 8 ++--
ExifLibrary/ExifPropertyFactory.cs | 10 ++---
4 files changed, 83 insertions(+), 21 deletions(-)
diff --git a/ExifLibrary/ExifBitConverter.cs b/ExifLibrary/ExifBitConverter.cs
index 54cb56a..4d07d12 100644
--- a/ExifLibrary/ExifBitConverter.cs
+++ b/ExifLibrary/ExifBitConverter.cs
@@ -59,7 +59,7 @@ public static string ToString(byte[] data)
///
/// Returns a DateTime object converted from the given byte array.
///
- public static DateTime ToDateTime(byte[] data, bool hastime)
+ public static DateTime ToDateTime(byte[] data, bool hastime, out bool preserveMilliseconds)
{
string str = ToAscii(data, Encoding.ASCII);
string[] parts = str.Split(new char[] { ':', ' ' });
@@ -68,27 +68,58 @@ public static DateTime ToDateTime(byte[] data, bool hastime)
if (hastime && parts.Length == 6)
{
// yyyy:MM:dd HH:mm:ss
- // This is the expected format though some cameras
+ // This is the expected format through some cameras
// can use single digits. See Issue 21.
// Also, the seconds can be non-decimal (e.g. 2016:07:31 10:10:20.291), so use double to parse. See Issue 82
- return new DateTime(int.Parse(parts[0]), int.Parse(parts[1]), int.Parse(parts[2]), int.Parse(parts[3]), int.Parse(parts[4]), (int)double.Parse(parts[5]));
+
+ if (parts[5].Contains("."))
+ {
+ string[] secondParts = parts[5].Split('.');
+
+ preserveMilliseconds = true;
+ return new DateTime(
+ int.Parse(parts[0]),
+ int.Parse(parts[1]),
+ int.Parse(parts[2]),
+ int.Parse(parts[3]),
+ int.Parse(parts[4]),
+ int.Parse(secondParts[0]),
+ int.Parse(secondParts[1])
+ );
+ }
+ else
+ {
+ preserveMilliseconds = false;
+ return new DateTime(
+ int.Parse(parts[0]),
+ int.Parse(parts[1]),
+ int.Parse(parts[2]),
+ int.Parse(parts[3]),
+ int.Parse(parts[4]),
+ int.Parse(parts[5])
+ );
+ }
}
else if (!hastime && parts.Length == 3)
{
// yyyy:MM:dd
+ preserveMilliseconds = false;
return new DateTime(int.Parse(parts[0]), int.Parse(parts[1]), int.Parse(parts[2]));
}
else
{
+ preserveMilliseconds = false;
return DateTime.MinValue;
}
}
catch (ArgumentOutOfRangeException)
{
+ preserveMilliseconds = false;
return DateTime.MinValue;
}
catch (ArgumentException)
{
+ preserveMilliseconds = false;
return DateTime.MinValue;
}
}
@@ -96,9 +127,9 @@ public static DateTime ToDateTime(byte[] data, bool hastime)
///
/// Returns a DateTime object converted from the given byte array.
///
- public static DateTime ToDateTime(byte[] data)
+ public static DateTime ToDateTime(byte[] data, out bool preserveMilliseconds)
{
- return ToDateTime(data, true);
+ return ToDateTime(data, true, out preserveMilliseconds);
}
///
@@ -294,13 +325,22 @@ public static byte[] GetBytes(string value, Encoding encoding)
///
/// Converts the given datetime to an array of bytes with a null terminator.
///
- public static byte[] GetBytes(DateTime value, bool hastime)
+ public static byte[] GetBytes(DateTime value, bool hastime, bool preserveMilliseconds)
{
- string str = "";
+ string str;
+
if (hastime)
- str = value.ToString("yyyy:MM:dd HH:mm:ss", System.Globalization.CultureInfo.InvariantCulture);
+ {
+ if (preserveMilliseconds)
+ str = value.ToString("yyyy:MM:dd HH:mm:ss.fff", System.Globalization.CultureInfo.InvariantCulture);
+ else
+ str = value.ToString("yyyy:MM:dd HH:mm:ss", System.Globalization.CultureInfo.InvariantCulture);
+ }
else
+ {
str = value.ToString("yyyy:MM:dd", System.Globalization.CultureInfo.InvariantCulture);
+ }
+
return GetBytes(str, true, Encoding.ASCII);
}
diff --git a/ExifLibrary/ExifExtendedProperty.cs b/ExifLibrary/ExifExtendedProperty.cs
index 070c218..ce50541 100644
--- a/ExifLibrary/ExifExtendedProperty.cs
+++ b/ExifLibrary/ExifExtendedProperty.cs
@@ -125,25 +125,38 @@ public override ExifInterOperability Interoperability
///
public class ExifDateTime : ExifProperty
{
+ private readonly bool mPreserveMilliseconds;
protected DateTime mValue;
protected override object _Value { get { return Value; } set { Value = (DateTime)value; } }
public new DateTime Value { get { return mValue; } set { mValue = value; } }
static public implicit operator DateTime(ExifDateTime obj) { return obj.mValue; }
- public override string ToString() { return mValue.ToString("yyyy.MM.dd HH:mm:ss"); }
+ public override string ToString()
+ {
+ if (mPreserveMilliseconds)
+ return mValue.ToString("yyyy.MM.dd HH:mm:ss.fff");
+ else
+ return mValue.ToString("yyyy.MM.dd HH:mm:ss");
+ }
- public ExifDateTime(ExifTag tag, DateTime value)
+ public ExifDateTime(ExifTag tag, DateTime value, bool preserveMilliseconds)
: base(tag)
{
mValue = value;
+ mPreserveMilliseconds = preserveMilliseconds;
}
public override ExifInterOperability Interoperability
{
get
{
- return new ExifInterOperability(ExifTagFactory.GetTagID(mTag), InterOpType.ASCII, (uint)20, ExifBitConverter.GetBytes(mValue, true));
+ return new ExifInterOperability(
+ ExifTagFactory.GetTagID(mTag),
+ InterOpType.ASCII,
+ mPreserveMilliseconds ? (uint)24 : (uint)20,
+ ExifBitConverter.GetBytes(mValue, true, mPreserveMilliseconds)
+ );
}
}
}
@@ -172,7 +185,16 @@ public override ExifInterOperability Interoperability
{
get
{
- return new ExifInterOperability(ExifTagFactory.GetTagID(mTag), InterOpType.ASCII, (uint)11, ExifBitConverter.GetBytes(mValue, false));
+ return new ExifInterOperability(
+ ExifTagFactory.GetTagID(mTag),
+ InterOpType.ASCII,
+ (uint)11,
+ ExifBitConverter.GetBytes(
+ mValue,
+ false,
+ false // does not matter because we don't have time part anyway
+ )
+ );
}
}
}
diff --git a/ExifLibrary/ExifPropertyCollection.cs b/ExifLibrary/ExifPropertyCollection.cs
index 10b9d87..9a10479 100644
--- a/ExifLibrary/ExifPropertyCollection.cs
+++ b/ExifLibrary/ExifPropertyCollection.cs
@@ -159,9 +159,9 @@ public void Add(ExifTag key, object value)
///
/// The tag to set.
/// The value of tag.
- public void Add(ExifTag key, DateTime value)
+ public void Add(ExifTag key, DateTime value, bool preserveMilliseconds = false)
{
- AddItem(new ExifDateTime(key, value));
+ AddItem(new ExifDateTime(key, value, preserveMilliseconds));
}
///
/// Adds an with the specified key.
@@ -341,9 +341,9 @@ public void Set(ExifTag key, object value)
///
/// The tag to set.
/// The value of tag.
- public void Set(ExifTag key, DateTime value)
+ public void Set(ExifTag key, DateTime value, bool preserveMilliseconds = false)
{
- SetItem(new ExifDateTime(key, value));
+ SetItem(new ExifDateTime(key, value, preserveMilliseconds));
}
///
/// Sets an with the specified key.
diff --git a/ExifLibrary/ExifPropertyFactory.cs b/ExifLibrary/ExifPropertyFactory.cs
index 4c8e86f..b6bdf9f 100644
--- a/ExifLibrary/ExifPropertyFactory.cs
+++ b/ExifLibrary/ExifPropertyFactory.cs
@@ -41,7 +41,7 @@ public static ExifProperty Get(ushort tag, ushort type, uint count, byte[] value
else if (tag == 0x128) // ResolutionUnit
return new ExifEnumProperty(ExifTag.ResolutionUnit, (ResolutionUnit)conv.ToUInt16(value, 0));
else if (tag == 0x132) // DateTime
- return new ExifDateTime(ExifTag.DateTime, ExifBitConverter.ToDateTime(value));
+ return new ExifDateTime(ExifTag.DateTime, ExifBitConverter.ToDateTime(value, out bool _), false);
else if (tag == 0x9c9b || tag == 0x9c9c || // Windows tags
tag == 0x9c9d || tag == 0x9c9e || tag == 0x9c9f)
return new WindowsByteString(etag, Encoding.Unicode.GetString(value).TrimEnd('\0'));
@@ -80,9 +80,9 @@ public static ExifProperty Get(ushort tag, ushort type, uint count, byte[] value
return new ExifEncodedString(ExifTag.UserComment, val, enc);
}
else if (tag == 0x9003) // DateTimeOriginal
- return new ExifDateTime(ExifTag.DateTimeOriginal, ExifBitConverter.ToDateTime(value));
+ return new ExifDateTime(ExifTag.DateTimeOriginal, ExifBitConverter.ToDateTime(value, out bool preserveMilliseconds), preserveMilliseconds);
else if (tag == 0x9004) // DateTimeDigitized
- return new ExifDateTime(ExifTag.DateTimeDigitized, ExifBitConverter.ToDateTime(value));
+ return new ExifDateTime(ExifTag.DateTimeDigitized, ExifBitConverter.ToDateTime(value, out bool preserveMilliseconds), preserveMilliseconds);
else if (tag == 0x8822) // ExposureProgram
return new ExifEnumProperty(ExifTag.ExposureProgram, (ExposureProgram)conv.ToUInt16(value, 0));
else if (tag == 0x9207) // MeteringMode
@@ -170,7 +170,7 @@ public static ExifProperty Get(ushort tag, ushort type, uint count, byte[] value
else if (tag == 25) // GPSDestDistanceRef
return new ExifEnumProperty(ExifTag.GPSDestDistanceRef, (GPSDistanceRef)value[0]);
else if (tag == 29) // GPSDateStamp
- return new ExifDate(ExifTag.GPSDateStamp, ExifBitConverter.ToDateTime(value, false));
+ return new ExifDate(ExifTag.GPSDateStamp, ExifBitConverter.ToDateTime(value, false, out bool _));
else if (tag == 30) // GPSDifferential
return new ExifEnumProperty(ExifTag.GPSDifferential, (GPSDifferential)conv.ToUInt16(value, 0));
}
@@ -196,7 +196,7 @@ public static ExifProperty Get(ushort tag, ushort type, uint count, byte[] value
else if (tag == 0x128) // ResolutionUnit
return new ExifEnumProperty(ExifTag.ThumbnailResolutionUnit, (ResolutionUnit)conv.ToUInt16(value, 0));
else if (tag == 0x132) // DateTime
- return new ExifDateTime(ExifTag.ThumbnailDateTime, ExifBitConverter.ToDateTime(value));
+ return new ExifDateTime(ExifTag.ThumbnailDateTime, ExifBitConverter.ToDateTime(value, out bool _), false);
}
if (type == 1) // 1 = BYTE An 8-bit unsigned integer.