Skip to content

Commit 66e368e

Browse files
DateOnly and TimeOnly: retarget net10.0, add conversion samples and tests (#2152)
Retarget both projects from net6.0 to net10.0 and bring the test packages up to the versions a net10.0 test project needs (Microsoft.NET.Test.Sdk 18.9.0, xunit 2.9.3, xunit.runner.visualstudio 4.0.0, coverlet.collector 10.0.1). Add DemonstrateConversions() covering the DateOnly-to-DateTime direction the sample did not have: ToDateTime(TimeOnly), ToDateTime(TimeOnly.MinValue), ToDateTime(TimeOnly, DateTimeKind), FromDateTime(DateTime.Now) on both types, TimeOnly.FromTimeSpan/ToTimeSpan, DayNumber/FromDayNumber, ParseExact with a fixed format, and AddHours(double, out int). Set CurrentCulture to en-US in Main so the printed output is the same on every machine instead of depending on the host culture. Eleven new xUnit facts cover the additions. The DateTime.Now facts assert round-trip consistency rather than an exact value, because the suite runs at an arbitrary instant.
1 parent 2c1ead6 commit 66e368e

5 files changed

Lines changed: 211 additions & 7 deletions

File tree

dotnet-datetime/DateOnlyAndTimeOnlyInCSharp/DateOnlyAndTimeOnlyInCSharpExample/DateOnlyAndTimeOnlyInCSharpExample.csproj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
<PropertyGroup>
44
<OutputType>Exe</OutputType>
5-
<TargetFramework>net6.0</TargetFramework>
5+
<TargetFramework>net10.0</TargetFramework>
66
<ImplicitUsings>enable</ImplicitUsings>
77
<Nullable>enable</Nullable>
88
</PropertyGroup>

dotnet-datetime/DateOnlyAndTimeOnlyInCSharp/DateOnlyAndTimeOnlyInCSharpExample/Program.cs

Lines changed: 56 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,19 @@
1-
namespace DateOnlyAndTimeOnlyInCSharpExample;
1+
using System.Globalization;
2+
3+
namespace DateOnlyAndTimeOnlyInCSharpExample;
24

35
public class Program
46
{
57
public static void Main()
68
{
9+
// The printed output below is culture-dependent, so we state the culture
10+
// instead of letting the host machine decide what the samples look like.
11+
CultureInfo.CurrentCulture = new CultureInfo("en-US");
12+
713
DemonstrateDateOnly();
814
DemonstrateTimeOnly();
915
DemonstrateFromDateTime();
16+
DemonstrateConversions();
1017
DemonstrateBasicOperators();
1118
}
1219

@@ -62,6 +69,54 @@ public static void DemonstrateFromDateTime()
6269
Console.WriteLine(timeOnly);
6370
}
6471

72+
public static void DemonstrateConversions()
73+
{
74+
var dateOnly = new DateOnly(2022, 1, 1);
75+
var timeOnly = new TimeOnly(11, 30);
76+
77+
// DateOnly back to DateTime, in all three shapes.
78+
var combined = dateOnly.ToDateTime(timeOnly);
79+
var midnight = dateOnly.ToDateTime(TimeOnly.MinValue);
80+
var utc = dateOnly.ToDateTime(timeOnly, DateTimeKind.Utc);
81+
82+
Console.WriteLine($"{combined:O} Kind={combined.Kind}");
83+
Console.WriteLine($"{midnight:O} Kind={midnight.Kind}");
84+
Console.WriteLine($"{utc:O} Kind={utc.Kind}");
85+
86+
// Neither type has a Now or a Today property.
87+
var today = DateOnly.FromDateTime(DateTime.Now);
88+
var timeOfDay = TimeOnly.FromDateTime(DateTime.Now);
89+
90+
Console.WriteLine(today);
91+
Console.WriteLine(timeOfDay);
92+
93+
// TimeSpan in both directions.
94+
var fromTimeSpan = TimeOnly.FromTimeSpan(TimeSpan.FromHours(11.5));
95+
var sinceMidnight = timeOnly.ToTimeSpan();
96+
97+
Console.WriteLine(fromTimeSpan);
98+
Console.WriteLine(sinceMidnight);
99+
100+
// The underlying day count.
101+
var dayNumber = dateOnly.DayNumber;
102+
var fromDayNumber = DateOnly.FromDayNumber(dayNumber);
103+
104+
Console.WriteLine(dayNumber);
105+
Console.WriteLine(fromDayNumber);
106+
107+
// Parsing a string in a known, fixed format.
108+
var parsed = DateOnly.ParseExact("2022-01-01", "yyyy-MM-dd");
109+
110+
Console.WriteLine(parsed);
111+
112+
// AddHours wraps past midnight; the second overload reports the day boundary.
113+
var elevenPM = new TimeOnly(23, 0);
114+
var wrapped = elevenPM.AddHours(2);
115+
var wrappedWithCount = elevenPM.AddHours(2, out var excessDays);
116+
117+
Console.WriteLine($"{wrapped} / {wrappedWithCount} excessDays={excessDays}");
118+
}
119+
65120
public static void DemonstrateBasicOperators()
66121
{
67122
var firstOfJan = new DateOnly(2022, 1, 1);

dotnet-datetime/DateOnlyAndTimeOnlyInCSharp/Tests/DateOnlyTests.cs

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -112,5 +112,92 @@ public void CanUseGreaterThanOperator()
112112
// Assert.
113113
Assert.True(isAfter);
114114
}
115+
[Fact]
116+
public void CanParseExactWithAKnownFormat()
117+
{
118+
// Arrange.
119+
var text = "2022-01-01";
120+
121+
// Act.
122+
var date = DateOnly.ParseExact(text, "yyyy-MM-dd");
123+
124+
// Assert.
125+
Assert.Equal(new DateOnly(2022, 1, 1), date);
126+
}
127+
128+
[Fact]
129+
public void CanConvertToDateTimeWithATimeOnly()
130+
{
131+
// Arrange.
132+
var dateOnly = new DateOnly(2022, 1, 1);
133+
var timeOnly = new TimeOnly(11, 30);
134+
135+
// Act.
136+
var combined = dateOnly.ToDateTime(timeOnly);
137+
138+
// Assert.
139+
Assert.Equal(new DateTime(2022, 1, 1, 11, 30, 0), combined);
140+
Assert.Equal(DateTimeKind.Unspecified, combined.Kind);
141+
}
142+
143+
[Fact]
144+
public void CanConvertToDateTimeAtMidnight()
145+
{
146+
// Arrange.
147+
var dateOnly = new DateOnly(2022, 1, 1);
148+
149+
// Act.
150+
var midnight = dateOnly.ToDateTime(TimeOnly.MinValue);
151+
152+
// Assert.
153+
Assert.Equal(new DateTime(2022, 1, 1, 0, 0, 0), midnight);
154+
Assert.Equal(DateTimeKind.Unspecified, midnight.Kind);
155+
}
156+
157+
[Fact]
158+
public void CanConvertToDateTimeWithAKnownKind()
159+
{
160+
// Arrange.
161+
var dateOnly = new DateOnly(2022, 1, 1);
162+
var timeOnly = new TimeOnly(11, 30);
163+
164+
// Act.
165+
var utc = dateOnly.ToDateTime(timeOnly, DateTimeKind.Utc);
166+
167+
// Assert.
168+
Assert.Equal(DateTimeKind.Utc, utc.Kind);
169+
Assert.Equal(new DateTime(2022, 1, 1, 11, 30, 0, DateTimeKind.Utc), utc);
170+
}
171+
172+
[Fact]
173+
public void CanGetTodaysDateFromDateTimeNow()
174+
{
175+
// Arrange.
176+
var now = DateTime.Now;
177+
178+
// Act.
179+
var today = DateOnly.FromDateTime(now);
180+
181+
// Assert. CI runs at an arbitrary instant, so we assert consistency, not a value.
182+
Assert.Equal(now.Year, today.Year);
183+
Assert.Equal(now.Month, today.Month);
184+
Assert.Equal(now.Day, today.Day);
185+
}
186+
187+
[Fact]
188+
public void CanRoundTripThroughDayNumber()
189+
{
190+
// Arrange.
191+
var dateOnly = new DateOnly(2022, 1, 1);
192+
193+
// Act.
194+
var dayNumber = dateOnly.DayNumber;
195+
var roundTripped = DateOnly.FromDayNumber(dayNumber);
196+
197+
// Assert.
198+
Assert.Equal(dateOnly, roundTripped);
199+
Assert.Equal(1, dateOnly.DayNumber - new DateOnly(2021, 12, 31).DayNumber);
200+
}
201+
115202
}
116203
}

