在 .NET 中使用日期和时间(针对 .NET 8/9 更新)

科技   2024-11-01 08:01   广东  

处理日期和时间是任何软件系统的一个重要方面,无论您是记录事件、计算时差还是安排任务。在 .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 通过将时间源与系统时间分离,为单元测试提供了灵活性。


推荐阅读:
2款.NET开源且高效的代码格式化工具
一个适用于 ASP.NET Core 的轻量级插件框架
面试常考:彻底搞清楚C#垃圾回收机制(GC)
在 .NET 和 Python 中创建了相同的 API — 哪个性能更好?
6款支持C#语言的AI辅助编程工具,开发效率提升利器!
.Net 依赖注入深入探索,做一个DI拓展,实现一个简易灵活的,自动依赖注入框架

点击下方卡片关注DotNet NB

一起交流学习

▲ 点击上方卡片关注DotNet NB,一起交流学习

请在公众号后台

回复 【路线图】获取.NET 2024开发者路线
回复 【原创内容】获取公众号原创内容
回复 【峰会视频】获取.NET Conf大会视频
回复 【个人简介】获取作者个人简介
回复 【年终总结】获取作者年终回顾
回复 加群加入DotNet NB 交流学习群

长按识别下方二维码,或点击阅读原文。和我一起,交流学习,分享心得。

DotNet NB
.NET 技术学习分享,社区热点分享,专注为 .NET 社区做贡献,愿我们互相交流学习,共同推动社区发展
 最新文章