1. 认识 DateTime 类型
日期和时间——这是任何业务逻辑的基础砖头,哪怕是最简单的项目也不能忽略。有时候你得知道用户啥时候注册的,啥时候发邮件,航班表怎么排,时区怎么搞——到处都是日期!但说实话,处理日期和时间是编程里最坑的主题之一。😅
C#给你两个特别重要的时间类型:DateTime 和 DateTimeOffset。简单说,一个适合简单场景,另一个适合涉及时区和精确时间存储的任务。
DateTime 是啥?
DateTime 类型是个结构体,定义在 System 命名空间里。它用来存储日期和时间,精确到100纳秒(十亿分之一秒)。
- 值类型(struct),也就是按值复制。
- 同时包含日期和时间。
- 可以只存日期(2025-05-24),也可以存日期加时间(2025-05-24 15:17:42)。
- “不知道”时区,除非你明确指定。
2. 创建 DateTime
有好几种方式,咱们挑主要的说。
1. 当前日期和时间
DateTime now = DateTime.Now; // 电脑本地时间
DateTime utcNow = DateTime.UtcNow; // 格林威治时间(UTC)
- Now —— 就是“你电脑上的时间”,带着Windows本地时区。
- UtcNow —— “通用时间”,也就是世界协调时间。存数据库或者全球系统用这个很方便。
2. 构造函数
// 年,月,日
var date1 = new DateTime(2025, 5, 24);
// 年,月,日,小时,分钟,秒
var date2 = new DateTime(2025, 5, 24, 12, 0, 0);
// 年,月,日,小时,分钟,秒,毫秒
var date3 = new DateTime(2025, 5, 24, 12, 0, 0, 500);
月份不是从0开始的! 也就是说一月是1,十二月是12。
3. 从字符串解析
经常日期是字符串形式来的!比如用户手动输入日期。
string userInput = "2025-05-24 18:30";
DateTime parsed = DateTime.Parse(userInput); // 可能会抛异常!
想安全点解析——用 TryParse:
string input = "2025-05-24 18:30";
if (DateTime.TryParse(input, out DateTime result))
{
Console.WriteLine($"转换成功: {result}");
}
else
{
Console.WriteLine("字符串转日期失败。");
}
4. 获取“只有日期”的DateTime
DateTime today = DateTime.Today; // 今天,时间是00:00:00
提取日期和时间的组成部分
所有属性都能直接拿到:
var dt = new DateTime(2025, 5, 24, 15, 17, 42);
int year = dt.Year; // 2025
int month = dt.Month; // 5(五月)
int day = dt.Day; // 24
int hour = dt.Hour; // 15
int minute = dt.Minute; // 17
int second = dt.Second; // 42
DayOfWeek dow = dt.DayOfWeek; // 星期五
3. 用 DateTime 干活
日期加减
时间在编程里过得飞快,别自己算“离deadline还有几小时”,直接用类的方法:
DateTime now = DateTime.Now;
DateTime tomorrow = now.AddDays(1); // 明天
DateTime afterFiveHours = now.AddHours(5); // 5小时后
DateTime inTenMinutes = now.AddMinutes(10); // 10分钟后
两个日期的差值:TimeSpan
两个日期的差值是个独立的类型,叫 TimeSpan:
DateTime deadline = new DateTime(2026, 6, 1);
DateTime now = DateTime.Now;
TimeSpan untilDeadline = deadline - now;
Console.WriteLine($"离截止还有天数: {untilDeadline.Days}");
Console.WriteLine($"离截止还有小时: {untilDeadline.TotalHours:N2}");
TimeSpan 是“差值”,不是“具体的日期/时间”!
判断日期是未来还是过去
if (deadline > now)
Console.WriteLine("截止还没到!");
else
Console.WriteLine("截止已经过了!赶紧交作业!");
4. 日期在底层是怎么存的
为了方便日期加减,程序员决定把所有日期都存成从公元元年开始经过的秒数。取值范围如下:
| 属性 | 值 |
|---|---|
| 最小日期 | = 0001-01-01 00:00:00 |
| 最大日期 | = 9999-12-31 23:59:59.9999999 |
| 单位 | 1 tick = 100纳秒 |
DateTime 存的不是秒数,而是“tick”数(ticks),从特殊的日期——公元0001年1月1日(没错,程序员不管恐龙,纪年更严谨)开始。一个tick=100纳秒。
5. DateTimeOffset 类型
有时候 DateTime 不够用。举个经典例子:公司在两个国家有业务,时区不同,所有会议都按“我的时间”定。咋办?
用 DateTime 存的时间其实是“抽象的”:就是一串数字和tick,除非你明确说这是哪个时区。比如你拿到"2025-05-24 15:00",你根本不知道——这是内罗毕时间?巴格达?柏林?(要是还有夏令时,头都大了!)
所以,涉及精确时刻的任务,不只是“日历上的日期”,就有了第二种类型——DateTimeOffset。创建 DateTimeOffset 时可以指定偏移量(比如内罗毕比UTC快3小时):
var localTime = new DateTimeOffset(2025, 5, 24, 15, 0, 0, TimeSpan.FromHours(3));
如果你遇到这些情况,就用 DateTimeOffset:
- 需要精确时刻(比如日志事件,和服务器时间绑定)。
- 程序有不同时区的用户。
- 要存/传带时区的时间(比如交易、事件、日程)。
6. 怎么创建 DateTimeOffset?
- 当前时刻
DateTimeOffset now = DateTimeOffset.Now; // 本地时间+偏移 DateTimeOffset utc = DateTimeOffset.UtcNow; // UTC(偏移0) - 手动创建
// 年,月,日,小时,分钟,秒,偏移(比如+2小时) DateTimeOffset custom = new DateTimeOffset(2025, 5, 24, 20, 0, 0, TimeSpan.FromHours(2)); - 从DateTime转换
DateTime dt = DateTime.Now; DateTimeOffset dto = new DateTimeOffset(dt); // 自动带上本地偏移
提取组成部分
DateTimeOffset dto = DateTimeOffset.Now;
var datePart = dto.Date; // 只有日期
var timePart = dto.TimeOfDay; // 只有时间
var offset = dto.Offset; // 相对UTC的偏移,比如03:00:00
时区之间的转换
偏移的魔法:
// 拿到UTC时间
var now = DateTimeOffset.Now;
var utc = now.ToUniversalTime();
Console.WriteLine(utc); // 格林威治时间
// 转到另一个时区
var berlinTime = utc.ToOffset(TimeSpan.FromHours(2));
Console.WriteLine(berlinTime); // 柏林时间(CET +2)
7. 什么时候用 DateTime,什么时候用 DateTimeOffset?
选择哲学
如果只是简单本地的事——比如生日、文章发布时间、问卷截止到15:00,通常用 DateTime 就够了。这种场景绝对时间不重要,只要事件发生在某天某小时就行,不用管时区。
但如果你要记录精确时刻,带时区,比如日志、交易、分布式系统——最好用 DateTimeOffset。这样跨系统传时间不会乱,尤其是不同地区的系统。
| 场景 | 类型 | 理由 |
|---|---|---|
| 生日 | |
只关心日期,不关心时区(通常) |
| 登录时间 | |
要知道精确时刻和偏移 |
| 商品有效期 | |
截止日期,不关心时区 |
| 日志事件时间 | |
最好总是带偏移存 |
GO TO FULL VERSION