From 075336d55274ef08fdbdd4b14f2e5c20dc13d1ff Mon Sep 17 00:00:00 2001 From: iacsha <122627209+iacsha@users.noreply.github.com> Date: Thu, 12 Nov 2026 13:29:34 +0000 Subject: [PATCH] Keep a DTM's value unchanged when reading its parts (fixes #917) Reading Year, Month, Day or another part of a TSComponentOne builds the cached CommonTS detail, and the Value getter then returned the detail's value. CommonTS adds the local GMT offset to a value that has none, so "20100101" became "20100101-0500" and GetAsDate() then threw. Value now always returns the stored value. The Offset setter and the SetDatePrecision, SetDateMinutePrecision and SetDateSecondPrecision methods write the detail's result back to it, so they still update the value as before. Adds DtmPartsTest26: three of its cases fail on master. --- .../Model/Primitive/TSComponentOne.cs | 22 +++++- tests/NHapi.NUnit/DtmPartsTest26.cs | 72 +++++++++++++++++++ 2 files changed, 92 insertions(+), 2 deletions(-) create mode 100644 tests/NHapi.NUnit/DtmPartsTest26.cs diff --git a/src/NHapi.Base/Model/Primitive/TSComponentOne.cs b/src/NHapi.Base/Model/Primitive/TSComponentOne.cs index 2a12073fa..2747d9c1e 100644 --- a/src/NHapi.Base/Model/Primitive/TSComponentOne.cs +++ b/src/NHapi.Base/Model/Primitive/TSComponentOne.cs @@ -73,9 +73,12 @@ public TSComponentOne(IMessage theMessage, string description) public override string Value { + // Reading Year, Month, Day and the other parts builds the parsed detail, which adds a + // default GMT offset to a value that has none. Return the stored value, so reading a + // part never changes it. The detail setters below write their result back here. get { - return (myDetail != null) ? myDetail.Value : base.Value; + return base.Value; } set @@ -94,7 +97,11 @@ public override string Value /// public virtual int Offset { - set { Detail.Offset = value; } + set + { + Detail.Offset = value; + SyncValueFromDetail(); + } } /// @@ -194,6 +201,7 @@ public virtual void setDatePrecision(int yr, int mnth, int dy) public virtual void SetDatePrecision(int yr, int mnth, int dy) { Detail.SetDatePrecision(yr, mnth, dy); + SyncValueFromDetail(); } [Obsolete("This method has been replaced by 'SetDateMinutePrecision'.")] @@ -215,6 +223,7 @@ public virtual void setDateMinutePrecision(int yr, int mnth, int dy, int hr, int public virtual void SetDateMinutePrecision(int yr, int mnth, int dy, int hr, int min) { Detail.SetDateMinutePrecision(yr, mnth, dy, hr, min); + SyncValueFromDetail(); } [Obsolete("This method has been replaced by 'SetDateSecondPrecision'.")] @@ -236,6 +245,7 @@ public virtual void setDateSecondPrecision(int yr, int mnth, int dy, int hr, int public virtual void SetDateSecondPrecision(int yr, int mnth, int dy, int hr, int min, float sec) { Detail.SetDateSecondPrecision(yr, mnth, dy, hr, min, sec); + SyncValueFromDetail(); } /// @@ -315,5 +325,13 @@ public virtual void Set(DateTime value, string format) "Could not format the date " + value + " to a long date. Format must be " + LongDateTimeFormat); } } + + /// + /// Stores the detail's value after one of its setters has changed it. + /// + private void SyncValueFromDetail() + { + base.Value = myDetail.Value; + } } } \ No newline at end of file diff --git a/tests/NHapi.NUnit/DtmPartsTest26.cs b/tests/NHapi.NUnit/DtmPartsTest26.cs new file mode 100644 index 000000000..7b9ba715f --- /dev/null +++ b/tests/NHapi.NUnit/DtmPartsTest26.cs @@ -0,0 +1,72 @@ +namespace NHapi.NUnit +{ + using System; + + using global::NUnit.Framework; + + using NHapi.Model.V26.Message; + + /// + /// Reading the parts of a DTM must not change its value. See nHapiNET/nHapi#917. + /// + [TestFixture] + public class DtmPartsTest26 + { + [TestCase("20100101")] + [TestCase("201001011230")] + [TestCase("20100101123045")] + [TestCase("20100101123045+0200")] + public void ReadingParts_DoesNotChangeValue(string value) + { + var pid = new ADT_A05().PID; + pid.DateTimeOfBirth.Value = value; + + _ = pid.DateTimeOfBirth.Year; + _ = pid.DateTimeOfBirth.Month; + _ = pid.DateTimeOfBirth.Day; + _ = pid.DateTimeOfBirth.Hour; + _ = pid.DateTimeOfBirth.GMTOffset; + + Assert.That(pid.DateTimeOfBirth.Value, Is.EqualTo(value)); + } + + [Test] + public void ReadingParts_ReturnsTheParts_AndGetAsDateStillWorks() + { + var pid = new ADT_A05().PID; + pid.DateTimeOfBirth.Value = "20100102"; + + Assert.That(pid.DateTimeOfBirth.Year, Is.EqualTo(2010)); + Assert.That(pid.DateTimeOfBirth.Month, Is.EqualTo(1)); + Assert.That(pid.DateTimeOfBirth.Day, Is.EqualTo(2)); + Assert.That(pid.DateTimeOfBirth.GetAsDate(), Is.EqualTo(new DateTime(2010, 1, 2))); + } + + [Test] + public void SettingValueAfterReadingParts_UpdatesTheParts() + { + var pid = new ADT_A05().PID; + pid.DateTimeOfBirth.Value = "20100102"; + _ = pid.DateTimeOfBirth.Year; + + pid.DateTimeOfBirth.Value = "19991231"; + + Assert.That(pid.DateTimeOfBirth.Value, Is.EqualTo("19991231")); + Assert.That(pid.DateTimeOfBirth.Year, Is.EqualTo(1999)); + Assert.That(pid.DateTimeOfBirth.Day, Is.EqualTo(31)); + } + + [Test] + public void PrecisionSetters_StillUpdateValue() + { + var pid = new ADT_A05().PID; + + pid.DateTimeOfBirth.SetDatePrecision(2010, 1, 2); + Assert.That(pid.DateTimeOfBirth.Value, Is.EqualTo("20100102")); + + pid.DateTimeOfBirth.SetDateMinutePrecision(2010, 1, 2, 12, 30); + Assert.That(pid.DateTimeOfBirth.Value, Does.StartWith("201001021230")); + Assert.That(pid.DateTimeOfBirth.Minute, Is.EqualTo(30)); + } + } +}