Add format and parse functionality.

This commit is contained in:
Howard Hinnant 2016-04-03 18:57:02 -04:00
parent d3be73c664
commit e8f95dddb7
3 changed files with 237 additions and 2 deletions

8
date.h
View File

@ -29,6 +29,7 @@
# include <cmath> # include <cmath>
#endif #endif
#include <limits> #include <limits>
#include <locale>
#include <ostream> #include <ostream>
#include <ratio> #include <ratio>
#include <stdexcept> #include <stdexcept>
@ -787,12 +788,14 @@ class save_stream
std::ostream& os_; std::ostream& os_;
char fill_; char fill_;
std::ios::fmtflags flags_; std::ios::fmtflags flags_;
std::locale loc_;
public: public:
~save_stream() ~save_stream()
{ {
os_.fill(fill_); os_.fill(fill_);
os_.flags(flags_); os_.flags(flags_);
os_.imbue(loc_);
} }
save_stream(const save_stream&) = delete; save_stream(const save_stream&) = delete;
@ -802,6 +805,7 @@ public:
: os_(os) : os_(os)
, fill_(os.fill()) , fill_(os.fill())
, flags_(os.flags()) , flags_(os.flags())
, loc_(os.getloc())
{} {}
}; };
@ -3564,7 +3568,9 @@ public:
os.width(2); os.width(2);
os << std::abs(t.m_.count()) << ':'; os << std::abs(t.m_.count()) << ':';
os.width(2); os.width(2);
os << std::abs(t.s_.count()) << '.'; os << std::abs(t.s_.count())
<< use_facet<numpunct<char>>(os.getloc()).decimal_point();
os.imbue(locale{});
#if __cplusplus >= 201402 #if __cplusplus >= 201402
CONSTDATA auto cl10 = ceil_log10(Period::den); CONSTDATA auto cl10 = ceil_log10(Period::den);
using scale = std::ratio_multiply<Period, std::ratio<pow10(cl10)>>; using scale = std::ratio_multiply<Period, std::ratio<pow10(cl10)>>;

2
tz.cpp
View File

@ -84,7 +84,7 @@ namespace date
#if _WIN32 // TODO: sensible default for all platforms. #if _WIN32 // TODO: sensible default for all platforms.
static std::string install{ "c:\\tzdata" }; static std::string install{ "c:\\tzdata" };
#else #else
static std::string install{ "/Users/howardhinnant/Downloads/tzdata2016a" }; static std::string install{ "/Users/howardhinnant/Downloads/tzdata" };
#endif #endif
static const std::vector<std::string> files = static const std::vector<std::string> files =

229
tz.h
View File

