From 4318716ed2470e485ea0cdc276ebe60e1030098b Mon Sep 17 00:00:00 2001 From: Howard Hinnant Date: Sun, 29 May 2016 22:34:04 -0400 Subject: [PATCH] Updated Examples and Recipes (markdown) --- Examples-and-Recipes.md | 74 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 74 insertions(+) diff --git a/Examples-and-Recipes.md b/Examples-and-Recipes.md index 89fc731..3dee666 100644 --- a/Examples-and-Recipes.md +++ b/Examples-and-Recipes.md @@ -4,6 +4,7 @@ This page contains examples and recipes contributed by community members. Feel f - [Obtaining a `time_point` from `y/m/d h:m:s` components](#time_point_to_components) - [Obtaining `y/m/d h:m:s` components from a `time_point`](#components_to_time_point) - [Normalizing `y/m/d` when it is `!ok()`](#normalize) +- [When is it ok to be `!ok()`?](#not_ok_is_ok) - [Converting from {year, microseconds} to CCSDS](#ccsds) - [Difference in months between two dates](#deltamonths) - [Parsing ISO strings](http://stackoverflow.com/a/33438989/576911) @@ -157,6 +158,79 @@ Outputs: The specifications for these functions do not yet guarantee this "normalization behavior." But the implementation has been thoroughly tested for this behavior and the specification will be updated soon. + +### When is it ok to be `!ok()`? +(by [Howard Hinnant](https://github.com/HowardHinnant)) + +This library allows dates to silently fall into a state of `!ok()`. Why does not `!ok()` assert or throw? When is it *ever* ok to be `!ok()`? + +Consider this problem: + +I want to find all dates for some year `y` which are the 5th Friday of the month (because that is party day or whatever). Here is a **very** efficient function which collects all of the 5th Fridays of a year: + + std::pair, unsigned> + fifth_friday(date::year y) + { + using namespace date; + std::array dates{0_y/0/0, 0_y/0/0, 0_y/0/0, 0_y/0/0, 0_y/0/0}; + unsigned n = 0; + for (auto m = jan; true; ++m) + { + auto d = fri[5]/m/y; + if (d.ok()) + { + dates[n] = year_month_day{d}; + ++n; + } + if (m == dec) + break; + } + return {dates, n}; + } + +It turns out that it is an invariant that *every* year will have either 4 or 5 months which will have 5 Fridays. So we can efficiently return the results as a `pair, unsigned>`, where the second member of the `pair` will always be either 4 or 5. + +The first job is just to initialize the `array` with a bunch of `year_month_day`s. I've arbitrarily chosen `0_y/0/0` as a good initialization value. What do I like about this value? One of the things I like is that it is `!ok()`!. If I accidentally access `.first[4]` when `.second == 4`, an extra bit of safety is that the resultant `year_month_day` is `!ok()`. So being able to construct these `!ok()` values without an assert or exception is important just for that reason (like a `nan`). The cost? Nothing. These are compile-time constants. + +Next I iterate over each month for the year `y`. The first thing to do is construct the 5th Friday for this month/year pair: + + auto d = fri[5]/m/y; + +Now since not every month has a 5th Friday, this may not result in a valid date. But in this function the proper response to constructing an invalid date _is not_ an assert nor an exception. The _proper_ response is to _ignore_ the date and iterate on to the next month. If it is a valid date, then it pushed on to the result. + +This function can be exercised like this: + + int + main() + { + using namespace std::chrono; + using namespace date; + auto current_year = year_month_day{floor(system_clock::now())}.year(); + auto dates = fifth_friday(current_year); + std::cout << "Fifth Friday dates for " << current_year << " are:\n"; + for (auto i = 0u; i < dates.second; ++i) + std::cout << dates.first[i] << '\n'; + } + +The variable `current_year` is initialized with the current year in the UTC time zone (close enough for government work -- use "tz.h" if you need to make it more exact to your locale). Then it is a simple matter to feed `current_year` into `fifth_friday` and iterate over the results. This just output for me: + + Fifth Friday dates for 2016 are: + 2016-01-29 + 2016-04-29 + 2016-07-29 + 2016-09-30 + 2016-12-30 + +Next year it will output: + + Fifth Friday dates for 2017 are: + 2017-03-31 + 2017-06-30 + 2017-09-29 + 2017-12-29 + +_Many_ invalid dates were computed during the execution of this program. And _none_ of them represented errors. + ### Converting from {year, microseconds} to CCSDS (by [Howard Hinnant](https://github.com/HowardHinnant))