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
et_time to be something different than UTC because I constructed it providing the et_tz timezone object.
cet_time to be in a different timezone as I constructed it with local_time_in and providing the cet_tz timezone object.
What am I doing wrong?
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>).
Related
I need to serialize a UTC date time instance from java to c++ using the minimal length of pure ByteBuffer of unsigned chars.
I need the time point with to be able to support minimum nanoseconds precision.
From the java side i have looked in the classes ZonedDateTime and OffsetDateTime and seen what kind of primitive they are using to store the time and all together this is equal to 17 bytes which is kind of a lot.
Please note that i am here not transferring the zone itself since i have not found a way to map those zone strings from IANA to integers. Thus i guess the zone offset will be enough. Since bought sides will have to convert their time point to a particular offset before sending it to the server lets say that will be UTC 0. I am looking for a solution that is queering the local time i.e. no NTP servers involved!
the java side looks like this:
// 4 byte
Integer year = offsetDateTime.getYear();
// 1 byte
Integer month = offsetDateTime.getMonthValue();
// 1 byte
Integer day = offsetDateTime.getDayOfMonth();
// 1 byte
Integer hour = offsetDateTime.getHour();
// 1 byte
Integer minutes = offsetDateTime.getMinute();
// 1 byte
Integer seconds = offsetDateTime.getSecond();
// 4 byte
Integer nanoSeconds = offsetDateTime.getNano();
// 4 byte
Integer utcZoneTimeTotalSecondsOffset = offsetDateTime.getOffset().getTotalSeconds();
on the c++ side i need to do the same as well as to be able to construct a time point from all those integers above.
I have found how to get the the above information except the utcOffset i.e. the last integer.
i would like to use chrono to consume this buffer and instantiate a time point from it. however i am not able to find a way to to get the time offset in c++. How do i get the time offset with chrono and then construct a time point from the above information?
auto now = std::chrono::system_clock::now();
time_t itt = std::chrono::system_clock::to_time_t(now);
tm utc_tm = *gmtime(&itt);
std::cout << utc_tm.tm_year + 1900 << '-';
std::cout << utc_tm.tm_mon + 1 << '-';
std::cout << utc_tm.tm_mday << ' ';
std::cout << utc_tm.tm_hour << ':';
std::cout << utc_tm.tm_min << ':';
std::cout << utc_tm.tm_sec << '\n';
? wher is the time zoneOffset here ?
if i use
utc_tm.tm_gmtoff
this is giving me the wrong information . at least in my case. So i believe gmtoff is not the way to go , but if gmtoff is not the way then what is?
Not a complete answer, but here is one way one might serialize a C++ local time point using Howard Hinnant's free, open source time zone library.
#include "date/tz.h"
#include <iostream>
#include <sstream>
std::string
to_string(date::zoned_time<std::chrono::nanoseconds> t)
{
using namespace std;
using namespace date;
ostringstream out;
out << t.get_sys_time().time_since_epoch().count()
<< ' ' << t.get_time_zone()->name();
return out.str();
}
date::zoned_time<std::chrono::nanoseconds>
from_string(const std::string& s)
{
using namespace std;
using namespace std::chrono;
using namespace date;
istringstream in{s};
long long ns;
std::string tz_name;
in >> ns >> tz_name;
return {tz_name, sys_time<nanoseconds>{nanoseconds{ns}}};
}
int
main()
{
using namespace std;
using namespace std::chrono;
using namespace date;
zoned_time<nanoseconds> zt{current_zone(), system_clock::now()};
cout << zt << '\n';
auto s = to_string(zt);
cout << s << '\n';
zt = from_string(s);
cout << zt << '\n';
}
I'm using a std::string to represent the serialization, which one might send across a network.
This example begins in main with creating a nanosecond-precision local time point, stored in date::zoned_time<nanoseconds>, using the computer's current local time zone, and the current UTC time. main first simply prints this time stamp out, which for me just output:
2019-09-11 12:37:04.846272000 EDT
which is my current local date/time and time zone abbreviation.
Next the program converts this to a string with a small (but not necessarily minimal) number of ASCII characters to completely describe the local time point. I've chosen the format:
<Number of nanoseconds since epoch> <IANA time zone name>
In this example, that string is:
1568219824846272000 America/New_York
This string is formed in to_string, which simply streams out the "time since epoch" of the system time, of the zoned_time. Then adds a space, and streams out the name of the time zone.
from_string reverses this operation by reading in the number of nanoseconds, and then reading in the time zone name. It then forms a zoned_time<nanoseconds> by pairing the time zone name, and forming a sys_time<nanoseconds> with the integer that was parsed.
main prints out the parsed zoned_time to ensure we have a loss-less round trip:
2019-09-11 12:37:04.846272000 EDT
In order to get a loss-less conversion that includes local time, one really needs to transmit the IANA time zone name, not just the current UTC offset. With only the UTC offset, one can recover the exact UTC time point. But one can not perform any local time arithmetic, or comparison with other local time points because one can not know the rules for when one changes the UTC offset. Only with the full IANA time zone name can one pass the information along about those rules.
I have string 20150410 121416 in c++.
I need to turn this into 20150410 12:14:16
How can i insert a colon to the string?
One can format date/times in C and C++ with strftime. There also exists a non-standard but common POSIX function called strptime one can use to parse times. One could use these to parse your date/time in your input format, and then format it back out in your desired format.
That is, assuming you didn't want to write the parsing code yourself.
If you have C++11, then you could use this free, open-source date/time library to help you do all this with strftime-like format strings. Such code could look like:
#include "tz.h"
#include <iostream>
#include <sstream>
int
main()
{
using namespace date;
std::string input = "20150410 121416";
std::stringstream stream{input};
stream.exceptions(std::ios::failbit);
sys_seconds tp;
parse(stream, "%Y%m%d %H%M%S", tp);
auto output = format("%Y%m%d %T", tp);
std::cout << output << '\n';
}
Output:
20150410 12:14:16
One advantage of using a date/time parsing/formatting library, as opposed to just treating these as generic strings, is that you can more easily alter the formatting, or manipulate the datetime during the format conversion (e.g. have it change timezones).
For example, next month the specification might change on you and now you're told that this is a timestamp representing local time in Moscow and you need to convert it to local time in London and output it in the form YYYY-MM-DD HH:MM:SS <UTC offset>. The above code hardly changes at all if you're using a good date/time library.
#include "tz.h"
#include <iostream>
#include <sstream>
int
main()
{
using namespace date;
std::string input = "20150410 121416";
std::stringstream stream{input};
stream.exceptions(std::ios::failbit);
local_seconds tp;
parse(stream, "%Y%m%d %H%M%S", tp);
auto moscow_time = make_zoned("Europe/Moscow", tp);
auto london_time = make_zoned("Europe/London", moscow_time);
auto output = format("%F %T %z", london_time);
std::cout << output << '\n';
}
2015-04-10 10:14:16 +0100
But if you started out just doing string manipulation, all of the sudden you've got a major task in front of you. Writing code that understands the semantics of the datetime "20150410 121416" is a significant leap above manipulating the characters of "20150410 121416" as a string.
<script type="text/javascript">
function formatTime(objFormField){
intFieldLength = objFormField.value.length;
if(intFieldLength==2 || intFieldLength == 2){
objFormField.value = objFormField.value + ":";
return false;
}
}
</script>
Enter time <input type="text" maxlength="5" minlength="5" onKeyPress="formatTime(this)"/>
The problems is:
I know how to get the local time in boost
the code:
boost::local_time::local_date_time currentTime(
boost::posix_time::second_clock::local_time(),
boost::local_time::time_zone_ptr());
std::cout << currentTime.local_time() << std::endl;
I know how to get the current time zone data from the machine (I hope it's a right way)
the code:
tzset();
// the var tzname will have time zone names
// the var timezone will have the current offset
// the var daylight should show me if there is daylight "on"
but still I can't get local_date_time with the current time_zone... Does someone know, how to do it?
OK, as for now I still don't know the whole answer
but there is code, which could help to print the current time zone offset
(based on an answer for a related question here (stackoverflow) and some inner boost code)
I'm absolutely not sure it's will work correctly on all machines, but for now it's better than nothing:
boost::posix_time::time_duration getUtcOffset(const boost::posix_time::ptime& utcTime)
{
using boost::posix_time::ptime;
const ptime localTime = boost::date_time::c_local_adjustor<ptime>::utc_to_local(utcTime);
return localTime - utcTime;
}
std::wstring getUtcOffsetString(const boost::posix_time::ptime& utcTime)
{
const boost::posix_time::time_duration td = getUtcOffset(utcTime);
const wchar_t fillChar = L'0';
const wchar_t timeSeparator = L':';
std::wostringstream out;
out << (td.is_negative() ? L'-' : L'+');
out << std::setw(2) << std::setfill(fillChar)
<< boost::date_time::absolute_value(td.hours());
out << L':';
out << std::setw(2) << std::setfill(fillChar)
<< boost::date_time::absolute_value(td.minutes());
return out.str();
}
int main()
{
const boost::posix_time::ptime utcNow =
boost::posix_time::second_clock::universal_time();
const std::wstring curTimeOffset = getUtcOffsetString(utcNow);
std::wcout << curTimeOffset.c_str() << std::endl; // prints -05:00 on my comp
}
So, if you also need to obtain correct UTC offset than my previous example should be modified a bit to produce correct timezone string:
static boost::posix_time::time_duration utc_offset(
second_clock::local_time() - second_clock::universal_time());
std::ostringstream ss_posix_tz_def;
// We don't care about real zone name so, just put any three letters.
ss_posix_tz_def << "LOC" << utc_offset;
string posix_tz_def = ss_posix_tz_def.str();
Another solution is writing a whole new timezone provider. Like this simple one:
// We assume local TZ doesn't have DST, UTC offset is calculated basing on
// local system clocks. Thus, using of this provider for time calculations for
// an arbitrary date is not a good idea.
class machine_time_zone : public boost::local_time::custom_time_zone {
public:
typedef boost::local_time::custom_time_zone base_type;
typedef base_type::time_duration_type time_duration_type;
machine_time_zone()
: boost::local_time::custom_time_zone(
time_zone_names("Local machine TZ", "LOC", "", ""),
GetUTCOffset(),
boost::local_time::dst_adjustment_offsets(
time_duration_type(0, 0, 0),
time_duration_type(0, 0, 0), time_duration_type(0, 0, 0)),
boost::shared_ptr<boost::local_time::dst_calc_rule>()) {
}
// This method is not precise, real offset may be several seconds more or less.
static const boost::posix_time::time_duration& GetUTCOffset() {
using boost::posix_time::second_clock;
static boost::posix_time::time_duration utc_offset(
second_clock::local_time() - second_clock::universal_time());
return utc_offset;
}
};
Just pass it when constructing local_date_time:
local_date_time ldt(second_clock::local_time(),
time_zone_ptr(new machine_time_zone()));
Since you say you have timezone information the only question is how to use boost for formatting string you need. Below is a sample code:
using namespace boost::local_time;
using namespace boost::posix_time;
// Composing this string is the most tricky part. Syntax see in:
// boost\date_time\local_time\posix_time_zone.hpp
string posix_tz_def("PST-5PDT01:00:00,M4.1.0/02:00:00,M10.1.0/02:00:00");
local_date_time ldt(second_clock::local_time(),
time_zone_ptr(new posix_time_zone(posix_tz_def)));
std::stringstream ss;
local_time_facet* output_facet = new local_time_facet();
ss.imbue(std::locale(std::locale::classic(), output_facet));
output_facet->format("%Y-%m-%dT%H:%M:%S %Q");
ss << ldt;
string formatted_datetime = ss.str(); // 2012-01-05T18:14:06 -05:00
In this approach most problematic part is posix timezone string. I think there should be databases with these strings for every timezone and boost provides a template for working with .csv file. If the only thing you need is offset in the string just setup DTS to 0:00:00 and don't care about the rest. For example use this string: PST-5:30PDT0,0,365 (forever PDT, shift 0). Substitute "-5:00" with the offset you need.
Though, C++/boost way would be implementing own timezone provider by deriving from date_time::time_zone_base.
More samples and ideas could be found here.
I have a boost::posix_time::ptime instance and would like to convert ("format") it to a string using a given boost::local_time::time_zone_ptr instance. Below is a test program showing what I currently have. It converts a ptime to a local_date_time which, from my understanding, expresses a time zone in addition to the time information.
When running this program at 2011-08-18 12:00:00 UTC, I expect the output 2011-08-18 14.00.00 UTC+02:00. Instead it prints 2011-08-18 12:00:00 UTC+00:00. i.e. Relative to the printed time zone, the printed time is correct, but it's not in the time zone I used to create the boost::local_time::local_date_time instance.
I currently use the technique suggested in this question to use a custom format string.
#include <iostream>
#include <ctime>
#include <boost/date_time.hpp>
int main(int argc, char ** argv) {
using namespace std;
// Get current time, as an example
boost::posix_time::ptime dt = boost::posix_time::microsec_clock::universal_time();
// Create a time_zone_ptr for the desired time zone and use it to create a local_date_time
boost::local_time::time_zone_ptr zone(new boost::local_time::posix_time_zone("EST"));
boost::local_time::local_date_time dt_with_zone(dt, zone);
std::stringstream strm;
// Set the formatting facet on the stringstream and print the local_date_time to it.
// Ownership of the boost::local_time::local_time_facet object goes to the created std::locale object.
strm.imbue(std::locale(std::cout.getloc(), new boost::local_time::local_time_facet("%Y-%m-%d %H:%M:%S UTC%Q")));
strm << dt_with_zone;
// Print the stream's content to the console
cout << strm.str() << endl;
return 0;
}
How should I convert the local_date_time instance to a string so the date in the string is represented using the time zone specified by the time_zone_ptr instance?
I think boost does not know the time zone specifier. Replace
new boost::local_time::posix_time_zone("EST")
in your code by
new boost::local_time::posix_time_zone("EST-05:00:00")
and everything works fine. If you want to use the common standard names, you have to create a timezone database as described in the boost documentation.
Input: strings with date and optional time. Different representations would be nice but necessary. The strings are user-supplied and can be malformed. Examples:
"2004-03-21 12:45:33" (I consider this the default layout)
"2004/03/21 12:45:33" (optional layout)
"23.09.2004 04:12:21" (german format, optional)
"2003-02-11" (time may be missing)
Needed Output: Seconds since Epoch (1970/01/01 00:00:00) or some other fixed point.
Bonus: Also, reading the UTC-offset of the local system time would be great.
The input is assumed to be a local time on the machine in question.
The output needs to be UTC. System is Linux only (Debian Lenny and Ubuntu needed).
I have tried to use boost/date_time, but must admit I can't wrap my head around the documentation. The following works without the needed conversion from system local time to UTC:
std::string date = "2000-01-01";
boost::posix_time::ptime ptimedate = boost::posix_time::time_from_string(date);
ptimedate += boost::posix_time::hours(Hardcoded_UTC_Offset);// where to get from?
struct tm = boost::posix_time::to_tm(ptimedate);
int64_t ticks = mktime(&mTmTime);
I think boost::date_time can provide the needed UTC offset, but I wouldn't know how.
Although I don't know how to format a single-digit month input in boost, I can do it after the two-digit edit:
#include <iostream>
#include <boost/date_time.hpp>
namespace bt = boost::posix_time;
const std::locale formats[] = {
std::locale(std::locale::classic(),new bt::time_input_facet("%Y-%m-%d %H:%M:%S")),
std::locale(std::locale::classic(),new bt::time_input_facet("%Y/%m/%d %H:%M:%S")),
std::locale(std::locale::classic(),new bt::time_input_facet("%d.%m.%Y %H:%M:%S")),
std::locale(std::locale::classic(),new bt::time_input_facet("%Y-%m-%d"))};
const size_t formats_n = sizeof(formats)/sizeof(formats[0]);
std::time_t pt_to_time_t(const bt::ptime& pt)
{
bt::ptime timet_start(boost::gregorian::date(1970,1,1));
bt::time_duration diff = pt - timet_start;
return diff.ticks()/bt::time_duration::rep_type::ticks_per_second;
}
void seconds_from_epoch(const std::string& s)
{
bt::ptime pt;
for(size_t i=0; i<formats_n; ++i)
{
std::istringstream is(s);
is.imbue(formats[i]);
is >> pt;
if(pt != bt::ptime()) break;
}
std::cout << " ptime is " << pt << '\n';
std::cout << " seconds from epoch are " << pt_to_time_t(pt) << '\n';
}
int main()
{
seconds_from_epoch("2004-03-21 12:45:33");
seconds_from_epoch("2004/03/21 12:45:33");
seconds_from_epoch("23.09.2004 04:12:21");
seconds_from_epoch("2003-02-11");
}
note that the seconds-from-epoch output will be assuming the date was in UTC:
~ $ ./test | head -2
ptime is 2004-Mar-21 12:45:33
seconds from epoch are 1079873133
~ $ date -d #1079873133
Sun Mar 21 07:45:33 EST 2004
You could probably use boost::posix_time::c_time::localtime() from #include <boost/date_time/c_time.hpp> to get this conversion done assuming the input is in the current time zone, but it is rather inconsistent: for me, for example, the result will be different between today and next month, when daylight saving ends.
boost::gregorian has some of the stuff you need without you doing any more work:
using namespace boost::gregorian;
{
// The following date is in ISO 8601 extended format (CCYY-MM-DD)
std::string s("2000-01-01");
date d(from_simple_string(s));
std::cout << to_simple_string(d) << std::endl;
}
There is an example on how to use UTC offsets with boost::posix_time here.
You can provide generation of date and time from custom input string formats using date_input_facet and time_input_facet. There is an I/O tutorial on this page that should help you get going.
If c-style is acceptable: strptime() is the way to go, because you can specify the format and it can take locale in account:
tm brokenTime;
strptime(str.c_str(), "%Y-%m-%d %T", &brokenTime);
time_t sinceEpoch = timegm(brokenTime);
Different layouts will have to be checked with the return value (if possible).
Timezone will have to be added to by checking the system clock (localtime_r() with time(), tm_zone)
the simplest, portable solution is to use scanf:
int year, month, day, hour, minute, second = 0;
int r = 0;
r = scanf ("%d-%d-%d %d:%d:%d", &year, &month, &day,
&hour, &minute, &second);
if (r == 6)
{
printf ("%d-%d-%d %d:%d:%d\n", year, month, day, hour, minute,
second);
}
else
{
r = scanf ("%d/%d/%d %d:%d:%d", &year, &month, &day,
&hour, &minute, &second);
// and so on ...
Initialize a struct tm with the int values and pass it to mktime to get a calendar time as time_t. For timezone conversions, please see information on gmtime.