mirror of
https://github.com/HowardHinnant/date.git
synced 2025-08-03 12:44:27 +02:00
Updated Examples and Recipes (markdown)
@@ -1050,46 +1050,50 @@ How you choose between these time zones is beyond the scope of this library. Pe
|
||||
|
||||
Let's say you want to search the globe, and all time, for time zones when the daylight savings shift was not 1 hour. Sound strange? Maybe, but this code teaches you how to _efficiently_ iterate over **all** timezone transitions and inspect their characteristics. So you can use this code for all kinds of searches over time zones.
|
||||
|
||||
#include "tz.h"
|
||||
#include <iostream>
|
||||
```c++
|
||||
#include "tz.h"
|
||||
#include <iostream>
|
||||
|
||||
int
|
||||
main()
|
||||
int
|
||||
main()
|
||||
{
|
||||
using namespace date;
|
||||
using namespace std::chrono_literals;
|
||||
auto& db = get_tzdb();
|
||||
for (auto const& z : db.zones)
|
||||
{
|
||||
using namespace date;
|
||||
using namespace std::chrono_literals;
|
||||
auto& db = get_tzdb();
|
||||
for (auto const& z : db.zones)
|
||||
auto begin = sys_days{jan/1/year::min()} + 0s;
|
||||
auto end = sys_days{jan/1/2035} + 0s;
|
||||
do
|
||||
{
|
||||
auto begin = sys_days{jan/1/year::min()} + 0s;
|
||||
auto end = sys_days{jan/1/2035} + 0s;
|
||||
do
|
||||
auto info = z.get_info(begin);
|
||||
if (info.save != 0h && info.save != 1h)
|
||||
{
|
||||
auto info = z.get_info(begin);
|
||||
if (info.save != 0h && info.save != 1h)
|
||||
{
|
||||
std::cout << z.name() << " has a daylight savings offset of "
|
||||
<< info.save.count() << "min from " << info.begin
|
||||
<< " UTC to " << info.end << " UTC with the abbreviation "
|
||||
<< info.abbrev << '\n';
|
||||
}
|
||||
begin = info.end;
|
||||
} while (begin < end);
|
||||
}
|
||||
std::cout << z.name() << " has a daylight savings offset of "
|
||||
<< info.save.count() << "min from " << info.begin
|
||||
<< " UTC to " << info.end << " UTC with the abbreviation "
|
||||
<< info.abbrev << '\n';
|
||||
}
|
||||
begin = info.end;
|
||||
} while (begin < end);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
You first get a reference to the tz database, then iterate over each zone in the database. For each zone, set a range of time points to search over. In this example I start searching as far back as possible, and search forward to the year 2035.
|
||||
|
||||
Starting at the beginning of time, get an `sys_info` for that UTC `time_point`. An `sys_info` looks like this:
|
||||
|
||||
struct sys_info
|
||||
{
|
||||
second_point begin;
|
||||
second_point end;
|
||||
std::chrono::seconds offset;
|
||||
std::chrono::minutes save;
|
||||
std::string abbrev;
|
||||
};
|
||||
```c++
|
||||
struct sys_info
|
||||
{
|
||||
second_point begin;
|
||||
second_point end;
|
||||
std::chrono::seconds offset;
|
||||
std::chrono::minutes save;
|
||||
std::string abbrev;
|
||||
};
|
||||
```
|
||||
|
||||
Each time zone transition happens at `begin` (UTC). The total offset from UTC for this timezone and period is `offset`. This `offset` will be in effect until `end` (UTC). The difference between this period's "normal" `offset`, and this `offset` is `save`. And this period's timezone abbreviation is `abbrev`.
|
||||
|
||||
@@ -1110,32 +1114,34 @@ Sample output of this program:
|
||||
|
||||
Ever wonder how the global use of daylight saving time is trending with time? Here's one way to find out:
|
||||
|
||||
#include "tz.h"
|
||||
#include <iostream>
|
||||
```c++
|
||||
#include "tz.h"
|
||||
#include <iostream>
|
||||
|
||||
int
|
||||
main()
|
||||
int
|
||||
main()
|
||||
{
|
||||
using namespace date;
|
||||
using namespace std::chrono_literals;
|
||||
auto& db = get_tzdb();
|
||||
std::cout << db.version << '\n';
|
||||
for (auto y = 1850_y; y < 2020_y; ++y)
|
||||
{
|
||||
using namespace date;
|
||||
using namespace std::chrono_literals;
|
||||
auto& db = get_tzdb();
|
||||
std::cout << db.version << '\n';
|
||||
for (auto y = 1850_y; y < 2020_y; ++y)
|
||||
auto use_daylight = 0;
|
||||
auto total = 0;
|
||||
for (auto& z : db.zones)
|
||||
{
|
||||
auto use_daylight = 0;
|
||||
auto total = 0;
|
||||
for (auto& z : db.zones)
|
||||
{
|
||||
++total;
|
||||
auto info1 = z.get_info(sys_days{y/jan/15});
|
||||
auto info2 = z.get_info(sys_days{y/jul/15});
|
||||
if (info1.save != 0min || info2.save != 0min)
|
||||
++use_daylight;
|
||||
}
|
||||
std::cout << y << " : " << use_daylight << '/' << total
|
||||
<< " = " << static_cast<float>(use_daylight)/total << '\n';
|
||||
++total;
|
||||
auto info1 = z.get_info(sys_days{y/jan/15});
|
||||
auto info2 = z.get_info(sys_days{y/jul/15});
|
||||
if (info1.save != 0min || info2.save != 0min)
|
||||
++use_daylight;
|
||||
}
|
||||
std::cout << y << " : " << use_daylight << '/' << total
|
||||
<< " = " << static_cast<float>(use_daylight)/total << '\n';
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
This code loops over a wide range of years, and then for each year, loops over all timezones in the database, and for each timezone, detects whether it is switching back and forth between standard time and daylight time for that year. The switch detection is rather crude, but you can make this detection as elaborate as you want. Currently it picks two dates 6 months apart which are unlikely to both be using standard time if the zone is using daylight saving time that year. For each of those two dates, the `sys_info.save` member is checked. If either `save != 0min`, daylight saving is in use that year.
|
||||
|
||||
@@ -1155,12 +1161,16 @@ This all means that subtracting two `sys_time` time points can give a different
|
||||
|
||||
First of all we need to get the `gps_time` epoch and the `sys_time` epoch. We do not even need to know the dates of these epochs. We can just use `0`:
|
||||
|
||||
auto gps_epoch = gps_seconds{0s}; // 1980-01-06 00:00:00 UTC
|
||||
auto unix_epoch = sys_seconds{0s}; // 1970-01-01 00:00:00 UTC
|
||||
```c++
|
||||
auto gps_epoch = gps_seconds{0s}; // 1980-01-06 00:00:00 UTC
|
||||
auto unix_epoch = sys_seconds{0s}; // 1970-01-01 00:00:00 UTC
|
||||
```
|
||||
|
||||
These are both `std::chrono::time_point`s, but if we try to subtract them we will get a compile-time error because they refer to different clocks. So to subtract them we must convert one to the other prior to subtracting. Here is one way to do this:
|
||||
|
||||
std::cout << (gps_epoch - to_gps_time(unix_epoch)).count() << "s\n";
|
||||
```c++
|
||||
std::cout << (gps_epoch - to_gps_time(unix_epoch)).count() << "s\n";
|
||||
```
|
||||
|
||||
This converts the `sys_time` to a `gps_time`, does the subtraction, and outputs:
|
||||
|
||||
@@ -1170,7 +1180,9 @@ In `gps_time`, there are 315,964,809 seconds between these two epochs.
|
||||
|
||||
Here is another way:
|
||||
|
||||
std::cout << (to_sys_time(gps_epoch) - unix_epoch).count() << "s\n";
|
||||
```c++
|
||||
std::cout << (to_sys_time(gps_epoch) - unix_epoch).count() << "s\n";
|
||||
```
|
||||
|
||||
which outputs:
|
||||
|
||||
@@ -1208,41 +1220,47 @@ We'll provide two bidirectional conversions:
|
||||
|
||||
The `TDate`/`sys_days` conversions don't have to worry about the fractional day issue for negative `TDateTime` values, and so they are both easier and more efficient:
|
||||
|
||||
date::sys_days
|
||||
to_sys_days(System::TDate td)
|
||||
{
|
||||
using namespace date;
|
||||
return sys_days(days{static_cast<int>(td)} -
|
||||
(sys_days{1970_y/jan/1} - sys_days{1899_y/dec/30}));
|
||||
}
|
||||
```c++
|
||||
date::sys_days
|
||||
to_sys_days(System::TDate td)
|
||||
{
|
||||
using namespace date;
|
||||
return sys_days(days{static_cast<int>(td)} -
|
||||
(sys_days{1970_y/jan/1} - sys_days{1899_y/dec/30}));
|
||||
}
|
||||
```
|
||||
|
||||
The only thing to do here is to extract the integral value from the `TDate`, convert that into `sys_days` and subtract the difference between the two epochs. The reverse conversion is just as easy:
|
||||
|
||||
System::TDate
|
||||
to_TDate(date::sys_days sd)
|
||||
{
|
||||
using namespace date;
|
||||
return System::TDate(static_cast<int>((sd.time_since_epoch() +
|
||||
(sys_days{1970_y/jan/1} - sys_days{1899_y/dec/30})).count()));
|
||||
}
|
||||
```c++
|
||||
System::TDate
|
||||
to_TDate(date::sys_days sd)
|
||||
{
|
||||
using namespace date;
|
||||
return System::TDate(static_cast<int>((sd.time_since_epoch() +
|
||||
(sys_days{1970_y/jan/1} - sys_days{1899_y/dec/30})).count()));
|
||||
}
|
||||
```
|
||||
|
||||
These can be exercised like this:
|
||||
|
||||
int
|
||||
main()
|
||||
{
|
||||
using namespace date;
|
||||
using namespace System;
|
||||
std::cout << to_sys_days(TDate{0}) << '\n';
|
||||
std::cout << to_sys_days(TDate{2}) << '\n';
|
||||
std::cout << to_sys_days(TDate{-1}) << '\n';
|
||||
std::cout << to_sys_days(TDate{35065}) << '\n';
|
||||
```c++
|
||||
int
|
||||
main()
|
||||
{
|
||||
using namespace date;
|
||||
using namespace System;
|
||||
std::cout << to_sys_days(TDate{0}) << '\n';
|
||||
std::cout << to_sys_days(TDate{2}) << '\n';
|
||||
std::cout << to_sys_days(TDate{-1}) << '\n';
|
||||
std::cout << to_sys_days(TDate{35065}) << '\n';
|
||||
|
||||
std::cout << (int)to_TDate(1899_y/dec/30) << '\n';
|
||||
std::cout << (int)to_TDate(1900_y/jan/1) << '\n';
|
||||
std::cout << (int)to_TDate(1899_y/dec/29) << '\n';
|
||||
std::cout << (int)to_TDate(1996_y/jan/1) << '\n';
|
||||
}
|
||||
std::cout << (int)to_TDate(1899_y/dec/30) << '\n';
|
||||
std::cout << (int)to_TDate(1900_y/jan/1) << '\n';
|
||||
std::cout << (int)to_TDate(1899_y/dec/29) << '\n';
|
||||
std::cout << (int)to_TDate(1996_y/jan/1) << '\n';
|
||||
}
|
||||
```
|
||||
|
||||
which outputs:
|
||||
|
||||
@@ -1259,32 +1277,38 @@ This output is consistent with what is in the `TDateTime` documentation.
|
||||
|
||||
For converting to/from `TDateTime` it is convenient to allow the client to choose the precision of the `sys_time` to convert to or from. For example this converts to a precision of `minutes`:
|
||||
|
||||
to_sys_time<minutes>(TDateTime{2.75})
|
||||
```c++
|
||||
to_sys_time<minutes>(TDateTime{2.75})
|
||||
```
|
||||
|
||||
while this converts to a precision of `milliseconds`:
|
||||
|
||||
to_sys_time<milliseconds>(TDateTime{2.75})
|
||||
```c++
|
||||
to_sys_time<milliseconds>(TDateTime{2.75})
|
||||
```
|
||||
|
||||
Here's the implementation:
|
||||
|
||||
template <class D>
|
||||
date::sys_time<D>
|
||||
to_sys_time(System::TDateTime dt)
|
||||
```c++
|
||||
template <class D>
|
||||
date::sys_time<D>
|
||||
to_sys_time(System::TDateTime dt)
|
||||
{
|
||||
using namespace date;
|
||||
using namespace std::chrono;
|
||||
using fdays = duration<double, days::period>;
|
||||
using ftime = time_point<system_clock, fdays>;
|
||||
auto ft = ftime{fdays{static_cast<double>(dt)}};
|
||||
if (ft < ftime{})
|
||||
{
|
||||
using namespace date;
|
||||
using namespace std::chrono;
|
||||
using fdays = duration<double, days::period>;
|
||||
using ftime = time_point<system_clock, fdays>;
|
||||
auto ft = ftime{fdays{static_cast<double>(dt)}};
|
||||
if (ft < ftime{})
|
||||
{
|
||||
auto d = time_point_cast<days>(ft);
|
||||
auto t = d - ft;
|
||||
ft = d + t;
|
||||
}
|
||||
ft -= sys_days{1970_y/jan/1} - sys_days{1899_y/dec/30};
|
||||
return round<D>(ft);
|
||||
auto d = time_point_cast<days>(ft);
|
||||
auto t = d - ft;
|
||||
ft = d + t;
|
||||
}
|
||||
ft -= sys_days{1970_y/jan/1} - sys_days{1899_y/dec/30};
|
||||
return round<D>(ft);
|
||||
}
|
||||
```
|
||||
|
||||
For time points not prior to the `TDateTime` epoch, it is quite straightforward. It helps to create a `duration` and chrono `time_point` based on `double` that counts `days`. Then one simply extracts the `double` from the `TDateTime` and converts it to our double-based `time_point`, subtracts the difference in the epochs, and then uses the `round<D>` facility to truncate the result to the requested precision.
|
||||
|
||||
@@ -1292,40 +1316,44 @@ If this is a pre-epoch `TDateTime`, then there's an extra dance to treat the int
|
||||
|
||||
The reverse conversion is similar:
|
||||
|
||||
template <class D>
|
||||
System::TDateTime
|
||||
to_TDateTime(date::sys_time<D> tp)
|
||||
{
|
||||
using namespace date;
|
||||
using namespace std::chrono;
|
||||
using fdays = duration<double, days::period>;
|
||||
using ftime = time_point<system_clock, fdays>;
|
||||
auto ft = ftime{tp} + (sys_days{1970_y/jan/1} - sys_days{1899_y/dec/30});
|
||||
if (ft >= ftime{})
|
||||
return System::TDateTime(ft.time_since_epoch().count());
|
||||
auto d = floor<days>(ft);
|
||||
auto t = d - ft;
|
||||
return System::TDateTime((d + t).time_since_epoch().count());
|
||||
}
|
||||
```c++
|
||||
template <class D>
|
||||
System::TDateTime
|
||||
to_TDateTime(date::sys_time<D> tp)
|
||||
{
|
||||
using namespace date;
|
||||
using namespace std::chrono;
|
||||
using fdays = duration<double, days::period>;
|
||||
using ftime = time_point<system_clock, fdays>;
|
||||
auto ft = ftime{tp} + (sys_days{1970_y/jan/1} - sys_days{1899_y/dec/30});
|
||||
if (ft >= ftime{})
|
||||
return System::TDateTime(ft.time_since_epoch().count());
|
||||
auto d = floor<days>(ft);
|
||||
auto t = d - ft;
|
||||
return System::TDateTime((d + t).time_since_epoch().count());
|
||||
}
|
||||
```
|
||||
|
||||
This can all be exercised like this:
|
||||
|
||||
int
|
||||
main()
|
||||
{
|
||||
using namespace date;
|
||||
using namespace std::chrono;
|
||||
using namespace System;
|
||||
std::cout << to_sys_time<minutes>(TDateTime{0.}) << '\n';
|
||||
std::cout << to_sys_time<minutes>(TDateTime{2.75}) << '\n';
|
||||
std::cout << to_sys_time<minutes>(TDateTime{-1.25}) << '\n';
|
||||
std::cout << to_sys_time<minutes>(TDateTime{35065.}) << '\n';
|
||||
```c++
|
||||
int
|
||||
main()
|
||||
{
|
||||
using namespace date;
|
||||
using namespace std::chrono;
|
||||
using namespace System;
|
||||
std::cout << to_sys_time<minutes>(TDateTime{0.}) << '\n';
|
||||
std::cout << to_sys_time<minutes>(TDateTime{2.75}) << '\n';
|
||||
std::cout << to_sys_time<minutes>(TDateTime{-1.25}) << '\n';
|
||||
std::cout << to_sys_time<minutes>(TDateTime{35065.}) << '\n';
|
||||
|
||||
std::cout << (double)to_TDateTime(sys_days{1899_y/dec/30} + 0min) << '\n';
|
||||
std::cout << (double)to_TDateTime(sys_days{1900_y/jan/1} + 18h + 0min) << '\n';
|
||||
std::cout << (double)to_TDateTime(sys_days{1899_y/dec/29} + 6h + 0min) << '\n';
|
||||
std::cout << (double)to_TDateTime(sys_days{1996_y/jan/1} + 0min) << '\n';
|
||||
}
|
||||
std::cout << (double)to_TDateTime(sys_days{1899_y/dec/30} + 0min) << '\n';
|
||||
std::cout << (double)to_TDateTime(sys_days{1900_y/jan/1} + 18h + 0min) << '\n';
|
||||
std::cout << (double)to_TDateTime(sys_days{1899_y/dec/29} + 6h + 0min) << '\n';
|
||||
std::cout << (double)to_TDateTime(sys_days{1996_y/jan/1} + 0min) << '\n';
|
||||
}
|
||||
```
|
||||
|
||||
which outputs:
|
||||
|
||||
@@ -1344,21 +1372,23 @@ which outputs:
|
||||
|
||||
Here are functions you can use to convert between [`QDate`](http://doc.qt.io/qt-5/qdate.html) and `sys_days`:
|
||||
|
||||
date::sys_days
|
||||
to_sys_days(QDate qd)
|
||||
{
|
||||
using namespace date;
|
||||
return sys_days{days{qd.toJulianDay()} -
|
||||
(sys_days{1970_y/jan/1} - sys_days{year{-4713}/nov/24})};
|
||||
}
|
||||
```c++
|
||||
date::sys_days
|
||||
to_sys_days(QDate qd)
|
||||
{
|
||||
using namespace date;
|
||||
return sys_days{days{qd.toJulianDay()} -
|
||||
(sys_days{1970_y/jan/1} - sys_days{year{-4713}/nov/24})};
|
||||
}
|
||||
|
||||
QDate
|
||||
to_QDate(date::sys_days sd)
|
||||
{
|
||||
using namespace date;
|
||||
return QDate::fromJulianDay((sd.time_since_epoch() +
|
||||
(sys_days{1970_y/jan/1} - sys_days{year{-4713}/nov/24})).count());
|
||||
}
|
||||
QDate
|
||||
to_QDate(date::sys_days sd)
|
||||
{
|
||||
using namespace date;
|
||||
return QDate::fromJulianDay((sd.time_since_epoch() +
|
||||
(sys_days{1970_y/jan/1} - sys_days{year{-4713}/nov/24})).count());
|
||||
}
|
||||
```
|
||||
|
||||
These work by simply adjusting the epoch of these two types.
|
||||
|
||||
@@ -1370,35 +1400,37 @@ Here are functions and typedefs you can use to work with Windows' [FILETIME](htt
|
||||
|
||||
Assuming `system_clock` is based on the Unix epoch:
|
||||
|
||||
using std::ratio;
|
||||
using std::chrono::duration;
|
||||
using std::chrono::duration_cast;
|
||||
using std::chrono::system_clock;
|
||||
```c++
|
||||
using std::ratio;
|
||||
using std::chrono::duration;
|
||||
using std::chrono::duration_cast;
|
||||
using std::chrono::system_clock;
|
||||
|
||||
// filetime_duration has the same layout as FILETIME; 100ns intervals
|
||||
using filetime_duration = duration<int64_t, ratio<1, 10'000'000>>;
|
||||
// January 1, 1601 (NT epoch) - January 1, 1970 (Unix epoch):
|
||||
constexpr duration<int64_t> nt_to_unix_epoch{INT64_C(-11644473600)};
|
||||
// filetime_duration has the same layout as FILETIME; 100ns intervals
|
||||
using filetime_duration = duration<int64_t, ratio<1, 10'000'000>>;
|
||||
// January 1, 1601 (NT epoch) - January 1, 1970 (Unix epoch):
|
||||
constexpr duration<int64_t> nt_to_unix_epoch{INT64_C(-11644473600)};
|
||||
|
||||
system_clock::time_point FILETIME_to_system_clock(FILETIME fileTime) {
|
||||
const filetime_duration asDuration{static_cast<int64_t>(
|
||||
(static_cast<uint64_t>(fileTime.dwHighDateTime) << 32)
|
||||
| fileTime.dwLowDateTime)};
|
||||
const auto withUnixEpoch = asDuration + nt_to_unix_epoch;
|
||||
return system_clock::time_point{
|
||||
duration_cast<system_clock::duration>(withUnixEpoch)};
|
||||
}
|
||||
system_clock::time_point FILETIME_to_system_clock(FILETIME fileTime) {
|
||||
const filetime_duration asDuration{static_cast<int64_t>(
|
||||
(static_cast<uint64_t>(fileTime.dwHighDateTime) << 32)
|
||||
| fileTime.dwLowDateTime)};
|
||||
const auto withUnixEpoch = asDuration + nt_to_unix_epoch;
|
||||
return system_clock::time_point{
|
||||
duration_cast<system_clock::duration>(withUnixEpoch)};
|
||||
}
|
||||
|
||||
FILETIME system_clock_to_FILETIME(system_clock::time_point systemPoint) {
|
||||
const auto asDuration = duration_cast<filetime_duration>(
|
||||
systemPoint.time_since_epoch());
|
||||
const auto withNtEpoch = asDuration - nt_to_unix_epoch;
|
||||
const uint64_t rawCount = withNtEpoch.count();
|
||||
FILETIME result;
|
||||
result.dwLowDateTime = static_cast<DWORD>(rawCount); // discards upper bits
|
||||
result.dwHighDateTime = static_cast<DWORD>(rawCount >> 32);
|
||||
return result;
|
||||
}
|
||||
FILETIME system_clock_to_FILETIME(system_clock::time_point systemPoint) {
|
||||
const auto asDuration = duration_cast<filetime_duration>(
|
||||
systemPoint.time_since_epoch());
|
||||
const auto withNtEpoch = asDuration - nt_to_unix_epoch;
|
||||
const uint64_t rawCount = withNtEpoch.count();
|
||||
FILETIME result;
|
||||
result.dwLowDateTime = static_cast<DWORD>(rawCount); // discards upper bits
|
||||
result.dwHighDateTime = static_cast<DWORD>(rawCount >> 32);
|
||||
return result;
|
||||
}
|
||||
```
|
||||
|
||||
<a name="calendar"></a>
|
||||
### Print out a compact calendar for the year
|
||||
@@ -1408,135 +1440,137 @@ Printing out the calendar for an entire year is an interesting exercise. You ca
|
||||
|
||||
First the code, and then a detailed explanation:
|
||||
|
||||
#include "date.h"
|
||||
#include <algorithm>
|
||||
#include <iomanip>
|
||||
#include <iostream>
|
||||
#include <locale>
|
||||
#include <ostream>
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
```c++
|
||||
#include "date.h"
|
||||
#include <algorithm>
|
||||
#include <iomanip>
|
||||
#include <iostream>
|
||||
#include <locale>
|
||||
#include <ostream>
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
|
||||
date::year
|
||||
current_year()
|
||||
{
|
||||
using namespace std::chrono;
|
||||
using namespace date;
|
||||
year_month_day ymd = floor<days>(system_clock::now());
|
||||
return ymd.year();
|
||||
}
|
||||
date::year
|
||||
current_year()
|
||||
{
|
||||
using namespace std::chrono;
|
||||
using namespace date;
|
||||
year_month_day ymd = floor<days>(system_clock::now());
|
||||
return ymd.year();
|
||||
}
|
||||
|
||||
// The number of weeks in a calendar month layout plus 2 more for the calendar titles
|
||||
unsigned
|
||||
number_of_lines_calendar(date::year_month const ym, date::weekday const firstdow)
|
||||
{
|
||||
using namespace date;
|
||||
return static_cast<unsigned>(
|
||||
ceil<weeks>((weekday{ym/1} - firstdow) + ((ym/last).day() - day{0})).count()) + 2;
|
||||
}
|
||||
// The number of weeks in a calendar month layout plus 2 more for the calendar titles
|
||||
unsigned
|
||||
number_of_lines_calendar(date::year_month const ym, date::weekday const firstdow)
|
||||
{
|
||||
using namespace date;
|
||||
return static_cast<unsigned>(
|
||||
ceil<weeks>((weekday{ym/1} - firstdow) + ((ym/last).day() - day{0})).count()) + 2;
|
||||
}
|
||||
|
||||
// Print one line of a calendar month
|
||||
void
|
||||
print_line_of_calendar_month(std::ostream& os, date::year_month const ym,
|
||||
unsigned const line, date::weekday const firstdow)
|
||||
// Print one line of a calendar month
|
||||
void
|
||||
print_line_of_calendar_month(std::ostream& os, date::year_month const ym,
|
||||
unsigned const line, date::weekday const firstdow)
|
||||
{
|
||||
using namespace std;
|
||||
using namespace date;
|
||||
switch (line)
|
||||
{
|
||||
using namespace std;
|
||||
using namespace date;
|
||||
switch (line)
|
||||
case 0:
|
||||
// Output month and year title
|
||||
os << left << setw(21) << format(os.getloc(), " %B %Y", ym) << right;
|
||||
break;
|
||||
case 1:
|
||||
{
|
||||
case 0:
|
||||
// Output month and year title
|
||||
os << left << setw(21) << format(os.getloc(), " %B %Y", ym) << right;
|
||||
break;
|
||||
case 1:
|
||||
{
|
||||
// Output weekday names title
|
||||
auto wd = first dow;
|
||||
do
|
||||
{
|
||||
auto d = format(os.getloc(), "%a", wd);
|
||||
d.resize(2);
|
||||
os << ' ' << d;
|
||||
} while (++wd != firstdow);
|
||||
break;
|
||||
}
|
||||
case 2:
|
||||
{
|
||||
// Output first week prefixed with spaces if necessary
|
||||
auto wd = weekday{ym/1};
|
||||
os << string(static_cast<unsigned>((wd-firstdow).count())*3, ' ');
|
||||
auto d = 1_d;
|
||||
// Output weekday names title
|
||||
auto wd = first dow;
|
||||
do
|
||||
{
|
||||
auto d = format(os.getloc(), "%a", wd);
|
||||
d.resize(2);
|
||||
os << ' ' << d;
|
||||
} while (++wd != firstdow);
|
||||
break;
|
||||
}
|
||||
case 2:
|
||||
{
|
||||
// Output first week prefixed with spaces if necessary
|
||||
auto wd = weekday{ym/1};
|
||||
os << string(static_cast<unsigned>((wd-firstdow).count())*3, ' ');
|
||||
auto d = 1_d;
|
||||
do
|
||||
{
|
||||
os << format(" %e", d);
|
||||
++d;
|
||||
} while (++wd != firstdow);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
{
|
||||
// Output a non-first week:
|
||||
// First find first day of week
|
||||
unsigned index = line - 2;
|
||||
auto sd = sys_days{ym/1};
|
||||
if (weekday{sd} == firstdow)
|
||||
++index;
|
||||
auto ymdw = ym/firstdow[index];
|
||||
if (ymdw.ok()) // If this is a valid week, print it out
|
||||
{
|
||||
auto d = year_month_day{ymdw}.day();
|
||||
auto const e = (ym/last).day();
|
||||
auto wd = firstdow;
|
||||
do
|
||||
{
|
||||
os << format(" %e", d);
|
||||
++d;
|
||||
} while (++wd != firstdow);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
{
|
||||
// Output a non-first week:
|
||||
// First find first day of week
|
||||
unsigned index = line - 2;
|
||||
auto sd = sys_days{ym/1};
|
||||
if (weekday{sd} == firstdow)
|
||||
++index;
|
||||
auto ymdw = ym/firstdow[index];
|
||||
if (ymdw.ok()) // If this is a valid week, print it out
|
||||
{
|
||||
auto d = year_month_day{ymdw}.day();
|
||||
auto const e = (ym/last).day();
|
||||
auto wd = firstdow;
|
||||
do
|
||||
{
|
||||
os << format(" %e", d);
|
||||
} while (++wd != firstdow && ++d <= e);
|
||||
// Append row with spaces if the week did not complete
|
||||
os << string(static_cast<unsigned>((firstdow-wd).count())*3, ' ');
|
||||
}
|
||||
else // Otherwise not a valid week, output a blank row
|
||||
os << string(21, ' ');
|
||||
break;
|
||||
}
|
||||
} while (++wd != firstdow && ++d <= e);
|
||||
// Append row with spaces if the week did not complete
|
||||
os << string(static_cast<unsigned>((firstdow-wd).count())*3, ' ');
|
||||
}
|
||||
else // Otherwise not a valid week, output a blank row
|
||||
os << string(21, ' ');
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
print_calendar_year(std::ostream& os, unsigned const cols = 3,
|
||||
date::year const y = current_year(),
|
||||
date::weekday const firstdow = date::sun)
|
||||
void
|
||||
print_calendar_year(std::ostream& os, unsigned const cols = 3,
|
||||
date::year const y = current_year(),
|
||||
date::weekday const firstdow = date::sun)
|
||||
{
|
||||
using namespace date;
|
||||
if (cols == 0 || 12 % cols != 0)
|
||||
throw std::runtime_error("The number of columns " + std::to_string(cols)
|
||||
+ " must be one of [1, 2, 3, 4, 6, 12]");
|
||||
// Compute number of lines needed for each calendar month
|
||||
unsigned ml[12] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12};
|
||||
for (auto& m : ml)
|
||||
m = number_of_lines_calendar(y/month{m}, firstdow);
|
||||
for (auto r = 0u; r < 12/cols; ++r) // for each row
|
||||
{
|
||||
using namespace date;
|
||||
if (cols == 0 || 12 % cols != 0)
|
||||
throw std::runtime_error("The number of columns " + std::to_string(cols)
|
||||
+ " must be one of [1, 2, 3, 4, 6, 12]");
|
||||
// Compute number of lines needed for each calendar month
|
||||
unsigned ml[12] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12};
|
||||
for (auto& m : ml)
|
||||
m = number_of_lines_calendar(y/month{m}, firstdow);
|
||||
for (auto r = 0u; r < 12/cols; ++r) // for each row
|
||||
const auto lines = *std::max_element(std::begin(ml) + (r*cols),
|
||||
std::begin(ml) + ((r+1)*cols));
|
||||
for (auto l = 0u; l < lines; ++l) // for each line
|
||||
{
|
||||
const auto lines = *std::max_element(std::begin(ml) + (r*cols),
|
||||
std::begin(ml) + ((r+1)*cols));
|
||||
for (auto l = 0u; l < lines; ++l) // for each line
|
||||
for (auto c = 0u; c < cols; ++c) // for each column
|
||||
{
|
||||
for (auto c = 0u; c < cols; ++c) // for each column
|
||||
{
|
||||
if (c != 0)
|
||||
os << " ";
|
||||
print_line_of_calendar_month(os, y/month{r*cols + c+1}, l, firstdow);
|
||||
}
|
||||
os << '\n';
|
||||
if (c != 0)
|
||||
os << " ";
|
||||
print_line_of_calendar_month(os, y/month{r*cols + c+1}, l, firstdow);
|
||||
}
|
||||
os << '\n';
|
||||
}
|
||||
os << '\n';
|
||||
}
|
||||
}
|
||||
|
||||
int
|
||||
main()
|
||||
{
|
||||
print_calendar_year(std::cout);
|
||||
}
|
||||
int
|
||||
main()
|
||||
{
|
||||
print_calendar_year(std::cout);
|
||||
}
|
||||
```
|
||||
|
||||
As written this program outputs:
|
||||
|
||||
@@ -1577,13 +1611,17 @@ As written this program outputs:
|
||||
|
||||
But the above program is very flexible and can localize this output into a wide variety of formats to accommodate your preferences.
|
||||
|
||||
date::year
|
||||
current_year();
|
||||
```c++
|
||||
date::year
|
||||
current_year();
|
||||
```
|
||||
|
||||
All `current_year()` does is find the current `year`. The UTC time zone is used for simplicity. If this is not sufficient for your needs, it is easy enough to specify which year you want a calendar for. This works by calling `system_clock::now()`, truncating that result into `sys_days` (a count of days), converting the `sys_days` into a `year_month_day`, and returning the `year()` field of that.
|
||||
|
||||
unsigned
|
||||
number_of_lines_calendar(date::year_month const ym, date::weekday const firstdow);
|
||||
```c++
|
||||
unsigned
|
||||
number_of_lines_calendar(date::year_month const ym, date::weekday const firstdow);
|
||||
```
|
||||
|
||||
This function computes the number of lines that printing out the calendar will take for the year/month combination `ym` and using `firstdow` as the first day of the week for that calendar. The first thing to compute is the number of days the first of the month is past the first day of the week: `(weekday{ym/1} - firstdow)`. Week day subtraction is unsigned modulo 7, so this is _always_ a positive number in the range [0, 6], no matter the underlying encoding of `weekday`.
|
||||
|
||||
@@ -1591,9 +1629,11 @@ Add to that the number of days in the month. This is computed by `((ym/last).da
|
||||
|
||||
Next we want to convert this number of `days` into `weeks` using `ceil` which will round up if the conversion is not exact. This allows for months that don't completely fill out their last row. We then extract the number of weeks with `.count()` and add `2` more lines: One for the day-of-the-week title, and one for the month year title.
|
||||
|
||||
void
|
||||
print_line_of_calendar_month(std::ostream& os, date::year_month const ym,
|
||||
unsigned const line, date::weekday const firstdow);
|
||||
```c++
|
||||
void
|
||||
print_line_of_calendar_month(std::ostream& os, date::year_month const ym,
|
||||
unsigned const line, date::weekday const firstdow);
|
||||
```
|
||||
|
||||
This is the heart of the calendar-printing logic. This prints _one_ line of a month calendar, with no line break afterwards. The argument `line` says which line [0, infinity]. If more lines are asked for than the calendar takes up, blank lines are printed. The calendar starts with the `weekday firstdow`. The entire function is just a switch on `line` to see which line to print out:
|
||||
|
||||
@@ -1611,10 +1651,12 @@ Finally we check if we output a full week, and if not, how many days for the wee
|
||||
|
||||
And that concludes the hardest part of the hardest function for this entire utility!
|
||||
|
||||
void
|
||||
print_calendar_year(std::ostream& os, unsigned const cols = 3,
|
||||
date::year const y = current_year(),
|
||||
date::weekday const firstdow = date::sun);
|
||||
```c++
|
||||
void
|
||||
print_calendar_year(std::ostream& os, unsigned const cols = 3,
|
||||
date::year const y = current_year(),
|
||||
date::weekday const firstdow = date::sun);
|
||||
```
|
||||
|
||||
This function prints the yearly calendar to `os` by calling the functions we've already defined. The calendar is in a format of `cols` by `rows` months, where `cols` is input by the client and represents how many months you want to print out horizontally. This argument must be one of `[1, 2, 3, 4, 6, 12]`. The example output above defaulted this to 3 months across by 4 down. The year can be input, or defaults to the current year UTC. And the day of the week which the calendar starts with can be specified and defaults to Sunday.
|
||||
|
||||
@@ -1628,9 +1670,11 @@ Now just print out one line for each calendar for this `{row, col, line}` combin
|
||||
|
||||
This program can now be used to localize and output a wide variety of calendars. For example is a German calendar in a 4x3 output for the year 2016 (output on macOS which supports this localization):
|
||||
|
||||
using namespace date::literals;
|
||||
std::cout.imbue(std::locale("de_DE"));
|
||||
print_calendar_year(std::cout, 4, 2016_y, mon);
|
||||
```c++
|
||||
using namespace date::literals;
|
||||
std::cout.imbue(std::locale("de_DE"));
|
||||
print_calendar_year(std::cout, 4, 2016_y, mon);
|
||||
```
|
||||
|
||||
which outputs:
|
||||
|
||||
|
Reference in New Issue
Block a user