C / C ++时区正确的时间转换(自纪元以来的秒数)

时间:2015-10-29 17:59:35

标签: c++ time

我想知道自纪元以来的秒数。值得注意的是,我不喜欢转换机器的位置,时区字符串应该足够了。

我有这个测试程序,pt.cc:

#include <assert.h>
#include <errno.h>
#include <iostream>
#include <stdio.h>
#include <string>
#include <string.h>
#ifndef _XOPEN_SOURCE
#define _XOPEN_SOURCE
#endif
#include <time.h>

using namespace std;  // To be brief, don't do this in real life.

int main(int argc, char* argv[]) {
    (void)argc; (void)argv;   // Skip compile warning.

    // I expect both of these to transform to 1440671500.
    cout << "1440671500 expected" << endl;
    const char utc_example[] = "2015-08-27T11:31:40+0100";
    struct tm tm;
    memset(&tm, 0, sizeof(struct tm));
    char* end = strptime(utc_example, "%Y-%m-%dT%H:%M:%S%z", &tm);
    assert(end);
    assert(*end == '\0');
    time_t seconds_since_epoch = mktime(&tm);
    cout << "utc example: " << seconds_since_epoch << "  or maybe  "
         << seconds_since_epoch - tm.tm_gmtoff + (tm.tm_isdst ? 3600 : 0) << endl;

    const char tz_example[] = "2015-08-27T10:31:40Z";
    memset(&tm, 0, sizeof(struct tm));
    end = strptime(tz_example, "%Y-%m-%dT%H:%M:%S%nZ", &tm);
    assert(end);
    assert(*end == '\0');
    seconds_since_epoch = mktime(&tm);
    cout << " tz example: " << seconds_since_epoch << "  or maybe  "
         << seconds_since_epoch - tm.tm_gmtoff + (tm.tm_isdst ? 3600 : 0) << endl;

    return 0;
}

这是输出:

jeff@birdsong:tmp $ clang++ -ggdb3 -Wall -Wextra -std=c++14 pt.cc -o pt
jeff@birdsong:tmp $ ./pt
1440671500 expected
utc example: 1440671500  or maybe  1440667900
 tz example: 1440667900  or maybe  1440664300
jeff@birdsong:tmp $ TZ=America/New_York ./pt
1440671500 expected
utc example: 1440693100  or maybe  1440711100
 tz example: 1440689500  or maybe  1440707500
jeff@birdsong:tmp $ TZ=Europe/London ./pt
1440671500 expected
utc example: 1440675100  or maybe  1440675100
 tz example: 1440671500  or maybe  1440671500
jeff@birdsong:tmp $ 

请注意mktime()的返回值如何根据环境时区而变化。 mktime()的手册页条目建议将解析时间解释为本地时间。所以我尝试减去GMT偏移并补偿时区,以防它忽略这些值(“或者”值)。

有关如何正确执行此操作的任何提示? (重要的是,我只需要在linux上工作。)

4 个答案:

答案 0 :(得分:7)

此答案使用此日期/时间库:

http://howardhinnant.github.io/date/date.html

这里采用的方法是完全绕过C日期/时间API。我个人认为C方法有点混乱,麻烦,有点危险。

话虽这么说,我的日期/时间库中的解析和格式化工具是不存在的。我预计这些设施可能会成为一个独立的图书馆,将来会在我的图书馆之上分层。

与此同时,为这个特定问题推出自己的解析并不困难。方法如下:

#include "chrono_io.h"
#include "date.h"
#include <iostream>
#include <string>
#include <sstream>

using second_point = std::chrono::time_point<std::chrono::system_clock,
                                             std::chrono::seconds>;

std::chrono::minutes
parse_offset(std::istream& in)
{
    using namespace std::chrono;
    char c;
    in >> c;
    minutes result = 10*hours{c - '0'};
    in >> c;
    result += hours{c - '0'};
    in >> c;
    result += 10*minutes{c - '0'};
    in >> c;
    result += minutes{c - '0'};
    return result;
}

