【问题标题】:Convert textual date and time to ATL::CTime将文本日期和时间转换为 ATL::CTime
【发布时间】:2018-06-23 00:32:13
【问题描述】:

给定一个文本日期和时间,例如:

Sat, 13 Jan 2018 07:54:39 -0500 (EST)

如何将其转换为ATL / MFC CTime

函数应该返回(在我的例子中):

CTime(2018,1,13,7,54,39) 

GMT/UTF 或 plus the time zone

更新:

我尝试编写以下函数,但 ParseDateTime() 似乎总是失败。

CTime DateTimeString2CTime(CString DateTimeStr)
{
    COleDateTime t;
    if (t.ParseDateTime(DateTimeStr))
    {
        CTime result(t);
        return result;
    }
    return (CTime)NULL;
}

【问题讨论】:

  • 您确定要忽略时区信息吗?这似乎是一件非常不寻常的事情,基本上是在浪费信息。
  • 我不想忽略(或丢弃)任何信息。
  • 那么,更新您的问题以反映这一点。如目前所写,您要求使用基于CTime 的日期时间字符串表示,忽略时区信息。
  • CTime 不会忽略时区(请参阅 msdn.microsoft.com/en-us/library/78zb0ese.aspx#ctime__formatgmt),但我更新了我的问题,以明确不应该丢弃任何东西(也包括夏令时)。
  • 您只能从当地时间的时间戳有意义地构造一个CTime 对象。您必须将源字符串分解为其组成部分,通过执行必要的调整将其转换为本地时间,然后将其输入CTime c'tor。或者,计算从源到本地时间的偏移量,从源时间构造一个CTime,并添加一个相应的CTimeSpan 来修复源时间和本地时间之间的差异。没有一个答案提取时区信息。

标签: c++ winapi mfc atl ctime


【解决方案1】:

作为手动解析的替代方法,您可以使用COleDateTime Class,它是成员COleDateTime::ParseDateTime

bool ParseDateTime(  
 LPCTSTR lpszDate,
 DWORD dwFlags = 0,
 LCID lcid = LANG_USER_DEFAULT) throw();

来自文档:

The lpszDate parameter can take a variety of formats.
For example, the following strings contain acceptable date/time formats:

"25 January 1996"
"8:30:00"
"20:30:00"
"January 25, 1996 8:30:00"
"8:30:00 Jan. 25, 1996"
"1/25/1996 8:30:00" // always specify the full year,
// even in a 'short date' format

如果需要,您可以从那里转换为 CTime

【讨论】:

  • 我尝试了以下 CTime DateTimeString2CTime(CString DateTimeStr) { COleDateTime t; if (t.ParseDateTime(DateTimeStr)) { CTime 结果(t);返回结果; } 返回 (CTime)NULL; } 但它总是失败
  • @MichaelHaephrati 我做了一些测试(昨天无法测试)。看起来解析器不喜欢日期(星期六)和时区。您可以在当天(我们不需要当天的名称)到时区之后开始解析,然后从t 中添加/减去时差。
  • “2018 年 3 月 15 日星期四 21:15:11 GMT”怎么样?只是遇到这种格式,(例如,当您从 WMI 数据库中获取日期/时间时)
【解决方案2】:

您必须将字符串解析为单独的时间分量,将它们转换为整数并将它们传递给适当的CTime 构造函数。

解析的方法有很多,其中一种最直接且易于维护的方法是使用regular expressions(一旦你习惯了语法):

#include <iostream>
#include <regex>

void test( std::wstring const& s, std::wregex const& r );

int main()
{
    std::wregex const r{ 
        LR"(.*?)"            // any characters (none or more) 
        LR"((\d+))"          // match[1] = day
        LR"(\s*)"            // whitespace (none or more)
        LR"((Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec))"  // match[2] = month 
        LR"(\s*)"            // whitespace (none or more)
        LR"((\d+))"          // match[3] = year
        LR"(\s+)"            // whitespace (1 or more)
        LR"((\d+))"          // match[4] = hour
        LR"(\s*:\s*)"        // whitespace (none ore more), colon (1), whitespace (none ore more)
        LR"((\d+))"          // match[5] = minute
        LR"((?:\s*:\s*(\d+))?)" // match[6] = second (none or more) 
        LR"(.*)"             // any characters (none or more)
        , std::regex_constants::icase };

    test( L"Sat, 13 Jan 2018 07:54:39 -0500 (EST)", r );
    test( L"Wed, 10 jan2018 18:30 +0100", r );
    test( L"10Jan 2018 18 :30 : 00 + 0100", r );
}

void test( std::wstring const& s, std::wregex const& r )
{
    std::wsmatch m;
    if( regex_match( s, m, r ) )
    {
        std::wcout 
            << L"Day    : " << m[ 1 ] << L'\n'
            << L"Month  : " << m[ 2 ] << L'\n'
            << L"Year   : " << m[ 3 ] << L'\n'
            << L"Hour   : " << m[ 4 ] << L'\n'
            << L"Minute : " << m[ 5 ] << L'\n'
            << L"Second : " << m[ 6 ] << L'\n';
    }
    else
    {
        std::wcout << "no match" << '\n';    
    }
    std::wcout << std::endl;
}

Live demo.

您指定将每个组件括在括号中的模式(r 变量)。调用regex_match 后,结果存储在变量m 中,您可以通过下标运算符访问每个组件(也称为子匹配)。这些也是std::wstrings。

如有必要,捕获正则表达式库以及std::stoi 可能引发的异常。为简洁起见,我省略了这段代码。

编辑

在 OP 评论说需要更强大的解析之后,我相应地修改了正则表达式。 从对test() 函数的调用中可以看出,现在对空格的要求更加宽松。此外,时间戳的 seconds 部分现在是可选的。这是使用以(?: 引入并以) 结尾的非捕获组来实现的。通过在该组之后放置 ?,整个组(包括空格、: 和数字)可以不出现或出现一次,但只会捕获数字。

注意: LR"()" 指定raw string literal 以使正则表达式更具可读性(它避免转义反斜杠)。所以外括号不是实际正则表达式的一部分!


对于手动解析,可以使用std::wstringstream。在我看来,相对于正则表达式的唯一优势可能是更好的性能。否则,这个解决方案就很难维护,例如,如果将来必须更改时间格式。

#include <iostream>
#include <sstream>
#include <array>
#include <string>

int month_to_int( std::wstring const& m )
{
    std::array<wchar_t const*, 12> names{ L"Jan", L"Feb", L"Mar", L"Apr", L"May", L"Jun", L"Jul", L"Aug", L"Sep", L"Oct", L"Nov", L"Dec" };
    for( std::size_t i = 0; i < names.size(); ++i )
    {
        if( names[ i ] == m )
            return i + 1;
    }
    return 0;
}

int main()
{
    std::wstringstream s{ L"Sat, 13 Jan 2018 07:54:39 -0500 (EST)" };
    std::wstring temp;
    int day, month, year, hour, minute, second;
    // operator >> reads until whitespace delimiter
    s >> temp;
    s >> day;
    s >> temp; month = month_to_int( temp );
    s >> year;
    // use getline to explicitly specify the delimiter
    std::getline( s, temp, L':' ); hour = std::stoi( temp );
    std::getline( s, temp, L':' ); minute = std::stoi( temp );
    // last token separated by whitespace again
    s >> second;

    std::cout 
        << "Day    : " << day << '\n'
        << "Month  : " << month << '\n'
        << "Year   : " << year << '\n'
        << "Hour   : " << hour << '\n'
        << "Minute : " << minute << '\n'
        << "Second : " << second << '\n';
}

Live demo.

再次,为简洁起见,这里没有错误处理。您应该在每次输入操作后检查流状态或在构造后调用std::wstringstream::exceptions() 以启用异常并处理它们。

【讨论】:

  • 我在“2018 年 1 月 10 日星期三 18:30 +0100”或“2018 年 1 月 10 日星期三 18:30:00 + 0100”上尝试过你的现场演示,但它失败了。有什么建议吗?
  • @MichaelHaephrati 我尝试制作regex more robust
  • 如何考虑时区和夏令时?
  • @MichaelHaephrati 好吧,您可以通过向正则表达式添加更多捕获组来提取时区,我将其留作练习。那么如何处理这些信息可能最好作为一个新问题提出。
  • 您的答案可能是最好的。如果您有时间,添加时区和夏令时会很棒。
猜你喜欢
  • 2012-05-01
  • 1970-01-01
  • 1970-01-01
  • 2018-07-23
  • 2013-03-24
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-10-07
相关资源
最近更新 更多