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 的主要好处
- 它降低了出现 Bug 的可能性
- 代码变得更具可读性
- 它提供了与时间相关的有用功能
但是,您难道不能简单地使用数值类型(如 int)来表示持续时间吗?
是的,但是这种方法有几个缺点。如果您使用原始类型 在 C# 中表示时间间隔,则无法表达您要表示的计量单位。
如果代码的某个部分“认为”持续时间以分钟为单位,而另一部分则认为以秒为单位,则这种不匹配将导致 Bug。是的,您可以使用注释和/或命名约定来减少此类不匹配的可能性,但这些方法只能到此为止。
另一个例子:假设您有一个方法,该方法接受两个整数作为参数,一个表示标识符,另一个表示持续时间。一个常见的错误是以错误的顺序传递这两个参数。此类错误在代码审查期间很容易被忽略,并且会导致 Bug。但是,如果您对时间间隔使用了 TimeSpan,则该错误将是不可能的:编译器会阻止您传递错误的类型。
另一个后果?代码可读性降低。如果您看到返回 TimeSpan 的方法签名,那么您就知道您正在处理持续时间。另一方面,int 可以是任何东西,您必须求助于命名约定才能理解其含义。
使用原始类型而不是更具体的时间的最后一个缺点是,原始类型无法提供与其表面上表示的域相关的有用操作。毕竟,数字只是一个数字;它无法了解任何关于时间域的信息。
另一方面,TimeSpan 确实提供了许多与时间间隔概念相关的有用操作。此外,毫不奇怪,它与其他时间相关类型(如 DateTime)配合良好。
获取 C#TimeSpan
涵盖了 TimeSpan 类型的“是什么”之后,现在是“如何”了。我要向您展示的第一件事是如何获取 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); // 1000 万个刻度等于一秒
一旦您深入到微秒,事情可能会变得令人困惑,因此这里提醒一下这些单位
- 毫秒是千分之一秒。
- 微秒是百万分之一秒。
- 微秒包含 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 value: {result}" : "Input isn't a valid timespan";
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 提供了允许您获取其值组成部分的属性,例如小时、分钟、天等等。以下是所有属性
- 天
- 小时
- 毫秒
- 分钟
- 秒
- 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 变量的时间之间发生了夏令时转换,您最终会得到一个错误的值,其中包含一个额外的小时或缺少一个小时。
在这些情况下,您应该使用不随夏令时转换而波动的“中性”时区。换句话说,使用 UTC
var start = DateTime.UtcNow;
// 发生了很多事情
var end = DateTime.UtcNow;
TimeSpan duration = end - start;
C# TimeSpan:告别原始类型痴迷
在这篇文章中,我们向您介绍了 TimeSpan。现在,您知道它是什么、可以将其用于什么、有什么好处以及如何开始使用它。
现在,作为下一步,我邀请您继续探索。好好阅读 TimeSpan 文档。创建玩具项目以探索此类型及其与 .NET 中其他时间相关类型的关系。当然,一旦有机会,就在您的实际项目中使用 TimeSpan。让原始类型痴迷成为过去。
这篇文章由 Carlos Schults 撰写。Carlos 是一位技术娴熟的软件工程师,也是众多客户的杰出技术作家。他的热情是深入了解事物的本质(原始来源),并通过平易近人和信息丰富的技术内容吸引读者。