choucavalier
choucavalier

Reputation: 2770

`boost::local_time::date_time` from `std::string`

I am working with date/time string in the %Y-%m-%d %H:%M:%S%f format.

I want to design a function that takes a date/time string in the America/New_York timezone and returns a date/time string in the Europe/Paris timezone.

I came up with the following

std::string ec2cet(const std::string &date_time_str)
{
  using namespace boost::posix_time;
  using namespace boost::gregorian;
  using namespace boost::local_time;

  tz_database tz_db;
  time_zone_ptr et_tz = tz_db.time_zone_from_region("America/New_York");
  time_zone_ptr cet_tz = tz_db.time_zone_from_region("Europe/Paris");

  ptime absolute_time = time_from_string(date_time_str);
  local_date_time ec_time(absolute_time, et_tz);
  local_date_time cet_time = et_time.local_time_in(cet_tz);

  return to_simple_string(cet_time);
}

When printing either et_time or cet_time with the input string 16:03:38.539000 I get

16:03:38.539000 UTC

I was expecting

What am I doing wrong?

Upvotes: 2

Views: 460

Answers (1)

Howard Hinnant
Howard Hinnant

Reputation: 218710

I don't know what is wrong with the boost implementation of ec2cet. However I can show how to do this with this free, open source library quite easily. First the code, and then the line-by-line explanation:

#include "tz.h"
#include <iostream>
#include <sstream>
#include <string>

std::string
ec2cet(const std::string& date_time_str)
{
    using namespace std;                                 //  1
    using namespace std::chrono;                         //  2
    using namespace date;                                //  3
    constexpr auto fmt = "%F %T";                        //  4
    istringstream in{date_time_str};                     //  5
    local_time<microseconds> tp;                         //  6
    in >> parse(fmt, tp);                                //  7
    if (in.fail())                                       //  8
        return std::string{};                            //  9
    auto et_time  = make_zoned("America/New_York", tp);  // 10
    auto cet_time = make_zoned("Europe/Paris", et_time); // 11
    cout << "et_time  = " << et_time << '\n';            // 12
    cout << "cet_time = " << cet_time << '\n';           // 13
    return format(fmt, cet_time);                        // 14
}

Lines 1-3 just bring everything into the local space so things aren't quite so verbose.

Line 4: Just write the format string in one place. "%F %T" is equivalent to "%Y-%m-%d %H:%M:%S" and you could use that too. (I'm lazy).

Line 5: This library will parse out of any stream, so we need to turn the input string date_time_str into a stream (in).

Line 6: You say that the input string is known to represent the local date/time in "America/New_York". The type local_time<microseconds> is a chrono::time_point which can represent the local time in any time zone to microseconds precision. An instance of this (tp) is what we will parse date_time_str into.

Line 7: Parse into tp.

Line 8-9: Check for a parse error.

Line 10: Create a zoned_time<microseconds> using the "America/New_York" time_zone and the local_time tp. You can think of a zoned_time as simply a pair<time_zone, local_time> though the details are slightly more complex than that.

Line 11: Create a zoned_time<microseconds> from the time_zone "Europe/Paris" and the zoned_time et_time. This converts one local time to another, equating their UTC equivalents in the process.

Lines 12-13: Output these intermediate results (et_time and cet_time for debugging purposes).

Line 14: Format cet_time into the desired std::string and return it.

Running this with the following driver:

int
main()
{
    auto s = ec2cet("2017-01-08 16:03:38.539000");
    std::cout << "ec2cet   = " << s << '\n';
}

Outputs:

et_time  = 2017-01-08 16:03:38.539000 EST
cet_time = 2017-01-08 22:03:38.539000 CET
ec2cet   = 2017-01-08 22:03:38.539000

This program tracks the current IANA timezone database, and will correctly follow the time zone rules for the complete history of the IANA database, which for these two timezones starts in the late 1800's.

If microseconds precision isn't what you want, you can change that (in one place on line 6). If the input string should be UTC instead of "America/New_York", that is also a one-line change on line 6 (make the type of tp sys_time<microseconds> instead of local_time<microseconds>).

Upvotes: 2

Related Questions