HowardHinnant / date

A date and time library based on the C++11/14/17 <chrono> header
Other
3.14k stars 677 forks source link

Correct way of retrieving date and time of day in a given time zone #707

Closed pawnissimo closed 2 years ago

pawnissimo commented 2 years ago

const date::tzdb& tzdb = date::get_tzdb(); const date::time_zone* tzone = tzdb.locate_zone(timeZone.getTimeZone()); std::chrono::_V2::system_clock::time_point t_sys = date::to_sys_time(t_utc); auto t_zoned = make_zoned(tzone, t_sys);

What is the correct way of retrieving the corresponding year, month, day, hour, minute, second (in variables) for t_zoned ?

HowardHinnant commented 2 years ago

I'm going to make a couple of assumptions:

  1. timeZone.getTimeZone() returns a string or string_view.
  2. ... Wait, I have no clue what t_utc is.

I don't know what your inputs are, or if you are wanting the current time in your current time zone.

HowardHinnant commented 2 years ago

It would also be good to know which C++ standard you're using. Later versions allow more convenient syntax.

pawnissimo commented 2 years ago
  1. timeZone.getTimeZone() does return a string with the name of the timezone
  2. t_utc is a date::utc_clock::time_point
  3. I'm currently using C++17.

I would like to know the date and time of day (factoring in DST if applicable) corresponding to "t_utc" in the timezone defined by tzone.

HowardHinnant commented 2 years ago
#include "date/tz.h"
#include <chrono>
#include <iostream>

int
main()
{
    using namespace std::chrono_literals;

    std::string tz_name = "America/New_York";
    date::utc_clock::time_point t_utc{1633448911665608us};

    auto leap_second_info = date::get_leap_second_info(t_utc);
    auto t_sys = date::clock_cast<std::chrono::system_clock>(t_utc);
    date::zoned_time t_zoned{tz_name, t_sys};
    auto t_local = t_zoned.get_local_time();
    auto today_local = std::chrono::floor<date::days>(t_local);
    date::hh_mm_ss time_local{t_local - today_local};
    date::year_month_day date_local{today_local};

    date::year y = date_local.year();
    date::month m = date_local.month();
    date::day d = date_local.day();
    std::chrono::hours h = time_local.hours();
    std::chrono::minutes M = time_local.minutes();
    std::chrono::seconds s = time_local.seconds()
        + std::chrono::seconds{leap_second_info.is_leap_second};
}

If you would like integral representations of these fields then:

pawnissimo commented 2 years ago

@HowardHinnant Thank you very much for the detailed walkthrough. So I was able to compile this code on Linux with C++17 but I get compile time errors in tz.h with VisualStudio2019/C++17 (also tried C++20).

The errors occur while compiling these two lines: date::zoned_time t_zoned{tz_name, t_sys}; auto t_local = t_zoned.get_local_time();

_1>\date\include\date\tz.h(1651,1): error C2819: type 'std::basic_string<char,std::char_traits,std::allocator>' does not have an overloaded member 'operator ->' 1>C:\Program Files (x86)\Microsoft Visual Studio\2019\Professional\VC\Tools\MSVC\14.29.30133\include\xstring(4905): message : see declaration of 'std::basic_string<char,std::char_traits,std::allocator>' 1>\date\include\date\tz.h(1651,1): message : did you intend to use '.' instead? 1>\date\include\date\tz.h(1650): message : while compiling class template member function 'std::chrono::time_point<date::local_t,Duration> date::zoned_time<Duration,std::string>::get_local_time(void) const' 1> with 1> [ 1> Duration=std::chrono::duration<std::chrono::system_clock::rep,std::chrono::system_clock::period> 1> ] 1>C:\dev\Kayak\Repos\TimeConversion\TimeConversion\TimeConversionCore.cpp(76): message : see reference to function template instantiation 'std::chrono::time_point<date::local_t,Duration> date::zoned_time<Duration,std::string>::get_local_time(void) const' being compiled 1> with 1> [ 1> Duration=std::chrono::duration<std::chrono::system_clock::rep,std::chrono::system_clock::period> 1> ] 1>\TimeConversionCore.cpp(65): message : see reference to class template instantiation 'date::zoned_time<std::chrono::duration<std::chrono::system_clock::rep,std::chrono::system_clock::period>,std::string>' being compiled 1>\date\include\date\tz.h(1651,19): error C2039: 'to_local': is not a member of 'std::basic_string<char,std::char_traits,std::allocator>' 1>C:\Program Files (x86)\Microsoft Visual Studio\2019\Professional\VC\Tools\MSVC\14.29.30133\include\xstring(4905): message : see declaration of 'std::basic_string<char,std::char_traits,std::allocator>'_

This would be the corresponding code in tz.h:

template <class Duration, class TimeZonePtr> inline local_time<typename zoned_time<Duration, TimeZonePtr>::duration> zoned_time<Duration, TimeZonePtr>::get_localtime() const { return zone->tolocal(tp); }

zone_ does not seem to be of the correct type.

I have a workaround using locate_zone and make_zoned in place of date::zoned_time t_zoned{tz_name, t_sys}; but I read in an older post that make_zoned will be phased out.

HowardHinnant commented 2 years ago

Glad you found a workaround. I don't have Visual to experiment with, but here is my guess at another workaround: Change:

date::zoned_time t_zoned{tz_name, t_sys};

To:

date::zoned_time<std::chrono::system_clock::duration> t_zoned{tz_name, t_sys};

My theory is that Visual is not correctly implementing the C++17 CTAD language feature. CTAD is a feature that deduces the correct zoned_time template arguments from the constructor argument list. Indeed, pre-CTAD, deducing this template parameter was the only reason the make_zoned factory function was introduced in the first place. And CTAD is the reason that the make_zoned factory function is now no longer needed (modulo broken compilers).

HowardHinnant commented 2 years ago

You may or may not have a similar issue with hh_mm_ss, which also is templated on a duration type.

HowardHinnant commented 2 years ago

One more thought: zoned_time has deduction guides which help CTAD to figure things out. But these are guarded in tz.h by HAS_DEDUCTION_GUIDES=1. This guard is triggered by cplusplus >= 201703. If Visual is not correctly setting cplusplus, this could also cause these symptoms. So another workaround is to define the configuration macro HAS_DEDUCTION_GUIDES=1 in the Visual Studio IDE (https://docs.microsoft.com/en-us/cpp/build/working-with-project-properties?view=msvc-160#to-create-a-user-defined-macro).

pawnissimo commented 2 years ago

Thanks, you are right about HAS_DEDUCTION_GUIDES. __cplusplus remains set to 1997 in VS regardless of the configured C++ standard. However there's a compiler option to override this behavior; documented here: https://docs.microsoft.com/en-us/cpp/build/reference/zc-cplusplus?view=msvc-160

Tried it and seems to work. Thanks for all the help.

HowardHinnant commented 2 years ago

That's good to know, thanks.