Skip to content

Commit 23ad428

Browse files
Copilotniemyjski
andcommitted
Add support for y, M, w time units to TimeUnit parser with comprehensive tests
Co-authored-by: niemyjski <[email protected]>
1 parent eb0a1b3 commit 23ad428

File tree

2 files changed

+77
-0
lines changed

2 files changed

+77
-0
lines changed

src/Exceptionless.DateTimeExtensions/TimeUnit.cs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,20 @@ public static bool TryParse(string value, out TimeSpan? time)
3535

3636
// compare using the original value as uppercase M could mean months.
3737
string normalized = value.Trim();
38+
39+
// 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))
41+
return new TimeSpan((int)(years * TimeSpanExtensions.AvgDaysInAYear), 0, 0, 0);
42+
43+
// 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))
45+
return new TimeSpan((int)(months * TimeSpanExtensions.AvgDaysInAMonth), 0, 0, 0);
46+
47+
// Handle weeks (w)
48+
if (value.EndsWith("w") && Int32.TryParse(normalized.Substring(0, normalized.Length - 1), out int weeks))
49+
return new TimeSpan(weeks * 7, 0, 0, 0);
50+
51+
// Handle minutes (m) - lowercase m for minutes
3852
if (value.EndsWith("m") && Int32.TryParse(normalized.Substring(0, normalized.Length - 1), out int minutes))
3953
return new TimeSpan(0, minutes, 0);
4054

tests/Exceptionless.DateTimeExtensions.Tests/TimeUnitTests.cs

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,15 @@ public class TimeUnitTests
1818
["10m", new TimeSpan(0, 10, 0)],
1919
["10h", new TimeSpan(10, 0, 0)],
2020
["10d", new TimeSpan(10, 0, 0, 0)],
21+
["1w", new TimeSpan(7, 0, 0, 0)],
22+
["2w", new TimeSpan(14, 0, 0, 0)],
23+
["-1w", new TimeSpan(-7, 0, 0, 0)],
24+
["1M", new TimeSpan((int)TimeSpanExtensions.AvgDaysInAMonth, 0, 0, 0)],
25+
["2M", new TimeSpan((int)(2 * TimeSpanExtensions.AvgDaysInAMonth), 0, 0, 0)],
26+
["-1M", new TimeSpan((int)(-1 * TimeSpanExtensions.AvgDaysInAMonth), 0, 0, 0)],
27+
["1y", new TimeSpan((int)TimeSpanExtensions.AvgDaysInAYear, 0, 0, 0)],
28+
["2y", new TimeSpan((int)(2 * TimeSpanExtensions.AvgDaysInAYear), 0, 0, 0)],
29+
["-1y", new TimeSpan((int)(-1 * TimeSpanExtensions.AvgDaysInAYear), 0, 0, 0)],
2130
};
2231

2332
[Theory]
@@ -50,6 +59,15 @@ public void VerifyParseFailure(string value)
5059
[InlineData("10m", true)]
5160
[InlineData("10h", true)]
5261
[InlineData("10d", true)]
62+
[InlineData("1w", true)]
63+
[InlineData("2w", true)]
64+
[InlineData("-1w", true)]
65+
[InlineData("1M", true)]
66+
[InlineData("2M", true)]
67+
[InlineData("-1M", true)]
68+
[InlineData("1y", true)]
69+
[InlineData("2y", true)]
70+
[InlineData("-1y", true)]
5371
[InlineData(null, false)]
5472
[InlineData("1.234h", false)] // fractional time
5573
[InlineData("1234", false)] // missing unit
@@ -61,4 +79,49 @@ public void VerifyTryParse(string value, bool expected)
6179
bool success = TimeUnit.TryParse(value, out var result);
6280
Assert.Equal(expected, success);
6381
}
82+
83+
[Fact]
84+
public void VerifyMonthsVsMinutesCaseSensitive()
85+
{
86+
// Uppercase M should be months
87+
var monthResult = TimeUnit.Parse("1M");
88+
var expectedMonthDays = (int)TimeSpanExtensions.AvgDaysInAMonth;
89+
Assert.Equal(new TimeSpan(expectedMonthDays, 0, 0, 0), monthResult);
90+
91+
// Lowercase m should be minutes
92+
var minuteResult = TimeUnit.Parse("1m");
93+
Assert.Equal(new TimeSpan(0, 1, 0), minuteResult);
94+
95+
// Verify they are different
96+
Assert.NotEqual(monthResult, minuteResult);
97+
}
98+
99+
[Theory]
100+
[InlineData("1y", 365)] // Approximately 365 days in a year
101+
[InlineData("1M", 30)] // Approximately 30 days in a month
102+
[InlineData("1w", 7)] // Exactly 7 days in a week
103+
public void VerifyNewTimeUnitsConvertCorrectly(string input, int expectedApproxDays)
104+
{
105+
var result = TimeUnit.Parse(input);
106+
107+
// For years and months, check approximate values due to fractional constants
108+
if (input.EndsWith("y"))
109+
{
110+
Assert.True(Math.Abs(result.TotalDays - TimeSpanExtensions.AvgDaysInAYear) < 1,
111+
$"Year conversion should be close to {TimeSpanExtensions.AvgDaysInAYear} days, got {result.TotalDays}");
112+
Assert.True(Math.Abs(result.TotalDays - expectedApproxDays) < 10,
113+
$"Year conversion should be approximately {expectedApproxDays} days, got {result.TotalDays}");
114+
}
115+
else if (input.EndsWith("M"))
116+
{
117+
Assert.True(Math.Abs(result.TotalDays - TimeSpanExtensions.AvgDaysInAMonth) < 1,
118+
$"Month conversion should be close to {TimeSpanExtensions.AvgDaysInAMonth} days, got {result.TotalDays}");
119+
Assert.True(Math.Abs(result.TotalDays - expectedApproxDays) < 5,
120+
$"Month conversion should be approximately {expectedApproxDays} days, got {result.TotalDays}");
121+
}
122+
else if (input.EndsWith("w"))
123+
{
124+
Assert.Equal(expectedApproxDays, result.TotalDays);
125+
}
126+
}
64127
}

0 commit comments

Comments
 (0)