commit 796448e4ad5cad30cf638c0d6a0c72e3c76a50c3 Author: Howard Hinnant Date: Fri Jul 17 22:30:53 2015 -0400 Initial commit diff --git a/date.h b/date.h new file mode 100644 index 0000000..e672377 --- /dev/null +++ b/date.h @@ -0,0 +1,3405 @@ +#ifndef DATE_H +#define DATE_H + +// Howard Hinnant +// This work is licensed under a Creative Commons Attribution 4.0 International License. +// http://creativecommons.org/licenses/by/4.0/ + +#include +#if !(__cplusplus >= 201402) +# include +#endif +#include +#include +#include +#include + +namespace date +{ + +//---------------+ +// Configuration | +//---------------+ + +#if __cplusplus >= 201402 +# define CONSTDATA constexpr +# define CONSTCD11 constexpr +# define CONSTCD14 constexpr +#else +# define CONSTDATA constexpr +# define CONSTCD11 constexpr +# define CONSTCD14 +#endif + +//-----------+ +// Interface | +//-----------+ + +// durations + +using days = std::chrono::duration + , std::chrono::hours::period>>; + +using weeks = std::chrono::duration + , days::period>>; + +using years = std::chrono::duration + , days::period>>; + +using months = std::chrono::duration + >>; + +// time_point + +using day_point = std::chrono::time_point; + +// types + +CONSTDATA struct last_spec {} last{}; + +class day; +class month; +class year; + +class weekday; +class weekday_indexed; +class weekday_last; + +class month_day; +class month_day_last; +class month_weekday; +class month_weekday_last; + +class year_month; + +class year_month_day; +class year_month_day_last; +class year_month_weekday; +class year_month_weekday_last; + +// date composition operators + +CONSTCD11 year_month operator/(const year& y, const month& m) noexcept; +CONSTCD11 year_month operator/(const year& y, int m) noexcept; + +CONSTCD11 month_day operator/(const day& d, const month& m) noexcept; +CONSTCD11 month_day operator/(const day& d, int m) noexcept; +CONSTCD11 month_day operator/(const month& m, const day& d) noexcept; +CONSTCD11 month_day operator/(const month& m, int d) noexcept; +CONSTCD11 month_day operator/(int m, const day& d) noexcept; + +CONSTCD11 month_day_last operator/(const month& m, last_spec) noexcept; +CONSTCD11 month_day_last operator/(int m, last_spec) noexcept; +CONSTCD11 month_day_last operator/(last_spec, const month& m) noexcept; +CONSTCD11 month_day_last operator/(last_spec, int m) noexcept; + +CONSTCD11 month_weekday operator/(const month& m, const weekday_indexed& wdi) noexcept; +CONSTCD11 month_weekday operator/(int m, const weekday_indexed& wdi) noexcept; +CONSTCD11 month_weekday operator/(const weekday_indexed& wdi, const month& m) noexcept; +CONSTCD11 month_weekday operator/(const weekday_indexed& wdi, int m) noexcept; + +CONSTCD11 month_weekday_last operator/(const month& m, const weekday_last& wdl) noexcept; +CONSTCD11 month_weekday_last operator/(int m, const weekday_last& wdl) noexcept; +CONSTCD11 month_weekday_last operator/(const weekday_last& wdl, const month& m) noexcept; +CONSTCD11 month_weekday_last operator/(const weekday_last& wdl, int m) noexcept; + +CONSTCD11 year_month_day operator/(const year_month& ym, const day& d) noexcept; +CONSTCD11 year_month_day operator/(const year_month& ym, int d) noexcept; +CONSTCD11 year_month_day operator/(const year& y, const month_day& md) noexcept; +CONSTCD11 year_month_day operator/(int y, const month_day& md) noexcept; +CONSTCD11 year_month_day operator/(const month_day& md, const year& y) noexcept; +CONSTCD11 year_month_day operator/(const month_day& md, int y) noexcept; + +CONSTCD11 + year_month_day_last operator/(const year_month& ym, last_spec) noexcept; +CONSTCD11 + year_month_day_last operator/(const year& y, const month_day_last& mdl) noexcept; +CONSTCD11 + year_month_day_last operator/(int y, const month_day_last& mdl) noexcept; +CONSTCD11 + year_month_day_last operator/(const month_day_last& mdl, const year& y) noexcept; +CONSTCD11 + year_month_day_last operator/(const month_day_last& mdl, int y) noexcept; + +CONSTCD11 +year_month_weekday +operator/(const year_month& ym, const weekday_indexed& wdi) noexcept; + +CONSTCD11 +year_month_weekday +operator/(const year& y, const month_weekday& mwd) noexcept; + +CONSTCD11 +year_month_weekday +operator/(int y, const month_weekday& mwd) noexcept; + +CONSTCD11 +year_month_weekday +operator/(const month_weekday& mwd, const year& y) noexcept; + +CONSTCD11 +year_month_weekday +operator/(const month_weekday& mwd, int y) noexcept; + +CONSTCD11 +year_month_weekday_last +operator/(const year_month& ym, const weekday_last& wdl) noexcept; + +CONSTCD11 +year_month_weekday_last +operator/(const year& y, const month_weekday_last& mwdl) noexcept; + +CONSTCD11 +year_month_weekday_last +operator/(int y, const month_weekday_last& mwdl) noexcept; + +CONSTCD11 +year_month_weekday_last +operator/(const month_weekday_last& mwdl, const year& y) noexcept; + +CONSTCD11 +year_month_weekday_last +operator/(const month_weekday_last& mwdl, int y) noexcept; + +// Detailed interface + +// day + +class day +{ + unsigned char d_; +public: + explicit CONSTCD11 day(unsigned d) noexcept; + + day& operator++() noexcept; + day operator++(int) noexcept; + day& operator--() noexcept; + day operator--(int) noexcept; + + day& operator+=(const days& d) noexcept; + day& operator-=(const days& d) noexcept; + + CONSTCD11 explicit operator unsigned() const noexcept; + CONSTCD11 bool ok() const noexcept; +}; + +CONSTCD11 bool operator==(const day& x, const day& y) noexcept; +CONSTCD11 bool operator!=(const day& x, const day& y) noexcept; +CONSTCD11 bool operator< (const day& x, const day& y) noexcept; +CONSTCD11 bool operator> (const day& x, const day& y) noexcept; +CONSTCD11 bool operator<=(const day& x, const day& y) noexcept; +CONSTCD11 bool operator>=(const day& x, const day& y) noexcept; + +CONSTCD11 day operator+(const day& x, const days& y) noexcept; +CONSTCD11 day operator+(const days& x, const day& y) noexcept; +CONSTCD11 day operator-(const day& x, const days& y) noexcept; +CONSTCD11 days operator-(const day& x, const day& y) noexcept; + +CONSTCD11 day operator "" _d(unsigned long long d) noexcept; +std::ostream& operator<<(std::ostream& os, const day& d); + +// month + +class month +{ + unsigned char m_; +public: + explicit CONSTCD11 month(unsigned m) noexcept; + + month& operator++() noexcept; + month operator++(int) noexcept; + month& operator--() noexcept; + month operator--(int) noexcept; + + month& operator+=(const months& m) noexcept; + month& operator-=(const months& m) noexcept; + + CONSTCD11 explicit operator unsigned() const noexcept; + CONSTCD11 bool ok() const noexcept; +}; + +CONSTCD11 bool operator==(const month& x, const month& y) noexcept; +CONSTCD11 bool operator!=(const month& x, const month& y) noexcept; +CONSTCD11 bool operator< (const month& x, const month& y) noexcept; +CONSTCD11 bool operator> (const month& x, const month& y) noexcept; +CONSTCD11 bool operator<=(const month& x, const month& y) noexcept; +CONSTCD11 bool operator>=(const month& x, const month& y) noexcept; + +CONSTCD14 month operator+(const month& x, const months& y) noexcept; +CONSTCD14 month operator+(const months& x, const month& y) noexcept; +CONSTCD14 month operator-(const month& x, const months& y) noexcept; +CONSTCD14 months operator-(const month& x, const month& y) noexcept; + +std::ostream& operator<<(std::ostream& os, const month& m); + +// constexpr month jan{1}; +// constexpr month feb{2}; +// constexpr month mar{3}; +// constexpr month apr{4}; +// constexpr month may{5}; +// constexpr month jun{6}; +// constexpr month jul{7}; +// constexpr month aug{8}; +// constexpr month sep{9}; +// constexpr month oct{10}; +// constexpr month nov{11}; +// constexpr month dec{12}; + +// year + +class year +{ + short y_; +public: + explicit CONSTCD11 year(int y) noexcept; + + year& operator++() noexcept; + year operator++(int) noexcept; + year& operator--() noexcept; + year operator--(int) noexcept; + + year& operator+=(const years& y) noexcept; + year& operator-=(const years& y) noexcept; + + CONSTCD11 bool is_leap() const noexcept; + + CONSTCD11 explicit operator int() const noexcept; + CONSTCD11 bool ok() const noexcept; + + static CONSTCD11 year min() noexcept; + static CONSTCD11 year max() noexcept; +}; + +CONSTCD11 bool operator==(const year& x, const year& y) noexcept; +CONSTCD11 bool operator!=(const year& x, const year& y) noexcept; +CONSTCD11 bool operator< (const year& x, const year& y) noexcept; +CONSTCD11 bool operator> (const year& x, const year& y) noexcept; +CONSTCD11 bool operator<=(const year& x, const year& y) noexcept; +CONSTCD11 bool operator>=(const year& x, const year& y) noexcept; + +CONSTCD11 year operator+(const year& x, const years& y) noexcept; +CONSTCD11 year operator+(const years& x, const year& y) noexcept; +CONSTCD11 year operator-(const year& x, const years& y) noexcept; +CONSTCD11 years operator-(const year& x, const year& y) noexcept; + +CONSTCD11 year operator "" _y(unsigned long long y) noexcept; +std::ostream& operator<<(std::ostream& os, const year& y); + +// weekday + +class weekday +{ + unsigned char wd_; +public: + explicit CONSTCD11 weekday(unsigned wd) noexcept; + CONSTCD11 weekday(const day_point& dp) noexcept; + + weekday& operator++() noexcept; + weekday operator++(int) noexcept; + weekday& operator--() noexcept; + weekday operator--(int) noexcept; + + weekday& operator+=(const days& d) noexcept; + weekday& operator-=(const days& d) noexcept; + + CONSTCD11 explicit operator unsigned() const noexcept; + CONSTCD11 bool ok() const noexcept; + + CONSTCD11 weekday_indexed operator[](unsigned index) const noexcept; + CONSTCD11 weekday_last operator[](last_spec) const noexcept; + +private: + static CONSTCD11 unsigned char weekday_from_days(int z) noexcept; +}; + +CONSTCD11 bool operator==(const weekday& x, const weekday& y) noexcept; +CONSTCD11 bool operator!=(const weekday& x, const weekday& y) noexcept; + +CONSTCD14 weekday operator+(const weekday& x, const days& y) noexcept; +CONSTCD14 weekday operator+(const days& x, const weekday& y) noexcept; +CONSTCD14 weekday operator-(const weekday& x, const days& y) noexcept; +CONSTCD14 days operator-(const weekday& x, const weekday& y) noexcept; + +std::ostream& operator<<(std::ostream& os, const weekday& wd); + +// constexpr weekday sun{0}; +// constexpr weekday mon{1}; +// constexpr weekday tue{2}; +// constexpr weekday wed{3}; +// constexpr weekday thu{4}; +// constexpr weekday fri{5}; +// constexpr weekday sat{6}; + +// weekday_indexed + +class weekday_indexed +{ + unsigned char wd_ : 4; + unsigned char index_ : 4; + +public: + CONSTCD11 weekday_indexed(const date::weekday& wd, unsigned index) noexcept; + + CONSTCD11 date::weekday weekday() const noexcept; + CONSTCD11 unsigned index() const noexcept; + CONSTCD11 bool ok() const noexcept; +}; + +CONSTCD11 bool operator==(const weekday_indexed& x, const weekday_indexed& y) noexcept; +CONSTCD11 bool operator!=(const weekday_indexed& x, const weekday_indexed& y) noexcept; + +std::ostream& operator<<(std::ostream& os, const weekday_indexed& wdi); + +// weekday_last + +class weekday_last +{ + date::weekday wd_; + +public: + explicit CONSTCD11 weekday_last(const date::weekday& wd) noexcept; + + CONSTCD11 date::weekday weekday() const noexcept; + CONSTCD11 bool ok() const noexcept; +}; + +CONSTCD11 bool operator==(const weekday_last& x, const weekday_last& y) noexcept; +CONSTCD11 bool operator!=(const weekday_last& x, const weekday_last& y) noexcept; + +std::ostream& operator<<(std::ostream& os, const weekday_last& wdl); + +// year_month + +class year_month +{ + date::year y_; + date::month m_; + +public: + CONSTCD11 year_month(const date::year& y, const date::month& m) noexcept; + + CONSTCD11 date::year year() const noexcept; + CONSTCD11 date::month month() const noexcept; + + CONSTCD11 bool ok() const noexcept; +}; + +CONSTCD11 bool operator==(const year_month& x, const year_month& y) noexcept; +CONSTCD11 bool operator!=(const year_month& x, const year_month& y) noexcept; +CONSTCD11 bool operator< (const year_month& x, const year_month& y) noexcept; +CONSTCD11 bool operator> (const year_month& x, const year_month& y) noexcept; +CONSTCD11 bool operator<=(const year_month& x, const year_month& y) noexcept; +CONSTCD11 bool operator>=(const year_month& x, const year_month& y) noexcept; + +CONSTCD14 year_month operator+(const year_month& ym, const months& dm) noexcept; +CONSTCD14 year_month operator+(const months& dm, const year_month& ym) noexcept; +CONSTCD14 year_month operator-(const year_month& ym, const months& dm) noexcept; + +CONSTCD11 months operator-(const year_month& x, const year_month& y) noexcept; +CONSTCD11 year_month operator+(const year_month& ym, const years& dy) noexcept; +CONSTCD11 year_month operator+(const years& dy, const year_month& ym) noexcept; +CONSTCD11 year_month operator-(const year_month& ym, const years& dy) noexcept; + +std::ostream& operator<<(std::ostream& os, const year_month& ym); + +// month_day + +class month_day +{ + date::month m_; + date::day d_; + +public: + CONSTCD11 month_day(const date::month& m, const date::day& d) noexcept; + + CONSTCD11 date::month month() const noexcept; + CONSTCD11 date::day day() const noexcept; + + CONSTCD14 bool ok() const noexcept; +}; + +CONSTCD11 bool operator==(const month_day& x, const month_day& y) noexcept; +CONSTCD11 bool operator!=(const month_day& x, const month_day& y) noexcept; +CONSTCD11 bool operator< (const month_day& x, const month_day& y) noexcept; +CONSTCD11 bool operator> (const month_day& x, const month_day& y) noexcept; +CONSTCD11 bool operator<=(const month_day& x, const month_day& y) noexcept; +CONSTCD11 bool operator>=(const month_day& x, const month_day& y) noexcept; + +std::ostream& operator<<(std::ostream& os, const month_day& md); + +// month_day_last + +class month_day_last +{ + date::month m_; + +public: + CONSTCD11 explicit month_day_last(const date::month& m) noexcept; + + CONSTCD11 date::month month() const noexcept; + CONSTCD11 bool ok() const noexcept; +}; + +CONSTCD11 bool operator==(const month_day_last& x, const month_day_last& y) noexcept; +CONSTCD11 bool operator!=(const month_day_last& x, const month_day_last& y) noexcept; +CONSTCD11 bool operator< (const month_day_last& x, const month_day_last& y) noexcept; +CONSTCD11 bool operator> (const month_day_last& x, const month_day_last& y) noexcept; +CONSTCD11 bool operator<=(const month_day_last& x, const month_day_last& y) noexcept; +CONSTCD11 bool operator>=(const month_day_last& x, const month_day_last& y) noexcept; + +std::ostream& operator<<(std::ostream& os, const month_day_last& mdl); + +// month_weekday + +class month_weekday +{ + date::month m_; + date::weekday_indexed wdi_; +public: + CONSTCD11 month_weekday(const date::month& m, + const date::weekday_indexed& wdi) noexcept; + + CONSTCD11 date::month month() const noexcept; + CONSTCD11 date::weekday_indexed weekday_indexed() const noexcept; + + CONSTCD11 bool ok() const noexcept; +}; + +CONSTCD11 bool operator==(const month_weekday& x, const month_weekday& y) noexcept; +CONSTCD11 bool operator!=(const month_weekday& x, const month_weekday& y) noexcept; + +std::ostream& operator<<(std::ostream& os, const month_weekday& mwd); + +// month_weekday_last + +class month_weekday_last +{ + date::month m_; + date::weekday wd_; + +public: + CONSTCD11 month_weekday_last(const date::month& m, const date::weekday& wd) noexcept; + + CONSTCD11 date::month month() const noexcept; + CONSTCD11 date::weekday weekday() const noexcept; + + CONSTCD11 bool ok() const noexcept; +}; + +CONSTCD11 + bool operator==(const month_weekday_last& x, const month_weekday_last& y) noexcept; +CONSTCD11 + bool operator!=(const month_weekday_last& x, const month_weekday_last& y) noexcept; + +std::ostream& operator<<(std::ostream& os, const month_weekday_last& mwdl); + +// class year_month_day + +class year_month_day +{ + date::year y_; + date::month m_; + date::day d_; + +public: + CONSTCD11 year_month_day(const date::year& y, const date::month& m, + const date::day& d) noexcept; + CONSTCD14 year_month_day(const year_month_day_last& ymdl) noexcept; + CONSTCD14 year_month_day(const day_point& dp) noexcept; + + year_month_day& operator+=(const months& m) noexcept; + year_month_day& operator-=(const months& m) noexcept; + year_month_day& operator+=(const years& y) noexcept; + year_month_day& operator-=(const years& y) noexcept; + + CONSTCD11 date::year year() const noexcept; + CONSTCD11 date::month month() const noexcept; + CONSTCD11 date::day day() const noexcept; + + CONSTCD14 operator day_point() const noexcept; + CONSTCD14 bool ok() const noexcept; + +private: + static CONSTCD14 year_month_day from_day_point(const day_point& dp) noexcept; +}; + +CONSTCD11 bool operator==(const year_month_day& x, const year_month_day& y) noexcept; +CONSTCD11 bool operator!=(const year_month_day& x, const year_month_day& y) noexcept; +CONSTCD11 bool operator< (const year_month_day& x, const year_month_day& y) noexcept; +CONSTCD11 bool operator> (const year_month_day& x, const year_month_day& y) noexcept; +CONSTCD11 bool operator<=(const year_month_day& x, const year_month_day& y) noexcept; +CONSTCD11 bool operator>=(const year_month_day& x, const year_month_day& y) noexcept; + +CONSTCD14 year_month_day operator+(const year_month_day& ymd, const months& dm) noexcept; +CONSTCD14 year_month_day operator+(const months& dm, const year_month_day& ymd) noexcept; +CONSTCD14 year_month_day operator-(const year_month_day& ymd, const months& dm) noexcept; +CONSTCD11 year_month_day operator+(const year_month_day& ymd, const years& dy) noexcept; +CONSTCD11 year_month_day operator+(const years& dy, const year_month_day& ymd) noexcept; +CONSTCD11 year_month_day operator-(const year_month_day& ymd, const years& dy) noexcept; + +std::ostream& operator<<(std::ostream& os, const year_month_day& ymd); + +// year_month_day_last + +class year_month_day_last +{ + date::year y_; + date::month m_; + +public: + CONSTCD11 year_month_day_last(const date::year& y, const date::month& m) noexcept; + + year_month_day_last& operator+=(const months& m) noexcept; + year_month_day_last& operator-=(const months& m) noexcept; + year_month_day_last& operator+=(const years& y) noexcept; + year_month_day_last& operator-=(const years& y) noexcept; + + CONSTCD11 date::year year() const noexcept; + CONSTCD11 date::month month() const noexcept; + CONSTCD14 date::day day() const noexcept; + + CONSTCD14 operator day_point() const noexcept; + CONSTCD11 bool ok() const noexcept; +}; + +CONSTCD11 + bool operator==(const year_month_day_last& x, const year_month_day_last& y) noexcept; +CONSTCD11 + bool operator!=(const year_month_day_last& x, const year_month_day_last& y) noexcept; +CONSTCD11 + bool operator< (const year_month_day_last& x, const year_month_day_last& y) noexcept; +CONSTCD11 + bool operator> (const year_month_day_last& x, const year_month_day_last& y) noexcept; +CONSTCD11 + bool operator<=(const year_month_day_last& x, const year_month_day_last& y) noexcept; +CONSTCD11 + bool operator>=(const year_month_day_last& x, const year_month_day_last& y) noexcept; + +CONSTCD14 +year_month_day_last +operator+(const year_month_day_last& ymdl, const months& dm) noexcept; + +CONSTCD14 +year_month_day_last +operator+(const months& dm, const year_month_day_last& ymdl) noexcept; + +CONSTCD11 +year_month_day_last +operator+(const year_month_day_last& ymdl, const years& dy) noexcept; + +CONSTCD11 +year_month_day_last +operator+(const years& dy, const year_month_day_last& ymdl) noexcept; + +CONSTCD14 +year_month_day_last +operator-(const year_month_day_last& ymdl, const months& dm) noexcept; + +CONSTCD11 +year_month_day_last +operator-(const year_month_day_last& ymdl, const years& dy) noexcept; + +std::ostream& operator<<(std::ostream& os, const year_month_day_last& ymdl); + +// year_month_weekday + +class year_month_weekday +{ + date::year y_; + date::month m_; + date::weekday_indexed wdi_; + +public: + CONSTCD11 year_month_weekday(const date::year& y, const date::month& m, + const date::weekday_indexed& wdi) noexcept; + CONSTCD14 year_month_weekday(const day_point& dp) noexcept; + + year_month_weekday& operator+=(const months& m) noexcept; + year_month_weekday& operator-=(const months& m) noexcept; + year_month_weekday& operator+=(const years& y) noexcept; + year_month_weekday& operator-=(const years& y) noexcept; + + CONSTCD11 date::year year() const noexcept; + CONSTCD11 date::month month() const noexcept; + CONSTCD11 date::weekday_indexed weekday_indexed() const noexcept; + + CONSTCD14 operator day_point() const noexcept; + CONSTCD14 bool ok() const noexcept; + +private: + static CONSTCD14 year_month_weekday from_day_point(const day_point& dp) noexcept; +}; + +CONSTCD11 + bool operator==(const year_month_weekday& x, const year_month_weekday& y) noexcept; +CONSTCD11 + bool operator!=(const year_month_weekday& x, const year_month_weekday& y) noexcept; + +CONSTCD14 +year_month_weekday +operator+(const year_month_weekday& ymwd, const months& dm) noexcept; + +CONSTCD14 +year_month_weekday +operator+(const months& dm, const year_month_weekday& ymwd) noexcept; + +CONSTCD11 +year_month_weekday +operator+(const year_month_weekday& ymwd, const years& dy) noexcept; + +CONSTCD11 +year_month_weekday +operator+(const years& dy, const year_month_weekday& ymwd) noexcept; + +CONSTCD14 +year_month_weekday +operator-(const year_month_weekday& ymwd, const months& dm) noexcept; + +CONSTCD11 +year_month_weekday +operator-(const year_month_weekday& ymwd, const years& dy) noexcept; + +std::ostream& operator<<(std::ostream& os, const year_month_weekday& ymwdi); + +// year_month_weekday_last + +class year_month_weekday_last +{ + date::year y_; + date::month m_; + date::weekday_last wdl_; + +public: + CONSTCD11 year_month_weekday_last(const date::year& y, const date::month& m, + const date::weekday_last& wdl) noexcept; + + year_month_weekday_last& operator+=(const months& m) noexcept; + year_month_weekday_last& operator-=(const months& m) noexcept; + year_month_weekday_last& operator+=(const years& y) noexcept; + year_month_weekday_last& operator-=(const years& y) noexcept; + + CONSTCD11 date::year year() const noexcept; + CONSTCD11 date::month month() const noexcept; + CONSTCD11 date::weekday_last weekday_last() const noexcept; + + CONSTCD14 operator day_point() const noexcept; + CONSTCD11 bool ok() const noexcept; +}; + +CONSTCD11 +bool +operator==(const year_month_weekday_last& x, const year_month_weekday_last& y) noexcept; + +CONSTCD11 +bool +operator!=(const year_month_weekday_last& x, const year_month_weekday_last& y) noexcept; + +CONSTCD14 +year_month_weekday_last +operator+(const year_month_weekday_last& ymwdl, const months& dm) noexcept; + +CONSTCD14 +year_month_weekday_last +operator+(const months& dm, const year_month_weekday_last& ymwdl) noexcept; + +CONSTCD11 +year_month_weekday_last +operator+(const year_month_weekday_last& ymwdl, const years& dy) noexcept; + +CONSTCD11 +year_month_weekday_last +operator+(const years& dy, const year_month_weekday_last& ymwdl) noexcept; + +CONSTCD14 +year_month_weekday_last +operator-(const year_month_weekday_last& ymwdl, const months& dm) noexcept; + +CONSTCD11 +year_month_weekday_last +operator-(const year_month_weekday_last& ymwdl, const years& dy) noexcept; + +std::ostream& operator<<(std::ostream& os, const year_month_weekday_last& ymwdl); + +//----------------+ +// Implementation | +//----------------+ + +// utilities + +class save_stream +{ + std::ostream& os_; + char fill_; + std::ios::fmtflags flags_; + +public: + ~save_stream() + { + os_.fill(fill_); + os_.flags(flags_); + } + + save_stream(const save_stream&) = delete; + save_stream& operator=(const save_stream&) = delete; + + explicit save_stream(std::ostream& os) + : os_(os) + , fill_(os.fill()) + , flags_(os.flags()) + {} +}; + +// truncate towards zero +template +CONSTCD11 +inline +To +truncate(const std::chrono::duration& d) +{ + return std::chrono::duration_cast(d); +} + +// round down +template +CONSTCD14 +inline +To +floor(const std::chrono::duration& d) +{ + To t = std::chrono::duration_cast(d); + if (t > d) + t = t - To{1}; + return t; +} + +// round to nearest, to even on tie +template +CONSTCD14 +inline +To +round(const std::chrono::duration& d) +{ + To t0 = std::chrono::duration_cast(d); + To t1 = t0 + To{1}; + auto diff0 = d - t0; + auto diff1 = t1 - d; + if (diff0 == diff1) + { + if (t0.count() & 1) + return t1; + return t0; + } + else if (diff0 < diff1) + return t0; + return t1; +} + +// round up +template +CONSTCD14 +inline +To +ceil(const std::chrono::duration& d) +{ + To t = std::chrono::duration_cast(d); + if (t < d) + t = t + To{1}; + return t; +} + +// truncate towards zero +template +CONSTCD11 +inline +std::chrono::time_point +truncate(const std::chrono::time_point& tp) +{ + return std::chrono::time_point_cast(tp); +} + +// round down +template +CONSTCD11 +inline +std::chrono::time_point +floor(const std::chrono::time_point& tp) +{ + using std::chrono::time_point; + return time_point{floor(tp.time_since_epoch())}; +} + +// round to nearest, to even on tie +template +CONSTCD11 +inline +std::chrono::time_point +round(const std::chrono::time_point& tp) +{ + using std::chrono::time_point; + return time_point{round(tp.time_since_epoch())}; +} + +// round up +template +CONSTCD11 +inline +std::chrono::time_point +ceil(const std::chrono::time_point& tp) +{ + using std::chrono::time_point; + return time_point{ceil(tp.time_since_epoch())}; +} + +// day + +CONSTCD11 inline day::day(unsigned d) noexcept : d_(static_cast(d)) {} +inline day& day::operator++() noexcept {++d_; return *this;} +inline day day::operator++(int) noexcept {auto tmp(*this); ++(*this); return tmp;} +inline day& day::operator--() noexcept {--d_; return *this;} +inline day day::operator--(int) noexcept {auto tmp(*this); --(*this); return tmp;} +inline day& day::operator+=(const days& d) noexcept {*this = *this + d; return *this;} +inline day& day::operator-=(const days& d) noexcept {*this = *this - d; return *this;} +CONSTCD11 inline day::operator unsigned() const noexcept {return d_;} +CONSTCD11 inline bool day::ok() const noexcept {return 1 <= d_ && d_ <= 31;} + +CONSTCD11 +inline +bool +operator==(const day& x, const day& y) noexcept +{ + return static_cast(x) == static_cast(y); +} + +CONSTCD11 +inline +bool +operator!=(const day& x, const day& y) noexcept +{ + return !(x == y); +} + +CONSTCD11 +inline +bool +operator<(const day& x, const day& y) noexcept +{ + return static_cast(x) < static_cast(y); +} + +CONSTCD11 +inline +bool +operator>(const day& x, const day& y) noexcept +{ + return y < x; +} + +CONSTCD11 +inline +bool +operator<=(const day& x, const day& y) noexcept +{ + return !(y < x); +} + +CONSTCD11 +inline +bool +operator>=(const day& x, const day& y) noexcept +{ + return !(x < y); +} + +CONSTCD11 +inline +days +operator-(const day& x, const day& y) noexcept +{ + return days{static_cast(static_cast(x) + - static_cast(y))}; +} + +CONSTCD11 +inline +day +operator+(const day& x, const days& y) noexcept +{ + return day{static_cast(x) + static_cast(y.count())}; +} + +CONSTCD11 +inline +day +operator+(const days& x, const day& y) noexcept +{ + return y + x; +} + +CONSTCD11 +inline +day +operator-(const day& x, const days& y) noexcept +{ + return x + -y; +} + +inline +std::ostream& +operator<<(std::ostream& os, const day& d) +{ + save_stream _(os); + os.fill('0'); + os.flags(std::ios::dec | std::ios::right); + os.width(2); + os << static_cast(d); + return os; +} + +CONSTCD11 +inline +day +operator "" _d(unsigned long long d) noexcept +{ + return day{static_cast(d)}; +} + +// month + +CONSTCD11 inline month::month(unsigned m) noexcept : m_(static_cast(m)) {} +inline month& month::operator++() noexcept {if (++m_ == 13) m_ = 1; return *this;} +inline month month::operator++(int) noexcept {auto tmp(*this); ++(*this); return tmp;} +inline month& month::operator--() noexcept {if (--m_ == 0) m_ = 12; return *this;} +inline month month::operator--(int) noexcept {auto tmp(*this); --(*this); return tmp;} + +inline +month& +month::operator+=(const months& m) noexcept +{ + *this = *this + m; + return *this; +} + +inline +month& +month::operator-=(const months& m) noexcept +{ + *this = *this - m; + return *this; +} + +CONSTCD11 inline month::operator unsigned() const noexcept {return m_;} +CONSTCD11 inline bool month::ok() const noexcept {return 1 <= m_ && m_ <= 12;} + +CONSTCD11 +inline +bool +operator==(const month& x, const month& y) noexcept +{ + return static_cast(x) == static_cast(y); +} + +CONSTCD11 +inline +bool +operator!=(const month& x, const month& y) noexcept +{ + return !(x == y); +} + +CONSTCD11 +inline +bool +operator<(const month& x, const month& y) noexcept +{ + return static_cast(x) < static_cast(y); +} + +CONSTCD11 +inline +bool +operator>(const month& x, const month& y) noexcept +{ + return y < x; +} + +CONSTCD11 +inline +bool +operator<=(const month& x, const month& y) noexcept +{ + return !(y < x); +} + +CONSTCD11 +inline +bool +operator>=(const month& x, const month& y) noexcept +{ + return !(x < y); +} + +CONSTCD14 +inline +months +operator-(const month& x, const month& y) noexcept +{ + auto const d = static_cast(x) - static_cast(y); + return months(d <= 11 ? d : d + 12); +} + +CONSTCD14 +inline +month +operator+(const month& x, const months& y) noexcept +{ + auto const mu = static_cast(static_cast(x)) - 1 + y.count(); + auto const yr = (mu >= 0 ? mu : mu-11) / 12; + return month{static_cast(mu - yr * 12 + 1)}; +} + +CONSTCD14 +inline +month +operator+(const months& x, const month& y) noexcept +{ + return y + x; +} + +CONSTCD14 +inline +month +operator-(const month& x, const months& y) noexcept +{ + return x + -y; +} + +CONSTDATA month jan{1}; +CONSTDATA month feb{2}; +CONSTDATA month mar{3}; +CONSTDATA month apr{4}; +CONSTDATA month may{5}; +CONSTDATA month jun{6}; +CONSTDATA month jul{7}; +CONSTDATA month aug{8}; +CONSTDATA month sep{9}; +CONSTDATA month oct{10}; +CONSTDATA month nov{11}; +CONSTDATA month dec{12}; + +inline +std::ostream& +operator<<(std::ostream& os, const month& m) +{ + switch (static_cast(m)) + { + case 1: + os << "Jan"; + break; + case 2: + os << "Feb"; + break; + case 3: + os << "Mar"; + break; + case 4: + os << "Apr"; + break; + case 5: + os << "May"; + break; + case 6: + os << "Jun"; + break; + case 7: + os << "Jul"; + break; + case 8: + os << "Aug"; + break; + case 9: + os << "Sep"; + break; + case 10: + os << "Oct"; + break; + case 11: + os << "Nov"; + break; + case 12: + os << "Dec"; + break; + default: + os << static_cast(m) << " is not a valid month"; + break; + } + return os; +} + +// year + +CONSTCD11 inline year::year(int y) noexcept : y_(static_cast(y)) {} +inline year& year::operator++() noexcept {++y_; return *this;} +inline year year::operator++(int) noexcept {auto tmp(*this); ++(*this); return tmp;} +inline year& year::operator--() noexcept {--y_; return *this;} +inline year year::operator--(int) noexcept {auto tmp(*this); --(*this); return tmp;} +inline year& year::operator+=(const years& y) noexcept {*this = *this + y; return *this;} +inline year& year::operator-=(const years& y) noexcept {*this = *this - y; return *this;} + +CONSTCD11 +inline +bool +year::is_leap() const noexcept +{ + return y_ % 4 == 0 && (y_ % 100 != 0 || y_ % 400 == 0); +} + +CONSTCD11 inline year::operator int() const noexcept {return y_;} +CONSTCD11 inline bool year::ok() const noexcept {return true;} + +CONSTCD11 +inline +year +year::min() noexcept +{ + return year{std::numeric_limits::min()}; +} + +CONSTCD11 +inline +year +year::max() noexcept +{ + return year{std::numeric_limits::max()}; +} + +CONSTCD11 +inline +bool +operator==(const year& x, const year& y) noexcept +{ + return static_cast(x) == static_cast(y); +} + +CONSTCD11 +inline +bool +operator!=(const year& x, const year& y) noexcept +{ + return !(x == y); +} + +CONSTCD11 +inline +bool +operator<(const year& x, const year& y) noexcept +{ + return static_cast(x) < static_cast(y); +} + +CONSTCD11 +inline +bool +operator>(const year& x, const year& y) noexcept +{ + return y < x; +} + +CONSTCD11 +inline +bool +operator<=(const year& x, const year& y) noexcept +{ + return !(y < x); +} + +CONSTCD11 +inline +bool +operator>=(const year& x, const year& y) noexcept +{ + return !(x < y); +} + +CONSTCD11 +inline +years +operator-(const year& x, const year& y) noexcept +{ + return years{static_cast(x) - static_cast(y)}; +} + +CONSTCD11 +inline +year +operator+(const year& x, const years& y) noexcept +{ + return year{static_cast(x) + y.count()}; +} + +CONSTCD11 +inline +year +operator+(const years& x, const year& y) noexcept +{ + return y + x; +} + +CONSTCD11 +inline +year +operator-(const year& x, const years& y) noexcept +{ + return year{static_cast(x) - y.count()}; +} + +inline +std::ostream& +operator<<(std::ostream& os, const year& y) +{ + save_stream _(os); + os.fill('0'); + os.flags(std::ios::dec | std::ios::internal); + os.width(4 + (y < year{0})); + os << static_cast(y); + return os; +} + +CONSTCD11 +inline +year +operator "" _y(unsigned long long y) noexcept +{ + return year(static_cast(y)); +} + +// weekday + +CONSTCD11 +inline +unsigned char +weekday::weekday_from_days(int z) noexcept +{ + return static_cast(static_cast( + z >= -4 ? (z+4) % 7 : (z+5) % 7 + 6)); +} + +CONSTCD11 +inline +weekday::weekday(unsigned wd) noexcept + : wd_(static_cast(wd)) + {} + +CONSTCD11 +inline +weekday::weekday(const day_point& dp) noexcept + : wd_(weekday_from_days(dp.time_since_epoch().count())) + {} + +inline weekday& weekday::operator++() noexcept {if (++wd_ == 7) wd_ = 0; return *this;} +inline weekday weekday::operator++(int) noexcept {auto tmp(*this); ++(*this); return tmp;} +inline weekday& weekday::operator--() noexcept {if (wd_-- == 0) wd_ = 6; return *this;} +inline weekday weekday::operator--(int) noexcept {auto tmp(*this); --(*this); return tmp;} + +inline +weekday& +weekday::operator+=(const days& d) noexcept +{ + *this = *this + d; + return *this; +} + +inline +weekday& +weekday::operator-=(const days& d) noexcept +{ + *this = *this - d; + return *this; +} + +CONSTCD11 +inline +weekday::operator unsigned() const noexcept +{ + return static_cast(wd_); +} + +CONSTCD11 inline bool weekday::ok() const noexcept {return wd_ <= 6;} + +CONSTCD11 +inline +bool +operator==(const weekday& x, const weekday& y) noexcept +{ + return static_cast(x) == static_cast(y); +} + +CONSTCD11 +inline +bool +operator!=(const weekday& x, const weekday& y) noexcept +{ + return !(x == y); +} + +CONSTCD14 +inline +days +operator-(const weekday& x, const weekday& y) noexcept +{ + auto const diff = static_cast(x) - static_cast(y); + return days{diff <= 6 ? diff : diff + 7}; +} + +CONSTCD14 +inline +weekday +operator+(const weekday& x, const days& y) noexcept +{ + auto const wdu = static_cast(static_cast(x)) + y.count(); + auto const wk = (wdu >= 0 ? wdu : wdu-6) / 7; + return weekday{static_cast(wdu - wk * 7)}; +} + +CONSTCD14 +inline +weekday +operator+(const days& x, const weekday& y) noexcept +{ + return y + x; +} + +CONSTCD14 +inline +weekday +operator-(const weekday& x, const days& y) noexcept +{ + return x + -y; +} + +inline +std::ostream& +operator<<(std::ostream& os, const weekday& wd) +{ + switch (static_cast(wd)) + { + case 0: + os << "Sun"; + break; + case 1: + os << "Mon"; + break; + case 2: + os << "Tue"; + break; + case 3: + os << "Wed"; + break; + case 4: + os << "Thu"; + break; + case 5: + os << "Fri"; + break; + case 6: + os << "Sat"; + break; + default: + os << static_cast(wd) << " is not a valid weekday"; + break; + } + return os; +} + +CONSTDATA weekday sun{0}; +CONSTDATA weekday mon{1}; +CONSTDATA weekday tue{2}; +CONSTDATA weekday wed{3}; +CONSTDATA weekday thu{4}; +CONSTDATA weekday fri{5}; +CONSTDATA weekday sat{6}; + +// weekday_indexed + +CONSTCD11 +inline +weekday +weekday_indexed::weekday() const noexcept +{ + return date::weekday{wd_}; +} + +CONSTCD11 inline unsigned weekday_indexed::index() const noexcept {return index_;} + +CONSTCD11 +inline +bool +weekday_indexed::ok() const noexcept +{ + return weekday().ok() && 1 <= index_ && index_ <= 5; +} + +CONSTCD11 +inline +weekday_indexed::weekday_indexed(const date::weekday& wd, unsigned index) noexcept + : wd_(static_cast(static_cast(wd))) + , index_(static_cast(index)) + {} + +inline +std::ostream& +operator<<(std::ostream& os, const weekday_indexed& wdi) +{ + return os << wdi.weekday() << '[' << wdi.index() << ']'; +} + +CONSTCD11 +inline +weekday_indexed +weekday::operator[](unsigned index) const noexcept +{ + return {*this, index}; +} + +CONSTCD11 +inline +bool +operator==(const weekday_indexed& x, const weekday_indexed& y) noexcept +{ + return x.weekday() == y.weekday() && x.index() == y.index(); +} + +CONSTCD11 +inline +bool +operator!=(const weekday_indexed& x, const weekday_indexed& y) noexcept +{ + return !(x == y); +} + +// weekday_last + +CONSTCD11 inline date::weekday weekday_last::weekday() const noexcept {return wd_;} +CONSTCD11 inline bool weekday_last::ok() const noexcept {return wd_.ok();} +CONSTCD11 inline weekday_last::weekday_last(const date::weekday& wd) noexcept : wd_(wd) {} + +CONSTCD11 +inline +bool +operator==(const weekday_last& x, const weekday_last& y) noexcept +{ + return x.weekday() == y.weekday(); +} + +CONSTCD11 +inline +bool +operator!=(const weekday_last& x, const weekday_last& y) noexcept +{ + return !(x == y); +} + +inline +std::ostream& +operator<<(std::ostream& os, const weekday_last& wdl) +{ + return os << wdl.weekday() << "[last]"; +} + +CONSTCD11 +inline +weekday_last +weekday::operator[](last_spec) const noexcept +{ + return weekday_last{*this}; +} + +// year_month + +CONSTCD11 +inline +year_month::year_month(const date::year& y, const date::month& m) noexcept + : y_(y) + , m_(m) + {} + +CONSTCD11 inline year year_month::year() const noexcept {return y_;} +CONSTCD11 inline month year_month::month() const noexcept {return m_;} +CONSTCD11 inline bool year_month::ok() const noexcept {return y_.ok() && m_.ok();} + +CONSTCD11 +inline +bool +operator==(const year_month& x, const year_month& y) noexcept +{ + return x.year() == y.year() && x.month() == y.month(); +} + +CONSTCD11 +inline +bool +operator!=(const year_month& x, const year_month& y) noexcept +{ + return !(x == y); +} + +CONSTCD11 +inline +bool +operator<(const year_month& x, const year_month& y) noexcept +{ + return x.year() < y.year() ? true + : (x.year() > y.year() ? false + : (x.month() < y.month())); +} + +CONSTCD11 +inline +bool +operator>(const year_month& x, const year_month& y) noexcept +{ + return y < x; +} + +CONSTCD11 +inline +bool +operator<=(const year_month& x, const year_month& y) noexcept +{ + return !(y < x); +} + +CONSTCD11 +inline +bool +operator>=(const year_month& x, const year_month& y) noexcept +{ + return !(x < y); +} + +CONSTCD14 +inline +year_month +operator+(const year_month& ym, const months& dm) noexcept +{ + auto dmi = static_cast(static_cast(ym.month())) - 1 + dm.count(); + auto dy = (dmi >= 0 ? dmi : dmi-11) / 12; + dmi = dmi - dy * 12 + 1; + return (ym.year() + years(dy)) / month(static_cast(dmi)); +} + +CONSTCD14 +inline +year_month +operator+(const months& dm, const year_month& ym) noexcept +{ + return ym + dm; +} + +CONSTCD14 +inline +year_month +operator-(const year_month& ym, const months& dm) noexcept +{ + return ym + -dm; +} + +CONSTCD11 +inline +months +operator-(const year_month& x, const year_month& y) noexcept +{ + return (x.year() - y.year()) + + months(static_cast(x.month()) - static_cast(y.month())); +} + +CONSTCD11 +inline +year_month +operator+(const year_month& ym, const years& dy) noexcept +{ + return (ym.year() + dy) / ym.month(); +} + +CONSTCD11 +inline +year_month +operator+(const years& dy, const year_month& ym) noexcept +{ + return ym + dy; +} + +CONSTCD11 +inline +year_month +operator-(const year_month& ym, const years& dy) noexcept +{ + return ym + -dy; +} + +inline +std::ostream& +operator<<(std::ostream& os, const year_month& ym) +{ + return os << ym.year() << '/' << ym.month(); +} + +// month_day + +CONSTCD11 +inline +month_day::month_day(const date::month& m, const date::day& d) noexcept + : m_(m) + , d_(d) + {} + +CONSTCD11 inline date::month month_day::month() const noexcept {return m_;} +CONSTCD11 inline date::day month_day::day() const noexcept {return d_;} + +CONSTCD14 +inline +bool +month_day::ok() const noexcept +{ + CONSTDATA date::day d[] = + {31_d, 29_d, 31_d, 30_d, 31_d, 30_d, 31_d, 31_d, 30_d, 31_d, 30_d, 31_d}; + return m_.ok() && 1_d <= d_ && d_ <= d[static_cast(m_)-1]; +} + +CONSTCD11 +inline +bool +operator==(const month_day& x, const month_day& y) noexcept +{ + return x.month() == y.month() && x.day() == y.day(); +} + +CONSTCD11 +inline +bool +operator!=(const month_day& x, const month_day& y) noexcept +{ + return !(x == y); +} + +CONSTCD11 +inline +bool +operator<(const month_day& x, const month_day& y) noexcept +{ + return x.month() < y.month() ? true + : (x.month() > y.month() ? false + : (x.day() < y.day())); +} + +CONSTCD11 +inline +bool +operator>(const month_day& x, const month_day& y) noexcept +{ + return y < x; +} + +CONSTCD11 +inline +bool +operator<=(const month_day& x, const month_day& y) noexcept +{ + return !(y < x); +} + +CONSTCD11 +inline +bool +operator>=(const month_day& x, const month_day& y) noexcept +{ + return !(x < y); +} + +inline +std::ostream& +operator<<(std::ostream& os, const month_day& md) +{ + return os << md.month() << '/' << md.day(); +} + +// month_day_last + +CONSTCD11 inline month month_day_last::month() const noexcept {return m_;} +CONSTCD11 inline bool month_day_last::ok() const noexcept {return m_.ok();} +CONSTCD11 inline month_day_last::month_day_last(const date::month& m) noexcept : m_(m) {} + +CONSTCD11 +inline +bool +operator==(const month_day_last& x, const month_day_last& y) noexcept +{ + return x.month() == y.month(); +} + +CONSTCD11 +inline +bool +operator!=(const month_day_last& x, const month_day_last& y) noexcept +{ + return !(x == y); +} + +CONSTCD11 +inline +bool +operator<(const month_day_last& x, const month_day_last& y) noexcept +{ + return x.month() < y.month(); +} + +CONSTCD11 +inline +bool +operator>(const month_day_last& x, const month_day_last& y) noexcept +{ + return y < x; +} + +CONSTCD11 +inline +bool +operator<=(const month_day_last& x, const month_day_last& y) noexcept +{ + return !(y < x); +} + +CONSTCD11 +inline +bool +operator>=(const month_day_last& x, const month_day_last& y) noexcept +{ + return !(x < y); +} + +inline +std::ostream& +operator<<(std::ostream& os, const month_day_last& mdl) +{ + return os << mdl.month() << "/last"; +} + +// month_weekday + +CONSTCD11 +inline +month_weekday::month_weekday(const date::month& m, + const date::weekday_indexed& wdi) noexcept + : m_(m) + , wdi_(wdi) + {} + +CONSTCD11 inline month month_weekday::month() const noexcept {return m_;} + +CONSTCD11 +inline +weekday_indexed +month_weekday::weekday_indexed() const noexcept +{ + return wdi_; +} + +CONSTCD11 +inline +bool +month_weekday::ok() const noexcept +{ + return m_.ok() && wdi_.ok(); +} + +CONSTCD11 +inline +bool +operator==(const month_weekday& x, const month_weekday& y) noexcept +{ + return x.month() == y.month() && x.weekday_indexed() == y.weekday_indexed(); +} + +CONSTCD11 +inline +bool +operator!=(const month_weekday& x, const month_weekday& y) noexcept +{ + return !(x == y); +} + +inline +std::ostream& +operator<<(std::ostream& os, const month_weekday& mwd) +{ + return os << mwd.month() << '/' << mwd.weekday_indexed(); +} + +// month_weekday_last + +CONSTCD11 +inline +month_weekday_last::month_weekday_last(const date::month& m, + const date::weekday& wd) noexcept + : m_(m) + , wd_(wd) + {} + +CONSTCD11 inline month month_weekday_last::month() const noexcept {return m_;} +CONSTCD11 inline weekday month_weekday_last::weekday() const noexcept {return wd_;} + +CONSTCD11 +inline +bool +month_weekday_last::ok() const noexcept +{ + return m_.ok() && wd_.ok(); +} + +CONSTCD11 +inline +bool +operator==(const month_weekday_last& x, const month_weekday_last& y) noexcept +{ + return x.month() == y.month() && x.weekday() == y.weekday(); +} + +CONSTCD11 +inline +bool +operator!=(const month_weekday_last& x, const month_weekday_last& y) noexcept +{ + return !(x == y); +} + +inline +std::ostream& +operator<<(std::ostream& os, const month_weekday_last& mwdl) +{ + return os << mwdl.month() << '/' << mwdl.weekday() << "[last]"; +} + +// year_month_day_last + +CONSTCD11 +inline +year_month_day_last::year_month_day_last(const date::year& y, + const date::month& m) noexcept + : y_(y) + , m_(m) + {} + +inline +year_month_day_last& +year_month_day_last::operator+=(const months& m) noexcept +{ + *this = *this + m; + return *this; +} + +inline +year_month_day_last& +year_month_day_last::operator-=(const months& m) noexcept +{ + *this = *this - m; + return *this; +} + +inline +year_month_day_last& +year_month_day_last::operator+=(const years& y) noexcept +{ + *this = *this + y; + return *this; +} + +inline +year_month_day_last& +year_month_day_last::operator-=(const years& y) noexcept +{ + *this = *this - y; + return *this; +} + +CONSTCD11 inline year year_month_day_last::year() const noexcept {return y_;} +CONSTCD11 inline month year_month_day_last::month() const noexcept {return m_;} + +CONSTCD14 +inline +day +year_month_day_last::day() const noexcept +{ + CONSTDATA date::day d[] = + {31_d, 28_d, 31_d, 30_d, 31_d, 30_d, 31_d, 31_d, 30_d, 31_d, 30_d, 31_d}; + return m_ != feb || !y_.is_leap() ? d[static_cast(m_)-1] : 29_d; +} + +CONSTCD11 inline bool year_month_day_last::ok() const noexcept {return m_.ok();} + +CONSTCD11 +inline +bool +operator==(const year_month_day_last& x, const year_month_day_last& y) noexcept +{ + return x.year() == y.year() && x.month() == y.month(); +} + +CONSTCD11 +inline +bool +operator!=(const year_month_day_last& x, const year_month_day_last& y) noexcept +{ + return !(x == y); +} + +CONSTCD11 +inline +bool +operator<(const year_month_day_last& x, const year_month_day_last& y) noexcept +{ + return x.year() < y.year() ? true + : (x.year() > y.year() ? false + : (x.month() < y.month())); +} + +CONSTCD11 +inline +bool +operator>(const year_month_day_last& x, const year_month_day_last& y) noexcept +{ + return y < x; +} + +CONSTCD11 +inline +bool +operator<=(const year_month_day_last& x, const year_month_day_last& y) noexcept +{ + return !(y < x); +} + +CONSTCD11 +inline +bool +operator>=(const year_month_day_last& x, const year_month_day_last& y) noexcept +{ + return !(x < y); +} + +inline +std::ostream& +operator<<(std::ostream& os, const year_month_day_last& ymdl) +{ + return os << ymdl.year() << '/' << ymdl.month() << "/last"; +} + +CONSTCD14 +inline +year_month_day_last +operator+(const year_month_day_last& ymdl, const months& dm) noexcept +{ + return (ymdl.year() / ymdl.month() + dm) / last; +} + +CONSTCD14 +inline +year_month_day_last +operator+(const months& dm, const year_month_day_last& ymdl) noexcept +{ + return ymdl + dm; +} + +CONSTCD14 +inline +year_month_day_last +operator-(const year_month_day_last& ymdl, const months& dm) noexcept +{ + return ymdl + (-dm); +} + +CONSTCD11 +inline +year_month_day_last +operator+(const year_month_day_last& ymdl, const years& dy) noexcept +{ + return {ymdl.year()+dy, ymdl.month()}; +} + +CONSTCD11 +inline +year_month_day_last +operator+(const years& dy, const year_month_day_last& ymdl) noexcept +{ + return ymdl + dy; +} + +CONSTCD11 +inline +year_month_day_last +operator-(const year_month_day_last& ymdl, const years& dy) noexcept +{ + return ymdl + (-dy); +} + +// year_month_day + +CONSTCD11 +inline +year_month_day::year_month_day(const date::year& y, const date::month& m, + const date::day& d) noexcept + : y_(y) + , m_(m) + , d_(d) + {} + +CONSTCD14 +inline +year_month_day::year_month_day(const year_month_day_last& ymdl) noexcept + : y_(ymdl.year()) + , m_(ymdl.month()) + , d_(ymdl.day()) + {} + +CONSTCD14 +inline +year_month_day::year_month_day(const day_point& dp) noexcept + : year_month_day(from_day_point(dp)) + {} + +CONSTCD11 inline year year_month_day::year() const noexcept {return y_;} +CONSTCD11 inline month year_month_day::month() const noexcept {return m_;} +CONSTCD11 inline day year_month_day::day() const noexcept {return d_;} + +inline +year_month_day& +year_month_day::operator+=(const months& m) noexcept +{ + *this = *this + m; + return *this; +} + +inline +year_month_day& +year_month_day::operator-=(const months& m) noexcept +{ + *this = *this - m; + return *this; +} + +inline +year_month_day& +year_month_day::operator+=(const years& y) noexcept +{ + *this = *this + y; + return *this; +} + +inline +year_month_day& +year_month_day::operator-=(const years& y) noexcept +{ + *this = *this - y; + return *this; +} + +CONSTCD14 +inline +year_month_day::operator day_point() const noexcept +{ + static_assert(std::numeric_limits::digits >= 18, + "This algorithm has not been ported to a 16 bit unsigned integer"); + static_assert(std::numeric_limits::digits >= 20, + "This algorithm has not been ported to a 16 bit signed integer"); + auto const y = static_cast(y_) - (m_ <= feb); + auto const m = static_cast(m_); + auto const d = static_cast(d_); + auto const era = (y >= 0 ? y : y-399) / 400; + auto const yoe = static_cast(y - era * 400); // [0, 399] + auto const doy = (153*(m + (m > 2 ? -3u : 9)) + 2)/5 + d-1; // [0, 365] + auto const doe = yoe * 365 + yoe/4 - yoe/100 + doy; // [0, 146096] + return day_point{days{era * 146097 + static_cast(doe) - 719468}}; +} + +CONSTCD14 +inline +year_month_day_last::operator day_point() const noexcept +{ + return day_point(y_/m_/day()); +} + +CONSTCD14 +inline +bool +year_month_day::ok() const noexcept +{ + if (!m_.ok()) + return false; + return 1_d <= d_ && d_ <= (y_/m_/last).day(); +} + +CONSTCD11 +inline +bool +operator==(const year_month_day& x, const year_month_day& y) noexcept +{ + return x.year() == y.year() && x.month() == y.month() && x.day() == y.day(); +} + +CONSTCD11 +inline +bool +operator!=(const year_month_day& x, const year_month_day& y) noexcept +{ + return !(x == y); +} + +CONSTCD11 +inline +bool +operator<(const year_month_day& x, const year_month_day& y) noexcept +{ + return x.year() < y.year() ? true + : (x.year() > y.year() ? false + : (x.month() < y.month() ? true + : (x.month() > y.month() ? false + : (x.day() < y.day())))); +} + +CONSTCD11 +inline +bool +operator>(const year_month_day& x, const year_month_day& y) noexcept +{ + return y < x; +} + +CONSTCD11 +inline +bool +operator<=(const year_month_day& x, const year_month_day& y) noexcept +{ + return !(y < x); +} + +CONSTCD11 +inline +bool +operator>=(const year_month_day& x, const year_month_day& y) noexcept +{ + return !(x < y); +} + +inline +std::ostream& +operator<<(std::ostream& os, const year_month_day& ymd) +{ + save_stream _(os); + os.fill('0'); + os.flags(std::ios::dec | std::ios::right); + os << ymd.year() << '-'; + os.width(2); + os << static_cast(ymd.month()) << '-'; + os << ymd.day(); + return os; +} + +CONSTCD14 +inline +year_month_day +year_month_day::from_day_point(const day_point& dp) noexcept +{ + static_assert(std::numeric_limits::digits >= 18, + "This algorithm has not been ported to a 16 bit unsigned integer"); + static_assert(std::numeric_limits::digits >= 20, + "This algorithm has not been ported to a 16 bit signed integer"); + auto const z = dp.time_since_epoch().count() + 719468; + auto const era = (z >= 0 ? z : z - 146096) / 146097; + auto const doe = static_cast(z - era * 146097); // [0, 146096] + auto const yoe = (doe - doe/1460 + doe/36524 - doe/146096) / 365; // [0, 399] + auto const y = static_cast(yoe) + era * 400; + auto const doy = doe - (365*yoe + yoe/4 - yoe/100); // [0, 365] + auto const mp = (5*doy + 2)/153; // [0, 11] + auto const d = doy - (153*mp+2)/5 + 1; // [1, 31] + auto const m = mp + (mp < 10 ? 3 : -9u); // [1, 12] + return year_month_day{date::year{y + (m <= 2)}, date::month(m), date::day(d)}; +} + +CONSTCD14 +inline +year_month_day +operator+(const year_month_day& ymd, const months& dm) noexcept +{ + return (ymd.year() / ymd.month() + dm) / ymd.day(); +} + +CONSTCD14 +inline +year_month_day +operator+(const months& dm, const year_month_day& ymd) noexcept +{ + return ymd + dm; +} + +CONSTCD14 +inline +year_month_day +operator-(const year_month_day& ymd, const months& dm) noexcept +{ + return ymd + (-dm); +} + +CONSTCD11 +inline +year_month_day +operator+(const year_month_day& ymd, const years& dy) noexcept +{ + return (ymd.year() + dy) / ymd.month() / ymd.day(); +} + +CONSTCD11 +inline +year_month_day +operator+(const years& dy, const year_month_day& ymd) noexcept +{ + return ymd + dy; +} + +CONSTCD11 +inline +year_month_day +operator-(const year_month_day& ymd, const years& dy) noexcept +{ + return ymd + (-dy); +} + +// year_month_weekday + +CONSTCD11 +inline +year_month_weekday::year_month_weekday(const date::year& y, const date::month& m, + const date::weekday_indexed& wdi) + noexcept + : y_(y) + , m_(m) + , wdi_(wdi) + {} + +CONSTCD14 +inline +year_month_weekday::year_month_weekday(const day_point& dp) noexcept + : year_month_weekday(from_day_point(dp)) + {} + +inline +year_month_weekday& +year_month_weekday::operator+=(const months& m) noexcept +{ + *this = *this + m; + return *this; +} + +inline +year_month_weekday& +year_month_weekday::operator-=(const months& m) noexcept +{ + *this = *this - m; + return *this; +} + +inline +year_month_weekday& +year_month_weekday::operator+=(const years& y) noexcept +{ + *this = *this + y; + return *this; +} + +inline +year_month_weekday& +year_month_weekday::operator-=(const years& y) noexcept +{ + *this = *this - y; + return *this; +} + +CONSTCD11 inline year year_month_weekday::year() const noexcept {return y_;} +CONSTCD11 inline month year_month_weekday::month() const noexcept {return m_;} + +CONSTCD11 +inline +weekday_indexed +year_month_weekday::weekday_indexed() const noexcept +{ + return wdi_; +} + +CONSTCD14 +inline +year_month_weekday::operator day_point() const noexcept +{ + auto d = day_point(y_/m_/1); + return d + (wdi_.weekday() - weekday(d) + days{(wdi_.index()-1)*7}); +} + +CONSTCD14 +inline +bool +year_month_weekday::ok() const noexcept +{ + if (!m_.ok() || !wdi_.weekday().ok() || wdi_.index() < 1) + return false; + if (wdi_.index() <= 4) + return true; + auto d2 = wdi_.weekday() - weekday(y_/m_/1) + days((wdi_.index()-1)*7 + 1); + return static_cast(d2.count()) <= static_cast((y_/m_/last).day()); +} + +CONSTCD14 +inline +year_month_weekday +year_month_weekday::from_day_point(const day_point& dp) noexcept +{ + auto const wd = weekday(dp); + auto const ymd = year_month_day(dp); + return {ymd.year(), ymd.month(), wd[(static_cast(ymd.day())-1)/7+1]}; +} + +CONSTCD11 +inline +bool +operator==(const year_month_weekday& x, const year_month_weekday& y) noexcept +{ + return x.year() == y.year() && x.month() == y.month() && + x.weekday_indexed() == y.weekday_indexed(); +} + +CONSTCD11 +inline +bool +operator!=(const year_month_weekday& x, const year_month_weekday& y) noexcept +{ + return !(x == y); +} + +inline +std::ostream& +operator<<(std::ostream& os, const year_month_weekday& ymwdi) +{ + return os << ymwdi.year() << '/' << ymwdi.month() + << '/' << ymwdi.weekday_indexed(); +} + +CONSTCD14 +inline +year_month_weekday +operator+(const year_month_weekday& ymwd, const months& dm) noexcept +{ + return (ymwd.year() / ymwd.month() + dm) / ymwd.weekday_indexed(); +} + +CONSTCD14 +inline +year_month_weekday +operator+(const months& dm, const year_month_weekday& ymwd) noexcept +{ + return ymwd + dm; +} + +CONSTCD14 +inline +year_month_weekday +operator-(const year_month_weekday& ymwd, const months& dm) noexcept +{ + return ymwd + (-dm); +} + +CONSTCD11 +inline +year_month_weekday +operator+(const year_month_weekday& ymwd, const years& dy) noexcept +{ + return {ymwd.year()+dy, ymwd.month(), ymwd.weekday_indexed()}; +} + +CONSTCD11 +inline +year_month_weekday +operator+(const years& dy, const year_month_weekday& ymwd) noexcept +{ + return ymwd + dy; +} + +CONSTCD11 +inline +year_month_weekday +operator-(const year_month_weekday& ymwd, const years& dy) noexcept +{ + return ymwd + (-dy); +} + +// year_month_weekday_last + +CONSTCD11 +inline +year_month_weekday_last::year_month_weekday_last(const date::year& y, + const date::month& m, + const date::weekday_last& wdl) noexcept + : y_(y) + , m_(m) + , wdl_(wdl) + {} + +inline +year_month_weekday_last& +year_month_weekday_last::operator+=(const months& m) noexcept +{ + *this = *this + m; + return *this; +} + +inline +year_month_weekday_last& +year_month_weekday_last::operator-=(const months& m) noexcept +{ + *this = *this - m; + return *this; +} + +inline +year_month_weekday_last& +year_month_weekday_last::operator+=(const years& y) noexcept +{ + *this = *this + y; + return *this; +} + +inline +year_month_weekday_last& +year_month_weekday_last::operator-=(const years& y) noexcept +{ + *this = *this - y; + return *this; +} + +CONSTCD11 inline year year_month_weekday_last::year() const noexcept {return y_;} +CONSTCD11 inline month year_month_weekday_last::month() const noexcept {return m_;} + +CONSTCD11 +inline +weekday_last +year_month_weekday_last::weekday_last() const noexcept +{ + return wdl_; +} + +CONSTCD14 +inline +year_month_weekday_last::operator day_point() const noexcept +{ + auto const d = day_point(y_/m_/last); + return d - (weekday{d} - wdl_.weekday()); +} + +CONSTCD11 +inline +bool +year_month_weekday_last::ok() const noexcept +{ + return m_.ok() && wdl_.ok(); +} + +CONSTCD11 +inline +bool +operator==(const year_month_weekday_last& x, const year_month_weekday_last& y) noexcept +{ + return x.year() == y.year() && x.month() == y.month() && + x.weekday_last() == y.weekday_last(); +} + +CONSTCD11 +inline +bool +operator!=(const year_month_weekday_last& x, const year_month_weekday_last& y) noexcept +{ + return !(x == y); +} + +inline +std::ostream& +operator<<(std::ostream& os, const year_month_weekday_last& ymwdl) +{ + return os << ymwdl.year() << '/' << ymwdl.month() << '/' << ymwdl.weekday_last(); +} + +CONSTCD14 +inline +year_month_weekday_last +operator+(const year_month_weekday_last& ymwdl, const months& dm) noexcept +{ + return (ymwdl.year() / ymwdl.month() + dm) / ymwdl.weekday_last(); +} + +CONSTCD14 +inline +year_month_weekday_last +operator+(const months& dm, const year_month_weekday_last& ymwdl) noexcept +{ + return ymwdl + dm; +} + +CONSTCD14 +inline +year_month_weekday_last +operator-(const year_month_weekday_last& ymwdl, const months& dm) noexcept +{ + return ymwdl + (-dm); +} + +CONSTCD11 +inline +year_month_weekday_last +operator+(const year_month_weekday_last& ymwdl, const years& dy) noexcept +{ + return {ymwdl.year()+dy, ymwdl.month(), ymwdl.weekday_last()}; +} + +CONSTCD11 +inline +year_month_weekday_last +operator+(const years& dy, const year_month_weekday_last& ymwdl) noexcept +{ + return ymwdl + dy; +} + +CONSTCD11 +inline +year_month_weekday_last +operator-(const year_month_weekday_last& ymwdl, const years& dy) noexcept +{ + return ymwdl + (-dy); +} + +// year_month from operator/() + +CONSTCD11 +inline +year_month +operator/(const year& y, const month& m) noexcept +{ + return {y, m}; +} + +CONSTCD11 +inline +year_month +operator/(const year& y, int m) noexcept +{ + return y / month(static_cast(m)); +} + +// month_day from operator/() + +CONSTCD11 +inline +month_day +operator/(const month& m, const day& d) noexcept +{ + return {m, d}; +} + +CONSTCD11 +inline +month_day +operator/(const day& d, const month& m) noexcept +{ + return m / d; +} + +CONSTCD11 +inline +month_day +operator/(const month& m, int d) noexcept +{ + return m / day(static_cast(d)); +} + +CONSTCD11 +inline +month_day +operator/(int m, const day& d) noexcept +{ + return month(static_cast(m)) / d; +} + +CONSTCD11 inline month_day operator/(const day& d, int m) noexcept {return m / d;} + +// month_day_last from operator/() + +CONSTCD11 +inline +month_day_last +operator/(const month& m, last_spec) noexcept +{ + return month_day_last{m}; +} + +CONSTCD11 +inline +month_day_last +operator/(last_spec, const month& m) noexcept +{ + return m/last; +} + +CONSTCD11 +inline +month_day_last +operator/(int m, last_spec) noexcept +{ + return month(static_cast(m))/last; +} + +CONSTCD11 +inline +month_day_last +operator/(last_spec, int m) noexcept +{ + return m/last; +} + +// month_weekday from operator/() + +CONSTCD11 +inline +month_weekday +operator/(const month& m, const weekday_indexed& wdi) noexcept +{ + return {m, wdi}; +} + +CONSTCD11 +inline +month_weekday +operator/(const weekday_indexed& wdi, const month& m) noexcept +{ + return m / wdi; +} + +CONSTCD11 +inline +month_weekday +operator/(int m, const weekday_indexed& wdi) noexcept +{ + return month(static_cast(m)) / wdi; +} + +CONSTCD11 +inline +month_weekday +operator/(const weekday_indexed& wdi, int m) noexcept +{ + return m / wdi; +} + +// month_weekday_last from operator/() + +CONSTCD11 +inline +month_weekday_last +operator/(const month& m, const weekday_last& wdl) noexcept +{ + return {m, wdl.weekday()}; +} + +CONSTCD11 +inline +month_weekday_last +operator/(const weekday_last& wdl, const month& m) noexcept +{ + return m / wdl; +} + +CONSTCD11 +inline +month_weekday_last +operator/(int m, const weekday_last& wdl) noexcept +{ + return month(static_cast(m)) / wdl; +} + +CONSTCD11 +inline +month_weekday_last +operator/(const weekday_last& wdl, int m) noexcept +{ + return m / wdl; +} + +// year_month_day from operator/() + +CONSTCD11 +inline +year_month_day +operator/(const year_month& ym, const day& d) noexcept +{ + return {ym.year(), ym.month(), d}; +} + +CONSTCD11 +inline +year_month_day +operator/(const year_month& ym, int d) noexcept +{ + return ym / day(static_cast(d)); +} + +CONSTCD11 +inline +year_month_day +operator/(const year& y, const month_day& md) noexcept +{ + return y / md.month() / md.day(); +} + +CONSTCD11 +inline +year_month_day +operator/(int y, const month_day& md) noexcept +{ + return year(y) / md; +} + +CONSTCD11 +inline +year_month_day +operator/(const month_day& md, const year& y) noexcept +{ + return y / md; +} + +CONSTCD11 +inline +year_month_day +operator/(const month_day& md, int y) noexcept +{ + return year(y) / md; +} + +// year_month_day_last from operator/() + +CONSTCD11 +inline +year_month_day_last +operator/(const year_month& ym, last_spec) noexcept +{ + return {ym.year(), ym.month()}; +} + +CONSTCD11 +inline +year_month_day_last +operator/(const year& y, const month_day_last& mdl) noexcept +{ + return {y, mdl.month()}; +} + +CONSTCD11 +inline +year_month_day_last +operator/(int y, const month_day_last& mdl) noexcept +{ + return year(y) / mdl; +} + +CONSTCD11 +inline +year_month_day_last +operator/(const month_day_last& mdl, const year& y) noexcept +{ + return y / mdl; +} + +CONSTCD11 +inline +year_month_day_last +operator/(const month_day_last& mdl, int y) noexcept +{ + return year(y) / mdl; +} + +// year_month_weekday from operator/() + +CONSTCD11 +inline +year_month_weekday +operator/(const year_month& ym, const weekday_indexed& wdi) noexcept +{ + return {ym.year(), ym.month(), wdi}; +} + +CONSTCD11 +inline +year_month_weekday +operator/(const year& y, const month_weekday& mwd) noexcept +{ + return {y, mwd.month(), mwd.weekday_indexed()}; +} + +CONSTCD11 +inline +year_month_weekday +operator/(int y, const month_weekday& mwd) noexcept +{ + return year(y) / mwd; +} + +CONSTCD11 +inline +year_month_weekday +operator/(const month_weekday& mwd, const year& y) noexcept +{ + return y / mwd; +} + +CONSTCD11 +inline +year_month_weekday +operator/(const month_weekday& mwd, int y) noexcept +{ + return year(y) / mwd; +} + +// year_month_weekday_last from operator/() + +CONSTCD11 +inline +year_month_weekday_last +operator/(const year_month& ym, const weekday_last& wdl) noexcept +{ + return {ym.year(), ym.month(), wdl}; +} + +CONSTCD11 +inline +year_month_weekday_last +operator/(const year& y, const month_weekday_last& mwdl) noexcept +{ + return {y, mwdl.month(), mwdl.weekday()[last]}; +} + +CONSTCD11 +inline +year_month_weekday_last +operator/(int y, const month_weekday_last& mwdl) noexcept +{ + return year(y) / mwdl; +} + +CONSTCD11 +inline +year_month_weekday_last +operator/(const month_weekday_last& mwdl, const year& y) noexcept +{ + return y / mwdl; +} + +CONSTCD11 +inline +year_month_weekday_last +operator/(const month_weekday_last& mwdl, int y) noexcept +{ + return year(y) / mwdl; +} + +// time_of_day + +enum {am = 1, pm}; + +namespace detail +{ + +enum class classify +{ + hour, + minute, + second, + subsecond +}; + +template +struct classify_duration +{ + static CONSTDATA classify value = + Duration{1} >= std::chrono::hours{1} ? classify::hour : + Duration{1} >= std::chrono::minutes{1} ? classify::minute : + Duration{1} >= std::chrono::seconds{1} ? classify::second : + classify::subsecond; +}; + +class time_of_day_base +{ +protected: + std::chrono::hours h_; + unsigned char mode_; + + enum {is24hr}; + + CONSTCD11 time_of_day_base(std::chrono::hours h, unsigned m) noexcept + : h_(h) + , mode_(static_cast(m)) + {} + + CONSTCD14 void make24() noexcept; + CONSTCD14 void make12() noexcept; + + CONSTCD14 std::chrono::hours to24hr() const; +}; + +CONSTCD14 +inline +std::chrono::hours +time_of_day_base::to24hr() const +{ + auto h = h_; + if (mode_ == am || mode_ == pm) + { + CONSTDATA auto h12 = std::chrono::hours(12); + if (mode_ == pm) + { + if (h != h12) + h = h + h12; + } + else if (h == h12) + h = std::chrono::hours(0); + } + return h; +} + +CONSTCD14 +inline +void +time_of_day_base::make24() noexcept +{ + h_ = to24hr(); + mode_ = is24hr; +} + +CONSTCD14 +inline +void +time_of_day_base::make12() noexcept +{ + if (mode_ == is24hr) + { + CONSTDATA auto h12 = std::chrono::hours(12); + if (h_ >= h12) + { + if (h_ > h12) + h_ = h_ - h12; + mode_ = pm; + } + else + { + if (h_ == std::chrono::hours(0)) + h_ = h12; + mode_ = am; + } + } +} + +template ::value> +class time_of_day_storage; + +template +class time_of_day_storage, detail::classify::hour> + : private detail::time_of_day_base +{ + using base = detail::time_of_day_base; + +public: + using precision = std::chrono::hours; + + CONSTCD11 explicit time_of_day_storage(std::chrono::hours since_midnight) noexcept + : base(since_midnight, is24hr) + {} + + CONSTCD11 explicit time_of_day_storage(std::chrono::hours h, unsigned md) noexcept + : base(h, md) + {} + + CONSTCD11 std::chrono::hours hours() const noexcept {return h_;} + CONSTCD11 unsigned mode() const noexcept {return mode_;} + + CONSTCD14 explicit operator precision() const noexcept + { + return to24hr(); + } + + CONSTCD14 time_of_day_storage& make24() noexcept {base::make24(); return *this;} + CONSTCD14 time_of_day_storage& make12() noexcept {base::make12(); return *this;} + + friend + std::ostream& + operator<<(std::ostream& os, const time_of_day_storage& t) + { + using namespace std; + save_stream _(os); + os.fill('0'); + os.flags(std::ios::dec | std::ios::right); + if (t.mode_ != am && t.mode_ != pm) + os.width(2); + os << t.h_.count(); + switch (t.mode_) + { + case time_of_day_storage::is24hr: + os << "00"; + break; + case am: + os << "am"; + break; + case pm: + os << "pm"; + break; + } + return os; + } +}; + +template +class time_of_day_storage, detail::classify::minute> + : private detail::time_of_day_base +{ + using base = detail::time_of_day_base; + + std::chrono::minutes m_; + +public: + using precision = std::chrono::minutes; + + CONSTCD11 explicit time_of_day_storage(std::chrono::minutes since_midnight) noexcept + : base(std::chrono::duration_cast(since_midnight), is24hr) + , m_(since_midnight - h_) + {} + + CONSTCD11 explicit time_of_day_storage(std::chrono::hours h, std::chrono::minutes m, + unsigned md) noexcept + : base(h, md) + , m_(m) + {} + + CONSTCD11 std::chrono::hours hours() const noexcept {return h_;} + CONSTCD11 std::chrono::minutes minutes() const noexcept {return m_;} + CONSTCD11 unsigned mode() const noexcept {return mode_;} + + CONSTCD14 explicit operator precision() const noexcept + { + return to24hr() + m_; + } + + CONSTCD14 time_of_day_storage& make24() noexcept {base::make24(); return *this;} + CONSTCD14 time_of_day_storage& make12() noexcept {base::make12(); return *this;} + + friend + std::ostream& + operator<<(std::ostream& os, const time_of_day_storage& t) + { + using namespace std; + save_stream _(os); + if (t.h_ < std::chrono::hours{0}) + os << '-'; + os.fill('0'); + os.flags(std::ios::dec | std::ios::right); + if (t.mode_ != am && t.mode_ != pm) + os.width(2); + os << abs(t.h_.count()) << ':'; + os.width(2); + os << abs(t.m_.count()); + switch (t.mode_) + { + case am: + os << "am"; + break; + case pm: + os << "pm"; + break; + } + return os; + } +}; + +template +class time_of_day_storage, detail::classify::second> + : private detail::time_of_day_base +{ + using base = detail::time_of_day_base; + + std::chrono::minutes m_; + std::chrono::seconds s_; + +public: + using precision = std::chrono::seconds; + + CONSTCD11 explicit time_of_day_storage(std::chrono::seconds since_midnight) noexcept + : base(std::chrono::duration_cast(since_midnight), is24hr) + , m_(std::chrono::duration_cast(since_midnight - h_)) + , s_(since_midnight - h_ - m_) + {} + + CONSTCD11 explicit time_of_day_storage(std::chrono::hours h, std::chrono::minutes m, + std::chrono::seconds s, unsigned md) noexcept + : base(h, md) + , m_(m) + , s_(s) + {} + + CONSTCD11 std::chrono::hours hours() const noexcept {return h_;} + CONSTCD11 std::chrono::minutes minutes() const noexcept {return m_;} + CONSTCD11 std::chrono::seconds seconds() const noexcept {return s_;} + CONSTCD11 unsigned mode() const noexcept {return mode_;} + + CONSTCD14 explicit operator precision() const noexcept + { + return to24hr() + m_ + s_; + } + + CONSTCD14 time_of_day_storage& make24() noexcept {base::make24(); return *this;} + CONSTCD14 time_of_day_storage& make12() noexcept {base::make12(); return *this;} + + friend + std::ostream& + operator<<(std::ostream& os, const time_of_day_storage& t) + { + using namespace std; + save_stream _(os); + if (t.h_ < std::chrono::hours{0}) + os << '-'; + os.fill('0'); + os.flags(std::ios::dec | std::ios::right); + if (t.mode_ != am && t.mode_ != pm) + os.width(2); + os << abs(t.h_.count()) << ':'; + os.width(2); + os << abs(t.m_.count()) << ':'; + os.width(2); + os << abs(t.s_.count()); + switch (t.mode_) + { + case am: + os << "am"; + break; + case pm: + os << "pm"; + break; + } + return os; + } +}; + +template +class time_of_day_storage, detail::classify::subsecond> + : private detail::time_of_day_base +{ +public: + using precision = std::chrono::duration; + +private: + using base = detail::time_of_day_base; + + std::chrono::minutes m_; + std::chrono::seconds s_; + precision sub_s_; + +public: + CONSTCD11 explicit time_of_day_storage(precision since_midnight) noexcept + : base(std::chrono::duration_cast(since_midnight), is24hr) + , m_(std::chrono::duration_cast(since_midnight - h_)) + , s_(std::chrono::duration_cast(since_midnight - h_ - m_)) + , sub_s_(since_midnight - h_ - m_ - s_) + {} + + CONSTCD11 explicit time_of_day_storage(std::chrono::hours h, std::chrono::minutes m, + std::chrono::seconds s, precision sub_s, + unsigned md) noexcept + : base(h, md) + , m_(m) + , s_(s) + , sub_s_(sub_s) + {} + + CONSTCD11 std::chrono::hours hours() const noexcept {return h_;} + CONSTCD11 std::chrono::minutes minutes() const noexcept {return m_;} + CONSTCD11 std::chrono::seconds seconds() const noexcept {return s_;} + CONSTCD11 precision subseconds() const noexcept {return sub_s_;} + CONSTCD11 unsigned mode() const noexcept {return mode_;} + + CONSTCD14 explicit operator precision() const noexcept + { + return to24hr() + m_ + s_ + sub_s_; + } + + CONSTCD14 time_of_day_storage& make24() noexcept {base::make24(); return *this;} + CONSTCD14 time_of_day_storage& make12() noexcept {base::make12(); return *this;} + + friend + std::ostream& + operator<<(std::ostream& os, const time_of_day_storage& t) + { + using namespace std; + save_stream _(os); + if (t.h_ < std::chrono::hours{0}) + os << '-'; + os.fill('0'); + os.flags(std::ios::dec | std::ios::right); + if (t.mode_ != am && t.mode_ != pm) + os.width(2); + os << abs(t.h_.count()) << ':'; + os.width(2); + os << abs(t.m_.count()) << ':'; + os.width(2); + os << abs(t.s_.count()) << '.'; +#if __cplusplus >= 201402 + CONSTDATA auto cl10 = ceil_log10(Period::den); + using scale = std::ratio_multiply>; + os.width(cl10); + os << abs(t.sub_s_.count()) * scale::num / scale::den; +#else // __cplusplus >= 201402 + // inefficient sub-optimal run-time mess, but gets the job done + const unsigned long long cl10 = std::ceil(log10(Period::den)); + const auto p10 = std::pow(10., cl10); + os.width(cl10); + os << static_cast(abs(t.sub_s_.count()) + * Period::num * p10 / Period::den); +#endif // __cplusplus >= 201402 + switch (t.mode_) + { + case am: + os << "am"; + break; + case pm: + os << "pm"; + break; + } + return os; + } + +private: +#if __cplusplus >= 201402 + CONSTCD11 static int ceil_log10(unsigned long long i) noexcept + { + --i; + int n = 0; + if (i >= 10000000000000000) {i /= 10000000000000000; n += 16;} + if (i >= 100000000) {i /= 100000000; n += 8;} + if (i >= 10000) {i /= 10000; n += 4;} + if (i >= 100) {i /= 100; n += 2;} + if (i >= 10) {i /= 10; n += 1;} + if (i >= 1) {i /= 10; n += 1;} + return n; + } + + CONSTCD11 static unsigned long long pow10(unsigned y) noexcept + { + CONSTDATA unsigned long long p10[] = + { + 1ull, + 10ull, + 100ull, + 1000ull, + 10000ull, + 100000ull, + 1000000ull, + 10000000ull, + 100000000ull, + 1000000000ull, + 10000000000ull, + 100000000000ull, + 1000000000000ull, + 10000000000000ull, + 100000000000000ull, + 1000000000000000ull, + 10000000000000000ull, + 100000000000000000ull, + 1000000000000000000ull, + 10000000000000000000ull + }; + return p10[y]; + } +#endif // __cplusplus >= 201402 +}; + +} // namespace detail + +template +class time_of_day + : public detail::time_of_day_storage +{ + using base = detail::time_of_day_storage; +public: + using base::base; +}; + +template ::value>::type> +CONSTCD11 +inline +time_of_day> +make_time(std::chrono::duration d) noexcept +{ + return time_of_day>(d); +} + +CONSTCD11 +inline +time_of_day +make_time(std::chrono::hours h, unsigned md) noexcept +{ + return time_of_day(h, md); +} + +CONSTCD11 +inline +time_of_day +make_time(std::chrono::hours h, std::chrono::minutes m, unsigned md) noexcept +{ + return time_of_day(h, m, md); +} + +CONSTCD11 +inline +time_of_day +make_time(std::chrono::hours h, std::chrono::minutes m, std::chrono::seconds s, + unsigned md) noexcept +{ + return time_of_day(h, m, s, md); +} + +template >::value>::type> +CONSTCD11 +inline +time_of_day> +make_time(std::chrono::hours h, std::chrono::minutes m, std::chrono::seconds s, + std::chrono::duration sub_s, unsigned md) noexcept +{ + return time_of_day>(h, m, s, sub_s, md); +} + +template ::value>::type> +inline +std::ostream& +operator<<(std::ostream& os, + const std::chrono::time_point + >>& tp) +{ + auto const dp = floor(tp); + return os << year_month_day(dp) << ' ' << make_time(tp-dp); +} + +} // namespace date + +#endif // DATE_H diff --git a/tz.cpp b/tz.cpp new file mode 100644 index 0000000..6d23229 --- /dev/null +++ b/tz.cpp @@ -0,0 +1,1622 @@ +// Howard Hinnant +// This work is licensed under a Creative Commons Attribution 4.0 International License. +// http://creativecommons.org/licenses/by/4.0/ + +#include "tz_private.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace date +{ + +// +---------------------+ +// | Begin Configuration | +// +---------------------+ + +static std::string install{"/Users/howardhinnant/Downloads/tzdata2015e"}; + +static const std::vector files = +{ + "africa", "antarctica", "asia", "australasia", "etcetera", "europe", + "pacificnew", "northamerica", "southamerica", "systemv", "leapseconds" +}; + +// These can be used to reduce the range of the database to save memory +CONSTDATA auto min_year = date::year::min(); +CONSTDATA auto max_year = date::year::max(); + +// Arbitrary day of the year that will be away from any limits. +// Used with year::min() and year::max(). +CONSTDATA auto boring_day = date::aug/18; + +// +-------------------+ +// | End Configuration | +// +-------------------+ + +static_assert(min_year <= max_year, "Configuration error"); +#if __cplusplus >= 201402 +static_assert(boring_day.ok(), "Configuration error"); +#endif + +// Parsing helpers + +static +std::string +parse3(std::istream& in) +{ + std::string r(3, ' '); + ws(in); + r[0] = static_cast(in.get()); + r[1] = static_cast(in.get()); + r[2] = static_cast(in.get()); + return r; +} + +static +unsigned +parse_dow(std::istream& in) +{ + CONSTDATA const char* dow_names[] = + {"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"}; + auto s = parse3(in); + auto dow = std::find(std::begin(dow_names), std::end(dow_names), s) - dow_names; + if (dow >= std::end(dow_names) - std::begin(dow_names)) + throw std::runtime_error("oops: bad dow name: " + s); + return static_cast(dow); +} + +static +unsigned +parse_month(std::istream& in) +{ + CONSTDATA const char* month_names[] = + {"Jan", "Feb", "Mar", "Apr", "May", "Jun", + "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"}; + auto s = parse3(in); + auto m = std::find(std::begin(month_names), std::end(month_names), s) - month_names; + if (m >= std::end(month_names) - std::begin(month_names)) + throw std::runtime_error("oops: bad month name: " + s); + return static_cast(++m); +} + +static +std::chrono::seconds +parse_unsigned_time(std::istream& in) +{ + using namespace std::chrono; + int x; + in >> x; + auto r = seconds{hours{x}}; + if (!in.eof() && in.peek() == ':') + { + in.get(); + in >> x; + r += minutes{x}; + if (!in.eof() && in.peek() == ':') + { + in.get(); + in >> x; + r += seconds{x}; + } + } + return r; +} + +static +std::chrono::seconds +parse_signed_time(std::istream& in) +{ + ws(in); + auto sign = 1; + if (in.peek() == '-') + { + sign = -1; + in.get(); + } + else if (in.peek() == '+') + in.get(); + return sign * parse_unsigned_time(in); +} + +// MonthDayTime + +MonthDayTime::MonthDayTime(seconds_point tp, tz timezone) + : zone_(timezone) +{ + using namespace date; + const auto dp = floor(tp); + const auto hms = make_time(tp - dp); + const auto ymd = year_month_day(dp); + u = ymd.month() / ymd.day(); + h_ = hms.hours(); + m_ = hms.minutes(); + s_ = hms.seconds(); +} + +MonthDayTime::MonthDayTime(const date::month_day& md, tz timezone) + : zone_(timezone) +{ + u = md; +} + +date::day +MonthDayTime::day() const +{ + switch (type_) + { + case month_day: + return u.month_day_.day(); + case month_last_dow: + return date::day{31}; + case lteq: + case gteq: + return u.month_day_weekday_.month_day_.day(); + } +} + +date::month +MonthDayTime::month() const +{ + switch (type_) + { + case month_day: + return u.month_day_.month(); + case month_last_dow: + return u.month_weekday_last_.month(); + case lteq: + case gteq: + return u.month_day_weekday_.month_day_.month(); + } +} + +int +MonthDayTime::compare(date::year y, const MonthDayTime& x, date::year yx, + std::chrono::seconds offset, std::chrono::minutes prev_save) const +{ + if (zone_ != x.zone_) + { + auto dp0 = to_day_point(y); + auto dp1 = x.to_day_point(yx); + if (std::abs((dp0-dp1).count()) > 1) + return dp0 < dp1 ? -1 : 1; + if (zone_ == tz::local) + { + auto tp0 = to_time_point(y) - prev_save; + if (x.zone_ == tz::utc) + tp0 -= offset; + auto tp1 = x.to_time_point(yx); + return tp0 < tp1 ? -1 : tp0 == tp1 ? 0 : 1; + } + else if (zone_ == tz::standard) + { + auto tp0 = to_time_point(y); + auto tp1 = x.to_time_point(yx); + if (x.zone_ == tz::local) + tp1 -= prev_save; + else + tp0 -= offset; + return tp0 < tp1 ? -1 : tp0 == tp1 ? 0 : 1; + } + // zone_ == tz::utc + auto tp0 = to_time_point(y); + auto tp1 = x.to_time_point(yx); + if (x.zone_ == tz::local) + tp1 -= offset + prev_save; + else + tp1 -= offset; + return tp0 < tp1 ? -1 : tp0 == tp1 ? 0 : 1; + } + auto const t0 = to_time_point(y); + auto const t1 = x.to_time_point(yx); + return t0 < t1 ? -1 : t0 == t1 ? 0 : 1; +} + +seconds_point +MonthDayTime::to_sys(date::year y, std::chrono::seconds offset, + std::chrono::seconds save) const +{ + using namespace date; + using namespace std::chrono; + auto until_utc = to_time_point(y); + if (zone_ == tz::standard) + until_utc -= offset; + else if (zone_ == tz::local) + until_utc -= offset + save; + return until_utc; +} + +MonthDayTime::U& +MonthDayTime::U::operator=(const date::month_day& x) +{ + month_day_ = x; + return *this; +} + +MonthDayTime::U& +MonthDayTime::U::operator=(const date::month_weekday_last& x) +{ + month_weekday_last_ = x; + return *this; +} + +MonthDayTime::U& +MonthDayTime::U::operator=(const pair& x) +{ + month_day_weekday_ = x; + return *this; +} + +date::day_point +MonthDayTime::to_day_point(date::year y) const +{ + using namespace std::chrono; + using namespace date; + switch (type_) + { + case month_day: + return day_point(y/u.month_day_); + case month_last_dow: + return day_point(y/u.month_weekday_last_); + case lteq: + { + auto const x = y/u.month_day_weekday_.month_day_; + auto const wd1 = weekday(x); + auto const wd0 = u.month_day_weekday_.weekday_; + return day_point(x) - (wd1-wd0); + } + case gteq: + { + auto const x = y/u.month_day_weekday_.month_day_; + auto const wd1 = u.month_day_weekday_.weekday_; + auto const wd0 = weekday(x); + return day_point(x) + (wd1-wd0); + } + } +} + +seconds_point +MonthDayTime::to_time_point(date::year y) const +{ + return to_day_point(y) + h_ + m_ + s_; +} + +void +MonthDayTime::canonicalize(date::year y) +{ + using namespace std::chrono; + using namespace date; + switch (type_) + { + case month_day: + return; + case month_last_dow: + { + auto const ymd = year_month_day(y/u.month_weekday_last_); + u.month_day_ = ymd.month()/ymd.day(); + type_ = month_day; + return; + } + case lteq: + { + auto const x = y/u.month_day_weekday_.month_day_; + auto const wd1 = weekday(x); + auto const wd0 = u.month_day_weekday_.weekday_; + auto const ymd = year_month_day(day_point(x) - (wd1-wd0)); + u.month_day_ = ymd.month()/ymd.day(); + type_ = month_day; + return; + } + case gteq: + { + auto const x = y/u.month_day_weekday_.month_day_; + auto const wd1 = u.month_day_weekday_.weekday_; + auto const wd0 = weekday(x); + auto const ymd = year_month_day(day_point(x) + (wd1-wd0)); + u.month_day_ = ymd.month()/ymd.day(); + type_ = month_day; + return; + } + } +} + +std::istream& +operator>>(std::istream& is, MonthDayTime& x) +{ + using namespace date; + using namespace std::chrono; + x = MonthDayTime{}; + if (!is.eof() && ws(is) && !is.eof() && is.peek() != '#') + { + auto m = parse_month(is); + if (!is.eof() && ws(is) && !is.eof() && is.peek() != '#') + { + if (is.peek() == 'l') + { + for (int i = 0; i < 4; ++i) + is.get(); + auto dow = parse_dow(is); + x.type_ = MonthDayTime::month_last_dow; + x.u = date::month(m)/weekday(dow)[last]; + } + else if (std::isalpha(is.peek())) + { + auto dow = parse_dow(is); + char c; + is >> c; + if (c == '<' || c == '>') + { + char c2; + is >> c2; + if (c2 != '=') + throw std::runtime_error(std::string("bad operator: ") + c + c2); + int d; + is >> d; + if (d < 1 || d > 31) + throw std::runtime_error(std::string("bad operator: ") + c + c2 + + std::to_string(d)); + x.type_ = c == '<' ? MonthDayTime::lteq : MonthDayTime::gteq; + x.u = {date::month(m)/d, weekday(dow)}; + } + else + throw std::runtime_error(std::string("bad operator: ") + c); + } + else // if (std::isdigit(is.peek()) + { + int d; + is >> d; + if (d < 1 || d > 31) + throw std::runtime_error(std::string("day of month: ") + + std::to_string(d)); + x.type_ = MonthDayTime::month_day; + x.u = date::month(m)/d; + } + if (!is.eof() && ws(is) && !is.eof() && is.peek() != '#') + { + int t; + is >> t; + x.h_ = hours{t}; + if (!is.eof() && is.peek() == ':') + { + is.get(); + is >> t; + x.m_ = minutes{t}; + if (!is.eof() && is.peek() == ':') + { + is.get(); + is >> t; + x.s_ = seconds{t}; + } + } + if (!is.eof() && std::isalpha(is.peek())) + { + char c; + is >> c; + switch (c) + { + case 's': + x.zone_ = tz::standard; + break; + case 'u': + x.zone_ = tz::utc; + break; + } + } + } + } + else + { + x.u = month{m}/1; + } + } + return is; +} + +std::ostream& +operator<<(std::ostream& os, const MonthDayTime& x) +{ + switch (x.type_) + { + case MonthDayTime::month_day: + os << x.u.month_day_ << " "; + break; + case MonthDayTime::month_last_dow: + os << x.u.month_weekday_last_ << " "; + break; + case MonthDayTime::lteq: + os << x.u.month_day_weekday_.weekday_ << " on or before " + << x.u.month_day_weekday_.month_day_ << " "; + break; + case MonthDayTime::gteq: + if ((static_cast(x.day()) - 1) % 7 == 0) + { + os << (x.u.month_day_weekday_.month_day_.month() / + x.u.month_day_weekday_.weekday_[ + (static_cast(x.day()) - 1)/7+1]) << " "; + } + else + { + os << x.u.month_day_weekday_.weekday_ << " on or after " + << x.u.month_day_weekday_.month_day_ << " "; + } + break; + } + os << date::make_time(x.h_ + x.m_ + x.s_); + if (x.zone_ == tz::utc) + os << "UTC "; + else if (x.zone_ == tz::standard) + os << "STD "; + else + os << " "; + return os; +} + +// Rule + +Rule::Rule(const std::string& s) +{ + try + { + using namespace date; + using namespace std::chrono; + std::istringstream in(s); + in.exceptions(std::ios::failbit | std::ios::badbit); + std::string word; + in >> word >> name_; + int x; + ws(in); + if (std::isalpha(in.peek())) + { + in >> word; + if (word == "min") + { + starting_year_ = year::min(); + } + else + throw std::runtime_error("Didn't find expected word: " + word); + } + else + { + in >> x; + starting_year_ = year{x}; + } + std::ws(in); + if (std::isalpha(in.peek())) + { + in >> word; + if (word == "only") + { + ending_year_ = starting_year_; + } + else if (word == "max") + { + ending_year_ = year::max(); + } + else + throw std::runtime_error("Didn't find expected word: " + word); + } + else + { + in >> x; + ending_year_ = year{x}; + } + in >> word; // TYPE (always "-") + assert(word == "-"); + in >> starting_at_; + save_ = duration_cast(parse_signed_time(in)); + in >> abbrev_; + if (abbrev_ == "-") + abbrev_.clear(); + assert(hours{0} <= save_ && save_ <= hours{2}); + } + catch (...) + { + std::cerr << s << '\n'; + std::cerr << *this << '\n'; + throw; + } +} + +Rule::Rule(const Rule& r, date::year starting_year, date::year ending_year) + : name_(r.name_) + , starting_year_(starting_year) + , ending_year_(ending_year) + , starting_at_(r.starting_at_) + , save_(r.save_) + , abbrev_(r.abbrev_) +{ +} + +bool +operator==(const Rule& x, const Rule& y) +{ + if (std::tie(x.name_, x.save_, x.starting_year_, x.ending_year_) == + std::tie(y.name_, y.save_, y.starting_year_, y.ending_year_)) + return x.month() == y.month() && x.day() == y.day(); + return false; +} + +bool +operator<(const Rule& x, const Rule& y) +{ + using namespace std::chrono; + auto const xm = x.month(); + auto const ym = y.month(); + if (std::tie(x.name_, x.starting_year_, xm, x.ending_year_) < + std::tie(y.name_, y.starting_year_, ym, y.ending_year_)) + return true; + if (std::tie(x.name_, x.starting_year_, xm, x.ending_year_) > + std::tie(y.name_, y.starting_year_, ym, y.ending_year_)) + return false; + return x.day() < y.day(); +} + +bool +operator==(const Rule& x, const date::year& y) +{ + return x.starting_year_ <= y && y <= x.ending_year_; +} + +bool +operator<(const Rule& x, const date::year& y) +{ + return x.ending_year_ < y; +} + +bool +operator==(const date::year& x, const Rule& y) +{ + return y.starting_year_ <= x && x <= y.ending_year_; +} + +bool +operator<(const date::year& x, const Rule& y) +{ + return x < y.starting_year_; +} + +bool +operator==(const Rule& x, const std::string& y) +{ + return x.name() == y; +} + +bool +operator<(const Rule& x, const std::string& y) +{ + return x.name() < y; +} + +bool +operator==(const std::string& x, const Rule& y) +{ + return y.name() == x; +} + +bool +operator<(const std::string& x, const Rule& y) +{ + return x < y.name(); +} + +std::ostream& +operator<<(std::ostream& os, const Rule& r) +{ + using namespace date; + using namespace std::chrono; + save_stream _(os); + os.fill(' '); + os.flags(std::ios::dec | std::ios::left); + os.width(15); + os << r.name_; + os << r.starting_year_ << " " << r.ending_year_ << " "; + os << r.starting_at_; + if (r.save_ >= minutes{0}) + os << ' '; + os << date::make_time(r.save_) << " "; + os << r.abbrev_; + return os; +} + +date::day +Rule::day() const +{ + return starting_at_.day(); +} + +date::month +Rule::month() const +{ + return starting_at_.month(); +} + +struct find_rule_by_name +{ + bool operator()(const Rule& x, const std::string& nm) const + { + return x.name() < nm; + } + + bool operator()(const std::string& nm, const Rule& x) const + { + return nm < x.name(); + } +}; + +bool +Rule::overlaps(const Rule& x, const Rule& y) +{ + // assume x.starting_year_ <= y.starting_year_; + if (!(x.starting_year_ <= y.starting_year_)) + { + std::cerr << x << '\n'; + std::cerr << y << '\n'; + assert(x.starting_year_ <= y.starting_year_); + } + if (y.starting_year_ > x.ending_year_) + return false; + return !(x.starting_year_ == y.starting_year_ && x.ending_year_ == y.ending_year_); +} + +void +Rule::split(std::vector& rules, std::size_t i, std::size_t k, std::size_t& e) +{ + using namespace date; + using difference_type = std::vector::iterator::difference_type; + // rules[i].starting_year_ <= rules[k].starting_year_ && + // rules[i].ending_year_ >= rules[k].starting_year_ && + // (rules[i].starting_year_ != rules[k].starting_year_ || + // rules[i].ending_year_ != rules[k].ending_year_) + assert(rules[i].starting_year_ <= rules[k].starting_year_ && + rules[i].ending_year_ >= rules[k].starting_year_ && + (rules[i].starting_year_ != rules[k].starting_year_ || + rules[i].ending_year_ != rules[k].ending_year_)); + if (rules[i].starting_year_ == rules[k].starting_year_) + { + if (rules[k].ending_year_ < rules[i].ending_year_) + { + rules.insert(rules.begin() + static_cast(k+1), + Rule(rules[i], rules[k].ending_year_ + years{1}, + std::move(rules[i].ending_year_))); + ++e; + rules[i].ending_year_ = rules[k].ending_year_; + } + else // rules[k].ending_year_ > rules[i].ending_year_ + { + rules.insert(rules.begin() + static_cast(k+1), + Rule(rules[k], rules[i].ending_year_ + years{1}, + std::move(rules[k].ending_year_))); + ++e; + rules[k].ending_year_ = rules[i].ending_year_; + } + } + else // rules[i].starting_year_ < rules[k].starting_year_ + { + if (rules[k].ending_year_ < rules[i].ending_year_) + { + rules.insert(rules.begin() + static_cast(k), + Rule(rules[i], rules[k].starting_year_, rules[k].ending_year_)); + ++k; + rules.insert(rules.begin() + static_cast(k+1), + Rule(rules[i], rules[k].ending_year_ + years{1}, + std::move(rules[i].ending_year_))); + rules[i].ending_year_ = rules[k].starting_year_ - years{1}; + e += 2; + } + else if (rules[k].ending_year_ > rules[i].ending_year_) + { + rules.insert(rules.begin() + static_cast(k), + Rule(rules[i], rules[k].starting_year_, rules[i].ending_year_)); + ++k; + rules.insert(rules.begin() + static_cast(k+1), + Rule(rules[k], rules[i].ending_year_ + years{1}, + std::move(rules[k].ending_year_))); + e += 2; + rules[k].ending_year_ = std::move(rules[i].ending_year_); + rules[i].ending_year_ = rules[k].starting_year_ - years{1}; + } + else // rules[k].ending_year_ == rules[i].ending_year_ + { + rules.insert(rules.begin() + static_cast(k), + Rule(rules[i], rules[k].starting_year_, + std::move(rules[i].ending_year_))); + ++k; + ++e; + rules[i].ending_year_ = rules[k].starting_year_ - years{1}; + } + } +} + +void +Rule::split_overlaps(std::vector& rules, std::size_t i, std::size_t& e) +{ + using difference_type = std::vector::iterator::difference_type; + auto j = i; + for (; i + 1 < e; ++i) + { + for (auto k = i + 1; k < e; ++k) + { + if (overlaps(rules[i], rules[k])) + { + split(rules, i, k, e); + std::sort(rules.begin() + static_cast(i), + rules.begin() + static_cast(e)); + } + } + } + for (; j < e; ++j) + { + if (rules[j].starting_year() == rules[j].ending_year()) + rules[j].starting_at_.canonicalize(rules[j].starting_year()); + } +} + +void +Rule::split_overlaps(std::vector& rules) +{ + using difference_type = std::vector::iterator::difference_type; + for (std::size_t i = 0; i < rules.size();) + { + auto e = static_cast(std::upper_bound( + rules.cbegin()+static_cast(i), rules.cend(), rules[i].name(), + [](const std::string& nm, const Rule& x) + { + return nm < x.name(); + }) - rules.cbegin()); + split_overlaps(rules, i, e); + auto first = rules.cbegin() + static_cast(i); + auto last = rules.cbegin() + static_cast(e); + auto t = std::lower_bound(first, last, min_year); + if (t > first+1) + { + if (t == last || t->starting_year() >= min_year) + --t; + auto d = static_cast(t - first); + rules.erase(first, t); + e -= d; + } + first = rules.cbegin() + static_cast(i); + last = rules.cbegin() + static_cast(e); + t = std::upper_bound(first, last, max_year); + if (t != last) + { + auto d = static_cast(last - t); + rules.erase(t, last); + e -= d; + } + i = e; + } + rules.shrink_to_fit(); +} + +// Zone + +Zone::zonelet::~zonelet() +{ + if (tag_ == has_save) + u.save_.~decltype(u.save_)(); + else + u.rule_.~decltype(u.rule_)(); +} + +Zone::zonelet::zonelet() +{ + ::new(&u.rule_) std::string(); +} + +Zone::zonelet::zonelet(const zonelet& i) + : gmtoff_(i.gmtoff_) + , tag_(i.tag_) + , format_(i.format_) + , until_year_(i.until_year_) + , until_date_(i.until_date_) + , until_utc_(i.until_utc_) + , until_std_(i.until_std_) + , until_loc_(i.until_loc_) + , initial_save_(i.initial_save_) + , initial_abrev_(i.initial_abrev_) + , first_rule_(i.first_rule_) + , last_rule_(i.last_rule_) +{ + if (tag_ == has_save) + ::new(&u.save_) std::chrono::minutes(i.u.save_); + else + ::new(&u.rule_) std::string(i.u.rule_); +} + +Zone::Zone(const std::string& s) +{ + try + { + using namespace date; + std::istringstream in(s); + in.exceptions(std::ios::failbit | std::ios::badbit); + std::string word; + in >> word >> name_; + parse_info(in); + } + catch (...) + { + std::cerr << s << '\n'; + std::cerr << *this << '\n'; + zonelets_.pop_back(); + throw; + } +} + +void +Zone::add(const std::string& s) +{ + try + { + std::istringstream in(s); + in.exceptions(std::ios::failbit | std::ios::badbit); + ws(in); + if (!in.eof() && in.peek() != '#') + parse_info(in); + } + catch (...) + { + std::cerr << s << '\n'; + std::cerr << *this << '\n'; + zonelets_.pop_back(); + throw; + } +} + +void +Zone::parse_info(std::istream& in) +{ + using namespace date; + using namespace std::chrono; + zonelets_.emplace_back(); + auto& zonelet = zonelets_.back(); + zonelet.gmtoff_ = parse_signed_time(in); + in >> zonelet.u.rule_; + if (zonelet.u.rule_ == "-") + zonelet.u.rule_.clear(); + in >> zonelet.format_; + if (!in.eof()) + ws(in); + if (in.eof() || in.peek() == '#') + { + zonelet.until_year_ = year::max(); + zonelet.until_date_ = MonthDayTime(boring_day, tz::utc); + } + else + { + int y; + in >> y; + zonelet.until_year_ = year{y}; + in >> zonelet.until_date_; + zonelet.until_date_.canonicalize(zonelet.until_year_); + } + if ((zonelet.until_year_ < min_year) || + (zonelets_.size() > 1 && zonelets_.end()[-2].until_year_ > max_year)) + zonelets_.pop_back(); +} + +// Find the rule that comes chronologically before Rule r. For multi-year rules, +// y specifies which rules in r. For single year rules, y is assumed to be equal +// to the year specified by r. +// Returns a pointer to the chronologically previous rule, and the year within +// that rule. If there is no previous rule, returns nullptr and year::min(). +// Preconditions: +// r->starting_year() <= y && y <= r->ending_year() +static +std::pair +find_previous_rule(const Rule* r, date::year y) +{ + using namespace date; + auto const& rules = get_tzdb().rules; + if (y == r->starting_year()) + { + if (r == &rules.front() || r->name() != r[-1].name()) + return {nullptr, year::min()}; + --r; + if (y == r->starting_year()) + return {r, y}; + return {r, r->ending_year()}; + } + if (r == &rules.front() || r->name() != r[-1].name() || + r[-1].starting_year() < r->starting_year()) + { + while (r < &rules.back() && r->name() == r[1].name() && + r->starting_year() == r[1].starting_year()) + ++r; + return {r, --y}; + } + --r; + return {r, y}; +} + +// Find the rule that comes chronologically after Rule r. For multi-year rules, +// y specifies which rules in r. For single year rules, y is assumed to be equal +// to the year specified by r. +// Returns a pointer to the chronologically next rule, and the year within +// that rule. If there is no next rule, return a pointer to a defaulted rule +// and y+1. +// Preconditions: +// first <= r && r < last && r->starting_year() <= y && y <= r->ending_year() +// [first, last) all have the same name +static +std::pair +find_next_rule(const Rule* first, const Rule* last, const Rule* r, date::year y) +{ + using namespace date; + if (y == r->ending_year()) + { + if (r == last-1) + return {nullptr, year::max()}; + ++r; + if (y == r->ending_year()) + return {r, y}; + return {r, r->starting_year()}; + } + if (r == last-1 || r->ending_year() < r[1].ending_year()) + { + while (r > first && r->starting_year() == r[-1].starting_year()) + --r; + return {r, ++y}; + } + ++r; + return {r, y}; +} + +// Find the rule that comes chronologically after Rule r. For multi-year rules, +// y specifies which rules in r. For single year rules, y is assumed to be equal +// to the year specified by r. +// Returns a pointer to the chronologically next rule, and the year within +// that rule. If there is no next rule, return nullptr and year::max(). +// Preconditions: +// r->starting_year() <= y && y <= r->ending_year() +static +std::pair +find_next_rule(const Rule* r, date::year y) +{ + using namespace date; + auto const& rules = get_tzdb().rules; + if (y == r->ending_year()) + { + if (r == &rules.back() || r->name() != r[1].name()) + return {nullptr, year::max()}; + ++r; + if (y == r->ending_year()) + return {r, y}; + return {r, r->starting_year()}; + } + if (r == &rules.back() || r->name() != r[1].name() || + r->ending_year() < r[1].ending_year()) + { + while (r > &rules.front() && r->name() == r[-1].name() && + r->starting_year() == r[-1].starting_year()) + --r; + return {r, ++y}; + } + ++r; + return {r, y}; +} + +static +std::pair +find_rule_for_zone(const std::pair& eqr, + const date::year& y, const std::chrono::seconds& offset, + const MonthDayTime& mdt) +{ + using namespace std::chrono; + using namespace date; + auto r = eqr.first; + auto ry = r->starting_year(); + auto prev_save = minutes{0}; + auto prev_year = year::min(); + const Rule* prev_rule = nullptr; + while (r != nullptr) + { + if (mdt.compare(y, r->mdt(), ry, offset, prev_save) <= 0) + break; + prev_rule = r; + prev_year = ry; + prev_save = prev_rule->save(); + std::tie(r, ry) = find_next_rule(eqr.first, eqr.second, r, ry); + } + return {prev_rule, prev_year}; +} + +static +std::pair +find_rule_for_zone(const std::pair& eqr, + const seconds_point& tp_utc, const seconds_point& tp_std, + const seconds_point& tp_loc) +{ + using namespace std::chrono; + using namespace date; + auto r = eqr.first; + auto ry = r->starting_year(); + auto prev_save = minutes{0}; + auto prev_year = year::min(); + const Rule* prev_rule = nullptr; + while (r != nullptr) + { + bool found; + switch (r->mdt().zone()) + { + case tz::utc: + found = tp_utc < r->mdt().to_time_point(ry); + break; + case tz::standard: + found = tp_std < r->mdt().to_time_point(ry); + break; + case tz::local: + found = tp_loc < r->mdt().to_time_point(ry); + break; + default: + assert(false); + } + if (found) + break; + prev_rule = r; + prev_year = ry; + prev_save = prev_rule->save(); + std::tie(r, ry) = find_next_rule(eqr.first, eqr.second, r, ry); + } + return {prev_rule, prev_year}; +} + +static +Info +find_rule(const std::pair& first, + const std::pair& last, + const date::year& y, const std::chrono::seconds& offset, + const MonthDayTime& mdt, const std::chrono::minutes& initial_save, + const std::string& initial_abrev) +{ + using namespace std::chrono; + using namespace date; + auto r = first.first; + auto ry = first.second; + Info x{day_point(year::min()/boring_day), day_point(year::max()/boring_day), + seconds{0}, initial_save, initial_abrev}; + while (r != nullptr) + { + auto tr = r->mdt().to_sys(ry, offset, x.save); + auto tx = mdt.to_sys(y, offset, x.save); + // Find last rule where tx >= tr + if (tx <= tr || (r == last.first && ry == last.second)) + { + if (tx < tr && r == first.first && ry == first.second) + { + x.end = r->mdt().to_sys(ry, offset, x.save); + break; + } + if (tx < tr) + { + std::tie(r, ry) = find_previous_rule(r, ry); // can't return nullptr for r + assert(r != nullptr); + } + // r != nullptr && tx >= tr (if tr were to be recomputed) + auto prev_save = initial_save; + if (!(r == first.first && ry == first.second)) + prev_save = find_previous_rule(r, ry).first->save(); + x.begin = r->mdt().to_sys(ry, offset, prev_save); + x.save = r->save(); + x.abbrev = r->abbrev(); + if (!(r == last.first && ry == last.second)) + { + std::tie(r, ry) = find_next_rule(r, ry); // can't return nullptr for r + assert(r != nullptr); + x.end = r->mdt().to_sys(ry, offset, x.save); + } + else + x.end = day_point(year::max()/boring_day); + break; + } + x.save = r->save(); + std::tie(r, ry) = find_next_rule(r, ry); // Can't return nullptr for r + assert(r != nullptr); + } + return x; +} + +void +Zone::adjust_infos(const std::vector& rules) +{ + using namespace std::chrono; + using namespace date; + const zonelet* prev_zonelet = nullptr; + for (auto& z : zonelets_) + { + // Classify info as rule-based, has save, or neither + if (!z.u.rule_.empty()) + { + // Find out if this zonelet has a rule or a save + auto i = std::lower_bound(rules.begin(), rules.end(), z.u.rule_, + [](const Rule& r, const std::string& nm) + { + return r.name() < nm; + }); + if (i == rules.end() || i->name() != z.u.rule_) + { + // The rule doesn't exist. Assume this is a save + try + { + using namespace std::chrono; + std::istringstream in(z.u.rule_); + in.exceptions(std::ios::failbit | std::ios::badbit); + auto tmp = duration_cast(parse_signed_time(in)); + z.u.rule_.~decltype(z.u.rule_)(); + z.tag_ = zonelet::has_save; + ::new(&z.u.save_) minutes(tmp); + } + catch (...) + { + std::cerr << name_ << " : " << z.u.rule_ << '\n'; + throw; + } + } + } + else + { + // This zone::zonelet has no rule and no save + z.tag_ = zonelet::is_empty; + } + + std::pair eqr{}; + if (z.tag_ == zonelet::has_rule) + { + eqr = std::equal_range(rules.data(), rules.data() + rules.size(), z.u.rule_); + assert(eqr.first != eqr.second); + } + + minutes final_save{0}; + if (z.tag_ == zonelet::has_save) + { + final_save = z.u.save_; + } + else if (z.tag_ == zonelet::has_rule) + { + z.last_rule_ = find_rule_for_zone(eqr, z.until_year_, z.gmtoff_, + z.until_date_); + if (z.last_rule_.first != nullptr) + final_save = z.last_rule_.first->save(); + } + z.until_utc_ = z.until_date_.to_sys(z.until_year_, z.gmtoff_, final_save); + z.until_std_ = z.until_utc_ + z.gmtoff_; + z.until_loc_ = z.until_std_ + final_save; + + if (z.tag_ == zonelet::has_rule) + { + if (prev_zonelet != nullptr) + { + z.first_rule_ = find_rule_for_zone(eqr, prev_zonelet->until_utc_, + prev_zonelet->until_std_, + prev_zonelet->until_loc_); + if (z.first_rule_.first != nullptr) + { + z.initial_save_ = z.first_rule_.first->save(); + z.initial_abrev_ = z.first_rule_.first->abbrev(); + if (z.first_rule_ != z.last_rule_) + { + z.first_rule_ = find_next_rule(eqr.first, eqr.second, + z.first_rule_.first, + z.first_rule_.second); + } + else + { + z.first_rule_ = std::make_pair(nullptr, year::min()); + z.last_rule_ = std::make_pair(nullptr, year::max()); + } + } + } + if (z.first_rule_.first == nullptr && z.last_rule_.first != nullptr) + z.first_rule_ = std::make_pair(eqr.first, eqr.first->starting_year()); + } + +#ifndef NDEBUG + if (z.first_rule_.first == nullptr) + { + assert(z.first_rule_.second == year::min()); + assert(z.last_rule_.first == nullptr); + assert(z.last_rule_.second == year::max()); + } + else + { + assert(z.last_rule_.first != nullptr); + } +#endif + prev_zonelet = &z; + } +} + +Info +Zone::get_info(std::chrono::system_clock::time_point tp, tz timezone) const +{ + using namespace std::chrono; + using namespace date; + assert(timezone != tz::standard); + auto y = year_month_day(floor(tp)).year(); + if (y < min_year || y > max_year) + throw std::runtime_error("The year " + std::to_string(static_cast(y)) + + " is out of range:[" + std::to_string(static_cast(min_year)) + ", " + + std::to_string(static_cast(max_year)) + "]"); + auto i = std::upper_bound(zonelets_.begin(), zonelets_.end(), tp, + [timezone](std::chrono::system_clock::time_point t, const zonelet& zl) + { + return timezone == tz::utc ? t < zl.until_utc_ : t < zl.until_loc_; + }); + + Info r{}; + if (i != zonelets_.end()) + { + if (i->tag_ == zonelet::has_save) + { + r.begin = i != zonelets_.begin() ? i[-1].until_utc_ + : day_point(year::min()/boring_day); + r.end = i->until_utc_; + r.offset = i->gmtoff_ + i->u.save_; + r.save = i->u.save_; + r.abbrev = i->format_; + } + else if (i->u.rule_.empty()) + { + r.begin = i != zonelets_.begin() ? i[-1].until_utc_ + : day_point(year::min()/boring_day); + r.end = i->until_utc_; + r.offset = i->gmtoff_; + r.abbrev = i->format_; + } + else + { + r = find_rule(i->first_rule_, i->last_rule_, y, i->gmtoff_, + MonthDayTime(floor(tp), timezone), i->initial_save_, + i->initial_abrev_); + auto k = i->format_.find("%s"); + if (k != std::string::npos) + { + std::string abbrev = r.abbrev; + r.abbrev = i->format_; + r.abbrev.replace(k, 2, abbrev); + } + else + { + k = i->format_.find('/'); + if (k != std::string::npos) + { + if (r.save == seconds{0}) + r.abbrev = i->format_.substr(0, k); + else + r.abbrev = i->format_.substr(k+1); + } + else + { + r.abbrev = i->format_; + } + } + r.offset = i->gmtoff_ + r.save; + if (i != zonelets_.begin() && r.begin < i[-1].until_utc_) + r.begin = i[-1].until_utc_; + if (r.end > i->until_utc_) + r.end = i->until_utc_; + } + assert(r.begin < r.end); + } + return r; +} + +std::ostream& +operator<<(std::ostream& os, const Zone& z) +{ + using namespace date; + using namespace std::chrono; + save_stream _(os); + os.fill(' '); + os.flags(std::ios::dec | std::ios::left); + os.width(35); + os << z.name_; + std::string indent; + for (auto const& s : z.zonelets_) + { + os << indent; + if (s.gmtoff_ >= minutes{0}) + os << ' '; + os << make_time(s.gmtoff_) << " "; + os.width(15); + if (s.tag_ != Zone::zonelet::has_save) + os << s.u.rule_; + else + { + std::ostringstream tmp; + tmp << make_time(s.u.save_); + os << tmp.str(); + } + os.width(8); + os << s.format_ << " "; + os << s.until_year_ << ' ' << s.until_date_; + os << " " << s.until_utc_ << " UTC"; + os << " " << s.until_std_ << " STD"; + os << " " << s.until_loc_; + os << " " << make_time(s.initial_save_); + os << " " << s.initial_abrev_; + if (s.first_rule_.first != nullptr) + os << " {" << *s.first_rule_.first << ", " << s.first_rule_.second << '}'; + else + os << " {" << "nullptr" << ", " << s.first_rule_.second << '}'; + if (s.last_rule_.first != nullptr) + os << " {" << *s.last_rule_.first << ", " << s.last_rule_.second << '}'; + else + os << " {" << "nullptr" << ", " << s.last_rule_.second << '}'; + os << '\n'; + if (indent.empty()) + indent = std::string(35, ' '); + } + return os; +} + +// Link + +Link::Link(const std::string& s) +{ + using namespace date; + std::istringstream in(s); + in.exceptions(std::ios::failbit | std::ios::badbit); + std::string word; + in >> word >> target_ >> name_; +} + +std::ostream& +operator<<(std::ostream& os, const Link& x) +{ + using namespace date; + save_stream _(os); + os.fill(' '); + os.flags(std::ios::dec | std::ios::left); + os.width(35); + return os << x.name_ << " --> " << x.target_; +} + +// Leap + +Leap::Leap(const std::string& s) +{ + using namespace date; + std::istringstream in(s); + in.exceptions(std::ios::failbit | std::ios::badbit); + std::string word; + int y; + MonthDayTime date; + in >> word >> y >> date; + date_ = date.to_time_point(year(y)); +} + +std::ostream& +operator<<(std::ostream& os, const Leap& x) +{ + using namespace date; + return os << x.date_ << " +"; +} + +static +TZ_DB +init_tzdb() +{ + using namespace date; + const std::string path = install + "/"; + std::string line; + bool continue_zone = false; + TZ_DB db; + for (const auto& filename : files) + { + std::ifstream infile(path + filename); + while (infile) + { + std::getline(infile, line); + if (!line.empty() && line[0] != '#') + { + std::istringstream in(line); + std::string word; + in >> word; + if (word == "Rule") + { + db.rules.push_back(Rule(line)); + continue_zone = false; + } + else if (word == "Link") + { + db.links.push_back(Link(line)); + continue_zone = false; + } + else if (word == "Leap") + { + db.leaps.push_back(Leap(line)); + continue_zone = false; + } + else if (word == "Zone") + { + db.zones.push_back(Zone(line)); + continue_zone = true; + } + else if (line[0] == '\t' && continue_zone) + { + db.zones.back().add(line); + } + else + { + std::cerr << line << '\n'; + } + } + } + } + std::sort(db.rules.begin(), db.rules.end()); + Rule::split_overlaps(db.rules); + std::sort(db.zones.begin(), db.zones.end()); + for (auto& z : db.zones) + z.adjust_infos(db.rules); + db.zones.shrink_to_fit(); + std::sort(db.links.begin(), db.links.end()); + db.links.shrink_to_fit(); + std::sort(db.leaps.begin(), db.leaps.end()); + db.leaps.shrink_to_fit(); + return db; +} + +static +TZ_DB& +access_tzdb() +{ + static TZ_DB tz_db; + return tz_db; +} + +const TZ_DB& +reload_tzdb() +{ + return access_tzdb() = init_tzdb(); +} + +const TZ_DB& +reload_tzdb(const std::string& new_install) +{ + install = new_install; + return access_tzdb() = init_tzdb(); +} + +const TZ_DB& +get_tzdb() +{ + static const TZ_DB& ref = access_tzdb() = init_tzdb(); + return ref; +} + +const Zone* +locate_zone(const std::string& tz_name) +{ + const auto& db = get_tzdb(); + auto zi = std::lower_bound(db.zones.begin(), db.zones.end(), tz_name, + [](const Zone& z, const std::string& nm) + { + return z.name() < nm; + }); + if (zi == db.zones.end() || zi->name() != tz_name) + { + auto li = std::lower_bound(db.links.begin(), db.links.end(), tz_name, + [](const Link& z, const std::string& nm) + { + return z.name() < nm; + }); + if (li != db.links.end() && li->name() == tz_name) + { + zi = std::lower_bound(db.zones.begin(), db.zones.end(), li->target(), + [](const Zone& z, const std::string& nm) + { + return z.name() < nm; + }); + if (zi != db.zones.end() && zi->name() == li->target()) + return &*zi; + } + throw std::runtime_error(tz_name + " not found in timezone database"); + } + return &*zi; +} + +std::ostream& +operator<<(std::ostream& os, const TZ_DB& db) +{ + std::string title("--------------------------------------------" + "--------------------------------------------\n" + "Name ""Start Y ""End Y " + "Beginning ""Offset " + "Designator\n" + "--------------------------------------------" + "--------------------------------------------\n"); + int count = 0; + for (const auto& x : db.rules) + { + if (count++ % 50 == 0) + os << title; + os << x << '\n'; + } + os << '\n'; + title = std::string("---------------------------------------------------------" + "--------------------------------------------------------\n" + "Name ""Offset " + "Rule ""Abrev ""Until\n" + "---------------------------------------------------------" + "--------------------------------------------------------\n"); + count = 0; + for (const auto& x : db.zones) + { + if (count++ % 10 == 0) + os << title; + os << x << '\n'; + } + os << '\n'; + title = std::string("---------------------------------------------------------" + "--------------------------------------------------------\n" + "Alias ""To\n" + "---------------------------------------------------------" + "--------------------------------------------------------\n"); + count = 0; + for (const auto& x : db.links) + { + if (count++ % 45 == 0) + os << title; + os << x << '\n'; + } + os << '\n'; + title = std::string("---------------------------------------------------------" + "--------------------------------------------------------\n" + "Leap second on\n" + "---------------------------------------------------------" + "--------------------------------------------------------\n"); + os << title; + for (const auto& x : db.leaps) + os << x << '\n'; + return os; +} + +// ----------------------- + +std::ostream& +operator<<(std::ostream& os, const Info& r) +{ + using namespace date; + os << r.begin << '\n'; + os << r.end << '\n'; + os << make_time(r.offset) << "\n"; + os << make_time(r.save) << "\n"; + os << r.abbrev << '\n'; + return os; +} + +const Zone* +current_timezone() +{ + struct stat sb; + CONSTDATA auto timezone = "/etc/localtime"; + if (lstat(timezone, &sb) == -1 || sb.st_size == 0) + throw std::runtime_error("Could not get lstat on /etc/localtime"); + std::string result(sb.st_size, '\0'); + while (true) + { + auto sz = readlink(timezone, &result.front(), result.size()); + if (sz == -1) + throw std::runtime_error("readlink failure"); + auto tmp = result.size(); + result.resize(sz); + if (sz <= tmp) + break; + } + result.erase(0, 20); + return locate_zone(result); +} + +} // namespace date diff --git a/tz.h b/tz.h new file mode 100644 index 0000000..7c484ad --- /dev/null +++ b/tz.h @@ -0,0 +1,550 @@ +#ifndef TZ_H +#define TZ_H + +// Howard Hinnant +// This work is licensed under a Creative Commons Attribution 4.0 International License. +// http://creativecommons.org/licenses/by/4.0/ + +// Get more recent database at http://www.iana.org/time-zones + +// Questions: +// 1. Reload database. +// 4. Is the utc to sys renaming complete? Was it done correctly? + +#include "date.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace date +{ + +using seconds_point = std::chrono::time_point; + +enum class tz {utc, local, standard}; +enum class choose {earliest, latest}; + +class nonexistent_local_time + : public std::runtime_error +{ +public: + template + nonexistent_local_time(std::chrono::time_point> tp, + seconds_point first, const std::string& first_abrev, + seconds_point last, const std::string& last_abrev, + seconds_point time_sys); + +private: + template + static + std::string + make_msg(std::chrono::time_point> tp, + seconds_point first, const std::string& first_abrev, + seconds_point last, const std::string& last_abrev, + seconds_point time_sys); +}; + +template +inline +nonexistent_local_time::nonexistent_local_time( + std::chrono::time_point> tp, + seconds_point first, const std::string& first_abrev, + seconds_point last, const std::string& last_abrev, + seconds_point time_sys) + : std::runtime_error(make_msg(tp, first, first_abrev, last, last_abrev, time_sys)) + {} + +template +std::string +nonexistent_local_time::make_msg(std::chrono::time_point> tp, + seconds_point first, const std::string& first_abrev, + seconds_point last, const std::string& last_abrev, + seconds_point time_sys) +{ + using namespace date; + std::ostringstream os; + os << tp << " is in a gap between\n" + << first << ' ' << first_abrev << " and\n" + << last << ' ' << last_abrev + << " which are both equivalent to\n" + << time_sys << " UTC"; + return os.str(); +} + +class ambiguous_local_time + : public std::runtime_error +{ +public: + template + ambiguous_local_time(std::chrono::time_point> tp, + std::chrono::seconds first_offset, + const std::string& first_abrev, + std::chrono::seconds second_offset, + const std::string& second_abrev); + +private: + template + static + std::string + make_msg(std::chrono::time_point> tp, + std::chrono::seconds first_offset, const std::string& first_abrev, + std::chrono::seconds second_offset, const std::string& second_abrev); +}; + +template +inline +ambiguous_local_time::ambiguous_local_time( + std::chrono::time_point> tp, + std::chrono::seconds first_offset, + const std::string& first_abrev, + std::chrono::seconds second_offset, + const std::string& second_abrev) + : std::runtime_error(make_msg(tp, first_offset, first_abrev, second_offset, + second_abrev)) + {} + +template +std::string +ambiguous_local_time::make_msg(std::chrono::time_point> tp, + std::chrono::seconds first_offset, + const std::string& first_abrev, + std::chrono::seconds second_offset, + const std::string& second_abrev) +{ + using namespace date; + std::ostringstream os; + os << tp << " is ambiguous. It could be\n" + << tp << ' ' << first_abrev << " == " << tp - first_offset << " UTC or\n" + << tp << ' ' << second_abrev << " == " << tp - second_offset << " UTC"; + return os.str(); +} + +class Rule; + +struct Info +{ + seconds_point begin; + seconds_point end; + std::chrono::seconds offset; + std::chrono::minutes save; + std::string abbrev; +}; + +std::ostream& +operator<<(std::ostream& os, const Info& r); + +class Zone +{ +private: + struct zonelet; + + std::string name_; + std::vector zonelets_; + +public: + explicit Zone(const std::string& s); + + const std::string& name() const {return name_;} + Info get_info(std::chrono::system_clock::time_point tp, tz timezone) const; + + template + Info + get_info(std::chrono::time_point> tp, + tz timezone) const + { + using namespace std::chrono; + return get_info(floor(tp), timezone); + } + + template + std::chrono::time_point, + std::chrono::seconds>::type> + to_sys(std::chrono::time_point> tp) const; + + template + std::chrono::time_point, + std::chrono::seconds>::type> + to_sys(std::chrono::time_point> tp, + choose z) const; + + template + std::pair + < + std::chrono::time_point, + std::chrono::seconds>::type>, + std::string + > + to_local(std::chrono::time_point> tp) const; + + friend bool operator==(const Zone& x, const Zone& y); + friend bool operator< (const Zone& x, const Zone& y); + friend std::ostream& operator<<(std::ostream& os, const Zone& z); + + void add(const std::string& s); + void adjust_infos(const std::vector& rules); + +private: + void parse_info(std::istream& in); + + template + std::chrono::time_point, + std::chrono::seconds>::type> + to_sys_impl(std::chrono::time_point> tp, + choose z, std::integral_constant do_throw) const; +}; + +template +inline +std::chrono::time_point, + std::chrono::seconds>::type> +Zone::to_sys(std::chrono::time_point> tp) const +{ + return to_sys_impl(tp, choose{}, std::true_type{}); +} + +template +inline +std::chrono::time_point, + std::chrono::seconds>::type> +Zone::to_sys(std::chrono::time_point> tp, choose z) const +{ + return to_sys_impl(tp, z, std::false_type{}); +} + +template +inline +std::pair +< + std::chrono::time_point, + std::chrono::seconds>::type>, + std::string +> +Zone::to_local(std::chrono::time_point> tp) const +{ + auto const i = get_info(tp, tz::utc); + return {tp + i.offset, i.abbrev}; +} + +inline bool operator==(const Zone& x, const Zone& y) {return x.name_ == y.name_;} +inline bool operator< (const Zone& x, const Zone& y) {return x.name_ < y.name_;} + +inline bool operator!=(const Zone& x, const Zone& y) {return !(x == y);} +inline bool operator> (const Zone& x, const Zone& y) {return y < x;} +inline bool operator<=(const Zone& x, const Zone& y) {return !(y < x);} +inline bool operator>=(const Zone& x, const Zone& y) {return !(x < y);} + +template +std::chrono::time_point, + std::chrono::seconds>::type> +Zone::to_sys_impl(std::chrono::time_point> tp, + choose z, std::integral_constant do_throw) const +{ + using namespace date; + using namespace std::chrono; + auto i = get_info(tp, tz::local); + auto tp_sys = tp - i.offset; + if (tp_sys - i.begin <= days{1}) + { + if (tp < i.begin + i.offset) + { + if (do_throw) + { + auto prev = get_info(i.begin - seconds{1}, tz::utc); + throw nonexistent_local_time(tp, i.begin + prev.offset, prev.abbrev, + i.begin + i.offset, i.abbrev, i.begin); + } + return i.begin; + } + assert(tp >= i.begin + get_info(i.begin - seconds{1}, tz::utc).offset); + } + if (i.end - tp_sys <= days{1}) + { + assert(tp < i.end + i.offset); + auto next = get_info(i.end, tz::utc); + if (tp >= i.end + next.offset) + { + if (do_throw) + throw ambiguous_local_time(tp, i.offset, i.abbrev, + next.offset, next.abbrev); + if (z == choose::earliest) + return tp_sys; + return tp - next.offset; + } + } + return tp_sys; +} + +class Link +{ +private: + std::string name_; + std::string target_; +public: + explicit Link(const std::string& s); + + const std::string& name() const {return name_;} + const std::string& target() const {return target_;} + + friend bool operator==(const Link& x, const Link& y) {return x.name_ == y.name_;} + friend bool operator< (const Link& x, const Link& y) {return x.name_ < y.name_;} + + friend std::ostream& operator<<(std::ostream& os, const Link& x); +}; + +inline bool operator!=(const Link& x, const Link& y) {return !(x == y);} +inline bool operator> (const Link& x, const Link& y) {return y < x;} +inline bool operator<=(const Link& x, const Link& y) {return !(y < x);} +inline bool operator>=(const Link& x, const Link& y) {return !(x < y);} + +class Leap +{ +private: + seconds_point date_; + +public: + explicit Leap(const std::string& s); + + seconds_point date() const {return date_;} + + friend bool operator==(const Leap& x, const Leap& y) {return x.date_ == y.date_;} + friend bool operator< (const Leap& x, const Leap& y) {return x.date_ < y.date_;} + + template + friend + bool + operator==(const Leap& x, + const std::chrono::time_point& y) + { + return x.date_ == y; + } + + template + friend + bool + operator< (const Leap& x, + const std::chrono::time_point& y) + { + return x.date_ < y; + } + + template + friend + bool + operator< (const std::chrono::time_point& x, + const Leap& y) + { + return x < y.date_; + } + + friend std::ostream& operator<<(std::ostream& os, const Leap& x); +}; + +inline bool operator!=(const Leap& x, const Leap& y) {return !(x == y);} +inline bool operator> (const Leap& x, const Leap& y) {return y < x;} +inline bool operator<=(const Leap& x, const Leap& y) {return !(y < x);} +inline bool operator>=(const Leap& x, const Leap& y) {return !(x < y);} + +template +inline +bool +operator==(const std::chrono::time_point& x, + const Leap& y) +{ + return y == x; +} + +template +inline +bool +operator!=(const Leap& x, + const std::chrono::time_point& y) +{ + return !(x == y); +} + +template +inline +bool +operator!=(const std::chrono::time_point& x, + const Leap& y) +{ + return !(x == y); +} + +template +inline +bool +operator> (const Leap& x, + const std::chrono::time_point& y) +{ + return y < x; +} + +template +inline +bool +operator> (const std::chrono::time_point& x, + const Leap& y) +{ + return y < x; +} + +template +inline +bool +operator<=(const Leap& x, + const std::chrono::time_point& y) +{ + return !(y < x); +} + +template +inline +bool +operator<=(const std::chrono::time_point& x, + const Leap& y) +{ + return !(y < x); +} + +template +inline +bool +operator>=(const Leap& x, + const std::chrono::time_point& y) +{ + return !(x < y); +} + +template +inline +bool +operator>=(const std::chrono::time_point& x, + const Leap& y) +{ + return !(x < y); +} + +struct TZ_DB +{ + std::vector zones; + std::vector links; + std::vector leaps; + std::vector rules; + + TZ_DB() = default; + TZ_DB(TZ_DB&&) = default; + TZ_DB& operator=(TZ_DB&&) = default; +}; + +std::ostream& operator<<(std::ostream& os, const TZ_DB& db); + +const TZ_DB& get_tzdb(); +const TZ_DB& reload_tzdb(); +const TZ_DB& reload_tzdb(const std::string& new_install); + +const Zone* locate_zone(const std::string& tz_name); +const Zone* current_timezone(); + +class utc_clock +{ +public: + using duration = std::chrono::system_clock::duration; + using rep = duration::rep; + using period = duration::period; + using time_point = std::chrono::time_point; + static CONSTDATA bool is_steady = true; + + static time_point now() noexcept; + + template + static + std::chrono::time_point::type> + sys_to_utc(std::chrono::time_point t); + + template + static + std::chrono::time_point::type> + utc_to_sys(std::chrono::time_point t); +}; + +inline +utc_clock::time_point +utc_clock::now() noexcept +{ + using namespace std::chrono; + return sys_to_utc(system_clock::now()); +} + +template +std::chrono::time_point::type> +utc_clock::sys_to_utc(std::chrono::time_point t) +{ + using namespace std::chrono; + using duration = typename std::common_type::type; + using time_point = std::chrono::time_point; + auto const& leaps = get_tzdb().leaps; + auto const lt = std::upper_bound(leaps.begin(), leaps.end(), t); + return time_point{t.time_since_epoch() + seconds{lt-leaps.begin()}}; +} + +template +std::chrono::time_point::type> +utc_clock::utc_to_sys(std::chrono::time_point t) +{ + using namespace std::chrono; + using duration = typename std::common_type::type; + using time_point = std::chrono::time_point; + auto const& leaps = get_tzdb().leaps; + auto tp = time_point{t.time_since_epoch()}; + auto const lt = std::upper_bound(leaps.begin(), leaps.end(), tp); + tp -= seconds{lt-leaps.begin()}; + if (lt != leaps.begin() && tp + seconds{1} < lt[-1]) + tp += seconds{1}; + return tp; +} + +} // namespace date + +#endif // TZ_H diff --git a/tz_private.h b/tz_private.h new file mode 100644 index 0000000..838c04c --- /dev/null +++ b/tz_private.h @@ -0,0 +1,179 @@ +#ifndef TZ_PRIVATE_H +#define TZ_PRIVATE_H + +// Howard Hinnant +// This work is licensed under a Creative Commons Attribution 4.0 International License. +// http://creativecommons.org/licenses/by/4.0/ + +#include "tz.h" + +namespace date +{ + +class MonthDayTime +{ +private: + struct pair + { + date::month_day month_day_; + date::weekday weekday_; + }; + + enum Type {month_day, month_last_dow, lteq, gteq}; + + Type type_{month_day}; + union U + { + date::month_day month_day_; + date::month_weekday_last month_weekday_last_; + pair month_day_weekday_; + + U() : month_day_{date::jan/1} {} + U& operator=(const date::month_day& x); + U& operator=(const date::month_weekday_last& x); + U& operator=(const pair& x); + } u; + std::chrono::hours h_{0}; + std::chrono::minutes m_{0}; + std::chrono::seconds s_{0}; + tz zone_{tz::local}; + +public: + MonthDayTime() = default; + MonthDayTime(seconds_point tp, tz timezone); + MonthDayTime(const date::month_day& md, tz timezone); + + date::day day() const; + date::month month() const; + tz zone() const {return zone_;} + + void canonicalize(date::year y); + + seconds_point + to_sys(date::year y, std::chrono::seconds offset, std::chrono::seconds save) const; + date::day_point to_day_point(date::year y) const; + + seconds_point to_time_point(date::year y) const; + int compare(date::year y, const MonthDayTime& x, date::year yx, + std::chrono::seconds offset, std::chrono::minutes prev_save) const; + + friend std::istream& operator>>(std::istream& is, MonthDayTime& x); + friend std::ostream& operator<<(std::ostream& os, const MonthDayTime& x); +}; + +// A Rule specifies one or more set of datetimes without using an offset. +// Multiple dates are specified with multiple years. The years in effect +// go from starting_year_ to ending_year_, inclusive. starting_year_ <= +// ending_year_. save_ is ineffect for times from the specified time +// onward, including the specified time. When the specified time is +// local, it uses the save_ from the chronologically previous Rule, or if +// there is none, 0. + +class Rule +{ +private: + std::string name_; + date::year starting_year_{0}; + date::year ending_year_{0}; + MonthDayTime starting_at_; + std::chrono::minutes save_{0}; + std::string abbrev_; + +public: + Rule() = default; + explicit Rule(const std::string& s); + Rule(const Rule& r, date::year starting_year, date::year ending_year); + + const std::string& name() const {return name_;} + const std::string& abbrev() const {return abbrev_;} + + const MonthDayTime& mdt() const {return starting_at_;} + const date::year& starting_year() const {return starting_year_;} + const date::year& ending_year() const {return ending_year_;} + const std::chrono::minutes& save() const {return save_;} + + static void split_overlaps(std::vector& rules); + + friend bool operator==(const Rule& x, const Rule& y); + friend bool operator<(const Rule& x, const Rule& y); + friend bool operator==(const Rule& x, const date::year& y); + friend bool operator<(const Rule& x, const date::year& y); + friend bool operator==(const date::year& x, const Rule& y); + friend bool operator<(const date::year& x, const Rule& y); + friend bool operator==(const Rule& x, const std::string& y); + friend bool operator<(const Rule& x, const std::string& y); + friend bool operator==(const std::string& x, const Rule& y); + friend bool operator<(const std::string& x, const Rule& y); + + friend std::ostream& operator<<(std::ostream& os, const Rule& r); + +private: + date::day day() const; + date::month month() const; + static void split_overlaps(std::vector& rules, std::size_t i, std::size_t& e); + static bool overlaps(const Rule& x, const Rule& y); + static void split(std::vector& rules, std::size_t i, std::size_t k, + std::size_t& e); +}; + +inline bool operator!=(const Rule& x, const Rule& y) {return !(x == y);} +inline bool operator> (const Rule& x, const Rule& y) {return y < x;} +inline bool operator<=(const Rule& x, const Rule& y) {return !(y < x);} +inline bool operator>=(const Rule& x, const Rule& y) {return !(x < y);} + +inline bool operator!=(const Rule& x, const date::year& y) {return !(x == y);} +inline bool operator> (const Rule& x, const date::year& y) {return y < x;} +inline bool operator<=(const Rule& x, const date::year& y) {return !(y < x);} +inline bool operator>=(const Rule& x, const date::year& y) {return !(x < y);} + +inline bool operator!=(const date::year& x, const Rule& y) {return !(x == y);} +inline bool operator> (const date::year& x, const Rule& y) {return y < x;} +inline bool operator<=(const date::year& x, const Rule& y) {return !(y < x);} +inline bool operator>=(const date::year& x, const Rule& y) {return !(x < y);} + +inline bool operator!=(const Rule& x, const std::string& y) {return !(x == y);} +inline bool operator> (const Rule& x, const std::string& y) {return y < x;} +inline bool operator<=(const Rule& x, const std::string& y) {return !(y < x);} +inline bool operator>=(const Rule& x, const std::string& y) {return !(x < y);} + +inline bool operator!=(const std::string& x, const Rule& y) {return !(x == y);} +inline bool operator> (const std::string& x, const Rule& y) {return y < x;} +inline bool operator<=(const std::string& x, const Rule& y) {return !(y < x);} +inline bool operator>=(const std::string& x, const Rule& y) {return !(x < y);} + +struct Zone::zonelet +{ + enum tag {has_rule, has_save, is_empty}; + + std::chrono::seconds gmtoff_; + tag tag_ = has_rule; + union U + { + std::string rule_; + std::chrono::minutes save_; + + ~U() {} + U() {} + U(const U&) {} + U& operator=(const U&) = delete; + } u; + std::string format_; + date::year until_year_{0}; + MonthDayTime until_date_; + seconds_point until_utc_; + seconds_point until_std_; + seconds_point until_loc_; + std::chrono::minutes initial_save_{}; + std::string initial_abrev_; + std::pair first_rule_{nullptr, date::year::min()}; + std::pair last_rule_{nullptr, date::year::max()}; + + ~zonelet(); + zonelet(); + zonelet(const zonelet& i); + zonelet& operator=(const zonelet&) = delete; +}; + +} // namespace date + +#endif // TZ_PRIVATE_H