aboutsummaryrefslogtreecommitdiffstats
path: root/library/cpp/timezone_conversion/ut
diff options
context:
space:
mode:
authorDevtools Arcadia <arcadia-devtools@yandex-team.ru>2022-02-07 18:08:42 +0300
committerDevtools Arcadia <arcadia-devtools@mous.vla.yp-c.yandex.net>2022-02-07 18:08:42 +0300
commit1110808a9d39d4b808aef724c861a2e1a38d2a69 (patch)
treee26c9fed0de5d9873cce7e00bc214573dc2195b7 /library/cpp/timezone_conversion/ut
downloadydb-1110808a9d39d4b808aef724c861a2e1a38d2a69.tar.gz
intermediate changes
ref:cde9a383711a11544ce7e107a78147fb96cc4029
Diffstat (limited to 'library/cpp/timezone_conversion/ut')
-rw-r--r--library/cpp/timezone_conversion/ut/civil_ut.cpp157
-rw-r--r--library/cpp/timezone_conversion/ut/convert_ut.cpp204
-rw-r--r--library/cpp/timezone_conversion/ut/ya.make15
3 files changed, 376 insertions, 0 deletions
diff --git a/library/cpp/timezone_conversion/ut/civil_ut.cpp b/library/cpp/timezone_conversion/ut/civil_ut.cpp
new file mode 100644
index 0000000000..a21bd4bd7d
--- /dev/null
+++ b/library/cpp/timezone_conversion/ut/civil_ut.cpp
@@ -0,0 +1,157 @@
+#include <library/cpp/timezone_conversion/civil.h>
+#include <library/cpp/testing/unittest/registar.h>
+#include <library/cpp/testing/unittest/tests_data.h>
+
+#include <util/stream/str.h>
+
+namespace NDatetime {
+ inline bool operator==(const NDatetime::TCivilDiff& x, const NDatetime::TCivilDiff& y) {
+ return x.Unit == y.Unit && x.Value == y.Value;
+ }
+}
+
+template <>
+inline void Out<NDatetime::TCivilDiff>(IOutputStream& out, const NDatetime::TCivilDiff& diff) {
+ out << "(" << diff.Value << "," << diff.Unit << ")";
+}
+
+Y_UNIT_TEST_SUITE(DateTime) {
+ Y_UNIT_TEST(Calc) {
+ NDatetime::TCivilSecond s(2017, 2, 1, 10, 12, 9);
+ UNIT_ASSERT_VALUES_EQUAL(NDatetime::Calc<NDatetime::TCivilDay>(s, 2), NDatetime::TCivilDay(2017, 2, 3));
+ UNIT_ASSERT_VALUES_EQUAL(NDatetime::Calc<NDatetime::TCivilDay>(s, -2), NDatetime::TCivilDay(2017, 1, 30));
+ }
+ Y_UNIT_TEST(Adds) {
+ NDatetime::TCivilSecond s(2017, 2, 1, 10, 12, 9);
+ UNIT_ASSERT_VALUES_EQUAL(NDatetime::AddDays(s, 2), NDatetime::TCivilSecond(2017, 2, 3, 10, 12, 9));
+ UNIT_ASSERT_VALUES_EQUAL(NDatetime::AddMonths(s, -2), NDatetime::TCivilSecond(2016, 12, 1, 10, 12, 9));
+ UNIT_ASSERT_VALUES_EQUAL(NDatetime::AddYears(s, -55), NDatetime::TCivilSecond(1962, 2, 1, 10, 12, 9));
+ UNIT_ASSERT_VALUES_EQUAL(NDatetime::AddHours(s, 40), NDatetime::TCivilSecond(2017, 2, 3, 2, 12, 9));
+ }
+ Y_UNIT_TEST(Convert) {
+ TInstant absTime = TInstant::Seconds(1500299239);
+ NDatetime::TTimeZone lax = NDatetime::GetTimeZone("America/Los_Angeles");
+ NDatetime::TCivilSecond dt1 = NDatetime::Convert(absTime, lax);
+ NDatetime::TCivilSecond dt2(2017, 7, 17, 6, 47, 19);
+ UNIT_ASSERT_VALUES_EQUAL(dt1, dt2);
+ UNIT_ASSERT_VALUES_EQUAL(absTime, NDatetime::Convert(dt2, lax));
+ UNIT_ASSERT_EXCEPTION(NDatetime::Convert(absTime, "Unknown time zone"), NDatetime::TInvalidTimezone);
+ }
+ Y_UNIT_TEST(UTCOffsetTimezone) {
+ NDatetime::TTimeZone lax = NDatetime::GetTimeZone("UTC+12");
+ auto lookup = lax.lookup(std::chrono::system_clock::from_time_t(0));
+ UNIT_ASSERT_VALUES_EQUAL(12 * 60 * 60, lookup.offset);
+ lax = NDatetime::GetTimeZone("UTC-10");
+ lookup = lax.lookup(std::chrono::system_clock::from_time_t(0));
+ UNIT_ASSERT_VALUES_EQUAL(-10 * 60 * 60, lookup.offset);
+ lax = NDatetime::GetTimeZone("UTC");
+ lookup = lax.lookup(std::chrono::system_clock::from_time_t(0));
+ UNIT_ASSERT_VALUES_EQUAL(0, lookup.offset);
+ lax = NDatetime::GetTimeZone("UTC+0");
+ lookup = lax.lookup(std::chrono::system_clock::from_time_t(0));
+ UNIT_ASSERT_VALUES_EQUAL(0, lookup.offset);
+ lax = NDatetime::GetTimeZone("UTC-2");
+ lookup = lax.lookup(std::chrono::system_clock::from_time_t(0));
+ UNIT_ASSERT_VALUES_EQUAL(-2 * 60 * 60, lookup.offset);
+ lax = NDatetime::GetTimeZone("UTC-00:30");
+ lookup = lax.lookup(std::chrono::system_clock::from_time_t(0));
+ UNIT_ASSERT_VALUES_EQUAL(-30 * 60, lookup.offset);
+ lax = NDatetime::GetTimeZone("UTC-0241");
+ lookup = lax.lookup(std::chrono::system_clock::from_time_t(0));
+ UNIT_ASSERT_VALUES_EQUAL(-(2 * 60 + 41) * 60, lookup.offset);
+ UNIT_ASSERT_EXCEPTION(NDatetime::GetTimeZone("UTCUnknown"), NDatetime::TInvalidTimezone);
+ UNIT_ASSERT_EXCEPTION(NDatetime::GetTimeZone("UTC+:"), NDatetime::TInvalidTimezone);
+ UNIT_ASSERT_EXCEPTION(NDatetime::GetTimeZone("UTC+24:01"), NDatetime::TInvalidTimezone);
+ UNIT_ASSERT_EXCEPTION(NDatetime::GetTimeZone("UTC+20:"), NDatetime::TInvalidTimezone);
+ UNIT_ASSERT_EXCEPTION(NDatetime::GetTimeZone("UTC+20:60"), NDatetime::TInvalidTimezone);
+ UNIT_ASSERT_EXCEPTION(NDatetime::GetTimeZone("UTC+20:30:"), NDatetime::TInvalidTimezone);
+ }
+ Y_UNIT_TEST(Format) {
+ NDatetime::TTimeZone lax = NDatetime::GetTimeZone("America/Los_Angeles");
+ NDatetime::TCivilSecond tp(2013, 1, 2, 3, 4, 5);
+ UNIT_ASSERT_VALUES_EQUAL(NDatetime::Format("%H:%M:%S", tp, lax), "03:04:05");
+ UNIT_ASSERT_VALUES_EQUAL(NDatetime::Format("%H:%M:%E3S", tp, lax), "03:04:05.000");
+ }
+ Y_UNIT_TEST(Weekday) {
+ NDatetime::TCivilDay d(2013, 1, 2);
+ NDatetime::TWeekday wd = NDatetime::GetWeekday(d);
+ UNIT_ASSERT_VALUES_EQUAL(wd, NDatetime::TWeekday::wednesday);
+ UNIT_ASSERT_VALUES_EQUAL(NDatetime::NextWeekday(d, NDatetime::TWeekday::monday), NDatetime::TCivilDay(2013, 1, 7));
+ UNIT_ASSERT_VALUES_EQUAL(NDatetime::PrevWeekday(d, NDatetime::TWeekday::monday), NDatetime::TCivilDay(2012, 12, 31));
+ UNIT_ASSERT_VALUES_EQUAL(NDatetime::WeekdayOnTheWeek(d, NDatetime::TWeekday::monday), NDatetime::TCivilDay(2012, 12, 31));
+ UNIT_ASSERT_VALUES_EQUAL(NDatetime::WeekdayOnTheWeek(d, NDatetime::TWeekday::wednesday), NDatetime::TCivilDay(2013, 1, 2));
+ UNIT_ASSERT_VALUES_EQUAL(NDatetime::WeekdayOnTheWeek(d, NDatetime::TWeekday::friday), NDatetime::TCivilDay(2013, 1, 4));
+ }
+ Y_UNIT_TEST(CivilUnit) {
+ using namespace NDatetime;
+
+ UNIT_ASSERT_VALUES_EQUAL(GetCivilUnit<TCivilMonth>(), ECivilUnit::Month);
+ UNIT_ASSERT_VALUES_EQUAL(GetCivilUnit(TCivilHour{}), ECivilUnit::Hour);
+
+ UNIT_ASSERT_VALUES_EQUAL(TCivilTime<ECivilUnit::Day>(2017, 1, 11), TCivilDay(2017, 1, 11));
+
+ NDatetime::TCivilSecond s(2017, 2, 1, 10, 12, 9);
+
+ UNIT_ASSERT_VALUES_EQUAL(
+ NDatetime::AddCivil(s, TCivilDiff{2, ECivilUnit::Day}),
+ NDatetime::AddDays(s, 2));
+ UNIT_ASSERT_VALUES_EQUAL(
+ NDatetime::AddCivil(s, TCivilDiff{-2, ECivilUnit::Month}),
+ NDatetime::AddMonths(s, -2));
+ UNIT_ASSERT_VALUES_EQUAL(
+ NDatetime::AddCivil(s, TCivilDiff{-55, ECivilUnit::Year}),
+ NDatetime::AddYears(s, -55));
+ UNIT_ASSERT_VALUES_EQUAL(
+ NDatetime::AddCivil(s, TCivilDiff{40, ECivilUnit::Hour}),
+ NDatetime::AddHours(s, 40));
+
+ UNIT_ASSERT_VALUES_EQUAL(
+ GetCivilDiff(TCivilSecond(2017, 10), TCivilSecond(2017, 7), ECivilUnit::Month),
+ TCivilDiff(3, ECivilUnit::Month));
+
+ UNIT_ASSERT_VALUES_EQUAL(
+ GetCivilDiff(TCivilSecond(2017, 10, 1), TCivilSecond(2017, 9, 30), ECivilUnit::Month),
+ TCivilDiff(1, ECivilUnit::Month));
+
+ UNIT_ASSERT_VALUES_EQUAL(
+ GetCivilDiff(TCivilSecond(2017, 10, 1), TCivilSecond(2017, 9, 31), ECivilUnit::Month),
+ TCivilDiff(0, ECivilUnit::Month));
+ }
+
+ Y_UNIT_TEST(TestYearWeekNmb) {
+
+ // YEAR 2021 - start from Friday, first dates (1-3) will have week# 0
+ UNIT_ASSERT_VALUES_EQUAL(NDatetime::GetYearWeek(NDatetime::TCivilDay{2021, 1, 1}), 0);
+ UNIT_ASSERT_VALUES_EQUAL(NDatetime::GetYearWeek(NDatetime::TCivilDay{2021, 1, 2}), 0);
+ UNIT_ASSERT_VALUES_EQUAL(NDatetime::GetYearWeek(NDatetime::TCivilDay{2021, 1, 3}), 0);
+ UNIT_ASSERT_VALUES_EQUAL(NDatetime::GetYearWeek(NDatetime::TCivilDay{2021, 1, 4}), 1);
+
+ UNIT_ASSERT_VALUES_EQUAL(NDatetime::GetYearWeek(NDatetime::TCivilDay{2021, 1, 1}, true), 53);
+ UNIT_ASSERT_VALUES_EQUAL(NDatetime::GetYearWeek(NDatetime::TCivilDay{2021, 1, 2}, true), 53);
+ UNIT_ASSERT_VALUES_EQUAL(NDatetime::GetYearWeek(NDatetime::TCivilDay{2021, 1, 3}, true), 53);
+ UNIT_ASSERT_VALUES_EQUAL(NDatetime::GetYearWeek(NDatetime::TCivilDay{2021, 1, 4}, true), 54);
+
+ UNIT_ASSERT_VALUES_EQUAL(NDatetime::GetYearWeek(NDatetime::TCivilDay{2021, 2, 28}), 8);
+ UNIT_ASSERT_VALUES_EQUAL(NDatetime::GetYearWeek(NDatetime::TCivilDay{2021, 2, 29}), 9); // <- this is invalid date, should be normalized to March 1
+ UNIT_ASSERT_VALUES_EQUAL(NDatetime::GetYearWeek(NDatetime::TCivilDay{2021, 3, 1}), 9);
+
+ UNIT_ASSERT_VALUES_EQUAL(NDatetime::GetYearWeek(NDatetime::TCivilDay{2021, 12, 26}), 51);
+ UNIT_ASSERT_VALUES_EQUAL(NDatetime::GetYearWeek(NDatetime::TCivilDay{2021, 12, 27}), 52);
+ UNIT_ASSERT_VALUES_EQUAL(NDatetime::GetYearWeek(NDatetime::TCivilDay{2021, 12, 31}), 52);
+
+ // YEAR 2020 - start from Wednesday, all dates start from week# 1
+ UNIT_ASSERT_VALUES_EQUAL(NDatetime::GetYearWeek(NDatetime::TCivilDay{2020, 1, 1}), 1);
+ UNIT_ASSERT_VALUES_EQUAL(NDatetime::GetYearWeek(NDatetime::TCivilDay{2020, 1, 5}), 1);
+ UNIT_ASSERT_VALUES_EQUAL(NDatetime::GetYearWeek(NDatetime::TCivilDay{2020, 1, 6}), 2);
+
+ UNIT_ASSERT_VALUES_EQUAL(NDatetime::GetYearWeek(NDatetime::TCivilDay{2020, 2, 28}), 9);
+ UNIT_ASSERT_VALUES_EQUAL(NDatetime::GetYearWeek(NDatetime::TCivilDay{2020, 2, 29}), 9);
+ UNIT_ASSERT_VALUES_EQUAL(NDatetime::GetYearWeek(NDatetime::TCivilDay{2020, 3, 1}), 9);
+ UNIT_ASSERT_VALUES_EQUAL(NDatetime::GetYearWeek(NDatetime::TCivilDay{2020, 3, 2}), 10);
+
+ UNIT_ASSERT_VALUES_EQUAL(NDatetime::GetYearWeek(NDatetime::TCivilDay{2020, 12, 31}), 53);
+
+ // Max possible delta - calcuate week # for 31 Dec 2021 from 1 Jan 2020
+ UNIT_ASSERT_VALUES_EQUAL(NDatetime::GetYearWeek(NDatetime::TCivilDay{2021, 12, 31}, true), 105);
+ }
+}
diff --git a/library/cpp/timezone_conversion/ut/convert_ut.cpp b/library/cpp/timezone_conversion/ut/convert_ut.cpp
new file mode 100644
index 0000000000..bbf9e9b826
--- /dev/null
+++ b/library/cpp/timezone_conversion/ut/convert_ut.cpp
@@ -0,0 +1,204 @@
+#include <library/cpp/timezone_conversion/convert.h>
+#include <library/cpp/testing/unittest/gtest.h>
+
+using namespace NDatetime;
+
+template <>
+void Out<TSimpleTM>(IOutputStream& os, TTypeTraits<TSimpleTM>::TFuncParam value) {
+ os << value.ToString() << ", dst: " << int(value.IsDst);
+}
+
+TSimpleTM ZonedTm(i32 utcHours, bool isDst, ui32 year, ui32 mon, ui32 day, ui32 h, ui32 m, ui32 s) {
+ TSimpleTM res(year, mon, day, h, m, s);
+ res.GMTOff = utcHours * 60 * 60;
+ res.IsDst = isDst;
+ return res;
+}
+
+void CompareCivilTimes(const TSimpleTM& expected, const TSimpleTM& actual) {
+ EXPECT_EQ(expected.GMTOff, actual.GMTOff);
+ EXPECT_EQ(expected.Year, actual.Year);
+ EXPECT_EQ(expected.Mon, actual.Mon);
+ EXPECT_EQ(expected.MDay, actual.MDay);
+ EXPECT_EQ(expected.WDay, actual.WDay);
+ EXPECT_EQ(expected.Hour, actual.Hour);
+ EXPECT_EQ(expected.Min, actual.Min);
+ EXPECT_EQ(expected.Sec, actual.Sec);
+ EXPECT_EQ(expected.IsDst, actual.IsDst);
+ EXPECT_EQ(expected.IsLeap, actual.IsLeap);
+}
+
+#define CHECK_ROUND_TRIP(tz, unixTime, civil) \
+ EXPECT_EQ( \
+ TInstant::Seconds(unixTime), \
+ ToAbsoluteTime(civil, tz)); \
+ CompareCivilTimes( \
+ civil, \
+ ToCivilTime(TInstant::Seconds(unixTime), tz));
+
+// Tests only unambiguous civil times (i.e., those that occurred exactly once).
+TEST(TimeZoneConversion, Simple) {
+ TTimeZone msk = GetTimeZone("Europe/Moscow");
+ // Before and after the temporary switch to UTC+3 in 2010.
+ CHECK_ROUND_TRIP(
+ msk,
+ 1288475999,
+ ZonedTm(+4, true, 2010, 10, 31, 1, 59, 59));
+ CHECK_ROUND_TRIP(
+ msk,
+ 1288475999 + 3 * 60 * 60,
+ ZonedTm(+3, false, 2010, 10, 31, 3, 59, 59));
+
+ // Before and after the permanent switch to UTC+4 in 2011.
+ CHECK_ROUND_TRIP(
+ msk,
+ 1301180399,
+ ZonedTm(+3, false, 2011, 3, 27, 1, 59, 59));
+ CHECK_ROUND_TRIP(
+ msk,
+ 1301180399 + 60 * 60,
+ ZonedTm(+4, false, 2011, 3, 27, 3, 59, 59));
+
+ // Some random moment between 2011 and 2014 when UTC+4 (no DST) was in place.
+ CHECK_ROUND_TRIP(
+ msk,
+ 1378901234,
+ ZonedTm(+4, false, 2013, 9, 11, 16, 7, 14));
+
+ // As of right now (i.e., as I'm writing this) Moscow is in UTC+3 (no DST).
+ CHECK_ROUND_TRIP(
+ msk,
+ 1458513396,
+ ZonedTm(+3, false, 2016, 3, 21, 1, 36, 36));
+
+ // Please add a new test if the current president moves Moscow back to UTC+4
+ // or introduces DST again.
+}
+
+TEST(TimeZoneConversion, TestRepeatedDate) {
+ TTimeZone ekb = GetTimeZone("Asia/Yekaterinburg");
+
+ CompareCivilTimes(
+ ZonedTm(+6, true, 2010, 10, 31, 2, 30, 0),
+ ToCivilTime(TInstant::Seconds(1288470600), ekb));
+
+ CompareCivilTimes(
+ ZonedTm(+5, false, 2010, 10, 31, 2, 30, 0),
+ ToCivilTime(TInstant::Seconds(1288474200), ekb));
+
+ CompareCivilTimes(
+ ZonedTm(+5, false, 2016, 5, 10, 9, 8, 7),
+ CreateCivilTime(ekb, 2016, 5, 10, 9, 8, 7));
+
+ CompareCivilTimes(
+ ZonedTm(+6, true, 2010, 10, 31, 2, 30, 0),
+ CreateCivilTime(ekb, 2010, 10, 31, 2, 30, 0));
+
+ // The earlier timestamp should be chosen.
+ EXPECT_EQ(
+ TInstant::Seconds(1288470600),
+ ToAbsoluteTime(TSimpleTM(2010, 10, 31, 2, 30, 0), ekb));
+}
+
+TEST(TimeZoneConversion, TestSkippedDate) {
+ TTimeZone nsk = GetTimeZone("Asia/Novosibirsk");
+
+ CompareCivilTimes(
+ ZonedTm(+6, false, 2011, 3, 27, 1, 30, 0),
+ ToCivilTime(TInstant::Seconds(1301167800), nsk));
+
+ CompareCivilTimes(
+ ZonedTm(+7, false, 2011, 3, 27, 3, 30, 0),
+ ToCivilTime(TInstant::Seconds(1301171400), nsk));
+
+ EXPECT_EQ(
+ TInstant::Seconds(1301171400),
+ ToAbsoluteTime(TSimpleTM(2011, 3, 27, 2, 30, 0), nsk));
+
+ EXPECT_EQ(
+ TInstant::Seconds(1301171400),
+ ToAbsoluteTime(TSimpleTM(2011, 3, 27, 3, 30, 0), nsk));
+}
+
+TEST(TimeZoneConversion, Utc) {
+ CHECK_ROUND_TRIP(
+ GetUtcTimeZone(),
+ 1451703845,
+ ZonedTm(0, false, 2016, 1, 2, 3, 4, 5));
+}
+
+TEST(TimeZoneConversion, Local) {
+ TTimeZone local = GetLocalTimeZone();
+ auto nowAbsolute = TInstant::Now();
+ auto nowCivilLocal = ToCivilTime(nowAbsolute, local);
+ EXPECT_EQ(nowAbsolute.Seconds(), ToAbsoluteTime(nowCivilLocal, local).Seconds());
+}
+
+TEST(TimeZoneConversion, BeforeEpoch) {
+ {
+ //NOTE: This test will not work because NDatetime::Convert() with TInstant does not work properly for dates before 1/1/1970
+ NDatetime::TCivilSecond civilTime = NDatetime::TCivilSecond{1969, 12, 1, 0, 0, 0};
+ TInstant absTime = NDatetime::Convert(civilTime, NDatetime::GetUtcTimeZone());
+ NDatetime::TCivilSecond civilTime2 = NDatetime::Convert(absTime, NDatetime::GetUtcTimeZone());
+ EXPECT_NE(civilTime2, civilTime); // ERROR. Must be EXPECT_EQ, but Convert() functions with TInstant doesnot wotk properly for dates before EPOCH
+ }
+
+ // Right test
+ NDatetime::TCivilSecond civilTime = NDatetime::TCivilSecond{1969, 12, 1, 0, 0, 0};
+ NDatetime::TCivilSecond civilTime2 = Convert<NDatetime::TCivilSecond>(civilTime, NDatetime::GetUtcTimeZone(), NDatetime::GetUtcTimeZone());
+ EXPECT_EQ(civilTime2, civilTime);
+
+}
+
+TEST(TimeZoneConversion, InvalidTimeZone) {
+ EXPECT_THROW(GetTimeZone("Europe/Mscow"), yexception);
+ EXPECT_THROW(GetTimeZone(""), yexception);
+}
+
+TEST(TimeZoneConversion, TestSaratov) {
+ TTimeZone saratov = GetTimeZone("Europe/Saratov");
+
+ CompareCivilTimes(
+ ZonedTm(+4, false, 2016, 12, 5, 1, 55, 35),
+ ToCivilTime(TInstant::Seconds(1480888535), saratov));
+
+ CompareCivilTimes(
+ ZonedTm(+3, false, 2016, 12, 1, 0, 55, 35),
+ ToCivilTime(TInstant::Seconds(1480542935), saratov));
+}
+
+TEST(TimeZoneConversion, TestFutureDstChanges) {
+ TTimeZone london = GetTimeZone("Europe/London");
+
+ // This test assumes the British won't cancel DST before 2025.
+ // I don't think they will, but then again, nobody really expected Brexit.
+
+ // DST is still in effect in early October 2025, meaning we are in UTC+1.
+ CHECK_ROUND_TRIP(
+ london,
+ 1760124660,
+ ZonedTm(+1, true, 2025, 10, 10, 20, 31, 0));
+
+ // 31 days later we're back to UTC+0 again.
+ CHECK_ROUND_TRIP(
+ london,
+ 1760124660 + 31 * 24 * 60 * 60,
+ ZonedTm(+0, false, 2025, 11, 10, 19, 31, 0));
+}
+
+TEST(TimeZoneConversion, TWDay) {
+ TTimeZone nsk = GetTimeZone("Asia/Novosibirsk");
+
+ for (time_t e = 1301167800, to = 1301167800 + 86400 * 7, dow = 0; e < to; e += 86400, ++dow) {
+ EXPECT_EQ(dow, ToCivilTime(TInstant::Seconds(e), nsk).WDay);
+ }
+}
+
+TEST(TimeZoneConversion, TestBaikonur) {
+ // Yes, the Baikonur spaceport is located in Kyzylorda Region.
+ const auto baikonur = GetTimeZone("Asia/Qyzylorda");
+
+ CompareCivilTimes(
+ ZonedTm(+5, false, 2019, 1, 11, 23, 55, 23),
+ ToCivilTime(TInstant::Seconds(1547232923), baikonur));
+}
diff --git a/library/cpp/timezone_conversion/ut/ya.make b/library/cpp/timezone_conversion/ut/ya.make
new file mode 100644
index 0000000000..781a57da9f
--- /dev/null
+++ b/library/cpp/timezone_conversion/ut/ya.make
@@ -0,0 +1,15 @@
+UNITTEST()
+
+OWNER(dfyz)
+
+PEERDIR(
+ library/cpp/testing/unittest
+ library/cpp/timezone_conversion
+)
+
+SRCS(
+ convert_ut.cpp
+ civil_ut.cpp
+)
+
+END()