diff --git a/.gitignore b/.gitignore index 584b230..40f074e 100644 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,4 @@ *.*~ *.user .idea/ +cmake-build-debug/ diff --git a/CMakeLists.txt b/CMakeLists.txt index a8d01da..255a441 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -172,4 +172,5 @@ target_link_libraries(${PROJECT_NAME} pthread unbound unwind + curl dl) diff --git a/README.md b/README.md index 29f44a6..cacffc8 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,8 @@ Curently available Monero blockchain explorer websites have several limitations - are closed sourced, - are not available as hidden services, - provide only basic search capabilities, - - can't identify users outputs based on provided Monero address and viewkey. + - can't identify users outputs based on provided Monero address and viewkey, or private tx key, + - do not support Monero testnet, i.e., they dont work with RingCT transactions In this example, these limitations are addressed by development of @@ -18,7 +19,8 @@ an Onion Monero Blockchain Explorer. The example not only shows how to use Moner - [lmdb++](https://github.com/bendiken/lmdbxx) - C++ wrapper for the LMDB - [mstch](https://github.com/no1msd/mstch) - C++ {{mustache}} templates - [rapidjson](https://github.com/miloyip/rapidjson) - C++ JSON parser/generator - + - [date](https://github.com/HowardHinnant/date) - C++ date and time library + - [fmt](https://github.com/fmtlib/fmt) - Small, safe and fast string formatting library ## Address @@ -33,8 +35,8 @@ Tor users (testnet Monero network): Non tor users, can use its clearnet version (thanks to [Gingeropolous](https://github.com/Gingeropolous)): - [http://explore.MoneroWorld.com](http://explore.moneroworld.com) - - + + ## Onion Monero Blockchain Explorer features The key features of the Onion Monero Blockchain Explorer are @@ -45,7 +47,8 @@ The key features of the Onion Monero Blockchain Explorer are - made fully in C++, - the only explorer showing encrypted payments ID, - the only explorer with the ability to search by encrypted payments ID, tx public - and private keys, stealth addresses, input key images, + and private keys, stealth addresses, input key images, block timestamps + (UTC time, e.g., 2016-11-23 14:03:05) - the only explorer showing ring signatures, - the only explorer showing transaction extra field, - the only explorer showing public components of Monero addresses, @@ -53,13 +56,14 @@ The key features of the Onion Monero Blockchain Explorer are - the only explorer that can be used to prove that you send Monero to someone, - the only explorer showing detailed information about mixins, such as, mixins' age, timescale, mixin of mixins, - - the only explorer showing number of amount output indices. + - the only explorer showing number of amount output indices, + - the only explorer supporting Monero testnet network and RingCT. ## Prerequisite Everything here was done and tested using Monero 0.9.4 on Ubuntu 16.04 x86_64. -Instruction for Monero 0.9 compilation and Monero headers and libraries setup are +Instruction for Monero 0.10 compilation and Monero headers and libraries setup are as shown here: - [Compile Monero 0.9 on Ubuntu 16.04 x64](https://github.com/moneroexamples/compile-monero-09-on-ubuntu-16-04) - [lmdbcpp-monero](https://github.com/moneroexamples/lmdbcpp-monero.git) @@ -230,8 +234,11 @@ git clone https://github.com/moneroexamples/onion-monero-blockchain-explorer.git # enter the downloaded sourced code folder cd onion-monero-blockchain-explorer +# make a build folder and enter it +mkdir build && cd build + # create the makefile -cmake . +cmake .. # compile make diff --git a/ext/CMakeLists.txt b/ext/CMakeLists.txt index 1340cc1..13be6f0 100644 --- a/ext/CMakeLists.txt +++ b/ext/CMakeLists.txt @@ -8,11 +8,11 @@ project(myext) set(SOURCE_HEADERS minicsv.h format.h - dateparser.h) + ) set(SOURCE_FILES format.cc - dateparser.cpp) + date/tz.cpp) # make static library called libmyxrm # that we are going to link to diff --git a/ext/date/chrono_io.h b/ext/date/chrono_io.h new file mode 100644 index 0000000..d66a4f2 --- /dev/null +++ b/ext/date/chrono_io.h @@ -0,0 +1,668 @@ +#ifndef CHRONO_IO_H +#define CHRONO_IO_H + +// The MIT License (MIT) +// +// Copyright (c) 2016 Howard Hinnant +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +// +// Our apologies. When the previous paragraph was written, lowercase had not yet +// been invented (that woud involve another several millennia of evolution). +// We did not mean to shout. + +#include +#include +#include +#include +#include +#include +#include + +namespace date +{ + +namespace detail +{ + +#if __cplusplus >= 201402 + +template +class string_literal +{ + CharT p_[N]; + +public: + using const_iterator = const CharT*; + + string_literal(string_literal const&) = default; + string_literal& operator=(string_literal const&) = delete; + + template > + constexpr string_literal(CharT c) noexcept + : p_{c} + { + } + + constexpr string_literal(const CharT(&a)[N]) noexcept + : p_{} + { + for (std::size_t i = 0; i < N; ++i) + p_[i] = a[i]; + } + + template > + constexpr string_literal(const char(&a)[N]) noexcept + : p_{} + { + for (std::size_t i = 0; i < N; ++i) + p_[i] = a[i]; + } + + template {}>> + constexpr string_literal(string_literal const& a) noexcept + : p_{} + { + for (std::size_t i = 0; i < N; ++i) + p_[i] = a[i]; + } + + template > + constexpr string_literal(const string_literal& x, + const string_literal& y) noexcept + : p_{} + { + std::size_t i = 0; + for (; i < N1-1; ++i) + p_[i] = x[i]; + for (std::size_t j = 0; j < N2; ++j, ++i) + p_[i] = y[j]; + } + + constexpr const CharT* data() const noexcept {return p_;} + constexpr std::size_t size() const noexcept {return N-1;} + + constexpr const_iterator begin() const noexcept {return p_;} + constexpr const_iterator end() const noexcept {return p_ + N-1;} + + constexpr CharT const& operator[](std::size_t n) const noexcept + { + return p_[n]; + } + + template + friend + std::basic_ostream& + operator<<(std::basic_ostream& os, const string_literal& s) + { + return os << s.p_; + } +}; + +template +constexpr +inline +string_literal, + N1 + N2 - 1> +operator+(const string_literal& x, const string_literal& y) noexcept +{ + using CharT = std::conditional_t; + return string_literal{string_literal{x}, + string_literal{y}}; +} + +template +constexpr +inline +string_literal +msl(const CharT(&a)[N]) noexcept +{ + return string_literal{a}; +} + +template {} || + std::is_same{} || + std::is_same{} || + std::is_same{}>> +constexpr +inline +string_literal +msl(CharT c) noexcept +{ + return string_literal{c}; +} + +constexpr +std::size_t +to_string_len(std::intmax_t i) +{ + std::size_t r = 0; + do + { + i /= 10; + ++r; + } while (i > 0); + return r; +} + +template +constexpr +inline +std::enable_if_t +< + N < 10, + string_literal +> +msl() noexcept +{ + return msl(char(N % 10 + '0')); +} + +template +constexpr +inline +std::enable_if_t +< + 10 <= N, + string_literal +> +msl() noexcept +{ + return msl() + msl(char(N % 10 + '0')); +} + +template +constexpr +inline +std::enable_if_t +< + std::ratio::type::den != 1, + string_literal::type::num) + + to_string_len(std::ratio::type::den) + 4> +> +msl(std::ratio) noexcept +{ + using R = typename std::ratio::type; + return msl(CharT{'['}) + msl() + msl(CharT{'/'}) + + msl() + msl(CharT{']'}); +} + +template +constexpr +inline +std::enable_if_t +< + std::ratio::type::den == 1, + string_literal::type::num) + 3> +> +msl(std::ratio) noexcept +{ + using R = typename std::ratio::type; + return msl(CharT{'['}) + msl() + msl(CharT{']'}); +} + +template +constexpr +inline +auto +msl(std::atto) noexcept +{ + return msl(CharT{'a'}); +} + +template +constexpr +inline +auto +msl(std::femto) noexcept +{ + return msl(CharT{'f'}); +} + +template +constexpr +inline +auto +msl(std::pico) noexcept +{ + return msl(CharT{'p'}); +} + +template +constexpr +inline +auto +msl(std::nano) noexcept +{ + return msl(CharT{'n'}); +} + +template +constexpr +inline +std::enable_if_t +< + std::is_same{}, + string_literal +> +msl(std::micro) noexcept +{ + return string_literal{"\xC2\xB5"}; +} + +template +constexpr +inline +std::enable_if_t +< + !std::is_same{}, + string_literal +> +msl(std::micro) noexcept +{ + return string_literal{CharT{static_cast('\xB5')}}; +} + +template +constexpr +inline +auto +msl(std::milli) noexcept +{ + return msl(CharT{'m'}); +} + +template +constexpr +inline +auto +msl(std::centi) noexcept +{ + return msl(CharT{'c'}); +} + +template +constexpr +inline +auto +msl(std::deci) noexcept +{ + return msl(CharT{'d'}); +} + +template +constexpr +inline +auto +msl(std::deca) noexcept +{ + return string_literal{"da"}; +} + +template +constexpr +inline +auto +msl(std::hecto) noexcept +{ + return msl(CharT{'h'}); +} + +template +constexpr +inline +auto +msl(std::kilo) noexcept +{ + return msl(CharT{'k'}); +} + +template +constexpr +inline +auto +msl(std::mega) noexcept +{ + return msl(CharT{'M'}); +} + +template +constexpr +inline +auto +msl(std::giga) noexcept +{ + return msl(CharT{'G'}); +} + +template +constexpr +inline +auto +msl(std::tera) noexcept +{ + return msl(CharT{'T'}); +} + +template +constexpr +inline +auto +msl(std::peta) noexcept +{ + return msl(CharT{'P'}); +} + +template +constexpr +inline +auto +msl(std::exa) noexcept +{ + return msl(CharT{'E'}); +} + +template +constexpr +auto +get_units(Period p) +{ + return msl(p) + string_literal{"s"}; +} + +template +constexpr +auto +get_units(std::ratio<1>) +{ + return string_literal{"s"}; +} + +template +constexpr +auto +get_units(std::ratio<60>) +{ + return string_literal{"min"}; +} + +template +constexpr +auto +get_units(std::ratio<3600>) +{ + return string_literal{"h"}; +} + +#else // __cplusplus < 201402 + +inline +std::string +to_string(std::uint64_t x) +{ + return std::to_string(x); +} + +template +std::basic_string +to_string(std::uint64_t x) +{ + auto y = std::to_string(x); + return std::basic_string(y.begin(), y.end()); +} + +template +constexpr +inline +typename std::enable_if +< + std::ratio::type::den != 1, + std::basic_string +>::type +msl(std::ratio) noexcept +{ + using R = typename std::ratio::type; + return std::basic_string(1, '[') + to_string(R::num) + CharT{'/'} + + to_string(R::den) + CharT{']'}; +} + +template +constexpr +inline +typename std::enable_if +< + std::ratio::type::den == 1, + std::basic_string +>::type +msl(std::ratio) noexcept +{ + using R = typename std::ratio::type; + return std::basic_string(1, '[') + to_string(R::num) + CharT{']'}; +} + +template +constexpr +inline +std::basic_string +msl(std::atto) noexcept +{ + return {'a'}; +} + +template +constexpr +inline +std::basic_string +msl(std::femto) noexcept +{ + return {'f'}; +} + +template +constexpr +inline +std::basic_string +msl(std::pico) noexcept +{ + return {'p'}; +} + +template +constexpr +inline +std::basic_string +msl(std::nano) noexcept +{ + return {'n'}; +} + +template +constexpr +inline +typename std::enable_if +< + std::is_same::value, + std::string +>::type +msl(std::micro) noexcept +{ + return "\xC2\xB5"; +} + +template +constexpr +inline +typename std::enable_if +< + !std::is_same::value, + std::basic_string +>::type +msl(std::micro) noexcept +{ + return {CharT(static_cast('\xB5'))}; +} + +template +constexpr +inline +std::basic_string +msl(std::milli) noexcept +{ + return {'m'}; +} + +template +constexpr +inline +std::basic_string +msl(std::centi) noexcept +{ + return {'c'}; +} + +template +constexpr +inline +std::basic_string +msl(std::deci) noexcept +{ + return {'d'}; +} + +template +constexpr +inline +std::basic_string +msl(std::deca) noexcept +{ + return {'d', 'a'}; +} + +template +constexpr +inline +std::basic_string +msl(std::hecto) noexcept +{ + return {'h'}; +} + +template +constexpr +inline +std::basic_string +msl(std::kilo) noexcept +{ + return {'k'}; +} + +template +constexpr +inline +std::basic_string +msl(std::mega) noexcept +{ + return {'M'}; +} + +template +constexpr +inline +std::basic_string +msl(std::giga) noexcept +{ + return {'G'}; +} + +template +constexpr +inline +std::basic_string +msl(std::tera) noexcept +{ + return {'T'}; +} + +template +constexpr +inline +std::basic_string +msl(std::peta) noexcept +{ + return {'P'}; +} + +template +constexpr +inline +std::basic_string +msl(std::exa) noexcept +{ + return {'E'}; +} + +template +std::basic_string +get_units(Period p) +{ + return msl(p) + CharT{'s'}; +} + +template +std::basic_string +get_units(std::ratio<1>) +{ + return {'s'}; +} + +template +std::basic_string +get_units(std::ratio<60>) +{ + return {'m', 'i', 'n'}; +} + +template +std::basic_string +get_units(std::ratio<3600>) +{ + return {'h'}; +} + +#endif // __cplusplus >= 201402 + +} // namespace detail + +template +inline +std::basic_ostream& +operator<<(std::basic_ostream& os, + const std::chrono::duration& d) +{ + using namespace std::chrono; + return os << d.count() + << detail::get_units(typename Period::type{}); +} + +} // namespace date + +#endif // CHRONO_IO_H diff --git a/ext/date/date.h b/ext/date/date.h new file mode 100644 index 0000000..5ff8f01 --- /dev/null +++ b/ext/date/date.h @@ -0,0 +1,4823 @@ +#ifndef DATE_H +#define DATE_H + +// The MIT License (MIT) +// +// Copyright (c) 2015, 2016 Howard Hinnant +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +// +// Our apologies. When the previous paragraph was written, lowercase had not yet +// been invented (that woud involve another several millennia of evolution). +// We did not mean to shout. + +#include +#include +#if !(__cplusplus >= 201402) +# include +#endif +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace date +{ + +//---------------+ +// Configuration | +//---------------+ + +// MSVC's constexpr support is still a WIP, even in VS2015. +// Fall back to a lesser mode to support it. +// TODO: Remove this or retest later once MSVC's constexpr improves. +#if defined(_MSC_VER) && ! defined(__clang__) +// MS cl compiler. +# define CONSTDATA const +# define CONSTCD11 +# define CONSTCD14 +# define NOEXCEPT _NOEXCEPT +#elif __cplusplus >= 201402 +// C++14 +# define CONSTDATA constexpr +# define CONSTCD11 constexpr +# define CONSTCD14 constexpr +# define NOEXCEPT noexcept +#else +// C++11 +# define CONSTDATA constexpr +# define CONSTCD11 constexpr +# define CONSTCD14 +# define NOEXCEPT noexcept +#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 + +template + using sys_time = std::chrono::time_point; + +using sys_days = sys_time; +using sys_seconds = sys_time; + +struct local_t {}; + +template + using local_time = std::chrono::time_point; + +using local_seconds = local_time; +using local_days = local_time; + +// types + +struct last_spec +{ + explicit last_spec() = default; +}; + +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: + day() = default; + explicit CONSTCD11 day(unsigned d) NOEXCEPT; + + CONSTCD14 day& operator++() NOEXCEPT; + CONSTCD14 day operator++(int) NOEXCEPT; + CONSTCD14 day& operator--() NOEXCEPT; + CONSTCD14 day operator--(int) NOEXCEPT; + + CONSTCD14 day& operator+=(const days& d) NOEXCEPT; + CONSTCD14 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; + +template +std::basic_ostream& +operator<<(std::basic_ostream& os, const day& d); + +// month + +class month +{ + unsigned char m_; + +public: + month() = default; + explicit CONSTCD11 month(unsigned m) NOEXCEPT; + + CONSTCD14 month& operator++() NOEXCEPT; + CONSTCD14 month operator++(int) NOEXCEPT; + CONSTCD14 month& operator--() NOEXCEPT; + CONSTCD14 month operator--(int) NOEXCEPT; + + CONSTCD14 month& operator+=(const months& m) NOEXCEPT; + CONSTCD14 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; + +template +std::basic_ostream& +operator<<(std::basic_ostream& os, const month& m); + +// year + +class year +{ + short y_; + +public: + year() = default; + explicit CONSTCD11 year(int y) NOEXCEPT; + + CONSTCD14 year& operator++() NOEXCEPT; + CONSTCD14 year operator++(int) NOEXCEPT; + CONSTCD14 year& operator--() NOEXCEPT; + CONSTCD14 year operator--(int) NOEXCEPT; + + CONSTCD14 year& operator+=(const years& y) NOEXCEPT; + CONSTCD14 year& operator-=(const years& y) NOEXCEPT; + + CONSTCD11 year operator-() const NOEXCEPT; + CONSTCD11 year operator+() const 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; + +template +std::basic_ostream& +operator<<(std::basic_ostream& os, const year& y); + +// weekday + +class weekday +{ + unsigned char wd_; +public: + weekday() = default; + explicit CONSTCD11 weekday(unsigned wd) NOEXCEPT; + explicit weekday(int) = delete; + CONSTCD11 weekday(const sys_days& dp) NOEXCEPT; + CONSTCD11 explicit weekday(const local_days& dp) NOEXCEPT; + + CONSTCD14 weekday& operator++() NOEXCEPT; + CONSTCD14 weekday operator++(int) NOEXCEPT; + CONSTCD14 weekday& operator--() NOEXCEPT; + CONSTCD14 weekday operator--(int) NOEXCEPT; + + CONSTCD14 weekday& operator+=(const days& d) NOEXCEPT; + CONSTCD14 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; + +template +std::basic_ostream& +operator<<(std::basic_ostream& os, const weekday& wd); + +// 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; + +template +std::basic_ostream& +operator<<(std::basic_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; + +template +std::basic_ostream& +operator<<(std::basic_ostream& os, const weekday_last& wdl); + +// year_month + +class year_month +{ + date::year y_; + date::month m_; + +public: + year_month() = default; + CONSTCD11 year_month(const date::year& y, const date::month& m) NOEXCEPT; + + CONSTCD11 date::year year() const NOEXCEPT; + CONSTCD11 date::month month() const NOEXCEPT; + + CONSTCD14 year_month& operator+=(const months& dm) NOEXCEPT; + CONSTCD14 year_month& operator-=(const months& dm) NOEXCEPT; + CONSTCD14 year_month& operator+=(const years& dy) NOEXCEPT; + CONSTCD14 year_month& operator-=(const years& dy) 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; + +template +std::basic_ostream& +operator<<(std::basic_ostream& os, const year_month& ym); + +// month_day + +class month_day +{ + date::month m_; + date::day d_; + +public: + month_day() = default; + 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; + +template +std::basic_ostream& +operator<<(std::basic_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; + +template +std::basic_ostream& +operator<<(std::basic_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; + +template +std::basic_ostream& +operator<<(std::basic_ostream& os, const month_weekday& mwd); + +// month_weekday_last + +class month_weekday_last +{ + date::month m_; + date::weekday_last wdl_; + +public: + CONSTCD11 month_weekday_last(const date::month& m, + const date::weekday_last& wd) NOEXCEPT; + + CONSTCD11 date::month month() const NOEXCEPT; + CONSTCD11 date::weekday_last weekday_last() 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; + +template +std::basic_ostream& +operator<<(std::basic_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: + year_month_day() = default; + 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(sys_days dp) NOEXCEPT; + CONSTCD14 explicit year_month_day(local_days dp) NOEXCEPT; + + CONSTCD14 year_month_day& operator+=(const months& m) NOEXCEPT; + CONSTCD14 year_month_day& operator-=(const months& m) NOEXCEPT; + CONSTCD14 year_month_day& operator+=(const years& y) NOEXCEPT; + CONSTCD14 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 sys_days() const NOEXCEPT; + CONSTCD14 explicit operator local_days() const NOEXCEPT; + CONSTCD14 bool ok() const NOEXCEPT; + +private: + static CONSTCD14 year_month_day from_days(days dp) NOEXCEPT; + CONSTCD14 days to_days() const 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; + +template +std::basic_ostream& +operator<<(std::basic_ostream& os, const year_month_day& ymd); + +// year_month_day_last + +class year_month_day_last +{ + date::year y_; + date::month_day_last mdl_; + +public: + CONSTCD11 year_month_day_last(const date::year& y, + const date::month_day_last& mdl) NOEXCEPT; + + CONSTCD14 year_month_day_last& operator+=(const months& m) NOEXCEPT; + CONSTCD14 year_month_day_last& operator-=(const months& m) NOEXCEPT; + CONSTCD14 year_month_day_last& operator+=(const years& y) NOEXCEPT; + CONSTCD14 year_month_day_last& operator-=(const years& y) NOEXCEPT; + + CONSTCD11 date::year year() const NOEXCEPT; + CONSTCD11 date::month month() const NOEXCEPT; + CONSTCD11 date::month_day_last month_day_last() const NOEXCEPT; + CONSTCD14 date::day day() const NOEXCEPT; + + CONSTCD14 operator sys_days() const NOEXCEPT; + CONSTCD14 explicit operator local_days() 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; + +template +std::basic_ostream& +operator<<(std::basic_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: + year_month_weekday() = default; + CONSTCD11 year_month_weekday(const date::year& y, const date::month& m, + const date::weekday_indexed& wdi) NOEXCEPT; + CONSTCD14 year_month_weekday(const sys_days& dp) NOEXCEPT; + CONSTCD14 explicit year_month_weekday(const local_days& dp) NOEXCEPT; + + CONSTCD14 year_month_weekday& operator+=(const months& m) NOEXCEPT; + CONSTCD14 year_month_weekday& operator-=(const months& m) NOEXCEPT; + CONSTCD14 year_month_weekday& operator+=(const years& y) NOEXCEPT; + CONSTCD14 year_month_weekday& operator-=(const years& y) NOEXCEPT; + + CONSTCD11 date::year year() const NOEXCEPT; + CONSTCD11 date::month month() const NOEXCEPT; + CONSTCD11 date::weekday weekday() const NOEXCEPT; + CONSTCD11 unsigned index() const NOEXCEPT; + CONSTCD11 date::weekday_indexed weekday_indexed() const NOEXCEPT; + + CONSTCD14 operator sys_days() const NOEXCEPT; + CONSTCD14 explicit operator local_days() const NOEXCEPT; + CONSTCD14 bool ok() const NOEXCEPT; + +private: + static CONSTCD14 year_month_weekday from_days(days dp) NOEXCEPT; + CONSTCD14 days to_days() const 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; + +template +std::basic_ostream& +operator<<(std::basic_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; + + CONSTCD14 year_month_weekday_last& operator+=(const months& m) NOEXCEPT; + CONSTCD14 year_month_weekday_last& operator-=(const months& m) NOEXCEPT; + CONSTCD14 year_month_weekday_last& operator+=(const years& y) NOEXCEPT; + CONSTCD14 year_month_weekday_last& operator-=(const years& y) NOEXCEPT; + + CONSTCD11 date::year year() const NOEXCEPT; + CONSTCD11 date::month month() const NOEXCEPT; + CONSTCD11 date::weekday weekday() const NOEXCEPT; + CONSTCD11 date::weekday_last weekday_last() const NOEXCEPT; + + CONSTCD14 operator sys_days() const NOEXCEPT; + CONSTCD14 explicit operator local_days() const NOEXCEPT; + CONSTCD11 bool ok() const NOEXCEPT; + +private: + CONSTCD14 days to_days() 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; + +template +std::basic_ostream& +operator<<(std::basic_ostream& os, const year_month_weekday_last& ymwdl); + +#if !defined(_MSC_VER) || (_MSC_VER >= 1900) +inline namespace literals +{ + +CONSTCD11 date::day operator "" _d(unsigned long long d) NOEXCEPT; +CONSTCD11 date::year operator "" _y(unsigned long long y) NOEXCEPT; + +// CONSTDATA date::month jan{1}; +// CONSTDATA date::month feb{2}; +// CONSTDATA date::month mar{3}; +// CONSTDATA date::month apr{4}; +// CONSTDATA date::month may{5}; +// CONSTDATA date::month jun{6}; +// CONSTDATA date::month jul{7}; +// CONSTDATA date::month aug{8}; +// CONSTDATA date::month sep{9}; +// CONSTDATA date::month oct{10}; +// CONSTDATA date::month nov{11}; +// CONSTDATA date::month dec{12}; +// +// CONSTDATA date::weekday sun{0u}; +// CONSTDATA date::weekday mon{1u}; +// CONSTDATA date::weekday tue{2u}; +// CONSTDATA date::weekday wed{3u}; +// CONSTDATA date::weekday thu{4u}; +// CONSTDATA date::weekday fri{5u}; +// CONSTDATA date::weekday sat{6u}; + +} // inline namespace literals +#endif // !defined(_MSC_VER) || (_MSC_VER >= 1900) + +//----------------+ +// Implementation | +//----------------+ + +// utilities +namespace detail { + +template> +class save_stream +{ + std::basic_ostream& os_; + CharT fill_; + std::ios::fmtflags flags_; + std::locale loc_; + +public: + ~save_stream() + { + os_.fill(fill_); + os_.flags(flags_); + os_.imbue(loc_); + } + + save_stream(const save_stream&) = delete; + save_stream& operator=(const save_stream&) = delete; + + explicit save_stream(std::basic_ostream& os) + : os_(os) + , fill_(os.fill()) + , flags_(os.flags()) + , loc_(os.getloc()) + {} +}; + +#ifdef __GNUC__ +// GCC complains about __int128 with -pedantic or -pedantic-errors +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wpedantic" +#endif + +template +struct choose_trunc_type +{ + static const int digits = std::numeric_limits::digits; + using type = typename std::conditional + < + digits < 32, + std::int32_t, + typename std::conditional + < + digits < 64, + std::int64_t, +#ifdef __SIZEOF_INT128__ + __int128 +#else + std::int64_t +#endif + >::type + >::type; +}; + +#ifdef __GNUC__ +#pragma GCC diagnostic pop +#endif + +template +CONSTCD11 +inline +typename std::enable_if +< + !std::chrono::treat_as_floating_point::value, + T +>::type +trunc(T t) NOEXCEPT +{ + return t; +} + +template +CONSTCD14 +inline +typename std::enable_if +< + std::chrono::treat_as_floating_point::value, + T +>::type +trunc(T t) NOEXCEPT +{ + using namespace std; + using I = typename choose_trunc_type::type; + CONSTDATA auto digits = numeric_limits::digits; + static_assert(digits < numeric_limits::digits, ""); + CONSTDATA auto max = I{1} << (digits-1); + CONSTDATA auto min = -max; + const auto negative = t < T{0}; + if (min <= t && t <= max && t != 0 && t == t) + { + t = static_cast(static_cast(t)); + if (t == 0 && negative) + t = -t; + } + return t; +} + +} // detail + +// trunc towards zero +template +CONSTCD11 +inline +To +trunc(const std::chrono::duration& d) +{ + return To{detail::trunc(std::chrono::duration_cast(d).count())}; +} + +#ifndef HAS_CHRONO_ROUNDING +# if defined(_MSC_FULL_VER) && _MSC_FULL_VER >= 190023918 +# define HAS_CHRONO_ROUNDING 1 +# elif defined(__cpp_lib_chrono) && __cplusplus > 201402 && __cpp_lib_chrono >= 201510 +# define HAS_CHRONO_ROUNDING 1 +# elif defined(_LIBCPP_VERSION) && __cplusplus > 201402 && _LIBCPP_VERSION >= 3800 +# define HAS_CHRONO_ROUNDING 1 +# else +# define HAS_CHRONO_ROUNDING 0 +# endif +#endif // HAS_CHRONO_ROUNDING + +#if HAS_CHRONO_ROUNDING == 0 + +// round down +template +CONSTCD14 +inline +To +floor(const std::chrono::duration& d) +{ + auto t = trunc(d); + if (t > d) + return t - To{1}; + return t; +} + +// round to nearest, to even on tie +template +CONSTCD14 +inline +To +round(const std::chrono::duration& d) +{ + auto t0 = floor(d); + auto t1 = t0 + To{1}; + if (t1 == To{0} && t0 < To{0}) + t1 = -t1; + auto diff0 = d - t0; + auto diff1 = t1 - d; + if (diff0 == diff1) + { + if (t0 - trunc(t0/2)*2 == To{0}) + return t0; + return t1; + } + if (diff0 < diff1) + return t0; + return t1; +} + +// round up +template +CONSTCD14 +inline +To +ceil(const std::chrono::duration& d) +{ + auto t = trunc(d); + if (t < d) + return t + To{1}; + return t; +} + +template ::is_signed + >::type> +CONSTCD11 +std::chrono::duration +abs(std::chrono::duration d) +{ + return d >= d.zero() ? d : -d; +} + +// 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())}; +} + +#else // HAS_CHRONO_ROUNDING == 1 + +using std::chrono::floor; +using std::chrono::ceil; +using std::chrono::round; +using std::chrono::abs; + +#endif // HAS_CHRONO_ROUNDING + +// trunc towards zero +template +CONSTCD11 +inline +std::chrono::time_point +trunc(const std::chrono::time_point& tp) +{ + using std::chrono::time_point; + return time_point{trunc(tp.time_since_epoch())}; +} + +// day + +CONSTCD11 inline day::day(unsigned d) NOEXCEPT : d_(static_cast(d)) {} +CONSTCD14 inline day& day::operator++() NOEXCEPT {++d_; return *this;} +CONSTCD14 inline day day::operator++(int) NOEXCEPT {auto tmp(*this); ++(*this); return tmp;} +CONSTCD14 inline day& day::operator--() NOEXCEPT {--d_; return *this;} +CONSTCD14 inline day day::operator--(int) NOEXCEPT {auto tmp(*this); --(*this); return tmp;} +CONSTCD14 inline day& day::operator+=(const days& d) NOEXCEPT {*this = *this + d; return *this;} +CONSTCD14 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; +} + +template +inline +std::basic_ostream& +operator<<(std::basic_ostream& os, const day& d) +{ + detail::save_stream _(os); + os.fill('0'); + os.flags(std::ios::dec | std::ios::right); + os.width(2); + os << static_cast(d); + return os; +} + +// month + +CONSTCD11 inline month::month(unsigned m) NOEXCEPT : m_(static_cast(m)) {} +CONSTCD14 inline month& month::operator++() NOEXCEPT {if (++m_ == 13) m_ = 1; return *this;} +CONSTCD14 inline month month::operator++(int) NOEXCEPT {auto tmp(*this); ++(*this); return tmp;} +CONSTCD14 inline month& month::operator--() NOEXCEPT {if (--m_ == 0) m_ = 12; return *this;} +CONSTCD14 inline month month::operator--(int) NOEXCEPT {auto tmp(*this); --(*this); return tmp;} + +CONSTCD14 +inline +month& +month::operator+=(const months& m) NOEXCEPT +{ + *this = *this + m; + return *this; +} + +CONSTCD14 +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; +} + +template +inline +std::basic_ostream& +operator<<(std::basic_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)) {} +CONSTCD14 inline year& year::operator++() NOEXCEPT {++y_; return *this;} +CONSTCD14 inline year year::operator++(int) NOEXCEPT {auto tmp(*this); ++(*this); return tmp;} +CONSTCD14 inline year& year::operator--() NOEXCEPT {--y_; return *this;} +CONSTCD14 inline year year::operator--(int) NOEXCEPT {auto tmp(*this); --(*this); return tmp;} +CONSTCD14 inline year& year::operator+=(const years& y) NOEXCEPT {*this = *this + y; return *this;} +CONSTCD14 inline year& year::operator-=(const years& y) NOEXCEPT {*this = *this - y; return *this;} +CONSTCD11 inline year year::operator-() const NOEXCEPT {return year{-y_};} +CONSTCD11 inline year year::operator+() const NOEXCEPT {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()}; +} + +template +inline +std::basic_ostream& +operator<<(std::basic_ostream& os, const year& y) +{ + detail::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; +} + +// 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 sys_days& dp) NOEXCEPT + : wd_(weekday_from_days(dp.time_since_epoch().count())) + {} + +CONSTCD11 +inline +weekday::weekday(const local_days& dp) NOEXCEPT + : wd_(weekday_from_days(dp.time_since_epoch().count())) + {} + +CONSTCD14 inline weekday& weekday::operator++() NOEXCEPT {if (++wd_ == 7) wd_ = 0; return *this;} +CONSTCD14 inline weekday weekday::operator++(int) NOEXCEPT {auto tmp(*this); ++(*this); return tmp;} +CONSTCD14 inline weekday& weekday::operator--() NOEXCEPT {if (wd_-- == 0) wd_ = 6; return *this;} +CONSTCD14 inline weekday weekday::operator--(int) NOEXCEPT {auto tmp(*this); --(*this); return tmp;} + +CONSTCD14 +inline +weekday& +weekday::operator+=(const days& d) NOEXCEPT +{ + *this = *this + d; + return *this; +} + +CONSTCD14 +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; +} + +template +inline +std::basic_ostream& +operator<<(std::basic_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; +} + +#if !defined(_MSC_VER) || (_MSC_VER >= 1900) +inline namespace literals +{ + +CONSTCD11 +inline +date::day +operator "" _d(unsigned long long d) NOEXCEPT +{ + return date::day{static_cast(d)}; +} + +CONSTCD11 +inline +date::year +operator "" _y(unsigned long long y) NOEXCEPT +{ + return date::year(static_cast(y)); +} +#endif // !defined(_MSC_VER) || (_MSC_VER >= 1900) + +CONSTDATA date::last_spec last{}; + +CONSTDATA date::month jan{1}; +CONSTDATA date::month feb{2}; +CONSTDATA date::month mar{3}; +CONSTDATA date::month apr{4}; +CONSTDATA date::month may{5}; +CONSTDATA date::month jun{6}; +CONSTDATA date::month jul{7}; +CONSTDATA date::month aug{8}; +CONSTDATA date::month sep{9}; +CONSTDATA date::month oct{10}; +CONSTDATA date::month nov{11}; +CONSTDATA date::month dec{12}; + +CONSTDATA date::weekday sun{0u}; +CONSTDATA date::weekday mon{1u}; +CONSTDATA date::weekday tue{2u}; +CONSTDATA date::weekday wed{3u}; +CONSTDATA date::weekday thu{4u}; +CONSTDATA date::weekday fri{5u}; +CONSTDATA date::weekday sat{6u}; + +#if !defined(_MSC_VER) || (_MSC_VER >= 1900) +} // inline namespace literals +#endif + +// weekday_indexed + +CONSTCD11 +inline +weekday +weekday_indexed::weekday() const NOEXCEPT +{ + return date::weekday{static_cast(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)) + {} + +template +inline +std::basic_ostream& +operator<<(std::basic_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); +} + +template +inline +std::basic_ostream& +operator<<(std::basic_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();} + +CONSTCD14 +inline +year_month& +year_month::operator+=(const months& dm) NOEXCEPT +{ + *this = *this + dm; + return *this; +} + +CONSTCD14 +inline +year_month& +year_month::operator-=(const months& dm) NOEXCEPT +{ + *this = *this - dm; + return *this; +} + +CONSTCD14 +inline +year_month& +year_month::operator+=(const years& dy) NOEXCEPT +{ + *this = *this + dy; + return *this; +} + +CONSTCD14 +inline +year_month& +year_month::operator-=(const years& dy) NOEXCEPT +{ + *this = *this - dy; + return *this; +} + +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; +} + +template +inline +std::basic_ostream& +operator<<(std::basic_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[] = + { + date::day(31), date::day(29), date::day(31), + date::day(30), date::day(31), date::day(30), + date::day(31), date::day(31), date::day(30), + date::day(31), date::day(30), date::day(31) + }; + return m_.ok() && date::day{1} <= 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); +} + +template +inline +std::basic_ostream& +operator<<(std::basic_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); +} + +template +inline +std::basic_ostream& +operator<<(std::basic_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); +} + +template +inline +std::basic_ostream& +operator<<(std::basic_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_last& wdl) NOEXCEPT + : m_(m) + , wdl_(wdl) + {} + +CONSTCD11 inline month month_weekday_last::month() const NOEXCEPT {return m_;} + +CONSTCD11 +inline +weekday_last +month_weekday_last::weekday_last() const NOEXCEPT +{ + return wdl_; +} + +CONSTCD11 +inline +bool +month_weekday_last::ok() const NOEXCEPT +{ + return m_.ok() && wdl_.ok(); +} + +CONSTCD11 +inline +bool +operator==(const month_weekday_last& x, const month_weekday_last& y) NOEXCEPT +{ + return x.month() == y.month() && x.weekday_last() == y.weekday_last(); +} + +CONSTCD11 +inline +bool +operator!=(const month_weekday_last& x, const month_weekday_last& y) NOEXCEPT +{ + return !(x == y); +} + +template +inline +std::basic_ostream& +operator<<(std::basic_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_day_last& mdl) NOEXCEPT + : y_(y) + , mdl_(mdl) + {} + +CONSTCD14 +inline +year_month_day_last& +year_month_day_last::operator+=(const months& m) NOEXCEPT +{ + *this = *this + m; + return *this; +} + +CONSTCD14 +inline +year_month_day_last& +year_month_day_last::operator-=(const months& m) NOEXCEPT +{ + *this = *this - m; + return *this; +} + +CONSTCD14 +inline +year_month_day_last& +year_month_day_last::operator+=(const years& y) NOEXCEPT +{ + *this = *this + y; + return *this; +} + +CONSTCD14 +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 mdl_.month();} + +CONSTCD11 +inline +month_day_last +year_month_day_last::month_day_last() const NOEXCEPT +{ + return mdl_; +} + +CONSTCD14 +inline +day +year_month_day_last::day() const NOEXCEPT +{ + CONSTDATA date::day d[] = + { + date::day(31), date::day(28), date::day(31), + date::day(30), date::day(31), date::day(30), + date::day(31), date::day(31), date::day(30), + date::day(31), date::day(30), date::day(31) + }; + return month() != feb || !y_.is_leap() ? + d[static_cast(month()) - 1] : date::day{29}; +} + +CONSTCD14 +inline +year_month_day_last::operator sys_days() const NOEXCEPT +{ + return sys_days(year()/month()/day()); +} + +CONSTCD14 +inline +year_month_day_last::operator local_days() const NOEXCEPT +{ + return local_days(year()/month()/day()); +} + +CONSTCD11 +inline +bool +year_month_day_last::ok() const NOEXCEPT +{ + return y_.ok() && mdl_.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_day_last() == y.month_day_last(); +} + +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_day_last() < y.month_day_last())); +} + +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); +} + +template +inline +std::basic_ostream& +operator<<(std::basic_ostream& os, const year_month_day_last& ymdl) +{ + return os << ymdl.year() << '/' << ymdl.month_day_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_day_last()}; +} + +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(sys_days dp) NOEXCEPT + : year_month_day(from_days(dp.time_since_epoch())) + {} + +CONSTCD14 +inline +year_month_day::year_month_day(local_days dp) NOEXCEPT + : year_month_day(from_days(dp.time_since_epoch())) + {} + +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_;} + +CONSTCD14 +inline +year_month_day& +year_month_day::operator+=(const months& m) NOEXCEPT +{ + *this = *this + m; + return *this; +} + +CONSTCD14 +inline +year_month_day& +year_month_day::operator-=(const months& m) NOEXCEPT +{ + *this = *this - m; + return *this; +} + +CONSTCD14 +inline +year_month_day& +year_month_day::operator+=(const years& y) NOEXCEPT +{ + *this = *this + y; + return *this; +} + +CONSTCD14 +inline +year_month_day& +year_month_day::operator-=(const years& y) NOEXCEPT +{ + *this = *this - y; + return *this; +} + +CONSTCD14 +inline +days +year_month_day::to_days() 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 > 2 ? m-3 : m+9) + 2)/5 + d-1; // [0, 365] + auto const doe = yoe * 365 + yoe/4 - yoe/100 + doy; // [0, 146096] + return days{era * 146097 + static_cast(doe) - 719468}; +} + +CONSTCD14 +inline +year_month_day::operator sys_days() const NOEXCEPT +{ + return sys_days{to_days()}; +} + +CONSTCD14 +inline +year_month_day::operator local_days() const NOEXCEPT +{ + return local_days{to_days()}; +} + +CONSTCD14 +inline +bool +year_month_day::ok() const NOEXCEPT +{ + if (!(y_.ok() && m_.ok())) + return false; + return date::day{1} <= 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); +} + +template +inline +std::basic_ostream& +operator<<(std::basic_ostream& os, const year_month_day& ymd) +{ + detail::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_days(days 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.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 < 10 ? mp+3 : mp-9; // [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 sys_days& dp) NOEXCEPT + : year_month_weekday(from_days(dp.time_since_epoch())) + {} + +CONSTCD14 +inline +year_month_weekday::year_month_weekday(const local_days& dp) NOEXCEPT + : year_month_weekday(from_days(dp.time_since_epoch())) + {} + +CONSTCD14 +inline +year_month_weekday& +year_month_weekday::operator+=(const months& m) NOEXCEPT +{ + *this = *this + m; + return *this; +} + +CONSTCD14 +inline +year_month_weekday& +year_month_weekday::operator-=(const months& m) NOEXCEPT +{ + *this = *this - m; + return *this; +} + +CONSTCD14 +inline +year_month_weekday& +year_month_weekday::operator+=(const years& y) NOEXCEPT +{ + *this = *this + y; + return *this; +} + +CONSTCD14 +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 +year_month_weekday::weekday() const NOEXCEPT +{ + return wdi_.weekday(); +} + +CONSTCD11 +inline +unsigned +year_month_weekday::index() const NOEXCEPT +{ + return wdi_.index(); +} + +CONSTCD11 +inline +weekday_indexed +year_month_weekday::weekday_indexed() const NOEXCEPT +{ + return wdi_; +} + +CONSTCD14 +inline +year_month_weekday::operator sys_days() const NOEXCEPT +{ + return sys_days{to_days()}; +} + +CONSTCD14 +inline +year_month_weekday::operator local_days() const NOEXCEPT +{ + return local_days{to_days()}; +} + +CONSTCD14 +inline +bool +year_month_weekday::ok() const NOEXCEPT +{ + if (!y_.ok() || !m_.ok() || !wdi_.weekday().ok() || wdi_.index() < 1) + return false; + if (wdi_.index() <= 4) + return true; + auto d2 = wdi_.weekday() - date::weekday(static_cast(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_days(days d) NOEXCEPT +{ + sys_days dp{d}; + auto const wd = date::weekday(dp); + auto const ymd = year_month_day(dp); + return {ymd.year(), ymd.month(), wd[(static_cast(ymd.day())-1)/7+1]}; +} + +CONSTCD14 +inline +days +year_month_weekday::to_days() const NOEXCEPT +{ + auto d = sys_days(y_/m_/1); + return (d + (wdi_.weekday() - date::weekday(d) + days{(wdi_.index()-1)*7}) + ).time_since_epoch(); +} + +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); +} + +template +inline +std::basic_ostream& +operator<<(std::basic_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) + {} + +CONSTCD14 +inline +year_month_weekday_last& +year_month_weekday_last::operator+=(const months& m) NOEXCEPT +{ + *this = *this + m; + return *this; +} + +CONSTCD14 +inline +year_month_weekday_last& +year_month_weekday_last::operator-=(const months& m) NOEXCEPT +{ + *this = *this - m; + return *this; +} + +CONSTCD14 +inline +year_month_weekday_last& +year_month_weekday_last::operator+=(const years& y) NOEXCEPT +{ + *this = *this + y; + return *this; +} + +CONSTCD14 +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 +year_month_weekday_last::weekday() const NOEXCEPT +{ + return wdl_.weekday(); +} + +CONSTCD11 +inline +weekday_last +year_month_weekday_last::weekday_last() const NOEXCEPT +{ + return wdl_; +} + +CONSTCD14 +inline +year_month_weekday_last::operator sys_days() const NOEXCEPT +{ + return sys_days{to_days()}; +} + +CONSTCD14 +inline +year_month_weekday_last::operator local_days() const NOEXCEPT +{ + return local_days{to_days()}; +} + +CONSTCD11 +inline +bool +year_month_weekday_last::ok() const NOEXCEPT +{ + return y_.ok() && m_.ok() && wdl_.ok(); +} + +CONSTCD14 +inline +days +year_month_weekday_last::to_days() const NOEXCEPT +{ + auto const d = sys_days(y_/m_/last); + return (d - (date::weekday{d} - wdl_.weekday())).time_since_epoch(); +} + +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); +} + +template +inline +std::basic_ostream& +operator<<(std::basic_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}; +} + +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(), month_day_last{ym.month()}}; +} + +CONSTCD11 +inline +year_month_day_last +operator/(const year& y, const month_day_last& mdl) NOEXCEPT +{ + return {y, mdl}; +} + +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 +{ + not_valid, + hour, + minute, + second, + subsecond +}; + +#if !defined(_MSC_VER) || (_MSC_VER >= 1900) + +template +struct classify_duration +{ + static CONSTDATA classify value = + Duration{1} >= days{1} ? classify::not_valid : + 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; +}; + +#else + +template +struct classify_duration +{ + static CONSTDATA classify value = + std::ratio_greater_equal< + typename Duration::period, + days::period >::value + ? classify::not_valid : + std::ratio_greater_equal< + typename Duration::period, + std::chrono::hours::period>::value + ? classify::hour : + std::ratio_greater_equal< + typename Duration::period, + std::chrono::minutes::period>::value + ? classify::minute : + std::ratio_greater_equal< + typename Duration::period, + std::chrono::seconds::period>::value + ? classify::second : + classify::subsecond; +}; + +#endif // !defined(_MSC_VER) || (_MSC_VER >= 1900) + +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 precision to_duration() const NOEXCEPT + { + return static_cast(*this); + } + + CONSTCD14 time_of_day_storage& make24() NOEXCEPT {base::make24(); return *this;} + CONSTCD14 time_of_day_storage& make12() NOEXCEPT {base::make12(); return *this;} + + template + friend + std::basic_ostream& + operator<<(std::basic_ostream& os, const time_of_day_storage& t) + { + using namespace std; + detail::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 precision to_duration() const NOEXCEPT + { + return static_cast(*this); + } + + CONSTCD14 time_of_day_storage& make24() NOEXCEPT {base::make24(); return *this;} + CONSTCD14 time_of_day_storage& make12() NOEXCEPT {base::make12(); return *this;} + + template + friend + std::basic_ostream& + operator<<(std::basic_ostream& os, const time_of_day_storage& t) + { + using namespace std; + detail::save_stream _(os); + if (static_cast(t) < 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 << std::abs(t.h_.count()) << ':'; + os.width(2); + os << std::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_;} + CONSTCD14 std::chrono::seconds& seconds() NOEXCEPT {return s_;} + CONSTCD11 std::chrono::seconds seconds() const NOEXCEPT {return s_;} + CONSTCD11 unsigned mode() const NOEXCEPT {return mode_;} + + CONSTCD14 explicit operator precision() const NOEXCEPT + { + return to24hr() + s_ + m_; + } + + CONSTCD14 precision to_duration() const NOEXCEPT + { + return static_cast(*this); + } + + CONSTCD14 time_of_day_storage& make24() NOEXCEPT {base::make24(); return *this;} + CONSTCD14 time_of_day_storage& make12() NOEXCEPT {base::make12(); return *this;} + + template + friend + std::basic_ostream& + operator<<(std::basic_ostream& os, const time_of_day_storage& t) + { + using namespace std; + detail::save_stream _(os); + if (static_cast(t) < 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 << std::abs(t.h_.count()) << ':'; + os.width(2); + os << std::abs(t.m_.count()) << ':'; + os.width(2); + os << std::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_;} + CONSTCD14 std::chrono::seconds& seconds() NOEXCEPT {return s_;} + 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() + s_ + sub_s_ + m_; + } + + CONSTCD14 precision to_duration() const NOEXCEPT + { + return static_cast(*this); + } + + CONSTCD14 time_of_day_storage& make24() NOEXCEPT {base::make24(); return *this;} + CONSTCD14 time_of_day_storage& make12() NOEXCEPT {base::make12(); return *this;} + + template + friend + std::basic_ostream& + operator<<(std::basic_ostream& os, const time_of_day_storage& t) + { + using namespace std; + detail::save_stream _(os); + if (static_cast(t) < 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 << std::abs(t.h_.count()) << ':'; + os.width(2); + os << std::abs(t.m_.count()) << ':'; + os.width(2); + os << std::abs(t.s_.count()) + << use_facet>(os.getloc()).decimal_point(); + os.imbue(locale{}); +#if __cplusplus >= 201402 + CONSTDATA auto cl10 = ceil_log10(Period::den); + using scale = std::ratio_multiply>; + os.width(cl10); + os << std::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 = + static_cast(std::ceil(log10(Period::den))); + const auto p10 = std::pow(10., cl10); + os.width(cl10); + os << static_cast(std::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: +#if !(defined(_MSC_VER) && !defined(__clang__)) + // C++11 + using base::base; +#else + // MS cl compiler workaround. + template + explicit time_of_day(Args&& ...args) + : base(std::forward(args)...) + {} +#endif +}; + +template ::value>::type> +CONSTCD11 +inline +time_of_day> +make_time(const std::chrono::duration& d) +{ + return time_of_day>(d); +} + +CONSTCD11 +inline +time_of_day +make_time(const std::chrono::hours& h, unsigned md) +{ + return time_of_day(h, md); +} + +CONSTCD11 +inline +time_of_day +make_time(const std::chrono::hours& h, const std::chrono::minutes& m, + unsigned md) +{ + return time_of_day(h, m, md); +} + +CONSTCD11 +inline +time_of_day +make_time(const std::chrono::hours& h, const std::chrono::minutes& m, + const std::chrono::seconds& s, unsigned md) +{ + return time_of_day(h, m, s, md); +} + +template >::value>::type> +CONSTCD11 +inline +time_of_day> +make_time(const std::chrono::hours& h, const std::chrono::minutes& m, + const std::chrono::seconds& s, const std::chrono::duration& sub_s, + unsigned md) +{ + return time_of_day>(h, m, s, sub_s, md); +} + +template +inline +typename std::enable_if +< + !std::chrono::treat_as_floating_point::value && + std::ratio_less::value + , std::basic_ostream& +>::type +operator<<(std::basic_ostream& os, const sys_time& tp) +{ + auto const dp = floor(tp); + return os << year_month_day(dp) << ' ' << make_time(tp-dp); +} + +template +inline +std::basic_ostream& +operator<<(std::basic_ostream& os, const sys_days& dp) +{ + return os << year_month_day(dp); +} + +template +inline +std::basic_ostream& +operator<<(std::basic_ostream& os, const local_time& ut) +{ + return os << sys_time{ut.time_since_epoch()}; +} + +// format + +namespace detail +{ + +template +std::basic_string +format(const std::locale& loc, std::basic_string fmt, + const local_time& tp, const std::string* abbrev = nullptr, + const std::chrono::seconds* offset_sec = nullptr) +{ + // Handle these specially + // %S append fractional seconds if tp has precision finer than seconds + // %T append fractional seconds if tp has precision finer than seconds + // %z replace with offset from zone on +/-hhmm format + // %Ez, %Oz replace with offset from zone on +/-hh:mm format + // %Z replace with abbreviation from zone + + using namespace std; + using namespace std::chrono; + auto command = false; + auto modified = false; + for (std::size_t i = 0; i < fmt.size(); ++i) + { + switch (fmt[i]) + { + case '%': + command = true; + modified = false; + break; + case 'O': + case 'E': + modified = true; + break; + case 'S': + case 'T': + if (command && !modified && ratio_less>::value) + { + basic_ostringstream os; + os.imbue(loc); + os << make_time(tp - floor(tp)); + auto s = os.str(); + s.erase(0, 8); + fmt.insert(i+1, s); + i += s.size() - 1; + } + command = false; + modified = false; + break; + case 'z': + if (command) + { + if (offset_sec == nullptr) + throw std::runtime_error("Can not format local_time with %z"); + else + { + auto offset = duration_cast(*offset_sec); + basic_ostringstream os; + os.imbue(loc); + if (offset >= minutes{0}) + os << '+'; + os << make_time(offset); + auto s = os.str(); + if (!modified) + s.erase(s.find(':'), 1); + fmt.replace(i - 1 - modified, 2 + modified, s); + i += 3; + } + } + command = false; + modified = false; + break; + case 'Z': + if (command && !modified) + { + if (abbrev == nullptr) + throw std::runtime_error("Can not format local_time with %Z"); + else + { + fmt.replace(i - 1, 2, + std::basic_string(abbrev->begin(), abbrev->end())); + i += abbrev->size() - 1; + } + } + command = false; + modified = false; + break; + default: + command = false; + modified = false; + break; + } + } + auto& f = use_facet>(loc); + basic_ostringstream os; + os.imbue(loc); + auto ld = floor(tp); + auto ymd = year_month_day{ld}; + auto hms = make_time(floor(tp - ld)); + std::tm tm{}; + tm.tm_sec = static_cast(hms.seconds().count()); + tm.tm_min = static_cast(hms.minutes().count()); + tm.tm_hour = static_cast(hms.hours().count()); + tm.tm_mday = static_cast(static_cast(ymd.day())); + tm.tm_mon = static_cast(static_cast(ymd.month()) - 1); + tm.tm_year = static_cast(ymd.year()) - 1900; + tm.tm_wday = static_cast(static_cast(weekday{ld})); + tm.tm_yday = static_cast((ld - local_days(ymd.year()/1/1)).count()); + f.put(os, os, os.fill(), &tm, fmt.data(), fmt.data() + fmt.size()); + return os.str(); +} + +} // namespace detail + +template +inline +std::basic_string +format(const std::locale& loc, std::basic_string fmt, + const local_time& tp) +{ + return detail::format(loc, std::move(fmt), tp); +} + +template +inline +std::basic_string +format(std::basic_string fmt, const local_time& tp) +{ + return detail::format(std::locale{}, std::move(fmt), tp); +} + +template +inline +std::basic_string +format(const std::locale& loc, std::basic_string fmt, + const sys_time& tp) +{ + const std::string abbrev("UTC"); + CONSTDATA std::chrono::seconds offset{0}; + return detail::format(loc, std::move(fmt), + local_time{tp.time_since_epoch()}, &abbrev, &offset); +} + +template +inline +std::basic_string +format(std::basic_string fmt, const sys_time& tp) +{ + const std::string abbrev("UTC"); + CONSTDATA std::chrono::seconds offset{0}; + return detail::format(std::locale{}, std::move(fmt), + local_time{tp.time_since_epoch()}, &abbrev, &offset); +} + +// const CharT* formats + +template +inline +std::basic_string +format(const std::locale& loc, const CharT* fmt, const local_time& tp) +{ + return detail::format(loc, std::basic_string(fmt), tp); +} + +template +inline +std::basic_string +format(const CharT* fmt, const local_time& tp) +{ + return detail::format(std::locale{}, std::basic_string(fmt), tp); +} + +template +inline +std::basic_string +format(const std::locale& loc, const CharT* fmt, const sys_time& tp) +{ + const std::string abbrev("UTC"); + CONSTDATA std::chrono::seconds offset{0}; + return detail::format(loc, std::basic_string(fmt), + local_time{tp.time_since_epoch()}, + &abbrev, &offset); +} + +template +inline +std::basic_string +format(const CharT* fmt, const sys_time& tp) +{ + const std::string abbrev("UTC"); + CONSTDATA std::chrono::seconds offset{0}; + return detail::format(std::locale{}, std::basic_string(fmt), + local_time{tp.time_since_epoch()}, + &abbrev, &offset); +} + +// parse + +namespace detail +{ + +template +void +parse(std::basic_istream& is, + const std::basic_string& format, local_time& tp, + std::basic_string* abbrev = nullptr, + std::chrono::minutes* offset = nullptr) +{ + using namespace std; + using namespace std::chrono; + typename basic_istream::sentry ok{is}; + if (ok) + { + auto& f = use_facet>(is.getloc()); + ios_base::iostate err = ios_base::goodbit; + std::tm tm{}; + Duration subseconds{}; + std::basic_string temp_abbrev; + minutes temp_offset{}; + + auto b = format.data(); + auto i = b; + auto e = b + format.size(); + auto command = false; + auto modified = false; + for (; i < e && 0 == (err & ios_base::failbit); ++i) + { + switch (*i) + { + case '%': + command = true; + modified = false; + break; + case 'F': + if (command && !modified) + { + f.get(is, 0, is, err, &tm, b, i-1); + b = i+1; + if ((err & ios_base::failbit) == 0) + { + const CharT ymd[] = {'%', 'Y', '-', '%', 'm', '-', '%', 'd'}; + f.get(is, 0, is, err, &tm, ymd, ymd+8); + } + } + command = false; + modified = false; + break; + case 'O': + case 'E': + modified = true; + break; + case 'T': + case 'S': + if (command && !modified) + { + f.get(is, 0, is, err, &tm, b, i-1); + if (err & ios_base::failbit) + { + command = modified = false; + break; // break the switch/case + } + b = i+1; + if (*i == 'T') + { + const CharT hm[] = {'%', 'H', ':', '%', 'M', ':'}; + f.get(is, 0, is, err, &tm, hm, hm+6); + if (err & ios_base::failbit) + { + command = modified = false; + break; // break the switch/case + } + } + if (ratio_less>::value) + { + auto decimal_point = Traits::to_int_type( + use_facet>(is.getloc()).decimal_point()); + string buf; + while (true) + { + auto k = is.peek(); + if (Traits::eq_int_type(k, Traits::eof())) + break; + if (Traits::eq_int_type(k, decimal_point)) + { + buf += '.'; + decimal_point = Traits::eof(); + is.get(); + } + else + { + auto c = static_cast(Traits::to_char_type(k)); + if (isdigit(c)) + { + buf += c; + is.get(); + } + else + { + break; + } + } + }; + if (!buf.empty()) + subseconds = round(duration{stod(buf)}); + else + err |= ios_base::failbit; + } + else + { + const CharT hm[] = {'%', 'S'}; + f.get(is, 0, is, err, &tm, hm, hm+2); + } + } + command = false; + modified = false; + break; + case 'z': + if (command) + { + f.get(is, 0, is, err, &tm, b, i-1-modified); + b = i+1; + if ((err & ios_base::failbit) == 0) + { + CharT sign{}; + is >> sign; + if (!is.fail() && (sign == '+' || sign == '-')) + { + char h1, h0, m1, m0; + char colon = ':'; + h1 = static_cast(is.get()); + h0 = static_cast(is.get()); + if (modified) + { + if (h0 == ':') + { + colon = h0; + h0 = h1; + h1 = '0'; + } + else + colon = static_cast(is.get()); + } + m1 = static_cast(is.get()); + m0 = static_cast(is.get()); + if (!is.fail() && std::isdigit(h1) && std::isdigit(h0) + && std::isdigit(m1) && std::isdigit(m0) + && colon == ':') + { + temp_offset = 10*hours{h1 - '0'} + hours{h0 - '0'} + + 10*minutes{m1 - '0'} + minutes{m0 - '0'}; + if (sign == '-') + temp_offset = -temp_offset; + } + else + err |= ios_base::failbit; + } + else + err |= ios_base::failbit; + } + } + command = false; + modified = false; + break; + case 'Z': + if (command && !modified) + { + f.get(is, 0, is, err, &tm, b, i-1); + b = i+1; + if ((err & ios_base::failbit) == 0) + { + is >> temp_abbrev; + if (is.fail()) + err |= ios_base::failbit; + } + } + command = false; + modified = false; + break; + default: + command = false; + modified = false; + break; + } + } + if ((err & ios_base::failbit) == 0) + { + if (b < e) + f.get(is, 0, is, err, &tm, b, e); + if ((err & ios_base::failbit) == 0) + { + using namespace std::chrono; + tp = floor(local_days(year{tm.tm_year + 1900}/ + (tm.tm_mon+1)/ + (tm.tm_mday)) + + subseconds + seconds{tm.tm_sec} + + minutes{tm.tm_min} + hours{tm.tm_hour}); + if (abbrev != nullptr) + *abbrev = std::move(temp_abbrev); + if (offset != nullptr) + *offset = temp_offset; + } + } + is.setstate(err); + } + else + is.setstate(ios_base::failbit); +} + +template +inline +void +parse(std::basic_istream& is, + const std::basic_string& format, local_time& tp, + std::chrono::minutes* offset) +{ + parse(is, format, tp, static_cast*>(nullptr), offset); +} + +template > +struct parse_local_manip +{ + const std::basic_string format_; + local_time& tp_; + std::basic_string* abbrev_; + std::chrono::minutes* offset_; + +public: + parse_local_manip(std::basic_string format, + local_time& tp, std::basic_string* abbrev = nullptr, + std::chrono::minutes* offset = nullptr) + : format_(std::move(format)) + , tp_(tp) + , abbrev_(abbrev) + , offset_(offset) + {} + +}; + +template +std::basic_istream& +operator>>(std::basic_istream& is, + const parse_local_manip& x) +{ + parse(is, x.format_, x.tp_, x.abbrev_, x.offset_); + return is; +} + +template > +struct parse_sys_manip +{ + const std::basic_string format_; + sys_time& tp_; + std::basic_string* abbrev_; + std::chrono::minutes* offset_; + +public: + parse_sys_manip(std::basic_string format, + sys_time& tp, std::basic_string* abbrev = nullptr, + std::chrono::minutes* offset = nullptr) + : format_(std::move(format)) + , tp_(tp) + , abbrev_(abbrev) + , offset_(offset) + {} + +}; + +template +std::basic_istream& +operator>>(std::basic_istream& is, + const parse_sys_manip& x) +{ + std::chrono::minutes offset{}; + auto offptr = x.offset_ ? x.offset_ : &offset; + local_time lt; + parse(is, x.format_, lt, x.abbrev_, offptr); + if (!is.fail()) + x.tp_ = sys_time{floor(lt - *offptr).time_since_epoch()}; + return is; +} + +} // namespace detail + +template +inline +void +parse(std::basic_istream& is, + const std::basic_string& format, sys_time& tp) +{ + std::chrono::minutes offset{}; + local_time lt; + detail::parse(is, format, lt, &offset); + if (!is.fail()) + tp = sys_time{floor(lt - offset).time_since_epoch()}; +} + +template +inline +detail::parse_sys_manip +parse(const std::basic_string& format, sys_time& tp) +{ + return {format, tp}; +} + +template +inline +void +parse(std::basic_istream& is, + const std::basic_string& format, sys_time& tp, + std::basic_string& abbrev) +{ + std::chrono::minutes offset{}; + local_time lt; + detail::parse(is, format, lt, &abbrev, &offset); + if (!is.fail()) + tp = sys_time{floor(lt - offset).time_since_epoch()}; +} + +template +inline +detail::parse_sys_manip +parse(const std::basic_string& format, sys_time& tp, + std::basic_string& abbrev) +{ + return {format, tp, &abbrev}; +} + +template +inline +void +parse(std::basic_istream& is, + const std::basic_string& format, sys_time& tp, + std::chrono::minutes& offset) +{ + local_time lt; + detail::parse(is, format, lt, &offset); + if (!is.fail()) + tp = sys_time{floor(lt - offset).time_since_epoch()}; +} + +template +inline +detail::parse_sys_manip +parse(const std::basic_string& format, sys_time& tp, + std::chrono::minutes& offset) +{ + return {format, tp, nullptr, &offset}; +} + +template +inline +void +parse(std::basic_istream& is, + const std::basic_string& format, sys_time& tp, + std::basic_string& abbrev, std::chrono::minutes& offset) +{ + local_time lt; + detail::parse(is, format, lt, &abbrev, &offset); + if (!is.fail()) + tp = sys_time{floor(lt - offset).time_since_epoch()}; +} + +template +inline +detail::parse_sys_manip +parse(const std::basic_string& format, sys_time& tp, + std::basic_string& abbrev, std::chrono::minutes& offset) +{ + return {format, tp, &abbrev, &offset}; +} + +template +inline +void +parse(std::basic_istream& is, + const std::basic_string& format, sys_time& tp, + std::chrono::minutes& offset, std::basic_string& abbrev) +{ + local_time lt; + detail::parse(is, format, lt, &abbrev, &offset); + if (!is.fail()) + tp = sys_time{floor(lt - offset).time_since_epoch()}; +} + +template +inline +detail::parse_sys_manip +parse(const std::basic_string& format, sys_time& tp, + std::chrono::minutes& offset, std::basic_string& abbrev) +{ + return {format, tp, &abbrev, &offset}; +} + +template +inline +void +parse(std::basic_istream& is, + const std::basic_string& format, local_time& tp) +{ + detail::parse(is, format, tp); +} + +template +inline +detail::parse_local_manip +parse(const std::basic_string& format, local_time& tp) +{ + return {format, tp}; +} + +template +inline +void +parse(std::basic_istream& is, + const std::basic_string& format, local_time& tp, + std::basic_string& abbrev) +{ + detail::parse(is, format, tp, &abbrev); +} + +template +inline +detail::parse_local_manip +parse(const std::basic_string& format, local_time& tp, + std::basic_string& abbrev) +{ + return {format, tp, &abbrev}; +} + +template +inline +void +parse(std::basic_istream& is, + const std::basic_string& format, local_time& tp, + std::chrono::minutes& offset) +{ + detail::parse(is, format, tp, &offset); +} + +template +inline +detail::parse_local_manip +parse(const std::basic_string& format, local_time& tp, + std::chrono::minutes& offset) +{ + return {format, tp, nullptr, &offset}; +} + +template +inline +void +parse(std::basic_istream& is, + const std::basic_string& format, local_time& tp, + std::basic_string& abbrev, std::chrono::minutes& offset) +{ + detail::parse(is, format, tp, &abbrev, &offset); +} + +template +inline +detail::parse_local_manip +parse(const std::basic_string& format, local_time& tp, + std::basic_string& abbrev, std::chrono::minutes& offset) +{ + return {format, tp, &abbrev, &offset}; +} + +template +inline +void +parse(std::basic_istream& is, + const std::basic_string& format, local_time& tp, + std::chrono::minutes& offset, std::basic_string& abbrev) +{ + detail::parse(is, format, tp, &abbrev, &offset); +} + +template +inline +detail::parse_local_manip +parse(const std::basic_string& format, local_time& tp, + std::chrono::minutes& offset, std::basic_string& abbrev) +{ + return {format, tp, &abbrev, &offset}; +} + +// const CharT* formats + +template +inline +void +parse(std::basic_istream& is, const CharT* format, sys_time& tp) +{ + parse(is, std::basic_string(format), tp); +} + +template +inline +detail::parse_sys_manip +parse(const CharT* format, sys_time& tp) +{ + return {format, tp}; +} + +template +inline +void +parse(std::basic_istream& is, const CharT* format, sys_time& tp, + std::basic_string& abbrev) +{ + parse(is, std::basic_string(format), tp, abbrev); +} + +template +inline +detail::parse_sys_manip +parse(const CharT* format, sys_time& tp, + std::basic_string& abbrev) +{ + return {format, tp, &abbrev}; +} + +template +inline +void +parse(std::basic_istream& is, const CharT* format, sys_time& tp, + std::chrono::minutes& offset) +{ + parse(is, std::basic_string(format), tp, offset); +} + +template +inline +detail::parse_sys_manip +parse(const CharT* format, sys_time& tp, std::chrono::minutes& offset) +{ + return {format, tp, nullptr, &offset}; +} + +template +inline +void +parse(std::basic_istream& is, const CharT* format, sys_time& tp, + std::basic_string& abbrev, std::chrono::minutes& offset) +{ + parse(is, std::basic_string(format), tp, abbrev, offset); +} + +template +inline +detail::parse_sys_manip +parse(const CharT* format, sys_time& tp, + std::basic_string& abbrev, std::chrono::minutes& offset) +{ + return {format, tp, &abbrev, &offset}; +} + +template +inline +void +parse(std::basic_istream& is, const CharT* format, sys_time& tp, + std::chrono::minutes& offset, std::basic_string& abbrev) +{ + parse(is, std::basic_string(format), tp, abbrev, offset); +} + +template +inline +detail::parse_sys_manip +parse(const CharT* format, sys_time& tp, + std::chrono::minutes& offset, std::basic_string& abbrev) +{ + return {format, tp, &abbrev, &offset}; +} + +template +inline +void +parse(std::basic_istream& is, const CharT* format, + local_time& tp) +{ + parse(is, std::basic_string(format), tp); +} + +template +inline +detail::parse_local_manip +parse(const CharT* format, local_time& tp) +{ + return {format, tp}; +} + +template +inline +void +parse(std::basic_istream& is, const CharT* format, + local_time& tp, std::basic_string& abbrev) +{ + parse(is, std::basic_string(format), tp, abbrev); +} + +template +inline +detail::parse_local_manip +parse(const CharT* format, local_time& tp, + std::basic_string& abbrev) +{ + return {format, tp, &abbrev}; +} + +template +inline +void +parse(std::basic_istream& is, const CharT* format, + local_time& tp, std::chrono::minutes& offset) +{ + parse(is, std::basic_string(format), tp, offset); +} + +template +inline +detail::parse_local_manip +parse(const CharT* format, local_time& tp, std::chrono::minutes& offset) +{ + return {format, tp, nullptr, &offset}; +} + +template +inline +void +parse(std::basic_istream& is, const CharT* format, + local_time& tp, std::basic_string& abbrev, + std::chrono::minutes& offset) +{ + parse(is, std::basic_string(format), tp, abbrev, offset); +} + +template +inline +detail::parse_local_manip +parse(const CharT* format, local_time& tp, + std::basic_string& abbrev, std::chrono::minutes& offset) +{ + return {format, tp, &abbrev, &offset}; +} + +template +inline +void +parse(std::basic_istream& is, const CharT* format, + local_time& tp, std::chrono::minutes& offset, + std::basic_string& abbrev) +{ + parse(is, std::basic_string(format), tp, abbrev, offset); +} + +template +inline +detail::parse_local_manip +parse(const CharT* format, local_time& tp, std::chrono::minutes& offset, + std::basic_string& abbrev) +{ + return {format, tp, &abbrev, &offset}; +} + +} // namespace date + +#endif // DATE_H diff --git a/ext/date/ios.h b/ext/date/ios.h new file mode 100644 index 0000000..3f791bd --- /dev/null +++ b/ext/date/ios.h @@ -0,0 +1,49 @@ +// +// ios.h +// DateTimeLib +// +// The MIT License (MIT) +// +// Copyright (c) 2016 Alexander Kormanovsky +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +#ifndef ios_hpp +#define ios_hpp + +#if __APPLE__ +# include +# if TARGET_OS_IPHONE +# include + + namespace date + { + namespace iOSUtils + { + + std::string get_tzdata_path(); + + } // namespace iOSUtils + } // namespace date + +# endif // TARGET_OS_IPHONE +#else // !__APPLE__ +# define TARGET_OS_IPHONE 0 +#endif // !__APPLE__ +#endif // ios_hpp diff --git a/ext/date/ios.mm b/ext/date/ios.mm new file mode 100644 index 0000000..ec95302 --- /dev/null +++ b/ext/date/ios.mm @@ -0,0 +1,405 @@ +// +// The MIT License (MIT) +// +// Copyright (c) 2016 Alexander Kormanovsky +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +// + +#include "ios.h" + +#if TARGET_OS_IPHONE + +#include + +#include +#include +#include + +#ifndef TAR_DEBUG +# define TAR_DEBUG 0 +#endif + +#define INTERNAL_DIR "Library/tzdata" +#define TARGZ_EXTENSION "tar.gz" + +#define TAR_BLOCK_SIZE 512 +#define TAR_TYPE_POSITION 156 +#define TAR_NAME_POSITION 0 +#define TAR_NAME_SIZE 100 +#define TAR_SIZE_POSITION 124 +#define TAR_SIZE_SIZE 12 + +namespace date +{ +namespace iOSUtils +{ + +struct TarInfo +{ + char objType; + std::string objName; + int64_t realContentSize; // writable size without padding zeroes + int64_t blocksContentSize; // adjusted size to 512 bytes blocks + bool success; +}; + +char* convertCFStringRefPathToCStringPath(CFStringRef ref); +bool extractTzdata(CFURLRef homeUrl, CFURLRef archiveUrl, std::string destPath); +TarInfo getTarObjectInfo(CFReadStreamRef readStream, int64_t location); +std::string getTarObject(CFReadStreamRef readStream, int64_t size); +bool writeFile(CFURLRef tzdataUrl, std::string fileName, std::string data, + int64_t realContentSize); + +std::string +date::iOSUtils::get_tzdata_path() +{ + CFURLRef ref = CFCopyHomeDirectoryURL(); + CFStringRef homePath = CFURLCopyPath(CFCopyHomeDirectoryURL()); + std::string tzdata_path(std::string(convertCFStringRefPathToCStringPath(homePath)) + + INTERNAL_DIR); + + if (access(tzdata_path.c_str(), F_OK) == 0) + { +#if TAR_DEBUG + printf("tzdata exists\n"); +#endif + return tzdata_path; + } + + CFBundleRef mainBundle = CFBundleGetMainBundle(); + CFArrayRef paths = CFBundleCopyResourceURLsOfType(mainBundle, CFSTR(TARGZ_EXTENSION), + NULL); + + if (CFArrayGetCount(paths) != 0) + { + // get archive path, assume there is no other tar.gz in bundle + CFURLRef archiveUrl = static_cast(CFArrayGetValueAtIndex(paths, 0)); + CFStringRef archiveName= CFURLCopyPath(archiveUrl); + archiveUrl = CFBundleCopyResourceURL(mainBundle, archiveName, NULL, NULL); + + extractTzdata(CFCopyHomeDirectoryURL(), archiveUrl, tzdata_path); + } + + return tzdata_path; +} + +char* +convertCFStringRefPathToCStringPath(CFStringRef ref) +{ + CFIndex bufferSize = CFStringGetMaximumSizeOfFileSystemRepresentation(ref); + char *buffer = new char[bufferSize]; + CFStringGetFileSystemRepresentation(ref, buffer, bufferSize); + return buffer; +} + +bool extractTzdata(CFURLRef homeUrl, CFURLRef archiveUrl, std::string destPath) +{ + const char *TAR_TMP_PATH = "/tmp.tar"; + + // create Library path + CFStringRef libraryStr = CFStringCreateWithCString(NULL, "Library", + CFStringGetSystemEncoding()); + CFURLRef libraryUrl = CFURLCreateCopyAppendingPathComponent(kCFAllocatorDefault, + homeUrl, libraryStr, + false); + + // create tzdata path + CFStringRef tzdataPathRef = CFStringCreateWithCString(NULL, INTERNAL_DIR, + CFStringGetSystemEncoding()); + CFURLRef tzdataPathUrl = CFURLCreateCopyAppendingPathComponent(NULL, homeUrl, + tzdataPathRef, false); + + // create src archive path + CFStringRef archivePath = CFURLCopyPath(archiveUrl); + gzFile tarFile = gzopen(convertCFStringRefPathToCStringPath(archivePath), "rb"); + + // create tar unpacking path + CFStringRef tarName = CFStringCreateWithCString(NULL, TAR_TMP_PATH, + CFStringGetSystemEncoding()); + CFURLRef tarUrl = CFURLCreateCopyAppendingPathComponent(NULL, libraryUrl, tarName, + false); + const char *tarPath = convertCFStringRefPathToCStringPath(CFURLCopyPath(tarUrl)); + + // create tzdata directory + mkdir(destPath.c_str(), S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH); + + // create stream + CFWriteStreamRef writeStream = CFWriteStreamCreateWithFile(NULL, tarUrl); + bool success = true; + + if (!CFWriteStreamOpen(writeStream)) + { + CFStreamError err = CFWriteStreamGetError(writeStream); + + if (err.domain == kCFStreamErrorDomainPOSIX) + { + printf("kCFStreamErrorDomainPOSIX %i\n", err.error); + } + else if(err.domain == kCFStreamErrorDomainMacOSStatus) + { + printf("kCFStreamErrorDomainMacOSStatus %i\n", err.error); + } + + success = false; + } + + if (!success) + { + remove(tarPath); + return false; + } + + // ======= extract tar ======== + + unsigned int bufferLength = 1024 * 256; // 256Kb + void *buffer = malloc(bufferLength); + + while (true) + { + int readBytes = gzread(tarFile, buffer, bufferLength); + + if (readBytes > 0) + { + CFIndex writtenBytes = CFWriteStreamWrite(writeStream, (unsigned char*)buffer, + readBytes); + + if (writtenBytes < 0) + { + CFStreamError err = CFWriteStreamGetError(writeStream); + printf("write stream error %i\n", err.error); + success = false; + break; + } + } + else if (readBytes == 0) + { + break; + } + else if (readBytes == -1) + { + printf("decompression failed\n"); + success = false; + break; + } + else + { + printf("unexpected zlib state\n"); + success = false; + break; + } + } + + CFWriteStreamClose(writeStream); + CFRelease(writeStream); + free(buffer); + gzclose(tarFile); + + if (!success) + { + remove(tarPath); + return false; + } + + // ======== extract files ========= + + uint64_t location = 0; // Position in the file + + // get file size + struct stat stat_buf; + int res = stat(tarPath, &stat_buf); + if (res != 0) + { + printf("error file size\n"); + remove(tarPath); + return false; + } + int64_t tarSize = stat_buf.st_size; + + // create read stream + CFReadStreamRef readStream = CFReadStreamCreateWithFile(kCFAllocatorDefault, tarUrl); + + if (!CFReadStreamOpen(readStream)) + { + CFStreamError err = CFReadStreamGetError(readStream); + + if (err.domain == kCFStreamErrorDomainPOSIX) + { + printf("kCFStreamErrorDomainPOSIX %i", err.error); + } + else if(err.domain == kCFStreamErrorDomainMacOSStatus) + { + printf("kCFStreamErrorDomainMacOSStatus %i", err.error); + } + + success = false; + } + + if (!success) + { + CFRelease(readStream); + remove(tarPath); + return false; + } + + int count = 0; + long size = 0; + + // process files + while (location < tarSize) + { + TarInfo info = getTarObjectInfo(readStream, location); + + if (!info.success || info.realContentSize == 0) + { + break; // something wrong or all files are read + } + + switch (info.objType) + { + case '0': // file + case '\0': // + { + std::string obj = getTarObject(readStream, info.blocksContentSize); +#if TAR_DEBUG + size += info.realContentSize; + printf("#%i %s file size %lld written total %ld from %lld\n", ++count, + info.objName.c_str(), info.realContentSize, size, tarSize); +#endif + writeFile(tzdataPathUrl, info.objName, obj, info.realContentSize); + location += info.blocksContentSize; + + break; + } + } + } + + CFReadStreamClose(readStream); + CFRelease(readStream); + + remove(tarPath); + + return true; +} + +TarInfo +getTarObjectInfo(CFReadStreamRef readStream, int64_t location) +{ + int64_t length = TAR_BLOCK_SIZE; + uint8_t buffer[length]; + + char type; + char name[TAR_NAME_SIZE + 1]; + char sizeBuf[TAR_SIZE_SIZE + 1]; + CFIndex bytesRead; + + bool avail = CFReadStreamHasBytesAvailable(readStream); + + bytesRead = CFReadStreamRead(readStream, buffer, length); + + if (bytesRead < 0) + { + CFStreamError err = CFReadStreamGetError(readStream); + printf("error reading tar object info %i", err.error); + return {false}; + } + + memcpy(&type, &buffer[TAR_TYPE_POSITION], 1); + + memset(&name, '\0', TAR_NAME_SIZE + 1); + memcpy(&name, &buffer[TAR_NAME_POSITION], TAR_NAME_SIZE); + + memset(&sizeBuf, '\0', TAR_SIZE_SIZE + 1); + memcpy(&sizeBuf, &buffer[TAR_SIZE_POSITION], TAR_SIZE_SIZE); + int64_t realSize = strtol(sizeBuf, NULL, 8); + int64_t blocksSize = realSize + (TAR_BLOCK_SIZE - (realSize % TAR_BLOCK_SIZE)); + + return {type, std::string(name), realSize, blocksSize, true}; +} + +std::string +getTarObject(CFReadStreamRef readStream, int64_t size) +{ + uint8_t buffer[size]; + + CFIndex bytesRead = CFReadStreamRead(readStream, buffer, size); + + if (bytesRead < 0) + { + CFStreamError err = CFReadStreamGetError(readStream); + printf("error reading tar object info %i", err.error); + } + + return std::string((char *)buffer); +} + +bool +writeFile(CFURLRef tzdataUrl, std::string fileName, std::string data, + int64_t realContentSize) +{ + // create stream + CFStringRef fileNameRef = CFStringCreateWithCString(NULL, fileName.c_str(), + CFStringGetSystemEncoding()); + CFURLRef url = CFURLCreateCopyAppendingPathComponent(NULL, tzdataUrl, fileNameRef, + false); + CFWriteStreamRef writeStream = CFWriteStreamCreateWithFile(NULL, url); + + // open stream + if (!CFWriteStreamOpen(writeStream)) + { + CFStreamError err = CFWriteStreamGetError(writeStream); + + if (err.domain == kCFStreamErrorDomainPOSIX) + { + printf("kCFStreamErrorDomainPOSIX %i\n", err.error); + } + else if(err.domain == kCFStreamErrorDomainMacOSStatus) + { + printf("kCFStreamErrorDomainMacOSStatus %i\n", err.error); + } + + CFRelease(writeStream); + return false; + } + + // trim empty space + uint8_t trimmedData[realContentSize + 1]; + memset(&trimmedData, '\0', realContentSize); + memcpy(&trimmedData, data.c_str(), realContentSize); + + // write + CFIndex writtenBytes = CFWriteStreamWrite(writeStream, trimmedData, realContentSize); + + if (writtenBytes < 0) + { + CFStreamError err = CFWriteStreamGetError(writeStream); + printf("write stream error %i\n", err.error); + } + + CFWriteStreamClose(writeStream); + CFRelease(writeStream); + writeStream = NULL; + + return true; +} + +} // namespace iOSUtils +} // namespace date + +#endif // TARGET_OS_IPHONE diff --git a/ext/date/julian.h b/ext/date/julian.h new file mode 100644 index 0000000..696be7c --- /dev/null +++ b/ext/date/julian.h @@ -0,0 +1,3046 @@ +#ifndef JULIAN_H +#define JULIAN_H + +// The MIT License (MIT) +// +// Copyright (c) 2016 Howard Hinnant +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +// +// Our apologies. When the previous paragraph was written, lowercase had not yet +// been invented (that woud involve another several millennia of evolution). +// We did not mean to shout. + +#include "date.h" + +namespace julian +{ + +// durations + +using days = date::days; + +using weeks = date::weeks; + +using years = std::chrono::duration + , days::period>>; + +using months = std::chrono::duration + >>; + +// time_point + +using sys_days = date::sys_days; +using local_days = date::local_days; + +// types + +struct last_spec +{ + explicit last_spec() = default; +}; + +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; + + CONSTCD14 day& operator++() NOEXCEPT; + CONSTCD14 day operator++(int) NOEXCEPT; + CONSTCD14 day& operator--() NOEXCEPT; + CONSTCD14 day operator--(int) NOEXCEPT; + + CONSTCD14 day& operator+=(const days& d) NOEXCEPT; + CONSTCD14 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; + +template +std::basic_ostream& +operator<<(std::basic_ostream& os, const day& d); + +// month + +class month +{ + unsigned char m_; + +public: + explicit CONSTCD11 month(unsigned m) NOEXCEPT; + + CONSTCD14 month& operator++() NOEXCEPT; + CONSTCD14 month operator++(int) NOEXCEPT; + CONSTCD14 month& operator--() NOEXCEPT; + CONSTCD14 month operator--(int) NOEXCEPT; + + CONSTCD14 month& operator+=(const months& m) NOEXCEPT; + CONSTCD14 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; + +template +std::basic_ostream& +operator<<(std::basic_ostream& os, const month& m); + +// year + +class year +{ + short y_; + +public: + explicit CONSTCD11 year(int y) NOEXCEPT; + + CONSTCD14 year& operator++() NOEXCEPT; + CONSTCD14 year operator++(int) NOEXCEPT; + CONSTCD14 year& operator--() NOEXCEPT; + CONSTCD14 year operator--(int) NOEXCEPT; + + CONSTCD14 year& operator+=(const years& y) NOEXCEPT; + CONSTCD14 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; + +template +std::basic_ostream& +operator<<(std::basic_ostream& os, const year& y); + +// weekday + +class weekday +{ + unsigned char wd_; +public: + explicit CONSTCD11 weekday(unsigned wd) NOEXCEPT; + explicit weekday(int) = delete; + CONSTCD11 weekday(const sys_days& dp) NOEXCEPT; + CONSTCD11 explicit weekday(const local_days& dp) NOEXCEPT; + + CONSTCD14 weekday& operator++() NOEXCEPT; + CONSTCD14 weekday operator++(int) NOEXCEPT; + CONSTCD14 weekday& operator--() NOEXCEPT; + CONSTCD14 weekday operator--(int) NOEXCEPT; + + CONSTCD14 weekday& operator+=(const days& d) NOEXCEPT; + CONSTCD14 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; + +template +std::basic_ostream& +operator<<(std::basic_ostream& os, const weekday& wd); + +// weekday_indexed + +class weekday_indexed +{ + unsigned char wd_ : 4; + unsigned char index_ : 4; + +public: + CONSTCD11 weekday_indexed(const julian::weekday& wd, unsigned index) NOEXCEPT; + + CONSTCD11 julian::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; + +template +std::basic_ostream& +operator<<(std::basic_ostream& os, const weekday_indexed& wdi); + +// weekday_last + +class weekday_last +{ + julian::weekday wd_; + +public: + explicit CONSTCD11 weekday_last(const julian::weekday& wd) NOEXCEPT; + + CONSTCD11 julian::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; + +template +std::basic_ostream& +operator<<(std::basic_ostream& os, const weekday_last& wdl); + +// year_month + +class year_month +{ + julian::year y_; + julian::month m_; + +public: + CONSTCD11 year_month(const julian::year& y, const julian::month& m) NOEXCEPT; + + CONSTCD11 julian::year year() const NOEXCEPT; + CONSTCD11 julian::month month() const NOEXCEPT; + + CONSTCD14 year_month& operator+=(const months& dm) NOEXCEPT; + CONSTCD14 year_month& operator-=(const months& dm) NOEXCEPT; + CONSTCD14 year_month& operator+=(const years& dy) NOEXCEPT; + CONSTCD14 year_month& operator-=(const years& dy) 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; + +template +std::basic_ostream& +operator<<(std::basic_ostream& os, const year_month& ym); + +// month_day + +class month_day +{ + julian::month m_; + julian::day d_; + +public: + CONSTCD11 month_day(const julian::month& m, const julian::day& d) NOEXCEPT; + + CONSTCD11 julian::month month() const NOEXCEPT; + CONSTCD11 julian::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; + +template +std::basic_ostream& +operator<<(std::basic_ostream& os, const month_day& md); + +// month_day_last + +class month_day_last +{ + julian::month m_; + +public: + CONSTCD11 explicit month_day_last(const julian::month& m) NOEXCEPT; + + CONSTCD11 julian::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; + +template +std::basic_ostream& +operator<<(std::basic_ostream& os, const month_day_last& mdl); + +// month_weekday + +class month_weekday +{ + julian::month m_; + julian::weekday_indexed wdi_; +public: + CONSTCD11 month_weekday(const julian::month& m, + const julian::weekday_indexed& wdi) NOEXCEPT; + + CONSTCD11 julian::month month() const NOEXCEPT; + CONSTCD11 julian::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; + +template +std::basic_ostream& +operator<<(std::basic_ostream& os, const month_weekday& mwd); + +// month_weekday_last + +class month_weekday_last +{ + julian::month m_; + julian::weekday_last wdl_; + +public: + CONSTCD11 month_weekday_last(const julian::month& m, + const julian::weekday_last& wd) NOEXCEPT; + + CONSTCD11 julian::month month() const NOEXCEPT; + CONSTCD11 julian::weekday_last weekday_last() 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; + +template +std::basic_ostream& +operator<<(std::basic_ostream& os, const month_weekday_last& mwdl); + +// class year_month_day + +class year_month_day +{ + julian::year y_; + julian::month m_; + julian::day d_; + +public: + CONSTCD11 year_month_day(const julian::year& y, const julian::month& m, + const julian::day& d) NOEXCEPT; + CONSTCD14 year_month_day(const year_month_day_last& ymdl) NOEXCEPT; + + CONSTCD14 year_month_day(sys_days dp) NOEXCEPT; + CONSTCD14 explicit year_month_day(local_days dp) NOEXCEPT; + + CONSTCD14 year_month_day& operator+=(const months& m) NOEXCEPT; + CONSTCD14 year_month_day& operator-=(const months& m) NOEXCEPT; + CONSTCD14 year_month_day& operator+=(const years& y) NOEXCEPT; + CONSTCD14 year_month_day& operator-=(const years& y) NOEXCEPT; + + CONSTCD11 julian::year year() const NOEXCEPT; + CONSTCD11 julian::month month() const NOEXCEPT; + CONSTCD11 julian::day day() const NOEXCEPT; + + CONSTCD14 operator sys_days() const NOEXCEPT; + CONSTCD14 explicit operator local_days() const NOEXCEPT; + CONSTCD14 bool ok() const NOEXCEPT; + +private: + static CONSTCD14 year_month_day from_days(days dp) NOEXCEPT; + CONSTCD14 days to_days() const 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; + +template +std::basic_ostream& +operator<<(std::basic_ostream& os, const year_month_day& ymd); + +// year_month_day_last + +class year_month_day_last +{ + julian::year y_; + julian::month_day_last mdl_; + +public: + CONSTCD11 year_month_day_last(const julian::year& y, + const julian::month_day_last& mdl) NOEXCEPT; + + CONSTCD14 year_month_day_last& operator+=(const months& m) NOEXCEPT; + CONSTCD14 year_month_day_last& operator-=(const months& m) NOEXCEPT; + CONSTCD14 year_month_day_last& operator+=(const years& y) NOEXCEPT; + CONSTCD14 year_month_day_last& operator-=(const years& y) NOEXCEPT; + + CONSTCD11 julian::year year() const NOEXCEPT; + CONSTCD11 julian::month month() const NOEXCEPT; + CONSTCD11 julian::month_day_last month_day_last() const NOEXCEPT; + CONSTCD14 julian::day day() const NOEXCEPT; + + CONSTCD14 operator sys_days() const NOEXCEPT; + CONSTCD14 explicit operator local_days() 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; + +template +std::basic_ostream& +operator<<(std::basic_ostream& os, const year_month_day_last& ymdl); + +// year_month_weekday + +class year_month_weekday +{ + julian::year y_; + julian::month m_; + julian::weekday_indexed wdi_; + +public: + CONSTCD11 year_month_weekday(const julian::year& y, const julian::month& m, + const julian::weekday_indexed& wdi) NOEXCEPT; + CONSTCD14 year_month_weekday(const sys_days& dp) NOEXCEPT; + CONSTCD14 explicit year_month_weekday(const local_days& dp) NOEXCEPT; + + CONSTCD14 year_month_weekday& operator+=(const months& m) NOEXCEPT; + CONSTCD14 year_month_weekday& operator-=(const months& m) NOEXCEPT; + CONSTCD14 year_month_weekday& operator+=(const years& y) NOEXCEPT; + CONSTCD14 year_month_weekday& operator-=(const years& y) NOEXCEPT; + + CONSTCD11 julian::year year() const NOEXCEPT; + CONSTCD11 julian::month month() const NOEXCEPT; + CONSTCD11 julian::weekday weekday() const NOEXCEPT; + CONSTCD11 unsigned index() const NOEXCEPT; + CONSTCD11 julian::weekday_indexed weekday_indexed() const NOEXCEPT; + + CONSTCD14 operator sys_days() const NOEXCEPT; + CONSTCD14 explicit operator local_days() const NOEXCEPT; + CONSTCD14 bool ok() const NOEXCEPT; + +private: + static CONSTCD14 year_month_weekday from_days(days dp) NOEXCEPT; + CONSTCD14 days to_days() const 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; + +template +std::basic_ostream& +operator<<(std::basic_ostream& os, const year_month_weekday& ymwdi); + +// year_month_weekday_last + +class year_month_weekday_last +{ + julian::year y_; + julian::month m_; + julian::weekday_last wdl_; + +public: + CONSTCD11 year_month_weekday_last(const julian::year& y, const julian::month& m, + const julian::weekday_last& wdl) NOEXCEPT; + + CONSTCD14 year_month_weekday_last& operator+=(const months& m) NOEXCEPT; + CONSTCD14 year_month_weekday_last& operator-=(const months& m) NOEXCEPT; + CONSTCD14 year_month_weekday_last& operator+=(const years& y) NOEXCEPT; + CONSTCD14 year_month_weekday_last& operator-=(const years& y) NOEXCEPT; + + CONSTCD11 julian::year year() const NOEXCEPT; + CONSTCD11 julian::month month() const NOEXCEPT; + CONSTCD11 julian::weekday weekday() const NOEXCEPT; + CONSTCD11 julian::weekday_last weekday_last() const NOEXCEPT; + + CONSTCD14 operator sys_days() const NOEXCEPT; + CONSTCD14 explicit operator local_days() const NOEXCEPT; + CONSTCD11 bool ok() const NOEXCEPT; + +private: + CONSTCD14 days to_days() 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; + +template +std::basic_ostream& +operator<<(std::basic_ostream& os, const year_month_weekday_last& ymwdl); + +#if !defined(_MSC_VER) || (_MSC_VER >= 1900) +inline namespace literals +{ + +CONSTCD11 julian::day operator "" _d(unsigned long long d) NOEXCEPT; +CONSTCD11 julian::year operator "" _y(unsigned long long y) NOEXCEPT; + +// CONSTDATA julian::month jan{1}; +// CONSTDATA julian::month feb{2}; +// CONSTDATA julian::month mar{3}; +// CONSTDATA julian::month apr{4}; +// CONSTDATA julian::month may{5}; +// CONSTDATA julian::month jun{6}; +// CONSTDATA julian::month jul{7}; +// CONSTDATA julian::month aug{8}; +// CONSTDATA julian::month sep{9}; +// CONSTDATA julian::month oct{10}; +// CONSTDATA julian::month nov{11}; +// CONSTDATA julian::month dec{12}; + +} // inline namespace literals +#endif // !defined(_MSC_VER) || (_MSC_VER >= 1900) + +//----------------+ +// Implementation | +//----------------+ + +// day + +CONSTCD11 inline day::day(unsigned d) NOEXCEPT : d_(static_cast(d)) {} +CONSTCD14 inline day& day::operator++() NOEXCEPT {++d_; return *this;} +CONSTCD14 inline day day::operator++(int) NOEXCEPT {auto tmp(*this); ++(*this); return tmp;} +CONSTCD14 inline day& day::operator--() NOEXCEPT {--d_; return *this;} +CONSTCD14 inline day day::operator--(int) NOEXCEPT {auto tmp(*this); --(*this); return tmp;} +CONSTCD14 inline day& day::operator+=(const days& d) NOEXCEPT {*this = *this + d; return *this;} +CONSTCD14 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; +} + +template +inline +std::basic_ostream& +operator<<(std::basic_ostream& os, const day& d) +{ + date::detail::save_stream _(os); + os.fill('0'); + os.flags(std::ios::dec | std::ios::right); + os.width(2); + os << static_cast(d); + return os; +} + +// month + +CONSTCD11 inline month::month(unsigned m) NOEXCEPT : m_(static_cast(m)) {} +CONSTCD14 inline month& month::operator++() NOEXCEPT {if (++m_ == 13) m_ = 1; return *this;} +CONSTCD14 inline month month::operator++(int) NOEXCEPT {auto tmp(*this); ++(*this); return tmp;} +CONSTCD14 inline month& month::operator--() NOEXCEPT {if (--m_ == 0) m_ = 12; return *this;} +CONSTCD14 inline month month::operator--(int) NOEXCEPT {auto tmp(*this); --(*this); return tmp;} + +CONSTCD14 +inline +month& +month::operator+=(const months& m) NOEXCEPT +{ + *this = *this + m; + return *this; +} + +CONSTCD14 +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; +} + +template +inline +std::basic_ostream& +operator<<(std::basic_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)) {} +CONSTCD14 inline year& year::operator++() NOEXCEPT {++y_; return *this;} +CONSTCD14 inline year year::operator++(int) NOEXCEPT {auto tmp(*this); ++(*this); return tmp;} +CONSTCD14 inline year& year::operator--() NOEXCEPT {--y_; return *this;} +CONSTCD14 inline year year::operator--(int) NOEXCEPT {auto tmp(*this); --(*this); return tmp;} +CONSTCD14 inline year& year::operator+=(const years& y) NOEXCEPT {*this = *this + y; return *this;} +CONSTCD14 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; +} + +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()}; +} + +template +inline +std::basic_ostream& +operator<<(std::basic_ostream& os, const year& y) +{ + date::detail::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; +} + +// 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 sys_days& dp) NOEXCEPT + : wd_(weekday_from_days(dp.time_since_epoch().count())) + {} + +CONSTCD11 +inline +weekday::weekday(const local_days& dp) NOEXCEPT + : wd_(weekday_from_days(dp.time_since_epoch().count())) + {} + +CONSTCD14 inline weekday& weekday::operator++() NOEXCEPT {if (++wd_ == 7) wd_ = 0; return *this;} +CONSTCD14 inline weekday weekday::operator++(int) NOEXCEPT {auto tmp(*this); ++(*this); return tmp;} +CONSTCD14 inline weekday& weekday::operator--() NOEXCEPT {if (wd_-- == 0) wd_ = 6; return *this;} +CONSTCD14 inline weekday weekday::operator--(int) NOEXCEPT {auto tmp(*this); --(*this); return tmp;} + +CONSTCD14 +inline +weekday& +weekday::operator+=(const days& d) NOEXCEPT +{ + *this = *this + d; + return *this; +} + +CONSTCD14 +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; +} + +template +inline +std::basic_ostream& +operator<<(std::basic_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; +} + +#if !defined(_MSC_VER) || (_MSC_VER >= 1900) +inline namespace literals +{ + +CONSTCD11 +inline +julian::day +operator "" _d(unsigned long long d) NOEXCEPT +{ + return julian::day{static_cast(d)}; +} + +CONSTCD11 +inline +julian::year +operator "" _y(unsigned long long y) NOEXCEPT +{ + return julian::year(static_cast(y)); +} +#endif // !defined(_MSC_VER) || (_MSC_VER >= 1900) + +CONSTDATA julian::last_spec last{}; + +CONSTDATA julian::month jan{1}; +CONSTDATA julian::month feb{2}; +CONSTDATA julian::month mar{3}; +CONSTDATA julian::month apr{4}; +CONSTDATA julian::month may{5}; +CONSTDATA julian::month jun{6}; +CONSTDATA julian::month jul{7}; +CONSTDATA julian::month aug{8}; +CONSTDATA julian::month sep{9}; +CONSTDATA julian::month oct{10}; +CONSTDATA julian::month nov{11}; +CONSTDATA julian::month dec{12}; + +CONSTDATA julian::weekday sun{0u}; +CONSTDATA julian::weekday mon{1u}; +CONSTDATA julian::weekday tue{2u}; +CONSTDATA julian::weekday wed{3u}; +CONSTDATA julian::weekday thu{4u}; +CONSTDATA julian::weekday fri{5u}; +CONSTDATA julian::weekday sat{6u}; + +#if !defined(_MSC_VER) || (_MSC_VER >= 1900) +} // inline namespace literals +#endif + +// weekday_indexed + +CONSTCD11 +inline +weekday +weekday_indexed::weekday() const NOEXCEPT +{ + return julian::weekday{static_cast(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 julian::weekday& wd, unsigned index) NOEXCEPT + : wd_(static_cast(static_cast(wd))) + , index_(static_cast(index)) + {} + +template +inline +std::basic_ostream& +operator<<(std::basic_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 julian::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 julian::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); +} + +template +inline +std::basic_ostream& +operator<<(std::basic_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 julian::year& y, const julian::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();} + +CONSTCD14 +inline +year_month& +year_month::operator+=(const months& dm) NOEXCEPT +{ + *this = *this + dm; + return *this; +} + +CONSTCD14 +inline +year_month& +year_month::operator-=(const months& dm) NOEXCEPT +{ + *this = *this - dm; + return *this; +} + +CONSTCD14 +inline +year_month& +year_month::operator+=(const years& dy) NOEXCEPT +{ + *this = *this + dy; + return *this; +} + +CONSTCD14 +inline +year_month& +year_month::operator-=(const years& dy) NOEXCEPT +{ + *this = *this - dy; + return *this; +} + +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; +} + +template +inline +std::basic_ostream& +operator<<(std::basic_ostream& os, const year_month& ym) +{ + return os << ym.year() << '/' << ym.month(); +} + +// month_day + +CONSTCD11 +inline +month_day::month_day(const julian::month& m, const julian::day& d) NOEXCEPT + : m_(m) + , d_(d) + {} + +CONSTCD11 inline julian::month month_day::month() const NOEXCEPT {return m_;} +CONSTCD11 inline julian::day month_day::day() const NOEXCEPT {return d_;} + +CONSTCD14 +inline +bool +month_day::ok() const NOEXCEPT +{ + CONSTDATA julian::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); +} + +template +inline +std::basic_ostream& +operator<<(std::basic_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 julian::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); +} + +template +inline +std::basic_ostream& +operator<<(std::basic_ostream& os, const month_day_last& mdl) +{ + return os << mdl.month() << "/last"; +} + +// month_weekday + +CONSTCD11 +inline +month_weekday::month_weekday(const julian::month& m, + const julian::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); +} + +template +inline +std::basic_ostream& +operator<<(std::basic_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 julian::month& m, + const julian::weekday_last& wdl) NOEXCEPT + : m_(m) + , wdl_(wdl) + {} + +CONSTCD11 inline month month_weekday_last::month() const NOEXCEPT {return m_;} + +CONSTCD11 +inline +weekday_last +month_weekday_last::weekday_last() const NOEXCEPT +{ + return wdl_; +} + +CONSTCD11 +inline +bool +month_weekday_last::ok() const NOEXCEPT +{ + return m_.ok() && wdl_.ok(); +} + +CONSTCD11 +inline +bool +operator==(const month_weekday_last& x, const month_weekday_last& y) NOEXCEPT +{ + return x.month() == y.month() && x.weekday_last() == y.weekday_last(); +} + +CONSTCD11 +inline +bool +operator!=(const month_weekday_last& x, const month_weekday_last& y) NOEXCEPT +{ + return !(x == y); +} + +template +inline +std::basic_ostream& +operator<<(std::basic_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 julian::year& y, + const julian::month_day_last& mdl) NOEXCEPT + : y_(y) + , mdl_(mdl) + {} + +CONSTCD14 +inline +year_month_day_last& +year_month_day_last::operator+=(const months& m) NOEXCEPT +{ + *this = *this + m; + return *this; +} + +CONSTCD14 +inline +year_month_day_last& +year_month_day_last::operator-=(const months& m) NOEXCEPT +{ + *this = *this - m; + return *this; +} + +CONSTCD14 +inline +year_month_day_last& +year_month_day_last::operator+=(const years& y) NOEXCEPT +{ + *this = *this + y; + return *this; +} + +CONSTCD14 +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 mdl_.month();} + +CONSTCD11 +inline +month_day_last +year_month_day_last::month_day_last() const NOEXCEPT +{ + return mdl_; +} + +CONSTCD14 +inline +day +year_month_day_last::day() const NOEXCEPT +{ + CONSTDATA julian::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 month() != feb || !y_.is_leap() ? d[static_cast(month())-1] : 29_d; +} + +CONSTCD14 +inline +year_month_day_last::operator sys_days() const NOEXCEPT +{ + return sys_days(year()/month()/day()); +} + +CONSTCD14 +inline +year_month_day_last::operator local_days() const NOEXCEPT +{ + return local_days(year()/month()/day()); +} + +CONSTCD11 +inline +bool +year_month_day_last::ok() const NOEXCEPT +{ + return y_.ok() && mdl_.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_day_last() == y.month_day_last(); +} + +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_day_last() < y.month_day_last())); +} + +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); +} + +template +inline +std::basic_ostream& +operator<<(std::basic_ostream& os, const year_month_day_last& ymdl) +{ + return os << ymdl.year() << '/' << ymdl.month_day_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_day_last()}; +} + +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 julian::year& y, const julian::month& m, + const julian::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(sys_days dp) NOEXCEPT + : year_month_day(from_days(dp.time_since_epoch())) + {} + +CONSTCD14 +inline +year_month_day::year_month_day(local_days dp) NOEXCEPT + : year_month_day(from_days(dp.time_since_epoch())) + {} + +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_;} + +CONSTCD14 +inline +year_month_day& +year_month_day::operator+=(const months& m) NOEXCEPT +{ + *this = *this + m; + return *this; +} + +CONSTCD14 +inline +year_month_day& +year_month_day::operator-=(const months& m) NOEXCEPT +{ + *this = *this - m; + return *this; +} + +CONSTCD14 +inline +year_month_day& +year_month_day::operator+=(const years& y) NOEXCEPT +{ + *this = *this + y; + return *this; +} + +CONSTCD14 +inline +year_month_day& +year_month_day::operator-=(const years& y) NOEXCEPT +{ + *this = *this - y; + return *this; +} + +CONSTCD14 +inline +days +year_month_day::to_days() 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-3) / 4; + auto const yoe = static_cast(y - era * 4); // [0, 3] + auto const doy = (153*(m > 2 ? m-3 : m+9) + 2)/5 + d-1; // [0, 365] + auto const doe = yoe * 365 + doy; // [0, 1460] + return days{era * 1461 + static_cast(doe) - 719470}; +} + +CONSTCD14 +inline +year_month_day::operator sys_days() const NOEXCEPT +{ + return sys_days{to_days()}; +} + +CONSTCD14 +inline +year_month_day::operator local_days() const NOEXCEPT +{ + return local_days{to_days()}; +} + +CONSTCD14 +inline +bool +year_month_day::ok() const NOEXCEPT +{ + if (!(y_.ok() && 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); +} + +template +inline +std::basic_ostream& +operator<<(std::basic_ostream& os, const year_month_day& ymd) +{ + date::detail::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_days(days 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.count() + 719470; + auto const era = (z >= 0 ? z : z - 1460) / 1461; + auto const doe = static_cast(z - era * 1461); // [0, 1460] + auto const yoe = (doe - doe/1460) / 365; // [0, 3] + auto const y = static_cast(yoe) + era * 4; + auto const doy = doe - 365*yoe; // [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 < 10 ? mp+3 : mp-9; // [1, 12] + return year_month_day{julian::year{y + (m <= 2)}, julian::month(m), julian::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 julian::year& y, const julian::month& m, + const julian::weekday_indexed& wdi) + NOEXCEPT + : y_(y) + , m_(m) + , wdi_(wdi) + {} + +CONSTCD14 +inline +year_month_weekday::year_month_weekday(const sys_days& dp) NOEXCEPT + : year_month_weekday(from_days(dp.time_since_epoch())) + {} + +CONSTCD14 +inline +year_month_weekday::year_month_weekday(const local_days& dp) NOEXCEPT + : year_month_weekday(from_days(dp.time_since_epoch())) + {} + +CONSTCD14 +inline +year_month_weekday& +year_month_weekday::operator+=(const months& m) NOEXCEPT +{ + *this = *this + m; + return *this; +} + +CONSTCD14 +inline +year_month_weekday& +year_month_weekday::operator-=(const months& m) NOEXCEPT +{ + *this = *this - m; + return *this; +} + +CONSTCD14 +inline +year_month_weekday& +year_month_weekday::operator+=(const years& y) NOEXCEPT +{ + *this = *this + y; + return *this; +} + +CONSTCD14 +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 +year_month_weekday::weekday() const NOEXCEPT +{ + return wdi_.weekday(); +} + +CONSTCD11 +inline +unsigned +year_month_weekday::index() const NOEXCEPT +{ + return wdi_.index(); +} + +CONSTCD11 +inline +weekday_indexed +year_month_weekday::weekday_indexed() const NOEXCEPT +{ + return wdi_; +} + +CONSTCD14 +inline +year_month_weekday::operator sys_days() const NOEXCEPT +{ + return sys_days{to_days()}; +} + +CONSTCD14 +inline +year_month_weekday::operator local_days() const NOEXCEPT +{ + return local_days{to_days()}; +} + +CONSTCD14 +inline +bool +year_month_weekday::ok() const NOEXCEPT +{ + if (!y_.ok() || !m_.ok() || !wdi_.weekday().ok() || wdi_.index() < 1) + return false; + if (wdi_.index() <= 4) + return true; + auto d2 = wdi_.weekday() - julian::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_days(days d) NOEXCEPT +{ + sys_days dp{d}; + auto const wd = julian::weekday(dp); + auto const ymd = year_month_day(dp); + return {ymd.year(), ymd.month(), wd[(static_cast(ymd.day())-1)/7+1]}; +} + +CONSTCD14 +inline +days +year_month_weekday::to_days() const NOEXCEPT +{ + auto d = sys_days(y_/m_/1); + return (d + (wdi_.weekday() - julian::weekday(d) + days{(wdi_.index()-1)*7}) + ).time_since_epoch(); +} + +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); +} + +template +inline +std::basic_ostream& +operator<<(std::basic_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 julian::year& y, + const julian::month& m, + const julian::weekday_last& wdl) NOEXCEPT + : y_(y) + , m_(m) + , wdl_(wdl) + {} + +CONSTCD14 +inline +year_month_weekday_last& +year_month_weekday_last::operator+=(const months& m) NOEXCEPT +{ + *this = *this + m; + return *this; +} + +CONSTCD14 +inline +year_month_weekday_last& +year_month_weekday_last::operator-=(const months& m) NOEXCEPT +{ + *this = *this - m; + return *this; +} + +CONSTCD14 +inline +year_month_weekday_last& +year_month_weekday_last::operator+=(const years& y) NOEXCEPT +{ + *this = *this + y; + return *this; +} + +CONSTCD14 +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 +year_month_weekday_last::weekday() const NOEXCEPT +{ + return wdl_.weekday(); +} + +CONSTCD11 +inline +weekday_last +year_month_weekday_last::weekday_last() const NOEXCEPT +{ + return wdl_; +} + +CONSTCD14 +inline +year_month_weekday_last::operator sys_days() const NOEXCEPT +{ + return sys_days{to_days()}; +} + +CONSTCD14 +inline +year_month_weekday_last::operator local_days() const NOEXCEPT +{ + return local_days{to_days()}; +} + +CONSTCD11 +inline +bool +year_month_weekday_last::ok() const NOEXCEPT +{ + return y_.ok() && m_.ok() && wdl_.ok(); +} + +CONSTCD14 +inline +days +year_month_weekday_last::to_days() const NOEXCEPT +{ + auto const d = sys_days(y_/m_/last); + return (d - (julian::weekday{d} - wdl_.weekday())).time_since_epoch(); +} + +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); +} + +template +inline +std::basic_ostream& +operator<<(std::basic_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}; +} + +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(), month_day_last{ym.month()}}; +} + +CONSTCD11 +inline +year_month_day_last +operator/(const year& y, const month_day_last& mdl) NOEXCEPT +{ + return {y, mdl}; +} + +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; +} + +} // namespace julian + +#endif // JULIAN_H diff --git a/ext/date/tz.cpp b/ext/date/tz.cpp new file mode 100644 index 0000000..e3c4da7 --- /dev/null +++ b/ext/date/tz.cpp @@ -0,0 +1,3120 @@ +// The MIT License (MIT) +// +// Copyright (c) 2015, 2016 Howard Hinnant +// Copyright (c) 2015 Ville Voutilainen +// Copyright (c) 2016 Alexander Kormanovsky +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +// +// Our apologies. When the previous paragraph was written, lowercase had not yet +// been invented (that woud involve another several millennia of evolution). +// We did not mean to shout. + +#ifdef _WIN32 +// Windows.h will be included directly and indirectly (e.g. by curl). +// We need to define these macros to prevent Windows.h bringing in +// more than we need and do it eearly so Windows.h doesn't get included +// without these macros having been defined. +// min/max macrosinterfere with the C++ versions. +#ifndef NOMINMAX +#define NOMINMAX +#endif +// We don't need all that Windows has to offer. +#ifndef WIN32_LEAN_AND_MEAN +#define WIN32_LEAN_AND_MEAN +#endif // _WIN32 + +// None of this happens with the MS SDK (at least VS14 which I tested), but: +// Compiling with mingw, we get "error: 'KF_FLAG_DEFAULT' was not declared in this scope." +// and error: 'SHGetKnownFolderPath' was not declared in this scope.". +// It seems when using mingw NTDDI_VERSION is undefined and that +// causes KNOWN_FOLDER_FLAG and the KF_ flags to not get defined. +// So we must define NTDDI_VERSION to get those flags on mingw. +// The docs say though here: +// https://msdn.microsoft.com/en-nz/library/windows/desktop/aa383745(v=vs.85).aspx +// that "If you define NTDDI_VERSION, you must also define _WIN32_WINNT." +// So we declare we require Vista or greater. +#ifdef __MINGW32__ + +#ifndef NTDDI_VERSION +#define NTDDI_VERSION 0x06000000 +#define _WIN32_WINNT _WIN32_WINNT_VISTA +#elif NTDDI_VERSION < 0x06000000 +#warning "If this fails to compile NTDDI_VERSION may be to low. See comments above." +#endif +// But once we define the values above we then get this linker error: +// "tz.cpp:(.rdata$.refptr.FOLDERID_Downloads[.refptr.FOLDERID_Downloads]+0x0): " +// "undefined reference to `FOLDERID_Downloads'" +// which #include cures see: +// https://support.microsoft.com/en-us/kb/130869 +#include +// But with included, the error moves on to: +// error: 'FOLDERID_Downloads' was not declared in this scope +// Which #include cures. +#include + +#endif // __MINGW32__ + +#include +#endif // _WIN32 + +#include "tz_private.h" +#include "ios.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef _WIN32 +#include +#include +#endif // _WIN32 + +// unistd.h is used on some platforms as part of the the means to get +// the current time zone. On Win32 Windows.h provides a means to do it. +// gcc/mingw supports unistd.h on Win32 but MSVC does not. + +#ifdef _WIN32 +# include // _unlink etc. +# include // CoTaskFree, ShGetKnownFolderPath etc. +# if HAS_REMOTE_API +# include // _mkdir +# include // ShFileOperation etc. +# endif // HAS_REMOTE_API +#else // !WIN32 +# include +# include +# if !USE_SHELL_API +# include +# include +# include +# include +# include +# include +# endif //!USE_SHELL_API +#endif // !WIN32 + + +#if HAS_REMOTE_API +// Note curl includes windows.h so we must include curl AFTER definitions of things +// that effect windows.h such as NOMINMAX. +#include +#endif + +#ifdef _WIN32 +static CONSTDATA char folder_delimiter = '\\'; + +namespace +{ + struct task_mem_deleter + { + void operator()(wchar_t buf[]) + { + if (buf != nullptr) + CoTaskMemFree(buf); + } + }; + using co_task_mem_ptr = std::unique_ptr; +} + +// We might need to know certain locations even if not using the remote API, +// so keep these routines out of that block for now. +static +std::string +get_known_folder(const GUID& folderid) +{ + std::string folder; + PWSTR pfolder = nullptr; + HRESULT hr = SHGetKnownFolderPath(folderid, KF_FLAG_DEFAULT, NULL, &pfolder); + if (SUCCEEDED(hr)) + { + co_task_mem_ptr folder_ptr(pfolder); + folder = std::string(folder_ptr.get(), folder_ptr.get() + wcslen(folder_ptr.get())); + } + return folder; +} + +// Usually something like "c:\Program Files". +static +std::string +get_program_folder() +{ + return get_known_folder(FOLDERID_ProgramFiles); +} + +// Usually something like "c:\Users\username\Downloads". +static +std::string +get_download_folder() +{ + return get_known_folder(FOLDERID_Downloads); +} + +#else // !_WIN32 + +static CONSTDATA char folder_delimiter = '/'; + +static +std::string +expand_path(std::string path) +{ +#if TARGET_OS_IPHONE + return date::iOSUtils::get_tzdata_path(); +#else + ::wordexp_t w{}; + ::wordexp(path.c_str(), &w, 0); + assert(w.we_wordc == 1); + path = w.we_wordv[0]; + ::wordfree(&w); + return path; +#endif +} + +#endif // !_WIN32 + +namespace date +{ +// +---------------------+ +// | Begin Configuration | +// +---------------------+ + +using namespace detail; + +static std::string get_install() +{ +#ifdef _WIN32 + std::string install = get_download_folder(); + install += folder_delimiter; + install += "tzdata"; +#else + std::string install = expand_path("~/Downloads/tzdata"); +#endif + return install; +} + +#ifndef INSTALL + +static const std::string install = get_install(); + +#else // INSTALL + +#define STRINGIZEIMP(x) #x +#define STRINGIZE(x) STRINGIZEIMP(x) + +static const std::string install = STRINGIZE(INSTALL) + + std::string(1, folder_delimiter) + "tzdata"; + +#endif // INSTALL + +static +std::string +get_download_gz_file(const std::string& version) +{ + auto file = install + version + ".tar.gz"; + return file; +} + +static const std::vector files = +{ + "africa", "antarctica", "asia", "australasia", "backward", "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(); + +CONSTDATA auto min_day = date::jan/1; +CONSTDATA auto max_day = date::dec/31; + +// +-------------------+ +// | End Configuration | +// +-------------------+ + +namespace detail +{ +struct undocumented {explicit undocumented() = default;}; +} + +#ifndef _MSC_VER +static_assert(min_year <= max_year, "Configuration error"); +#endif + +#ifdef TIMEZONE_MAPPING + +namespace // Put types in an anonymous name space. +{ + +// A simple type to manage RAII for key handles and to +// implement the trivial registry interface we need. +// Not intended to be general-purpose. +class reg_key +{ +private: + // Note there is no value documented to be an invalid handle value. + // Not NULL nor INVALID_HANDLE_VALUE. We must rely on is_open. + HKEY m_key = nullptr; + bool m_is_open = false; +public: + ~reg_key() + { + close(); + } + + reg_key() = default; + reg_key(const reg_key&) = delete; + reg_key& operator=(const reg_key&) = delete; + + HKEY handle() + { + return m_key; + } + + bool is_open() const + { + return m_is_open; + } + + LONG open(const wchar_t* key_name) + { + LONG result; + result = RegOpenKeyExW(HKEY_LOCAL_MACHINE, key_name, 0, KEY_READ, &m_key); + if (result == ERROR_SUCCESS) + m_is_open = true; + return result; + } + + LONG close() + { + if (m_is_open) + { + auto result = RegCloseKey(m_key); + assert(result == ERROR_SUCCESS); + if (result == ERROR_SUCCESS) + { + m_is_open = false; + m_key = nullptr; + } + return result; + } + return ERROR_SUCCESS; + } + + // WARNING: this function is not a general-purpose function. + // It has a hard-coded value size limit that should be sufficient for our use cases. + bool get_string(const wchar_t* key_name, std::string& value, std::wstring_convert>& converter) + { + value.clear(); + wchar_t value_buffer[256]; + // in/out parameter. Documentation say that size is a count of bytes not chars. + DWORD size = sizeof(value_buffer) - sizeof(value_buffer[0]); + DWORD tzi_type = REG_SZ; + if (RegQueryValueExW(handle(), key_name, nullptr, &tzi_type, + reinterpret_cast(value_buffer), &size) == ERROR_SUCCESS) + { + // Function does not guarantee to null terminate. + value_buffer[size/sizeof(value_buffer[0])] = L'\0'; + value = converter.to_bytes(value_buffer); + return true; + } + return false; + } + + bool get_binary(const wchar_t* key_name, void* value, int value_size) + { + DWORD size = value_size; + DWORD type = REG_BINARY; + if (RegQueryValueExW(handle(), key_name, nullptr, &type, + reinterpret_cast(value), &size) == ERROR_SUCCESS + && (int) size == value_size) + return true; + return false; + } +}; + +} // anonymous namespace + +static +std::string +get_download_mapping_file(const std::string& version) +{ + auto file = install + version + "windowsZones.xml"; + return file; +} + +// Parse this XML file: +// http://unicode.org/repos/cldr/trunk/common/supplemental/windowsZones.xml +// The parsing method is designed to be simple and quick. It is not overly +// forgiving of change but it should diagnose basic format issues. +// See timezone_mapping structure for more info. +static +std::vector +load_timezone_mappings_from_xml_file(const std::string& input_path) +{ + std::size_t line_num = 0; + std::vector mappings; + std::string line; + + std::ifstream is(input_path); + if (!is.is_open()) + { + // We don't emit file exceptions because that's an implementation detail. + std::string msg = "Error opening time zone mapping file \""; + msg += input_path; + msg += "\"."; + throw std::runtime_error(msg); + } + + auto error = [&input_path, &line_num](const char* info) + { + std::string msg = "Error loading time zone mapping file \""; + msg += input_path; + msg += "\" at line "; + msg += std::to_string(line_num); + msg += ": "; + msg += info; + throw std::runtime_error(msg); + }; + // [optional space]a="b" + auto read_attribute = [&line_num, &line, &error] + (const char* name, std::string& value, std::size_t startPos) + ->std::size_t + { + value.clear(); + // Skip leading space before attribute name. + std::size_t spos = line.find_first_not_of(' ', startPos); + if (spos == std::string::npos) + spos = startPos; + // Assume everything up to next = is the attribute name + // and that an = will always delimit that. + std::size_t epos = line.find('=', spos); + if (epos == std::string::npos) + error("Expected \'=\' right after attribute name."); + std::size_t name_len = epos - spos; + // Expect the name we find matches the name we expect. + if (line.compare(spos, name_len, name) != 0) + { + std::string msg; + msg = "Expected attribute name \'"; + msg += name; + msg += "\' around position "; + msg += std::to_string(spos); + msg += " but found something else."; + error(msg.c_str()); + } + ++epos; // Skip the '=' that is after the attribute name. + spos = epos; + if (spos < line.length() && line[spos] == '\"') + ++spos; // Skip the quote that is before the attribute value. + else + { + std::string msg = "Expected '\"' to begin value of attribute \'"; + msg += name; + msg += "\'."; + error(msg.c_str()); + } + epos = line.find('\"', spos); + if (epos == std::string::npos) + { + std::string msg = "Expected '\"' to end value of attribute \'"; + msg += name; + msg += "\'."; + error(msg.c_str()); + } + // Extract everything in between the quotes. Note no escaping is done. + std::size_t value_len = epos - spos; + value.assign(line, spos, value_len); + ++epos; // Skip the quote that is after the attribute value; + return epos; + }; + + // Quick but not overly forgiving XML mapping file processing. + bool mapTimezonesOpenTagFound = false; + bool mapTimezonesCloseTagFound = false; + bool mapZoneOpenTagFound = false; + bool mapTZoneCloseTagFound = false; + std::size_t mapZonePos = std::string::npos; + std::size_t mapTimezonesPos = std::string::npos; + CONSTDATA char mapTimeZonesOpeningTag[] = { ""); + mapTimezonesCloseTagFound = (mapTimezonesPos != std::string::npos); + if (!mapTimezonesCloseTagFound) + { + std::size_t commentPos = line.find(" " << x.target_; +} + +// leap + +leap::leap(const std::string& s, detail::undocumented) +{ + 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 +bool +file_exists(const std::string& filename) +{ +#ifdef _WIN32 + return ::_access(filename.c_str(), 0) == 0; +#else + return ::access(filename.c_str(), F_OK) == 0; +#endif +} + +#if HAS_REMOTE_API + +// CURL tools + +static +int +curl_global() +{ + if (::curl_global_init(CURL_GLOBAL_DEFAULT) != 0) + throw std::runtime_error("CURL global initialization failed"); + return 0; +} + +static const auto curl_delete = [](CURL* p) {::curl_easy_cleanup(p);}; + +static +std::unique_ptr +curl_init() +{ + static const auto curl_is_now_initiailized = curl_global(); + (void)curl_is_now_initiailized; + return std::unique_ptr{::curl_easy_init(), curl_delete}; +} + +static +bool +download_to_string(const std::string& url, std::string& str) +{ + str.clear(); + auto curl = curl_init(); + if (!curl) + return false; + std::string version; + curl_easy_setopt(curl.get(), CURLOPT_URL, url.c_str()); + curl_write_callback write_cb = [](char* contents, std::size_t size, std::size_t nmemb, + void* userp) -> std::size_t + { + auto& str = *static_cast(userp); + auto realsize = size * nmemb; + str.append(contents, realsize); + return realsize; + }; + curl_easy_setopt(curl.get(), CURLOPT_WRITEFUNCTION, write_cb); + curl_easy_setopt(curl.get(), CURLOPT_WRITEDATA, &str); + auto res = curl_easy_perform(curl.get()); + return (res == CURLE_OK); +} + +namespace +{ + enum class download_file_options { binary, text }; +} + +static +bool +download_to_file(const std::string& url, const std::string& local_filename, + download_file_options opts) +{ + auto curl = curl_init(); + if (!curl) + return false; + curl_easy_setopt(curl.get(), CURLOPT_URL, url.c_str()); + curl_write_callback write_cb = [](char* contents, std::size_t size, std::size_t nmemb, + void* userp) -> std::size_t + { + auto& of = *static_cast(userp); + auto realsize = size * nmemb; + of.write(contents, realsize); + return realsize; + }; + curl_easy_setopt(curl.get(), CURLOPT_WRITEFUNCTION, write_cb); + decltype(curl_easy_perform(curl.get())) res; + { + std::ofstream of(local_filename, + opts == download_file_options::binary ? + std::ofstream::out | std::ofstream::binary : + std::ofstream::out); + of.exceptions(std::ios::badbit); + curl_easy_setopt(curl.get(), CURLOPT_WRITEDATA, &of); + res = curl_easy_perform(curl.get()); + } + return res == CURLE_OK; +} + +std::string +remote_version() +{ + std::string version; + std::string str; + if (download_to_string("http://www.iana.org/time-zones", str)) + { + CONSTDATA char db[] = "/time-zones/repository/releases/tzdata"; + CONSTDATA auto db_size = sizeof(db) - 1; + auto p = str.find(db, 0, db_size); + const int ver_str_len = 5; + if (p != std::string::npos && p + (db_size + ver_str_len) <= str.size()) + version = str.substr(p + db_size, ver_str_len); + } + return version; +} + +bool +remote_download(const std::string& version) +{ + assert(!version.empty()); + auto url = "http://www.iana.org/time-zones/repository/releases/tzdata" + version + + ".tar.gz"; + bool result = download_to_file(url, get_download_gz_file(version), + download_file_options::binary); +#ifdef TIMEZONE_MAPPING + if (result) + { + auto mapping_file = get_download_mapping_file(version); + result = download_to_file("http://unicode.org/repos/cldr/trunk/common/" + "supplemental/windowsZones.xml", + mapping_file, download_file_options::text); + } +#endif + return result; +} + +// TODO! Using system() create a process and a console window. +// This is useful to see what errors may occur but is slow and distracting. +// Consider implementing this functionality more directly, such as +// using _mkdir and CreateProcess etc. +// But use the current means now as matches Unix implementations and while +// in proof of concept / testing phase. +// TODO! Use eventually. +static +bool +remove_folder_and_subfolders(const std::string& folder) +{ +#ifdef _WIN32 +# if USE_SHELL_API + // Delete the folder contents by deleting the folder. + std::string cmd = "rd /s /q \""; + cmd += folder; + cmd += '\"'; + return std::system(cmd.c_str()) == EXIT_SUCCESS; +# else // !USE_SHELL_API + // Create a buffer containing the path to delete. It must be terminated + // by two nuls. Who designs these API's... + std::vector from; + from.assign(folder.begin(), folder.end()); + from.push_back('\0'); + from.push_back('\0'); + SHFILEOPSTRUCT fo{}; // Zero initialize. + fo.wFunc = FO_DELETE; + fo.pFrom = from.data(); + fo.fFlags = FOF_NO_UI; + int ret = SHFileOperation(&fo); + if (ret == 0 && !fo.fAnyOperationsAborted) + return true; + return false; +# endif // !USE_SHELL_API +#else // !WIN32 +# if USE_SHELL_API + return std::system(("rm -R " + folder).c_str()) == EXIT_SUCCESS; +# else // !USE_SHELL_API + struct dir_deleter { + dir_deleter() {} + void operator()(DIR* d) const + { + if (d != nullptr) + { + int result = closedir(d); + assert(result == 0); + } + } + }; + using closedir_ptr = std::unique_ptr; + + std::string filename; + struct stat statbuf; + std::size_t folder_len = folder.length(); + struct dirent* p = nullptr; + + closedir_ptr d(opendir(folder.c_str())); + bool r = d.get() != nullptr; + while (r && (p=readdir(d.get())) != nullptr) + { + if (strcmp(p->d_name, ".") == 0 || strcmp(p->d_name, "..") == 0) + continue; + + // + 2 for path delimiter and nul terminator. + std::size_t buf_len = folder_len + strlen(p->d_name) + 2; + filename.resize(buf_len); + std::size_t path_len = static_cast( + snprintf(&filename[0], buf_len, "%s/%s", folder.c_str(), p->d_name)); + assert(path_len == buf_len - 1); + filename.resize(path_len); + + if (stat(filename.c_str(), &statbuf) == 0) + r = S_ISDIR(statbuf.st_mode) + ? remove_folder_and_subfolders(filename) + : unlink(filename.c_str()) == 0; + } + d.reset(); + + if (r) + r = rmdir(folder.c_str()) == 0; + + return r; +# endif // !USE_SHELL_API +#endif // !WIN32 +} + +static +bool +make_directory(const std::string& folder) +{ +#ifdef _WIN32 +# if USE_SHELL_API + // Re-create the folder. + std::string cmd = "mkdir \""; + cmd += folder; + cmd += '\"'; + return std::system(cmd.c_str()) == EXIT_SUCCESS; +# else // !USE_SHELL_API + return _mkdir(folder.c_str()) == 0; +# endif // !USE_SHELL_API +#else // !WIN32 +# if USE_SHELL_API + return std::system(("mkdir " + folder).c_str()) == EXIT_SUCCESS; +# else // !USE_SHELL_API + return mkdir(folder.c_str(), 0777) == 0; +# endif // !USE_SHELL_API +#endif +} + +static +bool +delete_file(const std::string& file) +{ +#ifdef _WIN32 +# if USE_SHELL_API + std::string cmd = "del \""; + cmd += file; + cmd += '\"'; + return std::system(cmd.c_str()) == 0; +# else // !USE_SHELL_API + return _unlink(file.c_str()) == 0; +# endif // !USE_SHELL_API +#else // !WIN32 +# if USE_SHELL_API + return std::system(("rm " + file).c_str()) == EXIT_SUCCESS; +# else // !USE_SHELL_API + return unlink(file.c_str()) == 0; +# endif // !USE_SHELL_API +#endif // !WIN32 +} + +#ifdef TIMEZONE_MAPPING + +static +bool +move_file(const std::string& from, const std::string& to) +{ +#ifdef _WIN32 +# if USE_SHELL_API + std::string cmd = "move \""; + cmd += from; + cmd += "\" \""; + cmd += to; + cmd += '\"'; + return std::system(cmd.c_str()) == EXIT_SUCCESS; +# else // !USE_SHELL_API + return !!::MoveFile(from.c_str(), to.c_str()); +# endif // !USE_SHELL_API +#else // !WIN32 +# if USE_SHELL_API + return std::system(("mv " + from + " " + to).c_str()) == EXIT_SUCCESS; +# else + return rename(from, to) == 0); +# endif +#endif // !WIN32 +} + +#endif // TIMEZONE_MAPPING + +#ifdef _WIN32 + +// Note folder can and usually does contain spaces. +static +std::string +get_unzip_program() +{ + std::string path; + + // 7-Zip appears to note its location in the registry. + // If that doesn't work, fall through and take a guess, but it will likely be wrong. + HKEY hKey = nullptr; + if (RegOpenKeyExA(HKEY_LOCAL_MACHINE, "SOFTWARE\\7-Zip", 0, KEY_READ, &hKey) == ERROR_SUCCESS) + { + char value_buffer[MAX_PATH + 1]; // fyi 260 at time of writing. + // in/out parameter. Documentation say that size is a count of bytes not chars. + DWORD size = sizeof(value_buffer) - sizeof(value_buffer[0]); + DWORD tzi_type = REG_SZ; + // Testing shows Path key value is "C:\Program Files\7-Zip\" i.e. always with trailing \. + bool got_value = (RegQueryValueExA(hKey, "Path", nullptr, &tzi_type, + reinterpret_cast(value_buffer), &size) == ERROR_SUCCESS); + RegCloseKey(hKey); // Close now incase of throw later. + if (got_value) + { + // Function does not guarantee to null terminate. + value_buffer[size / sizeof(value_buffer[0])] = '\0'; + path = value_buffer; + if (!path.empty()) + { + path += "7z.exe"; + return path; + } + } + } + path += get_program_folder(); + path += folder_delimiter; + path += "7-Zip\\7z.exe"; + return path; +} + +#if !USE_SHELL_API +static +int +run_program(const std::string& command) +{ + STARTUPINFO si{}; + si.cb = sizeof(si); + PROCESS_INFORMATION pi{}; + + // Allegedly CreateProcess overwrites the command line. Ugh. + std::string mutable_command(command); + if (CreateProcess(nullptr, &mutable_command[0], + nullptr, nullptr, FALSE, CREATE_NO_WINDOW, nullptr, nullptr, &si, &pi)) + { + WaitForSingleObject(pi.hProcess, INFINITE); + DWORD exit_code; + bool got_exit_code = !!GetExitCodeProcess(pi.hProcess, &exit_code); + CloseHandle(pi.hProcess); + CloseHandle(pi.hThread); + // Not 100% sure about this still active thing is correct, + // but I'm going with it because I *think* WaitForSingleObject might + // return in some cases without INFINITE-ly waiting. + // But why/wouldn't GetExitCodeProcess return false in that case? + if (got_exit_code && exit_code != STILL_ACTIVE) + return static_cast(exit_code); + } + return EXIT_FAILURE; +} +#endif // !USE_SHELL_API + +static +std::string +get_download_tar_file(const std::string& version) +{ + auto file = install; + file += folder_delimiter; + file += "tzdata"; + file += version; + file += ".tar"; + return file; +} + +static +bool +extract_gz_file(const std::string& version, const std::string& gz_file, + const std::string& dest_folder) +{ + auto unzip_prog = get_unzip_program(); + bool unzip_result = false; + // Use the unzip program to extract the tar file from the archive. + + // Aim to create a string like: + // "C:\Program Files\7-Zip\7z.exe" x "C:\Users\SomeUser\Downloads\tzdata2016d.tar.gz" + // -o"C:\Users\SomeUser\Downloads\tzdata" + std::string cmd; + cmd = '\"'; + cmd += unzip_prog; + cmd += "\" x \""; + cmd += gz_file; + cmd += "\" -o\""; + cmd += dest_folder; + cmd += '\"'; + +#if USE_SHELL_API + // When using shelling out with std::system() extra quotes are required around the + // whole command. It's weird but neccessary it seems, see: + // http://stackoverflow.com/q/27975969/576911 + + cmd = "\"" + cmd + "\""; + if (std::system(cmd.c_str()) == EXIT_SUCCESS) + unzip_result = true; +#else // !USE_SHELL_API + if (run_program(cmd) == EXIT_SUCCESS) + unzip_result = true; +#endif // !USE_SHELL_API + if (unzip_result) + delete_file(gz_file); + + // Use the unzip program extract the data from the tar file that was + // just extracted from the archive. + auto tar_file = get_download_tar_file(version); + cmd = '\"'; + cmd += unzip_prog; + cmd += "\" x \""; + cmd += tar_file; + cmd += "\" -o\""; + cmd += install; + cmd += '\"'; +#if USE_SHELL_API + cmd = "\"" + cmd + "\""; + if (std::system(cmd.c_str()) == EXIT_SUCCESS) + unzip_result = true; +#else // !USE_SHELL_API + if (run_program(cmd) == EXIT_SUCCESS) + unzip_result = true; +#endif // !USE_SHELL_API + + if (unzip_result) + delete_file(tar_file); + + return unzip_result; +} + +#else // !_WIN32 + +#if !USE_SHELL_API +static +int +run_program(const char* prog, const char*const args[]) +{ + pid_t pid = fork(); + if (pid == -1) // Child failed to start. + return EXIT_FAILURE; + + if (pid != 0) + { + // We are in the parent. Child started. Wait for it. + pid_t ret; + int status; + while ((ret = waitpid(pid, &status, 0)) == -1) + { + if (errno != EINTR) + break; + } + if (ret != -1) + { + if (WIFEXITED(status)) + return WEXITSTATUS(status); + } + printf("Child issues!\n"); + + return EXIT_FAILURE; // Not sure what status of child is. + } + else // We are in the child process. Start the program the parent wants to run. + { + + if (execv(prog, const_cast(args)) == -1) // Does not return. + { + perror("unreachable 0\n"); + _Exit(127); + } + printf("unreachable 2\n"); + } + printf("unreachable 2\n"); + // Unreachable. + assert(false); + exit(EXIT_FAILURE); + return EXIT_FAILURE; +} +#endif // !USE_SHELL_API + +static +bool +extract_gz_file(const std::string&, const std::string& gz_file, const std::string&) +{ +#if USE_SHELL_API + bool unzipped = std::system(("tar -xzf " + gz_file + " -C " + install).c_str()) == EXIT_SUCCESS; +#else // !USE_SHELL_API + const char prog[] = {"/usr/bin/tar"}; + const char*const args[] = + { + prog, "-xzf", gz_file.c_str(), "-C", install.c_str(), nullptr + }; + bool unzipped = (run_program(prog, args) == EXIT_SUCCESS); +#endif // !USE_SHELL_API + if (unzipped) + { + delete_file(gz_file); + return true; + } + return false; +} + +#endif // !_WIN32 + +bool +remote_install(const std::string& version) +{ + auto success = false; + assert(!version.empty()); + + auto gz_file = get_download_gz_file(version); + if (file_exists(gz_file)) + { + if (file_exists(install)) + remove_folder_and_subfolders(install); + if (make_directory(install)) + { + if (extract_gz_file(version, gz_file, install)) + success = true; +#ifdef TIMEZONE_MAPPING + auto mapping_file_source = get_download_mapping_file(version); + auto mapping_file_dest = install; + mapping_file_dest += folder_delimiter; + mapping_file_dest += "windowsZones.xml"; + if (!move_file(mapping_file_source, mapping_file_dest)) + success = false; +#endif + } + } + return success; +} +#endif // HAS_REMOTE_API + +static +std::string +get_version(const std::string& path) +{ + std::ifstream infile(path + "NEWS"); + std::string version; + while (infile) + { + infile >> version; + if (version == "Release") + { + infile >> version; + return version; + } + } + throw std::runtime_error("Unable to get Timezone database version from " + path); +} + +static +TZ_DB +init_tzdb() +{ + using namespace date; + const std::string path = install + folder_delimiter; + std::string line; + bool continue_zone = false; + TZ_DB db; + +#if AUTO_DOWNLOAD + if (!file_exists(install)) + { + auto rv = remote_version(); + if (!rv.empty() && remote_download(rv)) + { + if (!remote_install(rv)) + { + std::string msg = "Timezone database version \""; + msg += rv; + msg += "\" did not install correctly to \""; + msg += install; + msg += "\""; + throw std::runtime_error(msg); + } + } + if (!file_exists(install)) + { + std::string msg = "Timezone database not found at \""; + msg += install; + msg += "\""; + throw std::runtime_error(msg); + } + db.version = get_version(path); + } + else + { + db.version = get_version(path); + auto rv = remote_version(); + if (!rv.empty() && db.version != rv) + { + if (remote_download(rv)) + { + remote_install(rv); + db.version = get_version(path); + } + } + } +#else // !AUTO_DOWNLOAD + if (!file_exists(install)) + { + std::string msg = "Timezone database not found at \""; + msg += install; + msg += "\""; + throw std::runtime_error(msg); + } + db.version = get_version(path); +#endif // !AUTO_DOWNLOAD + + 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, detail::undocumented{})); + continue_zone = false; + } + else if (word == "Zone") + { + db.zones.push_back(time_zone(line, detail::undocumented{})); + 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()); +#if !LAZY_INIT + for (auto& z : db.zones) + z.adjust_infos(db.rules); +#endif + 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(); + +#ifdef TIMEZONE_MAPPING + std::string mapping_file = path + "windowsZones.xml"; + db.mappings = load_timezone_mappings_from_xml_file(mapping_file); + sort_zone_mappings(db.mappings); + get_windows_timezone_info(db.native_zones); +#endif // TIMEZONE_MAPPING + + return db; +} + +static +TZ_DB& +access_tzdb() +{ + static TZ_DB tz_db; + return tz_db; +} + +const TZ_DB& +reload_tzdb() +{ +#if AUTO_DOWNLOAD + auto const& v = access_tzdb().version; + if (!v.empty() && v == remote_version()) + return access_tzdb(); +#endif + return access_tzdb() = init_tzdb(); +} + +const TZ_DB& +get_tzdb() +{ + static const TZ_DB& ref = access_tzdb() = init_tzdb(); + return ref; +} + +const time_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 time_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 time_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) +{ + os << "Version: " << db.version << '\n'; + 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; +} + +// ----------------------- + +#ifdef _WIN32 + +const time_zone* +current_zone() +{ +#ifdef TIMEZONE_MAPPING + TIME_ZONE_INFORMATION tzi{}; + DWORD tz_result = ::GetTimeZoneInformation(&tzi); + if (tz_result == TIME_ZONE_ID_INVALID) + { + auto error_code = ::GetLastError(); // Store this quick before it gets overwritten. + throw std::runtime_error("GetTimeZoneInformation failed: " + + get_win32_message(error_code)); + } + std::wstring_convert> converter; + std::string standard_name(converter.to_bytes(tzi.StandardName)); + auto tz = find_native_timezone_by_standard_name(standard_name); + if (!tz) + { + std::string msg; + msg = "current_zone() failed: "; + msg += standard_name; + msg += " was not found in the Windows Time Zone registry"; + throw std::runtime_error( msg ); + } + std::string standard_tzid; + if (!native_to_standard_timezone_name(tz->timezone_id, standard_tzid)) + { + std::string msg; + msg = "current_zone() failed: A mapping from the Windows Time Zone id \""; + msg += tz->timezone_id; + msg += "\" was not found in the time zone mapping database."; + throw std::runtime_error(msg); + } + return date::locate_zone(standard_tzid); +#else // !TIMEZONE_MAPPING + // Currently Win32 requires iana <--> windows tz name mappings + // for this function to work. + // TODO! we should really support TIMEZONE_MAPPINGS=0 on Windows, + // And in this mode we should read the current iana timezone from a file. + // This would allow the TZ library do be used by apps that don't care + // about Windows standard names just iana names. + // This would allow the xml dependency to be dropped and none of + // the name mapping functions would be needed. + throw std::runtime_error("current_zone not implemented."); +#endif // !TIMEZONE_MAPPING +} + +#else // !WIN32 + +const time_zone* +current_zone() +{ + // On some OS's a file called /etc/localtime may + // exist and it may be either a real file + // containing time zone details or a symlink to such a file. + // On MacOS and BSD Unix if this file is a symlink it + // might resolve to a path like this: + // "/usr/share/zoneinfo/America/Los_Angeles" + // If it does, we try to determine the current + // timezone from the remainder of the path by removing the prefix + // and hoping the rest resolves to valid timezone. + // It may not always work though. If it doesn't then an + // exception will be thrown by local_timezone. + // The path may also take a relative form: + // "../usr/share/zoneinfo/America/Los_Angeles". + struct stat sb; + CONSTDATA auto timezone = "/etc/localtime"; + if (lstat(timezone, &sb) == 0 && S_ISLNK(sb.st_mode) && sb.st_size > 0) + { + std::string result(sb.st_size, '\0'); + auto sz = readlink(timezone, &result.front(), result.size()); + if (sz == -1) + throw std::runtime_error("readlink failure"); + result.resize(sz); + const char zonepath[] = "/usr/share/zoneinfo/"; + const std::size_t zonepath_len = sizeof(zonepath)/sizeof(zonepath[0])-1; + const std::size_t pos = result.find(zonepath); + if (pos != result.npos) + result.erase(0, zonepath_len+pos); + return locate_zone(result); + } + { + // On some versions of some linux distro's (e.g. Ubuntu), + // the current timezone might be in the first line of + // the /etc/timezone file. + std::ifstream timezone_file("/etc/timezone"); + if (timezone_file.is_open()) + { + std::string result; + std::getline(timezone_file, result); + if (!result.empty()) + return locate_zone(result); + } + // Fall through to try other means. + } + { + // On some versions of some linux distro's (e.g. Red Hat), + // the current timezone might be in the first line of + // the /etc/sysconfig/clock file as: + // ZONE="US/Eastern" + std::ifstream timezone_file("/etc/sysconfig/clock"); + std::string result; + while (timezone_file) + { + std::getline(timezone_file, result); + auto p = result.find("ZONE=\""); + if (p != std::string::npos) + { + result.erase(p, p+6); + result.erase(result.rfind('"')); + return locate_zone(result); + } + } + // Fall through to try other means. + } + throw std::runtime_error("Could not get current timezone"); +} + +#endif // !WIN32 + +#if defined(TZ_TEST) && defined(TIMEZONE_MAPPING) + +const time_zone* +locate_native_zone(const std::string& native_tz_name) +{ + std::string standard_tz_name; + if (!native_to_standard_timezone_name(native_tz_name, standard_tz_name)) + { + std::string msg; + msg = "locate_native_zone() failed: A mapping from the native/Windows Time Zone id \""; + msg += native_tz_name; + msg += "\" was not found in the time zone mapping database."; + throw std::runtime_error(msg); + } + return locate_zone(standard_tz_name); +} + +#endif // TZ_TEST && TIMEZONE_MAPPING + +} // namespace date diff --git a/ext/date/tz.h b/ext/date/tz.h new file mode 100644 index 0000000..49cb805 --- /dev/null +++ b/ext/date/tz.h @@ -0,0 +1,1345 @@ +#ifndef TZ_H +#define TZ_H + +// The MIT License (MIT) +// +// Copyright (c) 2015, 2016 Howard Hinnant +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +// +// Our apologies. When the previous paragraph was written, lowercase had not yet +// been invented (that woud involve another several millennia of evolution). +// We did not mean to shout. + +// Get more recent database at http://www.iana.org/time-zones + +// The notion of "current timezone" is something the operating system is expected to "just +// know". How it knows this is system specific. It's often a value set by the user at OS +// intallation time and recorded by the OS somewhere. On Linux and Mac systems the current +// timezone name is obtained by looking at the name or contents of a particular file on +// disk. On Windows the current timzeone name comes from the registry. In either method, +// there is no guarantee that the "native" current timezone name obtained will match any +// of the "Standard" names in this library's "database". On Linux, the names usually do +// seem to match so mapping functions to map from native to "Standard" are typically not +// required. On Windows, the names are never "Standard" so mapping is always required. +// Technically any OS may use the mapping process but currently only Windows does use it. + +#ifdef _WIN32 +# ifndef TIMEZONE_MAPPING +# define TIMEZONE_MAPPING 1 +# endif +#else +# ifdef TIMEZONE_MAPPING +# error "Timezone mapping is not required or not implemented for this platform." +# endif +#endif + +#ifndef LAZY_INIT +# define LAZY_INIT 1 +#endif + +#ifndef HAS_REMOTE_API +# ifdef _WIN32 +# define HAS_REMOTE_API 0 +# else +# define HAS_REMOTE_API 1 +# endif +#endif + +#ifndef AUTO_DOWNLOAD +# define AUTO_DOWNLOAD HAS_REMOTE_API +#endif + +static_assert(HAS_REMOTE_API == 0 ? AUTO_DOWNLOAD == 0 : true, + "AUTO_DOWNLOAD can not be turned on without HAS_REMOTE_API"); + +#ifndef USE_SHELL_API +# define USE_SHELL_API 1 +#endif + +#include "date.h" + +#if defined(_MSC_VER) && (_MSC_VER < 1900) +#include "tz_private.h" +#endif + +#include +#include +#include +#include +#include +#if LAZY_INIT +# include +# include +#endif +#include +#include +#include +#include +#include +#include +#include + +namespace date +{ + +enum class choose {earliest, latest}; + +namespace detail +{ + struct undocumented; +} + +class nonexistent_local_time + : public std::runtime_error +{ +public: + template + nonexistent_local_time(local_time tp, local_seconds first, + const std::string& first_abbrev, local_seconds last, + const std::string& last_abbrev, sys_seconds time_sys); + +private: + template + static + std::string + make_msg(local_time tp, + local_seconds first, const std::string& first_abbrev, + local_seconds last, const std::string& last_abbrev, + sys_seconds time_sys); +}; + +template +inline +nonexistent_local_time::nonexistent_local_time(local_time tp, + local_seconds first, + const std::string& first_abbrev, + local_seconds last, + const std::string& last_abbrev, + sys_seconds time_sys) + : std::runtime_error(make_msg(tp, first, first_abbrev, last, last_abbrev, time_sys)) + {} + +template +std::string +nonexistent_local_time::make_msg(local_time tp, local_seconds first, + const std::string& first_abbrev, local_seconds last, + const std::string& last_abbrev, sys_seconds time_sys) +{ + using namespace date; + std::ostringstream os; + os << tp << " is in a gap between\n" + << first << ' ' << first_abbrev << " and\n" + << last << ' ' << last_abbrev + << " 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(local_time tp, std::chrono::seconds first_offset, + const std::string& first_abbrev, + std::chrono::seconds second_offset, + const std::string& second_abbrev); + +private: + template + static + std::string + make_msg(local_time tp, + std::chrono::seconds first_offset, const std::string& first_abbrev, + std::chrono::seconds second_offset, const std::string& second_abbrev); +}; + +template +inline +ambiguous_local_time::ambiguous_local_time( + local_time tp, + std::chrono::seconds first_offset, + const std::string& first_abbrev, + std::chrono::seconds second_offset, + const std::string& second_abbrev) + : std::runtime_error(make_msg(tp, first_offset, first_abbrev, second_offset, + second_abbrev)) + {} + +template +std::string +ambiguous_local_time::make_msg(local_time tp, + std::chrono::seconds first_offset, + const std::string& first_abbrev, + std::chrono::seconds second_offset, + const std::string& second_abbrev) +{ + using namespace date; + std::ostringstream os; + os << tp << " is ambiguous. It could be\n" + << tp << ' ' << first_abbrev << " == " + << tp - first_offset << " UTC or\n" + << tp << ' ' << second_abbrev << " == " + << tp - second_offset << " UTC"; + return os.str(); +} + +namespace detail { class Rule; } + +struct sys_info +{ + sys_seconds begin; + sys_seconds end; + std::chrono::seconds offset; + std::chrono::minutes save; + std::string abbrev; +}; + +template +std::basic_ostream& +operator<<(std::basic_ostream& os, const sys_info& r) +{ + 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; +} + +struct local_info +{ + enum {unique, nonexistent, ambiguous} result; + sys_info first; + sys_info second; +}; + +template +std::basic_ostream& +operator<<(std::basic_ostream& os, const local_info& r) +{ + if (r.result == local_info::nonexistent) + os << "nonexistent between\n"; + else if (r.result == local_info::ambiguous) + os << "ambiguous between\n"; + os << r.first; + if (r.result != local_info::unique) + { + os << "and\n"; + os << r.second; + } + return os; +} + +class time_zone; + +template +class zoned_time +{ + const time_zone* zone_; + sys_time tp_; + +public: + zoned_time(const sys_time& st); + explicit zoned_time(const time_zone* z); + explicit zoned_time(const std::string& name); + + template , + sys_time>::value + >::type> + zoned_time(const zoned_time& zt) NOEXCEPT; + + zoned_time(const time_zone* z, const local_time& tp); + zoned_time(const std::string& name, const local_time& tp); + zoned_time(const time_zone* z, const local_time& tp, choose c); + zoned_time(const std::string& name, const local_time& tp, choose c); + + zoned_time(const time_zone* z, const zoned_time& zt); + zoned_time(const std::string& name, const zoned_time& zt); + zoned_time(const time_zone* z, const zoned_time& zt, choose); + zoned_time(const std::string& name, const zoned_time& zt, choose); + + zoned_time(const time_zone* z, const sys_time& st); + zoned_time(const std::string& name, const sys_time& st); + + zoned_time& operator=(const sys_time& st); + zoned_time& operator=(const local_time& ut); + + operator sys_time() const; + explicit operator local_time() const; + + const time_zone* get_time_zone() const; + local_time get_local_time() const; + sys_time get_sys_time() const; + sys_info get_info() const; + + template + friend + bool + operator==(const zoned_time& x, const zoned_time& y); + + template + friend + std::basic_ostream& + operator<<(std::basic_ostream& os, const zoned_time& t); + +private: + template friend class zoned_time; + + static_assert(std::is_convertible::value, + "zoned_time must have a precision of seconds or finer"); +}; + +using zoned_seconds = zoned_time; + +template +inline +bool +operator==(const zoned_time& x, const zoned_time& y) +{ + return x.zone_ == y.zone_ && x.tp_ == y.tp_; +} + +template +inline +bool +operator!=(const zoned_time& x, const zoned_time& y) +{ + return !(x == y); +} + +#if !defined(_MSC_VER) || (_MSC_VER >= 1900) +namespace detail { struct zonelet; } +#endif + +class time_zone +{ +private: + + std::string name_; + std::vector zonelets_; +#if LAZY_INIT + std::unique_ptr adjusted_; +#endif + +public: +#if !defined(_MSC_VER) || (_MSC_VER >= 1900) + time_zone(time_zone&&) = default; + time_zone& operator=(time_zone&&) = default; +#else // defined(_MSC_VER) && (_MSC_VER < 1900) + time_zone(time_zone&& src); + time_zone& operator=(time_zone&& src); +#endif // defined(_MSC_VER) && (_MSC_VER < 1900) + + explicit time_zone(const std::string& s, detail::undocumented); + + const std::string& name() const NOEXCEPT; + + template sys_info get_info(sys_time st) const; + template local_info get_info(local_time tp) const; + + template + sys_time::type> + to_sys(local_time tp) const; + + template + sys_time::type> + to_sys(local_time tp, choose z) const; + + template + local_time::type> + to_local(sys_time tp) const; + + friend bool operator==(const time_zone& x, const time_zone& y) NOEXCEPT; + friend bool operator< (const time_zone& x, const time_zone& y) NOEXCEPT; + friend std::ostream& operator<<(std::ostream& os, const time_zone& z); + + void add(const std::string& s); + void adjust_infos(const std::vector& rules); + +private: + sys_info get_info_impl(sys_seconds tp) const; + local_info get_info_impl(local_seconds tp) const; + sys_info get_info_impl(sys_seconds tp, int timezone) const; + + void parse_info(std::istream& in); + + template + sys_time::type> + to_sys_impl(local_time tp, choose z, std::false_type) const; + template + sys_time::type> + to_sys_impl(local_time tp, choose, std::true_type) const; +}; + +#if defined(_MSC_VER) && (_MSC_VER < 1900) + +inline +time_zone::time_zone(time_zone&& src) + : name_(std::move(src.name_)) + , zonelets_(std::move(src.zonelets_)) +#if LAZY_INIT + , adjusted_(std::move(src.adjusted_)) +#endif + {} + +inline +time_zone& +time_zone::operator=(time_zone&& src) +{ + name_ = std::move(src.name_); + zonelets_ = std::move(src.zonelets_); +#if LAZY_INIT + adjusted_ = std::move(src.adjusted_); +#endif + return *this; +} + +#endif // defined(_MSC_VER) && (_MSC_VER < 1900) + +inline +const std::string& +time_zone::name() const NOEXCEPT +{ + return name_; +} + +template +inline +sys_info +time_zone::get_info(sys_time st) const +{ + using namespace std::chrono; + return get_info_impl(floor(st)); +} + +template +inline +local_info +time_zone::get_info(local_time tp) const +{ + using namespace std::chrono; + return get_info_impl(floor(tp)); +} + +template +inline +sys_time::type> +time_zone::to_sys(local_time tp) const +{ + return to_sys_impl(tp, choose{}, std::true_type{}); +} + +template +inline +sys_time::type> +time_zone::to_sys(local_time tp, choose z) const +{ + return to_sys_impl(tp, z, std::false_type{}); +} + +template +inline +local_time::type> +time_zone::to_local(sys_time tp) const +{ + using LT = local_time::type>; + auto i = get_info(tp); + return LT{(tp + i.offset).time_since_epoch()}; +} + +inline bool operator==(const time_zone& x, const time_zone& y) NOEXCEPT {return x.name_ == y.name_;} +inline bool operator< (const time_zone& x, const time_zone& y) NOEXCEPT {return x.name_ < y.name_;} + +inline bool operator!=(const time_zone& x, const time_zone& y) NOEXCEPT {return !(x == y);} +inline bool operator> (const time_zone& x, const time_zone& y) NOEXCEPT {return y < x;} +inline bool operator<=(const time_zone& x, const time_zone& y) NOEXCEPT {return !(y < x);} +inline bool operator>=(const time_zone& x, const time_zone& y) NOEXCEPT {return !(x < y);} + +template +sys_time::type> +time_zone::to_sys_impl(local_time tp, choose z, std::false_type) const +{ + using namespace date; + using namespace std::chrono; + auto i = get_info(tp); + if (i.result == local_info::nonexistent) + { + return i.first.end; + } + else if (i.result == local_info::ambiguous) + { + if (z == choose::latest) + return sys_time{tp.time_since_epoch()} - i.second.offset; + } + return sys_time{tp.time_since_epoch()} - i.first.offset; +} + +template +sys_time::type> +time_zone::to_sys_impl(local_time tp, choose, std::true_type) const +{ + using namespace date; + using namespace std::chrono; + auto i = get_info(tp); + if (i.result == local_info::nonexistent) + { + auto prev_end = local_seconds{i.first.end.time_since_epoch()} + + i.first.offset; + auto next_begin = local_seconds{i.second.begin.time_since_epoch()} + + i.second.offset; + throw nonexistent_local_time(tp, prev_end, i.first.abbrev, + next_begin, i.second.abbrev, i.first.end); + } + else if (i.result == local_info::ambiguous) + { + throw ambiguous_local_time(tp, i.first.offset, i.first.abbrev, + i.second.offset, i.second.abbrev); + } + return sys_time{tp.time_since_epoch()} - i.first.offset; +} + +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: + sys_seconds date_; + +public: + explicit leap(const std::string& s, detail::undocumented); + + sys_seconds 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 sys_time& y) + { + return x.date_ == y; + } + + template + friend + bool + operator< (const leap& x, const sys_time& y) + { + return x.date_ < y; + } + + template + friend + bool + operator< (const sys_time& 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 sys_time& x, const leap& y) +{ + return y == x; +} + +template +inline +bool +operator!=(const leap& x, const sys_time& y) +{ + return !(x == y); +} + +template +inline +bool +operator!=(const sys_time& x, const leap& y) +{ + return !(x == y); +} + +template +inline +bool +operator> (const leap& x, const sys_time& y) +{ + return y < x; +} + +template +inline +bool +operator> (const sys_time& x, const leap& y) +{ + return y < x; +} + +template +inline +bool +operator<=(const leap& x, const sys_time& y) +{ + return !(y < x); +} + +template +inline +bool +operator<=(const sys_time& x, const leap& y) +{ + return !(y < x); +} + +template +inline +bool +operator>=(const leap& x, const sys_time& y) +{ + return !(x < y); +} + +template +inline +bool +operator>=(const sys_time& x, const leap& y) +{ + return !(x < y); +} + +#ifdef TIMEZONE_MAPPING + +namespace detail +{ + +// The time zone mapping is modelled after this data file: +// http://unicode.org/repos/cldr/trunk/common/supplemental/windowsZones.xml +// and the field names match the element names from the mapZone element +// of windowsZones.xml. +// The website displays this file here: +// http://www.unicode.org/cldr/charts/latest/supplemental/zone_tzid.html +// The html view is sorted before being displayed but is otherwise the same +// There is a mapping between the os centric view (in this case windows) +// the html displays uses and the generic view the xml file. +// That mapping is this: +// display column "windows" -> xml field "other". +// display column "region" -> xml field "territory". +// display column "tzid" -> xml field "type". +// This structure uses the generic terminology because it could be +// used to to support other os/native name conversions, not just windows, +// and using the same generic names helps retain the connection to the +// origin of the data that we are using. +struct timezone_mapping +{ + timezone_mapping(const char* other, const char* territory, const char* type) + : other(other), territory(territory), type(type) + { + } + timezone_mapping() = default; + std::string other; + std::string territory; + std::string type; +}; + +struct timezone_info +{ + timezone_info() = default; + std::string timezone_id; + std::string standard_name; +}; + +} // detail + +#endif // TIMEZONE_MAPPING + +struct TZ_DB +{ + std::string version; + std::vector zones; + std::vector links; + std::vector leaps; + std::vector rules; +#ifdef TIMEZONE_MAPPING + // TODO! These need some protection. + std::vector mappings; + std::vector native_zones; +#endif + + TZ_DB() = default; +#if !defined(_MSC_VER) || (_MSC_VER >= 1900) + TZ_DB(TZ_DB&&) = default; + TZ_DB& operator=(TZ_DB&&) = default; +#else // defined(_MSC_VER) || (_MSC_VER >= 1900) + TZ_DB(TZ_DB&& src) + : + version(std::move(src.version)), + zones(std::move(src.zones)), + links(std::move(src.links)), + leaps(std::move(src.leaps)), + rules(std::move(src.rules)) +#ifdef TIMEZONE_MAPPING + , + mappings(std::move(src.mappings)), + native_zones(std::move(src.native_zones)) +#endif + {} + + TZ_DB& operator=(TZ_DB&& src) + { + version = std::move(src.version); + zones = std::move(src.zones); + links = std::move(src.links); + leaps = std::move(src.leaps); + rules = std::move(src.rules); +#ifdef TIMEZONE_MAPPING + mappings = std::move(src.mappings); + native_zones = std::move(src.native_zones); +#endif + return *this; + } +#endif // !defined(_MSC_VER) || (_MSC_VER >= 1900) +}; + +std::ostream& +operator<<(std::ostream& os, const TZ_DB& db); + +const TZ_DB& get_tzdb(); +const TZ_DB& reload_tzdb(); + +#if HAS_REMOTE_API +std::string remote_version(); +bool remote_download(const std::string& version); +bool remote_install(const std::string& version); +#endif + +const time_zone* locate_zone(const std::string& tz_name); +#ifdef TZ_TEST +# if _WIN32 +const time_zone* locate_native_zone(const std::string& native_tz_name); +# endif // _WIN32 +#endif // TZ_TEST +const time_zone* current_zone(); + +// zoned_time + +template +inline +zoned_time::zoned_time(const sys_time& st) + : zone_(locate_zone("UTC")) + , tp_(st) + {} + +template +inline +zoned_time::zoned_time(const time_zone* z) + : zone_(z) + {assert(zone_ != nullptr);} + +template +inline +zoned_time::zoned_time(const std::string& name) + : zoned_time(locate_zone(name)) + {} + +template +inline +zoned_time::zoned_time(const time_zone* z, const local_time& t) + : zone_(z) + , tp_(z->to_sys(t)) + {} + +template +inline +zoned_time::zoned_time(const std::string& name, const local_time& t) + : zoned_time(locate_zone(name), t) + {} + +template +inline +zoned_time::zoned_time(const time_zone* z, const local_time& t, + choose c) + : zone_(z) + , tp_(z->to_sys(t, c)) + {} + +template +inline +zoned_time::zoned_time(const std::string& name, const local_time& t, + choose c) + : zoned_time(locate_zone(name), t, c) + {} + +template +template +inline +zoned_time::zoned_time(const zoned_time& zt) NOEXCEPT + : zone_(zt.zone_) + , tp_(zt.tp_) + {} + +template +inline +zoned_time::zoned_time(const time_zone* z, const zoned_time& zt) + : zone_(z) + , tp_(zt.tp_) + {} + +template +inline +zoned_time::zoned_time(const std::string& name, const zoned_time& zt) + : zoned_time(locate_zone(name), zt) + {} + +template +inline +zoned_time::zoned_time(const time_zone* z, const zoned_time& zt, choose) + : zoned_time(z, zt) + {} + +template +inline +zoned_time::zoned_time(const std::string& name, + const zoned_time& zt, choose c) + : zoned_time(locate_zone(name), zt, c) + {} + +template +inline +zoned_time::zoned_time(const time_zone* z, const sys_time& st) + : zone_(z) + , tp_(st) + {} + +template +inline +zoned_time::zoned_time(const std::string& name, const sys_time& st) + : zoned_time(locate_zone(name), st) + {} + + +template +inline +zoned_time& +zoned_time::operator=(const sys_time& st) +{ + tp_ = st; + return *this; +} + +template +inline +zoned_time& +zoned_time::operator=(const local_time& ut) +{ + tp_ = zone_->to_sys(ut); + return *this; +} + +template +inline +zoned_time::operator local_time() const +{ + return get_local_time(); +} + +template +inline +zoned_time::operator sys_time() const +{ + return get_sys_time(); +} + +template +inline +const time_zone* +zoned_time::get_time_zone() const +{ + return zone_; +} + +template +inline +local_time +zoned_time::get_local_time() const +{ + return zone_->to_local(tp_); +} + +template +inline +sys_time +zoned_time::get_sys_time() const +{ + return tp_; +} + +template +inline +sys_info +zoned_time::get_info() const +{ + return zone_->get_info(tp_); +} + +// make_zoned_time + +template +inline +zoned_time::type> +make_zoned(const sys_time& tp) +{ + return {tp}; +} + +template +inline +zoned_time::type> +make_zoned(const time_zone* zone, const local_time& tp) +{ + return {zone, tp}; +} + +template +inline +zoned_time::type> +make_zoned(const std::string& name, const local_time& tp) +{ + return {name, tp}; +} + +template +inline +zoned_time::type> +make_zoned(const time_zone* zone, const local_time& tp, choose c) +{ + return {zone, tp, c}; +} + +template +inline +zoned_time::type> +make_zoned(const std::string& name, const local_time& tp, choose c) +{ + return {name, tp, c}; +} + +template +inline +zoned_time::type> +make_zoned(const time_zone* zone, const zoned_time& zt) +{ + return {zone, zt}; +} + +template +inline +zoned_time::type> +make_zoned(const std::string& name, const zoned_time& zt) +{ + return {name, zt}; +} + +template +inline +zoned_time::type> +make_zoned(const time_zone* zone, const zoned_time& zt, choose c) +{ + return {zone, zt, c}; +} + +template +inline +zoned_time::type> +make_zoned(const std::string& name, const zoned_time& zt, choose c) +{ + return {name, zt, c}; +} + +template +inline +zoned_time::type> +make_zoned(const time_zone* zone, const sys_time& st) +{ + return {zone, st}; +} + +template +inline +zoned_time::type> +make_zoned(const std::string& name, const sys_time& st) +{ + return {name, st}; +} + +template +inline +std::basic_ostream& +operator<<(std::basic_ostream& os, const zoned_time& t) +{ + auto i = t.zone_->get_info(t.tp_); + auto lt = t.tp_ + i.offset; + return os << lt << ' ' << i.abbrev; +} + +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 = false; + + static time_point now(); +}; + +template + using utc_time = std::chrono::time_point; + +using utc_seconds = utc_time; + +template +inline +utc_time::type> +to_utc_time(const sys_time& st) +{ + using namespace std::chrono; + using duration = typename std::common_type::type; + auto const& leaps = get_tzdb().leaps; + auto const lt = std::upper_bound(leaps.begin(), leaps.end(), st); + return utc_time{st.time_since_epoch() + seconds{lt-leaps.begin()}}; +} + +template +inline +sys_time::type> +to_sys_time(const utc_time& ut) +{ + using namespace std::chrono; + using duration = typename std::common_type::type; + auto const& leaps = get_tzdb().leaps; + auto tp = sys_time{ut.time_since_epoch()}; + if (tp >= leaps.front()) + { + auto const lt = std::upper_bound(leaps.begin(), leaps.end(), tp); + tp -= seconds{lt-leaps.begin()}; + if (tp < lt[-1]) + { + if (tp >= lt[-1].date() - seconds{1}) + tp = lt[-1].date() - duration{1}; + else + tp += seconds{1}; + } + } + return tp; +} + +inline +utc_clock::time_point +utc_clock::now() +{ + using namespace std::chrono; + return to_utc_time(system_clock::now()); +} + +template +std::basic_ostream& +operator<<(std::basic_ostream& os, const utc_time& t) +{ + using namespace std::chrono; + using duration = typename std::common_type::type; + auto const& leaps = get_tzdb().leaps; + auto tp = sys_time{t.time_since_epoch()}; + if (tp >= leaps.front()) + { + auto const lt = std::upper_bound(leaps.begin(), leaps.end(), tp); + tp -= seconds{lt-leaps.begin()}; + if (tp < lt[-1]) + { + if (tp >= lt[-1].date() - seconds{1}) + { + auto const dp = floor(tp); + auto time = make_time(tp-dp); + time.seconds() += seconds{1}; + return os << year_month_day(dp) << ' ' << time; + } + else + tp += seconds{1}; + } + } + return os << tp; +} + +// tai_clock + +class tai_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 const bool is_steady = false; + + static time_point now() NOEXCEPT; +}; + +template + using tai_time = std::chrono::time_point; + +using tai_seconds = tai_time; + +template +inline +utc_time::type> +to_utc_time(const tai_time& t) NOEXCEPT +{ + using namespace std::chrono; + using duration = typename std::common_type::type; + return utc_time{t.time_since_epoch()} - + (sys_days{year{1970}/jan/1} - sys_days{year{1958}/jan/1} + seconds{10}); +} + +template +inline +tai_time::type> +to_tai_time(const utc_time& t) NOEXCEPT +{ + using namespace std::chrono; + using duration = typename std::common_type::type; + return tai_time{t.time_since_epoch()} + + (sys_days{year{1970}/jan/1} - sys_days{year{1958}/jan/1} + seconds{10}); +} + +template +inline +tai_time::type> +to_tai_time(const sys_time& t) +{ + return to_tai_time(to_utc_time(t)); +} + +inline +tai_clock::time_point +tai_clock::now() NOEXCEPT +{ + using namespace std::chrono; + return to_tai_time(system_clock::now()); +} + +template +std::basic_ostream& +operator<<(std::basic_ostream& os, const tai_time& t) +{ + using namespace std::chrono; + using duration = typename std::common_type::type; + auto tp = sys_time{t.time_since_epoch()} - + (sys_days{year{1970}/jan/1} - sys_days{year{1958}/jan/1}); + return os << tp; +} + +// gps_clock + +class gps_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 const bool is_steady = false; + + static time_point now() NOEXCEPT; +}; + +template + using gps_time = std::chrono::time_point; + +using gps_seconds = gps_time; + +template +inline +utc_time::type> +to_utc_time(const gps_time& t) NOEXCEPT +{ + using namespace std::chrono; + using duration = typename std::common_type::type; + return utc_time{t.time_since_epoch()} + + (sys_days{year{1980}/jan/sun[1]} - sys_days{year{1970}/jan/1} + seconds{9}); +} + +template +inline +gps_time::type> +to_gps_time(const utc_time& t) +{ + using namespace std::chrono; + using duration = typename std::common_type::type; + return gps_time{t.time_since_epoch()} - + (sys_days{year{1980}/jan/sun[1]} - sys_days{year{1970}/jan/1} + seconds{9}); +} + +template +inline +gps_time::type> +to_gps_time(const sys_time& t) +{ + return to_gps_time(to_utc_time(t)); +} + +inline +gps_clock::time_point +gps_clock::now() NOEXCEPT +{ + using namespace std::chrono; + return to_gps_time(system_clock::now()); +} + +template +std::basic_ostream& +operator<<(std::basic_ostream& os, const gps_time& t) +{ + using namespace std::chrono; + using duration = typename std::common_type::type; + auto tp = sys_time{t.time_since_epoch()} + + (sys_days{year{1980}/jan/sun[1]} - sys_days{year{1970}/jan/1}); + return os << tp; +} + +template +inline +sys_time::type> +to_sys_time(const tai_time& t) +{ + return to_sys_time(to_utc_time(t)); +} + +template +inline +sys_time::type> +to_sys_time(const gps_time& t) +{ + return to_sys_time(to_utc_time(t)); +} + +template +inline +tai_time::type> +to_tai_time(const gps_time& t) NOEXCEPT +{ + using namespace std::chrono; + using duration = typename std::common_type::type; + return tai_time{t.time_since_epoch()} + + (sys_days{year{1980}/jan/sun[1]} - sys_days{year{1958}/jan/1} + seconds{19}); +} + +template +inline +gps_time::type> +to_gps_time(const tai_time& t) NOEXCEPT +{ + using namespace std::chrono; + using duration = typename std::common_type::type; + return gps_time{t.time_since_epoch()} - + (sys_days{year{1980}/jan/sun[1]} - sys_days{year{1958}/jan/1} + seconds{19}); +} + +// format + +template +inline +std::basic_string +format(const std::locale& loc, std::basic_string fmt, + const zoned_time& tp) +{ + auto const info = tp.get_info(); + return detail::format(loc, std::move(fmt), tp.get_local_time(), + &info.abbrev, &info.offset); +} + +template +inline +std::basic_string +format(std::basic_string fmt, const zoned_time& tp) +{ + auto const info = tp.get_info(); + return detail::format(std::locale{}, std::move(fmt), tp.get_local_time(), + &info.abbrev, &info.offset); +} + +// const CharT* formats + +template +inline +std::basic_string +format(const std::locale& loc, const CharT* fmt, const zoned_time& tp) +{ + auto const info = tp.get_info(); + return detail::format(loc, std::basic_string(fmt), tp.get_local_time(), + &info.abbrev, &info.offset); +} + +template +inline +std::basic_string +format(const CharT* fmt, const zoned_time& tp) +{ + auto const info = tp.get_info(); + return detail::format(std::locale{}, std::basic_string(fmt), + tp.get_local_time(), &info.abbrev, &info.offset); +} + +} // namespace date + +#endif // TZ_H diff --git a/ext/date/tz_private.h b/ext/date/tz_private.h new file mode 100644 index 0000000..e796e9c --- /dev/null +++ b/ext/date/tz_private.h @@ -0,0 +1,265 @@ +#ifndef TZ_PRIVATE_H +#define TZ_PRIVATE_H + +// The MIT License (MIT) +// +// Copyright (c) 2015, 2016 Howard Hinnant +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +// +// Our apologies. When the previous paragraph was written, lowercase had not yet +// been invented (that woud involve another several millennia of evolution). +// We did not mean to shout. + +#if !defined(_MSC_VER) || (_MSC_VER >= 1900) +#include "tz.h" +#else +#include "date.h" +#include +#endif + +namespace date +{ + +namespace detail +{ + +enum class tz {utc, local, standard}; + +//forward declare to avoid warnings in gcc 6.2 +class MonthDayTime; +std::istream& operator>>(std::istream& is, MonthDayTime& x); +std::ostream& operator<<(std::ostream& os, const MonthDayTime& x); + + +class MonthDayTime +{ +private: + struct pair + { +#if defined(_MSC_VER) && (_MSC_VER < 1900) + pair() : month_day_(date::jan / 1), weekday_(0U) {} + + pair(const date::month_day& month_day, const date::weekday& weekday) + : month_day_(month_day), weekday_(weekday) {} +#endif + + date::month_day month_day_; + date::weekday weekday_; + }; + + enum Type {month_day, month_last_dow, lteq, gteq}; + + Type type_{month_day}; + +#if !defined(_MSC_VER) || (_MSC_VER >= 1900) + union U +#else + struct U +#endif + { + date::month_day month_day_; + date::month_weekday_last month_weekday_last_; + pair month_day_weekday_; + +#if !defined(_MSC_VER) || (_MSC_VER >= 1900) + U() : month_day_{date::jan/1} {} +#else + U() : + month_day_(date::jan/1), + month_weekday_last_(date::month(0U), date::weekday_last(date::weekday(0U))) + {} + +#endif // !defined(_MSC_VER) || (_MSC_VER >= 1900) + + 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(local_seconds 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); + + sys_seconds + to_sys(date::year y, std::chrono::seconds offset, std::chrono::seconds save) const; + sys_days to_sys_days(date::year y) const; + + sys_seconds 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. + +//forward declare to avoid warnings in gcc 6.2 +class Rule; +bool operator==(const Rule& x, const Rule& y); +bool operator<(const Rule& x, const Rule& y); +bool operator==(const Rule& x, const date::year& y); +bool operator<(const Rule& x, const date::year& y); +bool operator==(const date::year& x, const Rule& y); +bool operator<(const date::year& x, const Rule& y); +bool operator==(const Rule& x, const std::string& y); +bool operator<(const Rule& x, const std::string& y); +bool operator==(const std::string& x, const Rule& y); +bool operator<(const std::string& x, const Rule& y); +std::ostream& operator<<(std::ostream& os, const Rule& r); + +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 zonelet +{ + enum tag {has_rule, has_save, is_empty}; + + std::chrono::seconds gmtoff_; + tag tag_ = has_rule; + +#if !defined(_MSC_VER) || (_MSC_VER >= 1900) + union U +#else + struct U +#endif + { + 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_; + sys_seconds until_utc_; + local_seconds until_std_; + local_seconds until_loc_; + std::chrono::minutes initial_save_{}; + std::string initial_abbrev_; + 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 detail + +} // namespace date + +#if defined(_MSC_VER) && (_MSC_VER < 1900) +#include "tz.h" +#endif + +#endif // TZ_PRIVATE_H diff --git a/ext/dateparser.cpp b/ext/dateparser.cpp deleted file mode 100644 index 0fd83fa..0000000 --- a/ext/dateparser.cpp +++ /dev/null @@ -1,35 +0,0 @@ -// -// Created by marcin on 22/11/15. -// - -#include "dateparser.h" - - -dateparser::dateparser(std::string fmt) -{ - // set format - using namespace boost::local_time; - local_time_input_facet* input_facet = new local_time_input_facet(); - input_facet->format(fmt.c_str()); - ss.imbue(std::locale(ss.getloc(), input_facet)); -} - - -bool -dateparser::operator()(std::string const& text) -{ - ss.clear(); - ss.str(text); - - bool ok = bool(ss >> pt); - - if (ok) - { - auto tm = to_tm(pt); - year = tm.tm_year; - month = tm.tm_mon + 1; // for 1-based (1:jan, .. 12:dec) - day = tm.tm_mday; - } - - return ok; -} \ No newline at end of file diff --git a/ext/dateparser.h b/ext/dateparser.h deleted file mode 100644 index 4216a48..0000000 --- a/ext/dateparser.h +++ /dev/null @@ -1,27 +0,0 @@ -// -// Created by marcin on 22/11/15. -// - -#ifndef XMR2CSV_DATEPARSER_H -#define XMR2CSV_DATEPARSER_H - -#include -#include - - -// taken from: http://stackoverflow.com/a/19482908/248823 -struct dateparser -{ - boost::posix_time::ptime pt; - unsigned year, month, day; - - dateparser(std::string fmt); - - bool - operator()(std::string const& text); - -private: - std::stringstream ss; -}; - -#endif //XMR2CSV_DATEPARSER_H diff --git a/main.cpp b/main.cpp index 5b9638f..f448062 100644 --- a/main.cpp +++ b/main.cpp @@ -223,12 +223,12 @@ int main(int ac, const char* av[]) { return xmrblocks.search(string(req.url_params.get("value"))); }); -// CROW_ROUTE(app, "/robots.txt") -// ([&]() { -// string text = "User-agent: *\n" -// "Disallow: "; -// return text; -// }); + CROW_ROUTE(app, "/robots.txt") + ([&]() { + string text = "User-agent: *\n" + "Disallow: "; + return text; + }); CROW_ROUTE(app, "/autorefresh") ([&]() { diff --git a/src/mylmdb.h b/src/mylmdb.h index 12720db..346e2c8 100644 --- a/src/mylmdb.h +++ b/src/mylmdb.h @@ -451,7 +451,7 @@ namespace xmreg // set cursor the the first item - if (cr.get(key_to_find, info_val, MDB_SET)) + if (cr.get(key_to_find, info_val, MDB_SET_RANGE)) { out_infos.push_back(*(info_val.data())); diff --git a/src/page.h b/src/page.h index 3e2339a..98583eb 100644 --- a/src/page.h +++ b/src/page.h @@ -48,1384 +48,1435 @@ namespace xmreg { - using namespace cryptonote; - using namespace crypto; - using namespace std; +using namespace cryptonote; +using namespace crypto; +using namespace std; + +// define a checker to test if a structure has "tx_blob" +// member variable. I use modified daemon with few extra +// bits and pieces here and there. One of them is +// tx_blob in cryptonote::tx_info structure +// thus I check if I run my version, or just +// generic one +DEFINE_MEMBER_CHECKER(tx_blob) + +// define getter to get tx_blob, i.e., get_tx_blob function +// as string if exists. the getter return empty string if +// tx_blob does not exist +DEFINE_MEMBER_GETTER(tx_blob, string) + + + +/** + * Check if a given header filed contains value string + * + * @param req + * @param field + * @param value + * @return string + */ +string +does_header_has(const crow::request& req, + const string& field = "Accept", + const string& value = "q=.2, */*; q=.2") +{ + string accept = req.get_header_value(field); + + if (!accept.empty()) + { + if (accept.find(value) != std::string::npos) + { + return accept; + } + } - // define a checker to test if a structure has "tx_blob" - // member variable. I use modified daemon with few extra - // bits and pieces here and there. One of them is - // tx_blob in cryptonote::tx_info structure - // thus I check if I run my version, or just - // generic one - DEFINE_MEMBER_CHECKER(tx_blob) + return string {}; +} - // define getter to get tx_blob, i.e., get_tx_blob function - // as string if exists. the getter return empty string if - // tx_blob does not exist - DEFINE_MEMBER_GETTER(tx_blob, string) +/** + * @brief The tx_details struct + * + * Basic information about tx + * + */ +struct tx_details +{ + crypto::hash hash; + crypto::public_key pk; + uint64_t xmr_inputs; + uint64_t xmr_outputs; + uint64_t fee; + uint64_t mixin_no; + uint64_t size; + uint64_t blk_height; + size_t version; + uint64_t unlock_time; + uint64_t no_confirmations; + vector extra; - /** - * Check if a given header filed contains value string - * - * @param req - * @param field - * @param value - * @return string - */ - string - does_header_has(const crow::request& req, - const string& field = "Accept", - const string& value = "q=.2, */*; q=.2") - { - string accept = req.get_header_value(field); + crypto::hash payment_id = null_hash; // normal + crypto::hash8 payment_id8 = null_hash8; // encrypted - if (!accept.empty()) - { - if (accept.find(value) != std::string::npos) - { - return accept; - } - } + string json_representation; - return string {}; - } + std::vector > signatures; + // key images of inputs + vector input_key_imgs; + // public keys and xmr amount of outputs + vector> output_pub_keys; - /** - * @brief The tx_details struct - * - * Basic information about tx - * - */ - struct tx_details + mstch::map + get_mstch_map() { - crypto::hash hash; - crypto::public_key pk; - uint64_t xmr_inputs; - uint64_t xmr_outputs; - uint64_t fee; - uint64_t mixin_no; - uint64_t size; - uint64_t blk_height; - size_t version; - uint64_t unlock_time; - uint64_t no_confirmations; - vector extra; + // remove "<" and ">" from the hash string + string tx_hash_str = REMOVE_HASH_BRAKETS(fmt::format("{:s}", hash)); - crypto::hash payment_id = null_hash; // normal - crypto::hash8 payment_id8 = null_hash8; // encrypted + string tx_pk_str = REMOVE_HASH_BRAKETS(fmt::format("{:s}", pk)); - string json_representation; + //cout << "payment_id: " << payment_id << endl; - std::vector > signatures; + string pid_str = REMOVE_HASH_BRAKETS(fmt::format("{:s}", payment_id)); + string pid8_str = REMOVE_HASH_BRAKETS(fmt::format("{:s}", payment_id8)); - // key images of inputs - vector input_key_imgs; + string mixin_str {"N/A"}; + string fee_str {"N/A"}; + string fee_short_str {"N/A"}; - // public keys and xmr amount of outputs - vector> output_pub_keys; - - mstch::map - get_mstch_map() + if (!input_key_imgs.empty()) { - // remove "<" and ">" from the hash string - string tx_hash_str = REMOVE_HASH_BRAKETS(fmt::format("{:s}", hash)); - - string tx_pk_str = REMOVE_HASH_BRAKETS(fmt::format("{:s}", pk)); - - //cout << "payment_id: " << payment_id << endl; - - string pid_str = REMOVE_HASH_BRAKETS(fmt::format("{:s}", payment_id)); - string pid8_str = REMOVE_HASH_BRAKETS(fmt::format("{:s}", payment_id8)); + mixin_str = std::to_string(mixin_no - 1); + fee_str = fmt::format("{:0.6f}", XMR_AMOUNT(fee)); + fee_short_str = fmt::format("{:0.3f}", XMR_AMOUNT(fee)); + } - string mixin_str {"N/A"}; - string fee_str {"N/A"}; - string fee_short_str {"N/A"}; - if (!input_key_imgs.empty()) - { - mixin_str = std::to_string(mixin_no - 1); - fee_str = fmt::format("{:0.6f}", XMR_AMOUNT(fee)); - fee_short_str = fmt::format("{:0.3f}", XMR_AMOUNT(fee)); - } + //cout << "extra: " << extra_str << endl; + + mstch::map txd_map { + {"hash" , tx_hash_str}, + {"pub_key" , tx_pk_str}, + {"tx_fee" , fee_str}, + {"tx_fee_short" , fee_short_str}, + {"sum_inputs" , fmt::format("{:0.6f}", XMR_AMOUNT(xmr_inputs))}, + {"sum_outputs" , fmt::format("{:0.6f}", XMR_AMOUNT(xmr_outputs))}, + {"sum_inputs_short" , fmt::format("{:0.3f}", XMR_AMOUNT(xmr_inputs))}, + {"sum_outputs_short" , fmt::format("{:0.3f}", XMR_AMOUNT(xmr_outputs))}, + {"no_inputs" , input_key_imgs.size()}, + {"no_outputs" , output_pub_keys.size()}, + {"mixin" , mixin_str}, + {"blk_height" , blk_height}, + {"version" , std::to_string(version)}, + {"has_payment_id" , payment_id != null_hash}, + {"has_payment_id8" , payment_id8 != null_hash8}, + {"payment_id" , pid_str}, + {"confirmations" , no_confirmations}, + {"extra" , get_extra_str()}, + {"payment_id8" , pid8_str}, + {"unlock_time" , std::to_string(unlock_time)}, + {"tx_size" , fmt::format("{:0.4f}", + static_cast(size)/1024.0)}, + {"tx_size_short" , fmt::format("{:0.2f}", + static_cast(size)/1024.0)} + }; - //cout << "extra: " << extra_str << endl; - - mstch::map txd_map { - {"hash" , tx_hash_str}, - {"pub_key" , tx_pk_str}, - {"tx_fee" , fee_str}, - {"tx_fee_short" , fee_short_str}, - {"sum_inputs" , fmt::format("{:0.6f}", XMR_AMOUNT(xmr_inputs))}, - {"sum_outputs" , fmt::format("{:0.6f}", XMR_AMOUNT(xmr_outputs))}, - {"sum_inputs_short" , fmt::format("{:0.3f}", XMR_AMOUNT(xmr_inputs))}, - {"sum_outputs_short" , fmt::format("{:0.3f}", XMR_AMOUNT(xmr_outputs))}, - {"no_inputs" , input_key_imgs.size()}, - {"no_outputs" , output_pub_keys.size()}, - {"mixin" , mixin_str}, - {"blk_height" , blk_height}, - {"version" , std::to_string(version)}, - {"has_payment_id" , payment_id != null_hash}, - {"has_payment_id8" , payment_id8 != null_hash8}, - {"payment_id" , pid_str}, - {"confirmations" , no_confirmations}, - {"extra" , get_extra_str()}, - {"payment_id8" , pid8_str}, - {"unlock_time" , std::to_string(unlock_time)}, - {"tx_size" , fmt::format("{:0.4f}", - static_cast(size)/1024.0)}, - {"tx_size_short" , fmt::format("{:0.2f}", - static_cast(size)/1024.0)} - }; + return txd_map; + } - return txd_map; - } + string + get_extra_str() + { + string extra_str = epee::string_tools::buff_to_hex_nodelimer( + string{reinterpret_cast(extra.data()), extra.size()}); - string - get_extra_str() - { + return extra_str; + } - string extra_str = epee::string_tools::buff_to_hex_nodelimer( - string{reinterpret_cast(extra.data()), extra.size()}); - return extra_str; - } + mstch::array + get_ring_sig_for_input(uint64_t in_i) + { + mstch::array ring_sigs {}; + if (in_i >= signatures.size()) + { + return ring_sigs; + } - mstch::array - get_ring_sig_for_input(uint64_t in_i) - { - mstch::array ring_sigs {}; + for (const crypto::signature &sig: signatures.at(in_i)) + { + ring_sigs.push_back(mstch::map{ + {"ring_sig", print_signature(sig)} + }); + } - if (in_i >= signatures.size()) - { - return ring_sigs; - } + return ring_sigs; + } - for (const crypto::signature &sig: signatures.at(in_i)) - { - ring_sigs.push_back(mstch::map{ - {"ring_sig", print_signature(sig)} - }); - } + string + print_signature(const signature& sig) + { + stringstream ss; - return ring_sigs; - } + ss << epee::string_tools::pod_to_hex(sig.c) + << epee::string_tools::pod_to_hex(sig.r); - string - print_signature(const signature& sig) - { - stringstream ss; + return ss.str(); + } +}; - ss << epee::string_tools::pod_to_hex(sig.c) - << epee::string_tools::pod_to_hex(sig.r); +class page { - return ss.str(); - } + // check if we have tx_blob member in tx_info structure + static const bool HAVE_TX_BLOB { + HAS_MEMBER(cryptonote::tx_info, tx_blob) }; - class page { + static const bool FULL_AGE_FORMAT {true}; - // check if we have tx_blob member in tx_info structure - static const bool HAVE_TX_BLOB { - HAS_MEMBER(cryptonote::tx_info, tx_blob) - }; - - static const bool FULL_AGE_FORMAT {true}; + MicroCore* mcore; + Blockchain* core_storage; + rpccalls rpc; + time_t server_timestamp; - MicroCore* mcore; - Blockchain* core_storage; - rpccalls rpc; - time_t server_timestamp; + string lmdb2_path; - string lmdb2_path; + string css_styles; - string css_styles; + bool testnet; - bool testnet; +public: - public: + page(MicroCore* _mcore, Blockchain* _core_storage, + string _deamon_url, string _lmdb2_path, bool _testnet) + : mcore {_mcore}, + core_storage {_core_storage}, + rpc {_deamon_url}, + server_timestamp {std::time(nullptr)}, + lmdb2_path {_lmdb2_path}, + testnet {_testnet} + { + css_styles = xmreg::read(TMPL_CSS_STYLES); + } - page(MicroCore* _mcore, Blockchain* _core_storage, - string _deamon_url, string _lmdb2_path, bool _testnet) - : mcore {_mcore}, - core_storage {_core_storage}, - rpc {_deamon_url}, - server_timestamp {std::time(nullptr)}, - lmdb2_path {_lmdb2_path}, - testnet {_testnet} - { - css_styles = xmreg::read(TMPL_CSS_STYLES); - } + /** + * @brief Show recent blocks and mempool + * + * Not used currently. index2 method is used instead + * + * @param page_no block page to show + * @param refresh_page enable autorefresh + * @return rendered index page + */ + string + index(uint64_t page_no = 0, bool refresh_page = false) + { + // connect to the deamon if not yet connected + bool is_connected = rpc.connect_to_monero_deamon(); - /** - * @brief Show recent blocks and mempool - * - * Not used currently. index2 method is used instead - * - * @param page_no block page to show - * @param refresh_page enable autorefresh - * @return rendered index page - */ - string - index(uint64_t page_no = 0, bool refresh_page = false) + if (!is_connected) { - // connect to the deamon if not yet connected - bool is_connected = rpc.connect_to_monero_deamon(); + cerr << "Connection to the Monero demon does not exist or was lost!" << endl; + return "Connection to the Monero demon does not exist or was lost!"; + } - if (!is_connected) - { - cerr << "Connection to the Monero demon does not exist or was lost!" << endl; - return "Connection to the Monero demon does not exist or was lost!"; - } + //get current server timestamp + server_timestamp = std::time(nullptr); + + // number of last blocks to show + uint64_t no_of_last_blocks {100 + 1}; + + uint64_t height = rpc.get_current_height() - 1; + + // initalise page tempate map with basic info about blockchain + mstch::map context { + {"refresh" , refresh_page}, + {"height" , std::to_string(height)}, + {"server_timestamp", xmreg::timestamp_to_str(server_timestamp)}, + {"blocks" , mstch::array()}, + {"age_format" , string("[h:m:d]")}, + {"page_no" , std::to_string(page_no)}, + {"total_page_no" , std::to_string(height / (no_of_last_blocks))}, + {"is_page_zero" , !bool(page_no)}, + {"next_page" , std::to_string(page_no + 1)}, + {"prev_page" , std::to_string((page_no > 0 ? page_no - 1 : 0))} + }; - //get current server timestamp - server_timestamp = std::time(nullptr); - - // number of last blocks to show - uint64_t no_of_last_blocks {100 + 1}; - - uint64_t height = rpc.get_current_height() - 1; - - // initalise page tempate map with basic info about blockchain - mstch::map context { - {"refresh" , refresh_page}, - {"height" , std::to_string(height)}, - {"server_timestamp", xmreg::timestamp_to_str(server_timestamp)}, - {"blocks" , mstch::array()}, - {"age_format" , string("[h:m:d]")}, - {"page_no" , std::to_string(page_no)}, - {"total_page_no" , std::to_string(height / (no_of_last_blocks))}, - {"is_page_zero" , !bool(page_no)}, - {"next_page" , std::to_string(page_no + 1)}, - {"prev_page" , std::to_string((page_no > 0 ? page_no - 1 : 0))} - }; + // get reference to blocks template map to be field below + mstch::array& blocks = boost::get(context["blocks"]); - // get reference to blocks template map to be field below - mstch::array& blocks = boost::get(context["blocks"]); + // calculate starting and ending block numbers to show + uint64_t start_height = height - no_of_last_blocks * (page_no + 1); + uint64_t end_height = height - no_of_last_blocks * (page_no); - // calculate starting and ending block numbers to show - uint64_t start_height = height - no_of_last_blocks * (page_no + 1); - uint64_t end_height = height - no_of_last_blocks * (page_no); + // check few conditions to make sure we are whithin the avaliable range + //@TODO its too messed up. needs to find cleaner way. + start_height = start_height > 0 ? start_height : 0; + end_height = end_height < height ? end_height : height; + start_height = start_height > end_height ? 0 : start_height; + end_height = end_height - start_height > no_of_last_blocks + ? no_of_last_blocks : end_height; - // check few conditions to make sure we are whithin the avaliable range - //@TODO its too messed up. needs to find cleaner way. - start_height = start_height > 0 ? start_height : 0; - end_height = end_height < height ? end_height : height; - start_height = start_height > end_height ? 0 : start_height; - end_height = end_height - start_height > no_of_last_blocks - ? no_of_last_blocks : end_height; + // previous blk timestamp, initalised to lowest possible value + double prev_blk_timestamp {std::numeric_limits::lowest()}; - // previous blk timestamp, initalised to lowest possible value - double prev_blk_timestamp {std::numeric_limits::lowest()}; + // iterate over last no_of_last_blocks of blocks + for (uint64_t i = start_height; i <= end_height; ++i) + { + // get block at the given height i + block blk; - // iterate over last no_of_last_blocks of blocks - for (uint64_t i = start_height; i <= end_height; ++i) + if (!mcore->get_block_by_height(i, blk)) { - // get block at the given height i - block blk; + cerr << "Cant get block: " << i << endl; + continue; + } - if (!mcore->get_block_by_height(i, blk)) - { - cerr << "Cant get block: " << i << endl; - continue; - } + // get block's hash + crypto::hash blk_hash = core_storage->get_block_id_by_height(i); - // get block's hash - crypto::hash blk_hash = core_storage->get_block_id_by_height(i); + // remove "<" and ">" from the hash string + string blk_hash_str = REMOVE_HASH_BRAKETS(fmt::format("{:s}", blk_hash)); - // remove "<" and ">" from the hash string - string blk_hash_str = REMOVE_HASH_BRAKETS(fmt::format("{:s}", blk_hash)); + // get block timestamp in user friendly format + string timestamp_str = xmreg::timestamp_to_str(blk.timestamp); - // get block timestamp in user friendly format - string timestamp_str = xmreg::timestamp_to_str(blk.timestamp); + pair age = get_age(server_timestamp, blk.timestamp); - pair age = get_age(server_timestamp, blk.timestamp); + context["age_format"] = age.second; - context["age_format"] = age.second; + // get time difference [m] between previous and current blocks + string time_delta_str {}; - // get time difference [m] between previous and current blocks - string time_delta_str {}; + if (prev_blk_timestamp > std::numeric_limits::lowest()) + { + time_delta_str = fmt::format("{:0.2f}", + (double(blk.timestamp) - double(prev_blk_timestamp))/60.0); + } - if (prev_blk_timestamp > std::numeric_limits::lowest()) - { - time_delta_str = fmt::format("{:0.2f}", - (double(blk.timestamp) - double(prev_blk_timestamp))/60.0); - } + // get xmr in the block reward + array coinbase_tx = sum_money_in_tx(blk.miner_tx); - // get xmr in the block reward - array coinbase_tx = sum_money_in_tx(blk.miner_tx); + // get transactions in the block + const vector& txs_in_blk = + core_storage->get_db().get_tx_list(blk.tx_hashes); - // get transactions in the block - const vector& txs_in_blk = - core_storage->get_db().get_tx_list(blk.tx_hashes); + // sum xmr in the inputs and ouputs of all transactions + array sum_xmr_in_out = sum_money_in_txs(txs_in_blk); - // sum xmr in the inputs and ouputs of all transactions - array sum_xmr_in_out = sum_money_in_txs(txs_in_blk); + // get sum of all transactions in the block + uint64_t sum_fees = sum_fees_in_txs(txs_in_blk); - // get sum of all transactions in the block - uint64_t sum_fees = sum_fees_in_txs(txs_in_blk); + // get mixin number in each transaction + vector mixin_numbers = xmreg::get_mixin_no_in_txs(txs_in_blk); - // get mixin number in each transaction - vector mixin_numbers = xmreg::get_mixin_no_in_txs(txs_in_blk); + // find minimum and maxium mixin numbers + int mixin_min {-1}; + int mixin_max {-1}; - // find minimum and maxium mixin numbers - int mixin_min {-1}; - int mixin_max {-1}; + if (!mixin_numbers.empty()) + { + mixin_min = static_cast( + *std::min_element(mixin_numbers.begin(), mixin_numbers.end())); + mixin_max = static_cast( + *max_element(mixin_numbers.begin(), mixin_numbers.end())); + } - if (!mixin_numbers.empty()) + // mixing format for the templates + auto mixin_format = [=]() -> mstch::node + { + if (mixin_min < 0) { - mixin_min = static_cast( - *std::min_element(mixin_numbers.begin(), mixin_numbers.end())); - mixin_max = static_cast( - *max_element(mixin_numbers.begin(), mixin_numbers.end())); + return string("N/A"); } + return fmt::format("{:d}-{:d}", mixin_min - 1, mixin_max - 1); + }; - // mixing format for the templates - auto mixin_format = [=]() -> mstch::node - { - if (mixin_min < 0) - { - return string("N/A"); - } - return fmt::format("{:d}-{:d}", mixin_min - 1, mixin_max - 1); - }; + // get block size in bytes + uint64_t blk_size = get_object_blobsize(blk); - // get block size in bytes - uint64_t blk_size = get_object_blobsize(blk); - - // set output page template map - blocks.push_back(mstch::map { - {"height" , to_string(i)}, - {"timestamp" , timestamp_str}, - {"time_delta" , time_delta_str}, - {"age" , age.first}, - {"hash" , blk_hash_str}, - {"block_reward", fmt::format("{:0.4f}/{:0.4f}", - XMR_AMOUNT(coinbase_tx[1] - sum_fees), - XMR_AMOUNT(sum_fees))}, - {"fees" , fmt::format("{:0.3f}", XMR_AMOUNT(sum_fees))}, - {"notx" , fmt::format("{:d}", blk.tx_hashes.size())}, - {"xmr_inputs" , fmt::format("{:0.2f}", - XMR_AMOUNT(sum_xmr_in_out[0]))}, - {"xmr_outputs" , fmt::format("{:0.2f}", - XMR_AMOUNT(sum_xmr_in_out[1]))}, - {"mixin_range" , mstch::lambda {mixin_format}}, - {"blksize" , fmt::format("{:0.2f}", - static_cast(blk_size) / 1024.0)} - }); + // set output page template map + blocks.push_back(mstch::map { + {"height" , to_string(i)}, + {"timestamp" , timestamp_str}, + {"time_delta" , time_delta_str}, + {"age" , age.first}, + {"hash" , blk_hash_str}, + {"block_reward", fmt::format("{:0.4f}/{:0.4f}", + XMR_AMOUNT(coinbase_tx[1] - sum_fees), + XMR_AMOUNT(sum_fees))}, + {"fees" , fmt::format("{:0.3f}", XMR_AMOUNT(sum_fees))}, + {"notx" , fmt::format("{:d}", blk.tx_hashes.size())}, + {"xmr_inputs" , fmt::format("{:0.2f}", + XMR_AMOUNT(sum_xmr_in_out[0]))}, + {"xmr_outputs" , fmt::format("{:0.2f}", + XMR_AMOUNT(sum_xmr_in_out[1]))}, + {"mixin_range" , mstch::lambda {mixin_format}}, + {"blksize" , fmt::format("{:0.2f}", + static_cast(blk_size) / 1024.0)} + }); + + // save current's block timestamp as reference for the next one + prev_blk_timestamp = static_cast(blk.timestamp); + + } // for (uint64_t i = start_height; i <= end_height; ++i) + + // reverse blocks and remove last (i.e., oldest) + // block. This is done so that time delats + // are easier to calcualte in the above for loop + std::reverse(blocks.begin(), blocks.end()); + + // if we look at the genesis time, we should not remove + // the last block, i.e. genesis one. + if (!(start_height < 2)) + { + blocks.pop_back(); + } - // save current's block timestamp as reference for the next one - prev_blk_timestamp = static_cast(blk.timestamp); + // get memory pool rendered template + string mempool_html = mempool(); - } // for (uint64_t i = start_height; i <= end_height; ++i) + // append mempool_html to the index context map + context["mempool_info"] = mempool_html; - // reverse blocks and remove last (i.e., oldest) - // block. This is done so that time delats - // are easier to calcualte in the above for loop - std::reverse(blocks.begin(), blocks.end()); + // read index.html + string index_html = xmreg::read(TMPL_INDEX); - // if we look at the genesis time, we should not remove - // the last block, i.e. genesis one. - if (!(start_height < 2)) - { - blocks.pop_back(); - } + // add header and footer + string full_page = get_full_page(index_html); - // get memory pool rendered template - string mempool_html = mempool(); + context["css_styles"] = this->css_styles; - // append mempool_html to the index context map - context["mempool_info"] = mempool_html; + // render the page + return mstch::render(full_page, context); + } - // read index.html - string index_html = xmreg::read(TMPL_INDEX); - // add header and footer - string full_page = get_full_page(index_html); - context["css_styles"] = this->css_styles; + /** + * @brief show recent transactions and mempool + * @param page_no block page to show + * @param refresh_page enable autorefresh + * @return rendered index page + */ + string + index2(uint64_t page_no = 0, bool refresh_page = false) + { - // render the page - return mstch::render(full_page, context); - } + //get current server timestamp + server_timestamp = std::time(nullptr); + + // number of last blocks to show + uint64_t no_of_last_blocks {25 + 1}; + + // get the current blockchain height. Just to check + uint64_t height = + xmreg::MyLMDB::get_blockchain_height(mcore->get_blkchain_path()) - 1; + + // initalise page tempate map with basic info about blockchain + mstch::map context { + {"testnet" , testnet}, + {"refresh" , refresh_page}, + {"height" , std::to_string(height)}, + {"server_timestamp", xmreg::timestamp_to_str(server_timestamp)}, + {"age_format" , string("[h:m:d]")}, + {"page_no" , std::to_string(page_no)}, + {"total_page_no" , std::to_string(height / (no_of_last_blocks))}, + {"is_page_zero" , !bool(page_no)}, + {"next_page" , std::to_string(page_no + 1)}, + {"prev_page" , std::to_string((page_no > 0 ? page_no - 1 : 0))}, + {"txs" , mstch::array()} // will keep tx to show + }; + // get reference to txs mstch map to be field below + mstch::array& txs = boost::get(context["txs"]); + // calculate starting and ending block numbers to show + uint64_t start_height = height - no_of_last_blocks * (page_no + 1); + uint64_t end_height = height - no_of_last_blocks * (page_no); - /** - * @brief show recent transactions and mempool - * @param page_no block page to show - * @param refresh_page enable autorefresh - * @return rendered index page - */ - string - index2(uint64_t page_no = 0, bool refresh_page = false) - { + // check few conditions to make sure we are whithin the avaliable range + //@TODO its too messed up. needs to find cleaner way. + start_height = start_height > 0 ? start_height : 0; + end_height = end_height < height ? end_height : height; + start_height = start_height > end_height ? 0 : start_height; + end_height = end_height - start_height > no_of_last_blocks + ? no_of_last_blocks : end_height; - //get current server timestamp - server_timestamp = std::time(nullptr); + // previous blk timestamp, initalised to lowest possible value + double prev_blk_timestamp {std::numeric_limits::lowest()}; - // number of last blocks to show - uint64_t no_of_last_blocks {25 + 1}; + // iterate over last no_of_last_blocks of blocks + for (uint64_t i = start_height; i <= end_height; ++i) + { + // get block at the given height i + block blk; - // get the current blockchain height. Just to check - uint64_t height = - xmreg::MyLMDB::get_blockchain_height(mcore->get_blkchain_path()) - 1; + if (!mcore->get_block_by_height(i, blk)) + { + cerr << "Cant get block: " << i << endl; + continue; + } - // initalise page tempate map with basic info about blockchain - mstch::map context { - {"testnet" , testnet}, - {"refresh" , refresh_page}, - {"height" , std::to_string(height)}, - {"server_timestamp", xmreg::timestamp_to_str(server_timestamp)}, - {"age_format" , string("[h:m:d]")}, - {"page_no" , std::to_string(page_no)}, - {"total_page_no" , std::to_string(height / (no_of_last_blocks))}, - {"is_page_zero" , !bool(page_no)}, - {"next_page" , std::to_string(page_no + 1)}, - {"prev_page" , std::to_string((page_no > 0 ? page_no - 1 : 0))}, - {"txs" , mstch::array()} // will keep tx to show - }; + // get block's hash + crypto::hash blk_hash = core_storage->get_block_id_by_height(i); + + // remove "<" and ">" from the hash string + string blk_hash_str = REMOVE_HASH_BRAKETS(fmt::format("{:s}", blk_hash)); - // get reference to txs mstch map to be field below - mstch::array& txs = boost::get(context["txs"]); + // get block age - // calculate starting and ending block numbers to show - uint64_t start_height = height - no_of_last_blocks * (page_no + 1); - uint64_t end_height = height - no_of_last_blocks * (page_no); + pair age = get_age(server_timestamp, blk.timestamp); - // check few conditions to make sure we are whithin the avaliable range - //@TODO its too messed up. needs to find cleaner way. - start_height = start_height > 0 ? start_height : 0; - end_height = end_height < height ? end_height : height; - start_height = start_height > end_height ? 0 : start_height; - end_height = end_height - start_height > no_of_last_blocks - ? no_of_last_blocks : end_height; + context["age_format"] = age.second; - // previous blk timestamp, initalised to lowest possible value - double prev_blk_timestamp {std::numeric_limits::lowest()}; + // get time difference [m] between previous and current blocks + string time_delta_str {}; - // iterate over last no_of_last_blocks of blocks - for (uint64_t i = start_height; i <= end_height; ++i) + if (prev_blk_timestamp > std::numeric_limits::lowest()) { - // get block at the given height i - block blk; + time_delta_str = fmt::format("({:06.2f})", + (double(blk.timestamp) - double(prev_blk_timestamp))/60.0); + } - if (!mcore->get_block_by_height(i, blk)) - { - cerr << "Cant get block: " << i << endl; - continue; - } + // get all transactions in the block found + // initialize the first list with transaction for solving + // the block i.e. coinbase. + list blk_txs {blk.miner_tx}; + list missed_txs; - // get block's hash - crypto::hash blk_hash = core_storage->get_block_id_by_height(i); + if (!core_storage->get_transactions(blk.tx_hashes, blk_txs, missed_txs)) + { + cerr << "Cant get transactions in block: " << i << endl; + continue; + } - // remove "<" and ">" from the hash string - string blk_hash_str = REMOVE_HASH_BRAKETS(fmt::format("{:s}", blk_hash)); + uint64_t tx_i {0}; - // get block age + for(list::reverse_iterator rit = blk_txs.rbegin(); + rit != blk_txs.rend(); ++rit) + { + const cryptonote::transaction& tx = *rit; - pair age = get_age(server_timestamp, blk.timestamp); + tx_details txd = get_tx_details(tx); - context["age_format"] = age.second; + mstch::map txd_map = txd.get_mstch_map(); - // get time difference [m] between previous and current blocks - string time_delta_str {}; + //add age to the txd mstch map + txd_map.insert({"height" , i}); + txd_map.insert({"blk_hash" , blk_hash_str}); + txd_map.insert({"time_delta", time_delta_str}); + txd_map.insert({"age" , age.first}); - if (prev_blk_timestamp > std::numeric_limits::lowest()) + // do not show block info for other than + // last (i.e., first after reverse below) + // tx in the block + if (tx_i < blk_txs.size() - 1) { - time_delta_str = fmt::format("({:06.2f})", - (double(blk.timestamp) - double(prev_blk_timestamp))/60.0); + txd_map["height"] = string(""); + txd_map["age"] = string(""); + txd_map["time_delta"] = string(""); } - // get all transactions in the block found - // initialize the first list with transaction for solving - // the block i.e. coinbase. - list blk_txs {blk.miner_tx}; - list missed_txs; - - if (!core_storage->get_transactions(blk.tx_hashes, blk_txs, missed_txs)) - { - cerr << "Cant get transactions in block: " << i << endl; - continue; - } + txs.push_back(txd_map); - uint64_t tx_i {0}; + ++tx_i; + } - for(list::reverse_iterator rit = blk_txs.rbegin(); - rit != blk_txs.rend(); ++rit) - { - const cryptonote::transaction& tx = *rit; + // save current's block timestamp as reference for the next one + prev_blk_timestamp = static_cast(blk.timestamp); - tx_details txd = get_tx_details(tx); + } // for (uint64_t i = start_height; i <= end_height; ++i) - mstch::map txd_map = txd.get_mstch_map(); + // reverse txs and remove last (i.e., oldest) + // tx. This is done so that time delats + // are easier to calcualte in the above for loop + std::reverse(txs.begin(), txs.end()); - //add age to the txd mstch map - txd_map.insert({"height" , i}); - txd_map.insert({"blk_hash" , blk_hash_str}); - txd_map.insert({"time_delta", time_delta_str}); - txd_map.insert({"age" , age.first}); + // if we look at the genesis time, we should not remove + // the last block, i.e. genesis one. + if (!(start_height < 2)) + { + //txs.pop_back(); + } - // do not show block info for other than - // last (i.e., first after reverse below) - // tx in the block - if (tx_i < blk_txs.size() - 1) - { - txd_map["height"] = string(""); - txd_map["age"] = string(""); - txd_map["time_delta"] = string(""); - } + // get memory pool rendered template + string mempool_html = mempool(); - txs.push_back(txd_map); + // append mempool_html to the index context map + context["mempool_info"] = mempool_html; - ++tx_i; - } + // read index.html + string index2_html = xmreg::read(TMPL_INDEX2); - // save current's block timestamp as reference for the next one - prev_blk_timestamp = static_cast(blk.timestamp); + // add header and footer + string full_page = get_full_page(index2_html); - } // for (uint64_t i = start_height; i <= end_height; ++i) + add_css_style(context); - // reverse txs and remove last (i.e., oldest) - // tx. This is done so that time delats - // are easier to calcualte in the above for loop - std::reverse(txs.begin(), txs.end()); + // render the page + return mstch::render(full_page, context); - // if we look at the genesis time, we should not remove - // the last block, i.e. genesis one. - if (!(start_height < 2)) - { - //txs.pop_back(); - } + } - // get memory pool rendered template - string mempool_html = mempool(); - // append mempool_html to the index context map - context["mempool_info"] = mempool_html; + /** + * Render mempool data + */ + string + mempool() + { + std::vector mempool_txs; - // read index.html - string index2_html = xmreg::read(TMPL_INDEX2); + if (!rpc.get_mempool(mempool_txs)) + { + return "Getting mempool failed"; + } - // add header and footer - string full_page = get_full_page(index2_html); + // initalise page tempate map with basic info about mempool + mstch::map context { + {"mempool_size", std::to_string(mempool_txs.size())}, + {"mempooltxs" , mstch::array()} + }; - add_css_style(context); + // get reference to blocks template map to be field below + mstch::array& txs = boost::get(context["mempooltxs"]); - // render the page - return mstch::render(full_page, context); + // for each transaction in the memory pool + for (size_t i = 0; i < mempool_txs.size(); ++i) + { + // get transaction info of the tx in the mempool + tx_info _tx_info = mempool_txs.at(i); - } + // calculate difference between tx in mempool and server timestamps + array delta_time = timestamp_difference( + server_timestamp, + _tx_info.receive_time); + // use only hours, so if we have days, add + // it to hours + uint64_t delta_hours {delta_time[1]*24 + delta_time[2]}; - /** - * Render mempool data - */ - string - mempool() - { - std::vector mempool_txs; + string age_str = fmt::format("{:02d}:{:02d}:{:02d}", + delta_hours, + delta_time[3], delta_time[4]); - if (!rpc.get_mempool(mempool_txs)) + // if more than 99 hourse, change formating + // for the template + if (delta_hours > 99) { - return "Getting mempool failed"; + age_str = fmt::format("{:03d}:{:02d}:{:02d}", + delta_hours, + delta_time[3], delta_time[4]); } - // initalise page tempate map with basic info about mempool - mstch::map context { - {"mempool_size", std::to_string(mempool_txs.size())}, - {"mempooltxs" , mstch::array()} - }; - - // get reference to blocks template map to be field below - mstch::array& txs = boost::get(context["mempooltxs"]); + //cout << _tx_info.tx_json << endl; - // for each transaction in the memory pool - for (size_t i = 0; i < mempool_txs.size(); ++i) - { - // get transaction info of the tx in the mempool - tx_info _tx_info = mempool_txs.at(i); + // sum xmr in inputs and ouputs in the given tx + pair sum_inputs = sum_xmr_inputs(_tx_info.tx_json); + pair sum_outputs = sum_xmr_outputs(_tx_info.tx_json); + + // get mixin number in each transaction + vector mixin_numbers = get_mixin_no_in_txs(_tx_info.tx_json); + + uint64_t mixin_no = 0; + + if (!mixin_numbers.empty()) + mixin_numbers.at(0) - 1; + + // set output page template map + txs.push_back(mstch::map { + {"timestamp" , xmreg::timestamp_to_str(_tx_info.receive_time)}, + {"age" , age_str}, + {"hash" , fmt::format("{:s}", _tx_info.id_hash)}, + {"fee" , fmt::format("{:0.3f}", XMR_AMOUNT(_tx_info.fee))}, + {"xmr_inputs" , fmt::format("{:0.2f}", XMR_AMOUNT(sum_inputs.first))}, + {"xmr_outputs" , fmt::format("{:0.2f}", XMR_AMOUNT(sum_outputs.first))}, + {"no_inputs" , sum_inputs.second}, + {"no_outputs" , sum_outputs.second}, + {"mixin" , fmt::format("{:d}", mixin_no)}, + {"txsize" , fmt::format("{:0.2f}", static_cast(_tx_info.blob_size)/1024.0)} + }); + } - // calculate difference between tx in mempool and server timestamps - array delta_time = timestamp_difference( - server_timestamp, - _tx_info.receive_time); + // read index.html + string mempool_html = xmreg::read(TMPL_MEMPOOL); - // use only hours, so if we have days, add - // it to hours - uint64_t delta_hours {delta_time[1]*24 + delta_time[2]}; + // render the page + return mstch::render(mempool_html, context); + } - string age_str = fmt::format("{:02d}:{:02d}:{:02d}", - delta_hours, - delta_time[3], delta_time[4]); - // if more than 99 hourse, change formating - // for the template - if (delta_hours > 99) - { - age_str = fmt::format("{:03d}:{:02d}:{:02d}", - delta_hours, - delta_time[3], delta_time[4]); - } + string + show_block(uint64_t _blk_height) + { - //cout << _tx_info.tx_json << endl; + // get block at the given height i + block blk; - // sum xmr in inputs and ouputs in the given tx - pair sum_inputs = sum_xmr_inputs(_tx_info.tx_json); - pair sum_outputs = sum_xmr_outputs(_tx_info.tx_json); + //cout << "_blk_height: " << _blk_height << endl; - // get mixin number in each transaction - vector mixin_numbers = get_mixin_no_in_txs(_tx_info.tx_json); + uint64_t current_blockchain_height + = xmreg::MyLMDB::get_blockchain_height(mcore->get_blkchain_path()) - 1; - uint64_t mixin_no = 0; + if (_blk_height > current_blockchain_height) + { + cerr << "Cant get block: " << _blk_height + << " since its higher than current blockchain height" + << " i.e., " << current_blockchain_height + << endl; + return fmt::format("Cant get block {:d} since its higher than current blockchain height!", + _blk_height); + } - if (!mixin_numbers.empty()) - mixin_numbers.at(0) - 1; - // set output page template map - txs.push_back(mstch::map { - {"timestamp" , xmreg::timestamp_to_str(_tx_info.receive_time)}, - {"age" , age_str}, - {"hash" , fmt::format("{:s}", _tx_info.id_hash)}, - {"fee" , fmt::format("{:0.3f}", XMR_AMOUNT(_tx_info.fee))}, - {"xmr_inputs" , fmt::format("{:0.2f}", XMR_AMOUNT(sum_inputs.first))}, - {"xmr_outputs" , fmt::format("{:0.2f}", XMR_AMOUNT(sum_outputs.first))}, - {"no_inputs" , sum_inputs.second}, - {"no_outputs" , sum_outputs.second}, - {"mixin" , fmt::format("{:d}", mixin_no)}, - {"txsize" , fmt::format("{:0.2f}", static_cast(_tx_info.blob_size)/1024.0)} - }); - } + if (!mcore->get_block_by_height(_blk_height, blk)) + { + cerr << "Cant get block: " << _blk_height << endl; + return fmt::format("Cant get block {:d}!", _blk_height); + } - // read index.html - string mempool_html = xmreg::read(TMPL_MEMPOOL); + // get block's hash + crypto::hash blk_hash = core_storage->get_block_id_by_height(_blk_height); - // render the page - return mstch::render(mempool_html, context); - } + crypto::hash prev_hash = blk.prev_id; + crypto::hash next_hash = core_storage->get_block_id_by_height(_blk_height + 1); + bool have_next_hash = (next_hash == null_hash ? false : true); + bool have_prev_hash = (prev_hash == null_hash ? false : true); - string - show_block(uint64_t _blk_height) - { + // remove "<" and ">" from the hash string + string prev_hash_str = REMOVE_HASH_BRAKETS(fmt::format("{:s}", prev_hash)); + string next_hash_str = REMOVE_HASH_BRAKETS(fmt::format("{:s}", next_hash)); - // get block at the given height i - block blk; + // remove "<" and ">" from the hash string + string blk_hash_str = REMOVE_HASH_BRAKETS(fmt::format("{:s}", blk_hash)); - //cout << "_blk_height: " << _blk_height << endl; + // get block timestamp in user friendly format + string blk_timestamp = xmreg::timestamp_to_str(blk.timestamp); - uint64_t current_blockchain_height - = xmreg::MyLMDB::get_blockchain_height(mcore->get_blkchain_path()) - 1; + // get age of the block relative to the server time + pair age = get_age(server_timestamp, blk.timestamp); - if (_blk_height > current_blockchain_height) - { - cerr << "Cant get block: " << _blk_height - << " since its higher than current blockchain height" - << " i.e., " << current_blockchain_height - << endl; - return fmt::format("Cant get block {:d} since its higher than current blockchain height!", - _blk_height); - } + // get time from the last block + string delta_time {"N/A"}; + if (have_prev_hash) + { + block prev_blk = core_storage->get_db().get_block(prev_hash); - if (!mcore->get_block_by_height(_blk_height, blk)) - { - cerr << "Cant get block: " << _blk_height << endl; - return fmt::format("Cant get block {:d}!", _blk_height); - } + pair delta_diff = get_age(blk.timestamp, prev_blk.timestamp); - // get block's hash - crypto::hash blk_hash = core_storage->get_block_id_by_height(_blk_height); + delta_time = delta_diff.first; + } - crypto::hash prev_hash = blk.prev_id; - crypto::hash next_hash = core_storage->get_block_id_by_height(_blk_height + 1); + // get block size in bytes + uint64_t blk_size = get_object_blobsize(blk); + + // miner reward tx + transaction coinbase_tx = blk.miner_tx; + + // transcation in the block + vector tx_hashes = blk.tx_hashes; + + + bool have_txs = !blk.tx_hashes.empty(); + + // sum of all transactions in the block + uint64_t sum_fees = 0; + + // get tx details for the coinbase tx, i.e., miners reward + tx_details txd_coinbase = get_tx_details(blk.miner_tx, true); + + // initalise page tempate map with basic info about blockchain + mstch::map context { + {"testnet" , testnet}, + {"blk_hash" , blk_hash_str}, + {"blk_height" , _blk_height}, + {"blk_timestamp" , blk_timestamp}, + {"blk_timestamp_epoch" , blk.timestamp}, + {"prev_hash" , prev_hash_str}, + {"next_hash" , next_hash_str}, + {"have_next_hash" , have_next_hash}, + {"have_prev_hash" , have_prev_hash}, + {"have_txs" , have_txs}, + {"no_txs" , std::to_string( + blk.tx_hashes.size())}, + {"blk_age" , age.first}, + {"delta_time" , delta_time}, + {"blk_nonce" , blk.nonce}, + {"age_format" , age.second}, + {"major_ver" , std::to_string(blk.major_version)}, + {"minor_ver" , std::to_string(blk.minor_version)}, + {"blk_size" , fmt::format("{:0.4f}", + static_cast(blk_size) / 1024.0)}, + {"coinbase_txs" , mstch::array{{txd_coinbase.get_mstch_map()}}}, + {"blk_txs" , mstch::array()} + }; - bool have_next_hash = (next_hash == null_hash ? false : true); - bool have_prev_hash = (prev_hash == null_hash ? false : true); + // .push_back(txd_coinbase.get_mstch_map() - // remove "<" and ">" from the hash string - string prev_hash_str = REMOVE_HASH_BRAKETS(fmt::format("{:s}", prev_hash)); - string next_hash_str = REMOVE_HASH_BRAKETS(fmt::format("{:s}", next_hash)); + // boost::get(context["blk_txs"]).push_back(txd_coinbase.get_mstch_map()); - // remove "<" and ">" from the hash string - string blk_hash_str = REMOVE_HASH_BRAKETS(fmt::format("{:s}", blk_hash)); + // now process nomral transactions + // get reference to blocks template map to be field below + mstch::array& txs = boost::get(context["blk_txs"]); - // get block timestamp in user friendly format - string blk_timestamp = xmreg::timestamp_to_str(blk.timestamp); + // timescale representation for each tx in the block + vector mixin_timescales_str; - // get age of the block relative to the server time - pair age = get_age(server_timestamp, blk.timestamp); + // for each transaction in the block + for (size_t i = 0; i < blk.tx_hashes.size(); ++i) + { + // get transaction info of the tx in the mempool + const crypto::hash& tx_hash = blk.tx_hashes.at(i); - // get time from the last block - string delta_time {"N/A"}; + // remove "<" and ">" from the hash string + string tx_hash_str = REMOVE_HASH_BRAKETS(fmt::format("{:s}", tx_hash)); - if (have_prev_hash) - { - block prev_blk = core_storage->get_db().get_block(prev_hash); - pair delta_diff = get_age(blk.timestamp, prev_blk.timestamp); + // get transaction + transaction tx; - delta_time = delta_diff.first; + if (!mcore->get_tx(tx_hash, tx)) + { + cerr << "Cant get tx: " << tx_hash << endl; + continue; } - // get block size in bytes - uint64_t blk_size = get_object_blobsize(blk); + tx_details txd = get_tx_details(tx); - // miner reward tx - transaction coinbase_tx = blk.miner_tx; - - // transcation in the block - vector tx_hashes = blk.tx_hashes; - - - bool have_txs = !blk.tx_hashes.empty(); - - // sum of all transactions in the block - uint64_t sum_fees = 0; - - // get tx details for the coinbase tx, i.e., miners reward - tx_details txd_coinbase = get_tx_details(blk.miner_tx, true); - - // initalise page tempate map with basic info about blockchain - mstch::map context { - {"testnet" , testnet}, - {"blk_hash" , blk_hash_str}, - {"blk_height" , _blk_height}, - {"blk_timestamp" , blk_timestamp}, - {"blk_timestamp_epoch" , blk.timestamp}, - {"prev_hash" , prev_hash_str}, - {"next_hash" , next_hash_str}, - {"have_next_hash" , have_next_hash}, - {"have_prev_hash" , have_prev_hash}, - {"have_txs" , have_txs}, - {"no_txs" , std::to_string( - blk.tx_hashes.size())}, - {"blk_age" , age.first}, - {"delta_time" , delta_time}, - {"blk_nonce" , blk.nonce}, - {"age_format" , age.second}, - {"major_ver" , std::to_string(blk.major_version)}, - {"minor_ver" , std::to_string(blk.minor_version)}, - {"blk_size" , fmt::format("{:0.4f}", - static_cast(blk_size) / 1024.0)}, - {"coinbase_txs" , mstch::array{{txd_coinbase.get_mstch_map()}}}, - {"blk_txs" , mstch::array()} - }; + // add fee to the rest + sum_fees += txd.fee; - // .push_back(txd_coinbase.get_mstch_map() - // boost::get(context["blk_txs"]).push_back(txd_coinbase.get_mstch_map()); + // get mixins in time scale for visual representation + //string mixin_times_scale = xmreg::timestamps_time_scale(mixin_timestamps, + // server_timestamp); - // now process nomral transactions - // get reference to blocks template map to be field below - mstch::array& txs = boost::get(context["blk_txs"]); - // timescale representation for each tx in the block - vector mixin_timescales_str; + // add tx details mstch map to context + txs.push_back(txd.get_mstch_map()); + } - // for each transaction in the block - for (size_t i = 0; i < blk.tx_hashes.size(); ++i) - { - // get transaction info of the tx in the mempool - const crypto::hash& tx_hash = blk.tx_hashes.at(i); - // remove "<" and ">" from the hash string - string tx_hash_str = REMOVE_HASH_BRAKETS(fmt::format("{:s}", tx_hash)); + // add total fees in the block to the context + context["sum_fees"] = fmt::format("{:0.6f}", + XMR_AMOUNT(sum_fees)); + // get xmr in the block reward + context["blk_reward"] = fmt::format("{:0.6f}", + XMR_AMOUNT(txd_coinbase.xmr_outputs - sum_fees)); - // get transaction - transaction tx; + // read block.html + string block_html = xmreg::read(TMPL_BLOCK); - if (!mcore->get_tx(tx_hash, tx)) - { - cerr << "Cant get tx: " << tx_hash << endl; - continue; - } + add_css_style(context); - tx_details txd = get_tx_details(tx); + // add header and footer + string full_page = get_full_page(block_html); - // add fee to the rest - sum_fees += txd.fee; + // render the page + return mstch::render(full_page, context); + } - // get mixins in time scale for visual representation - //string mixin_times_scale = xmreg::timestamps_time_scale(mixin_timestamps, - // server_timestamp); + string + show_block(string _blk_hash) + { + crypto::hash blk_hash; + if (!xmreg::parse_str_secret_key(_blk_hash, blk_hash)) + { + cerr << "Cant parse blk hash: " << blk_hash << endl; + return fmt::format("Cant get block {:s} due to block hash parse error!", blk_hash); + } - // add tx details mstch map to context - txs.push_back(txd.get_mstch_map()); - } + uint64_t blk_height; + if (core_storage->have_block(blk_hash)) + { + blk_height = core_storage->get_db().get_block_height(blk_hash); + } + else + { + cerr << "Cant get block: " << blk_hash << endl; + return fmt::format("Cant get block {:s} for some uknown reason", blk_hash); + } - // add total fees in the block to the context - context["sum_fees"] = fmt::format("{:0.6f}", - XMR_AMOUNT(sum_fees)); + return show_block(blk_height); + } - // get xmr in the block reward - context["blk_reward"] = fmt::format("{:0.6f}", - XMR_AMOUNT(txd_coinbase.xmr_outputs - sum_fees)); + string + show_tx(string tx_hash_str, uint with_ring_signatures = 0) + { - // read block.html - string block_html = xmreg::read(TMPL_BLOCK); + // parse tx hash string to hash object + crypto::hash tx_hash; - add_css_style(context); + if (!xmreg::parse_str_secret_key(tx_hash_str, tx_hash)) + { + cerr << "Cant parse tx hash: " << tx_hash_str << endl; + return string("Cant get tx hash due to parse error: " + tx_hash_str); + } - // add header and footer - string full_page = get_full_page(block_html); + // tx age + pair age; - // render the page - return mstch::render(full_page, context); - } + string blk_timestamp {"N/A"}; + // get transaction + transaction tx; - string - show_block(string _blk_hash) + if (!mcore->get_tx(tx_hash, tx)) { - crypto::hash blk_hash; + cerr << "Cant get tx in blockchain: " << tx_hash + << ". \n Check mempool now" << endl; + + vector> found_txs + = search_mempool(tx_hash); - if (!xmreg::parse_str_secret_key(_blk_hash, blk_hash)) + if (!found_txs.empty()) { - cerr << "Cant parse blk hash: " << blk_hash << endl; - return fmt::format("Cant get block {:s} due to block hash parse error!", blk_hash); - } + // there should be only one tx found + tx = found_txs.at(0).second; - uint64_t blk_height; + // since its tx in mempool, it has no blk yet + // so use its recive_time as timestamp to show - if (core_storage->have_block(blk_hash)) - { - blk_height = core_storage->get_db().get_block_height(blk_hash); + uint64_t tx_recieve_timestamp + = found_txs.at(0).first.receive_time; + + blk_timestamp = xmreg::timestamp_to_str(tx_recieve_timestamp); + + age = get_age(server_timestamp, tx_recieve_timestamp, + FULL_AGE_FORMAT); } else { - cerr << "Cant get block: " << blk_hash << endl; - return fmt::format("Cant get block {:s} for some uknown reason", blk_hash); + // tx is nowhere to be found :-( + return string("Cant get tx: " + tx_hash_str); } - - return show_block(blk_height); } - string - show_tx(string tx_hash_str, uint with_ring_signatures = 0) + mstch::map tx_context = construct_tx_context(tx, with_ring_signatures); + + if (boost::get(tx_context["has_error"])) { + return boost::get(tx_context["error_msg"]); + } - // parse tx hash string to hash object - crypto::hash tx_hash; + mstch::map context { + {"txs" , mstch::array{}}, + {"testnet" , this->testnet} + }; - if (!xmreg::parse_str_secret_key(tx_hash_str, tx_hash)) - { - cerr << "Cant parse tx hash: " << tx_hash_str << endl; - return string("Cant get tx hash due to parse error: " + tx_hash_str); - } + boost::get(context["txs"]).push_back(tx_context); - // tx age - pair age; + map partials { + {"tx_details", xmreg::read(string(TMPL_PARIALS_DIR) + "/tx_details.html")}, + }; - string blk_timestamp {"N/A"}; + // read tx.html + string tx_html = xmreg::read(TMPL_TX); - // get transaction - transaction tx; + add_css_style(context); - if (!mcore->get_tx(tx_hash, tx)) - { - cerr << "Cant get tx in blockchain: " << tx_hash - << ". \n Check mempool now" << endl; + // add header and footer + string full_page = get_full_page(tx_html); - vector> found_txs - = search_mempool(tx_hash); + // render the page + return mstch::render(full_page, context, partials); + } - if (!found_txs.empty()) - { - // there should be only one tx found - tx = found_txs.at(0).second; + string + show_my_outputs(string tx_hash_str, + string xmr_address_str, + string viewkey_str, /* or tx_prv_key_str when tx_prove == true */ + bool tx_prove = false) + { - // since its tx in mempool, it has no blk yet - // so use its recive_time as timestamp to show + // remove white characters + boost::trim(tx_hash_str); + boost::trim(xmr_address_str); + boost::trim(viewkey_str); - uint64_t tx_recieve_timestamp - = found_txs.at(0).first.receive_time; + if (tx_hash_str.empty()) + { + return string("tx hash not provided!"); + } - blk_timestamp = xmreg::timestamp_to_str(tx_recieve_timestamp); + if (xmr_address_str.empty()) + { + return string("Monero address not provided!"); + } - age = get_age(server_timestamp, tx_recieve_timestamp, - FULL_AGE_FORMAT); - } - else - { - // tx is nowhere to be found :-( - return string("Cant get tx: " + tx_hash_str); - } - } + if (viewkey_str.empty()) + { + if (!tx_prove) + return string("Viewkey not provided!"); + else + return string("Tx private key not provided!"); + } - mstch::map tx_context = construct_tx_context(tx, with_ring_signatures); + // parse tx hash string to hash object + crypto::hash tx_hash; - if (boost::get(tx_context["has_error"])) - { - return boost::get(tx_context["error_msg"]); - } + if (!xmreg::parse_str_secret_key(tx_hash_str, tx_hash)) + { + cerr << "Cant parse tx hash: " << tx_hash_str << endl; + return string("Cant get tx hash due to parse error: " + tx_hash_str); + } - mstch::map context { - {"txs" , mstch::array{}}, - {"testnet" , this->testnet} - }; + // parse string representing given monero address + cryptonote::account_public_address address; - boost::get(context["txs"]).push_back(tx_context); + if (!xmreg::parse_str_address(xmr_address_str, address, testnet)) + { + cerr << "Cant parse string address: " << xmr_address_str << endl; + return string("Cant parse xmr address: " + xmr_address_str); + } - map partials { - {"tx_details", xmreg::read(string(TMPL_PARIALS_DIR) + "/tx_details.html")}, - }; + // parse string representing given private key + crypto::secret_key prv_view_key; - // read tx.html - string tx_html = xmreg::read(TMPL_TX); + if (!xmreg::parse_str_secret_key(viewkey_str, prv_view_key)) + { + cerr << "Cant parse the private key: " << viewkey_str << endl; + return string("Cant parse private key: " + viewkey_str); + } - add_css_style(context); + // tx age + pair age; - // add header and footer - string full_page = get_full_page(tx_html); + string blk_timestamp {"N/A"}; - // render the page - return mstch::render(full_page, context, partials); - } + // get transaction + transaction tx; - string - show_my_outputs(string tx_hash_str, - string xmr_address_str, - string viewkey_str, /* or tx_prv_key_str when tx_prove == true */ - bool tx_prove = false) + if (!mcore->get_tx(tx_hash, tx)) { + cerr << "Cant get tx in blockchain: " << tx_hash + << ". \n Check mempool now" << endl; - // remove white characters - boost::trim(tx_hash_str); - boost::trim(xmr_address_str); - boost::trim(viewkey_str); + vector> found_txs + = search_mempool(tx_hash); - if (tx_hash_str.empty()) + if (!found_txs.empty()) { - return string("tx hash not provided!"); - } + // there should be only one tx found + tx = found_txs.at(0).second; - if (xmr_address_str.empty()) - { - return string("Monero address not provided!"); - } + // since its tx in mempool, it has no blk yet + // so use its recive_time as timestamp to show - if (viewkey_str.empty()) - { - if (!tx_prove) - return string("Viewkey not provided!"); - else - return string("Tx private key not provided!"); - } + uint64_t tx_recieve_timestamp + = found_txs.at(0).first.receive_time; - // parse tx hash string to hash object - crypto::hash tx_hash; + blk_timestamp = xmreg::timestamp_to_str(tx_recieve_timestamp); - if (!xmreg::parse_str_secret_key(tx_hash_str, tx_hash)) - { - cerr << "Cant parse tx hash: " << tx_hash_str << endl; - return string("Cant get tx hash due to parse error: " + tx_hash_str); + age = get_age(server_timestamp, + tx_recieve_timestamp, + FULL_AGE_FORMAT); } - - // parse string representing given monero address - cryptonote::account_public_address address; - - if (!xmreg::parse_str_address(xmr_address_str, address, testnet)) + else { - cerr << "Cant parse string address: " << xmr_address_str << endl; - return string("Cant parse xmr address: " + xmr_address_str); + // tx is nowhere to be found :-( + return string("Cant get tx: " + tx_hash_str); } + } - // parse string representing given private key - crypto::secret_key prv_view_key; - - if (!xmreg::parse_str_secret_key(viewkey_str, prv_view_key)) - { - cerr << "Cant parse the private key: " << viewkey_str << endl; - return string("Cant parse private key: " + viewkey_str); - } + tx_details txd = get_tx_details(tx); - // tx age - pair age; + uint64_t tx_blk_height {0}; - string blk_timestamp {"N/A"}; + bool tx_blk_found {false}; - // get transaction - transaction tx; + try + { + tx_blk_height = core_storage->get_db().get_tx_block_height(tx_hash); + tx_blk_found = true; + } + catch (exception& e) + { + cerr << "Cant get block height: " << tx_hash + << e.what() << endl; + } - if (!mcore->get_tx(tx_hash, tx)) - { - cerr << "Cant get tx in blockchain: " << tx_hash - << ". \n Check mempool now" << endl; - vector> found_txs - = search_mempool(tx_hash); + // get block cointaining this tx + block blk; - if (!found_txs.empty()) - { - // there should be only one tx found - tx = found_txs.at(0).second; + if (tx_blk_found && !mcore->get_block_by_height(tx_blk_height, blk)) + { + cerr << "Cant get block: " << tx_blk_height << endl; + } - // since its tx in mempool, it has no blk yet - // so use its recive_time as timestamp to show + string tx_blk_height_str {"N/A"}; - uint64_t tx_recieve_timestamp - = found_txs.at(0).first.receive_time; + if (tx_blk_found) + { + // calculate difference between tx and server timestamps + age = get_age(server_timestamp, blk.timestamp, FULL_AGE_FORMAT); - blk_timestamp = xmreg::timestamp_to_str(tx_recieve_timestamp); + blk_timestamp = xmreg::timestamp_to_str(blk.timestamp); - age = get_age(server_timestamp, - tx_recieve_timestamp, - FULL_AGE_FORMAT); - } - else - { - // tx is nowhere to be found :-( - return string("Cant get tx: " + tx_hash_str); - } - } + tx_blk_height_str = std::to_string(tx_blk_height); + } - tx_details txd = get_tx_details(tx); + // payments id. both normal and encrypted (payment_id8) + string pid_str = REMOVE_HASH_BRAKETS(fmt::format("{:s}", txd.payment_id)); + string pid8_str = REMOVE_HASH_BRAKETS(fmt::format("{:s}", txd.payment_id8)); + + // initalise page tempate map with basic info about blockchain + mstch::map context { + {"testnet" , testnet}, + {"tx_hash" , tx_hash_str}, + {"xmr_address" , xmr_address_str}, + {"viewkey" , viewkey_str}, + {"tx_pub_key" , REMOVE_HASH_BRAKETS(fmt::format("{:s}", txd.pk))}, + {"blk_height" , tx_blk_height_str}, + {"tx_size" , fmt::format("{:0.4f}", + static_cast(txd.size) / 1024.0)}, + {"tx_fee" , fmt::format("{:0.12f}", XMR_AMOUNT(txd.fee))}, + {"blk_timestamp" , blk_timestamp}, + {"delta_time" , age.first}, + {"outputs_no" , txd.output_pub_keys.size()}, + {"has_payment_id" , txd.payment_id != null_hash}, + {"has_payment_id8" , txd.payment_id8 != null_hash8}, + {"payment_id" , pid_str}, + {"payment_id8" , pid8_str}, + {"tx_prove" , tx_prove} + }; - uint64_t tx_blk_height {0}; + string server_time_str = xmreg::timestamp_to_str(server_timestamp, "%F"); - bool tx_blk_found {false}; + uint64_t output_idx {0}; - try - { - tx_blk_height = core_storage->get_db().get_tx_block_height(tx_hash); - tx_blk_found = true; - } - catch (exception& e) - { - cerr << "Cant get block height: " << tx_hash - << e.what() << endl; - } + // public transaction key is combined with our viewkey + // to create, so called, derived key. + key_derivation derivation; + public_key pub_key = tx_prove ? address.m_view_public_key : txd.pk; - // get block cointaining this tx - block blk; + if (!generate_key_derivation(pub_key, prv_view_key, derivation)) + { + cerr << "Cant get derived key for: " << "\n" + << "pub_tx_key: " << pub_key << " and " + << "prv_view_key" << prv_view_key << endl; - if (tx_blk_found && !mcore->get_block_by_height(tx_blk_height, blk)) - { - cerr << "Cant get block: " << tx_blk_height << endl; - } + return string("Cant get key_derivation"); + } - string tx_blk_height_str {"N/A"}; + mstch::array outputs; - if (tx_blk_found) - { - // calculate difference between tx and server timestamps - age = get_age(server_timestamp, blk.timestamp, FULL_AGE_FORMAT); + uint64_t sum_xmr {0}; - blk_timestamp = xmreg::timestamp_to_str(blk.timestamp); + std::vector money_transfered(tx.vout.size(), 0); - tx_blk_height_str = std::to_string(tx_blk_height); - } + //std::deque mask(tx.vout.size()); - // payments id. both normal and encrypted (payment_id8) - string pid_str = REMOVE_HASH_BRAKETS(fmt::format("{:s}", txd.payment_id)); - string pid8_str = REMOVE_HASH_BRAKETS(fmt::format("{:s}", txd.payment_id8)); - - // initalise page tempate map with basic info about blockchain - mstch::map context { - {"testnet" , testnet}, - {"tx_hash" , tx_hash_str}, - {"xmr_address" , xmr_address_str}, - {"viewkey" , viewkey_str}, - {"tx_pub_key" , REMOVE_HASH_BRAKETS(fmt::format("{:s}", txd.pk))}, - {"blk_height" , tx_blk_height_str}, - {"tx_size" , fmt::format("{:0.4f}", - static_cast(txd.size) / 1024.0)}, - {"tx_fee" , fmt::format("{:0.12f}", XMR_AMOUNT(txd.fee))}, - {"blk_timestamp" , blk_timestamp}, - {"delta_time" , age.first}, - {"outputs_no" , txd.output_pub_keys.size()}, - {"has_payment_id" , txd.payment_id != null_hash}, - {"has_payment_id8" , txd.payment_id8 != null_hash8}, - {"payment_id" , pid_str}, - {"payment_id8" , pid8_str}, - {"tx_prove" , tx_prove} - }; + uint64_t i {0}; - string server_time_str = xmreg::timestamp_to_str(server_timestamp, "%F"); + for (pair& outp: txd.output_pub_keys) + { - uint64_t output_idx {0}; + // get the tx output public key + // that normally would be generated for us, + // if someone had sent us some xmr. + public_key tx_pubkey; - // public transaction key is combined with our viewkey - // to create, so called, derived key. - key_derivation derivation; + derive_public_key(derivation, + output_idx, + address.m_spend_public_key, + tx_pubkey); - public_key pub_key = tx_prove ? address.m_view_public_key : txd.pk; + // check if generated public key matches the current output's key + bool mine_output = (outp.first.key == tx_pubkey); - if (!generate_key_derivation(pub_key, prv_view_key, derivation)) + // if mine output has RingCT, i.e., tx version is 2 + if (mine_output && tx.version == 2) { - cerr << "Cant get derived key for: " << "\n" - << "pub_tx_key: " << pub_key << " and " - << "prv_view_key" << prv_view_key << endl; - - return string("Cant get key_derivation"); - } + // initialize with regular amount + uint64_t rct_amount = money_transfered[i]; - mstch::array outputs; + bool r; - uint64_t sum_xmr {0}; + r = decode_ringct(tx.rct_signatures, + pub_key, + prv_view_key, + i, + tx.rct_signatures.ecdhInfo[i].mask, + rct_amount); - std::vector money_transfered(tx.vout.size(), 0); + if (!r) + { + cerr << "Cant decode ringCT!" << endl; + } - //std::deque mask(tx.vout.size()); + // cointbase txs have amounts in plain sight. + // so use amount from ringct, only for non-coinbase txs + if (!is_coinbase(tx)) + { + outp.second = rct_amount; + money_transfered[i] = rct_amount; + } - uint64_t i {0}; + } - for (pair& outp: txd.output_pub_keys) + if (mine_output) { + sum_xmr += outp.second; + } - // get the tx output public key - // that normally would be generated for us, - // if someone had sent us some xmr. - public_key tx_pubkey; + outputs.push_back(mstch::map { + {"out_pub_key" , REMOVE_HASH_BRAKETS( + fmt::format("{:s}", + outp.first.key))}, + {"amount" , fmt::format("{:0.12f}", + XMR_AMOUNT(outp.second))}, + {"mine_output" , mine_output}, + {"output_idx" , fmt::format("{:02d}", output_idx++)} + }); - derive_public_key(derivation, - output_idx, - address.m_spend_public_key, - tx_pubkey); + ++i; + } - // check if generated public key matches the current output's key - bool mine_output = (outp.first.key == tx_pubkey); + //cout << "outputs.size(): " << outputs.size() << endl; - // if mine output has RingCT, i.e., tx version is 2 - if (mine_output && tx.version == 2) - { - // initialize with regular amount - uint64_t rct_amount = money_transfered[i]; + context["outputs"] = outputs; + context["sum_xmr"] = XMR_AMOUNT(sum_xmr); - bool r; + // read my_outputs.html + string my_outputs_html = xmreg::read(TMPL_MY_OUTPUTS); - r = decode_ringct(tx.rct_signatures, - pub_key, - prv_view_key, - i, - tx.rct_signatures.ecdhInfo[i].mask, - rct_amount); + // add header and footer + string full_page = get_full_page(my_outputs_html); - if (!r) - { - cerr << "Cant decode ringCT!" << endl; - } + add_css_style(context); - // cointbase txs have amounts in plain sight. - // so use amount from ringct, only for non-coinbase txs - if (!is_coinbase(tx)) - { - outp.second = rct_amount; - money_transfered[i] = rct_amount; - } + // render the page + return mstch::render(full_page, context); + } - } + string + show_prove(string tx_hash_str, + string xmr_address_str, + string tx_prv_key_str) + { + return show_my_outputs(tx_hash_str, xmr_address_str, tx_prv_key_str, true); + } - if (mine_output) - { - sum_xmr += outp.second; - } + string + show_rawtx() + { - outputs.push_back(mstch::map { - {"out_pub_key" , REMOVE_HASH_BRAKETS( - fmt::format("{:s}", - outp.first.key))}, - {"amount" , fmt::format("{:0.12f}", - XMR_AMOUNT(outp.second))}, - {"mine_output" , mine_output}, - {"output_idx" , fmt::format("{:02d}", output_idx++)} - }); + // initalise page tempate map with basic info about blockchain + mstch::map context { + {"testnet" , testnet} + }; - ++i; - } + // read rawtx.html + string rawtx_html = xmreg::read(TMPL_MY_RAWTX); - //cout << "outputs.size(): " << outputs.size() << endl; + // add header and footer + string full_page = rawtx_html + xmreg::read(TMPL_FOOTER); - context["outputs"] = outputs; - context["sum_xmr"] = XMR_AMOUNT(sum_xmr); + add_css_style(context); - // read my_outputs.html - string my_outputs_html = xmreg::read(TMPL_MY_OUTPUTS); + // render the page + return mstch::render(full_page, context); + } - // add header and footer - string full_page = get_full_page(my_outputs_html); + string + show_checkrawtx(string raw_tx_data, string action) + { + // remove white characters + boost::trim(raw_tx_data); + boost::erase_all(raw_tx_data, "\r\n"); + boost::erase_all(raw_tx_data, "\n"); - add_css_style(context); + //cout << raw_tx_data << endl; - // render the page - return mstch::render(full_page, context); - } + string decoded_raw_tx_data = epee::string_encoding::base64_decode(raw_tx_data); - string - show_prove(string tx_hash_str, - string xmr_address_str, - string tx_prv_key_str) - { - return show_my_outputs(tx_hash_str, xmr_address_str, tx_prv_key_str, true); - } + //cout << decoded_raw_tx_data << endl; + + const size_t magiclen = strlen(UNSIGNED_TX_PREFIX); + + bool unsigned_tx_given {false}; - string - show_rawtx() + if (strncmp(decoded_raw_tx_data.c_str(), UNSIGNED_TX_PREFIX, magiclen) == 0) { + unsigned_tx_given = true; + cout << "UNSIGNED_TX_PREFIX data given" << endl; + } - // initalise page tempate map with basic info about blockchain - mstch::map context { - {"testnet" , testnet} - }; + // initalize page template context map + mstch::map context { + {"testnet" , testnet}, + {"unsigned_tx_given" , unsigned_tx_given}, + {"have_raw_tx" , true}, + {"txs" , mstch::array{}} + }; - // read rawtx.html - string rawtx_html = xmreg::read(TMPL_MY_RAWTX); + if (unsigned_tx_given) + { + ::tools::wallet2::unsigned_tx_set exported_txs; - // add header and footer - string full_page = rawtx_html + xmreg::read(TMPL_FOOTER); + bool r = serialization::parse_binary(std::string( + decoded_raw_tx_data.c_str() + magiclen, + decoded_raw_tx_data.size() - magiclen), + exported_txs); + if (r) + { + mstch::array& txs = boost::get(context["txs"]); - add_css_style(context); + for (const ::tools::wallet2::tx_construction_data& tx_cd: exported_txs.txes) + { + size_t no_of_sources = tx_cd.sources.size(); - // render the page - return mstch::render(full_page, context); - } + const tx_destination_entry& tx_change = tx_cd.change_dts; - string - show_checkrawtx(string raw_tx_data, string action) - { - // remove white characters - boost::trim(raw_tx_data); - boost::erase_all(raw_tx_data, "\r\n"); - boost::erase_all(raw_tx_data, "\n"); + crypto::hash payment_id = null_hash; + crypto::hash8 payment_id8 = null_hash8; - //cout << raw_tx_data << endl; + get_payment_id(tx_cd.extra, payment_id, payment_id8); - string decoded_raw_tx_data = epee::string_encoding::base64_decode(raw_tx_data); + // payments id. both normal and encrypted (payment_id8) + string pid_str = REMOVE_HASH_BRAKETS(fmt::format("{:s}", payment_id)); + string pid8_str = REMOVE_HASH_BRAKETS(fmt::format("{:s}", payment_id8)); - //cout << decoded_raw_tx_data << endl; - const size_t magiclen = strlen(UNSIGNED_TX_PREFIX); + mstch::map tx_cd_data { + {"no_of_sources" , no_of_sources}, + {"use_rct" , tx_cd.use_rct}, + {"change_amount" , fmt::format("{:0.12f}", XMR_AMOUNT(tx_change.amount))}, + {"has_payment_id" , (payment_id != null_hash)}, + {"has_payment_id8" , (payment_id8 != null_hash8)}, + {"payment_id" , pid_str}, + {"payment_id8" , pid8_str}, + {"dest_sources" , mstch::array{}}, + {"dest_infos" , mstch::array{}}, + }; - bool unsigned_tx_given {false}; + mstch::array& dest_sources = boost::get(tx_cd_data["dest_sources"]); + mstch::array& dest_infos = boost::get(tx_cd_data["dest_infos"]); - if (strncmp(decoded_raw_tx_data.c_str(), UNSIGNED_TX_PREFIX, magiclen) == 0) - { - unsigned_tx_given = true; - cout << "UNSIGNED_TX_PREFIX data given" << endl; - } + for (const tx_destination_entry& a_dest: tx_cd.splitted_dsts) + { + mstch::map dest_info { + {"dest_address" , get_account_address_as_str(testnet, a_dest.addr)}, + {"dest_amount" , fmt::format("{:0.12f}", XMR_AMOUNT(a_dest.amount))} + }; - // initalize page template context map - mstch::map context { - {"testnet" , testnet}, - {"unsigned_tx_given" , unsigned_tx_given}, - {"have_raw_tx" , true}, - {"txs" , mstch::array{}} - }; + dest_infos.push_back(dest_info); + } - if (unsigned_tx_given) - { - ::tools::wallet2::unsigned_tx_set exported_txs; + vector> mixin_timestamp_groups; + vector real_output_indices; - bool r = serialization::parse_binary(std::string( - decoded_raw_tx_data.c_str() + magiclen, - decoded_raw_tx_data.size() - magiclen), - exported_txs); - if (r) - { - mstch::array& txs = boost::get(context["txs"]); + uint64_t sum_outputs_amounts {0}; - for (const ::tools::wallet2::tx_construction_data& tx_cd: exported_txs.txes) + for (size_t i = 0; i < no_of_sources; ++i) { - size_t no_of_sources = tx_cd.sources.size(); - const tx_destination_entry& tx_change = tx_cd.change_dts; + const tx_source_entry& tx_source = tx_cd.sources.at(i); - crypto::hash payment_id = null_hash; - crypto::hash8 payment_id8 = null_hash8; + mstch::map single_dest_source { + {"output_amount" , fmt::format("{:0.12f}", + XMR_AMOUNT(tx_source.amount))}, + {"real_output" , tx_source.real_output}, + {"real_out_tx_key" , pod_to_hex(tx_source.real_out_tx_key)}, + {"real_output_in_tx_index" , tx_source.real_output_in_tx_index}, + {"outputs" , mstch::array{}} + }; - get_payment_id(tx_cd.extra, payment_id, payment_id8); + sum_outputs_amounts += tx_source.amount; - // payments id. both normal and encrypted (payment_id8) - string pid_str = REMOVE_HASH_BRAKETS(fmt::format("{:s}", payment_id)); - string pid8_str = REMOVE_HASH_BRAKETS(fmt::format("{:s}", payment_id8)); + //cout << "tx_source.real_output: " << tx_source.real_output << endl; + //cout << "tx_source.real_out_tx_key: " << tx_source.real_out_tx_key << endl; + //cout << "tx_source.real_output_in_tx_index: " << tx_source.real_output_in_tx_index << endl; + uint64_t index_of_real_output = tx_source.outputs[tx_source.real_output].first; - mstch::map tx_cd_data { - {"no_of_sources" , no_of_sources}, - {"use_rct" , tx_cd.use_rct}, - {"change_amount" , fmt::format("{:0.12f}", XMR_AMOUNT(tx_change.amount))}, - {"has_payment_id" , (payment_id != null_hash)}, - {"has_payment_id8" , (payment_id8 != null_hash8)}, - {"payment_id" , pid_str}, - {"payment_id8" , pid8_str}, - {"dest_sources" , mstch::array{}}, - {"dest_infos" , mstch::array{}}, - }; + tx_out_index real_toi; - mstch::array& dest_sources = boost::get(tx_cd_data["dest_sources"]); - mstch::array& dest_infos = boost::get(tx_cd_data["dest_infos"]); + uint64_t tx_source_amount = (tx_source.rct ? 0 : tx_source.amount); - for (const tx_destination_entry& a_dest: tx_cd.splitted_dsts) + try { - mstch::map dest_info { - {"dest_address" , get_account_address_as_str(testnet, a_dest.addr)}, - {"dest_amount" , fmt::format("{:0.12f}", XMR_AMOUNT(a_dest.amount))} - }; + // get tx of the real output + real_toi = core_storage->get_db() + .get_output_tx_and_index(tx_source_amount, + index_of_real_output); + } + catch (const OUTPUT_DNE& e) + { + + string out_msg = fmt::format( + "Output with amount {:d} and index {:d} does not exist!", + tx_source_amount, index_of_real_output + ); + + cerr << out_msg << endl; - dest_infos.push_back(dest_info); + return string(out_msg); } - vector> mixin_timestamp_groups; - vector real_output_indices; - uint64_t sum_outputs_amounts {0}; - for (size_t i = 0; i < no_of_sources; ++i) + transaction real_source_tx; + + if (!mcore->get_tx(real_toi.first, real_source_tx)) { + cerr << "Cant get tx in blockchain: " << real_toi.first << endl; + return string("Cant get tx: " + pod_to_hex(real_toi.first)); + } + + tx_details real_txd = get_tx_details(real_source_tx); + + real_output_indices.push_back(tx_source.real_output); - const tx_source_entry& tx_source = tx_cd.sources.at(i); + public_key real_out_pub_key = real_txd.output_pub_keys[tx_source.real_output_in_tx_index].first.key; - mstch::map single_dest_source { - {"output_amount" , fmt::format("{:0.12f}", - XMR_AMOUNT(tx_source.amount))}, - {"real_output" , tx_source.real_output}, - {"real_out_tx_key" , pod_to_hex(tx_source.real_out_tx_key)}, - {"real_output_in_tx_index" , tx_source.real_output_in_tx_index}, - {"outputs" , mstch::array{}} - }; + //cout << "real_txd.hash: " << pod_to_hex(real_txd.hash) << endl; + //cout << "real_txd.pk: " << pod_to_hex(real_txd.pk) << endl; + //cout << "real_out_pub_key: " << pod_to_hex(real_out_pub_key) << endl; - sum_outputs_amounts += tx_source.amount; + mstch::array& outputs = boost::get(single_dest_source["outputs"]); - //cout << "tx_source.real_output: " << tx_source.real_output << endl; - //cout << "tx_source.real_out_tx_key: " << tx_source.real_out_tx_key << endl; - //cout << "tx_source.real_output_in_tx_index: " << tx_source.real_output_in_tx_index << endl; + vector mixin_timestamps; - uint64_t index_of_real_output = tx_source.outputs[tx_source.real_output].first; + size_t output_i {0}; - tx_out_index real_toi; + for(const tx_source_entry::output_entry& oe: tx_source.outputs) + { - uint64_t tx_source_amount = (tx_source.rct ? 0 : tx_source.amount); + tx_out_index toi; try { + // get tx of the real output - real_toi = core_storage->get_db() - .get_output_tx_and_index(tx_source_amount, - index_of_real_output); + toi = core_storage->get_db() + .get_output_tx_and_index(tx_source_amount, oe.first); } - catch (const OUTPUT_DNE& e) + catch (OUTPUT_DNE& e) { string out_msg = fmt::format( "Output with amount {:d} and index {:d} does not exist!", - tx_source_amount, index_of_real_output + tx_source_amount, oe.first ); cerr << out_msg << endl; @@ -1433,1628 +1484,1630 @@ namespace xmreg { return string(out_msg); } + transaction tx; - - transaction real_source_tx; - - if (!mcore->get_tx(real_toi.first, real_source_tx)) + if (!mcore->get_tx(toi.first, tx)) { - cerr << "Cant get tx in blockchain: " << real_toi.first << endl; - return string("Cant get tx: " + pod_to_hex(real_toi.first)); + cerr << "Cant get tx in blockchain: " << toi.first + << ". \n Check mempool now" << endl; + // tx is nowhere to be found :-( + return string("Cant get tx: " + pod_to_hex(toi.first)); } - tx_details real_txd = get_tx_details(real_source_tx); + tx_details txd = get_tx_details(tx); - real_output_indices.push_back(tx_source.real_output); + public_key out_pub_key = txd.output_pub_keys[toi.second].first.key; - public_key real_out_pub_key = real_txd.output_pub_keys[tx_source.real_output_in_tx_index].first.key; - //cout << "real_txd.hash: " << pod_to_hex(real_txd.hash) << endl; - //cout << "real_txd.pk: " << pod_to_hex(real_txd.pk) << endl; - //cout << "real_out_pub_key: " << pod_to_hex(real_out_pub_key) << endl; + // get block cointaining this tx + block blk; - mstch::array& outputs = boost::get(single_dest_source["outputs"]); - - vector mixin_timestamps; - - size_t output_i {0}; - - for(const tx_source_entry::output_entry& oe: tx_source.outputs) + if (!mcore->get_block_by_height(txd.blk_height, blk)) { + cerr << "Cant get block: " << txd.blk_height << endl; + return string("Cant get block: " + to_string(txd.blk_height)); + } - tx_out_index toi; + pair age = get_age(server_timestamp, blk.timestamp); - try - { + mstch::map single_output { + {"out_index" , oe.first}, + {"tx_hash" , pod_to_hex(txd.hash)}, + {"out_pub_key" , pod_to_hex(out_pub_key)}, + {"ctkey" , pod_to_hex(oe.second)}, + {"output_age" , age.first}, + {"is_real" , (out_pub_key == real_out_pub_key)} + }; + + single_dest_source.insert({"age_format" , age.second}); - // get tx of the real output - toi = core_storage->get_db() - .get_output_tx_and_index(tx_source_amount, oe.first); - } - catch (OUTPUT_DNE& e) - { + outputs.push_back(single_output); - string out_msg = fmt::format( - "Output with amount {:d} and index {:d} does not exist!", - tx_source_amount, oe.first - ); + mixin_timestamps.push_back(blk.timestamp); - cerr << out_msg << endl; + ++output_i; + } // for(const tx_source_entry::output_entry& oe: tx_source.outputs) - return string(out_msg); - } + dest_sources.push_back(single_dest_source); - transaction tx; + mixin_timestamp_groups.push_back(mixin_timestamps); - if (!mcore->get_tx(toi.first, tx)) - { - cerr << "Cant get tx in blockchain: " << toi.first - << ". \n Check mempool now" << endl; - // tx is nowhere to be found :-( - return string("Cant get tx: " + pod_to_hex(toi.first)); - } + } // for (size_t i = 0; i < no_of_sources; ++i) - tx_details txd = get_tx_details(tx); + tx_cd_data.insert({"sum_outputs_amounts" , + fmt::format("{:0.12f}", XMR_AMOUNT(sum_outputs_amounts))}); - public_key out_pub_key = txd.output_pub_keys[toi.second].first.key; + uint64_t min_mix_timestamp; + uint64_t max_mix_timestamp; - // get block cointaining this tx - block blk; + pair mixins_timescales + = construct_mstch_mixin_timescales( + mixin_timestamp_groups, + min_mix_timestamp, + max_mix_timestamp + ); - if (!mcore->get_block_by_height(txd.blk_height, blk)) - { - cerr << "Cant get block: " << txd.blk_height << endl; - return string("Cant get block: " + to_string(txd.blk_height)); - } + tx_cd_data["timescales"] = mixins_timescales.first; + tx_cd_data["min_mix_time"] = xmreg::timestamp_to_str(min_mix_timestamp); + tx_cd_data["max_mix_time"] = xmreg::timestamp_to_str(max_mix_timestamp); + tx_cd_data["timescales_scale"] = fmt::format("{:0.2f}", + mixins_timescales.second + / 3600.0 / 24.0); // in days - pair age = get_age(server_timestamp, blk.timestamp); + // mark real mixing in the mixins timescale graph + mark_real_mixins_on_timescales(real_output_indices, tx_cd_data); - mstch::map single_output { - {"out_index" , oe.first}, - {"tx_hash" , pod_to_hex(txd.hash)}, - {"out_pub_key" , pod_to_hex(out_pub_key)}, - {"ctkey" , pod_to_hex(oe.second)}, - {"output_age" , age.first}, - {"is_real" , (out_pub_key == real_out_pub_key)} - }; + txs.push_back(tx_cd_data); - single_dest_source.insert({"age_format" , age.second}); + } // for (const ::tools::wallet2::tx_construction_data& tx_cd: exported_txs.txes) + } + else + { + cerr << "deserialization of unsigned tx data NOT successful" << endl; + return string("deserialization of unsigned tx data NOT successful. " + "Maybe its not base64 encoded?"); + } + } // if (unsigned_tx_given) + else + { + // if raw data is not unsigined tx, then assume it is signed tx - outputs.push_back(single_output); + const size_t magiclen = strlen(SIGNED_TX_PREFIX); - mixin_timestamps.push_back(blk.timestamp); + if (strncmp(decoded_raw_tx_data.c_str(), SIGNED_TX_PREFIX, magiclen) != 0) + { + cout << "The data is neither unsigned nor signed tx!" << endl; + return string( "The data is neither unsigned nor signed tx!"); + } - ++output_i; - } // for(const tx_source_entry::output_entry& oe: tx_source.outputs) + ::tools::wallet2::signed_tx_set signed_txs; - dest_sources.push_back(single_dest_source); + bool r = serialization::parse_binary(std::string( + decoded_raw_tx_data.c_str() + magiclen, + decoded_raw_tx_data.size() - magiclen), + signed_txs); - mixin_timestamp_groups.push_back(mixin_timestamps); + if (!r) + { + cerr << "deserialization of signed tx data NOT successful" << endl; + return string("deserialization of signed tx data NOT successful. " + "Maybe its not base64 encoded?"); + } - } // for (size_t i = 0; i < no_of_sources; ++i) + std::vector ptxs = signed_txs.ptx; - tx_cd_data.insert({"sum_outputs_amounts" , - fmt::format("{:0.12f}", XMR_AMOUNT(sum_outputs_amounts))}); + context.insert({"txs", mstch::array{}}); + for (tools::wallet2::pending_tx& ptx: ptxs) + { + mstch::map tx_context = construct_tx_context(ptx.tx); - uint64_t min_mix_timestamp; - uint64_t max_mix_timestamp; + if (boost::get(tx_context["has_error"])) + { + return boost::get(tx_context["error_msg"]); + } - pair mixins_timescales - = construct_mstch_mixin_timescales( - mixin_timestamp_groups, - min_mix_timestamp, - max_mix_timestamp - ); + tx_context["tx_prv_key"] = fmt::format("{:s}", ptx.tx_key); - tx_cd_data["timescales"] = mixins_timescales.first; - tx_cd_data["min_mix_time"] = xmreg::timestamp_to_str(min_mix_timestamp); - tx_cd_data["max_mix_time"] = xmreg::timestamp_to_str(max_mix_timestamp); - tx_cd_data["timescales_scale"] = fmt::format("{:0.2f}", - mixins_timescales.second - / 3600.0 / 24.0); // in days + mstch::array destination_addresses; + vector real_ammounts; + uint64_t outputs_xmr_sum {0}; - // mark real mixing in the mixins timescale graph - mark_real_mixins_on_timescales(real_output_indices, tx_cd_data); + // destiantion address for this tx + for (tx_destination_entry& a_dest: ptx.construction_data.splitted_dsts) + { + //stealth_address_amount.insert({dest.addr, dest.amount}); + cout << get_account_address_as_str(testnet, a_dest.addr) << endl; + //address_amounts.push_back(a_dest.amount); + + destination_addresses.push_back( + mstch::map { + {"dest_address" , get_account_address_as_str(testnet, a_dest.addr)}, + {"dest_amount" , fmt::format("{:0.12f}", XMR_AMOUNT(a_dest.amount))}, + {"is_this_change" , false} + } + ); - txs.push_back(tx_cd_data); + outputs_xmr_sum += a_dest.amount; - } // for (const ::tools::wallet2::tx_construction_data& tx_cd: exported_txs.txes) + real_ammounts.push_back(a_dest.amount); } - else + + // get change address and amount info + if (ptx.construction_data.change_dts.amount > 0) { - cerr << "deserialization of unsigned tx data NOT successful" << endl; - return string("deserialization of unsigned tx data NOT successful. " - "Maybe its not base64 encoded?"); - } - } // if (unsigned_tx_given) - else - { - // if raw data is not unsigined tx, then assume it is signed tx + destination_addresses.push_back( + mstch::map { + {"dest_address" , get_account_address_as_str(testnet, ptx.construction_data.change_dts.addr)}, + {"dest_amount" , fmt::format("{:0.12f}", XMR_AMOUNT(ptx.construction_data.change_dts.amount))}, + {"is_this_change" , true} + } + ); - const size_t magiclen = strlen(SIGNED_TX_PREFIX); + real_ammounts.push_back(ptx.construction_data.change_dts.amount); + }; - if (strncmp(decoded_raw_tx_data.c_str(), SIGNED_TX_PREFIX, magiclen) != 0) - { - cout << "The data is neither unsigned nor signed tx!" << endl; - return string( "The data is neither unsigned nor signed tx!"); - } + tx_context["outputs_xmr_sum"] = fmt::format("{:0.12f}", XMR_AMOUNT(outputs_xmr_sum)); - ::tools::wallet2::signed_tx_set signed_txs; + tx_context.insert({"dest_infos", destination_addresses}); - bool r = serialization::parse_binary(std::string( - decoded_raw_tx_data.c_str() + magiclen, - decoded_raw_tx_data.size() - magiclen), - signed_txs); + // get reference to inputs array created of the tx + mstch::array& outputs = boost::get(tx_context["outputs"]); - if (!r) + // show real output amount for ringct outputs. + // otherwise its only 0.000000000 + for (size_t i = 0; i < outputs.size(); ++i) { - cerr << "deserialization of signed tx data NOT successful" << endl; - return string("deserialization of signed tx data NOT successful. " - "Maybe its not base64 encoded?"); - } + mstch::map& output_map = boost::get(outputs.at(i)); - std::vector ptxs = signed_txs.ptx; + string& out_amount_str = boost::get(output_map["amount"]); - context.insert({"txs", mstch::array{}}); + //cout << boost::get(output_map["out_pub_key"]) + // <<", " << out_amount_str << endl; - for (tools::wallet2::pending_tx& ptx: ptxs) - { - mstch::map tx_context = construct_tx_context(ptx.tx); + uint64_t output_amount; - if (boost::get(tx_context["has_error"])) + if (parse_amount(output_amount, out_amount_str)) { - return boost::get(tx_context["error_msg"]); + if (output_amount == 0) + { + out_amount_str = fmt::format("{:0.12f}", XMR_AMOUNT(real_ammounts.at(i))); + } } + } + + // get public keys of real outputs + vector real_output_pub_keys; + vector real_output_indices; + vector real_amounts; + + uint64_t inputs_xmr_sum {0}; + + for (const tx_source_entry& tx_source: ptx.construction_data.sources) + { + transaction real_source_tx; + + uint64_t index_of_real_output = tx_source.outputs[tx_source.real_output].first; - tx_context["tx_prv_key"] = fmt::format("{:s}", ptx.tx_key); + uint64_t tx_source_amount = (tx_source.rct ? 0 : tx_source.amount); - mstch::array destination_addresses; - vector real_ammounts; - uint64_t outputs_xmr_sum {0}; + tx_out_index real_toi; - // destiantion address for this tx - for (tx_destination_entry& a_dest: ptx.construction_data.splitted_dsts) + try + { + // get tx of the real output + real_toi = core_storage->get_db() + .get_output_tx_and_index(tx_source_amount, index_of_real_output); + } + catch (const OUTPUT_DNE& e) { - //stealth_address_amount.insert({dest.addr, dest.amount}); - cout << get_account_address_as_str(testnet, a_dest.addr) << endl; - //address_amounts.push_back(a_dest.amount); - - destination_addresses.push_back( - mstch::map { - {"dest_address" , get_account_address_as_str(testnet, a_dest.addr)}, - {"dest_amount" , fmt::format("{:0.12f}", XMR_AMOUNT(a_dest.amount))}, - {"is_this_change" , false} - } + + string out_msg = fmt::format( + "Output with amount {:d} and index {:d} does not exist!", + tx_source_amount, index_of_real_output ); - outputs_xmr_sum += a_dest.amount; + cerr << out_msg << endl; - real_ammounts.push_back(a_dest.amount); + return string(out_msg); } - // get change address and amount info - if (ptx.construction_data.change_dts.amount > 0) + if (!mcore->get_tx(real_toi.first, real_source_tx)) { - destination_addresses.push_back( - mstch::map { - {"dest_address" , get_account_address_as_str(testnet, ptx.construction_data.change_dts.addr)}, - {"dest_amount" , fmt::format("{:0.12f}", XMR_AMOUNT(ptx.construction_data.change_dts.amount))}, - {"is_this_change" , true} - } - ); + cerr << "Cant get tx in blockchain: " << real_toi.first << endl; + return string("Cant get tx: " + pod_to_hex(real_toi.first)); + } - real_ammounts.push_back(ptx.construction_data.change_dts.amount); - }; + tx_details real_txd = get_tx_details(real_source_tx); - tx_context["outputs_xmr_sum"] = fmt::format("{:0.12f}", XMR_AMOUNT(outputs_xmr_sum)); + public_key real_out_pub_key + = real_txd.output_pub_keys[tx_source.real_output_in_tx_index].first.key; - tx_context.insert({"dest_infos", destination_addresses}); + real_output_pub_keys.push_back( + REMOVE_HASH_BRAKETS(fmt::format("{:s}",real_out_pub_key)) + ); - // get reference to inputs array created of the tx - mstch::array& outputs = boost::get(tx_context["outputs"]); + real_output_indices.push_back(tx_source.real_output); + real_amounts.push_back(tx_source.amount); - // show real output amount for ringct outputs. - // otherwise its only 0.000000000 - for (size_t i = 0; i < outputs.size(); ++i) - { - mstch::map& output_map = boost::get(outputs.at(i)); + inputs_xmr_sum += tx_source.amount; + } - string& out_amount_str = boost::get(output_map["amount"]); + // mark that we have signed tx data for use in mstch + tx_context["have_raw_tx"] = true; - //cout << boost::get(output_map["out_pub_key"]) - // <<", " << out_amount_str << endl; + // provide total mount of inputs xmr + tx_context["inputs_xmr_sum"] = fmt::format("{:0.12f}", XMR_AMOUNT(inputs_xmr_sum)); - uint64_t output_amount; + // get reference to inputs array created of the tx + mstch::array& inputs = boost::get(tx_context["inputs"]); - if (parse_amount(output_amount, out_amount_str)) - { - if (output_amount == 0) - { - out_amount_str = fmt::format("{:0.12f}", XMR_AMOUNT(real_ammounts.at(i))); - } - } - } + uint64_t input_idx {0}; - // get public keys of real outputs - vector real_output_pub_keys; - vector real_output_indices; - vector real_amounts; + // mark which mixin is real in each input's mstch context + for (mstch::node& input_node: inputs) + { + + mstch::map& input_map = boost::get(input_node); - uint64_t inputs_xmr_sum {0}; + // show input amount + string& amount = boost::get( + boost::get(input_node)["amount"] + ); + + amount = fmt::format("{:0.12f}", XMR_AMOUNT(real_amounts.at(input_idx))); + + // check if key images are spend or not + + string& in_key_img_str = boost::get( + boost::get(input_node)["in_key_img"] + ); + + key_image key_imgage; - for (const tx_source_entry& tx_source: ptx.construction_data.sources) + if (epee::string_tools::hex_to_pod(in_key_img_str, key_imgage)) { - transaction real_source_tx; + input_map["already_spent"] = core_storage->get_db().has_key_image(key_imgage); + } - uint64_t index_of_real_output = tx_source.outputs[tx_source.real_output].first; + // mark real mixings - uint64_t tx_source_amount = (tx_source.rct ? 0 : tx_source.amount); + mstch::array& mixins = boost::get( + boost::get(input_node)["mixins"] + ); - tx_out_index real_toi; + for (mstch::node& mixin_node: mixins) + { + mstch::map& mixin = boost::get(mixin_node); - try + string mix_pub_key_str = boost::get(mixin["mix_pub_key"]); + + //cout << mix_pub_key_str << endl; + + if (std::find( + real_output_pub_keys.begin(), + real_output_pub_keys.end(), + mix_pub_key_str) != real_output_pub_keys.end()) { - // get tx of the real output - real_toi = core_storage->get_db() - .get_output_tx_and_index(tx_source_amount, index_of_real_output); - } - catch (const OUTPUT_DNE& e) - { - - string out_msg = fmt::format( - "Output with amount {:d} and index {:d} does not exist!", - tx_source_amount, index_of_real_output - ); - - cerr << out_msg << endl; - - return string(out_msg); + mixin["mix_is_it_real"] = true; } - - if (!mcore->get_tx(real_toi.first, real_source_tx)) - { - cerr << "Cant get tx in blockchain: " << real_toi.first << endl; - return string("Cant get tx: " + pod_to_hex(real_toi.first)); - } - - tx_details real_txd = get_tx_details(real_source_tx); - - public_key real_out_pub_key - = real_txd.output_pub_keys[tx_source.real_output_in_tx_index].first.key; - - real_output_pub_keys.push_back( - REMOVE_HASH_BRAKETS(fmt::format("{:s}",real_out_pub_key)) - ); - - real_output_indices.push_back(tx_source.real_output); - real_amounts.push_back(tx_source.amount); - - inputs_xmr_sum += tx_source.amount; } - // mark that we have signed tx data for use in mstch - tx_context["have_raw_tx"] = true; - - // provide total mount of inputs xmr - tx_context["inputs_xmr_sum"] = fmt::format("{:0.12f}", XMR_AMOUNT(inputs_xmr_sum)); + ++input_idx; + } - // get reference to inputs array created of the tx - mstch::array& inputs = boost::get(tx_context["inputs"]); + // mark real mixing in the mixins timescale graph + mark_real_mixins_on_timescales(real_output_indices, tx_context); - uint64_t input_idx {0}; + boost::get(context["txs"]).push_back(tx_context); + } - // mark which mixin is real in each input's mstch context - for (mstch::node& input_node: inputs) - { + } - mstch::map& input_map = boost::get(input_node); + map partials { + {"tx_details", xmreg::read(string(TMPL_PARIALS_DIR) + "/tx_details.html")}, + }; - // show input amount - string& amount = boost::get( - boost::get(input_node)["amount"] - ); + // read checkrawtx.html + string checkrawtx_html = xmreg::read(TMPL_MY_CHECKRAWTX); - amount = fmt::format("{:0.12f}", XMR_AMOUNT(real_amounts.at(input_idx))); + // add header and footer + string full_page = checkrawtx_html + xmreg::read(TMPL_FOOTER); - // check if key images are spend or not + add_css_style(context); - string& in_key_img_str = boost::get( - boost::get(input_node)["in_key_img"] - ); + // render the page + return mstch::render(full_page, context, partials); + } - key_image key_imgage; + string + show_pushrawtx(string raw_tx_data, string action) + { + // remove white characters + boost::trim(raw_tx_data); + boost::erase_all(raw_tx_data, "\r\n"); + boost::erase_all(raw_tx_data, "\n"); + + string decoded_raw_tx_data = epee::string_encoding::base64_decode(raw_tx_data); + + const size_t magiclen = strlen(SIGNED_TX_PREFIX); + + // initalize page template context map + mstch::map context { + {"testnet" , testnet}, + {"have_raw_tx" , true}, + {"has_error" , false}, + {"error_msg" , string {}}, + {"txs" , mstch::array{}} + }; - if (epee::string_tools::hex_to_pod(in_key_img_str, key_imgage)) - { - input_map["already_spent"] = core_storage->get_db().has_key_image(key_imgage); - } + // read pushrawtx.html + string pushrawtx_html = xmreg::read(TMPL_MY_PUSHRAWTX); - // mark real mixings + // add header and footer + string full_page = pushrawtx_html + xmreg::read(TMPL_FOOTER); - mstch::array& mixins = boost::get( - boost::get(input_node)["mixins"] - ); + add_css_style(context); - for (mstch::node& mixin_node: mixins) - { - mstch::map& mixin = boost::get(mixin_node); + if (strncmp(decoded_raw_tx_data.c_str(), SIGNED_TX_PREFIX, magiclen) != 0) + { + string error_msg = fmt::format("The data does not appear to be signed raw tx!"); - string mix_pub_key_str = boost::get(mixin["mix_pub_key"]); + context["has_error"] = true; + context["error_msg"] = error_msg; - //cout << mix_pub_key_str << endl; + return mstch::render(full_page, context); + } - if (std::find( - real_output_pub_keys.begin(), - real_output_pub_keys.end(), - mix_pub_key_str) != real_output_pub_keys.end()) - { - mixin["mix_is_it_real"] = true; - } - } + if (!testnet) + { + string error_msg = fmt::format("Pushing disabled for mainet network!"); - ++input_idx; - } + context["has_error"] = true; + context["error_msg"] = error_msg; - // mark real mixing in the mixins timescale graph - mark_real_mixins_on_timescales(real_output_indices, tx_context); + return mstch::render(full_page, context); + } - boost::get(context["txs"]).push_back(tx_context); - } + ::tools::wallet2::signed_tx_set signed_txs; - } + bool r = serialization::parse_binary(std::string( + decoded_raw_tx_data.c_str() + magiclen, + decoded_raw_tx_data.size() - magiclen), + signed_txs); - map partials { - {"tx_details", xmreg::read(string(TMPL_PARIALS_DIR) + "/tx_details.html")}, - }; + if (!r) + { + string error_msg = fmt::format("Deserialization of signed tx data NOT successful! " + "Maybe its not base64 encoded?"); - // read checkrawtx.html - string checkrawtx_html = xmreg::read(TMPL_MY_CHECKRAWTX); + context["has_error"] = true; + context["error_msg"] = error_msg; - // add header and footer - string full_page = checkrawtx_html + xmreg::read(TMPL_FOOTER); + return mstch::render(full_page, context); + } - add_css_style(context); + mstch::array& txs = boost::get(context["txs"]); - // render the page - return mstch::render(full_page, context, partials); - } + std::vector ptx_vector = signed_txs.ptx; - string - show_pushrawtx(string raw_tx_data, string action) + // actually commit the transactions + while (!ptx_vector.empty()) { - // remove white characters - boost::trim(raw_tx_data); - boost::erase_all(raw_tx_data, "\r\n"); - boost::erase_all(raw_tx_data, "\n"); + tools::wallet2::pending_tx& ptx = ptx_vector.back(); - string decoded_raw_tx_data = epee::string_encoding::base64_decode(raw_tx_data); + tx_details txd = get_tx_details(ptx.tx); - const size_t magiclen = strlen(SIGNED_TX_PREFIX); + string tx_hash_str = REMOVE_HASH_BRAKETS(fmt::format("{:s}", txd.hash)); - // initalize page template context map - mstch::map context { - {"testnet" , testnet}, - {"have_raw_tx" , true}, - {"has_error" , false}, - {"error_msg" , string {}}, - {"txs" , mstch::array{}} + mstch::map tx_cd_data { + {"tx_hash" , tx_hash_str} }; - // read pushrawtx.html - string pushrawtx_html = xmreg::read(TMPL_MY_PUSHRAWTX); - - // add header and footer - string full_page = pushrawtx_html + xmreg::read(TMPL_FOOTER); - - add_css_style(context); + // check in mempool already contains tx to be submited + vector> found_mempool_txs + = search_mempool(txd.hash); - if (strncmp(decoded_raw_tx_data.c_str(), SIGNED_TX_PREFIX, magiclen) != 0) + if (!found_mempool_txs.empty()) { - string error_msg = fmt::format("The data does not appear to be signed raw tx!"); + string error_msg = fmt::format("Tx already exist in the mempool: {:s}\n", + tx_hash_str); context["has_error"] = true; context["error_msg"] = error_msg; - return mstch::render(full_page, context); + break; } - if (!testnet) + // check if tx to be submited already exists in the blockchain + if (core_storage->have_tx(txd.hash)) { - string error_msg = fmt::format("Pushing disabled for mainet network!"); + string error_msg = fmt::format("Tx already exist in the blockchain: {:s}\n", + tx_hash_str); context["has_error"] = true; context["error_msg"] = error_msg; - return mstch::render(full_page, context); + break; } - ::tools::wallet2::signed_tx_set signed_txs; + // check if any key images of the tx to be submited are already spend + vector key_images_spent; - bool r = serialization::parse_binary(std::string( - decoded_raw_tx_data.c_str() + magiclen, - decoded_raw_tx_data.size() - magiclen), - signed_txs); + for (const txin_to_key& tx_in: txd.input_key_imgs) + { + if (core_storage->have_tx_keyimg_as_spent(tx_in.k_image)) + key_images_spent.push_back(tx_in.k_image); + } - if (!r) + if (!key_images_spent.empty()) { - string error_msg = fmt::format("Deserialization of signed tx data NOT successful! " - "Maybe its not base64 encoded?"); + string error_msg = fmt::format("Tx with hash {:s} has already spent inputs\n", + tx_hash_str); + + for (key_image& k_img: key_images_spent) + { + error_msg += REMOVE_HASH_BRAKETS(fmt::format("{:s}", k_img)); + error_msg += "
"; + } context["has_error"] = true; context["error_msg"] = error_msg; - return mstch::render(full_page, context); + break; } - mstch::array& txs = boost::get(context["txs"]); - - std::vector ptx_vector = signed_txs.ptx; + string rpc_error_msg; - // actually commit the transactions - while (!ptx_vector.empty()) + if (!rpc.commit_tx(ptx, rpc_error_msg)) { - tools::wallet2::pending_tx& ptx = ptx_vector.back(); + string error_msg = fmt::format( + "Submitting signed tx {:s} to daemon failed: {:s}\n", + tx_hash_str, rpc_error_msg); - tx_details txd = get_tx_details(ptx.tx); + context["has_error"] = true; + context["error_msg"] = error_msg; - string tx_hash_str = REMOVE_HASH_BRAKETS(fmt::format("{:s}", txd.hash)); + break; + } - mstch::map tx_cd_data { - {"tx_hash" , tx_hash_str} - }; + txs.push_back(tx_cd_data); - // check in mempool already contains tx to be submited - vector> found_mempool_txs - = search_mempool(txd.hash); + // if no exception, remove element from vector + ptx_vector.pop_back(); + } - if (!found_mempool_txs.empty()) - { - string error_msg = fmt::format("Tx already exist in the mempool: {:s}\n", - tx_hash_str); + // render the page + return mstch::render(full_page, context); + } - context["has_error"] = true; - context["error_msg"] = error_msg; - break; - } + string + show_rawkeyimgs() + { + // initalize page template context map + mstch::map context { + {"testnet" , testnet} + }; - // check if tx to be submited already exists in the blockchain - if (core_storage->have_tx(txd.hash)) - { - string error_msg = fmt::format("Tx already exist in the blockchain: {:s}\n", - tx_hash_str); + // read checkrawtx.html + string rawkeyimgs_html = xmreg::read(TMPL_MY_RAWKEYIMGS); - context["has_error"] = true; - context["error_msg"] = error_msg; + // add header and footer + string full_page = rawkeyimgs_html + xmreg::read(TMPL_FOOTER); - break; - } + add_css_style(context); - // check if any key images of the tx to be submited are already spend - vector key_images_spent; + // render the page + return mstch::render(full_page, context); + } - for (const txin_to_key& tx_in: txd.input_key_imgs) - { - if (core_storage->have_tx_keyimg_as_spent(tx_in.k_image)) - key_images_spent.push_back(tx_in.k_image); - } + string + show_checkrawkeyimgs(string raw_data, string viewkey_str) + { + // remove white characters + boost::trim(raw_data); + boost::erase_all(raw_data, "\r\n"); + boost::erase_all(raw_data, "\n"); - if (!key_images_spent.empty()) - { - string error_msg = fmt::format("Tx with hash {:s} has already spent inputs\n", - tx_hash_str); + // remove white characters + boost::trim(viewkey_str); - for (key_image& k_img: key_images_spent) - { - error_msg += REMOVE_HASH_BRAKETS(fmt::format("{:s}", k_img)); - error_msg += "
"; - } + string decoded_raw_data = epee::string_encoding::base64_decode(raw_data); + secret_key prv_view_key; - context["has_error"] = true; - context["error_msg"] = error_msg; + if (!xmreg::parse_str_secret_key(viewkey_str, prv_view_key)) + { + cerr << "Cant parse the private key: " << viewkey_str << endl; + return string("Cant parse private key: " + viewkey_str); + } - break; - } + const size_t magiclen = strlen(KEY_IMAGE_EXPORT_FILE_MAGIC); - string rpc_error_msg; + if (!strncmp(decoded_raw_data.c_str(), KEY_IMAGE_EXPORT_FILE_MAGIC, magiclen) == 0) + { + cout << "This does not seem to be key image export data" << endl; + return string {"This does not seem to be key image export data"}; + } - if (!rpc.commit_tx(ptx, rpc_error_msg)) - { - string error_msg = fmt::format( - "Submitting signed tx {:s} to daemon failed: {:s}\n", - tx_hash_str, rpc_error_msg); + // decrypt key images data using private view key + decoded_raw_data = xmreg::decrypt( + std::string(decoded_raw_data, magiclen), + prv_view_key, true); - context["has_error"] = true; - context["error_msg"] = error_msg; + if (decoded_raw_data.empty()) + { + return string {"Failed to authenticate key images data. " + "Maybe wrong viewkey was porvided?"}; + } - break; - } + // header is public spend and keys + const size_t header_lenght = 2 * sizeof(crypto::public_key); + const size_t key_img_size = sizeof(crypto::key_image); + const size_t record_lenght = key_img_size + sizeof(crypto::signature); + const size_t chacha_length = sizeof(crypto::chacha8_key); + + if (decoded_raw_data.size() < header_lenght) + { + cerr << "Bad data size from submitted key images raw data" << endl; + return string {"Bad data size from submitted key images raw data"}; + } - txs.push_back(tx_cd_data); + // get xmr address stored in this key image file + const account_public_address* xmr_address = + reinterpret_cast( + decoded_raw_data.data()); + + // initalize page template context map + mstch::map context { + {"testnet" , testnet}, + {"address" , REMOVE_HASH_BRAKETS(xmreg::print_address(*xmr_address, testnet))}, + {"viewkey" , REMOVE_HASH_BRAKETS(fmt::format("{:s}", prv_view_key))}, + {"has_total_xmr" , false}, + {"total_xmr" , string{}}, + {"key_imgs" , mstch::array{}} + }; - // if no exception, remove element from vector - ptx_vector.pop_back(); - } + unique_ptr mylmdb; - // render the page - return mstch::render(full_page, context); + if (bf::is_directory(lmdb2_path)) + { + mylmdb = make_unique(lmdb2_path); + } + else + { + cout << "Custom lmdb database seem does not exist at: " << lmdb2_path << endl; } - string - show_rawkeyimgs() - { - // initalize page template context map - mstch::map context { - {"testnet" , testnet} - }; + size_t no_key_images = (decoded_raw_data.size() - header_lenght) / record_lenght; - // read checkrawtx.html - string rawkeyimgs_html = xmreg::read(TMPL_MY_RAWKEYIMGS); + //vector> signed_key_images; - // add header and footer - string full_page = rawkeyimgs_html + xmreg::read(TMPL_FOOTER); + mstch::array& key_imgs_ctx = boost::get(context["key_imgs"]); - add_css_style(context); + uint64_t total_xmr {0}; - // render the page - return mstch::render(full_page, context); - } - string - show_checkrawkeyimgs(string raw_data, string viewkey_str) + for (size_t n = 0; n < no_key_images; ++n) { - // remove white characters - boost::trim(raw_data); - boost::erase_all(raw_data, "\r\n"); - boost::erase_all(raw_data, "\n"); + const char* record_ptr = decoded_raw_data.data() + header_lenght + n * record_lenght; - // remove white characters - boost::trim(viewkey_str); + crypto::key_image key_image + = *reinterpret_cast(record_ptr); - string decoded_raw_data = epee::string_encoding::base64_decode(raw_data); - secret_key prv_view_key; + crypto::signature signature + = *reinterpret_cast(record_ptr + key_img_size); - if (!xmreg::parse_str_secret_key(viewkey_str, prv_view_key)) - { - cerr << "Cant parse the private key: " << viewkey_str << endl; - return string("Cant parse private key: " + viewkey_str); - } - const size_t magiclen = strlen(KEY_IMAGE_EXPORT_FILE_MAGIC); + vector found_tx_hashes; - if (!strncmp(decoded_raw_data.c_str(), KEY_IMAGE_EXPORT_FILE_MAGIC, magiclen) == 0) + if (mylmdb) { - cout << "This does not seem to be key image export data" << endl; - return string {"This does not seem to be key image export data"}; + mylmdb->search(epee::string_tools::pod_to_hex(key_image), + found_tx_hashes, "key_images"); } - // decrypt key images data using private view key - decoded_raw_data = xmreg::decrypt( - std::string(decoded_raw_data, magiclen), - prv_view_key, true); - - if (decoded_raw_data.empty()) - { - return string {"Failed to authenticate key images data. " - "Maybe wrong viewkey was porvided?"}; - } + mstch::map key_img_info { + {"key_no" , fmt::format("{:03d}", n)}, + {"key_image" , REMOVE_HASH_BRAKETS(fmt::format("{:s}", key_image))}, + {"signature" , fmt::format("{:s}", signature)}, + {"address" , xmreg::print_address(*xmr_address, testnet)}, + {"amount" , string{}}, + {"is_spent" , core_storage->have_tx_keyimg_as_spent(key_image)}, + {"tx_hash_found" , !found_tx_hashes.empty()}, + {"tx_hash" , string{}} + }; - // header is public spend and keys - const size_t header_lenght = 2 * sizeof(crypto::public_key); - const size_t key_img_size = sizeof(crypto::key_image); - const size_t record_lenght = key_img_size + sizeof(crypto::signature); - const size_t chacha_length = sizeof(crypto::chacha8_key); - if (decoded_raw_data.size() < header_lenght) + if (!found_tx_hashes.empty()) { - cerr << "Bad data size from submitted key images raw data" << endl; - return string {"Bad data size from submitted key images raw data"}; - } - // get xmr address stored in this key image file - const account_public_address* xmr_address = - reinterpret_cast( - decoded_raw_data.data()); - - // initalize page template context map - mstch::map context { - {"testnet" , testnet}, - {"address" , REMOVE_HASH_BRAKETS(xmreg::print_address(*xmr_address, testnet))}, - {"viewkey" , REMOVE_HASH_BRAKETS(fmt::format("{:s}", prv_view_key))}, - {"has_total_xmr" , false}, - {"total_xmr" , string{}}, - {"key_imgs" , mstch::array{}} - }; + string tx_hash_str = found_tx_hashes.at(0); - unique_ptr mylmdb; + key_img_info["tx_hash"] = tx_hash_str; + key_img_info["timestamp"] = ""; - if (bf::is_directory(lmdb2_path)) - { - mylmdb = make_unique(lmdb2_path); - } - else - { - cout << "Custom lmdb database seem does not exist at: " << lmdb2_path << endl; - } + transaction tx; + if (mcore->get_tx(tx_hash_str, tx)) + { + crypto::hash tx_hash; - size_t no_key_images = (decoded_raw_data.size() - header_lenght) / record_lenght; + epee::string_tools::hex_to_pod(tx_hash_str, tx_hash); - //vector> signed_key_images; + // get timestamp of the tx's block + uint64_t blk_height = core_storage + ->get_db().get_tx_block_height(tx_hash); - mstch::array& key_imgs_ctx = boost::get(context["key_imgs"]); + uint64_t blk_timestamp = core_storage + ->get_db().get_block_timestamp(blk_height); - uint64_t total_xmr {0}; + vector tx_key_imgs = get_key_images(tx); + const vector::const_iterator it = find_if(tx_key_imgs.begin(), tx_key_imgs.end(), [&](txin_to_key tx_in) + { + return tx_in.k_image == key_image; + }); - for (size_t n = 0; n < no_key_images; ++n) - { - const char* record_ptr = decoded_raw_data.data() + header_lenght + n * record_lenght; + if (it != tx_key_imgs.end()) + { + key_img_info["amount"] = fmt::format("{:0.12f}", XMR_AMOUNT((*it).amount)); + total_xmr += (*it).amount; + } - crypto::key_image key_image - = *reinterpret_cast(record_ptr); + key_img_info["timestamp"] = xmreg::timestamp_to_str(blk_timestamp); + } + } - crypto::signature signature - = *reinterpret_cast(record_ptr + key_img_size); + key_imgs_ctx.push_back(key_img_info); + //signed_key_images.push_back(make_pair(key_image, signature)); + } - vector found_tx_hashes; + if (total_xmr > 0) + { + context["has_total_xmr"] = true; + context["total_xmr"] = fmt::format("{:0.12f}", XMR_AMOUNT(total_xmr)); + } - if (mylmdb) - { - mylmdb->search(epee::string_tools::pod_to_hex(key_image), - found_tx_hashes, "key_images"); - } + string checkrawkeyimgs_html = xmreg::read(TMPL_MY_CHECKRAWKEYIMGS); - mstch::map key_img_info { - {"key_no" , fmt::format("{:03d}", n)}, - {"key_image" , REMOVE_HASH_BRAKETS(fmt::format("{:s}", key_image))}, - {"signature" , fmt::format("{:s}", signature)}, - {"address" , xmreg::print_address(*xmr_address, testnet)}, - {"amount" , string{}}, - {"is_spent" , core_storage->have_tx_keyimg_as_spent(key_image)}, - {"tx_hash_found" , !found_tx_hashes.empty()}, - {"tx_hash" , string{}} - }; + // add footer + string full_page = checkrawkeyimgs_html + xmreg::read(TMPL_FOOTER); + add_css_style(context); - if (!found_tx_hashes.empty()) - { + // render the page + return mstch::render(full_page, context); + } - string tx_hash_str = found_tx_hashes.at(0); - key_img_info["tx_hash"] = tx_hash_str; - key_img_info["timestamp"] = ""; + string + search(string search_text) + { - transaction tx; + // remove white characters + boost::trim(search_text); - if (mcore->get_tx(tx_hash_str, tx)) - { - crypto::hash tx_hash; + string default_txt {"No such thing found: " + search_text}; - epee::string_tools::hex_to_pod(tx_hash_str, tx_hash); + string result_html {default_txt}; - // get timestamp of the tx's block - uint64_t blk_height = core_storage - ->get_db().get_tx_block_height(tx_hash); + // check first if we look for output with given global index + // such search start with "goi_", e.g., "goi_543" + bool search_for_global_output_idx = (search_text.substr(0, 4) == "goi_"); - uint64_t blk_timestamp = core_storage - ->get_db().get_block_timestamp(blk_height); + // check if we look for output with amout index and amount + // such search start with "aoi_", e.g., "aoi_444-23.00" + bool search_for_amount_output_idx = (search_text.substr(0, 4) == "aoi_"); - vector tx_key_imgs = get_key_images(tx); + // first check if searching for block of given height + if (search_text.size() < 12 && + (search_for_global_output_idx == false + ||search_for_amount_output_idx == false)) + { + uint64_t blk_height; - const vector::const_iterator it = find_if(tx_key_imgs.begin(), tx_key_imgs.end(), [&](txin_to_key tx_in) - { - return tx_in.k_image == key_image; - }); + try + { + blk_height = boost::lexical_cast(search_text); - if (it != tx_key_imgs.end()) - { - key_img_info["amount"] = fmt::format("{:0.12f}", XMR_AMOUNT((*it).amount)); - total_xmr += (*it).amount; - } + result_html = show_block(blk_height); - key_img_info["timestamp"] = xmreg::timestamp_to_str(blk_timestamp); - } + // nasty check if output is "Cant get" as a sign of + // a not found tx. Later need to think of something better. + if (result_html.find("Cant get") == string::npos) + { + return result_html; } - key_imgs_ctx.push_back(key_img_info); + } + catch(boost::bad_lexical_cast &e) + { + return result_html; + } + } + + + // check if monero address is given based on its length + // if yes, then we can only show its public components + if (search_text.length() == 95) + { + // parse string representing given monero address + cryptonote::account_public_address address; + + bool testnet_addr {false}; - //signed_key_images.push_back(make_pair(key_image, signature)); + if (search_text[0] == '9' || search_text[0] == 'A') + testnet_addr = true; + + if (!xmreg::parse_str_address(search_text, address, testnet_addr)) + { + cerr << "Cant parse string address: " << search_text << endl; + return string("Cant parse address (probably incorrect format): ") + + search_text; } - if (total_xmr > 0) + return show_address_details(address, testnet_addr); + } + + // check if integrated monero address is given based on its length + // if yes, then show its public components search tx based on encrypted id + if (search_text.length() == 106) + { + + cryptonote::account_public_address address; + + bool has_payment_id; + + crypto::hash8 encrypted_payment_id; + + bool testnet; + + if (!get_account_integrated_address_from_str(address, + has_payment_id, + encrypted_payment_id, + testnet, + search_text)) { - context["has_total_xmr"] = true; - context["total_xmr"] = fmt::format("{:0.12f}", XMR_AMOUNT(total_xmr)); + cerr << "Cant parse string integerated address: " << search_text << endl; + return string("Cant parse address (probably incorrect format): ") + + search_text; } - string checkrawkeyimgs_html = xmreg::read(TMPL_MY_CHECKRAWKEYIMGS); + return show_integrated_address_details(address, encrypted_payment_id, testnet); + } - // add footer - string full_page = checkrawkeyimgs_html + xmreg::read(TMPL_FOOTER); + // second let try searching for tx + result_html = show_tx(search_text); - add_css_style(context); + // nasty check if output is "Cant get" as a sign of + // a not found tx. Later need to think of something better. + if (result_html.find("Cant get") == string::npos) + { + return result_html; + } - // render the page - return mstch::render(full_page, context); + // if tx search not successful, check if we are looking + // for a block with given hash + result_html = show_block(search_text); + + if (result_html.find("Cant get") == string::npos) + { + return result_html; } + result_html = default_txt; + + // get mempool transaction so that what we search, + // might be there. Note: show_tx above already searches it + // but only looks for tx hash. Now want to check + // for key_images, public_keys, payments_id, etc. + vector mempool_txs = get_mempool_txs(); + + // key is string indicating where search_text was found. + map> tx_search_results + = search_txs(mempool_txs, search_text); - string - search(string search_text) + // now search my own custom lmdb database + // with key_images, public_keys, payments_id etc. + + vector>> all_possible_tx_hashes; + + try { + unique_ptr mylmdb; - // remove white characters - boost::trim(search_text); + if (!bf::is_directory(lmdb2_path)) + { + cout << "Custom lmdb database seem does not exist at: " << lmdb2_path << endl; - string default_txt {"No such thing found: " + search_text}; + result_html = show_search_results(search_text, all_possible_tx_hashes); - string result_html {default_txt}; + return result_html; + } - // check first if we look for output with given global index - // such search start with "goi_", e.g., "goi_543" - bool search_for_global_output_idx = (search_text.substr(0, 4) == "goi_"); + cout << "Custom lmdb database seem to exist at: " << lmdb2_path << endl; + cout << "So lets try to search there for what we are after." << endl; - // check if we look for output with amout index and amount - // such search start with "aoi_", e.g., "aoi_444-23.00" - bool search_for_amount_output_idx = (search_text.substr(0, 4) == "aoi_"); + mylmdb = make_unique(lmdb2_path); - // check if date given in format: 2005-04-55 - // this is 10 characters + // check if date given in format: 2015-04-15 12:02:33 + // this is 19 characters if (search_text.length() == 19) { uint64_t estimated_blk_height {0}; - // first parse the string date into boost's ptime object - dateparser parser {"%Y-%m-%d %H:%M:%S"}; + // first parse the string to date::sys_seconds and then to timestamp + // since epoch + uint64_t blk_timestamp_utc = parse(search_text).time_since_epoch().count(); - if (parser(search_text)) + if (blk_timestamp_utc) { // seems we have a correct date! // so try to estimate block height from it. + // + // to find block we can use our lmdb outputs_info table + // its indexes are timestamps. - cout << "timestamp: " << xmreg::ptime_to_time_t(parser.pt) << endl; + vector out_infos; - // estimate blockchain height from the start date provided - estimated_blk_height = xmreg::estimate_bc_height(search_text); + if (mylmdb->get_output_info(blk_timestamp_utc, out_infos)) + { + // since many outputs can be in a single block + // just get the first one to obtained its block - result_html = show_block(estimated_blk_height); + uint64_t found_blk_height = core_storage->get_db() + .get_tx_block_height(out_infos.at(0).tx_hash); - // nasty check if output is "Cant get" as a sign of - // a not found tx. Later need to think of something better. - if (result_html.find("Cant get") == string::npos) - { - return result_html; + return show_block(found_blk_height); } } } - // first check if searching for block of given height - if (search_text.size() < 12 && - (search_for_global_output_idx == false - ||search_for_amount_output_idx == false)) - { - uint64_t blk_height; + mylmdb->search(search_text, + tx_search_results["key_images"], + "key_images"); - try - { - blk_height = boost::lexical_cast(search_text); + cout << "size: " << tx_search_results["key_images"].size() << endl; - result_html = show_block(blk_height); + all_possible_tx_hashes.push_back( + make_pair("key_images", + tx_search_results["key_images"])); - // nasty check if output is "Cant get" as a sign of - // a not found tx. Later need to think of something better. - if (result_html.find("Cant get") == string::npos) - { - return result_html; - } - } - catch(boost::bad_lexical_cast &e) - { - return result_html; - } - } + // search the custum lmdb for tx_public_keys and append the result + // to those from the mempool search if found + mylmdb->search(search_text, + tx_search_results["tx_public_keys"], + "tx_public_keys"); - // check if monero address is given based on its length - // if yes, then we can only show its public components - if (search_text.length() == 95) + if (!tx_search_results["tx_public_keys"].empty()) { - // parse string representing given monero address - cryptonote::account_public_address address; - - bool testnet_addr {false}; + all_possible_tx_hashes.push_back( + make_pair("tx_public_keys", + tx_search_results["tx_public_keys"])); + } + else + { + // if private tx key is added, use it to obtained tx_public_key + // and than search for corresponding tx - if (search_text[0] == '9' || search_text[0] == 'A') - testnet_addr = true; + public_key tx_pub_key = null_pkey; + secret_key tx_prv_key; - if (!xmreg::parse_str_address(search_text, address, testnet_addr)) + if (hex_to_pod(search_text, tx_prv_key)) { - cerr << "Cant parse string address: " << search_text << endl; - return string("Cant parse address (probably incorrect format): ") - + search_text; - } + secret_key recovery_key = tx_prv_key; - return show_address_details(address, testnet_addr); - } - - // check if integrated monero address is given based on its length - // if yes, then show its public components search tx based on encrypted id - if (search_text.length() == 106) - { + const unsigned char * tx_prv_key_ptr = reinterpret_cast(&tx_prv_key); + unsigned char * tx_pub_key_ptr = reinterpret_cast(&tx_pub_key); - cryptonote::account_public_address address; + //memcpy(&tx_pub_key.data, reinterpret_cast(tx_pub_key_ptr), sizeof(tx_pub_key.data)); - bool has_payment_id; + ge_p3 point; + ge_scalarmult_base(&point, tx_prv_key_ptr); + ge_p3_tobytes(tx_pub_key_ptr, &point); - crypto::hash8 encrypted_payment_id; + string tx_pub_key_str = pod_to_hex(tx_pub_key); - bool testnet; + mylmdb->search(tx_pub_key_str, + tx_search_results["tx_public_keys"], + "tx_public_keys"); - if (!get_account_integrated_address_from_str(address, - has_payment_id, - encrypted_payment_id, - testnet, - search_text)) - { - cerr << "Cant parse string integerated address: " << search_text << endl; - return string("Cant parse address (probably incorrect format): ") - + search_text; + all_possible_tx_hashes.push_back( + make_pair("tx_public_keys", + tx_search_results["tx_public_keys"])); } - - return show_integrated_address_details(address, encrypted_payment_id, testnet); } - // second let try searching for tx - result_html = show_tx(search_text); - // nasty check if output is "Cant get" as a sign of - // a not found tx. Later need to think of something better. - if (result_html.find("Cant get") == string::npos) - { - return result_html; - } + // search the custum lmdb for payments_id and append the result + // to those from the mempool search if found - // if tx search not successful, check if we are looking - // for a block with given hash - result_html = show_block(search_text); + mylmdb->search(search_text, + tx_search_results["payments_id"], + "payments_id"); - if (result_html.find("Cant get") == string::npos) - { - return result_html; - } + all_possible_tx_hashes.push_back( + make_pair("payments_id", + tx_search_results["payments_id"])); - result_html = default_txt; + // search the custum lmdb for encrypted_payments_id and append the result + // to those from the mempool search if found - // get mempool transaction so that what we search, - // might be there. Note: show_tx above already searches it - // but only looks for tx hash. Now want to check - // for key_images, public_keys, payments_id, etc. - vector mempool_txs = get_mempool_txs(); + mylmdb->search(search_text, + tx_search_results["encrypted_payments_id"], + "encrypted_payments_id"); - // key is string indicating where search_text was found. - map> tx_search_results - = search_txs(mempool_txs, search_text); + all_possible_tx_hashes.push_back( + make_pair("encrypted_payments_id", + tx_search_results["encrypted_payments_id"])); - // now search my own custom lmdb database - // with key_images, public_keys, payments_id etc. + // search the custum lmdb for output_public_keys and append the result + // to those from the mempool search if found - vector>> all_possible_tx_hashes; + mylmdb->search(search_text, + tx_search_results["output_public_keys"], + "output_public_keys"); - try - { - unique_ptr mylmdb; + all_possible_tx_hashes.push_back( + make_pair("output_public_keys", + tx_search_results["output_public_keys"])); + + + // seach for output using output global index - if (!bf::is_directory(lmdb2_path)) + if (search_for_global_output_idx) + { + try { - cout << "Custom lmdb database seem does not exist at: " << lmdb2_path << endl; + uint64_t global_idx = boost::lexical_cast( + search_text.substr(4)); - result_html = show_search_results(search_text, all_possible_tx_hashes); - return result_html; - } + output_data_t output_data; - cout << "Custom lmdb database seem to exist at: " << lmdb2_path << endl; - cout << "So lets try to search there for what we are after." << endl; + try + { + // get info about output of a given global index + output_data = core_storage->get_db() + .get_output_key(global_idx); + } + catch (const OUTPUT_DNE& e) + { + string out_msg = fmt::format( + "Output with index {:d} does not exist!", + global_idx + ); - mylmdb = make_unique(lmdb2_path); + cerr << out_msg << endl; + return out_msg; + } - mylmdb->search(search_text, - tx_search_results["key_images"], - "key_images"); + //cout << "tx_out.first: " << tx_out.first << endl; + //cout << "tx_out.second: " << tx_out.second << endl; - cout << "size: " << tx_search_results["key_images"].size() << endl; + string output_pub_key = pod_to_hex(output_data.pubkey); - all_possible_tx_hashes.push_back( - make_pair("key_images", - tx_search_results["key_images"])); + //cout << "output_pub_key: " << output_pub_key << endl; + vector found_outputs; - // search the custum lmdb for tx_public_keys and append the result - // to those from the mempool search if found + mylmdb->search(output_pub_key, + found_outputs, + "output_public_keys"); - mylmdb->search(search_text, - tx_search_results["tx_public_keys"], - "tx_public_keys"); + //cout << "found_outputs.size(): " << found_outputs.size() << endl; - if (!tx_search_results["tx_public_keys"].empty()) - { all_possible_tx_hashes.push_back( - make_pair("tx_public_keys", - tx_search_results["tx_public_keys"])); + make_pair("output_public_keys_based_on_global_idx", + found_outputs)); + } - else + catch(boost::bad_lexical_cast &e) { - // if private tx key is added, use it to obtained tx_public_key - // and than search for corresponding tx - - public_key tx_pub_key = null_pkey; - secret_key tx_prv_key; - - if (hex_to_pod(search_text, tx_prv_key)) - { - secret_key recovery_key = tx_prv_key; - - const unsigned char * tx_prv_key_ptr = reinterpret_cast(&tx_prv_key); - unsigned char * tx_pub_key_ptr = reinterpret_cast(&tx_pub_key); - - //memcpy(&tx_pub_key.data, reinterpret_cast(tx_pub_key_ptr), sizeof(tx_pub_key.data)); - - ge_p3 point; - ge_scalarmult_base(&point, tx_prv_key_ptr); - ge_p3_tobytes(tx_pub_key_ptr, &point); - - string tx_pub_key_str = pod_to_hex(tx_pub_key); - - mylmdb->search(tx_pub_key_str, - tx_search_results["tx_public_keys"], - "tx_public_keys"); - - all_possible_tx_hashes.push_back( - make_pair("tx_public_keys", - tx_search_results["tx_public_keys"])); - } + cerr << "Cant cast global_idx string: " + << search_text.substr(4) << endl; } + } // if (search_for_global_output_idx) + // seach for output using output amount index and amount - // search the custum lmdb for payments_id and append the result - // to those from the mempool search if found - - mylmdb->search(search_text, - tx_search_results["payments_id"], - "payments_id"); + if (search_for_amount_output_idx) + { + try + { - all_possible_tx_hashes.push_back( - make_pair("payments_id", - tx_search_results["payments_id"])); + string str_to_split = search_text.substr(4); - // search the custum lmdb for encrypted_payments_id and append the result - // to those from the mempool search if found + vector string_parts; - mylmdb->search(search_text, - tx_search_results["encrypted_payments_id"], - "encrypted_payments_id"); + boost::split(string_parts, str_to_split, + boost::is_any_of("-")); - all_possible_tx_hashes.push_back( - make_pair("encrypted_payments_id", - tx_search_results["encrypted_payments_id"])); + if (string_parts.size() != 2) + { + throw; + } - // search the custum lmdb for output_public_keys and append the result - // to those from the mempool search if found + uint64_t amount_idx = boost::lexical_cast( + string_parts[0]); - mylmdb->search(search_text, - tx_search_results["output_public_keys"], - "output_public_keys"); + uint64_t amount = static_cast + (boost::lexical_cast( + string_parts[1]) * 1e12); - all_possible_tx_hashes.push_back( - make_pair("output_public_keys", - tx_search_results["output_public_keys"])); + //cout << "amount_idx: " << amount_idx << endl; + //cout << "amount: " << amount << endl; - // seach for output using output global index + output_data_t output_data; - if (search_for_global_output_idx) - { try { - uint64_t global_idx = boost::lexical_cast( - search_text.substr(4)); + // get info about output of a given global index + output_data = core_storage->get_db() + .get_output_key( + amount, amount_idx); + } + catch (const OUTPUT_DNE& e) + { + string out_msg = fmt::format( + "Output with amount {:d} and index {:d} does not exist!", + amount, amount_idx + ); + cerr << out_msg << endl; - output_data_t output_data; + return out_msg; + } - try - { - // get info about output of a given global index - output_data = core_storage->get_db() - .get_output_key(global_idx); - } - catch (const OUTPUT_DNE& e) - { - string out_msg = fmt::format( - "Output with index {:d} does not exist!", - global_idx - ); + string output_pub_key = pod_to_hex(output_data.pubkey); - cerr << out_msg << endl; + //cout << "output_pub_key: " << output_pub_key << endl; - return out_msg; - } + vector found_outputs; - //cout << "tx_out.first: " << tx_out.first << endl; - //cout << "tx_out.second: " << tx_out.second << endl; + mylmdb->search(output_pub_key, + found_outputs, + "output_public_keys"); - string output_pub_key = pod_to_hex(output_data.pubkey); + //cout << "found_outputs.size(): " << found_outputs.size() << endl; - //cout << "output_pub_key: " << output_pub_key << endl; + all_possible_tx_hashes.push_back( + make_pair("output_public_keys_based_on_amount_idx", + found_outputs)); - vector found_outputs; + } + catch(boost::bad_lexical_cast& e) + { + cerr << "Cant parse amout index and amout string: " + << search_text.substr(4) << endl; + } + catch(const OUTPUT_DNE& e) + { + cerr << "Output not found in the blockchain: " + << search_text.substr(4) << endl; - mylmdb->search(output_pub_key, - found_outputs, - "output_public_keys"); + return(string("Output not found in the blockchain: ") + + search_text.substr(4)); + } + } // if (search_for_amount_output_idx) + } + catch (const lmdb::runtime_error& e) + { + cerr << "Error opening/accessing custom lmdb database: " + << e.what() << endl; + } + catch (...) + { + std::exception_ptr p = std::current_exception(); + cerr << "Error opening/accessing custom lmdb database: " + << p.__cxa_exception_type()->name() << endl; + } - //cout << "found_outputs.size(): " << found_outputs.size() << endl; - all_possible_tx_hashes.push_back( - make_pair("output_public_keys_based_on_global_idx", - found_outputs)); + result_html = show_search_results(search_text, all_possible_tx_hashes); - } - catch(boost::bad_lexical_cast &e) - { - cerr << "Cant cast global_idx string: " - << search_text.substr(4) << endl; - } - } // if (search_for_global_output_idx) + return result_html; + } - // seach for output using output amount index and amount + string + show_address_details(const account_public_address& address, bool testnet = false) + { - if (search_for_amount_output_idx) - { - try - { + string address_str = xmreg::print_address(address, testnet); + string pub_viewkey_str = fmt::format("{:s}", address.m_view_public_key); + string pub_spendkey_str = fmt::format("{:s}", address.m_spend_public_key); - string str_to_split = search_text.substr(4); + mstch::map context { + {"xmr_address" , REMOVE_HASH_BRAKETS(address_str)}, + {"public_viewkey" , REMOVE_HASH_BRAKETS(pub_viewkey_str)}, + {"public_spendkey" , REMOVE_HASH_BRAKETS(pub_spendkey_str)}, + {"is_integrated_addr" , false}, + {"testnet" , testnet}, + }; - vector string_parts; + // read address.html + string address_html = xmreg::read(TMPL_ADDRESS); - boost::split(string_parts, str_to_split, - boost::is_any_of("-")); + // add header and footer + string full_page = get_full_page(address_html); - if (string_parts.size() != 2) - { - throw; - } + add_css_style(context); - uint64_t amount_idx = boost::lexical_cast( - string_parts[0]); - - uint64_t amount = static_cast - (boost::lexical_cast( - string_parts[1]) * 1e12); + // render the page + return mstch::render(full_page, context); + } + // ; + string + show_integrated_address_details(const account_public_address& address, + const crypto::hash8& encrypted_payment_id, + bool testnet = false) + { - //cout << "amount_idx: " << amount_idx << endl; - //cout << "amount: " << amount << endl; + string address_str = xmreg::print_address(address, testnet); + string pub_viewkey_str = fmt::format("{:s}", address.m_view_public_key); + string pub_spendkey_str = fmt::format("{:s}", address.m_spend_public_key); + string enc_payment_id_str = fmt::format("{:s}", encrypted_payment_id); + + mstch::map context { + {"xmr_address" , REMOVE_HASH_BRAKETS(address_str)}, + {"public_viewkey" , REMOVE_HASH_BRAKETS(pub_viewkey_str)}, + {"public_spendkey" , REMOVE_HASH_BRAKETS(pub_spendkey_str)}, + {"encrypted_payment_id" , REMOVE_HASH_BRAKETS(enc_payment_id_str)}, + {"is_integrated_addr" , true}, + {"testnet" , testnet}, + }; - output_data_t output_data; + // read address.html + string address_html = xmreg::read(TMPL_ADDRESS); - try - { - // get info about output of a given global index - output_data = core_storage->get_db() - .get_output_key( - amount, amount_idx); - } - catch (const OUTPUT_DNE& e) - { - string out_msg = fmt::format( - "Output with amount {:d} and index {:d} does not exist!", - amount, amount_idx - ); + add_css_style(context); - cerr << out_msg << endl; + // add header and footer + string full_page = get_full_page(address_html); - return out_msg; - } + // render the page + return mstch::render(full_page, context); + } - string output_pub_key = pod_to_hex(output_data.pubkey); + map> + search_txs(vector txs, const string& search_text) + { + map> tx_hashes; - //cout << "output_pub_key: " << output_pub_key << endl; + // initlizte the map with empty results + tx_hashes["key_images"] = {}; + tx_hashes["tx_public_keys"] = {}; + tx_hashes["payments_id"] = {}; + tx_hashes["encrypted_payments_id"] = {}; + tx_hashes["output_public_keys"] = {}; - vector found_outputs; + for (const transaction& tx: txs) + { - mylmdb->search(output_pub_key, - found_outputs, - "output_public_keys"); + tx_details txd = get_tx_details(tx); - //cout << "found_outputs.size(): " << found_outputs.size() << endl; + string tx_hash_str = pod_to_hex(txd.hash); - all_possible_tx_hashes.push_back( - make_pair("output_public_keys_based_on_amount_idx", - found_outputs)); + // check if any key_image matches the search_text - } - catch(boost::bad_lexical_cast& e) + vector::iterator it1 = + find_if(begin(txd.input_key_imgs), end(txd.input_key_imgs), + [&](const txin_to_key& key_img) { - cerr << "Cant parse amout index and amout string: " - << search_text.substr(4) << endl; - } - catch(const OUTPUT_DNE& e) - { - cerr << "Output not found in the blockchain: " - << search_text.substr(4) << endl; + return pod_to_hex(key_img.k_image) == search_text; + }); - return(string("Output not found in the blockchain: ") - + search_text.substr(4)); - } - } // if (search_for_amount_output_idx) - } - catch (const lmdb::runtime_error& e) - { - cerr << "Error opening/accessing custom lmdb database: " - << e.what() << endl; - } - catch (...) + if (it1 != txd.input_key_imgs.end()) { - std::exception_ptr p = std::current_exception(); - cerr << "Error opening/accessing custom lmdb database: " - << p.__cxa_exception_type()->name() << endl; + tx_hashes["key_images"].push_back(tx_hash_str); } + // check if tx_public_key matches the search_text - result_html = show_search_results(search_text, all_possible_tx_hashes); + if (pod_to_hex(txd.pk) == search_text) + { + tx_hashes["tx_public_keys"].push_back(tx_hash_str); + } - return result_html; - } + // check if payments_id matches the search_text - string - show_address_details(const account_public_address& address, bool testnet = false) - { + if (pod_to_hex(txd.payment_id) == search_text) + { + tx_hashes["payment_id"].push_back(tx_hash_str); + } - string address_str = xmreg::print_address(address, testnet); - string pub_viewkey_str = fmt::format("{:s}", address.m_view_public_key); - string pub_spendkey_str = fmt::format("{:s}", address.m_spend_public_key); + // check if encrypted_payments_id matches the search_text - mstch::map context { - {"xmr_address" , REMOVE_HASH_BRAKETS(address_str)}, - {"public_viewkey" , REMOVE_HASH_BRAKETS(pub_viewkey_str)}, - {"public_spendkey" , REMOVE_HASH_BRAKETS(pub_spendkey_str)}, - {"is_integrated_addr" , false}, - {"testnet" , testnet}, - }; + if (pod_to_hex(txd.payment_id8) == search_text) + { + tx_hashes["encrypted_payments_id"].push_back(tx_hash_str); + } - // read address.html - string address_html = xmreg::read(TMPL_ADDRESS); + // check if output_public_keys matche the search_text - // add header and footer - string full_page = get_full_page(address_html); + vector>::iterator it2 = + find_if(begin(txd.output_pub_keys), end(txd.output_pub_keys), + [&](const pair& tx_out_pk) + { + return pod_to_hex(tx_out_pk.first.key) == search_text; + }); - add_css_style(context); + if (it2 != txd.output_pub_keys.end()) + { + tx_hashes["output_public_keys"].push_back(tx_hash_str); + } - // render the page - return mstch::render(full_page, context); } - // ; - string - show_integrated_address_details(const account_public_address& address, - const crypto::hash8& encrypted_payment_id, - bool testnet = false) - { - - string address_str = xmreg::print_address(address, testnet); - string pub_viewkey_str = fmt::format("{:s}", address.m_view_public_key); - string pub_spendkey_str = fmt::format("{:s}", address.m_spend_public_key); - string enc_payment_id_str = fmt::format("{:s}", encrypted_payment_id); + return tx_hashes; - mstch::map context { - {"xmr_address" , REMOVE_HASH_BRAKETS(address_str)}, - {"public_viewkey" , REMOVE_HASH_BRAKETS(pub_viewkey_str)}, - {"public_spendkey" , REMOVE_HASH_BRAKETS(pub_spendkey_str)}, - {"encrypted_payment_id" , REMOVE_HASH_BRAKETS(enc_payment_id_str)}, - {"is_integrated_addr" , true}, - {"testnet" , testnet}, - }; + } - // read address.html - string address_html = xmreg::read(TMPL_ADDRESS); + vector + get_mempool_txs() + { + // get mempool data using rpc call + vector> mempool_data = search_mempool(); - add_css_style(context); + // output only transactions + vector mempool_txs; - // add header and footer - string full_page = get_full_page(address_html); + mempool_txs.reserve(mempool_data.size()); - // render the page - return mstch::render(full_page, context); - } - - map> - search_txs(vector txs, const string& search_text) + for (const auto& a_pair: mempool_data) { - map> tx_hashes; - - // initlizte the map with empty results - tx_hashes["key_images"] = {}; - tx_hashes["tx_public_keys"] = {}; - tx_hashes["payments_id"] = {}; - tx_hashes["encrypted_payments_id"] = {}; - tx_hashes["output_public_keys"] = {}; - - for (const transaction& tx: txs) - { - - tx_details txd = get_tx_details(tx); - - string tx_hash_str = pod_to_hex(txd.hash); + mempool_txs.push_back(a_pair.second); + } - // check if any key_image matches the search_text + return mempool_txs; + } - vector::iterator it1 = - find_if(begin(txd.input_key_imgs), end(txd.input_key_imgs), - [&](const txin_to_key& key_img) - { - return pod_to_hex(key_img.k_image) == search_text; - }); + string + show_search_results(const string& search_text, + const vector>>& all_possible_tx_hashes) + { - if (it1 != txd.input_key_imgs.end()) - { - tx_hashes["key_images"].push_back(tx_hash_str); - } + // initalise page tempate map with basic info about blockchain + mstch::map context { + {"testnet" , testnet}, + {"search_text" , search_text}, + {"no_results" , true}, + {"to_many_results", false} + }; - // check if tx_public_key matches the search_text + for (const pair>& found_txs: all_possible_tx_hashes) + { + // define flag, e.g., has_key_images denoting that + // tx hashes for key_image searched were found + context.insert({"has_" + found_txs.first, !found_txs.second.empty()}); - if (pod_to_hex(txd.pk) == search_text) - { - tx_hashes["tx_public_keys"].push_back(tx_hash_str); - } + // cout << "found_txs.first: " << found_txs.first << endl; - // check if payments_id matches the search_text + // insert new array based on what we found to context if not exist + pair< mstch::map::iterator, bool> res + = context.insert({found_txs.first, mstch::array{}}); - if (pod_to_hex(txd.payment_id) == search_text) - { - tx_hashes["payment_id"].push_back(tx_hash_str); - } + if (!found_txs.second.empty()) + { - // check if encrypted_payments_id matches the search_text + uint64_t tx_i {0}; - if (pod_to_hex(txd.payment_id8) == search_text) + // for each found tx_hash, get the corresponding tx + // and its details, and put into mstch for rendering + for (const string& tx_hash: found_txs.second) { - tx_hashes["encrypted_payments_id"].push_back(tx_hash_str); - } - // check if output_public_keys matche the search_text + crypto::hash tx_hash_pod; - vector>::iterator it2 = - find_if(begin(txd.output_pub_keys), end(txd.output_pub_keys), - [&](const pair& tx_out_pk) - { - return pod_to_hex(tx_out_pk.first.key) == search_text; - }); + epee::string_tools::hex_to_pod(tx_hash, tx_hash_pod); - if (it2 != txd.output_pub_keys.end()) - { - tx_hashes["output_public_keys"].push_back(tx_hash_str); - } - - } + transaction tx; - return tx_hashes; + uint64_t blk_height {0}; - } + int64_t blk_timestamp; - vector - get_mempool_txs() - { - // get mempool data using rpc call - vector> mempool_data = search_mempool(); + // first check in the blockchain + if (mcore->get_tx(tx_hash, tx)) + { - // output only transactions - vector mempool_txs; + // get timestamp of the tx's block + blk_height = core_storage + ->get_db().get_tx_block_height(tx_hash_pod); - mempool_txs.reserve(mempool_data.size()); + blk_timestamp = core_storage + ->get_db().get_block_timestamp(blk_height); - for (const auto& a_pair: mempool_data) - { - mempool_txs.push_back(a_pair.second); - } + } + else + { + // check in mempool if tx_hash not found in the + // blockchain + vector> found_txs + = search_mempool(tx_hash_pod); - return mempool_txs; - } + if (!found_txs.empty()) + { + // there should be only one tx found + tx = found_txs.at(0).second; + } + else + { + return string("Cant get tx of hash (show_search_results): " + tx_hash); + } - string - show_search_results(const string& search_text, - const vector>>& all_possible_tx_hashes) - { + // tx in mempool have no blk_timestamp + // but can use their recive time + blk_timestamp = found_txs.at(0).first.receive_time; - // initalise page tempate map with basic info about blockchain - mstch::map context { - {"testnet" , testnet}, - {"search_text" , search_text}, - {"no_results" , true}, - {"to_many_results", false} - }; + } - for (const pair>& found_txs: all_possible_tx_hashes) - { - // define flag, e.g., has_key_images denoting that - // tx hashes for key_image searched were found - context.insert({"has_" + found_txs.first, !found_txs.second.empty()}); + tx_details txd = get_tx_details(tx); - // cout << "found_txs.first: " << found_txs.first << endl; + mstch::map txd_map = txd.get_mstch_map(); - // insert new array based on what we found to context if not exist - pair< mstch::map::iterator, bool> res - = context.insert({found_txs.first, mstch::array{}}); - if (!found_txs.second.empty()) - { + // add the timestamp to tx mstch map + txd_map.insert({"timestamp", xmreg::timestamp_to_str(blk_timestamp)}); - uint64_t tx_i {0}; + boost::get((res.first)->second).push_back(txd_map); - // for each found tx_hash, get the corresponding tx - // and its details, and put into mstch for rendering - for (const string& tx_hash: found_txs.second) + // dont show more than 500 results + if (tx_i > 500) { + context["to_many_results"] = true; + break; + } - crypto::hash tx_hash_pod; - - epee::string_tools::hex_to_pod(tx_hash, tx_hash_pod); - - transaction tx; + ++tx_i; + } - uint64_t blk_height {0}; + // if found something, set this flag to indicate this fact + context["no_results"] = false; + } + } - int64_t blk_timestamp; + // read search_results.html + string search_results_html = xmreg::read(TMPL_SEARCH_RESULTS); - // first check in the blockchain - if (mcore->get_tx(tx_hash, tx)) - { + // add header and footer + string full_page = get_full_page(search_results_html); - // get timestamp of the tx's block - blk_height = core_storage - ->get_db().get_tx_block_height(tx_hash_pod); + // read partial for showing details of tx(s) found + map partials { + {"tx_table_head", xmreg::read(string(TMPL_PARIALS_DIR) + "/tx_table_header.html")}, + {"tx_table_row" , xmreg::read(string(TMPL_PARIALS_DIR) + "/tx_table_row.html")} + }; - blk_timestamp = core_storage - ->get_db().get_block_timestamp(blk_height); + add_css_style(context); - } - else - { - // check in mempool if tx_hash not found in the - // blockchain - vector> found_txs - = search_mempool(tx_hash_pod); + // render the page + return mstch::render(full_page, context, partials); + } - if (!found_txs.empty()) - { - // there should be only one tx found - tx = found_txs.at(0).second; - } - else - { - return string("Cant get tx of hash (show_search_results): " + tx_hash); - } - // tx in mempool have no blk_timestamp - // but can use their recive time - blk_timestamp = found_txs.at(0).first.receive_time; +private: - } - tx_details txd = get_tx_details(tx); + void + mark_real_mixins_on_timescales( + const vector& real_output_indices, + mstch::map& tx_context) + { + // mark real mixing in the mixins timescale graph + mstch::array& mixins_timescales + = boost::get(tx_context["timescales"]); - mstch::map txd_map = txd.get_mstch_map(); + uint64_t idx {0}; + for (mstch::node& timescale_node: mixins_timescales) + { - // add the timestamp to tx mstch map - txd_map.insert({"timestamp", xmreg::timestamp_to_str(blk_timestamp)}); + string& timescale = boost::get( + boost::get(timescale_node)["timescale"] + ); - boost::get((res.first)->second).push_back(txd_map); + // claculated number of timescale points + // due to resolution, no of points might be lower than no of mixins + size_t no_points = std::count(timescale.begin(), timescale.end(), '*'); - // dont show more than 500 results - if (tx_i > 500) - { - context["to_many_results"] = true; - break; - } + size_t point_to_find = real_output_indices.at(idx); - ++tx_i; - } + // adjust point to find based on total number of points + if (point_to_find >= no_points) + point_to_find = no_points - 1; - // if found something, set this flag to indicate this fact - context["no_results"] = false; - } - } + boost::iterator_range r + = boost::find_nth(timescale, "*", point_to_find); - // read search_results.html - string search_results_html = xmreg::read(TMPL_SEARCH_RESULTS); + *(r.begin()) = 'R'; - // add header and footer - string full_page = get_full_page(search_results_html); + ++idx; + } + } - // read partial for showing details of tx(s) found - map partials { - {"tx_table_head", xmreg::read(string(TMPL_PARIALS_DIR) + "/tx_table_header.html")}, - {"tx_table_row" , xmreg::read(string(TMPL_PARIALS_DIR) + "/tx_table_row.html")} - }; + mstch::map + construct_tx_context(transaction tx, uint with_ring_signatures = 0) + { - add_css_style(context); + tx_details txd = get_tx_details(tx); - // render the page - return mstch::render(full_page, context, partials); - } + crypto::hash tx_hash = txd.hash; + string tx_hash_str = REMOVE_HASH_BRAKETS(fmt::format("{:s}", tx_hash)); - private: + uint64_t tx_blk_height {0}; + bool tx_blk_found {false}; - void - mark_real_mixins_on_timescales( - const vector& real_output_indices, - mstch::map& tx_context) + if (core_storage->have_tx(tx_hash)) { - // mark real mixing in the mixins timescale graph - mstch::array& mixins_timescales - = boost::get(tx_context["timescales"]); - - uint64_t idx {0}; + tx_blk_height = core_storage->get_db().get_tx_block_height(tx_hash); + tx_blk_found = true; + } - for (mstch::node& timescale_node: mixins_timescales) - { + // get block cointaining this tx + block blk; - string& timescale = boost::get( - boost::get(timescale_node)["timescale"] - ); + if (tx_blk_found && !mcore->get_block_by_height(tx_blk_height, blk)) + { + cerr << "Cant get block: " << tx_blk_height << endl; + } - // claculated number of timescale points - // due to resolution, no of points might be lower than no of mixins - size_t no_points = std::count(timescale.begin(), timescale.end(), '*'); + string tx_blk_height_str {"N/A"}; - size_t point_to_find = real_output_indices.at(idx); + // tx age + pair age; - // adjust point to find based on total number of points - if (point_to_find >= no_points) - point_to_find = no_points - 1; + string blk_timestamp {"N/A"}; - boost::iterator_range r - = boost::find_nth(timescale, "*", point_to_find); + if (tx_blk_found) + { + // calculate difference between tx and server timestamps + age = get_age(server_timestamp, blk.timestamp, FULL_AGE_FORMAT); - *(r.begin()) = 'R'; + blk_timestamp = xmreg::timestamp_to_str(blk.timestamp); - ++idx; - } + tx_blk_height_str = std::to_string(tx_blk_height); } - mstch::map - construct_tx_context(transaction tx, uint with_ring_signatures = 0) - { + // payments id. both normal and encrypted (payment_id8) + string pid_str = REMOVE_HASH_BRAKETS(fmt::format("{:s}", txd.payment_id)); + string pid8_str = REMOVE_HASH_BRAKETS(fmt::format("{:s}", txd.payment_id8)); + + + string tx_json = obj_to_json_str(tx); + + // initalise page tempate map with basic info about blockchain + mstch::map context { + {"testnet" , testnet}, + {"tx_hash" , tx_hash_str}, + {"tx_pub_key" , REMOVE_HASH_BRAKETS(fmt::format("{:s}", txd.pk))}, + {"blk_height" , tx_blk_height_str}, + {"tx_size" , fmt::format("{:0.4f}", + static_cast(txd.size) / 1024.0)}, + {"tx_fee" , fmt::format("{:0.12f}", XMR_AMOUNT(txd.fee))}, + {"tx_version" , fmt::format("{:d}", txd.version)}, + {"blk_timestamp" , blk_timestamp}, + {"blk_timestamp_uint" , blk.timestamp}, + {"delta_time" , age.first}, + {"inputs_no" , txd.input_key_imgs.size()}, + {"has_inputs" , !txd.input_key_imgs.empty()}, + {"outputs_no" , txd.output_pub_keys.size()}, + {"has_payment_id" , txd.payment_id != null_hash}, + {"has_payment_id8" , txd.payment_id8 != null_hash8}, + {"confirmations" , txd.no_confirmations}, + {"payment_id" , pid_str}, + {"payment_id8" , pid8_str}, + {"extra" , txd.get_extra_str()}, + {"with_ring_signatures" , static_cast( + with_ring_signatures)}, + {"tx_json" , tx_json}, + {"has_error" , false}, + {"error_msg" , string("")}, + {"have_raw_tx" , false}, + }; - tx_details txd = get_tx_details(tx); + string server_time_str = xmreg::timestamp_to_str(server_timestamp, "%F"); - crypto::hash tx_hash = txd.hash; + mstch::array inputs = mstch::array{}; - string tx_hash_str = REMOVE_HASH_BRAKETS(fmt::format("{:s}", tx_hash)); + uint64_t input_idx {0}; - uint64_t tx_blk_height {0}; + uint64_t inputs_xmr_sum {0}; - bool tx_blk_found {false}; + vector> mixin_timestamp_groups; - if (core_storage->have_tx(tx_hash)) - { - tx_blk_height = core_storage->get_db().get_tx_block_height(tx_hash); - tx_blk_found = true; - } + // make timescale maps for mixins in input + for (const txin_to_key& in_key: txd.input_key_imgs) + { + // get absolute offsets of mixins + std::vector absolute_offsets + = cryptonote::relative_output_offsets_to_absolute( + in_key.key_offsets); - // get block cointaining this tx - block blk; + // get public keys of outputs used in the mixins that match to the offests + std::vector outputs; - if (tx_blk_found && !mcore->get_block_by_height(tx_blk_height, blk)) + try { - cerr << "Cant get block: " << tx_blk_height << endl; + core_storage->get_db().get_output_key(in_key.amount, + absolute_offsets, + outputs); } + catch (const OUTPUT_DNE& e) + { + string out_msg = fmt::format( + "Outputs with amount {:d} do not exist and indexes ", + in_key.amount + ); - string tx_blk_height_str {"N/A"}; - - // tx age - pair age; + for (auto offset: absolute_offsets) + out_msg += ", " + to_string(offset); - string blk_timestamp {"N/A"}; + out_msg += " don't exist!"; - if (tx_blk_found) - { - // calculate difference between tx and server timestamps - age = get_age(server_timestamp, blk.timestamp, FULL_AGE_FORMAT); + cerr << out_msg << endl; - blk_timestamp = xmreg::timestamp_to_str(blk.timestamp); + context["has_error"] = true; + context["error_msg"] = out_msg; - tx_blk_height_str = std::to_string(tx_blk_height); + return context; } - // payments id. both normal and encrypted (payment_id8) - string pid_str = REMOVE_HASH_BRAKETS(fmt::format("{:s}", txd.payment_id)); - string pid8_str = REMOVE_HASH_BRAKETS(fmt::format("{:s}", txd.payment_id8)); - - - string tx_json = obj_to_json_str(tx); - - // initalise page tempate map with basic info about blockchain - mstch::map context { - {"testnet" , testnet}, - {"tx_hash" , tx_hash_str}, - {"tx_pub_key" , REMOVE_HASH_BRAKETS(fmt::format("{:s}", txd.pk))}, - {"blk_height" , tx_blk_height_str}, - {"tx_size" , fmt::format("{:0.4f}", - static_cast(txd.size) / 1024.0)}, - {"tx_fee" , fmt::format("{:0.12f}", XMR_AMOUNT(txd.fee))}, - {"tx_version" , fmt::format("{:d}", txd.version)}, - {"blk_timestamp" , blk_timestamp}, - {"blk_timestamp_uint" , blk.timestamp}, - {"delta_time" , age.first}, - {"inputs_no" , txd.input_key_imgs.size()}, - {"has_inputs" , !txd.input_key_imgs.empty()}, - {"outputs_no" , txd.output_pub_keys.size()}, - {"has_payment_id" , txd.payment_id != null_hash}, - {"has_payment_id8" , txd.payment_id8 != null_hash8}, - {"confirmations" , txd.no_confirmations}, - {"payment_id" , pid_str}, - {"payment_id8" , pid8_str}, - {"extra" , txd.get_extra_str()}, - {"with_ring_signatures" , static_cast( - with_ring_signatures)}, - {"tx_json" , tx_json}, - {"has_error" , false}, - {"error_msg" , string("")}, - {"have_raw_tx" , false}, - }; - - string server_time_str = xmreg::timestamp_to_str(server_timestamp, "%F"); + inputs.push_back(mstch::map { + {"in_key_img" , REMOVE_HASH_BRAKETS(fmt::format("{:s}", in_key.k_image))}, + {"amount" , fmt::format("{:0.12f}", XMR_AMOUNT(in_key.amount))}, + {"input_idx" , fmt::format("{:02d}", input_idx)}, + {"mixins" , mstch::array{}}, + {"ring_sigs" , txd.get_ring_sig_for_input(input_idx)}, + {"already_spent", false} // placeholder for later + }); - mstch::array inputs = mstch::array{}; + inputs_xmr_sum += in_key.amount; - uint64_t input_idx {0}; + vector mixin_timestamps; - uint64_t inputs_xmr_sum {0}; + // get reference to mixins array created above + mstch::array& mixins = boost::get( + boost::get(inputs.back())["mixins"]); - vector> mixin_timestamp_groups; + // mixin counter + size_t count = 0; - // make timescale maps for mixins in input - for (const txin_to_key& in_key: txd.input_key_imgs) + // for each found output public key find its block to get timestamp + for (const uint64_t &i: absolute_offsets) { - // get absolute offsets of mixins - std::vector absolute_offsets - = cryptonote::relative_output_offsets_to_absolute( - in_key.key_offsets); + // get basic information about mixn's output + cryptonote::output_data_t output_data = outputs.at(count); - // get public keys of outputs used in the mixins that match to the offests - std::vector outputs; + tx_out_index tx_out_idx; try { - core_storage->get_db().get_output_key(in_key.amount, - absolute_offsets, - outputs); + // get pair pair where first is tx hash + // and second is local index of the output i in that tx + tx_out_idx = core_storage->get_db() + .get_output_tx_and_index(in_key.amount, i); } catch (const OUTPUT_DNE& e) { + string out_msg = fmt::format( - "Outputs with amount {:d} do not exist and indexes ", - in_key.amount + "Output with amount {:d} and index {:d} does not exist!", + in_key.amount, i ); - for (auto offset: absolute_offsets) - out_msg += ", " + to_string(offset); - - out_msg += " don't exist!"; - cerr << out_msg << endl; context["has_error"] = true; @@ -3063,591 +3116,548 @@ namespace xmreg { return context; } - inputs.push_back(mstch::map { - {"in_key_img" , REMOVE_HASH_BRAKETS(fmt::format("{:s}", in_key.k_image))}, - {"amount" , fmt::format("{:0.12f}", XMR_AMOUNT(in_key.amount))}, - {"input_idx" , fmt::format("{:02d}", input_idx)}, - {"mixins" , mstch::array{}}, - {"ring_sigs" , txd.get_ring_sig_for_input(input_idx)}, - {"already_spent", false} // placeholder for later - }); - - inputs_xmr_sum += in_key.amount; - - vector mixin_timestamps; - - // get reference to mixins array created above - mstch::array& mixins = boost::get( - boost::get(inputs.back())["mixins"]); - // mixin counter - size_t count = 0; + // get block of given height, as we want to get its timestamp + cryptonote::block blk; - // for each found output public key find its block to get timestamp - for (const uint64_t &i: absolute_offsets) + if (!mcore->get_block_by_height(output_data.height, blk)) { - // get basic information about mixn's output - cryptonote::output_data_t output_data = outputs.at(count); + cerr << "- cant get block of height: " << output_data.height << endl; - tx_out_index tx_out_idx; - - try - { - // get pair pair where first is tx hash - // and second is local index of the output i in that tx - tx_out_idx = core_storage->get_db() - .get_output_tx_and_index(in_key.amount, i); - } - catch (const OUTPUT_DNE& e) - { - - string out_msg = fmt::format( - "Output with amount {:d} and index {:d} does not exist!", - in_key.amount, i - ); + context["has_error"] = true; + context["error_msg"] = fmt::format("- cant get block of height: {}", + output_data.height); + } - cerr << out_msg << endl; + // get age of mixin relative to server time + pair mixin_age = get_age(server_timestamp, + blk.timestamp, + FULL_AGE_FORMAT); + // get mixin transaction + transaction mixin_tx; - context["has_error"] = true; - context["error_msg"] = out_msg; + if (!mcore->get_tx(tx_out_idx.first, mixin_tx)) + { + cerr << "Cant get tx: " << tx_out_idx.first << endl; - return context; - } + context["has_error"] = true; + context["error_msg"] = fmt::format("Cant get tx: {:s}", tx_out_idx.first); + } + // mixin tx details + tx_details mixin_txd = get_tx_details(mixin_tx, true); + + mixins.push_back(mstch::map { + {"mix_blk" , fmt::format("{:08d}", output_data.height)}, + {"mix_pub_key" , REMOVE_HASH_BRAKETS(fmt::format("{:s}", + output_data.pubkey))}, + {"mix_tx_hash" , REMOVE_HASH_BRAKETS(fmt::format("{:s}", + tx_out_idx.first))}, + {"mix_out_indx" , fmt::format("{:d}", tx_out_idx.second)}, + {"mix_timestamp" , xmreg::timestamp_to_str(blk.timestamp)}, + {"mix_age" , mixin_age.first}, + {"mix_mixin_no" , mixin_txd.mixin_no}, + {"mix_inputs_no" , mixin_txd.input_key_imgs.size()}, + {"mix_outputs_no" , mixin_txd.output_pub_keys.size()}, + {"mix_age_format" , mixin_age.second}, + {"mix_idx" , fmt::format("{:02d}", count)}, + {"mix_is_it_real" , false}, // a placeholder for future + }); - // get block of given height, as we want to get its timestamp - cryptonote::block blk; + // get mixin timestamp from its orginal block + mixin_timestamps.push_back(blk.timestamp); - if (!mcore->get_block_by_height(output_data.height, blk)) - { - cerr << "- cant get block of height: " << output_data.height << endl; + ++count; - context["has_error"] = true; - context["error_msg"] = fmt::format("- cant get block of height: {}", - output_data.height); - } + } // for (const uint64_t &i: absolute_offsets) - // get age of mixin relative to server time - pair mixin_age = get_age(server_timestamp, - blk.timestamp, - FULL_AGE_FORMAT); - // get mixin transaction - transaction mixin_tx; + mixin_timestamp_groups.push_back(mixin_timestamps); - if (!mcore->get_tx(tx_out_idx.first, mixin_tx)) - { - cerr << "Cant get tx: " << tx_out_idx.first << endl; + input_idx++; + } // for (const txin_to_key& in_key: txd.input_key_imgs) - context["has_error"] = true; - context["error_msg"] = fmt::format("Cant get tx: {:s}", tx_out_idx.first); - } + uint64_t min_mix_timestamp; + uint64_t max_mix_timestamp; - // mixin tx details - tx_details mixin_txd = get_tx_details(mixin_tx, true); - - mixins.push_back(mstch::map { - {"mix_blk" , fmt::format("{:08d}", output_data.height)}, - {"mix_pub_key" , REMOVE_HASH_BRAKETS(fmt::format("{:s}", - output_data.pubkey))}, - {"mix_tx_hash" , REMOVE_HASH_BRAKETS(fmt::format("{:s}", - tx_out_idx.first))}, - {"mix_out_indx" , fmt::format("{:d}", tx_out_idx.second)}, - {"mix_timestamp" , xmreg::timestamp_to_str(blk.timestamp)}, - {"mix_age" , mixin_age.first}, - {"mix_mixin_no" , mixin_txd.mixin_no}, - {"mix_inputs_no" , mixin_txd.input_key_imgs.size()}, - {"mix_outputs_no" , mixin_txd.output_pub_keys.size()}, - {"mix_age_format" , mixin_age.second}, - {"mix_idx" , fmt::format("{:02d}", count)}, - {"mix_is_it_real" , false}, // a placeholder for future - }); + pair mixins_timescales + = construct_mstch_mixin_timescales( + mixin_timestamp_groups, + min_mix_timestamp, + max_mix_timestamp + ); - // get mixin timestamp from its orginal block - mixin_timestamps.push_back(blk.timestamp); - ++count; + context["inputs_xmr_sum"] = fmt::format("{:0.12f}", XMR_AMOUNT(inputs_xmr_sum)); + context["server_time"] = server_time_str; + context["inputs"] = inputs; + context["min_mix_time"] = xmreg::timestamp_to_str(min_mix_timestamp); + context["max_mix_time"] = xmreg::timestamp_to_str(max_mix_timestamp); + context["timescales"] = mixins_timescales.first; + context["timescales_scale"] = fmt::format("{:0.2f}", + mixins_timescales.second / 3600.0 / 24.0); // in days - } // for (const uint64_t &i: absolute_offsets) + // get indices of outputs in amounts tables + vector out_amount_indices; - mixin_timestamp_groups.push_back(mixin_timestamps); + try + { - input_idx++; - } // for (const txin_to_key& in_key: txd.input_key_imgs) + uint64_t tx_index; - uint64_t min_mix_timestamp; - uint64_t max_mix_timestamp; + if (core_storage->get_db().tx_exists(txd.hash, tx_index)) + { + out_amount_indices = core_storage->get_db() + .get_tx_amount_output_indices(tx_index); + } + else + { + cerr << "get_tx_outputs_gindexs failed to find transaction with id = " << txd.hash; + } - pair mixins_timescales - = construct_mstch_mixin_timescales( - mixin_timestamp_groups, - min_mix_timestamp, - max_mix_timestamp - ); + } + catch(const exception& e) + { + cerr << e.what() << endl; + } + uint64_t output_idx {0}; - context["inputs_xmr_sum"] = fmt::format("{:0.12f}", XMR_AMOUNT(inputs_xmr_sum)); - context["server_time"] = server_time_str; - context["inputs"] = inputs; - context["min_mix_time"] = xmreg::timestamp_to_str(min_mix_timestamp); - context["max_mix_time"] = xmreg::timestamp_to_str(max_mix_timestamp); - context["timescales"] = mixins_timescales.first; - context["timescales_scale"] = fmt::format("{:0.2f}", - mixins_timescales.second / 3600.0 / 24.0); // in days + mstch::array outputs; - // get indices of outputs in amounts tables - vector out_amount_indices; + uint64_t outputs_xmr_sum {0}; - try - { + for (pair& outp: txd.output_pub_keys) + { - uint64_t tx_index; + // total number of ouputs in the blockchain for this amount + uint64_t num_outputs_amount = core_storage->get_db() + .get_num_outputs(outp.second); - if (core_storage->get_db().tx_exists(txd.hash, tx_index)) - { - out_amount_indices = core_storage->get_db() - .get_tx_amount_output_indices(tx_index); - } - else - { - cerr << "get_tx_outputs_gindexs failed to find transaction with id = " << txd.hash; - } + string out_amount_index_str {"N/A"}; - } - catch(const exception& e) + // outputs in tx in them mempool dont have yet global indices + // thus for them, we print N/A + if (!out_amount_indices.empty()) { - cerr << e.what() << endl; + out_amount_index_str = fmt::format("{:d}", + out_amount_indices.at(output_idx)); } - uint64_t output_idx {0}; + outputs_xmr_sum += outp.second; - mstch::array outputs; + outputs.push_back(mstch::map { + {"out_pub_key" , REMOVE_HASH_BRAKETS(fmt::format("{:s}", outp.first.key))}, + {"amount" , fmt::format("{:0.12f}", XMR_AMOUNT(outp.second))}, + {"amount_idx" , out_amount_index_str}, + {"num_outputs" , fmt::format("{:d}", num_outputs_amount)}, + {"output_idx" , fmt::format("{:02d}", output_idx++)} + }); + } - uint64_t outputs_xmr_sum {0}; + context["outputs_xmr_sum"] = fmt::format("{:0.12f}", XMR_AMOUNT(outputs_xmr_sum)); - for (pair& outp: txd.output_pub_keys) - { + context["outputs"] = outputs; - // total number of ouputs in the blockchain for this amount - uint64_t num_outputs_amount = core_storage->get_db() - .get_num_outputs(outp.second); - string out_amount_index_str {"N/A"}; + return context; + } - // outputs in tx in them mempool dont have yet global indices - // thus for them, we print N/A - if (!out_amount_indices.empty()) - { - out_amount_index_str = fmt::format("{:d}", - out_amount_indices.at(output_idx)); - } + pair + construct_mstch_mixin_timescales( + const vector>& mixin_timestamp_groups, + uint64_t& min_mix_timestamp, + uint64_t& max_mix_timestamp + ) + { + mstch::array mixins_timescales; - outputs_xmr_sum += outp.second; + double timescale_scale {0.0}; // size of one '_' in days - outputs.push_back(mstch::map { - {"out_pub_key" , REMOVE_HASH_BRAKETS(fmt::format("{:s}", outp.first.key))}, - {"amount" , fmt::format("{:0.12f}", XMR_AMOUNT(outp.second))}, - {"amount_idx" , out_amount_index_str}, - {"num_outputs" , fmt::format("{:d}", num_outputs_amount)}, - {"output_idx" , fmt::format("{:02d}", output_idx++)} - }); - } + // initialize with some large and some numbers + min_mix_timestamp = server_timestamp*2L; + max_mix_timestamp = 0; - context["outputs_xmr_sum"] = fmt::format("{:0.12f}", XMR_AMOUNT(outputs_xmr_sum)); + // find min and maximum timestamps + for (const vector& mixn_timestamps : mixin_timestamp_groups) + { - context["outputs"] = outputs; + uint64_t min_found = *min_element(mixn_timestamps.begin(), mixn_timestamps.end()); + uint64_t max_found = *max_element(mixn_timestamps.begin(), mixn_timestamps.end()); + if (min_found < min_mix_timestamp) + min_mix_timestamp = min_found; - return context; + if (max_found > max_mix_timestamp) + max_mix_timestamp = max_found; } - pair - construct_mstch_mixin_timescales( - const vector>& mixin_timestamp_groups, - uint64_t& min_mix_timestamp, - uint64_t& max_mix_timestamp - ) - { - mstch::array mixins_timescales; - - double timescale_scale {0.0}; // size of one '_' in days - - // initialize with some large and some numbers - min_mix_timestamp = server_timestamp*2L; - max_mix_timestamp = 0; - // find min and maximum timestamps - for (const vector& mixn_timestamps : mixin_timestamp_groups) - { - - uint64_t min_found = *min_element(mixn_timestamps.begin(), mixn_timestamps.end()); - uint64_t max_found = *max_element(mixn_timestamps.begin(), mixn_timestamps.end()); + min_mix_timestamp -= 3600; + max_mix_timestamp += 3600; - if (min_found < min_mix_timestamp) - min_mix_timestamp = min_found; + // make timescale maps for mixins in input with adjusted range + for (auto& mixn_timestamps : mixin_timestamp_groups) + { + // get mixins in time scale for visual representation + pair mixin_times_scale = xmreg::timestamps_time_scale( + mixn_timestamps, + max_mix_timestamp, + 170, + min_mix_timestamp); + + // save resolution of mixin timescales + timescale_scale = mixin_times_scale.second; + + // save the string timescales for later to show + mixins_timescales.push_back(mstch::map { + {"timescale", mixin_times_scale.first} + }); + } - if (max_found > max_mix_timestamp) - max_mix_timestamp = max_found; - } + return make_pair(mixins_timescales, timescale_scale); + } - min_mix_timestamp -= 3600; - max_mix_timestamp += 3600; + tx_details + get_tx_details(const transaction& tx, bool coinbase = false) + { + tx_details txd; - // make timescale maps for mixins in input with adjusted range - for (auto& mixn_timestamps : mixin_timestamp_groups) - { - // get mixins in time scale for visual representation - pair mixin_times_scale = xmreg::timestamps_time_scale( - mixn_timestamps, - max_mix_timestamp, - 170, - min_mix_timestamp); - - // save resolution of mixin timescales - timescale_scale = mixin_times_scale.second; - - // save the string timescales for later to show - mixins_timescales.push_back(mstch::map { - {"timescale", mixin_times_scale.first} - }); - } + // get tx hash + txd.hash = get_transaction_hash(tx); - return make_pair(mixins_timescales, timescale_scale); - } + // get tx public key from extra + // this check if there are two public keys + // due to previous bug with sining txs: + // https://github.com/monero-project/monero/pull/1358/commits/7abfc5474c0f86e16c405f154570310468b635c2 + txd.pk = xmreg::get_tx_pub_key_from_received_outs(tx); + // sum xmr in inputs and ouputs in the given tx + txd.xmr_inputs = sum_money_in_inputs(tx); + txd.xmr_outputs = sum_money_in_outputs(tx); - tx_details - get_tx_details(const transaction& tx, bool coinbase = false) - { - tx_details txd; + // get mixin number + txd.mixin_no = get_mixin_no(tx); - // get tx hash - txd.hash = get_transaction_hash(tx); + txd.fee = 0; - // get tx public key from extra - // this check if there are two public keys - // due to previous bug with sining txs: - // https://github.com/monero-project/monero/pull/1358/commits/7abfc5474c0f86e16c405f154570310468b635c2 - txd.pk = xmreg::get_tx_pub_key_from_received_outs(tx); + transaction tx_copy = tx; - // sum xmr in inputs and ouputs in the given tx - txd.xmr_inputs = sum_money_in_inputs(tx); - txd.xmr_outputs = sum_money_in_outputs(tx); + txd.json_representation = obj_to_json_str(tx_copy); - // get mixin number - txd.mixin_no = get_mixin_no(tx); - txd.fee = 0; + if (!coinbase && tx.vin.size() > 0) + { + // check if not miner tx + // i.e., for blocks without any user transactions + if (tx.vin.at(0).type() != typeid(txin_gen)) + { + // get tx fee + txd.fee = get_tx_fee(tx); + } + } - transaction tx_copy = tx; + get_payment_id(tx, txd.payment_id, txd.payment_id8); - txd.json_representation = obj_to_json_str(tx_copy); + // get tx size in bytes + txd.size = get_object_blobsize(tx); + txd.input_key_imgs = get_key_images(tx); + txd.output_pub_keys = get_ouputs(tx); - if (!coinbase && tx.vin.size() > 0) - { - // check if not miner tx - // i.e., for blocks without any user transactions - if (tx.vin.at(0).type() != typeid(txin_gen)) - { - // get tx fee - txd.fee = get_tx_fee(tx); - } - } + txd.extra = tx.extra; - get_payment_id(tx, txd.payment_id, txd.payment_id8); + // get tx signatures for each input + txd.signatures = tx.signatures; - // get tx size in bytes - txd.size = get_object_blobsize(tx); + // get tx version + txd.version = tx.version; - txd.input_key_imgs = get_key_images(tx); - txd.output_pub_keys = get_ouputs(tx); + // get unlock time + txd.unlock_time = tx.unlock_time; - txd.extra = tx.extra; + txd.no_confirmations = 0; - // get tx signatures for each input - txd.signatures = tx.signatures; + if (core_storage->have_tx(txd.hash)) + { + txd.blk_height = core_storage->get_db().get_tx_block_height(txd.hash); - // get tx version - txd.version = tx.version; + // get the current blockchain height. Just to check + uint64_t bc_height = + xmreg::MyLMDB::get_blockchain_height(mcore->get_blkchain_path()) - 1; - // get unlock time - txd.unlock_time = tx.unlock_time; + txd.no_confirmations = bc_height - (txd.blk_height - 1); + } - txd.no_confirmations = 0; + return txd; + } - if (core_storage->have_tx(txd.hash)) - { - txd.blk_height = core_storage->get_db().get_tx_block_height(txd.hash); + vector> + search_mempool(crypto::hash tx_hash = null_hash) + { - // get the current blockchain height. Just to check - uint64_t bc_height = - xmreg::MyLMDB::get_blockchain_height(mcore->get_blkchain_path()) - 1; + vector> found_txs; - txd.no_confirmations = bc_height - (txd.blk_height - 1); - } + // get txs in the mempool + std::vector mempool_txs; - return txd; + if (!rpc.get_mempool(mempool_txs)) + { + cerr << "Getting mempool failed " << endl; + return found_txs; } - vector> - search_mempool(crypto::hash tx_hash = null_hash) + // if we have tx blob disply more. + // this info can also be obtained from json that is + // normally returned by the RCP call (see below in detailed view) + if (HAVE_TX_BLOB) { + // get tx_blob if exists + //string tx_blob = get_tx_blob(_tx_info); - vector> found_txs; - - // get txs in the mempool - std::vector mempool_txs; - - if (!rpc.get_mempool(mempool_txs)) + for (size_t i = 0; i < mempool_txs.size(); ++i) { - cerr << "Getting mempool failed " << endl; - return found_txs; - } + // get transaction info of the tx in the mempool + tx_info _tx_info = mempool_txs.at(i); - // if we have tx blob disply more. - // this info can also be obtained from json that is - // normally returned by the RCP call (see below in detailed view) - if (HAVE_TX_BLOB) - { // get tx_blob if exists - //string tx_blob = get_tx_blob(_tx_info); + string tx_blob = get_tx_blob(_tx_info); - for (size_t i = 0; i < mempool_txs.size(); ++i) + if (tx_blob.empty()) { - // get transaction info of the tx in the mempool - tx_info _tx_info = mempool_txs.at(i); - - // get tx_blob if exists - string tx_blob = get_tx_blob(_tx_info); - - if (tx_blob.empty()) - { - cerr << "tx_blob is empty. Probably its not a custom deamon." << endl; - continue; - } + cerr << "tx_blob is empty. Probably its not a custom deamon." << endl; + continue; + } - // pare tx_blob into tx class - transaction tx; + // pare tx_blob into tx class + transaction tx; - if (!parse_and_validate_tx_from_blob( - tx_blob, tx)) - { - cerr << "Cant get tx from blob" << endl; - continue; - } + if (!parse_and_validate_tx_from_blob( + tx_blob, tx)) + { + cerr << "Cant get tx from blob" << endl; + continue; + } - // if we dont provide tx_hash, just get all txs in - // the mempool - if (tx_hash != null_hash) - { - // check if tx hash matches, and if yes, save it for return - if (tx_hash == get_transaction_hash(tx)) - { - found_txs.push_back(make_pair(_tx_info, tx)); - break; - } - } - else + // if we dont provide tx_hash, just get all txs in + // the mempool + if (tx_hash != null_hash) + { + // check if tx hash matches, and if yes, save it for return + if (tx_hash == get_transaction_hash(tx)) { - found_txs.push_back(make_pair(_tx_info, tx)); + found_txs.push_back(make_pair(_tx_info, tx)); + break; } - } - } - else - { - // @TODO make tx_info from json - // if dont have tx_blob member, construct tx_info - // from json obtained from the rpc call - } + else + { + found_txs.push_back(make_pair(_tx_info, tx)); + } - return found_txs; + } } - - pair - get_age(uint64_t timestamp1, uint64_t timestamp2, bool full_format = 0) + else { + // @TODO make tx_info from json + // if dont have tx_blob member, construct tx_info + // from json obtained from the rpc call + } - pair age_pair; + return found_txs; + } - // calculate difference between server and block timestamps - array delta_time = timestamp_difference( - timestamp1, timestamp2); + pair + get_age(uint64_t timestamp1, uint64_t timestamp2, bool full_format = 0) + { - // default format for age - string age_str = fmt::format("{:02d}:{:02d}:{:02d}", - delta_time[2], delta_time[3], - delta_time[4]); + pair age_pair; - string age_format {"[h:m:s]"}; + // calculate difference between server and block timestamps + array delta_time = timestamp_difference( + timestamp1, timestamp2); - // if have days or years, change age format - if (delta_time[0] > 0 || full_format == true) - { - age_str = fmt::format("{:02d}:{:03d}:{:02d}:{:02d}:{:02d}", - delta_time[0], delta_time[1], delta_time[2], - delta_time[3], delta_time[4]); + // default format for age + string age_str = fmt::format("{:02d}:{:02d}:{:02d}", + delta_time[2], delta_time[3], + delta_time[4]); - age_format = string("[y:d:h:m:s]"); - } - else if (delta_time[1] > 0) - { - age_str = fmt::format("{:02d}:{:02d}:{:02d}:{:02d}", - delta_time[1], delta_time[2], - delta_time[3], delta_time[4]); + string age_format {"[h:m:s]"}; - age_format = string("[d:h:m:s]"); - } + // if have days or years, change age format + if (delta_time[0] > 0 || full_format == true) + { + age_str = fmt::format("{:02d}:{:03d}:{:02d}:{:02d}:{:02d}", + delta_time[0], delta_time[1], delta_time[2], + delta_time[3], delta_time[4]); - age_pair.first = age_str; - age_pair.second = age_format; + age_format = string("[y:d:h:m:s]"); + } + else if (delta_time[1] > 0) + { + age_str = fmt::format("{:02d}:{:02d}:{:02d}:{:02d}", + delta_time[1], delta_time[2], + delta_time[3], delta_time[4]); - return age_pair; + age_format = string("[d:h:m:s]"); } - pair - sum_xmr_outputs(const string& json_str) - { - pair sum_xmr {0, 0}; + age_pair.first = age_str; + age_pair.second = age_format; - rapidjson::Document json; + return age_pair; + } - if (json.Parse(json_str.c_str()).HasParseError()) - { - cerr << "Failed to parse JSON" << endl; - return sum_xmr; - } + pair + sum_xmr_outputs(const string& json_str) + { + pair sum_xmr {0, 0}; - // get information about outputs - const rapidjson::Value& vout = json["vout"]; + rapidjson::Document json; - if (vout.IsArray()) - { + if (json.Parse(json_str.c_str()).HasParseError()) + { + cerr << "Failed to parse JSON" << endl; + return sum_xmr; + } - for (rapidjson::SizeType i = 0; i < vout.Size(); ++i) - { - //print(" - {:s}, {:0.8f} xmr\n", - // vout[i]["target"]["key"].GetString(), - // XMR_AMOUNT(vout[i]["amount"].GetUint64())); + // get information about outputs + const rapidjson::Value& vout = json["vout"]; - sum_xmr.first += vout[i]["amount"].GetUint64(); - } + if (vout.IsArray()) + { + + for (rapidjson::SizeType i = 0; i < vout.Size(); ++i) + { + //print(" - {:s}, {:0.8f} xmr\n", + // vout[i]["target"]["key"].GetString(), + // XMR_AMOUNT(vout[i]["amount"].GetUint64())); - sum_xmr.second = vout.Size(); + sum_xmr.first += vout[i]["amount"].GetUint64(); } - return sum_xmr; + sum_xmr.second = vout.Size(); } - pair - sum_xmr_inputs(const string& json_str) - { - pair sum_xmr {0, 0}; + return sum_xmr; + } - rapidjson::Document json; + pair + sum_xmr_inputs(const string& json_str) + { + pair sum_xmr {0, 0}; - if (json.Parse(json_str.c_str()).HasParseError()) - { - cerr << "Failed to parse JSON" << endl; - return sum_xmr; - } + rapidjson::Document json; + + if (json.Parse(json_str.c_str()).HasParseError()) + { + cerr << "Failed to parse JSON" << endl; + return sum_xmr; + } - // get information about inputs - const rapidjson::Value& vin = json["vin"]; + // get information about inputs + const rapidjson::Value& vin = json["vin"]; - if (vin.IsArray()) - { - // print("Input key images:\n"); + if (vin.IsArray()) + { + // print("Input key images:\n"); - for (rapidjson::SizeType i = 0; i < vin.Size(); ++i) + for (rapidjson::SizeType i = 0; i < vin.Size(); ++i) + { + if (vin[i].HasMember("key")) { - if (vin[i].HasMember("key")) - { - const rapidjson::Value& key_img = vin[i]["key"]; + const rapidjson::Value& key_img = vin[i]["key"]; - // print(" - {:s}, {:0.8f} xmr\n", - // key_img["k_image"].GetString(), - // XMR_AMOUNT(key_img["amount"].GetUint64())); + // print(" - {:s}, {:0.8f} xmr\n", + // key_img["k_image"].GetString(), + // XMR_AMOUNT(key_img["amount"].GetUint64())); - sum_xmr.first += key_img["amount"].GetUint64(); - } + sum_xmr.first += key_img["amount"].GetUint64(); } - - sum_xmr.second = vin.Size(); } - return sum_xmr; + sum_xmr.second = vin.Size(); } + return sum_xmr; + } - vector - get_mixin_no_in_txs(const string& json_str) - { - vector mixin_no; - rapidjson::Document json; + vector + get_mixin_no_in_txs(const string& json_str) + { + vector mixin_no; - if (json.Parse(json_str.c_str()).HasParseError()) - { - cerr << "Failed to parse JSON" << endl; - return mixin_no; - } + rapidjson::Document json; + + if (json.Parse(json_str.c_str()).HasParseError()) + { + cerr << "Failed to parse JSON" << endl; + return mixin_no; + } - // get information about inputs - const rapidjson::Value& vin = json["vin"]; + // get information about inputs + const rapidjson::Value& vin = json["vin"]; - if (vin.IsArray()) - { - // print("Input key images:\n"); + if (vin.IsArray()) + { + // print("Input key images:\n"); - for (rapidjson::SizeType i = 0; i < vin.Size(); ++i) + for (rapidjson::SizeType i = 0; i < vin.Size(); ++i) + { + if (vin[i].HasMember("key")) { - if (vin[i].HasMember("key")) - { - const rapidjson::Value& key_img = vin[i]["key"]; + const rapidjson::Value& key_img = vin[i]["key"]; - // print(" - {:s}, {:0.8f} xmr\n", - // key_img["k_image"].GetString(), - // XMR_AMOUNT(key_img["amount"].GetUint64())); + // print(" - {:s}, {:0.8f} xmr\n", + // key_img["k_image"].GetString(), + // XMR_AMOUNT(key_img["amount"].GetUint64())); - mixin_no.push_back(key_img["key_offsets"].Size()); - } + mixin_no.push_back(key_img["key_offsets"].Size()); } } - - return mixin_no; } + return mixin_no; + } - string - get_full_page(string& middle) - { - // set last git commit date based on - // autogenrated version.h during compilation - static const mstch::map footer_context { - {"last_git_commit_hash", string {GIT_COMMIT_HASH}}, - {"last_git_commit_date", string {GIT_COMMIT_DATETIME}} - }; - string footer_html = mstch::render(xmreg::read(TMPL_FOOTER), footer_context); + string + get_full_page(string& middle) + { + // set last git commit date based on + // autogenrated version.h during compilation + static const mstch::map footer_context { + {"last_git_commit_hash", string {GIT_COMMIT_HASH}}, + {"last_git_commit_date", string {GIT_COMMIT_DATETIME}} + }; - return xmreg::read(TMPL_HEADER) - + middle - + footer_html; - } + string footer_html = mstch::render(xmreg::read(TMPL_FOOTER), footer_context); - void - add_css_style(mstch::map& context) - { - context["css_styles"] = mstch::lambda{[&](const std::string& text) -> mstch::node { - return this->css_styles; - }}; - } + return xmreg::read(TMPL_HEADER) + + middle + + footer_html; + } - }; + string + get_full_page(string& middle) + { + return xmreg::read(TMPL_HEADER) + + middle + + xmreg::read(TMPL_FOOTER); + } + + void + add_css_style(mstch::map& context) + { + context["css_styles"] = mstch::lambda{[&](const std::string& text) -> mstch::node { + return this->css_styles; + }}; + } + +}; } diff --git a/src/templates/header.html b/src/templates/header.html index d0c96f5..864327e 100644 --- a/src/templates/header.html +++ b/src/templates/header.html @@ -25,8 +25,8 @@ {{^testnet}}
- +
diff --git a/src/tools.cpp b/src/tools.cpp index a43ef55..d5b8b7b 100644 --- a/src/tools.cpp +++ b/src/tools.cpp @@ -143,23 +143,14 @@ namespace xmreg return bf::path(remove_trailing_path_separator(path_str)); } - string timestamp_to_str(time_t timestamp, const char* format) { + auto a_time_point = chrono::system_clock::from_time_t(timestamp); + auto utc = date::to_utc_time(chrono::system_clock::from_time_t(timestamp)); + auto sys_time = date::to_sys_time(utc); - const int TIME_LENGTH = 60; - - char str_buff[TIME_LENGTH]; - - tm *tm_ptr; - tm_ptr = localtime(×tamp); - - size_t len; - - len = std::strftime(str_buff, TIME_LENGTH, format, tm_ptr); - - return string(str_buff, len); + return date::format(format, date::floor(sys_time)); } @@ -516,36 +507,6 @@ namespace xmreg } - /** - * Rough estimate of block height from the time provided - * - */ - uint64_t - estimate_bc_height(const string& date, const char* format) - { - const pt::ptime MONERO_START {gt::date(2014,04,18)}; - const uint64_t MONERO_BLOCK_TIME {60}; // seconds - - dateparser parser {format}; - - if (!parser(date)) - { - throw runtime_error(string("Date format is incorrect: ") + date); - } - - pt::ptime requested_date = parser.pt; - - if (requested_date < MONERO_START) - { - return 0; - } - - pt::time_duration td = requested_date - MONERO_START; - - return static_cast(td.total_seconds()) / MONERO_BLOCK_TIME; - } - - array timestamp_difference(uint64_t t1, uint64_t t2) { @@ -890,5 +851,21 @@ namespace xmreg return null_pkey; } + date::sys_seconds + parse(const std::string& str, string format) + { + std::istringstream in(str); + date::sys_seconds tp; + in >> date::parse(format, tp); + if (in.fail()) + { + in.clear(); + in.str(str); + in >> date::parse(format, tp); + } + return tp; + } + + } diff --git a/src/tools.h b/src/tools.h index ed2151a..333fd66 100644 --- a/src/tools.h +++ b/src/tools.h @@ -1,5 +1,5 @@ // -// Created by marcin on 5/11/15. +// Created by mwo on 5/11/15. // #ifndef XMREG01_TOOLS_H @@ -16,8 +16,8 @@ #include "monero_headers.h" #include "tx_details.h" -#include "../ext/dateparser.h" #include "../ext/infix_iterator.h" +#include "../ext/date/tz.h" #include #include @@ -36,221 +36,208 @@ */ namespace xmreg { - using namespace cryptonote; - using namespace crypto; - using namespace std; - - namespace bf = boost::filesystem; - namespace pt = boost::posix_time; - namespace gt = boost::gregorian; - namespace lt = boost::local_time; - - - struct outputs_visitor - { - std::vector& m_output_keys; - - const Blockchain& m_bch; - - outputs_visitor(std::vector& output_keys, const Blockchain& bch) : - m_output_keys(output_keys), m_bch(bch) - { - } - - bool handle_output(uint64_t unlock_time, const crypto::public_key &pubkey) - { - //check tx unlock time -// if (!m_bch.is_tx_spendtime_unlocked(unlock_time)) -// { -// LOG_PRINT_L1("One of outputs for one of inputs has wrong tx.unlock_time = " << unlock_time); -// return false; -// } - - m_output_keys.push_back(pubkey); - - return true; - } - }; +using namespace cryptonote; +using namespace crypto; +using namespace std; +namespace bf = boost::filesystem; +namespace pt = boost::posix_time; +namespace gt = boost::gregorian; +namespace lt = boost::local_time; +struct outputs_visitor +{ + std::vector& m_output_keys; + const Blockchain& m_bch; + outputs_visitor(std::vector& output_keys, const Blockchain& bch) : + m_output_keys(output_keys), m_bch(bch) + { + } - template - bool - parse_str_secret_key(const string& key_str, T& secret_key); + bool handle_output(uint64_t unlock_time, const crypto::public_key &pubkey) + { + m_output_keys.push_back(pubkey); + return true; + } +}; - bool - get_tx_pub_key_from_str_hash(Blockchain& core_storage, - const string& hash_str, - transaction& tx); +template +bool +parse_str_secret_key(const string& key_str, T& secret_key); - bool - parse_str_address(const string& address_str, - account_public_address& address, - bool testnet = false); - inline bool - is_separator(char c); +bool +get_tx_pub_key_from_str_hash(Blockchain& core_storage, + const string& hash_str, + transaction& tx); - string - print_address(const account_public_address& address, +bool +parse_str_address(const string& address_str, + account_public_address& address, bool testnet = false); - string - print_sig (const signature& sig); +inline bool +is_separator(char c); - string - remove_trailing_path_separator(const string& in_path); +string +print_address(const account_public_address& address, + bool testnet = false); - bf::path - remove_trailing_path_separator(const bf::path& in_path); +string +print_sig (const signature& sig); - string - timestamp_to_str(time_t timestamp, const char* format = "%F %T"); +string +remove_trailing_path_separator(const string& in_path); +bf::path +remove_trailing_path_separator(const bf::path& in_path); - ostream& - operator<< (ostream& os, const account_public_address& addr); +string +timestamp_to_str(time_t timestamp, const char* format = "%F %T"); - string - get_default_lmdb_folder(bool testnet = false); +ostream& +operator<< (ostream& os, const account_public_address& addr); - bool - generate_key_image(const crypto::key_derivation& derivation, - const std::size_t output_index, - const crypto::secret_key& sec_key, - const crypto::public_key& pub_key, - crypto::key_image& key_img); - bool - get_blockchain_path(const boost::optional& bc_path, - bf::path& blockchain_path, - bool testnet = false); +string +get_default_lmdb_folder(bool testnet = false); - uint64_t - sum_money_in_outputs(const transaction& tx); +bool +generate_key_image(const crypto::key_derivation& derivation, + const std::size_t output_index, + const crypto::secret_key& sec_key, + const crypto::public_key& pub_key, + crypto::key_image& key_img); - uint64_t - sum_money_in_inputs(const transaction& tx); +bool +get_blockchain_path(const boost::optional& bc_path, + bf::path& blockchain_path, + bool testnet = false); - array - sum_money_in_tx(const transaction& tx); +uint64_t +sum_money_in_outputs(const transaction& tx); - array - sum_money_in_txs(const vector& txs); +uint64_t +sum_money_in_inputs(const transaction& tx); - uint64_t - sum_fees_in_txs(const vector& txs); +array +sum_money_in_tx(const transaction& tx); - uint64_t - get_mixin_no(const transaction& tx); +array +sum_money_in_txs(const vector& txs); - vector - get_mixin_no_in_txs(const vector& txs); +uint64_t +sum_fees_in_txs(const vector& txs); - vector> - get_ouputs(const transaction& tx); +uint64_t +get_mixin_no(const transaction& tx); - vector> - get_ouputs_tuple(const transaction& tx); +vector +get_mixin_no_in_txs(const vector& txs); - vector - get_key_images(const transaction& tx); +vector> +get_ouputs(const transaction& tx); +vector> +get_ouputs_tuple(const transaction& tx); - bool - get_payment_id(const vector& extra, - crypto::hash& payment_id, - crypto::hash8& payment_id8); +vector +get_key_images(const transaction& tx); - bool - get_payment_id(const transaction& tx, - crypto::hash& payment_id, - crypto::hash8& payment_id8); +bool +get_payment_id(const vector& extra, + crypto::hash& payment_id, + crypto::hash8& payment_id8); - inline void - enable_monero_log() { - uint32_t log_level = 0; - epee::log_space::get_set_log_detalisation_level(true, log_level); - epee::log_space::log_singletone::add_logger(LOGGER_CONSOLE, NULL, NULL); - } +bool +get_payment_id(const transaction& tx, + crypto::hash& payment_id, + crypto::hash8& payment_id8); - uint64_t - estimate_bc_height(const string& date, const char* format = "%Y-%m-%d"); +inline void +enable_monero_log() { + uint32_t log_level = 0; + epee::log_space::get_set_log_detalisation_level(true, log_level); + epee::log_space::log_singletone::add_logger(LOGGER_CONSOLE, NULL, NULL); +} - inline double - get_xmr(uint64_t core_amount) - { - return static_cast(core_amount) / 1e12; - } +inline double +get_xmr(uint64_t core_amount) +{ + return static_cast(core_amount) / 1e12; +} + +array +timestamp_difference(uint64_t t1, uint64_t t2); - array - timestamp_difference(uint64_t t1, uint64_t t2); +string +read(string filename); - string - read(string filename); +/** + * prints an iterable such as vector + */ +template +void print_iterable(const T & elems) { - /** - * prints an iterable such as vector - */ - template - void print_iterable(const T & elems) { + infix_ostream_iterator + oiter(std::cout, ","); - infix_ostream_iterator - oiter(std::cout, ","); + std::cout << "["; + std::copy(elems.begin(), elems.end(),oiter); + std::cout << "]" << std::endl; +} - std::cout << "["; - std::copy(elems.begin(), elems.end(),oiter); - std::cout << "]" << std::endl; - } +pair +timestamps_time_scale(const vector& timestamps, + uint64_t timeN, uint64_t resolution = 80, + uint64_t time0 = 1397818193 /* timestamp of the second block */); - pair - timestamps_time_scale(const vector& timestamps, - uint64_t timeN, uint64_t resolution = 80, - uint64_t time0 = 1397818193 /* timestamp of the second block */); +time_t +ptime_to_time_t(const pt::ptime& in_ptime); - time_t - ptime_to_time_t(const pt::ptime& in_ptime); +bool +decode_ringct(const rct::rctSig & rv, + const crypto::public_key pub, + const crypto::secret_key &sec, + unsigned int i, + rct::key & mask, + uint64_t & amount); - bool - decode_ringct(const rct::rctSig & rv, - const crypto::public_key pub, - const crypto::secret_key &sec, - unsigned int i, - rct::key & mask, - uint64_t & amount); +bool +url_decode(const std::string& in, std::string& out); - bool - url_decode(const std::string& in, std::string& out); +map +parse_crow_post_data(const string& req_body); - map - parse_crow_post_data(const string& req_body); +bool +get_dummy_account_keys(account_keys& dummy_keys, bool testnet = false); - bool - get_dummy_account_keys(account_keys& dummy_keys, bool testnet = false); +// from wallet2::decrypt +string +decrypt(const std::string &ciphertext, + const crypto::secret_key &skey, + bool authenticated = true); - // from wallet2::decrypt - string - decrypt(const std::string &ciphertext, - const crypto::secret_key &skey, - bool authenticated = true); +// based on +// crypto::public_key wallet2::get_tx_pub_key_from_received_outs(const tools::wallet2::transfer_details &td) const +public_key +get_tx_pub_key_from_received_outs(const transaction &tx); - // based on - // crypto::public_key wallet2::get_tx_pub_key_from_received_outs(const tools::wallet2::transfer_details &td) const - public_key - get_tx_pub_key_from_received_outs(const transaction &tx); +date::sys_seconds +parse(const std::string& str, string format="%Y-%m-%d %H:%M:%S"); }