处理日期和时间是任何软件系统的一个重要方面,无论您是记录事件、计算时差还是安排任务。在 .NET 中,使用日期和时间数据的范围可以从简单到高度复杂,具体取决于您的用例。
介绍
在 .NET 中,日期和时间操作围绕几个基本类型:
DateTime:表示日期和时间的最常用类型。
DateTimeOffset:表示与协调世界时 (UTC) 有偏移量的时间点。
TimeSpan:表示时间间隔(例如,持续时间或时间差)。
DateOnly 和 :在 .NET 6 中引入,分别表示仅表示日期或仅时间。TimeOnly
TimeProvider:在 .NET 8 中添加,这为时间提供了可测试的抽象,使涉及时间的单元测试变得更加容易。
这些类型中的每一种都适用于不同的方案。了解何时以及如何使用它们对于构建强大的应用程序至关重要。
1. DateTime:日期和时间的主力军
该结构是 .NET 中使用最广泛的类,用于表示日期和时间。它将日期和时间信息封装到毫秒。DateTime
主要特点:
刻度: 在内部,存储为自 0001 年 1 月 1 日以来的“刻度”(100 纳秒间隔)的数量。DateTime
种类:它可以表示:
UTC: 协调世界时。
Local:运行代码的计算机的时区。
Unspecified:未定义时区时。
下面是一个创建对象的示例:DateTime
DateTime now = DateTime.Now; // Current local date and time
DateTime utcNow = DateTime.UtcNow; // Current UTC date and time
DateTime specificDate = new DateTime(2023, 9, 30, 14, 30, 0); // A specific date and time
常见操作 :DateTime
加或减时间:
您可以使用 和 等方法轻松添加或减去天数、小时或其他时间间隔。.AddDays().AddHours()
DateTime tomorrow = now.AddDays(1);
DateTime nextHour = now.AddHours(1);
格式化日期:
向用户显示日期时,将对象格式化为字符串是一项常见的操作。该方法允许自定义格式。DateTimeToString
string formattedDate = now.ToString("yyyy-MM-dd HH:mm:ss");
虽然非常有用,但它有一个明显的限制:它不能很好地处理时区。如果您的应用程序需要跨不同地理区域管理时间,则单独管理可能会引入错误或意外行为。这就是发挥作用的地方。DateTimeDateTimeDateTimeOffset
2. DateTimeOffset:轻松处理时区
该类型通过包含与 UTC 的偏移量进行扩展,从而更轻松地使用时区。当您不仅需要跟踪时间,还需要跟踪与 UTC 的差异时,此结构非常有用。DateTimeOffsetDateTime
创建 的示例 :DateTimeOffset
DateTimeOffset currentTimeWithOffset = DateTimeOffset.Now; // Current time with the system's time zone offset
DateTimeOffset specificTimeWithOffset = new DateTimeOffset(2023, 9, 30, 14, 30, 0, TimeSpan.FromHours(-5)); // Specific time with a -5 hours offset from UTC
为什么使用 ?DateTimeOffset
DateTimeOffset当您需要存储或传输时间同时跟踪时区或偏移量时,它特别有用。例如,如果您在纽约记录一个事件,在东京记录另一个事件,则需要确保两个时间戳都反映正确的本地时间和 UTC 偏移量。
DateTimeOffset eventTimeInNY = new DateTimeOffset(2023, 9, 30, 9, 0, 0, TimeSpan.FromHours(-4)); // NY (UTC-4)
DateTimeOffset eventTimeInTokyo = new DateTimeOffset(2023, 9, 30, 23, 0, 0, TimeSpan.FromHours(9)); // Tokyo (UTC+9)
此类型通过显式管理 UTC 偏移量来确保跨时区的一致性。
3. TimeSpan:使用 Durations
该结构表示持续时间,而不是特定的时间点。当您需要计算 2 或 值之间的差值,或者需要表示经过的时间时,这非常有用。TimeSpanDateTimeDateTimeOffset
创建 的示例 :TimeSpan
TimeSpan duration = new TimeSpan(1, 30, 0); // 1 hour, 30 minutes, 0 seconds
DateTime endTime = now.Add(duration);
用于计算:TimeSpan
您可以减去 2 或 对象,以获得表示它们之间的差异的 a。DateTimeDateTimeOffsetTimeSpan
TimeSpan difference = endTime - now;
Console.WriteLine($"The time difference is {difference.TotalMinutes} minutes.");
这对于计算特定任务的持续时间、事件间隔或剩余时间特别有用。
4. DateOnly 和 TimeOnly:.NET 6+ 中的简单性
在 .NET 6 中引入,提供了一种更直观、更简洁的方式来处理您只关心日期或时间的情况,而没有任何时区或偏移量复杂性。DateOnlyTimeOnly
例:
DateOnly:非常适合存储生日、周年日期或截止日期。
DateOnly birthDate = new DateOnly(1990, 5, 20);
Console.WriteLine(birthDate.ToString()); // Outputs: 05/20/1990
TimeOnly:非常适合存储营业时间或约会时段等时间。
TimeOnly meetingTime = new TimeOnly(14, 30); // 2:30 PM
Console.WriteLine(meetingTime.ToString()); // Outputs: 14:30
5. TimeProvider(在 .NET 8 中引入)
该类是 .NET 8 的最新添加,它提供了时间抽象,允许您在单元测试中模拟或模拟时间。这解决了测试基于时间的功能时的一个常见问题。TimeProvider
例:
要将类用于自定义时间模拟:TimeProvider
TimeProvider provider = TimeProvider.System; // Use the system's time
DateTimeOffset currentTime = provider.GetUtcNow(); // Get current UTC time
您还可以为单元测试场景创建虚构实现,以模拟不同的时间点。TimeProvider
这种抽象实现了更好的可测试性,消除了对 or 的依赖,这可能会导致不稳定的测试。DateTime.NowDateTimeOffset.Now
6. Unix 时间戳:互操作性
Unix 时间戳表示自 1970 年 1 月 1 日 (UTC) 以来的秒数,经常用于系统和 API。.NET 提供内置支持,用于将 Unix 时间戳转换为 Unix 时间戳,反之亦然。DateTimeOffset
将 Unix 时间戳转换为 :DateTimeOffset
long unixTime = 1625072400; // Example Unix timestamp
DateTimeOffset dateTime = DateTimeOffset.FromUnixTimeSeconds(unixTime);
Console.WriteLine(dateTime); // Outputs: 30/06/2021 12:00:00 PM +00:00
转换为 Unix 时间戳:DateTimeOffset
DateTimeOffset now = DateTimeOffset.UtcNow;
long unixTimeNow = now.ToUnixTimeSeconds();
Console.WriteLine(unixTimeNow);
此转换可帮助您与使用 Unix 时间戳的外部系统无缝协作。
7. NodaTime:强大的日期和时间库
虽然 .NET 提供了用于处理日期和时间的可靠内置工具,但复杂的应用程序可能会受益于 NodaTime,这是一个旨在处理日期和时间的综合库,尤其是在涉及多个时区或不同日历的情况下。
NodaTime 的主要优势:
更清晰的 API:减少使用日期、时间和时区时的歧义。
更好的时区支持:无缝处理历史和未来时区数据。
ISO 8601 合规性:更容易遵守国际日期/时间标准。
使用 NodaTime 的示例:
var clock = SystemClock.Instance.GetCurrentInstant();
DateTimeZone timeZone = DateTimeZoneProviders.Tzdb["America/New_York"];
ZonedDateTime nyTime = clock.InZone(timeZone);
Console.WriteLine(nyTime); // Outputs current time in New York
当您需要管理全球时区或使用非公历时,NodaTime 特别有用。
8. 处理闰年和闰日
闰年和闰日会带来复杂性,尤其是在计算持续时间、延长合同或确定某人的年龄时。.NET 有助于无缝处理这些情况。
示例:计算年龄和处理闰年生日:
DateTime birthday = new DateTime(2000, 2, 29); // Leap year birthday
DateTime today = DateTime.Today;
int age = today.Year - birthday.Year;
// Adjust if the birthday hasn't occurred yet this year
if (birthday > today.AddYears(-age)) age--;
Console.WriteLine($"The person is {age} years old.");
9. 常见挑战和最佳实践
尽管 .NET 提供了灵活性,但使用日期和时间仍然存在挑战。以下是一些常见的陷阱以及如何避免它们:
9.1 时区和夏令时 (DST)
处理时区和 DST 更改可能很棘手。跨不同时区安排时,请始终考虑:
以 UTC 格式存储日期:在存储时间戳时(例如,在数据库中),请使用 UTC 以避免夏令时偏移导致的差异。
DateTime utcTimestamp = DateTime.UtcNow;
当您需要通过偏移量保留本地时间时使用。DateTimeOffset
9.2 日期解析和格式化
如果格式定义不明确,则从用户输入或外部数据源解析日期可能会导致错误。在解析字符串时,请始终指定预期的格式,以避免异常。
string dateStr = "30-09-2023";
DateTime parsedDate = DateTime.ParseExact(dateStr, "dd-MM-yyyy", CultureInfo.InvariantCulture);
9.3 比较日期和时间
比较日期或时间时,请确保在相同的基础上进行比较(例如,均采用 UTC 或均采用当地时间)。
bool isSameMoment = DateTime.UtcNow == someOtherDateTime.ToUniversalTime();
用于本地时间或 UTC 时间的简单日期和时间操作。DateTime
首选使用时区或需要保持与 UTC 的偏移量。DateTimeOffset
用于计算两个时间点之间的持续时间或间隔。TimeSpan
对于只有日期或时间更简单的方案,请考虑 and。DateOnlyTimeOnly
将 NodaTime 用于涉及时区、日历或历史日期处理的更复杂的场景。
TimeProvider 通过将时间源与系统时间分离,为单元测试提供了灵活性。
点击下方卡片关注DotNet NB
一起交流学习
▲ 点击上方卡片关注DotNet NB,一起交流学习
请在公众号后台