diff --git a/nanoFramework.Json.Test/Converters/TimeSpanConverterTests.cs b/nanoFramework.Json.Test/Converters/TimeSpanConverterTests.cs index 78eff95..4f126e8 100644 --- a/nanoFramework.Json.Test/Converters/TimeSpanConverterTests.cs +++ b/nanoFramework.Json.Test/Converters/TimeSpanConverterTests.cs @@ -3,6 +3,7 @@ // See LICENSE file in the project root for full license information. // +using nanoFramework.Json.Converters; using nanoFramework.TestFramework; using System; @@ -12,26 +13,77 @@ namespace nanoFramework.Json.Test.Converters public class TimeSpanConverterTests { [TestMethod] - [DataRow("10:00:00", 10)] - [DataRow("24:00:00", 24)] - public void TimeSpanConverter_ToType_ShouldReturnValidData(string value, int expectedValueHours) + public void TimeSpanConverter_ToType_Should_Return_Valid_Data() { - var converter = new Json.Converters.TimeSpanConverter(); - var convertedValue = (TimeSpan)converter.ToType(value); + var values = new[] + { + "-1.02:03:04.005", + "1.02:03:04.0050000", + "4.03:02:01.654321", + "4.03:02:01.65432", + "4.03:02:01.6543", + "4.03:02:01.654", + "4.03:02:01.65", + "4.03:02:01.6", + "04:20:19", + "07:32", + }; - var expectedTimeSpanValue = TimeSpan.FromHours(expectedValueHours); - Assert.AreEqual(expectedTimeSpanValue.Ticks, convertedValue.Ticks); + var expected = new[] + { + -new TimeSpan(1, 2, 3, 4, 5), + new TimeSpan(1, 2, 3, 4, 5), + new TimeSpan(4, 3, 2, 1, 654).Add(new TimeSpan(3210)), + new TimeSpan(4, 3, 2, 1, 654).Add(new TimeSpan(3200)), + new TimeSpan(4, 3, 2, 1, 654).Add(new TimeSpan(3000)), + new TimeSpan(4, 3, 2, 1, 654), + new TimeSpan(4, 3, 2, 1, 650), + new TimeSpan(4, 3, 2, 1, 600), + new TimeSpan(4, 20, 19), + new TimeSpan(7, 32, 0), + }; + + var sut = new TimeSpanConverter(); + + for (var i = 0; i < values.Length; i++) + { + var actual = (TimeSpan) sut.ToType(values[i]); + + Assert.AreEqual(expected[i], actual); + } } [TestMethod] - [DataRow(10, "\"10:00:00\"")] - [DataRow(24, "\"24:00:00\"")] - public void TimeSpanConverter_ToJson_Should_ReturnValidData(int valueHours, string expectedValue) + public void TimeSpanConverter_ToJson_Should_Return_Valid_Data() { - var converter = new Json.Converters.TimeSpanConverter(); - var convertedValue = converter.ToJson(TimeSpan.FromHours(valueHours)); + var values = new[] + { + -new TimeSpan(1, 2, 3, 4, 5), + new TimeSpan(1, 2, 3, 4, 5), + new TimeSpan(4, 3, 2, 1, 654).Add(new TimeSpan(3210)), + new TimeSpan(4, 20, 19), + new TimeSpan(7, 32, 0), + new TimeSpan(0, 29, 0), + }; + + var expected = new[] + { + "\"-1.02:03:04.0050000\"", + "\"1.02:03:04.0050000\"", + "\"4.03:02:01.6543210\"", + "\"04:20:19\"", + "\"07:32:00\"", + "\"00:29:00\"", + }; + + var sut = new TimeSpanConverter(); + + for (var i = 0; i < values.Length; i++) + { + var actual = sut.ToJson(values[i]); - Assert.AreEqual(expectedValue, convertedValue); + Assert.AreEqual(expected[i], actual); + } } } } diff --git a/nanoFramework.Json.Test/nanoFramework.Json.Test.nfproj b/nanoFramework.Json.Test/nanoFramework.Json.Test.nfproj index 5d028b8..479cd3f 100644 --- a/nanoFramework.Json.Test/nanoFramework.Json.Test.nfproj +++ b/nanoFramework.Json.Test/nanoFramework.Json.Test.nfproj @@ -59,40 +59,34 @@ - + + + + + + + ..\packages\nanoFramework.CoreLibrary.1.15.5\lib\mscorlib.dll - True - + ..\packages\nanoFramework.System.Collections.1.5.31\lib\nanoFramework.System.Collections.dll - True - + ..\packages\nanoFramework.System.Text.1.2.54\lib\nanoFramework.System.Text.dll - True - + ..\packages\nanoFramework.TestFramework.2.1.94\lib\nanoFramework.TestFramework.dll - True - + ..\packages\nanoFramework.TestFramework.2.1.94\lib\nanoFramework.UnitTestLauncher.exe - True - + ..\packages\nanoFramework.System.IO.Streams.1.1.59\lib\System.IO.Streams.dll - True - - - - - - diff --git a/nanoFramework.Json/Converters/TimeSpanConverter.cs b/nanoFramework.Json/Converters/TimeSpanConverter.cs index e2e467d..c576833 100644 --- a/nanoFramework.Json/Converters/TimeSpanConverter.cs +++ b/nanoFramework.Json/Converters/TimeSpanConverter.cs @@ -12,10 +12,7 @@ internal sealed class TimeSpanConverter : IConverter /// /// /// - public string ToJson(object value) - { - return "\"" + value.ToString() + "\""; - } + public string ToJson(object value) => $"\"{value}\""; /// /// @@ -34,32 +31,34 @@ internal static TimeSpan ConvertFromString(string value) { // split string value with all possible separators // format is: -ddddd.HH:mm:ss.fffffff - var timeSpanBits = value.Split(':', '.'); + var tokens = value.Split(':', '.'); // sanity check - if (timeSpanBits.Length == 0) + if (tokens.Length == 0) { return TimeSpan.Zero; } // figure out where the separators are - int indexOfFirstDot = value.IndexOf('.'); - int indexOfSecondDot = indexOfFirstDot > -1 ? value.IndexOf('.', indexOfFirstDot + 1) : -1; - int indexOfFirstColon = value.IndexOf(':'); - int indexOfSecondColon = indexOfFirstColon > -1 ? value.IndexOf(':', indexOfFirstColon + 1) : -1; + var indexOfFirstDot = value.IndexOf('.'); + var indexOfSecondDot = indexOfFirstDot > -1 ? value.IndexOf('.', indexOfFirstDot + 1) : -1; + var indexOfFirstColon = value.IndexOf(':'); + var indexOfSecondColon = indexOfFirstColon > -1 ? value.IndexOf(':', indexOfFirstColon + 1) : -1; // sanity check for separators: all have to be ahead of string start - if (SeparatorCheck(timeSpanBits, indexOfFirstDot, indexOfSecondDot, indexOfFirstColon, indexOfSecondColon)) + if (SeparatorCheck(tokens, indexOfFirstDot, indexOfSecondDot, indexOfFirstColon, indexOfSecondColon)) { throw new InvalidCastException(); } + var isNegative = value.StartsWith("-"); + // to have days, it has to have something before the 1st dot, or just have a single component - bool hasDays = (indexOfFirstDot > 0 && indexOfFirstDot < indexOfFirstColon) || timeSpanBits.Length == 1; - bool hasTicks = hasDays ? indexOfSecondDot > indexOfFirstDot : indexOfFirstDot > -1; - bool hasHours = indexOfFirstColon > 0; - bool hasMinutes = hasHours && indexOfFirstColon > -1; - bool hasSeconds = hasMinutes && indexOfSecondColon > -1; + var hasDays = (indexOfFirstDot > 0 && indexOfFirstDot < indexOfFirstColon) || tokens.Length == 1; + var hasTicks = hasDays ? indexOfSecondDot > indexOfFirstDot : indexOfFirstDot > -1; + var hasHours = indexOfFirstColon > 0; + var hasMinutes = hasHours && indexOfFirstColon > -1; + var hasSeconds = hasMinutes && indexOfSecondColon > -1; // sanity check for ticks without other time components if (hasTicks && !hasHours) @@ -68,20 +67,13 @@ internal static TimeSpan ConvertFromString(string value) } // let the parsing start! - int days = 0; - if (hasDays - && !int.TryParse(timeSpanBits[0], out days)) - { - throw new InvalidCastException(); - } + var tokenIndex = 0; - // bump the index if days component is present - int processIndex = hasDays ? 1 : 0; - - var hours = ParseValueFromString(hasHours, timeSpanBits, ref processIndex); - var minutes = ParseValueFromString(hasMinutes, timeSpanBits, ref processIndex); - var seconds = ParseValueFromString(hasSeconds, timeSpanBits, ref processIndex); - var ticks = HandleTicks(timeSpanBits, hasTicks, processIndex); + var days = ParseToken(hasDays, tokens, ref tokenIndex); + var hours = ParseToken(hasHours, tokens, ref tokenIndex); + var minutes = ParseToken(hasMinutes, tokens, ref tokenIndex); + var seconds = ParseToken(hasSeconds, tokens, ref tokenIndex); + var ticks = ParseTicks(hasTicks, tokens, tokenIndex); // sanity check for valid ranges if (IsInvalidTimeSpan(hours, minutes, seconds)) @@ -90,77 +82,86 @@ internal static TimeSpan ConvertFromString(string value) } // we should have everything now - return new TimeSpan(ticks).Add(new TimeSpan(days, hours, minutes, seconds, 0)); + var timeSpan = new TimeSpan(days, hours, minutes, seconds, 0).Add(new TimeSpan(ticks)); + + return isNegative ? -timeSpan : timeSpan; } - private static int HandleTicks(string[] timeSpanBits, bool hasTicks, int processIndex) + private static bool IsInvalidTimeSpan(int hour, int minutes, int seconds) { - if (!hasTicks || processIndex > timeSpanBits.Length) + if (hour is < 0 or > 24) { - return 0; + return true; } - if (!int.TryParse(timeSpanBits[processIndex], out var ticks)) + if (minutes is < 0 or >= 60) { - throw new InvalidCastException(); + return true; } - // if ticks are under 999, that's milliseconds - if (ticks < 1_000) + if (seconds is < 0 or >= 60) { - ticks *= 10_000; + return true; } - return ticks; - } - - private static bool SeparatorCheck(string[] timeSpanBits, int indexOfFirstDot, int indexOfSecondDot, int indexOfFirstColon, int indexOfSecondColon) - { - return timeSpanBits.Length > 1 - && indexOfFirstDot <= 0 - && indexOfSecondDot <= 0 - && indexOfFirstColon <= 0 - && indexOfSecondColon <= 0; + return false; } - private static int ParseValueFromString(bool hasValue,string[] timeSpanBits, ref int processIndex) + private static int ParseTicks(bool hasTicks, string[] tokens, int tokenIndex) { - if (!hasValue) + if (!hasTicks || tokenIndex > tokens.Length) { return 0; } - if (processIndex > timeSpanBits.Length) + var token = tokens[tokenIndex]; + + if (token.Length > 7) { - return 0; + token = token.Substring(0, 7); } - if (!int.TryParse(timeSpanBits[processIndex++], out var value)) + if (!int.TryParse(token, out var value)) { throw new InvalidCastException(); } - return value; + value = token.Length switch + { + 1 => value * 1_000_000, + 2 => value * 100_000, + 3 => value * 10_000, + 4 => value * 1_000, + 5 => value * 100, + 6 => value * 10, + _ => value + }; + + return value >= 0 ? value : value * -1; } - private static bool IsInvalidTimeSpan(int hour, int minutes, int seconds) + private static int ParseToken(bool hasValue, string[] tokens, ref int tokenIndex) { - if (hour < 0 || hour > 24) + if (!hasValue || tokenIndex > tokens.Length) { - return true; + return 0; } - if (minutes < 0 || minutes >= 60) + if (!int.TryParse(tokens[tokenIndex++], out var value)) { - return true; + throw new InvalidCastException(); } - if (seconds < 0 || seconds >= 60) - { - return true; - } + return value >= 0 ? value : value * -1; + } - return false; + private static bool SeparatorCheck(string[] tokens, int indexOfFirstDot, int indexOfSecondDot, int indexOfFirstColon, int indexOfSecondColon) + { + return tokens.Length > 1 + && indexOfFirstDot <= 0 + && indexOfSecondDot <= 0 + && indexOfFirstColon <= 0 + && indexOfSecondColon <= 0; } } }