Skip to content

Latest commit

 

History

History
429 lines (340 loc) · 16.2 KB

File metadata and controls

429 lines (340 loc) · 16.2 KB

选择语言 | Select a language

English
简体中文

DateTime for C++

一个模仿 C# DateTime 和 TimeSpan 类的 C++ 头文件库,提供近乎一致的功能和用法。

特性

  • 单头文件 — 仅需包含 GL_DateTime.hpp,无额外依赖
  • C# 风格格式化 — 使用 yyyy-MM-dd HH:mm:ss 等格式字符串
  • 全面的解析支持 — Parse() / TryParse() 自动推断常见格式或指定格式解析
  • 日期时间运算 — 添加/减少 年、月、日、时、分、秒、毫秒、微秒
  • TimeSpan 支持 — 完整的 TimeSpan 类(算术、乘除、比较、格式化、微秒)
  • DateTime 与 TimeSpan 运算 — DateTime ± TimeSpan = DateTime,DateTime - DateTime = TimeSpan
  • UTC/本地转换 — ToUniversalTime() / ToLocalTime(),附带 DateTimeKind 追踪
  • 比较运算 — 完整的 ==、!=、<、<=、>、>= 操作符
  • 流操作符 — 支持 << / >> 直接与 cin/cout 交互
  • Ticks 存储 — 内部使用 100 纳秒间隔的 ticks(自 0001-01-01 起),与 C# 完全一致
  • 完整日期范围 — 支持公元 1 年至 9999 年
  • C++20 — 使用 C++20 标准

快速开始

#include "GL_DateTime.hpp"
#include <iostream>

int main() {
    // 当前时间
    DateTime now = DateTime::Now();
    std::cout << "Now: " << now << std::endl;

    // 构造指定时间
    DateTime dt(2024, 12, 25, 10, 30, 0, 123);
    std::cout << dt.ToString("yyyy-MM-dd HH:mm:ss.fff") << std::endl;

    // 解析字符串
    DateTime parsed = DateTime::Parse("2024-06-07 14:30:00");

    // 使用 TimeSpan 做日期运算
    DateTime tomorrow = now.AddDays(1);
    TimeSpan diff = tomorrow - now;
    std::cout << "距离明天还有 " << diff.GetTotalHours() << " 小时" << std::endl;

    return 0;
}

编译

本项目仅需包含头文件即可使用:

g++ -std=c++20 -Iinclude your_program.cpp -o your_program

也可使用 Makefile 编译测试:

mingw32-make test    # 编译 test/test.cpp
./build/test.exe      # 运行测试

API 参考

DateTime 构造函数

构造函数 说明
DateTime() 默认构造,初始化为 MinValue (0001-01-01),与 C# 一致
DateTime(std::time_t t) 从 Unix 时间戳构造(换算为本地墙钟时间,Kind=Local)
DateTime(int64_t ticks, DateTimeKind kind = Unspecified) 从 ticks 和可选 Kind 构造
DateTime(year, month, day, hour=0, minute=0, second=0, ms=0, kind=Unspecified) 从各分量构造(超出范围抛异常)

DateTime 静态方法

方法 说明
DateTime::Now() 当前本地时间
DateTime::UtcNow() 当前 UTC 时间
DateTime::Today() 当天日期(时分秒归零)
DateTime::MinValue() 最小值 (0001-01-01)
DateTime::MaxValue() 最大值 (9999-12-31 23:59:59.9999999)
DateTime::DaysInMonth(year, month) 指定月份的天数
DateTime::IsLeapYear(year) 指定年份是否为闰年
DateTime::Compare(dt1, dt2) 比较两个 DateTime (-1 / 0 / 1)
DateTime::Equals(dt1, dt2) 判断两个 DateTime 是否相等
DateTime::SpecifyKind(dt, kind) 返回具有指定 Kind 的新 DateTime

DateTime 属性获取

