From 3524403061b2e09b7ce10e72e1e5ca59285ecc23 Mon Sep 17 00:00:00 2001 From: lordmilko Date: Sat, 25 Jul 2020 09:50:06 +1000 Subject: [PATCH] Fix ServerStatus.DateTime crashing when the server DateTime format differs from the client format (e.g. US vs UK time format) --- .../CSharp/BaseTest.cs | 18 +++ .../CSharp/ObjectData/ServerStatusTests.cs | 119 +++++++++++++++++- .../SetObjectPropertyTests.cs | 16 --- src/PrtgAPI/Objects/Models/ServerStatus.cs | 25 +++- 4 files changed, 158 insertions(+), 20 deletions(-) diff --git a/src/PrtgAPI.Tests.UnitTests/CSharp/BaseTest.cs b/src/PrtgAPI.Tests.UnitTests/CSharp/BaseTest.cs index 042fa90e..a361908d 100644 --- a/src/PrtgAPI.Tests.UnitTests/CSharp/BaseTest.cs +++ b/src/PrtgAPI.Tests.UnitTests/CSharp/BaseTest.cs @@ -1,7 +1,9 @@ using System; using System.Collections; using System.Collections.Generic; +using System.Globalization; using System.Linq; +using System.Threading; using System.Threading.Tasks; using PrtgAPI.Reflection; using PrtgAPI.Tests.UnitTests.Support.TestItems; @@ -109,6 +111,22 @@ protected T Execute(Func action) return action(client); } + protected void TestCustomCulture(Action action, CultureInfo newCulture) + { + var originalCulture = Thread.CurrentThread.CurrentCulture; + + try + { + Thread.CurrentThread.CurrentCulture = newCulture; + + action(); + } + finally + { + Thread.CurrentThread.CurrentCulture = originalCulture; + } + } + private AddressValidatorResponse GetValidator(object urls, Dictionary countOverride, Dictionary itemOverride) { #pragma warning disable 618 diff --git a/src/PrtgAPI.Tests.UnitTests/CSharp/ObjectData/ServerStatusTests.cs b/src/PrtgAPI.Tests.UnitTests/CSharp/ObjectData/ServerStatusTests.cs index c0e29dc8..0cc439b9 100644 --- a/src/PrtgAPI.Tests.UnitTests/CSharp/ObjectData/ServerStatusTests.cs +++ b/src/PrtgAPI.Tests.UnitTests/CSharp/ObjectData/ServerStatusTests.cs @@ -1,4 +1,6 @@ -using System.Threading.Tasks; +using System; +using System.Globalization; +using System.Threading.Tasks; using Microsoft.VisualStudio.TestTools.UnitTesting; using PrtgAPI.Tests.UnitTests.Support.TestItems; using PrtgAPI.Tests.UnitTests.Support.TestResponses; @@ -123,6 +125,121 @@ public async Task ServerStatus_ReadOnlyAsync() AssertEx.AllPropertiesRetrieveValues(result); } + [UnitTest] + [TestMethod] + public void ServerStatus_DateTime_ServerUS_ClientUK() + { + //There's a legal mismatch, so we get the dates backwards + TestClockSmallDate( + "MM/d/yyyy", + "en-GB", + expectedClientMonth: 12, + expectedClientDay: 1 + ); + + //There's an illegal mismatch, so we reparse the DateTime using US heuristics + TestClockLargeDate( + "MM/d/yyyy", + "en-GB", + expectedClientMonth: 1, + expectedClientDay: 13 + ); + } + + [UnitTest] + [TestMethod] + public void ServerStatus_DateTime_ServerUS_ClientUS() + { + TestClockSmallDate( + "MM/d/yyyy", + "en-US", + expectedClientMonth: 1, + expectedClientDay: 12 + ); + + TestClockLargeDate( + "MM/d/yyyy", + "en-US", + expectedClientMonth: 1, + expectedClientDay: 13 + ); + } + + [UnitTest] + [TestMethod] + public void ServerStatus_DateTime_ServerUK_ClientUS() + { + //There's a legal mismatch, so we get the dates backwards + TestClockSmallDate( + "dd/MM/yyyy", + "en-US", + expectedClientMonth: 12, + expectedClientDay: 1 + ); + + //There's an illegal mismatch, so we reparse the DateTime using US heuristics + TestClockLargeDate( + "dd/MM/yyyy", + "en-US", + expectedClientMonth: 1, + expectedClientDay: 13 + ); + } + + [UnitTest] + [TestMethod] + public void ServerStatus_DateTime_ServerUK_ClientUK() + { + TestClockSmallDate( + "dd/MM/yyyy", + "en-GB", + expectedClientMonth: 1, + expectedClientDay: 12 + ); + + TestClockLargeDate( + "dd/MM/yyyy", + "en-GB", + expectedClientMonth: 1, + expectedClientDay: 13 + ); + } + + private void TestClockSmallDate( + string serverDateFormat, + string clientCulture, + int expectedClientMonth, + int expectedClientDay) + { + var serverTime = new DateTime(2020, 1, 12, 4, 10, 20, DateTimeKind.Utc).ToLocalTime().ToString($"{serverDateFormat} h:mm:ss tt"); + + TestClock(serverTime, clientCulture, new DateTime(2020, expectedClientMonth, expectedClientDay, 4, 10, 20, DateTimeKind.Utc)); + } + + private void TestClockLargeDate( + string serverDateFormat, + string clientCulture, + int expectedClientMonth, + int expectedClientDay) + { + var serverTime = new DateTime(2020, 1, 13, 4, 10, 20, DateTimeKind.Utc).ToLocalTime().ToString($"{serverDateFormat} h:mm:ss tt"); + + TestClock(serverTime, clientCulture, new DateTime(2020, expectedClientMonth, expectedClientDay, 4, 10, 20, DateTimeKind.Utc)); + } + + private void TestClock(string serverTime, string clientCulture, DateTime expectedInvariantUtc) + { + var client = Initialize_Client(new ServerStatusResponse(new ServerStatusItem(clock: serverTime))); + + TestCustomCulture(() => + { + var now = DateTime.Now.ToString(); + + var status = client.GetStatus(); + Assert.AreEqual(expectedInvariantUtc, status.DateTime.ToUniversalTime()); + }, CultureInfo.GetCultureInfo(clientCulture)); + } + public ServerStatusItem GetItem() => new ServerStatusItem(); } } diff --git a/src/PrtgAPI.Tests.UnitTests/CSharp/ObjectManipulation/SetObjectPropertyTests.cs b/src/PrtgAPI.Tests.UnitTests/CSharp/ObjectManipulation/SetObjectPropertyTests.cs index b55100c2..adcf335f 100644 --- a/src/PrtgAPI.Tests.UnitTests/CSharp/ObjectManipulation/SetObjectPropertyTests.cs +++ b/src/PrtgAPI.Tests.UnitTests/CSharp/ObjectManipulation/SetObjectPropertyTests.cs @@ -872,22 +872,6 @@ public void SetObjectProperty_DecimalPoint_EuropeanCulture() }, new CultureInfo("de-DE")); } - private void TestCustomCulture(Action action, CultureInfo newCulture) - { - var originalCulture = Thread.CurrentThread.CurrentCulture; - - try - { - Thread.CurrentThread.CurrentCulture = newCulture; - - action(); - } - finally - { - Thread.CurrentThread.CurrentCulture = originalCulture; - } - } - #endregion #region Multiple diff --git a/src/PrtgAPI/Objects/Models/ServerStatus.cs b/src/PrtgAPI/Objects/Models/ServerStatus.cs index 7d7b42d9..4127c85a 100644 --- a/src/PrtgAPI/Objects/Models/ServerStatus.cs +++ b/src/PrtgAPI/Objects/Models/ServerStatus.cs @@ -1,4 +1,5 @@ using System; +using System.Globalization; using System.Runtime.Serialization; using PrtgAPI.Request.Serialization; using PrtgAPI.Utilities; @@ -18,14 +19,32 @@ public class ServerStatus private DateTime? clock; /// - /// Current system time on the PRTG Core Server. + /// Current system time on the PRTG Core Server. + /// Note that if the DateTime format of the PRTG Server's language region does not match the DateTime format of the + /// system running PrtgAPI, + /// the DateTime format of the value parsed by this property may have months and dates back to front. /// public DateTime DateTime { get { if (clock == null) - clock = DateTime.Parse(clockStr); + { + //Try and parse the remote DateTime based on the DateTime of the local system. If that fails, we have no idea whether the remote + //system was dd/MM/yyyy or MM/dd/yyyy so just try both. We might not be right if we're on the 1st-12th of the month, + //but it's the best we can do with an ambiguous date format. + DateTime temp; + + var ukCulture = CultureInfo.GetCultureInfo("en-GB").DateTimeFormat; + var usCulture = CultureInfo.GetCultureInfo("en-US").DateTimeFormat; + + if (DateTime.TryParse(clockStr, out temp)) + clock = temp; + else if (DateTime.TryParse(clockStr, ukCulture, DateTimeStyles.None, out temp)) + clock = temp; + else + clock = DateTime.Parse(clockStr, usCulture); + } return clock.Value; } @@ -154,7 +173,7 @@ public string UserId private string userTimeZone; /// - /// UTC offset of your PRTG Server's timezone. + /// UTC offset or name of your PRTG Server's timezone. /// [DataMember(Name = "UserTimeZone")] public string UserTimeZone