65.9K
CodeProject 正在变化。 阅读更多。
Home

C++ 中的日期和时间

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.83/5 (47投票s)

2002年10月16日

CPOL

8分钟阅读

viewsIcon

1001849

这是一篇关于 Windows C++ 中日期和时间各种用法的文章。

引言

除了string之外,在C++和Windows世界中,另一个比必要时可能更混乱的领域就是日期和时间领域。

从C开始,随着每个库的添加,获取日期和时间的方式太多了,人们很容易感到困惑。所以在这篇文章中,我希望总结并稍微简化Windows C++中的各种日期和时间功能。

一些术语

一如既往,在深入这个概念之前,你需要了解一些术语,所以我想我应该把它们列在最前面

UTC(协调世界时):这是国际标准时间或格林威治标准时间。

纪元(epoch):自1970年1月1日00:00:00协调世界时以来经过的秒数。

1. ASNI标准库

我们的第一站是ANSI标准库和time.h头文件。顾名思义,它的根源来自C,所以一切都以非常C的方式设计(即结构等)。

我将该文件内容分为两个部分

  1. clock_tclock()函数表示的CPU相关函数和类型。
  2. 由该头文件中几乎所有其他内容表示的日历时间相关的函数和类型。

本文仅讨论日历时间。

标准库头文件中的日历时间信息本身可以至少分为两组

  1. 本地时间分解时间,由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
  2. 日历时间,由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日期和时间功能的核心是SYSTEMTIMEFILETIME结构,它们在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 获取相对时间

这就是事情变得棘手的地方。这基本上就是你需要做的

  1. 使用SystemTimeToFileTime函数将时间从SYSTETIME转换为FILETIME
  2. 将生成的FILETIME结构复制到LARGE_INTEGER结构
  3. 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框架通过引入两个包装类CTimeCOleDatetime,大大简化了时间处理。此外,MFC还包含了CTimeSpanCOleDateTimeSpan,它们与CTimeCOleDateTime类协同工作。

2.2.1 CTime

CTime对象表示一个绝对时间日期,基于协调世界时(UTC)。

Microsoft为CTime类提供了7个不同的构造函数,其中,允许你执行以下操作

  1. 使用标准库time_t日历时间创建时间类。
  2. 使用DOS日期和时间创建时间类。
  3. 使用Win32 SYSTEMTIMEFILETIME创建时间类
  4. 使用单独的年、月、日、时、分、秒条目创建时间类。

通过整合ANSI time_t数据类型,CTime类提供了第1节中讨论的所有功能。它还有方法以SYSTEMTIMEFILETIME或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数据类型,其中包括shortlongfloatdoubleDATE等。而COleDateTime是唯一能够与DATE数据类型一起工作的日期/时间类。

DATE类型使用8字节浮点数实现。为了使事情更复杂,日期以整数增量表示,以1899年12月30日午夜作为时间零点。小时值表示为数字的小数部分的绝对值。

如果你查看COleDateTime的构造函数列表,你可能会注意到与CTime惊人的相似之处,但有两个构造函数除外

COleDateTime( const VARIANT& varSrc );
COleDateTime( DATE dtSrc );

这两个区别确实是它存在的原因。

从MFC 4.0版本开始,MFC的数据库编程函数使用COleDateTime。应该注意的是,尽管COleDateTime是一个很好的类,但它有一个很大的缺陷,那就是它会忽略夏令时,所以根据你的使用情况,你必须找到一种方法来解决这个大问题。

COleDateTimeAfxdisp.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。它提供的功能与CTimeSpanCTime提供的功能大致相同。一个主要的例外是包含了SetStatusGetStatus函数,你可以将这一点追溯到COleDateTime是为自动化支持而构建的事实。因此,GetStatus()可以告诉你CTimeDateSpan值是否已设置为正确值。

与它的伴随类一样,COleDateTimeSpan也在Afxdisp.h中定义。

3. 结论

嗯,正如你所见,Windows中的日期时间世界有点复杂。一方面,你有一系列日期时间,它们的基准时间定义在纪元。另一方面,你还有一些其他东西,它们的基准定义在1900年1月1日,而还有一个可以追溯到1601年。

我们甚至有一个不支持夏令时的类,还有一个定义为比1900年1月1日早两天开始的DATE数据类型,这也增加了一些复杂性。

我不确定这些决定的具体原因,但无论如何,在处理这些各种数据类型和类时,你必须格外小心,花几分钟思考你在做什么。

© . All rights reserved.