second_point
parse(const std::string& str)
{
    std::istringstream in(str);
    in.exceptions(std::ios::failbit | std::ios::badbit);
    int yi, mi, di;
    char dash;
    // check dash if you're picky
    in >> yi >> dash >> mi >> dash >> di;
    using namespace date;
    auto ymd = year{yi}/mi/di;
    // check ymd.ok() if you're picky
    char T;
    in >> T;
    // check T if you're picky
    int hi, si;
    char colon;
    in >> hi >> colon >> mi >> colon >> si;
    // check colon if you're picky
    using namespace std::chrono;
    auto h = hours{hi};
    auto m = minutes{mi};
    auto s = seconds{si};
    second_point result = sys_days{ymd} + h + m + s;
    char f;
    in >> f;
    if (f == '+')
        result -= parse_offset(in);
    else if (f == '-')
        result += parse_offset(in);
    else
        ;// check f == 'Z' if you're picky
    return result;
}

int
main()
{
    using namespace date;
    std::cout << parse("2015-08-27T11:31:40+0100").time_since_epoch() << '\n';
    std::cout << parse("2015-08-27T10:31:40Z").time_since_epoch() << '\n';
}

要完全提前,此解决方案主要使用std::istringstreamstd::chrono,实际上只有一小部分是我的日期库。

我做了几个设计选择,您可以选择不选择(解析时有很多选项)。例如,如果存在任何解析错误,我选择抛出异常,并且我选择不检查-:等分隔符(主要是出于简洁原因)。

代码相对不言自明。正如您在问题中所述,本地时区不是(也不应该是)解决方案的一部分。计时库用于在小时,分钟和秒之间管理算术。我的日期库用于处理将年/月/日转换为精度为chrono::time_point的{​​{1}}。

通过处理所有这些算法,您可以专注于解析整数和字符。可以直接向此示例添加更多检查,并为错误执行任何操作。

此示例输出:

days

<强>更新

我已经将解析功能添加到"date.h",现在可以更简单地编写上面的1440671500s 1440671500s 函数:

parse

并返回相同的结果。

答案 1 :(得分:2)

以下是使用Google https://github.com/google/cctz

执行您想要的答案
#include <chrono>
#include <iostream>
#include <string>

#include "src/cctz.h"

using namespace std;

int main(int argc, char* argv[]) {
  const char kFmt[] = "%Y-%m-%dT%H:%M:%S%Ez";

  // I expect both of these to transform to 1440671500.
  const char utc_example[] = "2015-08-27T11:31:40+0100";
  const char tz_example[] = "2015-08-27T10:31:40Z";
  cout << "1440671500 expected" << endl;

  // Required by cctz::Parse(). Only used if the formatted
  // time does not include offset info.
  const auto utc = cctz::UTCTimeZone();

  std::chrono::system_clock::time_point tp;
  if (!Parse(kFmt, utc_example, utc, &tp)) return -1;
  cout << "utc example: " << std::chrono::system_clock::to_time_t(tp) << "\n";

  if (!Parse(kFmt, tz_example, utc, &tp)) return -1;
  cout << " tz example: " << std::chrono::system_clock::to_time_t(tp) << "\n";

  return 0;
}

输出结果为:

1440671500 expected
utc example: 1440671500
 tz example: 1440671500

请注意,其他涉及从time_t添加/减去偏移量的答案正在使用一种称为“epoch shift”的技术,但它实际上并不起作用。我在CppCon的这次演讲中于12:30解释了原因:https://youtu.be/2rnIHsqABfM?t=12m30s

答案 2 :(得分:0)

这是一个简单的函数,它将以UTC为单位返回自1970年以来的秒数:

#include <time.h>
#include <sys/time.h>

time_t GetSecondsSinceNewYearsDay1970UTC()
{
    struct timeval tv;
    if (gettimeofday(&tv, NULL) != 0) perror("doh!");
    return tv.tv_sec;
}

如果您更喜欢反映计算机本地时区而不是全局UTC值的值,那么这是第二个返回该值的函数。

time_t GetSecondsSinceNewYearsDay1970LocalTimeZone()
{
   const time_t utcTime = GetSecondsSinceNewYearsDay1970UTC();
   time_t now = time(NULL);
   time_t localTime = utcTime;  // will be adjusted below
   struct tm gmtm;
   struct tm * tm = gmtime_r(&now, &gmtm);
   if (tm)
   {
      localTime += (now-mktime(tm));
      if (tm->tm_isdst>0) localTime += (60*60);  // add an hour for DST
   }
   return localTime;
}

答案 3 :(得分:0)

mktime()返回的对象上调用strptime()之前,将tm->tm_isdst设置为负数,以使库确定DST当时是否生效。请注意,strptime()的时区偏移是非标准扩展。 (另请注意,使用结构名称作为变量名称是C ++中的错误样式,因为它会对tm的内容产生歧义。)