C# TimeSpan 详细指南
作者:社区 / 开发者
2024年5月2日
导航至
哎,C# 的 TimeSpan 结构体。它相当低调,而且似乎没有得到充分利用,如果我的个人经验有任何价值的话。尽管如此,C# 的 TimeSpan 类型仍然具有巨大的潜力,可以使您的代码更加易于阅读和健壮。
您不相信吗?好吧,阅读这篇文章,您将了解到一个类型是如何做到看似最简单的任务——表示时间间隔——却非常出色的。以下是如何进行
- 我们将从对 TimeSpan 的定义和解释开始。您将了解这个类型是什么,以及它能解决哪些问题。
- 然后,您将了解使用 TimeSpan 而不是其他替代方案的好处。
- 接下来,是时候实际操作了:您将了解如何开始使用 TimeSpan,包括其基本语法、初始化方式,以及对其关键方法和属性的说明。
- 最后,我们将涵盖该类型的一些用例以及您可能遇到的常见错误。
让我们开始吧。
C# TimeSpan:基础知识
让我们从 TimeSpan 101 开始。
C# 中什么是 TimeSpan?
首先,从技术上讲——也许有点咬文嚼字——“C# TimeSpan”并不是一个东西。System.TimeSpan 结构体是 .NET 中的一个类型,其使用并不局限于 C#;您还可以使用任何 .NET 语言,例如 VB.NET 和 F#。(但,是的,这篇文章是为 C# 程序员准备的,所以所有示例都将使用该语言。)
那么,System.TimeSpan 结构体是什么?它来自 .NET BCL(基础类库),表示一个时间间隔。您是否需要表示“五分钟”或“两个小时”的概念?如果是这样,TimeSpan 就是您要找的。
C# TimeSpan:重要性和好处
以下是我认为使用 TimeSpan 的最大好处
- 它降低了出现错误的可能性
- 代码变得更易于阅读
- 它提供了与时间相关的有用功能
但是,你不能简单地使用一个数值类型——比如 int——来表示持续时间吗?
是的,但这种方法有几个缺点。首先,它会导致更易于出现错误的代码。如果在C#中使用原始类型来表示时间间隔,你无法表达你所指的单位。
如果代码的一部分“认为”持续时间是分钟,而另一部分假设它是秒,这样的不匹配将导致错误。是的,你可以使用注释和/或命名约定来减少这种错误的可能性,但这些只能做到一定程度。
另一个例子:假设你有一个方法,它接受两个整数作为参数,一个表示标识符,另一个表示持续时间。一个常见的错误是将两个参数以错误的顺序传递。这种错误在代码审查期间很容易被忽略,并会导致错误。然而,如果你使用了 TimeSpan 来表示时间间隔,这种错误将不可能发生:编译器将阻止你传递错误的数据类型。
另一个后果是代码的可读性降低。如果你看到一个方法签名返回一个 TimeSpan,那么你知道你正在处理一个持续时间。另一方面,一个 int 可能是任何东西,你必须依赖命名约定来理解它的含义。
使用原始类型而不是更具体的“时间”的最后一个缺点是,原始类型无法提供与它所代表的领域相关的有用操作。毕竟,一个数字只是一个数字;它无法了解关于时间的领域。
另一方面,TimeSpan 提供了许多与时间间隔概念相关的有用操作。而且——不出所料——它与其他时间相关类型(如 DateTime)配合得很好。
获取C#TimeSpan
在了解了 TimeSpan 类型的 what 之后,现在是时候了解 how 了。我将向您展示的第一个内容是如何获取一个 TimeSpan 值。你可以通过几种方式做到这一点
从两个DateTime值中减去
DateTime now = DateTime.Now;
DateTime twoHoursFromNow = now.AddHours(2);
TimeSpan difference = twoHoursFromNow - now;
Console.WriteLine(difference); // 显示 '02:00:00'
使用其中一个工厂方法
var twoHours = TimeSpan.FromHours(2);
var tenMinutes = TimeSpan.FromMinutes(10);
var eightSeconds = TimeSpan.FromSeconds(8);
将两个或更多TimeSpan相加
var fortyMinutes = TimeSpan.FromMinutes(40);
var twentyMinutes = TimeSpan.FromMinutes(20);
var oneHour = TimeSpan.FromHours(1);
var twoHours = fortyMinutes + twentyMinutes + oneHour;
Console.WriteLine(twoHours); // 显示 '02:00:00'
使用无参数构造函数(空值)
var time = new TimeSpan();
Console.WriteLine(time); // 显示 '00:00:00'
使用其他几个构造函数
var a = new TimeSpan(hours: 2, minutes: 10, seconds: 25);
var b = new TimeSpan(days: 1, hours: 3, minutes: 5, seconds: 48);
var c = new TimeSpan(days: 1, hours: 3, minutes: 5, seconds: 48, milliseconds: 456);
var d = new TimeSpan(days: 1, hours: 3, minutes: 5, seconds: 48, milliseconds: 456, microseconds: 45678);
var e = new TimeSpan(ticks: 10_000_000); // 10百万tick相当于一秒
一旦细化到微秒,事情可能会变得混乱,所以这里有一个关于这些单位的提醒
- 毫秒是秒的一千分之一。
- 微秒是秒的一百万分之一。
- 微秒包含1000纳秒。换句话说,纳秒是秒的一亿分之一。
- 一个tick相当于100纳秒。换句话说,一秒包含1000万tick。
从字符串表示形式解析
最后,您还可以通过解析字符串表示形式来获取一个 TimeSpan 值,这与您处理日期和数字的方式类似。以下几种方法遵循 Parse 和 TryParse 模式:没有“Try”后缀的方法在解析失败时抛出异常,而带有后缀的方法返回一个布尔值,指示操作的状态。实际解析的值通过 out 参数返回。
以下是一个 TryParse() 版本示例:
var success = TimeSpan.TryParse("1:20:15", out TimeSpan result);
var message = success ? $"Timespan 值: {result}" : "输入不是一个有效的时长";
Console.WriteLine(message);
C# 中与 TimeSpan 相关的关键方法和属性有哪些?
现在您已经知道了获取一个闪亮的、全新的 TimeSpan 值的主要方法。这很棒,但您可以用它做什么呢?这个类型提供了哪些功能?
实际上,TimeSpan 类型有很多方法、字段和属性。我不会全部介绍,只会介绍最重要的。
方法
您刚刚看到,您可以如何添加两个或更多 TimeSpan 值。您还可以使用 Subtract、Multiply、Divide 或相应的运算符执行其他操作。
var oneHour = TimeSpan.FromHours(1);
var twoHours = oneHour.Multiply(2);
var fourHours = twoHours * 2; // 这也行
var threeHours = fourHours.Subtract(oneHour);
var oneAndAHalfHours = threeHours.Divide(2);
Console.WriteLine(oneAndAHalfHours); // 显示 '01:30:00'
Console.WriteLine(threeHours); // 显示 '03:00:00'
Console.WriteLine(fourHours); // 显示 '04:00:00'
重要的是要记住,TimeSpan 是一个不可变类型。每次您对其值进行操作时,它都会返回一个新的值,而不是改变现有的值。
您需要反转 TimeSpan 吗?也就是说,使正值变为负值或反之亦然?那么,Negate() 方法就是您的朋友。如果您需要 TimeSpan 的绝对值,则 Duration() 是您要找的方法。
TimeSpan 覆盖了 Equals 方法,这可以通过值进行比较
var oneHour = TimeSpan.FromHours(1);
var sixtyMinutes = TimeSpan.FromMinutes(60);
Console.WriteLine(oneHour.Equals(sixtyMinutes) ? "equal" : "not equal"); // 显示 'equal'
CompareTo 方法可用于确定每个 TimeSpan 值是否比另一个值短、长或相等,为每种情况返回一个整数(-1、0、1)。这在排序值列表时很有用。
属性
TimeSpan 提供了属性,允许您获取其值的各个组成部分,例如小时、分钟、天数等。以下是所有属性
- Days
- Hours
- Milliseconds
- Minutes
- Seconds
- Ticks
- TotalDays
- TotalHours
- TotalMilliseconds
- TotalMinutes
- TotalSeconds
以“Total”开头的属性都是 double 类型。它们返回 TimeSpan 实例的总值,以所需的单位表示,并允许有分数部分。没有“Total”的属性是整数(int 和 long 类型),它们返回相应组件的值,没有分数部分。
让我们用一个例子来看看
var ninetyMinutes = new TimeSpan(hours: 1, minutes: 30, seconds: 0);
Console.WriteLine(ninetyMinutes.TotalHours); // 显示 1.5
Console.WriteLine(ninetyMinutes.Hours); // 显示 1
Console.WriteLine(ninetyMinutes.TotalMinutes); // 显示 90
Console.WriteLine(ninetyMinutes.Minutes); // 显示 30
C# 中如何格式化 TimeSpan
C# 中有几种方式来格式化 TimeSpan。让我们来看看其中的一些。
首先,您可以使用 ToString() 方法来返回默认格式
var timeSpan = new TimeSpan(days: 2, hours: 1, minutes: 30, seconds: 0);
Console.WriteLine(timeSpan.ToString()); // 显示 '2.01:30:00'
请注意,上面的 ToString() 不是必需的,因为 WriteLine() 已经在其传入的任何对象上调用了 ToString()。我之所以包含它,只是为了教学目的。
现在,让我们看一个包含更多组件的示例
var timeSpan = new TimeSpan(3, 15, 30, 45, 500);
Console.WriteLine(timeSpan.ToString(@"d'd 'h'h 'm'm 's's'")); // 显示 '3d 15h 30m 45s'
最后,我们来看另一种利用 字符串插值 和直接访问 TimeSpan 值属性的替代方案
var timeSpan = new TimeSpan(days: 3, hours: 15, minutes: 30, seconds: 45, milliseconds: 500);
var directFormat = $"{timeSpan.Days} 天, {timeSpan.Hours} 小时, {timeSpan.Minutes} 分钟, {timeSpan.Seconds} 秒";
Console.WriteLine(directFormat); // 显示 '3 天, 15 小时, 30 分钟, 45 秒'
C# TimeSpan:常见问题和避免方法
在结束之前,让我们来谈谈人们在使用 TimeSpan 值时常见的错误以及如何避免它们。
解析错误
在这个 StackOverflow 问题 中,用户尝试了以下操作
TimeSpan timeTaken = TimeSpan.Parse("51:45:33");
失败了。为什么?在上面的格式中,第一个组件表示小时,其有效范围是 23。因此,为了使其工作,这个人需要使用 Days 组件来表示完整的信息。
误解组件
在文章的前面,你学习了以“Total”开头的 TimeSpan 属性和那些不以“Total”开头的属性之间的区别。一个常见的错误是误解并交换属性类型,从而导致不期望的行为。
错误的算术
人们在处理 TimeSpan 值时可能会犯多种算术错误,但我在这里关注的一个非常具体的错误是:在从 DateTime 值中减去时忽略夏令时。
让我们假设你有这样的代码
var start = DateTime.Now;
// 发生了很多事情
var end = DateTime.Now;
TimeSpan duration = end - start;
这看起来可能没有问题,但如果你生活在有夏令时的地区,可能存在潜在的问题。如果在 start 和 end 变量初始化之间发生夏令时转换,你最终会得到一个错误值,包含额外的 hour 或缺少的 hour。
在这些情况下,你应该使用一个“中性”时区,该时区不会随着夏令时转换而波动。换句话说,使用 UTC
var start = DateTime.UtcNow;
// 发生了很多事情
var end = DateTime.UtcNow;
TimeSpan duration = end - start;
C# TimeSpan:告别原始固执
在这篇文章中,我们向您介绍了 TimeSpan。现在,你知道了它是什么,你可以用它来做什么,它的好处是什么,以及如何开始使用它。
现在,作为下一步,我邀请你继续探索。仔细阅读 TimeSpan 文档。创建玩具项目来探索这个类型及其与 .NET 中其他时间相关类型的关联。当然,一旦有机会,就在你的真实项目中使用 TimeSpan。让原始固执成为过去。
本文由Carlos Schults撰写。 Carlos是一位技术娴熟的软件工程师,也是一位资深的为多个客户撰写技术文档的作家。他热衷于深入探究事物的本质(原始来源),并用易于理解且信息丰富的技术内容吸引读者。