Skip to content

Commit 7e6613a

Browse files
Copilotniemyjski
andcommitted
Improve TimeUnit parsing: add early null/empty checks, use normalized values consistently, and add comprehensive whitespace/special character tests
Co-authored-by: niemyjski <[email protected]>
1 parent 23ad428 commit 7e6613a

File tree

2 files changed

+36
-7
lines changed

2 files changed

+36
-7
lines changed

src/Exceptionless.DateTimeExtensions/TimeUnit.cs

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -28,28 +28,32 @@ public static bool TryParse(string value, out TimeSpan? time)
2828

2929
private static TimeSpan? ParseTime(string value)
3030
{
31+
if (String.IsNullOrEmpty(value))
32+
return null;
33+
34+
string normalized = value.Trim();
35+
if (String.IsNullOrEmpty(normalized))
36+
return null;
37+
3138
// bail if we have any weird characters
3239
foreach (char c in value)
3340
if (!Char.IsLetterOrDigit(c) && c != '-' && c != '+' && !Char.IsWhiteSpace(c))
3441
return null;
3542

36-
// compare using the original value as uppercase M could mean months.
37-
string normalized = value.Trim();
38-
3943
// Handle years (y) - using average days in a year
40-
if (value.EndsWith("y") && Int32.TryParse(normalized.Substring(0, normalized.Length - 1), out int years))
44+
if (normalized.EndsWith("y") && Int32.TryParse(normalized.Substring(0, normalized.Length - 1), out int years))
4145
return new TimeSpan((int)(years * TimeSpanExtensions.AvgDaysInAYear), 0, 0, 0);
4246

4347
// Handle months (M) - using average days in a month, case-sensitive uppercase M
44-
if (value.EndsWith("M") && Int32.TryParse(normalized.Substring(0, normalized.Length - 1), out int months))
48+
if (normalized.EndsWith("M") && Int32.TryParse(normalized.Substring(0, normalized.Length - 1), out int months))
4549
return new TimeSpan((int)(months * TimeSpanExtensions.AvgDaysInAMonth), 0, 0, 0);
4650

4751
// Handle weeks (w)
48-
if (value.EndsWith("w") && Int32.TryParse(normalized.Substring(0, normalized.Length - 1), out int weeks))
52+
if (normalized.EndsWith("w") && Int32.TryParse(normalized.Substring(0, normalized.Length - 1), out int weeks))
4953
return new TimeSpan(weeks * 7, 0, 0, 0);
5054

5155
// Handle minutes (m) - lowercase m for minutes
52-
if (value.EndsWith("m") && Int32.TryParse(normalized.Substring(0, normalized.Length - 1), out int minutes))
56+
if (normalized.EndsWith("m") && Int32.TryParse(normalized.Substring(0, normalized.Length - 1), out int minutes))
5357
return new TimeSpan(0, minutes, 0);
5458

5559
if (normalized.EndsWith("h") && Int32.TryParse(normalized.Substring(0, normalized.Length - 1), out int hours))

tests/Exceptionless.DateTimeExtensions.Tests/TimeUnitTests.cs

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,11 @@ public class TimeUnitTests
2727
["1y", new TimeSpan((int)TimeSpanExtensions.AvgDaysInAYear, 0, 0, 0)],
2828
["2y", new TimeSpan((int)(2 * TimeSpanExtensions.AvgDaysInAYear), 0, 0, 0)],
2929
["-1y", new TimeSpan((int)(-1 * TimeSpanExtensions.AvgDaysInAYear), 0, 0, 0)],
30+
// Whitespace trimming tests
31+
[" 1y ", new TimeSpan((int)TimeSpanExtensions.AvgDaysInAYear, 0, 0, 0)],
32+
[" 2M ", new TimeSpan((int)(2 * TimeSpanExtensions.AvgDaysInAMonth), 0, 0, 0)],
33+
["\t3w\t", new TimeSpan(21, 0, 0, 0)],
34+
[" -1y ", new TimeSpan((int)(-1 * TimeSpanExtensions.AvgDaysInAYear), 0, 0, 0)],
3035
};
3136

3237
[Theory]
@@ -42,6 +47,13 @@ public void CanParse(string value, TimeSpan expected)
4247
[InlineData("1234")] // missing unit
4348
[InlineData("12unknownunit")]
4449
[InlineData("12h.")]
50+
[InlineData("")] // empty string
51+
[InlineData(" ")] // whitespace only
52+
[InlineData("\t\t")] // tabs only
53+
[InlineData("1y@")] // special character after unit
54+
[InlineData("1M!")] // special character after unit
55+
[InlineData("1w#")] // special character after unit
56+
[InlineData("1@y")] // special character in middle
4557
public void VerifyParseFailure(string value)
4658
{
4759
Assert.ThrowsAny<Exception>(() => TimeUnit.Parse(value));
@@ -68,6 +80,19 @@ public void VerifyParseFailure(string value)
6880
[InlineData("1y", true)]
6981
[InlineData("2y", true)]
7082
[InlineData("-1y", true)]
83+
// Whitespace tests
84+
[InlineData(" 1y ", true)]
85+
[InlineData(" 2M ", true)]
86+
[InlineData("\t3w\t", true)]
87+
[InlineData(" -1M ", true)]
88+
// Special character and edge case tests
89+
[InlineData("", false)]
90+
[InlineData(" ", false)]
91+
[InlineData("\t\t", false)]
92+
[InlineData("1y@", false)]
93+
[InlineData("1M!", false)]
94+
[InlineData("1w#", false)]
95+
[InlineData("1@y", false)]
7196
[InlineData(null, false)]
7297
[InlineData("1.234h", false)] // fractional time
7398
[InlineData("1234", false)] // missing unit

0 commit comments

Comments
 (0)