@ -57,6 +57,7 @@ Technically any OS may use the mapping process but currently only Windows does u
#include <chrono> #include <chrono>
#include <cstdint> #include <cstdint>
#include <istream> #include <istream>
#include <locale>
#include <ostream> #include <ostream>
#include <ratio> #include <ratio>
#include <sstream> #include <sstream>
@ -686,6 +687,234 @@ utc_clock::utc_to_sys(std::chrono::time_point<utc_clock, Duration> t)
return tp; return tp;
} }
// format
namespace detail
{
template <class Duration>
std::string
format(const std::locale& loc, std::string format,
std::chrono::time_point<std::chrono::system_clock, Duration> tp,
const Zone* zone)
{
// Handle these specially
// %S append fractional seconds if tp has precision finer than seconds
// %T append fractional seconds if tp has precision finer than seconds
// %z replace with offset from zone
// %Z replace with abbreviation from zone
using namespace std;
using namespace std::chrono;
for (auto i = 0; i < format.size(); ++i)
{
if (format[i] == '%' && i < format.size()-1)
{
switch (format[i+1])
{
case 'S':
case 'T':
if (ratio_less<typename Duration::period, ratio<1>>::value)
{
ostringstream os;
os.imbue(loc);
os << make_time(tp - floor<seconds>(tp));
auto s = os.str();
s.erase(0, 8);
format.insert(i+2, s);
i += 2 + s.size() - 1;
}
break;
case 'z':
if (zone == nullptr)
{
format.replace(i, 2, "+0000");
i += 5 - 1;
}
else
{
auto info = zone->get_info(tp, tz::local);
auto offset = duration_cast<minutes>(info.offset);
ostringstream os;
if (offset >= minutes{0})
os << '+';
os << make_time(offset);
auto s = os.str();
s.erase(s.find(':'), 1);
format.replace(i, 2, s);
i += s.size() - 1;
}
break;
case 'Z':
if (zone == nullptr)
{
format.replace(i, 2, "UTC");
i += 3 - 1;
}
else
{
auto info = zone->get_info(tp, tz::local);
format.replace(i, 2, info.abbrev);
i += info.abbrev.size() - 1;
}
break;
}
}
}
auto& f = use_facet<time_put<char>>(loc);
ostringstream os;
auto tt = system_clock::to_time_t(tp);
std::tm tm{};
#ifndef _MSC_VER
gmtime_r(&tt, &tm);
#else
gmtime_s(&tm, &tt);
#endif
f.put(os, os, os.fill(), &tm, format.data(), format.data() + format.size());
return os.str();
}
} // namespace detail
template <class Duration>
inline
std::string
format(const std::locale& loc, std::string format,
std::chrono::time_point<std::chrono::system_clock, Duration> tp,
const Zone* zone = nullptr)
{
return detail::format(loc, std::move(format), tp, zone);
}
inline
std::string
format(const std::locale& loc, std::string format, day_point tp,
const Zone* zone = nullptr)
{
return detail::format(loc, std::move(format), tp, zone);
}
template <class Duration>
inline
std::string
format(std::string format,
std::chrono::time_point<std::chrono::system_clock, Duration> tp,
const Zone* zone = nullptr)
{
return detail::format(std::locale{}, std::move(format), tp, zone);
}
inline
std::string
format(std::string format, day_point tp, const Zone* zone = nullptr)
{
return detail::format(std::locale{}, std::move(format), tp, zone);
}
// parse
template <class Duration>
void
parse(std::istream& is, const std::string& format,
std::chrono::time_point<std::chrono::system_clock, Duration>& tp)
{
using namespace std;
using namespace std::chrono;
istream::sentry ok{is};
if (ok)
{
auto& f = use_facet<time_get<char>>(is.getloc());
ios_base::iostate err = ios_base::goodbit;
std::tm tm{};
minutes offset{};
Duration subseconds{};
auto b = format.data();
auto i = b;
auto e = b + format.size();
for (; i < e; ++i)
{
if (*i == '%' && i < e-1)
{
switch (i[1])
{
case 'T':
case 'S':
f.get(is, 0, is, err, &tm, b, i);
++i;
b = i+1;
if (*i == 'T')
{
const char hm[] = "%H:%M:";
f.get(is, 0, is, err, &tm, hm, hm+6);
}
if (ratio_less<typename Duration::period, ratio<1>>::value)
{
double s;
is >> s;
if (!is.fail())
subseconds = duration_cast<Duration>(duration<double>{s});
else
err &= ios_base::failbit;
}
else
{
const char hm[] = "%S";
f.get(is, 0, is, err, &tm, hm, hm+2);
}
break;
case 'z':
f.get(is, 0, is, err, &tm, b, i);
++i;
b = i+1;
if ((err & ios_base::failbit) == 0)
{
char sign{};
is >> sign;
if (!is.fail() && (sign == '+' || sign == '-'))
{
char h1, h0, m1, m0;
h1 = static_cast<char>(is.get());
h0 = static_cast<char>(is.get());
m1 = static_cast<char>(is.get());
m0 = static_cast<char>(is.get());
if (!is.fail() && std::isdigit(h1) && std::isdigit(h0)
&& std::isdigit(m1) && std::isdigit(m0))
{
offset = 10*hours{h1 - '0'} + hours{h0 - '0'} +
10*minutes{m1 - '0'} + minutes{m0 - '0'};
if (sign == '-')
offset = -offset;
}
else
err &= ios_base::failbit;
}
else
err &= ios_base::failbit;
}
break;
}
}
}
if ((err & ios_base::failbit) == 0)
{
if (b < e)
f.get(is, 0, is, err, &tm, b, e);
if ((err & ios_base::failbit) == 0)
{
#ifndef _MSC_VER
auto tt = timegm(&tm);
#else
auto tt = _mkgmtime(&tm);
#endif
tp = floor<Duration>(system_clock::from_time_t(tt) +
subseconds - offset);
}
}
is.setstate(err);
}
}
} // namespace date } // namespace date
#endif // TZ_H #endif // TZ_H