C++ 中的日期和时间
这是一篇关于 Windows C++ 中日期和时间各种用法的文章。
引言
除了string
之外,在C++和Windows世界中,另一个比必要时可能更混乱的领域就是日期和时间领域。
从C开始,随着每个库的添加,获取日期和时间的方式太多了,人们很容易感到困惑。所以在这篇文章中,我希望总结并稍微简化Windows C++中的各种日期和时间功能。
一些术语
一如既往,在深入这个概念之前,你需要了解一些术语,所以我想我应该把它们列在最前面
UTC(协调世界时):这是国际标准时间或格林威治标准时间。
纪元(epoch):自1970年1月1日00:00:00协调世界时以来经过的秒数。
1. ASNI标准库
我们的第一站是ANSI标准库和time.h
头文件。顾名思义,它的根源来自C,所以一切都以非常C的方式设计(即结构等)。
我将该文件内容分为两个部分
- 由
clock_t
和clock()
函数表示的CPU相关函数和类型。 - 由该头文件中几乎所有其他内容表示的日历时间相关的函数和类型。
本文仅讨论日历时间。
标准库头文件中的日历时间信息本身可以至少分为两组
-
本地时间或分解时间,由
tm
结构表示,如下所示time.h
头文件中#ifndef _TM_DEFINED struct tm { int tm_sec; /* seconds after the minute - [0,59] */ int tm_min; /* minutes after the hour - [0,59] */ int tm_hour; /* hours since midnight - [0,23] */ int tm_mday; /* day of the month - [1,31] */ int tm_mon; /* months since January - [0,11] */ int tm_year; /* years since 1900 */ int tm_wday; /* days since Sunday - [0,6] */ int tm_yday; /* days since January 1 - [0,365] */ int tm_isdst; /* daylight savings time flag */ }; #define _TM_DEFINED #endif
-
日历时间,由
time_t
数据类型表示。time_t
的值通常是自某个特定于实现的基准时间以来经过的秒数。如果你查看time头文件,你会发现它被定义为
#ifndef _TIME_T_DEFINED typedef long time_t; /* time value */ #define _TIME_T_DEFINED /* avoid multiple def's of time_t */ #endif
如果你查看头文件,你会注意到有一个可以使用的函数数组,所有这些函数都使用
time_t
作为它们的参数或返回值double difftime(time_t time1, time_t time0); time_t mktime(struct tm * timeptr); time_t time(time_t * timer); char * asctime(const struct tm * timeptr); char * ctime(const time_t *timer);
此外,time.h提供了两种方法将
time_t
表示的日历时间转换为tm
结构表示的分解时间struct tm * gmtime(const time_t *timer); //converts the calender time represented by timer //to a UTC time represented by the tm structure. //converts the calendar time to the local time struct tm * localtime(const time_t * timer);
根据MSDN,在Microsoft C/C++的所有版本(Microsoft C/C++ 7.0除外)以及Microsoft Visual C++的所有版本中,time
函数返回自1970年1月1日午夜以来的秒数(即纪元)。在Microsoft C/C++ 7.0版本中,time
返回自1899年12月31日午夜以来的秒数。
2. Microsoft实现
多年来,Microsoft添加了自己的日期和时间函数版本,本文将尝试介绍。我们将首先从Win32 API函数开始,然后介绍可用的MFC函数。
2.1 Win32 API
使用Win32 API,Microsoft没有提供任何直接函数来计算时间差或提供除文件时间之外比较时间值的功能。相反,它提供了三个基本结构,其中两个将在此讨论,以及大约20个基本函数来检索各种时间信息并在各种时间格式之间进行转换。
Win32日期和时间功能的核心是SYSTEMTIME
和FILETIME
结构,它们在winbase.h
中定义,如下所示
typedef struct _FILETIME {
DWORD dwLowDateTime;
DWORD dwHighDateTime;
} FILETIME, *PFILETIME, *LPFILETIME;
//
// System time is represented with the following structure:
//
typedef struct _SYSTEMTIME {
WORD wYear;
WORD wMonth;
WORD wDayOfWeek;
WORD wDay;
WORD wHour;
WORD wMinute;
WORD wSecond;
WORD wMilliseconds;
} SYSTEMTIME, *PSYSTEMTIME, *LPSYSTEMTIME;
如你所知,SYSTEMTIME
结构在某种程度上类似于标准库的tm
结构,尽管它缺少tm
的两个成员,但提供了一个毫秒成员。
至于另一个结构,Microsoft决定将FILETIME
结构定义为64位值,表示自1601年1月1日以来100纳秒间隔的数量。因此,在Win32 API中使用时间的任务变得更加棘手。
2.1.1 获取系统时间
好了,这是最简单的一部分。您可以使用SYSTEMTIME
结构以及GetSystemTime
函数以UTC格式检索当前系统时间。
2.1.2 获取相对时间
这就是事情变得棘手的地方。这基本上就是你需要做的
- 使用
SystemTimeToFileTime
函数将时间从SYSTETIME
转换为FILETIME
- 将生成的
FILETIME
结构复制到LARGE_INTEGER
结构 - 对
LARGE_INTEGER
值使用正常算术运算
SYSTEMTIME st;
FILETIME ft;
LARGE_INTEGER li;
GetSystemTime(&st);
SystemTimeToFileTime(&st, &ft);
li.LowPart = ft.dwLowDateTime;
li.HighPart = ft.dwHighDateTime;
注意:有时一些热衷的程序员会尝试走捷径,直接使用FILETIME
而不是LARGE_INTEGER
来进行数据修改。
LARGE_INTEGER
结构定义如下
typedef union _LARGE_INTEGER {
struct {
DWORD LowPart;
LONG HighPart;
};
LONGLONG QuadPart;
} LARGE_INTEGER;
虽然它们具有相同的二进制格式,但所有FILETIME
结构的地址必须从32位边界开始,而所有LARGE_INTEGER
的地址必须从64位边界开始。因此,不要混用它们。
2.1.3 其他有用函数
在Win32日期和时间领域,你可能会遇到的一个更有用的函数是LocalFileTimeToFileTime
。此函数可用于将本地时间转换为UTC时间格式,这是许多API函数(如SetWaitableTimer
函数)的先决条件。
如果你正在构建一个显示文件系统的treeview
,你可能会发现CompareFileTime
函数也很有用。
至于可用的其他函数,以下是MSDN提供的函数列表
CompareFileTime
DosDateTimeToFileTime
FileTimeToDosDateTime
FileTimeToLocalFileTime
FileTimeToSystemTime
GetFileTime
GetLocalTime
GetSystemTime
GetSystemTimeAdjustment
GetSystemTimeAsFileTime
GetTickCount
GetTimeZoneInformation
LocalFileTimeToFileTime
SetFileTime
SetLocalTime
SetSystemTime
SetSystemTimeAdjustment
SetTimeZoneInformation
SystemTimeToFileTime
SystemTimeToTzSpecificLocalTime
2.2 MFC日期时间类
MFC框架通过引入两个包装类CTime
和COleDatetime
,大大简化了时间处理。此外,MFC还包含了CTimeSpan
和COleDateTimeSpan
,它们与CTime
和COleDateTime
类协同工作。
2.2.1 CTime
CTime
对象表示一个绝对时间日期,基于协调世界时(UTC)。
Microsoft为CTime
类提供了7个不同的构造函数,其中,允许你执行以下操作
- 使用标准库
time_t
日历时间创建时间类。 - 使用DOS日期和时间创建时间类。
- 使用Win32
SYSTEMTIME
或FILETIME
创建时间类 - 使用单独的年、月、日、时、分、秒条目创建时间类。
通过整合ANSI time_t
数据类型,CTime
类提供了第1节中讨论的所有功能。它还有方法以SYSTEMTIME
、FILETIME
或GMT格式获取时间。
此外,该类还重载了+、-、=、==、<、<<、>>运算符,提供了更多有用的功能。
你可以在afx.h
头文件中找到CTime
类的定义,如下所示
class CTime
{
public:
// Constructors
static CTime PASCAL GetCurrentTime();
CTime();
CTime(time_t time);
CTime(int nYear, int nMonth, int nDay, int nHour, int nMin, int nSec,
int nDST = -1);
CTime(WORD wDosDate, WORD wDosTime, int nDST = -1);
CTime(const CTime& timeSrc);
CTime(const SYSTEMTIME& sysTime, int nDST = -1);
CTime(const FILETIME& fileTime, int nDST = -1);
const CTime& operator=(const CTime& timeSrc);
const CTime& operator=(time_t t);
// Attributes
struct tm* GetGmtTm(struct tm* ptm = NULL) const;
struct tm* GetLocalTm(struct tm* ptm = NULL) const;
BOOL GetAsSystemTime(SYSTEMTIME& timeDest) const;
time_t GetTime() const;
int GetYear() const;
int GetMonth() const; // month of year (1 = Jan)
int GetDay() const; // day of month
int GetHour() const;
int GetMinute() const;
int GetSecond() const;
int GetDayOfWeek() const; // 1=Sun, 2=Mon, ..., 7=Sat
// Operations
// time math
CTimeSpan operator-(CTime time) const;
CTime operator-(CTimeSpan timeSpan) const;
CTime operator+(CTimeSpan timeSpan) const;
const CTime& operator+=(CTimeSpan timeSpan);
const CTime& operator-=(CTimeSpan timeSpan);
BOOL operator==(CTime time) const;
BOOL operator!=(CTime time) const;
BOOL operator<(CTime time) const;
BOOL operator>(CTime time) const;
BOOL operator<=(CTime time) const;
BOOL operator>=(CTime time) const;
// formatting using "C" strftime
CString Format(LPCTSTR pFormat) const;
CString FormatGmt(LPCTSTR pFormat) const;
CString Format(UINT nFormatID) const;
CString FormatGmt(UINT nFormatID) const;
#ifdef _UNICODE
// for compatibility with MFC 3.x
CString Format(LPCSTR pFormat) const;
CString FormatGmt(LPCSTR pFormat) const;
#endif
// serialization
#ifdef _DEBUG
friend CDumpContext& AFXAPI operator<<(CDumpContext& dc, CTime time);
#endif
friend CArchive& AFXAPI operator<<(CArchive& ar, CTime time);
friend CArchive& AFXAPI operator>>(CArchive& ar, CTime& rtime);
private:
time_t m_time;
};
2.2.2 CTimeSpan
CTimeSpan
类与CTime
一起使用以执行加减运算。顾名思义,它表示一个相对的时间跨度,并提供了四个构造函数,其中一个包含了ANSI time_t
数据类型。CTimeSpan
也在afx.h
中定义,如下所示
class CTimeSpan
{
public:
// Constructors
CTimeSpan();
CTimeSpan(time_t time);
CTimeSpan(LONG lDays, int nHours, int nMins, int nSecs);
CTimeSpan(const CTimeSpan& timeSpanSrc);
const CTimeSpan& operator=(const CTimeSpan& timeSpanSrc);
// Attributes
// extract parts
LONG GetDays() const; // total # of days
LONG GetTotalHours() const;
int GetHours() const;
LONG GetTotalMinutes() const;
int GetMinutes() const;
LONG GetTotalSeconds() const;
int GetSeconds() const;
// Operations
// time math
CTimeSpan operator-(CTimeSpan timeSpan) const;
CTimeSpan operator+(CTimeSpan timeSpan) const;
const CTimeSpan& operator+=(CTimeSpan timeSpan);
const CTimeSpan& operator-=(CTimeSpan timeSpan);
BOOL operator==(CTimeSpan timeSpan) const;
BOOL operator!=(CTimeSpan timeSpan) const;
BOOL operator<(CTimeSpan timeSpan) const;
BOOL operator>(CTimeSpan timeSpan) const;
BOOL operator<=(CTimeSpan timeSpan) const;
BOOL operator>=(CTimeSpan timeSpan) const;
#ifdef _UNICODE
// for compatibility with MFC 3.x
CString Format(LPCSTR pFormat) const;
#endif
CString Format(LPCTSTR pFormat) const;
CString Format(UINT nID) const;
// serialization
#ifdef _DEBUG
friend CDumpContext& AFXAPI operator<<(CDumpContext& dc,CTimeSpan timeSpan);
#endif
friend CArchive& AFXAPI operator<<(CArchive& ar, CTimeSpan timeSpan);
friend CArchive& AFXAPI operator>>(CArchive& ar, CTimeSpan& rtimeSpan);
private:
time_t m_timeSpan;
friend class CTime;
};
2.2.3 COleDateTime
MFC中包含的另一个主要的日期时间类是COleDateTime
类。COleDateTime
类可以容纳从100年1月1日到9999年12月31日的任何日期,但它的存在原因,正如其名称所示,是为了提供自动化支持。
在COM和自动化领域,Microsoft决定定义一个包含许多不同类型的大型联合,称为VARIANT
数据类型,其中包括short
、long
、float
、double
和DATE
等。而COleDateTime
是唯一能够与DATE
数据类型一起工作的日期/时间类。
DATE
类型使用8字节浮点数实现。为了使事情更复杂,日期以整数增量表示,以1899年12月30日午夜作为时间零点。小时值表示为数字的小数部分的绝对值。
如果你查看COleDateTime
的构造函数列表,你可能会注意到与CTime
惊人的相似之处,但有两个构造函数除外
COleDateTime( const VARIANT& varSrc );
COleDateTime( DATE dtSrc );
这两个区别确实是它存在的原因。
从MFC 4.0版本开始,MFC的数据库编程函数使用COleDateTime
。应该注意的是,尽管COleDateTime
是一个很好的类,但它有一个很大的缺陷,那就是它会忽略夏令时,所以根据你的使用情况,你必须找到一种方法来解决这个大问题。
COleDateTime
在Afxdisp.h
头文件中定义如下
/////////////////////////////////////////////////////////////////////////////
// COleDateTime class
class COleDateTime
{
// Constructors
public:
static COleDateTime PASCAL GetCurrentTime();
COleDateTime();
COleDateTime(const COleDateTime& dateSrc);
COleDateTime(const VARIANT& varSrc);
COleDateTime(DATE dtSrc);
COleDateTime(time_t timeSrc);
COleDateTime(const SYSTEMTIME& systimeSrc);
COleDateTime(const FILETIME& filetimeSrc);
COleDateTime(int nYear, int nMonth, int nDay,
int nHour, int nMin, int nSec);
COleDateTime(WORD wDosDate, WORD wDosTime);
// Attributes
public:
enum DateTimeStatus
{
valid = 0,
invalid = 1, // Invalid date (out of range, etc.)
null = 2, // Literally has no value
};
DATE m_dt;
DateTimeStatus m_status;
void SetStatus(DateTimeStatus status);
DateTimeStatus GetStatus() const;
BOOL GetAsSystemTime(SYSTEMTIME& sysTime) const;
int GetYear() const;
int GetMonth() const; // month of year (1 = Jan)
int GetDay() const; // day of month (0-31)
int GetHour() const; // hour in day (0-23)
int GetMinute() const; // minute in hour (0-59)
int GetSecond() const; // second in minute (0-59)
int GetDayOfWeek() const; // 1=Sun, 2=Mon, ..., 7=Sat
int GetDayOfYear() const; // days since start of year, Jan 1 = 1
// Operations
public:
const COleDateTime& operator=(const COleDateTime& dateSrc);
const COleDateTime& operator=(const VARIANT& varSrc);
const COleDateTime& operator=(DATE dtSrc);
const COleDateTime& operator=(const time_t& timeSrc);
const COleDateTime& operator=(const SYSTEMTIME& systimeSrc);
const COleDateTime& operator=(const FILETIME& filetimeSrc);
BOOL operator==(const COleDateTime& date) const;
BOOL operator!=(const COleDateTime& date) const;
BOOL operator<(const COleDateTime& date) const;
BOOL operator>(const COleDateTime& date) const;
BOOL operator<=(const COleDateTime& date) const;
BOOL operator>=(const COleDateTime& date) const;
// DateTime math
COleDateTime operator+(const COleDateTimeSpan& dateSpan) const;
COleDateTime operator-(const COleDateTimeSpan& dateSpan) const;
const COleDateTime& operator+=(const COleDateTimeSpan dateSpan);
const COleDateTime& operator-=(const COleDateTimeSpan dateSpan);
// DateTimeSpan math
COleDateTimeSpan operator-(const COleDateTime& date) const;
operator DATE() const;
int SetDateTime(int nYear, int nMonth, int nDay,
int nHour, int nMin, int nSec);
int SetDate(int nYear, int nMonth, int nDay);
int SetTime(int nHour, int nMin, int nSec);
BOOL ParseDateTime(LPCTSTR lpszDate, DWORD dwFlags = 0,
LCID lcid = LANG_USER_DEFAULT);
// formatting
CString Format(DWORD dwFlags = 0, LCID lcid = LANG_USER_DEFAULT) const;
CString Format(LPCTSTR lpszFormat) const;
CString Format(UINT nFormatID) const;
// Implementation
protected:
void CheckRange();
friend COleDateTimeSpan;
};
2.2.4 COleDateTimeSpan
与COleDateTime
一起使用的所谓伴随类是COleDateTimeSpan
。它提供的功能与CTimeSpan
为CTime
提供的功能大致相同。一个主要的例外是包含了SetStatus
和GetStatus
函数,你可以将这一点追溯到COleDateTime
是为自动化支持而构建的事实。因此,GetStatus()
可以告诉你CTimeDateSpan
值是否已设置为正确值。
与它的伴随类一样,COleDateTimeSpan
也在Afxdisp.h
中定义。
3. 结论
嗯,正如你所见,Windows中的日期时间世界有点复杂。一方面,你有一系列日期时间,它们的基准时间定义在纪元。另一方面,你还有一些其他东西,它们的基准定义在1900年1月1日,而还有一个可以追溯到1601年。
我们甚至有一个不支持夏令时的类,还有一个定义为比1900年1月1日早两天开始的DATE
数据类型,这也增加了一些复杂性。
我不确定这些决定的具体原因,但无论如何,在处理这些各种数据类型和类时,你必须格外小心,花几分钟思考你在做什么。