diff --git a/c++/src/Timezone.cc b/c++/src/Timezone.cc index fbad35fbcf..9ab7638188 100644 --- a/c++/src/Timezone.cc +++ b/c++/src/Timezone.cc @@ -656,10 +656,18 @@ namespace orc { epoch_ = utcEpoch - getVariant(utcEpoch).gmtOffset; } - const char* getTimezoneDirectory() { + std::string getTimezoneDirectory() { const char* dir = getenv("TZDIR"); if (!dir) { - dir = DEFAULT_TZDIR; + // this is present if we're in an activated conda environment + const char* condaPrefix = getenv("CONDA_PREFIX"); + if (condaPrefix) { + std::string condaDir(condaPrefix); + condaDir += "/share/zoneinfo"; + return condaDir; + } else { + dir = DEFAULT_TZDIR; + } } return dir; } diff --git a/c++/test/TestTimezone.cc b/c++/test/TestTimezone.cc index 2330fcfb04..727d6a2763 100644 --- a/c++/test/TestTimezone.cc +++ b/c++/test/TestTimezone.cc @@ -21,6 +21,7 @@ #include "wrap/gmock.h" #include "wrap/gtest-wrapper.h" +#include #include #include #include @@ -421,8 +422,12 @@ namespace orc { } TEST(TestTimezone, testMissingTZDB) { - const char* tzDirBackup = std::getenv("TZDIR"); - if (tzDirBackup != nullptr) { + const char* tzDir = std::getenv("TZDIR"); + std::string tzDirBackup; + if (tzDir != nullptr) { + // std::string creates a deepcopy of buffer, which avoids that + // unsetting environment variable wrecks pointer to tzDir + tzDirBackup = tzDir; ASSERT_TRUE(delEnv("TZDIR")); } ASSERT_TRUE(setEnv("TZDIR", "/path/to/wrong/tzdb")); @@ -430,11 +435,48 @@ namespace orc { testing::ThrowsMessage(testing::HasSubstr( "Time zone file /path/to/wrong/tzdb/America/Los_Angeles does not exist." " Please install IANA time zone database and set TZDIR env."))); - if (tzDirBackup != nullptr) { - ASSERT_TRUE(setEnv("TZDIR", tzDirBackup)); + if (!tzDirBackup.empty()) { + ASSERT_TRUE(setEnv("TZDIR", tzDirBackup.c_str())); } else { ASSERT_TRUE(delEnv("TZDIR")); } } + TEST(TestTimezone, testTzdbFromCondaEnv) { + const char* tzDir = std::getenv("TZDIR"); + // test only makes sense if TZDIR exists + if (tzDir != nullptr) { + std::string tzDirBackup = tzDir; + ASSERT_TRUE(delEnv("TZDIR")); + + // remove "/share/zoneinfo" from TZDIR (as set through TZDATA_DIR in CI) to + // get the equivalent of CONDA_PREFIX, relative to the location of the tzdb + std::string condaPrefix(tzDirBackup); + condaPrefix += "/../.."; + ASSERT_TRUE(setEnv("CONDA_PREFIX", condaPrefix.c_str())); + + // small test sample to ensure tzbd loads with CONDA_PREFIX, even without TZDIR + const Timezone* zrh = &getTimezoneByName("Europe/Zurich"); + EXPECT_EQ("CET", getVariantFromZone(*zrh, "2024-03-31 00:59:59")); + EXPECT_EQ("CEST", getVariantFromZone(*zrh, "2024-03-31 01:00:00")); + EXPECT_EQ("CEST", getVariantFromZone(*zrh, "2024-10-27 00:59:59")); + EXPECT_EQ("CET", getVariantFromZone(*zrh, "2024-10-27 01:00:00")); + + // CONDA_PREFIX contains backslashes on windows; test that this doesn't blow up + std::replace(condaPrefix.begin(), condaPrefix.end(), '/', '\\'); + ASSERT_TRUE(setEnv("CONDA_PREFIX", condaPrefix.c_str())); + + // as above, but different timezone to avoid hitting cache + const Timezone* syd = &getTimezoneByName("Australia/Sydney"); + EXPECT_EQ("AEDT", getVariantFromZone(*syd, "2024-04-06 15:59:59")); + EXPECT_EQ("AEST", getVariantFromZone(*syd, "2024-04-06 16:00:00")); + EXPECT_EQ("AEST", getVariantFromZone(*syd, "2024-10-05 15:59:59")); + EXPECT_EQ("AEDT", getVariantFromZone(*syd, "2024-10-05 16:00:00")); + + // restore state of environment variables + ASSERT_TRUE(delEnv("CONDA_PREFIX")); + ASSERT_TRUE(setEnv("TZDIR", tzDirBackup.c_str())); + } + } + } // namespace orc