convert_ut.cpp 6.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204
  1. #include <library/cpp/timezone_conversion/convert.h>
  2. #include <library/cpp/testing/unittest/gtest.h>
  3. using namespace NDatetime;
  4. template <>
  5. void Out<TSimpleTM>(IOutputStream& os, TTypeTraits<TSimpleTM>::TFuncParam value) {
  6. os << value.ToString() << ", dst: " << int(value.IsDst);
  7. }
  8. TSimpleTM ZonedTm(i32 utcHours, bool isDst, ui32 year, ui32 mon, ui32 day, ui32 h, ui32 m, ui32 s) {
  9. TSimpleTM res(year, mon, day, h, m, s);
  10. res.GMTOff = utcHours * 60 * 60;
  11. res.IsDst = isDst;
  12. return res;
  13. }
  14. void CompareCivilTimes(const TSimpleTM& expected, const TSimpleTM& actual) {
  15. EXPECT_EQ(expected.GMTOff, actual.GMTOff);
  16. EXPECT_EQ(expected.Year, actual.Year);
  17. EXPECT_EQ(expected.Mon, actual.Mon);
  18. EXPECT_EQ(expected.MDay, actual.MDay);
  19. EXPECT_EQ(expected.WDay, actual.WDay);
  20. EXPECT_EQ(expected.Hour, actual.Hour);
  21. EXPECT_EQ(expected.Min, actual.Min);
  22. EXPECT_EQ(expected.Sec, actual.Sec);
  23. EXPECT_EQ(expected.IsDst, actual.IsDst);
  24. EXPECT_EQ(expected.IsLeap, actual.IsLeap);
  25. }
  26. #define CHECK_ROUND_TRIP(tz, unixTime, civil) \
  27. EXPECT_EQ( \
  28. TInstant::Seconds(unixTime), \
  29. ToAbsoluteTime(civil, tz)); \
  30. CompareCivilTimes( \
  31. civil, \
  32. ToCivilTime(TInstant::Seconds(unixTime), tz));
  33. // Tests only unambiguous civil times (i.e., those that occurred exactly once).
  34. TEST(TimeZoneConversion, Simple) {
  35. TTimeZone msk = GetTimeZone("Europe/Moscow");
  36. // Before and after the temporary switch to UTC+3 in 2010.
  37. CHECK_ROUND_TRIP(
  38. msk,
  39. 1288475999,
  40. ZonedTm(+4, true, 2010, 10, 31, 1, 59, 59));
  41. CHECK_ROUND_TRIP(
  42. msk,
  43. 1288475999 + 3 * 60 * 60,
  44. ZonedTm(+3, false, 2010, 10, 31, 3, 59, 59));
  45. // Before and after the permanent switch to UTC+4 in 2011.
  46. CHECK_ROUND_TRIP(
  47. msk,
  48. 1301180399,
  49. ZonedTm(+3, false, 2011, 3, 27, 1, 59, 59));
  50. CHECK_ROUND_TRIP(
  51. msk,
  52. 1301180399 + 60 * 60,
  53. ZonedTm(+4, false, 2011, 3, 27, 3, 59, 59));
  54. // Some random moment between 2011 and 2014 when UTC+4 (no DST) was in place.
  55. CHECK_ROUND_TRIP(
  56. msk,
  57. 1378901234,
  58. ZonedTm(+4, false, 2013, 9, 11, 16, 7, 14));
  59. // As of right now (i.e., as I'm writing this) Moscow is in UTC+3 (no DST).
  60. CHECK_ROUND_TRIP(
  61. msk,
  62. 1458513396,
  63. ZonedTm(+3, false, 2016, 3, 21, 1, 36, 36));
  64. // Please add a new test if the current president moves Moscow back to UTC+4
  65. // or introduces DST again.
  66. }
  67. TEST(TimeZoneConversion, TestRepeatedDate) {
  68. TTimeZone ekb = GetTimeZone("Asia/Yekaterinburg");
  69. CompareCivilTimes(
  70. ZonedTm(+6, true, 2010, 10, 31, 2, 30, 0),
  71. ToCivilTime(TInstant::Seconds(1288470600), ekb));
  72. CompareCivilTimes(
  73. ZonedTm(+5, false, 2010, 10, 31, 2, 30, 0),
  74. ToCivilTime(TInstant::Seconds(1288474200), ekb));
  75. CompareCivilTimes(
  76. ZonedTm(+5, false, 2016, 5, 10, 9, 8, 7),
  77. CreateCivilTime(ekb, 2016, 5, 10, 9, 8, 7));
  78. CompareCivilTimes(
  79. ZonedTm(+6, true, 2010, 10, 31, 2, 30, 0),
  80. CreateCivilTime(ekb, 2010, 10, 31, 2, 30, 0));
  81. // The earlier timestamp should be chosen.
  82. EXPECT_EQ(
  83. TInstant::Seconds(1288470600),
  84. ToAbsoluteTime(TSimpleTM(2010, 10, 31, 2, 30, 0), ekb));
  85. }
  86. TEST(TimeZoneConversion, TestSkippedDate) {
  87. TTimeZone nsk = GetTimeZone("Asia/Novosibirsk");
  88. CompareCivilTimes(
  89. ZonedTm(+6, false, 2011, 3, 27, 1, 30, 0),
  90. ToCivilTime(TInstant::Seconds(1301167800), nsk));
  91. CompareCivilTimes(
  92. ZonedTm(+7, false, 2011, 3, 27, 3, 30, 0),
  93. ToCivilTime(TInstant::Seconds(1301171400), nsk));
  94. EXPECT_EQ(
  95. TInstant::Seconds(1301171400),
  96. ToAbsoluteTime(TSimpleTM(2011, 3, 27, 2, 30, 0), nsk));
  97. EXPECT_EQ(
  98. TInstant::Seconds(1301171400),
  99. ToAbsoluteTime(TSimpleTM(2011, 3, 27, 3, 30, 0), nsk));
  100. }
  101. TEST(TimeZoneConversion, Utc) {
  102. CHECK_ROUND_TRIP(
  103. GetUtcTimeZone(),
  104. 1451703845,
  105. ZonedTm(0, false, 2016, 1, 2, 3, 4, 5));
  106. }
  107. TEST(TimeZoneConversion, Local) {
  108. TTimeZone local = GetLocalTimeZone();
  109. auto nowAbsolute = TInstant::Now();
  110. auto nowCivilLocal = ToCivilTime(nowAbsolute, local);
  111. EXPECT_EQ(nowAbsolute.Seconds(), ToAbsoluteTime(nowCivilLocal, local).Seconds());
  112. }
  113. TEST(TimeZoneConversion, BeforeEpoch) {
  114. {
  115. //NOTE: This test will not work because NDatetime::Convert() with TInstant does not work properly for dates before 1/1/1970
  116. NDatetime::TCivilSecond civilTime = NDatetime::TCivilSecond{1969, 12, 1, 0, 0, 0};
  117. TInstant absTime = NDatetime::Convert(civilTime, NDatetime::GetUtcTimeZone());
  118. NDatetime::TCivilSecond civilTime2 = NDatetime::Convert(absTime, NDatetime::GetUtcTimeZone());
  119. EXPECT_NE(civilTime2, civilTime); // ERROR. Must be EXPECT_EQ, but Convert() functions with TInstant doesnot wotk properly for dates before EPOCH
  120. }
  121. // Right test
  122. NDatetime::TCivilSecond civilTime = NDatetime::TCivilSecond{1969, 12, 1, 0, 0, 0};
  123. NDatetime::TCivilSecond civilTime2 = Convert<NDatetime::TCivilSecond>(civilTime, NDatetime::GetUtcTimeZone(), NDatetime::GetUtcTimeZone());
  124. EXPECT_EQ(civilTime2, civilTime);
  125. }
  126. TEST(TimeZoneConversion, InvalidTimeZone) {
  127. EXPECT_THROW(GetTimeZone("Europe/Mscow"), yexception);
  128. EXPECT_THROW(GetTimeZone(""), yexception);
  129. }
  130. TEST(TimeZoneConversion, TestSaratov) {
  131. TTimeZone saratov = GetTimeZone("Europe/Saratov");
  132. CompareCivilTimes(
  133. ZonedTm(+4, false, 2016, 12, 5, 1, 55, 35),
  134. ToCivilTime(TInstant::Seconds(1480888535), saratov));
  135. CompareCivilTimes(
  136. ZonedTm(+3, false, 2016, 12, 1, 0, 55, 35),
  137. ToCivilTime(TInstant::Seconds(1480542935), saratov));
  138. }
  139. TEST(TimeZoneConversion, TestFutureDstChanges) {
  140. TTimeZone london = GetTimeZone("Europe/London");
  141. // This test assumes the British won't cancel DST before 2025.
  142. // I don't think they will, but then again, nobody really expected Brexit.
  143. // DST is still in effect in early October 2025, meaning we are in UTC+1.
  144. CHECK_ROUND_TRIP(
  145. london,
  146. 1760124660,
  147. ZonedTm(+1, true, 2025, 10, 10, 20, 31, 0));
  148. // 31 days later we're back to UTC+0 again.
  149. CHECK_ROUND_TRIP(
  150. london,
  151. 1760124660 + 31 * 24 * 60 * 60,
  152. ZonedTm(+0, false, 2025, 11, 10, 19, 31, 0));
  153. }
  154. TEST(TimeZoneConversion, TWDay) {
  155. TTimeZone nsk = GetTimeZone("Asia/Novosibirsk");
  156. for (time_t e = 1301167800, to = 1301167800 + 86400 * 7, dow = 0; e < to; e += 86400, ++dow) {
  157. EXPECT_EQ(dow, ToCivilTime(TInstant::Seconds(e), nsk).WDay);
  158. }
  159. }
  160. TEST(TimeZoneConversion, TestBaikonur) {
  161. // Yes, the Baikonur spaceport is located in Kyzylorda Region.
  162. const auto baikonur = GetTimeZone("Asia/Qyzylorda");
  163. CompareCivilTimes(
  164. ZonedTm(+5, false, 2019, 1, 11, 23, 55, 23),
  165. ToCivilTime(TInstant::Seconds(1547232923), baikonur));
  166. }