方法 说明
GetYear() / GetMonth() / GetDay() 年/月/日
GetHour() / GetMinute() / GetSecond() 时/分/秒
GetMillisecond() 毫秒
GetMicrosecond() 微秒(.NET 7+)
GetDayOfWeek() 星期几 (0=周日,与 C# 一致)
GetDayOfYear() 一年中的第几天(从 1 开始)
GetDate() 返回日期部分(时间归零)的新 DateTime
GetTimeOfDay() 返回当天时间作为 TimeSpan
GetTicks() 自 0001-01-01 以来的 100 纳秒间隔数
GetKind() DateTimeKind (Unspecified / Utc / Local)
ToTime_t() 转换为 Unix time_t

DateTime 日期运算

方法 说明
Add(TimeSpan) 添加时间间隔
AddDays(n) 添加 n 天(n 为 double,支持小数)
AddHours(n) 添加 n 小时(支持小数)
AddMinutes(n) 添加 n 分钟(支持小数)
AddSeconds(n) 添加 n 秒(支持小数)
AddMilliseconds(n) 添加 n 毫秒(支持小数)
AddTicks(n) 添加 n 个刻度(100 纳秒)
AddMicroseconds(n) 添加 n 微秒(.NET 7+,支持小数)
AddMonths(n) 添加 n 月(自动处理月末对齐)
AddYears(n) 添加 n 年(自动处理闰年2月29)
Subtract(TimeSpan) → DateTime 减去一个 TimeSpan
Subtract(DateTime) → TimeSpan 减去另一个 DateTime,返回 TimeSpan 差

DateTime 转换

方法 说明
ToUniversalTime() 转换为 UTC(更新 Kind)
ToLocalTime() 转换为本地时间(更新 Kind)

ToString 格式

使用 C# 风格的格式字符串,同时支持预定义简写(单字符)和自定义格式说明符。

预定义格式简写

说明符 含义 展开格式
d 短日期 MM/dd/yyyy
D 长日期 dddd, MMMM dd, yyyy
f 完整日期+短时间 dddd, MMMM dd, yyyy HH:mm
F 完整日期+长时间 dddd, MMMM dd, yyyy HH:mm:ss
g 常规+短时间 MM/dd/yyyy HH:mm
G 常规+长时间 MM/dd/yyyy HH:mm:ss
M / m 月/日 MMMM dd
O / o 往返(Round-trip) yyyy-MM-ddTHH:mm:ss.fffffff + Kind 后缀(Z / +08:00)
R / r RFC1123 ddd, dd MMM yyyy HH:mm:ss 'GMT'
s 可排序(Sortable) yyyy-MM-ddTHH:mm:ss
t 短时间 HH:mm
T 长时间 HH:mm:ss
u 通用可排序 yyyy-MM-dd HH:mm:ss'Z'
U 通用完整 先转换 UTC,再使用 F 格式
Y / y 年/月 yyyy MMMM
dt.ToString("d");     // "06/07/2024"
dt.ToString("D");     // "Friday, June 07, 2024"
dt.ToString("o");     // "2024-06-07T14:30:45.7890000"
dt.ToString("s");     // "2024-06-07T14:30:45"
dt.ToString("U");     // "Friday, June 07, 2024 06:30:45" (UTC)

自定义格式说明符

格式符 说明 示例
y / yy 短年份 24
yyyy 完整年份 2024
M / MM 数字月份 6 / 06
MMM 缩写月份名 Jun
MMMM 完整月份名 June
d / dd 数字日期 7 / 07
ddd 缩写星期名 Fri
dddd 完整星期名 Friday
H / HH 24小时制 14 / 14
h / hh 12小时制 2 / 02
m / mm 分钟 5 / 05
s / ss 秒钟 3 / 03
f … fffffff 秒的小数部分(100ns 精度) 7 / 78 / 789 / 7890123
F … FFFFFFF 同上,但省略尾随零 0.1 秒 → 1,整秒 → 空
t / tt AM/PM 指示符(首字符 / 完整) P / PM
z / zz / zzz 本地时区偏移 +8 / +08 / +08:00
K 时区说明符 Z(Utc)/ +08:00(Local)/ 空(Unspecified)
'text' 文本引号 '年' → 年
\x 单字符转义 \d → d
%x 强制单字符说明符 %d → 7

注意: 与 .NET 一致,单字符格式串总是预定义格式,因此 ToString("f") 表示“完整日期/短时间”而非“十分之一秒”;需要小数秒请写在更长的格式里,如 ToString("ss.fff")。

dt.ToString("yyyy-MM-dd HH:mm:ss");        // 2024-06-07 14:05:03
dt.ToString("hh:mm tt");                  // 02:05 PM
dt.ToString("dddd, MMMM d, yyyy");        // Friday, June 7, 2024
dt.ToString("yyyy'年'MM'月'dd'日'");      // 2024年06月07日
dt.ToString("yyyy-MM-dd HH:mm:ss.fff");   // 2024-06-07 14:05:03.789
dt.ToShortDateString();                   // 2024-06-07
dt.ToLongDateString();                    // Friday, June 07, 2024
dt.ToShortTimeString();                   // 14:05
dt.ToLongTimeString();                    // 14:05:03

Parse / TryParse

// 自动推断常见格式
DateTime dt = DateTime::Parse("2024-06-07 14:30:00");
DateTime dt = DateTime::Parse("06/07/2024");

// 指定格式解析
DateTime dt = DateTime::Parse("2024年06月07日", "yyyy'年'MM'月'dd'日'");
DateTime dt = DateTime::Parse("06/07/2024 02:30:00 PM", "MM/dd/yyyy hh:mm:ss tt");

// 使用预定义格式简写
DateTime dt = DateTime::Parse("06/07/2024", "d");                // 短日期
DateTime dt = DateTime::Parse("2024-06-07T14:30:00", "s");       // 可排序格式

// TryParse(安全版本)
DateTime result;
if (DateTime::TryParse("2024-06-07", result)) {
    // 解析成功
}
// 解析失败时会返回 false,并把 result 置为 DateTime::MinValue(与 C# 一致)

解析行为与 C# 保持一致:

  • 忽略首尾空白;支持小数秒(2024-06-07 14:30:00.1234567)与纯时间(14:30,日期取今天)
  • 格式中没有年份时取当前年(如 June 07);12 小时制 + PM/AM 也可解析
  • 字符串带时区标记(Z / GMT / +08:00 / -0500)时,会换算为本地时间(Kind=Local)
  • 使用预定义 u / R 等 UTC 格式时同样换算为本地时间
  • 解析失败会抛出 std::runtime_error(TryParse 不会)

DateTime 运算符

dt1 + ts     // DateTime + TimeSpan → DateTime
ts + dt1    // TimeSpan + DateTime → DateTime
dt1 - ts    // DateTime - TimeSpan → DateTime
dt1 - dt2   // DateTime - DateTime → TimeSpan
dt1 == dt2  // 相等
dt1 != dt2  // 不等
dt1 <  dt2  // 小于
dt1 <= dt2  // 小于等于
dt1 >  dt2  // 大于
dt1 >= dt2  // 大于等于

流操作符

DateTime dt = DateTime::Now();
std::cout << dt << std::endl;         // 输出: 2026-06-07 18:24:20

std::cin >> dt;                        // 输入: 2024-06-07 14:30:00

TimeSpan

完整的 TimeSpan 类,与 System.TimeSpan 一致。

TimeSpan 构造函数

构造函数 说明
TimeSpan() 零值
TimeSpan(int64_t ticks) 从刻度数构造(100 纳秒间隔)
TimeSpan(hours, minutes, seconds) 从时间分量构造
TimeSpan(days, hours, minutes, seconds) 从天 + 时间分量构造
TimeSpan(days, hours, minutes, seconds, milliseconds) 完整构造函数

TimeSpan 属性

方法 说明
GetDays() / GetHours() / GetMinutes() / GetSeconds() / GetMilliseconds() / GetMicroseconds() 各分量值(微秒对应 .NET 7+)
GetTicks() 总刻度数
GetTotalDays() / GetTotalHours() / GetTotalMinutes() / GetTotalSeconds() / GetTotalMilliseconds() / GetTotalMicroseconds() 以 double 表示的总值

TimeSpan 静态方法

TimeSpan::Zero()          // TimeSpan(0)
TimeSpan::MinValue()      // 最小可能的 TimeSpan
TimeSpan::MaxValue()      // 最大可能的 TimeSpan
TimeSpan::FromDays(1.5)   // 1.5 天 → TimeSpan
TimeSpan::FromHours(2.5)  // 2.5 小时 → TimeSpan
// ... FromMinutes, FromSeconds, FromMilliseconds, FromMicroseconds(.NET 7+)

TimeSpan 算术运算

ts1 + ts2   // 加法
ts1 - ts2   // 减法
-ts1        // 取反
ts1.Multiply(2)              // 乘法(也可写 ts1 * 2.0)
ts1.Divide(2)                // 除法(也可写 ts1 / 2.0)
ts1.Divide(ts2)              // 比值,返回 double(也可写 ts1 / ts2)
ts1.Duration()  // 绝对值
ts1.CompareTo(ts2)  // 比较

溢出 / NaN / 除以 0 会抛异常(与 C# 的 OverflowException、ArgumentException 对应); ts1 / ts2 这种两个 TimeSpan 相除则返回 double,除数为零时得到 ±∞(不抛异常)。

TimeSpan 运算符

ts1 + ts2      // 加法
ts1 - ts2      // 减法
-ts1           // 取反
ts1 * 2.0      // 乘以倍数
ts1 / 2.0      // 除以倍数
ts1 / ts2      // 比值(double)
ts1 == ts2     // 相等
ts1 != ts2     // 不等
ts1 <  ts2     // 小于
ts1 <= ts2     // 小于等于
ts1 >  ts2     // 大于
ts1 >= ts2     // 大于等于

TimeSpan 格式化

TimeSpan ts(1, 2, 30, 0);    // 1天2小时30分钟
std::cout << ts;             // 输出: 1.02:30:00

ToString() 与 C# 的 "c" 格式一致:不足 1 天时不输出天数部分,有小数秒时固定输出 7 位:

TimeSpan::FromSeconds(1.5).ToString();   // "00:00:01.5000000"
TimeSpan(0, 0, 1).ToString();            // "00:00:01"

DateTimeKind

enum class DateTimeKind {
    Unspecified = 0,   // 默认
    Utc         = 1,
    Local       = 2
};

DateTime 内部的 ticks 保存的始终是 Kind 所对应时区下的“墙钟时间”,Kind 只说明该值属于哪个时区:

  • Local — ticks 表示本地时间,DateTime::Now() 产生这种值
  • Utc — ticks 表示 UTC 时间,DateTime::UtcNow() 产生这种值
  • Unspecified — 未指定,按字面值使用(如由年月日构造、Parse 得到的值)

因此所有取值/格式化接口(GetYear()、GetHour()、ToString()、GetDayOfWeek() 等)都直接按字面值分解 ticks,不做任何时区换算:

DateTime dt(2024, 6, 7, 14, 5, 3);          // Unspecified
std::cout << dt.ToString();                 // 2024-06-07 14:05:03(不受本地时区影响)
std::cout << dt.GetHour();                  // 14

std::cout << DateTime::Now().GetHour();     // 本地小时
std::cout << DateTime::UtcNow().GetHour();  // UTC 小时

只有 ToUniversalTime() / ToLocalTime() 会真正进行时区换算,并把结果标记为对应的 Kind。ToTime_t() 同样按 Kind 换算(Local/Unspecified 视为本地时间)。

本地化

默认情况下,ToString 中的月份名和星期名使用系统语言。若要切换语言,调用 <locale.h> 的 setlocale 函数:

#include <locale.h>

// Windows - 简体中文
setlocale(LC_ALL, "Chinese (Simplified)_China.UTF-8");
// Windows - 美国英语
setlocale(LC_ALL, "English_United States.1252");

注意:"o" / "R" / "s" / "u" 属于不变区域性格式,其中的月份/星期名固定为英文缩写, 不受 setlocale 影响;相应地,Parse 解析月份/星期名时也只识别英文(详见下方“与 C# DateTime 的差异”)。

项目结构

DateTimeForCpp/
├── include/
│   └── GL_DateTime.hpp      # 主文件
├── test/
│   └── test.cpp             # 测试程序
├── Makefile                 # 构建配置
├── CHANGELOG.md             # 英文更新日志
├── CHANGELOG_cn.md          # 中文更新日志
├── README.md                # 英文文档
├── README_cn.md             # 中文文档
└── LICENSE

与 C# DateTime 的差异

已经对齐的行为不再赘述(详见 CHANGELOG_cn.md),当前仍需注意的差异如下:

行为差异

  • 预定义格式(d/D/f/F/g/G/t/T/M/Y)与 ToShortDateString() / ToLongDateString() / ToShortTimeString() / ToLongTimeString() 使用固定的不变区域性模式,而 C# 使用当前区域性 (例如 zh-CN 下 ToString("d") 得到 06/07/2024 而非 2024/6/7); "o" / "R" / "s" / "u" 与 C# 一样固定使用不变区域性
  • 无法识别的格式说明符会原样输出,C# 会抛 FormatException (本库的预定义格式与文档示例中 T/Z 是未加引号的字面量,故保留宽松行为)
  • 时区换算依赖平台的时间转换函数(mktime / timegm),仅在平台支持的范围(Windows 上约为 1970–3000 年) 内生效;范围外(如 1970 年之前、5000 年之后)ToUniversalTime() / ToLocalTime() 不做偏移换算、 ToTime_t() 返回 0;而 C# 使用自带的时区数据,可覆盖完整的 0001–9999 年
  • 月份名/星期名的格式化跟随 setlocale,而解析只识别英文名, 因此非英文 locale 下存在“能格式化、但解析不回来”的情况 ("R" 等不变区域性格式不受影响)

尚未实现的 API

  • ParseExact / TryParseExact
  • TimeSpan.Parse / TryParse / ToString(format)
  • ToBinary / FromBinary、ToFileTime / FromFileTime、ToOADate / FromOADate、IsDaylightSavingTime
  • Nanosecond / AddNanoseconds
  • DateTimeOffset

许可证

本项目基于 MIT 许可证开源。详见 LICENSE。