[chrono/date] Two axes for a date design: validity check and representation and an essence date class/factory
Hi, I would like to discuss about some alternatives to take in account the following use case "Reset a date if it resulting date is valid and report if this succeeds" when we have two axes of variation on the date design. Let me add two parameters to the possible date class. * validity: unchecked/checked * representation: serial, ymd(calendar), iso_ordinal, iso_week... date<serial, unchecked> is an unchecked serial date. It corresponds +/- to the N3344 date suggestion. date<ymd, checked> is a checked ymd date. It corresponds +/- to the H.H 2011 date proposal. WARNING !!! I'm not saying that a template class is a good thing, it is just to state clearly the features of the date. N3344 suggest a date::set_if_valid_date function. date<serial, unchecked> ud=...; if (ud.set_if_valid_date(y,m,d)) { // xx } else { // yy } But if we want to do as for the date constructors we will need multiple overloads again for this function :(. As show on this ML serial+unchecked dates validation could have some surprises. We can get the same service building a ymd date that can provide a way to be validated. //------------- // Validity check done when is_valid() is called on ymd_date. date<serial, unchecked> dd=...; date<ymd, unchecked> yd(y,m,d); if (yd.is_valid()) { // CHECK dd = yd; // xx } else { // yy } Instead of using explicitly date<ymd,unchecked> we can use a make_essence() factory that returns some kind of tuple essence_date<year,month,day> that is not a date, but its essence. It contains everything needed to build a date. This essence should be implicitly convertible to any date. The make_essence_date() factory supports all the different orders of parameters. The operator/() expression would also create an essence date. // -------------- //No validity check is done. date<serial, unchecked> ud = make_essence(y,m,d); date<serial, unchecked> ud1 = m/d/y; This essence dates support date validity, that is provides a is_valid() function. // -------------- // Validity check done when is_valid() is called on essence. date<serial, unchecked> ud; auto d = y/m/d; if (d.is_valid()) { // CHECK ud = d; // xx } else { // yy } //-------------- // Validity check done at construction of cd. serial_date<checked> cd = make_essence(m,d,y); //serial_date<checked> cd = m/d/y; Summary: IMO Having checked and unchecked dates with several representations could help to make dates interface more precise and concise. Checked dates are always valid. Dates with a specific representations would provide only, as suggested by Howard, only the functions that are efficient. The use of essence dates factories simplify the interface of all the date classes construction. Only 1 constructor by representation needed independent of the parameters order date(essence<ymd> &&); date(essence<serial> &&); date(essence<iso_ordinal> &&); date(essence<iso_week> &&); Comments welcome, Vicente P.S.The essence date classes and the contextual dates are quite related. I prefer to let this 3rd contextual axe of variation out of the current scope. I will try to integrate it soon.
Le 07/05/13 12:46, Vicente J. Botet Escriba a écrit :
Hi,
I would like to discuss about some alternatives to take in account the following use case "Reset a date if it resulting date is valid and report if this succeeds" when we have two axes of variation on the date design.
Let me add two parameters to the possible date class. * validity: unchecked/checked * representation: serial, ymd(calendar), iso_ordinal, iso_week...
date<serial, unchecked> is an unchecked serial date. It corresponds +/- to the N3344 date suggestion. date<ymd, checked> is a checked ymd date. It corresponds +/- to the H.H 2011 date proposal.
WARNING !!! I'm not saying that a template class is a good thing, it is just to state clearly the features of the date.
N3344 suggest a date::set_if_valid_date function.
date<serial, unchecked> ud=...; if (ud.set_if_valid_date(y,m,d)) { // xx } else { // yy }
But if we want to do as for the date constructors we will need multiple overloads again for this function :(. As show on this ML serial+unchecked dates validation could have some surprises. We can get the same service building a ymd date that can provide a way to be validated.
//------------- // Validity check done when is_valid() is called on ymd_date.
date<serial, unchecked> dd=...; date<ymd, unchecked> yd(y,m,d); if (yd.is_valid()) { // CHECK dd = yd; // xx } else { // yy }
Instead of using explicitly date<ymd,unchecked> we can use a make_essence() factory that returns some kind of tuple essence_date<year,month,day> that is not a date, but its essence. It contains everything needed to build a date. This essence should be implicitly convertible to any date. The make_essence_date() factory supports all the different orders of parameters. The operator/() expression would also create an essence date.
// -------------- //No validity check is done.
date<serial, unchecked> ud = make_essence(y,m,d); date<serial, unchecked> ud1 = m/d/y;
This essence dates support date validity, that is provides a is_valid() function.
// -------------- // Validity check done when is_valid() is called on essence.
date<serial, unchecked> ud; auto d = y/m/d; if (d.is_valid()) { // CHECK ud = d; // xx } else { // yy }
//-------------- // Validity check done at construction of cd.
serial_date<checked> cd = make_essence(m,d,y); //serial_date<checked> cd = m/d/y;
Summary: IMO Having checked and unchecked dates with several representations could help to make dates interface more precise and concise. Checked dates are always valid. Dates with a specific representations would provide only, as suggested by Howard, only the functions that are efficient. The use of essence dates factories simplify the interface of all the date classes construction. Only 1 constructor by representation needed independent of the parameters order
date(essence<ymd> &&); date(essence<serial> &&); date(essence<iso_ordinal> &&); date(essence<iso_week> &&);
Comments welcome, Vicente
P.S.The essence date classes and the contextual dates are quite related. I prefer to let this 3rd contextual axe of variation out of the current scope. I will try to integrate it soon.
After doing some trials, it seems that the essence essence<T> class is equivalent to date<T, unchecked> :). Vicente
Vicente J. Botet Escriba wrote:
Le 07/05/13 12:46, Vicente J. Botet Escriba a écrit :
I would like to discuss about some alternatives to take in account the following use case "Reset a date if it resulting date is valid and report if this succeeds" when we have two axes of variation on the date design.
Let me add two parameters to the possible date class. * validity: unchecked/checked * representation: serial, ymd(calendar), iso_ordinal, iso_week...
date<serial, unchecked> is an unchecked serial date. It corresponds +/- to the N3344 date suggestion. date<ymd, checked> is a checked ymd date. It corresponds +/- to the H.H 2011 date proposal.
WARNING !!! I'm not saying that a template class is a good thing, it is just to state clearly the features of the date.
N3344 suggest a date::set_if_valid_date function.
date<serial, unchecked> ud=...; if (ud.set_if_valid_date(y,m,d)) { // xx } else { // yy }
But if we want to do as for the date constructors we will need multiple overloads again for this function :(.
That possibly modifies ud. Spelling it "try_set" will be clearer (and shorter!). The benefit of this approach is that the checking is not wasted; the result is stored in ud. The downside is that ud is still unchecked, so the checked characteristic is lost except from context. Thus, creating a date<serial, checked> from a date<serial, unchecked> would be ideal, but hardly fits as a member function of the latter.
As show on this ML serial+unchecked dates validation could have some surprises. We can get the same service building a ymd date that can provide a way to be validated.
//------------- // Validity check done when is_valid() is called on ymd_date.
date<serial, unchecked> dd=...; date<ymd, unchecked> yd(y,m,d); if (yd.is_valid()) { // CHECK dd = yd; // xx } else { // yy }
That should probably be in addition to try_set(). That is, both have their uses, so choosing between them is problematic.
Instead of using explicitly date<ymd,unchecked> we can use a make_essence() factory that returns some kind of tuple essence_date<year,month,day> that is not a date, but its essence. It contains everything needed to build a date.
That sounds like Howard's ymd struct idea. The latter name is certainly more convenient and specific than "essence". Nevertheless, I understand that you're trying to be abstract here.
This essence should be implicitly convertible to any date. The make_essence_date() factory supports all the different orders of parameters.
Presumably, that also would include day-of-year, week-of-year, and other, similar constructs.
The operator/() expression would also create an essence date.
// -------------- //No validity check is done.
date<serial, unchecked> ud = make_essence(y,m,d); date<serial, unchecked> ud1 = m/d/y;
If s/make_essense/make_date/, that looks very workable.
This essence dates support date validity, that is provides a is_valid() function.
// -------------- // Validity check done when is_valid() is called on essence.
date<serial, unchecked> ud; auto d = y/m/d; if (d.is_valid()) { // CHECK ud = d; // xx } else { // yy }
//-------------- // Validity check done at construction of cd.
serial_date<checked> cd = make_essence(m,d,y); //serial_date<checked> cd = m/d/y;
That makes sense. serial_date<checked>(essence) can call essence::is_valid(), so the validity checking logic is only one place.
Summary: IMO Having checked and unchecked dates with several representations could help to make dates interface more precise and concise. Checked dates are always valid. Dates with a specific representations would provide only, as suggested by Howard, the functions that are efficient. The use of essence dates factories simplify the interface of all the date classes construction. Only 1 constructor by representation needed independent of the parameters order
date(essence<ymd> &&); date(essence<serial> &&); date(essence<iso_ordinal> &&); date(essence<iso_week> &&);
After doing some trials, it seems that the essence essence<T> class is equivalent to date<T, unchecked> :).
Here you've taken an unexpected turn, though looking back at the options you mentioned for the representation axis, it shouldn't have been unexpected. I was thinking your make_essence() would take various arguments, including day-of-year and similar special cases, convert them to ymd values, and the various date classes would construct from that information. ymd is likely not the best intermediate format of the information for every representation. A date<iso_week,*> date would convert trivially from an essence<iso_week>, so if the make_essence() overload, that builds an iso_week value, returns an essence<iso_week>, then creating a date<iso_week,*> can be optimally efficient. The approach seems good. Each date class can focus on its best features and leave others to other classes. There's one piece missing, however. You haven't discussed date type conversions. [From this point, I'll s/essence<Rep>/date<Rep,unchecked>/ to correspond to your findings and avoid the awkward name "essence".] Should date<Rep> be the intermediary for conversions, too? That is, does each date type provide a get_rep() accessor that returns a date<Rep,unchecked> from which another date type can be constructed? Can we make such conversions implicit? With delegating constructors, I think so: struct some_date_type { some_date_type(date<ymd,unchecked> const &); ... template <class Date> some_date_type(Date const & _other) : some_date_type(_other.get_rep()) { } date<my_ideal_rep,unchecked> get_rep() const; }; That means that no date type need know about the rest, yet they all can interconvert. Even user-defined date types can participate in this interoperably. date<Rep,unchecked> could be extended for a user-defined representation, but the standard date types need some help to convert from it. You'd start with a customization point: template <class Rep, class Checking, class Rep2> date<Rep,Checking> make_date(date<Rep2,unchecked> const &); Then, the standard date types need an appropriate constructor: template <class Rep> some_date_type(date<Rep,unchecked> const & _date) : some_date_type(make_date<MyRep,MyChecking>(_date)) { } So long as make_date() is specialized for a particular Rep/Checking/Rep2 tuple, then a standard date type can be constructed from a date<Rep2,unchecked>. Interestingly, given that customization point, the date classes now only need a move constructor and that templated constructor to support all representations. That is, all of the conversion work moves to the make_date() specializations. Indeed, a standard date class can look like the following yet support all of the argument combinations and interconversions: template <class Rep, checking_type Checking> struct date { date(date const &); date(date &&); template <class R, class C> date(date<R,C> const & _date) : date(static_cast<date<R,unchecked>>(_date)) { } template <class R> date(date<R,unchecked> const & _date) : date(make_date<Rep,Checking>(_date)) { } date & operator =(date const &); date & operator =(date &&); template <class R, class C> date & operator =(date<R,C> const & _date) { date(_date).swap(*this); } explicit operator date<Rep,unchecked>() const; }; Of course, that means the only way to create an arbitrary date type from a particular argument list is via make_date(), so other constructors may be desirable. _____ Rob Stewart robert.stewart@sig.com Software Engineer using std::disclaimer; Dev Tools & Components Susquehanna International Group, LLP http://www.sig.com ________________________________ IMPORTANT: The information contained in this email and/or its attachments is confidential. If you are not the intended recipient, please notify the sender immediately by reply and immediately delete this message and all its attachments. Any review, use, reproduction, disclosure or dissemination of this message or any attachment by an unintended recipient is strictly prohibited. Neither this message nor any attachment is intended as or should be construed as an offer, solicitation or recommendation to buy or sell any security or other financial instrument. Neither the sender, his or her employer nor any of their respective affiliates makes any warranties as to the completeness or accuracy of any of the information contained herein or that this message or any of its attachments is free of viruses.
Le 08/05/13 14:52, Stewart, Robert a écrit : Thanks Rob for your comments and suggestions. Note that I'm not YET suggesting yet a single class date with representation and validity parameters and yes the essence concept is represented by the unchecked dates as I said in a reply to my own post.
Vicente J. Botet Escriba wrote:
Le 07/05/13 12:46, Vicente J. Botet Escriba a écrit :
I would like to discuss about some alternatives to take in account the following use case "Reset a date if it resulting date is valid and report if this succeeds" when we have two axes of variation on the date design.
Let me add two parameters to the possible date class. * validity: unchecked/checked * representation: serial, ymd(calendar), iso_ordinal, iso_week...
date<serial, unchecked> is an unchecked serial date. It corresponds +/- to the N3344 date suggestion. date<ymd, checked> is a checked ymd date. It corresponds +/- to the H.H 2011 date proposal.
WARNING !!! I'm not saying that a template class is a good thing, it is just to state clearly the features of the date.
N3344 suggest a date::set_if_valid_date function.
date<serial, unchecked> ud=...; if (ud.set_if_valid_date(y,m,d)) { // xx } else { // yy }
But if we want to do as for the date constructors we will need multiple overloads again for this function :(. That possibly modifies ud. Spelling it "try_set" will be clearer (and shorter!). The benefit of this approach is that the checking is not wasted; the result is stored in ud. The downside is that ud is still unchecked, so the checked characteristic is lost except from context. I wanted to get rid of this set_if_valid_date, but I think it is the clearer and more concise of all the other alternatives. I agree with renaming it. The parameter could be (any other?) an unchecked date.
Thus, creating a date<serial, checked> from a date<serial, unchecked> would be ideal, but hardly fits as a member function of the latter. Both conversion must be supported. Whether this conversions are implicit or explicit would depend on the representation.
As show on this ML serial+unchecked dates validation could have some surprises. We can get the same service building a ymd date that can provide a way to be validated.
//------------- // Validity check done when is_valid() is called on ymd_date.
date<serial, unchecked> dd=...; date<ymd, unchecked> yd(y,m,d); if (yd.is_valid()) { // CHECK dd = yd; // xx } else { // yy } That should probably be in addition to try_set(). That is, both have their uses, so choosing between them is problematic. Agreed. Instead of using explicitly date<ymd,unchecked> we can use a make_essence() factory that returns some kind of tuple essence_date<year,month,day> that is not a date, but its essence. It contains everything needed to build a date. That sounds like Howard's ymd struct idea. The latter name is certainly more convenient and specific than "essence". Nevertheless, I understand that you're trying to be abstract here.
This essence should be implicitly convertible to any date. The make_essence_date() factory supports all the different orders of parameters. Presumably, that also would include day-of-year, week-of-year, and other, similar constructs. Of course.
The operator/() expression would also create an essence date.
// -------------- //No validity check is done.
date<serial, unchecked> ud = make_essence(y,m,d); date<serial, unchecked> ud1 = m/d/y; If s/make_essense/make_date/, that looks very workable. Yes.
This essence dates support date validity, that is provides a is_valid() function.
// -------------- // Validity check done when is_valid() is called on essence.
date<serial, unchecked> ud; auto d = y/m/d; if (d.is_valid()) { // CHECK ud = d; // xx } else { // yy }
//-------------- // Validity check done at construction of cd.
serial_date<checked> cd = make_essence(m,d,y); //serial_date<checked> cd = m/d/y; That makes sense. serial_date<checked>(essence) can call essence::is_valid(), so the validity checking logic is only one place. Yes, and can be checked at construction.
Summary: IMO Having checked and unchecked dates with several representations could help to make dates interface more precise and concise. Checked dates are always valid. Dates with a specific representations would provide only, as suggested by Howard, the functions that are efficient. The use of essence dates factories simplify the interface of all the date classes construction. Only 1 constructor by representation needed independent of the parameters order
date(essence<ymd> &&); date(essence<serial> &&); date(essence<iso_ordinal> &&); date(essence<iso_week> &&);
After doing some trials, it seems that the essence essence<T> class is equivalent to date<T, unchecked> :). Here you've taken an unexpected turn, though looking back at the options you mentioned for the representation axis, it shouldn't have been unexpected. I was thinking your make_essence() would take various arguments, including day-of-year and similar special cases, convert them to ymd values, and the various date classes would construct from that information. No I didn't wanted to reserve it to ymd representation . The essence is whatever information defining a date but not checked, so yes it could be any unchecked date. ymd is likely not the best intermediate format of the information for every representation. A date<iso_week,*> date would convert trivially from an essence<iso_week>, so if the make_essence() overload, that builds an iso_week value, returns an essence<iso_week>, then creating a date<iso_week,*> can be optimally efficient. This is the idea. The approach seems good. Each date class can focus on its best features and leave others to other classes. I hope we all agree on this point. There's one piece missing, however. You haven't discussed date type conversions.
[From this point, I'll s/essence<Rep>/date<Rep,unchecked>/ to correspond to your findings and avoid the awkward name "essence".] Consider it already done ;-) Should date<Rep> be the intermediary for conversions, too? That is, does each date type provide a get_rep() accessor that returns a date<Rep,unchecked> from which another date type can be constructed? I don't see the need. A date can be converted from any other date, without needing to get the representation. Can we make such conversions implicit? Not all the conversions should be implicit. Howard has suggested that we can start with explicit conversions except the calendar->serial. If we
bool date::try_set(date<R, uncheked>); When used together with factories the result is quite readable if (ud.try_set(m/d/y)) { provide ordinal_date or other date representation, the conversion from calendar_date to all the other should be implicit.
With delegating constructors, I think so:
struct some_date_type { some_date_type(date<ymd,unchecked> const &); ...
template <class Date> some_date_type(Date const & _other) : some_date_type(_other.get_rep()) { }
date<my_ideal_rep,unchecked> get_rep() const; }; I don't see yet the need for this get_rep() function.
That means that no date type need know about the rest, yet they all can interconvert. Even user-defined date types can participate in this interoperably. date<Rep,unchecked> could be extended for a user-defined representation, but the standard date types need some help to convert from it. You'd start with a customization point:
template <class Rep, class Checking, class Rep2> date<Rep,Checking> make_date(date<Rep2,unchecked> const &); Your make_date factory is now a converter. We can go this way but this remember my Boost.Conversion library review. I guess that it would be better to avoid all the troubles this kind of customization points can raise. Instead we can have a hierarchy of dates. The new added dates should provide the conversion to all the other.
Then, the standard date types need an appropriate constructor:
template <class Rep> some_date_type(date<Rep,unchecked> const & _date) : some_date_type(make_date<MyRep,MyChecking>(_date)) { } As I said, I'm not suggesting yet a date template class. I have not taken the time to see if there is something that could be obtained with it. So long as make_date() is specialized for a particular Rep/Checking/Rep2 tuple, then a standard date type can be constructed from a date<Rep2,unchecked>.
Interestingly, given that customization point, the date classes now only need a move constructor and that templated constructor to support all representations. That is, all of the conversion work moves to the make_date() specializations.
Indeed, a standard date class can look like the following yet support all of the argument combinations and interconversions:
template <class Rep, checking_type Checking> struct date { date(date const &);
date(date &&);
template <class R, class C> date(date<R,C> const & _date) : date(static_cast<date<R,unchecked>>(_date)) { }
template <class R> date(date<R,unchecked> const & _date) : date(make_date<Rep,Checking>(_date)) { }
date & operator =(date const &);
date & operator =(date &&);
template <class R, class C> date & operator =(date<R,C> const & _date) { date(_date).swap(*this); }
explicit operator date<Rep,unchecked>() const; };
Of course, that means the only way to create an arbitrary date type from a particular argument list is via make_date(), so other constructors may be desirable.
You know that I would like we can do this kind customization without having ODR issues, but I suspect we cannot. Best, Vicente
participants (2)
-
Stewart, Robert
-
Vicente J. Botet Escriba