civil.h 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355
  1. #pragma once
  2. #include <util/datetime/base.h>
  3. #include <contrib/libs/cctz/include/cctz/civil_time.h>
  4. #include <contrib/libs/cctz/include/cctz/time_zone.h>
  5. #include <util/generic/strbuf.h>
  6. #include <util/generic/string.h>
  7. #include <util/generic/yexception.h>
  8. #if __clang__ && __cpp_constexpr >= 201304
  9. #define CONSTEXPR_M constexpr
  10. #else
  11. #define CONSTEXPR_M inline
  12. #endif
  13. namespace NDatetime {
  14. /** Exception class which throws when time zone is not valid
  15. */
  16. class TInvalidTimezone: public yexception {
  17. };
  18. using TSystemClock = std::chrono::system_clock;
  19. using TTimePoint = std::chrono::time_point<TSystemClock>;
  20. /*
  21. * An opaque class representing past, present, and future rules of
  22. * mapping between absolute and civil times in a given region.
  23. * It is very lightweight and may be passed by value.
  24. */
  25. using TTimeZone = cctz::time_zone;
  26. using TCivilYear = cctz::civil_year;
  27. using TCivilMonth = cctz::civil_month;
  28. using TCivilDay = cctz::civil_day;
  29. using TCivilHour = cctz::civil_hour;
  30. using TCivilMinute = cctz::civil_minute;
  31. using TCivilSecond = cctz::civil_second;
  32. using TWeekday = cctz::weekday;
  33. using TDiff = cctz::diff_t;
  34. using TYear = cctz::year_t;
  35. using TMonth = cctz::detail::month_t;
  36. enum class ECivilUnit : int {
  37. Second = 0,
  38. Minute = 1,
  39. Hour = 2,
  40. Day = 3,
  41. Month = 4,
  42. Year = 5
  43. };
  44. namespace NDetail {
  45. template <typename T>
  46. struct TGetCivilUnit;
  47. template <ECivilUnit Unit>
  48. struct TGetCivilTime;
  49. }
  50. template <typename T>
  51. CONSTEXPR_M ECivilUnit GetCivilUnit(const T& = {}) {
  52. return NDetail::TGetCivilUnit<T>::Value;
  53. }
  54. template <ECivilUnit Unit>
  55. using TCivilTime = typename NDetail::TGetCivilTime<Unit>::TResult;
  56. /**
  57. * Class with variable unit diff.
  58. */
  59. struct TCivilDiff {
  60. TDiff Value = 0;
  61. ECivilUnit Unit = ECivilUnit::Second;
  62. TCivilDiff() = default;
  63. TCivilDiff(TDiff value, ECivilUnit unit)
  64. : Value(value)
  65. , Unit(unit)
  66. {
  67. }
  68. /**
  69. * Straightfoward implementation of operators <, == and unit conversions
  70. * can be potentially misleading (e.g. 1 month == 30 days?);
  71. * we leave it to user to implement it properly for each application.
  72. */
  73. };
  74. /**
  75. * Gets the time zone by an IANA name.
  76. * @param name A name in IANA format (e.g., "Europe/Moscow").
  77. * @note After you request a time zone for the first time, it is cached
  78. * in a (thread-safe) cache, so subsequent requests to the
  79. * same time zone should be extremely fast.
  80. * @throw TInvalidTimezone if the name is invalid.
  81. * @see http://www.iana.org/time-zones
  82. */
  83. TTimeZone GetTimeZone(TStringBuf name);
  84. /**
  85. *Helper for get timezone offset from timezone string
  86. * Examples:
  87. * "+01:30" -> 5400
  88. * "-10" -> -36000
  89. * "-0200" -> -7200
  90. */
  91. bool TryParseOffset(TStringBuf name, int& offset);
  92. /**
  93. * Returns a time zone that is a fixed offset (seconds east) from UTC.
  94. * Note: If the absolute value of the offset is greater than 24 hours
  95. * you'll get UTC (i.e., zero offset) instead.
  96. */
  97. TTimeZone GetFixedTimeZone(const long offset);
  98. /** Convert civil time from one timezone to another
  99. * @param[in] src is source time with 'from' timezone
  100. * @param[in] from is a initial timezone
  101. * @param[in] from is a destination timezone
  102. * @return a civil time
  103. */
  104. template <typename T>
  105. T Convert(const T& src, const TTimeZone& from, const TTimeZone& to) {
  106. return cctz::convert(cctz::convert(src, from), to);
  107. }
  108. /** Convert absolute time to civil time by rules from timezone.
  109. * @param[in] absTime is an absolute time which is used to convert
  110. * @param[in] tz is a loaded timezone
  111. * @return a civil time
  112. *
  113. * Note: This function doesn't work properly for dates before 1 Jan 1970!
  114. */
  115. TCivilSecond Convert(const TInstant& absTime, const TTimeZone& tz);
  116. /** Convert absolute time to civil time by rules from timezone which will be loaded.
  117. * @throw InvalidTimezone if the name is invalid.
  118. * @param[in] absTime is an absolute time which is used to convert
  119. * @param[in] tzName is a timezone name which will be loaded
  120. * @return a civil time
  121. *
  122. * Note: This function doesn't work properly for dates before 1 Jan 1970!
  123. */
  124. TCivilSecond Convert(const TInstant& absTime, TStringBuf tzName);
  125. /** Convert a civil time to absolute by using rules from timezone
  126. *
  127. * Note: This function doesn't work properly for dates before 1 Jan 1970!
  128. */
  129. TInstant Convert(const TCivilSecond& s, const TTimeZone& tz);
  130. /** Just to simply calculations between dates/times.
  131. * NDatetime::Calc<TCivilDay>(TCivilSecond(2001, 1, 1, 10, 10, 10), 5); // returns TCivilDay(2001, 1, 6);
  132. * @param[in] tp is a timepoint with which calc will be
  133. * @param[in] diff is quantity of which will be added (of type T) to the tp
  134. * @return the calculated T type
  135. */
  136. template <typename T, typename S>
  137. inline T Calc(const S& tp, TDiff diff) {
  138. return T(tp) + diff;
  139. }
  140. /** Non-template methods for adding dates/times.
  141. * @param[in] tp is a timepoint with which calc will be
  142. * @param[in] diff is quantity of which will be added to the tp
  143. * @return the calculated TCivilSecond object
  144. */
  145. TCivilSecond AddYears(const TCivilSecond& tp, TDiff diff);
  146. TCivilSecond AddMonths(const TCivilSecond& tp, TDiff diff);
  147. TCivilSecond AddDays(const TCivilSecond& tp, TDiff diff);
  148. TCivilSecond AddHours(const TCivilSecond& tp, TDiff diff);
  149. TCivilSecond AddMinutes(const TCivilSecond& tp, TDiff diff);
  150. TCivilSecond AddSeconds(const TCivilSecond& tp, TDiff diff);
  151. /** Method to add TCivilDiff
  152. * @param[in] tp is a timepoint with which calc will be
  153. * @param[in] diff is quantity of which will be added to the tp
  154. * @return the calculated TCivilSecond object
  155. */
  156. TCivilSecond AddCivil(const TCivilSecond& tp, TCivilDiff diff);
  157. /** Method to subtract to civil dates/times and get TCivilDiff.
  158. * First casts to unit, then subtracts;
  159. * e.g. GetCivilDiff(2017-10-01, 2017-09-30, Month) = 1.
  160. *
  161. * @param[in] tpX is a timepoint
  162. * @param[in] tpY is a timepoint to subtract from tpX
  163. * @param[in] unit is a civil time unit to use in subtraction
  164. * @return the calculated diff as TCivilDiff object
  165. */
  166. TCivilDiff GetCivilDiff(const TCivilSecond& tpX, const TCivilSecond& tpY, ECivilUnit unit);
  167. /** Formats the given TimePoint in the given TTimeZone according to
  168. * the provided format string. Uses strftime()-like formatting options,
  169. * with the following extensions:
  170. *
  171. * - %Ez - RFC3339-compatible numeric time zone (+hh:mm or -hh:mm)
  172. * - %E#S - Seconds with # digits of fractional precision
  173. * - %E*S - Seconds with full fractional precision (a literal '*')
  174. * - %E#f - Fractional seconds with # digits of precision
  175. * - %E*f - Fractional seconds with full precision (a literal '*')
  176. * - %E4Y - Four-character years (-999 ... -001, 0000, 0001 ... 9999)
  177. *
  178. * Note that %E0S behaves like %S, and %E0f produces no characters. In
  179. * contrast %E*f always produces at least one digit, which may be '0'.
  180. *
  181. * Note that %Y produces as many characters as it takes to fully render the
  182. * year. A year outside of [-999:9999] when formatted with %E4Y will produce
  183. * more than four characters, just like %Y.
  184. *
  185. * Tip: Format strings should include the UTC offset (e.g., %z or %Ez) so that
  186. * the resultng string uniquely identifies an absolute time.
  187. *
  188. * Example:
  189. * NDatetime::TTimeZone lax = NDatetime::GetTimeZone("America/Los_Angeles");
  190. * NDatetime::TCivilSecond tp(2013, 1, 2, 3, 4, 5);
  191. * TString f = NDatetime::Format("%H:%M:%S", tp, lax); // "03:04:05"
  192. * TString f = NDatetime::Format("%H:%M:%E3S", tp, lax); //"03:04:05.000"
  193. */
  194. template <typename TP>
  195. TString Format(TStringBuf fmt, const TP& tp, const TTimeZone& tz) {
  196. return TString(cctz::format(static_cast<std::string>(fmt), TTimePoint(cctz::convert(tp, tz)), tz));
  197. }
  198. /** Returns the weekday by day.
  199. * @param[in] day is a given day
  200. * @return a weekday (enum)
  201. */
  202. CONSTEXPR_M TWeekday GetWeekday(const TCivilDay& day) noexcept {
  203. return cctz::get_weekday(day);
  204. }
  205. /** Returns the weekday by day.
  206. * @param[in] second is a given seconds
  207. * @return a weekday (enum)
  208. */
  209. CONSTEXPR_M TWeekday GetWeekday(const TCivilSecond& second) noexcept {
  210. return cctz::get_weekday(second);
  211. }
  212. /** Returns the TCivilDay that strictly follows or precedes the given
  213. * civil_day, and that falls on the given weekday.
  214. * @code
  215. For example, given:
  216. August 2015
  217. Su Mo Tu We Th Fr Sa
  218. 1
  219. 2 3 4 5 6 7 8
  220. 9 10 11 12 13 14 15
  221. 16 17 18 19 20 21 22
  222. 23 24 25 26 27 28 29
  223. 30 31
  224. TCivilDay a(2015, 8, 13); // GetWeekday(a) == TWeekday::thursday
  225. TCivilDay b = NextWeekday(a, TWeekday::thursday); // b = 2015-08-20
  226. TCivilDay c = PrevWeekday(a, TWeekday::thursday); // c = 2015-08-06
  227. TCivilDay d = NearestWeekday(a, TWeekday::thursday); // d = 2015-08-13
  228. TCivilDay e = ...
  229. // Gets the following Thursday if e is not already Thursday
  230. TCivilDay thurs1 = PrevWeekday(e, TWeekday::thursday) + 7;
  231. // Gets the previous Thursday if e is not already Thursday
  232. TCivilDay thurs2 = NextWeekday(e, TWeekday::thursday) - 7;
  233. * @endcode
  234. * @see PrevWeekday()
  235. * @see NearestWeekday()
  236. * @param[in] cd is a current day
  237. * @param[in] wd is a weekday which wanetd for find on next week
  238. * @return a civil day on weekday next week
  239. */
  240. CONSTEXPR_M TCivilDay NextWeekday(const TCivilDay& cd, TWeekday wd) noexcept {
  241. return cctz::next_weekday(cd, wd);
  242. }
  243. /** Returns prev weekday. See the description of NextWeekday().
  244. * @see NextWeekday()
  245. * @see NearestWeekday()
  246. * @param[in] cd is a current day
  247. * @param[in] wd is a weekday which is looking for (backward)
  248. * @return a first occurence of the given weekday (backward)
  249. */
  250. CONSTEXPR_M TCivilDay PrevWeekday(const TCivilDay& cd, TWeekday wd) noexcept {
  251. return cctz::prev_weekday(cd, wd);
  252. }
  253. /** Find a nearest occurence of the given weekday forward (could be current day).
  254. * @see NextWeekday()
  255. * @param[in] cd is a current day
  256. * @param[in] wd is a weekday which is looking for (current day or later)
  257. * @return first occurence (including current day) of the given weekday
  258. */
  259. CONSTEXPR_M TCivilDay NearestWeekday(const TCivilDay& cd, TWeekday wd) noexcept {
  260. return get_weekday(cd) != wd ? next_weekday(cd, wd) : cd;
  261. }
  262. /** Find the date of the given weekday within the given week.
  263. * @param[in] cd is a current day
  264. * @param[in] wd is a requested week day
  265. * @return day within a week of a given day
  266. */
  267. CONSTEXPR_M TCivilDay WeekdayOnTheWeek(const TCivilDay& cd, TWeekday wd) noexcept {
  268. const auto d = get_weekday(cd);
  269. if (d == wd)
  270. return cd;
  271. return d < wd ? NextWeekday(cd, wd) : PrevWeekday(cd, wd);
  272. }
  273. /** Returns an absolute day of year by given day.
  274. */
  275. CONSTEXPR_M int GetYearDay(const TCivilDay& cd) noexcept {
  276. return cctz::get_yearday(cd);
  277. }
  278. CONSTEXPR_M int DaysPerMonth(TYear year, TMonth month) noexcept {
  279. return cctz::detail::impl::days_per_month(year, month);
  280. }
  281. CONSTEXPR_M int DaysPerYear(TYear year, TMonth month) noexcept {
  282. return cctz::detail::impl::days_per_year(year, month);
  283. }
  284. /** Calculate week number for the given date
  285. * @param[in] cd is a current day
  286. * @param[in] usePreviousYear (optional) true if calculate week number from previous year
  287. *
  288. * The week number starts from 1 for the first week, where Thursday exist (see ISO8601), i.e.
  289. * Jan 2021
  290. * week# mo tu we th fr sa su
  291. * 53 1 2 3
  292. * 01 4 5 6 7 8 9 10
  293. * 02 11 ...
  294. * Jan 2020
  295. * week# mo tu we th fr sa su
  296. * 01 1 2 3 4 5
  297. * 02 6 7 8 9 10 11...
  298. *
  299. * In case if you received zero value, you may call function again with usePreviousYear=true
  300. * Also you may use usePreviousYear to calculate week difference between two dates in different year
  301. */
  302. CONSTEXPR_M int GetYearWeek(const TCivilDay& cd, bool usePreviousYear = false) noexcept {
  303. const auto jan1 = NDatetime::GetWeekday(NDatetime::TCivilDay{cd.year() - (usePreviousYear ? 1 : 0), 1, 1});
  304. const auto daysCount = GetYearDay(cd) + (usePreviousYear ? DaysPerYear(cd.year()-1, 1) : 0);
  305. return (daysCount + static_cast<int>(jan1) - 1) / 7 + (jan1 == cctz::weekday::monday || jan1 == cctz::weekday::tuesday || jan1 == cctz::weekday::wednesday);
  306. }
  307. }
  308. #include "civil-inl.h"
  309. #undef CONSTEXPR_M