civil.cpp 8.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247
  1. #include "civil.h"
  2. #include <util/stream/output.h>
  3. #include <util/stream/format.h>
  4. #include <util/string/ascii.h>
  5. namespace {
  6. bool TryParseInt(TStringBuf& s, int& dst, size_t maxDigits) {
  7. int res = 0;
  8. size_t i = 0;
  9. while (i < maxDigits && !s.empty() && IsAsciiDigit(s[0])) {
  10. res = res * 10 + (s[0] - '0');
  11. ++i;
  12. s.Skip(1);
  13. }
  14. if (i == 0) {
  15. return false;
  16. }
  17. dst = res;
  18. return true;
  19. }
  20. bool TryParseUTCGMTOffsetTimezone(TStringBuf name, int& offset) {
  21. static constexpr TStringBuf OFFSET_PREFIX_UTC = "UTC";
  22. static constexpr TStringBuf OFFSET_PREFIX_GMT = "GMT";
  23. if (!name.SkipPrefix(OFFSET_PREFIX_UTC)) {
  24. // Sometimes timezones from client devices look like 'GMT+03:00'
  25. // This format is not standard but can be translated like UTC+xxx
  26. if (!name.SkipPrefix(OFFSET_PREFIX_GMT)) {
  27. return false;
  28. }
  29. }
  30. return NDatetime::TryParseOffset(name, offset);
  31. }
  32. } // anonymous namespace
  33. namespace NDatetime {
  34. bool TryParseOffset(TStringBuf name, int& offset) {
  35. if (name.empty()) {
  36. return false;
  37. }
  38. bool negative;
  39. if (name[0] == '+') {
  40. negative = false;
  41. } else if (name[0] == '-') {
  42. negative = true;
  43. } else {
  44. return false;
  45. }
  46. name.Skip(1);
  47. int hour;
  48. int minute = 0;
  49. if (!TryParseInt(name, hour, 2) || hour > 24) {
  50. return false;
  51. }
  52. if (!name.empty()) {
  53. if (name[0] == ':') {
  54. name.Skip(1);
  55. }
  56. if (!TryParseInt(name, minute, 2) || minute >= 60) {
  57. return false;
  58. }
  59. if (!name.empty()) {
  60. return false;
  61. }
  62. }
  63. if (hour == 24 && minute != 0) {
  64. return false;
  65. }
  66. offset = (hour * 60 + minute) * 60;
  67. if (negative)
  68. offset = -offset;
  69. return true;
  70. }
  71. TTimeZone GetTimeZone(TStringBuf name) {
  72. int offset;
  73. // Try to preparse constant timezones like:
  74. // UTC+03:00
  75. // GMT+03:00
  76. // Note constant timezones like 'Etc-03' will be handed in cctz library
  77. if (TryParseUTCGMTOffsetTimezone(name, offset)) {
  78. return GetFixedTimeZone(offset);
  79. }
  80. TTimeZone result;
  81. if (!cctz::load_time_zone(static_cast<std::string>(name), &result)) {
  82. ythrow TInvalidTimezone() << "Failed to load time zone " << name << ", " << result.name();
  83. }
  84. return result;
  85. }
  86. TTimeZone GetFixedTimeZone(const long offset) {
  87. return cctz::fixed_time_zone(std::chrono::seconds(offset));
  88. }
  89. TCivilSecond Convert(const TInstant& absTime, const TTimeZone& tz) {
  90. return cctz::convert(TSystemClock::from_time_t(absTime.TimeT()), tz);
  91. }
  92. TCivilSecond Convert(const TInstant& absTime, TStringBuf tzName) {
  93. TTimeZone tz = GetTimeZone(tzName);
  94. return cctz::convert(TSystemClock::from_time_t(absTime.TimeT()), tz);
  95. }
  96. TInstant Convert(const TCivilSecond& tp, const TTimeZone& tz) {
  97. return TInstant::Seconds(cctz::convert(tp, tz).time_since_epoch().count());
  98. }
  99. TCivilSecond AddYears(const TCivilSecond& tp, TDiff diff) {
  100. TCivilYear newYear = Calc<TCivilYear>(tp, diff);
  101. return NDatetime::TCivilSecond(newYear.year(), tp.month(), tp.day(), tp.hour(), tp.minute(), tp.second());
  102. }
  103. TCivilSecond AddMonths(const TCivilSecond& tp, TDiff diff) {
  104. TCivilMonth newMonth = Calc<TCivilMonth>(tp, diff);
  105. return NDatetime::TCivilSecond(newMonth.year(), newMonth.month(), tp.day(), tp.hour(), tp.minute(), tp.second());
  106. }
  107. TCivilSecond AddDays(const TCivilSecond& tp, TDiff diff) {
  108. TCivilDay newDay = Calc<TCivilDay>(tp, diff);
  109. return NDatetime::TCivilSecond(newDay.year(), newDay.month(), newDay.day(), tp.hour(), tp.minute(), tp.second());
  110. }
  111. TCivilSecond AddHours(const TCivilSecond& tp, TDiff diff) {
  112. TCivilHour newHour = Calc<TCivilHour>(tp, diff);
  113. return NDatetime::TCivilSecond(newHour.year(), newHour.month(), newHour.day(), newHour.hour(), tp.minute(), tp.second());
  114. }
  115. TCivilSecond AddMinutes(const TCivilSecond& tp, TDiff diff) {
  116. TCivilMinute newMinute = Calc<TCivilMinute>(tp, diff);
  117. return NDatetime::TCivilSecond(newMinute.year(), newMinute.month(), newMinute.day(), newMinute.hour(), newMinute.minute(), tp.second());
  118. }
  119. TCivilSecond AddSeconds(const TCivilSecond& tp, TDiff diff) {
  120. return Calc<TCivilSecond>(tp, diff);
  121. }
  122. TCivilSecond AddCivil(const TCivilSecond& tp, TCivilDiff diff) {
  123. switch (diff.Unit) {
  124. case ECivilUnit::Second: {
  125. return AddSeconds(tp, diff.Value);
  126. }
  127. case ECivilUnit::Minute: {
  128. return AddMinutes(tp, diff.Value);
  129. }
  130. case ECivilUnit::Hour: {
  131. return AddHours(tp, diff.Value);
  132. }
  133. case ECivilUnit::Day: {
  134. return AddDays(tp, diff.Value);
  135. }
  136. case ECivilUnit::Month: {
  137. return AddMonths(tp, diff.Value);
  138. }
  139. case ECivilUnit::Year: {
  140. return AddYears(tp, diff.Value);
  141. }
  142. default: {
  143. ythrow yexception() << "Unexpected civil unit value " << static_cast<int>(diff.Unit);
  144. }
  145. }
  146. }
  147. TCivilDiff GetCivilDiff(const TCivilSecond& tpX, const TCivilSecond& tpY, ECivilUnit unit) {
  148. switch (unit) {
  149. case ECivilUnit::Second: {
  150. return {tpX - tpY, unit};
  151. }
  152. case ECivilUnit::Minute: {
  153. return {static_cast<TCivilMinute>(tpX) - static_cast<TCivilMinute>(tpY), unit};
  154. }
  155. case ECivilUnit::Hour: {
  156. return {static_cast<TCivilHour>(tpX) - static_cast<TCivilHour>(tpY), unit};
  157. }
  158. case ECivilUnit::Day: {
  159. return {static_cast<TCivilDay>(tpX) - static_cast<TCivilDay>(tpY), unit};
  160. }
  161. case ECivilUnit::Month: {
  162. return {static_cast<TCivilMonth>(tpX) - static_cast<TCivilMonth>(tpY), unit};
  163. }
  164. case ECivilUnit::Year: {
  165. return {static_cast<TCivilYear>(tpX) - static_cast<TCivilYear>(tpY), unit};
  166. }
  167. default: {
  168. ythrow yexception() << "Unexpected civil unit value " << static_cast<int>(unit);
  169. }
  170. }
  171. }
  172. }
  173. template <>
  174. void Out<NDatetime::TCivilYear>(IOutputStream& out, const NDatetime::TCivilYear& y) {
  175. out << y.year();
  176. }
  177. template <>
  178. void Out<NDatetime::TCivilMonth>(IOutputStream& out, const NDatetime::TCivilMonth& m) {
  179. out << NDatetime::TCivilYear(m) << '-' << LeftPad(m.month(), 2, '0');
  180. }
  181. template <>
  182. void Out<NDatetime::TCivilDay>(IOutputStream& out, const NDatetime::TCivilDay& d) {
  183. out << NDatetime::TCivilMonth(d) << '-' << LeftPad(d.day(), 2, '0');
  184. }
  185. template <>
  186. void Out<NDatetime::TCivilHour>(IOutputStream& out, const NDatetime::TCivilHour& h) {
  187. out << NDatetime::TCivilDay(h) << 'T' << LeftPad(h.hour(), 2, '0');
  188. }
  189. template <>
  190. void Out<NDatetime::TCivilMinute>(IOutputStream& out, const NDatetime::TCivilMinute& m) {
  191. out << NDatetime::TCivilHour(m) << ':' << LeftPad(m.minute(), 2, '0');
  192. }
  193. template <>
  194. void Out<NDatetime::TCivilSecond>(IOutputStream& out, const NDatetime::TCivilSecond& s) {
  195. out << NDatetime::TCivilMinute(s) << ':' << LeftPad(s.second(), 2, '0');
  196. }
  197. template <>
  198. void Out<NDatetime::TWeekday>(IOutputStream& out, NDatetime::TWeekday wd) {
  199. using namespace cctz;
  200. switch (wd) {
  201. case weekday::monday:
  202. out << TStringBuf("Monday");
  203. break;
  204. case weekday::tuesday:
  205. out << TStringBuf("Tuesday");
  206. break;
  207. case weekday::wednesday:
  208. out << TStringBuf("Wednesday");
  209. break;
  210. case weekday::thursday:
  211. out << TStringBuf("Thursday");
  212. break;
  213. case weekday::friday:
  214. out << TStringBuf("Friday");
  215. break;
  216. case weekday::saturday:
  217. out << TStringBuf("Saturday");
  218. break;
  219. case weekday::sunday:
  220. out << TStringBuf("Sunday");
  221. break;
  222. }
  223. }