Open frostius opened 3 years ago
Field types are relatively inefficient data structures for much anything beyond retrieving values for the fields. They are good for formatting as that is when the fields are needed. However for storage, arithmetic, comparison, etc. the field types are inefficient both in size and speed.
If one really wants one, it is easy enough to build a {year, month, day, hour, minute, seconds, milliseconds}
struct, and use the existing facilities to translate between that and sys_time<milliseconds>
.
As far as I know, C++ doesn't provide a way to create a literal with the syntax 30 Mar 2021 17:30:00.123 -5Z
or 05:15:00
. Creating such literals would require new features in the language, and language changes are out of scope for this library project.
From the description of date.h:
"date.h"
is a header-only library which builds upon<chrono>
. It adds some newduration
types, and newtime_point
types. It also adds "field" types such asyear_month_day
which is a struct{year, month, day}
. And it provides convenient means to convert between the "field" types and thetime_point
types.
Then you say:
Field types are relatively inefficient data structures
I'm confused. <chrono>
and time_t
already provide efficient time handling. If date.h does not exists to "provide convenient means to convert between the "field" types and the time_point
types." what does it do? And if so, why not support time of day? Not trying to be antagonistic, but it just feels like date.h is half-baked w/o support for time. Either that, or it's already well supported and I'm just missing something?
And yeah, I can roll my own helper classes (and I have) but why not solve the problem once for everyone?
For just time, here does exist hh_mm_ss
. The documentation for this was obsoleted late in the standardization process, resulting in the best documentation is the std spec itself: http://eel.is/c++draft/time.hms.
hh_mm_ss
is a template struct of the form {hours, minutes, seconds, subseconds}
where subseconds
is a function of the class template parameter. It is best used as a formatting aid. It takes a general chrono::duration
and divides it into the various fields. subseconds
is constrained to be a unit with a precision that is a non-positive power of 10. This facilitates formatting the correct number of decimal digits to represent the fractions of a second.
And there are various date field types (e.g. year_month_day
) which have conversions to and from the type std::chrono::time_point<system_clock, duration<int, ratio<86400>>>
(a count of days since 1970-01-01).
One can construct a system_clock-based time_point like this:
auto tp = sys_days{30_d/March/2021} + 17h + 30min + 123ms; // 2021-03-30 17:30:00.123 UTC
Thanks for the example of full time construction - I was missing that I had to wrap it in a sys_days{} to get it work. That helps a lot!
------ Original Message ------ From: "Howard Hinnant" @.> To: "HowardHinnant/date" @.> Cc: "Matt Frost" @.>; "Author" @.> Sent: 3/30/2021 11:56:12 AM Subject: Re: [HowardHinnant/date] full time of day support? (#655)
For just time, here does exist hh_mm_ss. The documentation for this was obsoleted late in the standardization process, resulting in the best documentation is the std spec itself: http://eel.is/c++draft/time.hms.
hh_mm_ss is a template struct of the form {hours, minutes, seconds, subseconds} where subseconds is a function of the class template parameter. It is best used as a formatting aid. It takes a general chrono::duration and divides it into the various fields. subseconds is constrained to be a unit with a precision that is a non-positive power of 10. This facilitates formatting the correct number of decimal digits to represent the fractions of a second.
And there are various date field types (e.g. year_month_day) which have conversions to and from the type std::chrono::time_point<system_clock, duration<int, ratio<86400>>> (a count of days since 1970-01-01).
One can construct a system_clock-based time_point like this:
auto tp = sys_days{30_d/March/2021} + 17h + 30min + 123ms; // 2021-03-30 17:30:00.123 UTC — You are receiving this because you authored the thread. Reply to this email directly, view it on GitHub https://github.com/HowardHinnant/date/issues/655#issuecomment-810421551, or unsubscribe https://github.com/notifications/unsubscribe-auth/AA5EK4W2ALQVZJOHR24QNTDTGH7CZANCNFSM42CME35Q.
As we speak, Nicolai Josuttis is working on a new book that will better document this library (in its std form). No doubt his documentation will put mine to shame. ;-)
Given tp
above, one can deconstruct it back into fields with:
auto td = floor<days>(tp);
year_month_day ymd = td; // ymd has year(), month(), day() getters
hh_mm_ss tod{tp-td}; // tod has hours(), minutes(), seconds(), subseconds() getters
In case this is helpful to anyone else
/// Decompose system time_point to millisecond resolution fields
struct sys_ms_fields {
date::year_month_day ymd;
date::hh_mm_ss<std::chrono::milliseconds> hms;
sys_ms_fields(std::chrono::system_clock::time_point tp) {
auto td = floor<days>(tp);
ymd = date::year_month_day(td);
hms = date::hh_mm_ss(floor<std::chrono::milliseconds>(tp-td));
}
int year() { return static_cast<int> (ymd.year()); }
unsigned short month() { return (unsigned short)static_cast<unsigned>(ymd.month()); }
unsigned short day() { return (unsigned short)static_cast<unsigned>(ymd.day()); }
unsigned short hours() { return static_cast<unsigned short> (hms.hours().count()); }
unsigned short minutes() { return static_cast<unsigned short> (hms.minutes().count()); }
unsigned short seconds() { return static_cast<unsigned short> (hms.seconds().count()); }
unsigned short ms() { return static_cast<unsigned short> (hms.subseconds().count()); }
};
Is there a straight-forward way to make a hh-mm-ss, it's really clumsy to use the suffix, if not constants?
Why there's no such kind of easy stuff?
int hours = 24;
int minutes = 12;
int seconds = 59;
auto t = date::time_of_day<int> { hours, minutes, seconds};
This will do it:
using namespace date;
using namespace std::chrono;
int h = 24;
int m = 12;
int s = 59;
hh_mm_ss tod{hours{h} + minutes{m} + seconds{s}};
Thanks for the quick reply, Now I am able to make date { year, month, day}
, time { hours, mimutes, seconds}
in std::chrono way, would you update your documentation for those who just need a straightforward example?
BTW, another question, why the date::year
with int
operaor, date::month
& date::day
with unsigned int
operator? why not std::chrono::hours::count()
convention?
#include <date/date.h>
#include <gtest/gtest.h>
TEST(DateTest, Basic)
{
using namespace date;
using namespace std::chrono;
auto year = date::year(2023);
auto month = date::month(12);
auto day = date::day(13);
date::year_month_day x(year, month, day);
ASSERT_TRUE(x.year() == year && x.month() == month && x.day() == day);
int y = (int)year;
int m = (unsigned int)month;
int d = (unsigned int)day;
ASSERT_TRUE(y == 2023 && m == 12 && d == 13);
int hh = 23;
int mm = 12;
int ss = 59;
auto tod = date::time_of_day<std::chrono::seconds>{hours(hh) + hours(mm) + seconds(ss)};
}
I was working on the theory that a generic spelling for explicit type conversion would reduce the "apparent API".
In order to explicitly convert a value u
of type U
to a type T
, this syntax often works:
T t{u};
For example, to convert an int
to a year
:
int i = 2023;
year y{i};
And so I made it symmetrical:
year y = 2023_y;
int i{y};
This was done in the hopes that the apparent API would become smaller because one no longer has to remember the spelling of a named getter. But that will only happen if this pattern becomes more widely spread.
I should also emphasize that the conversions in both directions (year
to int
and int
to year
) are explicit
because the two types are not semantically equivalent. Though the conversion is necessary on a practical level, it is also a dangerous conversion in that the result has a different meaning than the source (even though the value is the same).
In general I encourage people to minimize conversions into and out of the chrono type system.
With all that in mind, here is alternate syntax for your test for your consideration:
TEST(DateTest, Basic)
{
using namespace date;
using namespace std::chrono;
auto year = 2023_y;
auto month = December;
auto day = 13_d;
auto x = year/month/day;
ASSERT_TRUE(x.year() == 2023_y && x.month() == December && x.day() == 13_d);
auto y = int{year};
auto m = unsigned{month};
auto d = unsigned{day};
ASSERT_TRUE(y == 2023 && m == 12 && d == 13);
int hh = 23;
int mm = 12;
int ss = 59;
auto tod = hours{hh} + hours{mm} + seconds{ss};
}
parse
can convert straight from input streams to chrono/date types./
"conventional syntax" avoids having to remember the names of some types such as year_month_day
. This becomes even more important when dealing with lesser-used types. E.g.: auto z = y.year()/y.month()/Friday[last];
. z
has the value of the last Friday of the indicated year and month, and has type year_month_weekday_last
. The name of the type of z
is extremely verbose and often never really needed in the code.{}
also helps catch narrowing errors.time_of_day
to hh_mm_ss
rather late in the standardization process. time_of_day
was retained in this library for backwards compatibility. I encourage the use of hh_mm_ss
going forward to ease eventual migration to C++20.hh_mm_ss
is a good formatting aid, and a decent tool for when you want to split a duration up into {hours, minutes, seconds}
fields. However it is not a good type for storage or computation. Consider storing your "time of day" types in a duration such as seconds
.Thanks for the thoughtful & detailed explanation, thanks for the contributions!
Is there a reason why there isn't a single class that contains all date & time fields? Also, why no support for time of day literals hh:mm:ss.ms and timezone? Its clumsy trying to do something as simple as initialize a time point with a known date & time.