DateTime是C#中处理日期和时间的核心结构,属于值类型,位于System命名空间下。它内部用一个64位整数表示从公元0001年1月1日午夜开始经过的100纳秒间隔数,因此精度是100纳秒,可表示范围从0001年1月1日到9999年12月31日。掌握DateTime的创建、格式化、运算和解析,是写好任何C#程序的基本功,本文将从零开始系统讲解。

一、创建DateTime对象的几种方式
最常见的是通过静态属性DateTime.Now获取当前本地时间,通过DateTime.UtcNow获取UTC标准时间,通过DateTime.Today获取当天日期(时间部分为零点)。三者用途不同:服务器端记录日志建议用UtcNow,避免时区混乱;显示给用户看则用Now。
除了取当前时间,还可以用构造函数指定年月日时分秒来创建对象。构造函数有多个重载,最简单的只需要年、月、日三个参数,完整的可以精确到毫秒。需要注意的是,传入的参数必须在合法范围内,比如月份不能是13,2月的天数要考虑闰年,否则会抛出ArgumentOutOfRangeException异常。
// 获取当前时间的三种方式 DateTime now = DateTime.Now; // 当前本地时间 DateTime utc = DateTime.UtcNow; // UTC时间 DateTime today = DateTime.Today; // 今天零点 // 使用构造函数创建指定日期 DateTime birthday = new DateTime(1995, 6, 15); DateTime precise = new DateTime(2024, 3, 1, 14, 30, 0); // 特殊的最小值和最大值 DateTime min = DateTime.MinValue; // 0001-01-01 DateTime max = DateTime.MaxValue; // 9999-12-31
另外还有一种空值场景需要注意。DateTime是结构体,不能直接赋值为null。如果数据库字段允许为空(比如用户还没填出生日期),应该使用可空类型DateTime?,判断时用HasValue属性,这也是新手常踩的坑之一。
二、常用属性与格式化输出
拿到一个DateTime对象后,可以通过Year、Month、Day、Hour、Minute、Second等属性获取各个部分,还可以通过DayOfWeek获取星期几(枚举类型)、DayOfYear获取这是一年中的第几天。这些属性都是只读的,不能直接修改,修改时间只能通过后面讲到的Add系列方法。
格式化输出是实际开发中用得最多的功能。ToString方法配合格式字符串可以把日期变成想要的样式。常用的标准格式符号有:yyyy四位年份、MM两位月份、dd两位日期、HH24小时制、hh12小时制、mm分钟、ss秒,注意大小写敏感,MM是月份而mm是分钟,写反了结果会莫名其妙。
DateTime dt = new DateTime(2024, 5, 20, 15, 8, 30);
// 自定义格式
Console.WriteLine(dt.ToString("yyyy-MM-dd")); // 2024-05-20
Console.WriteLine(dt.ToString("yyyy/MM/dd HH:mm:ss")); // 2024/05/20 15:08:30
Console.WriteLine(dt.ToString("yyyy年MM月dd日")); // 2024年05月20日
// 标准格式说明符
Console.WriteLine(dt.ToString("d")); // 短日期,如 2024/5/20
Console.WriteLine(dt.ToString("D")); // 长日期,如 2024年5月20日
Console.WriteLine(dt.ToString("f")); // 完整日期+短时间
Console.WriteLine(dt.ToString("yyyy-MM-dd hh:mm:ss tt")); // 12小时制带上午/下午
// 常用属性
Console.WriteLine(dt.DayOfWeek); // Monday
Console.WriteLine(dt.DayOfYear); // 一年中的第几天还有一点值得注意:如果要在字符串插值中直接拼接日期,可以在插值表达式里加冒号指定格式,例如$"{dt:yyyy-MM-dd}",写法更简洁,实际项目中推荐这种形式。此外,不同区域的文化设置会影响标准格式的输出结果,服务端程序建议显式传入CultureInfo.InvariantCulture避免服务器区域设置不同导致格式不一致。
三、日期加减与时间差计算
DateTime本身不可变,所有Add系列方法都不会修改原对象,而是返回一个新的DateTime实例,所以必须用变量接收返回值,直接调用不赋值是没有任何效果的,这是新手最容易犯的错误。AddDays可以传小数甚至负数,加负数就等于往前推算日期,非常方便。
计算两个日期相差多少天、多少小时,需要借助TimeSpan结构。两个DateTime相减的结果就是TimeSpan,它有Days、Hours、Minutes、TotalDays、TotalHours等属性。要特别区分Days和TotalDays:Days只表示完整的天数部分,而TotalDays是把整个时间差换算成天后的小数形式,做统计时用TotalDays更准确。
DateTime start = new DateTime(2024, 1, 1);
DateTime end = new DateTime(2024, 3, 15, 10, 30, 0);
// 日期加减(返回新对象)
DateTime nextWeek = start.AddDays(7);
DateTime lastMonth = start.AddMonths(-1);
DateTime later = start.AddHours(5).AddMinutes(30);
// 计算时间差
TimeSpan diff = end - start;
Console.WriteLine(diff.Days); // 74(完整天数部分)
Console.WriteLine(diff.TotalDays); // 74.4375(换算成天)
Console.WriteLine(diff.TotalHours); // 1786.5
// 比较两个日期
if (start < end)
{
Console.WriteLine("start早于end");
}
// 判断闰年、获取当月天数
bool isLeap = DateTime.IsLeapYear(2024); // true
int daysInMonth = DateTime.DaysInMonth(2024, 2); // 29除了加减运算,DateTime还提供了两个很实用的静态工具方法:DateTime.IsLeapYear用来判断闰年,DateTime.DaysInMonth用来获取某年某月有多少天,写日历类功能时特别有用,不用自己再去写判断逻辑。比较日期时可以直接用大于小于运算符,DateTime已经重载了这些比较运算符。
四、字符串解析与注意事项
把字符串转回DateTime用Parse或ParseExact,但Parse对格式要求宽松且失败会抛异常,实际项目中更推荐用TryParse,它通过返回值判断是否成功,解析结果通过out参数带出,不会因为用户输入了脏数据导致程序崩溃。如果字符串格式固定,比如解析接口返回的标准格式,可以用ParseExact指定精确的格式字符串,效率更高也更严格。
string input = "2024-05-20 15:30:00";
// 推荐方式:TryParse
if (DateTime.TryParse(input, out DateTime result))
{
Console.WriteLine($"解析成功:{result:yyyy-MM-dd}");
}
else
{
Console.WriteLine("日期格式不正确");
}
// 指定精确格式解析
string s = "20/05/2024";
DateTime dt = DateTime.ParseExact(s, "dd/MM/yyyy",
System.Globalization.CultureInfo.InvariantCulture);最后提两个进阶注意事项。第一,如果项目中安装了NodaTime或使用.NET 6以上版本,可以考虑用DateOnly和TimeOnly分别表示纯日期和纯时间,语义更清晰。第二,涉及跨时区的业务(比如国际化的订单系统),不要简单依赖Now,建议统一存储UTC时间,展示时再按用户时区转换,必要时使用DateTimeOffset保留时区偏移信息,能避免很多隐蔽的时间计算错误。