2

I am using Howard Hinnant's date C++ library (https://howardhinnant.github.io/date/date.html), but I have some confusions using it. Below is a program where I use this library to print the year-month-day of the 3rd Friday of Nov 2017. The date::year_month_weekday class, when used with date::sys_days(), shows a correct date (Nov 17, 2017), but when I convert it to struct tm with std::chrono::system_clock::to_time_t, the results stored in this tm becomes Nov 16, 2017. I tested other cases, it seems struct tm converted from date::year_month_weekday always one day behind. Did I miss something in my program? The program is listed as below, C++ 11 is needed to compile it.

#include <iostream>
#include <chrono>
#include <sys/time.h>
#include "date.h"

using namespace std; 
using namespace std::chrono; 
using namespace date;

int main(int argc, char *argv[]) {  
    date::year y(2017);
    date::month m(11);
    date::weekday wd((unsigned)5);
    date::weekday_indexed wi(wd,3);
    date::year_month_weekday dmwd(y, m, wi);
    std::cout <<  date::sys_days(dmwd) << std::endl; //prints 2017-11-17, which is the 3rd Friday of Nov 2017

    time_t tt = std::chrono::system_clock::to_time_t(date::sys_days(dmwd));        
    struct tm tm1;
    localtime_r(&tt, &tm1);
    std::cout << "tm1.tm_year = " << tm1.tm_year << std::endl;
    std::cout << "tm1.tm_mon = " << tm1.tm_mon << std::endl;
    std::cout << "tm1.tm_mday = " << tm1.tm_mday << std::endl;  //prints 16 instead of 17, one day behind. tm.mday is from 1 to 31.

    return 0; 
}

The output of this program is as follows

2017-11-17
tm1.tm_year = 117  <-- 117+1900=2017
tm1.tm_mon = 10    <-- tm_mon starts form 0, so 10 means November
tm1.tm_mday = 16  <-- tm_mday starts from 1, so 16 is the 16-th day in a month
Work Only
  • 23
  • 2
  • 1
    Have you tried using `gmtime_r()` instead of `localtime_r()`? – Freddie Chopin Mar 25 '17 at 15:14
  • Yes, I just tried, `gmtime_r` did give the consistent result, thank for the hint. Since the `date::year_month_weekday` contains no time (hour and minute) info, I wonder how the date library deals with it when converting to `struct tm`. I also print the `tm_hour` and `tm_min` of the `struct tm` converted from `date::year_month_weekday`. It seems `tm_hour == 18` and `tm_min == 0` for all the cases -- probably because I am in a UTC-6 time zone? – Work Only Mar 25 '17 at 15:22
  • Fwiw, here is alternative syntax to create `dmwd`: `auto dmwd = fri[3]/nov/2017;` – Howard Hinnant Mar 25 '17 at 16:42

1 Answers1

3

Howard Hinnant's date.h tracks Unix Time which for most practical purposes is UTC. At this same GitHub repository there is also a timezone library if you need to deal with your local time, or with any timezone other than UTC.

So yes, as Freddie Chopin noted in the comments, you are seeing the effects of localtime_r taking your computer's local timezone into account.

You can add any time of day to sys_days that you want. sys_days is a std::chrono::time_point, but with days precision. So once you have sys_days, you are actually within the <chrono> library now, instead of the date library:

system_clock::time_point t = date::sys_days(dmwd) + 6h + 53min + 4s + 123us:
Howard Hinnant
  • 206,506
  • 52
  • 449
  • 577
  • Thanks so much for the explanation. Just another question, I tried to change `sys_days(dmwd)` to `local_days(dmwd)`, but it won't compile, saying something about no matching function. What is the correct way to use `local_days`? Should I add the timezone library to my program as well? Thanks again! – Work Only Mar 25 '17 at 15:41
  • @WorkOnly: I just tried that experiment and it compiled for me. Which compiler/version? Can you set up a demo at some place such as: https://wandbox.org ? Here's a sandbox to start with: https://wandbox.org/permlink/HMMMIqtuG6Ia1AHY . – Howard Hinnant Mar 25 '17 at 15:48
  • I can compile the program in my original post without problem, but when I replaced `sys_days(dmwd)` with `local_days(dmwd)`, it gives the following error message (gcc 5.4 on Ubuntu 16.04, with `g++ -std=c++11 date_time.cpp`): `date_time.cpp: In function ‘int main(int, char**)’: date_time.cpp:18:73: error: no matching function for call to ‘std::chrono::_V2::system_clock::to_time_t(date::local_days)’ time_t tt = std::chrono::system_clock::to_time_t(date::local_days(dmwd)); ^ In file included from date_time.cpp:2:0:` – Work Only Mar 25 '17 at 22:21
  • (continued) `/usr/include/c++/5/chrono:734:7: note: candidate: static time_t std::chrono::_V2::system_clock::to_time_t(const time_point&) to_time_t(const time_point& __t) noexcept ^ /usr/include/c++/5/chrono:734:7: note: no known conversion for argument 1 from ‘date::local_days {aka std::chrono::time_point > >}’ to ‘const time_point& {aka const std::chrono::time_point > >&}’` – Work Only Mar 25 '17 at 22:23
  • @WorkOnly: Ok, that makes sense. `system_clock::to_time_t` takes a `system_clock::time_point` as an argument. `sys_days` is a typedef for a coarse `system_clock::time_point` and implicitly converts to `system_clock::time_point`. Whereas `local_days` is a `chrono::time_point` with no relationship whatsoever to `system_clock`. The compile-time error serves to keep you from accidentally mixing up these two families of `time_point`s. – Howard Hinnant Mar 25 '17 at 22:38
  • In my own experiment I replaced the wrong `date::sys_days(dmwd)`. :-) – Howard Hinnant Mar 25 '17 at 22:39
  • Thanks for your explanation, Howard. (Sorry I don't know how to @ here) – Work Only Mar 25 '17 at 22:41