dotnet-datetime/DateOnlyAndTimeOnlyInCSharp/Tests/Tests.csproj

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,20 @@
11
<Project Sdk="Microsoft.NET.Sdk">
22

33
<PropertyGroup>
4-
<TargetFramework>net6.0</TargetFramework>
4+
<TargetFramework>net10.0</TargetFramework>
55
<Nullable>enable</Nullable>
66

77
<IsPackable>false</IsPackable>
88
</PropertyGroup>
99

1010
<ItemGroup>
11-
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.11.0" />
12-
<PackageReference Include="xunit" Version="2.4.1" />
13-
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.3">
11+
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="18.9.0" />
12+
<PackageReference Include="xunit" Version="2.9.3" />
13+
<PackageReference Include="xunit.runner.visualstudio" Version="4.0.0">
1414
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
1515
<PrivateAssets>all</PrivateAssets>
1616
</PackageReference>
17-
<PackageReference Include="coverlet.collector" Version="3.1.0">
17+
<PackageReference Include="coverlet.collector" Version="10.0.1">
1818
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
1919
<PrivateAssets>all</PrivateAssets>
2020
</PackageReference>

dotnet-datetime/DateOnlyAndTimeOnlyInCSharp/Tests/TimeOnlyTests.cs

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,5 +97,67 @@ public void CanUseGreaterThanOperator()
9797
// Assert.
9898
Assert.True(isAfter);
9999
}
100+
[Fact]
101+
public void CanConvertToAndFromTimeSpan()
102+
{
103+
// Arrange.
104+
var timeOnly = new TimeOnly(11, 30);
105+
106+
// Act.
107+
var sinceMidnight = timeOnly.ToTimeSpan();
108+
var roundTripped = TimeOnly.FromTimeSpan(sinceMidnight);
109+
110+
// Assert.
111+
Assert.Equal(TimeSpan.FromHours(11.5), sinceMidnight);
112+
Assert.Equal(timeOnly, roundTripped);
113+
}
114+
115+
[Fact]
116+
public void CanGetTheCurrentTimeOfDayFromDateTimeNow()
117+
{
118+
// Arrange.
119+
var now = DateTime.Now;
120+
121+
// Act.
122+
var timeOfDay = TimeOnly.FromDateTime(now);
123+
124+
// Assert. CI runs at an arbitrary instant, so we assert consistency, not a value.
125+
Assert.Equal(now.Hour, timeOfDay.Hour);
126+
Assert.Equal(now.Minute, timeOfDay.Minute);
127+
Assert.Equal(now.TimeOfDay.Ticks, timeOfDay.Ticks);
128+
}
129+
130+
[Fact]
131+
public void AddHoursWrapsPastMidnightAndCanReportTheExcessDays()
132+
{
133+
// Arrange.
134+
var elevenPM = new TimeOnly(23, 0);
135+
136+
// Act.
137+
var wrapped = elevenPM.AddHours(2);
138+
var wrappedWithCount = elevenPM.AddHours(2, out var excessDays);
139+
140+
// Assert.
141+
Assert.Equal(new TimeOnly(1, 0), wrapped);
142+
Assert.Equal(new TimeOnly(1, 0), wrappedWithCount);
143+
Assert.Equal(1, excessDays);
144+
}
145+
146+
[Fact]
147+
public void SubtractingTwoValuesIsAlwaysPositive()
148+
{
149+
// Arrange.
150+
var elevenPM = new TimeOnly(23, 0);
151+
var oneAM = new TimeOnly(1, 0);
152+
153+
// Act.
154+
var forwards = oneAM - elevenPM;
155+
var backwards = elevenPM - oneAM;
156+
157+
// Assert.
158+
Assert.Equal(TimeSpan.FromHours(2), forwards);
159+
Assert.Equal(TimeSpan.FromHours(22), backwards);
160+
}
161+
100162
}
101163
}

0 commit comments

Comments
 (0)