From 5188881075cd4ab1410233316160f0eec939839e Mon Sep 17 00:00:00 2001 From: Ole Hansen Date: Tue, 28 Nov 2023 10:10:30 -0500 Subject: [PATCH] Bugfix: Temporary time zone switching not working correctly - On Linux/glibc, must call tzset() explicitly to pick up change of TZ. - On macOS (possibly elsewhere), must save the return value from getenv(); the pointed-to string may change if the environment is updated. - Must check if time zone correction is needed at actual run date/time --- Database/Database.cxx | 92 +++++++++++++++++++++---------------------- Database/Database.h | 31 ++++++++++++--- Podd/THaRunBase.cxx | 6 ++- 3 files changed, 77 insertions(+), 52 deletions(-) diff --git a/Database/Database.cxx b/Database/Database.cxx index c606ef48..36d34dd7 100644 --- a/Database/Database.cxx +++ b/Database/Database.cxx @@ -78,52 +78,6 @@ const char* Here( const char* method, const char* prefix ) //============================================================================= namespace Podd { -namespace { - -const char* gDefaultTZString = "US/Eastern"; - -//_____________________________________________________________________________ -Long64_t GetOffsetToLocal(const char* tz) -{ - // Returns the offset in seconds between the given time zone 'tz' and - // the time zone of the local machine - time_t tloc = time(nullptr); - struct tm tml{}, tmd{}; - localtime_r(&tloc, &tml); - - const char* cur_tz = gSystem->Getenv("TZ"); - gSystem->Setenv("TZ", tz); - localtime_r(&tloc, &tmd); - if( cur_tz ) - gSystem->Setenv("TZ", cur_tz); - else - gSystem->Unsetenv("TZ"); - - return tmd.tm_gmtoff - tml.tm_gmtoff; -} - -//_____________________________________________________________________________ -TString MkDefaultTZ() -{ - gNeedTZCorrection = (GetOffsetToLocal(gDefaultTZString) != 0); - return gDefaultTZString; -} - -} // namespace - -TString gDefaultTZ = MkDefaultTZ(); -Bool_t gNeedTZCorrection = true; - -//_____________________________________________________________________________ -void SetDefaultTZ( const char* tz ) -{ - if( !tz || !*tz ) { - tz = gDefaultTZString; - } - gNeedTZCorrection = (GetOffsetToLocal(tz) != 0); - gDefaultTZ = tz; -} - //_____________________________________________________________________________ TString& GetObjArrayString( const TObjArray* array, Int_t i ) { @@ -1385,6 +1339,52 @@ Bool_t IsDBtimestamp( const string& line, TDatime& date ) return IsDBdate(line, ldate, true); } +//_____________________________________________________________________________ +// Timezone handling +const char* const gDefaultTZString = "US/Eastern"; + +TString gDefaultTZ = gDefaultTZString; +Bool_t gNeedTZCorrection = true; + +//_____________________________________________________________________________ +// Set time zone to assume for legacy database time stamps without time zone +// offsets. The default is "US/Eastern". +void SetDefaultTZ( const char* tz ) +{ + if( !tz || !*tz ) { + tz = gDefaultTZString; + } + gDefaultTZ = tz; +} + +//_____________________________________________________________________________ +// Helper function for time zone calculations. Determine offset between +// the default time zone and the local time zone at Unix time 'tloc'. +Long64_t GetTZOffsetToLocal( UInt_t tloc ) +{ + // Returns the offset in seconds between the given time zone 'tz' and + // the time zone of the local machine + auto timet = static_cast(tloc); + struct tm tml{}, tmd{}; + localtime_r(&timet, &tml); + + TString cur_tz = gSystem->Getenv("TZ"); + gSystem->Setenv("TZ", gDefaultTZ); +#ifdef __GLIBC__ + tzset(); +#endif + localtime_r(&timet, &tmd); + if( !cur_tz.IsNull() ) + gSystem->Setenv("TZ", cur_tz); + else + gSystem->Unsetenv("TZ"); + +#ifdef __GLIBC__ + tzset(); +#endif + return tmd.tm_gmtoff - tml.tm_gmtoff; +} + //_____________________________________________________________________________ #ifdef __clang__ // Clang appears to make implicitly instantiated template functions private diff --git a/Database/Database.h b/Database/Database.h index 46c97fad..0aa1e3c8 100644 --- a/Database/Database.h +++ b/Database/Database.h @@ -17,10 +17,9 @@ // For backward compatibility with existing client code #include "Textvars.h" #include "TDatime.h" +#include "TString.h" class TObjArray; -//class TDatime; -class TString; // Helper function for error messages const char* Here( const char* here, const char* prefix = nullptr ); @@ -66,24 +65,46 @@ Bool_t IsDBtimestamp( const std::string& line, TDatime& keydate ); // Time zone to assume for legacy database time stamps without time zone offsets. // The default is "US/Eastern". -void SetDefaultTZ(const char* tz = nullptr); +void SetDefaultTZ( const char* tz = nullptr ); +Long64_t GetTZOffsetToLocal( UInt_t tloc ); extern TString gDefaultTZ; extern Bool_t gNeedTZCorrection; } // namespace Podd +// Macro for running arbitrary code ("cmd") with TZ set to gDefaultTZ. +#ifdef __GLIBC__ +// glibc seems to require an explicit call to tzset() to pick up a change of TZ +#include #define WithDefaultTZ(cmd) \ - const char* cur_tz = nullptr; \ + TString cur_tz; \ if( Podd::gNeedTZCorrection ) { \ cur_tz = gSystem->Getenv("TZ"); \ gSystem->Setenv("TZ", Podd::gDefaultTZ); \ + tzset(); \ } \ cmd; \ if( Podd::gNeedTZCorrection ) { \ - if( cur_tz ) \ + if( !cur_tz.IsNull() ) \ gSystem->Setenv("TZ", cur_tz ); \ else \ gSystem->Unsetenv("TZ"); \ + tzset(); \ } +#else +#define WithDefaultTZ(cmd) \ + TString cur_tz; \ + if( Podd::gNeedTZCorrection ) { \ + cur_tz = gSystem->Getenv("TZ"); \ + gSystem->Setenv("TZ", Podd::gDefaultTZ); \ + } \ + cmd; \ + if( Podd::gNeedTZCorrection ) { \ + if( !cur_tz.IsNull() ) \ + gSystem->Setenv("TZ", cur_tz ); \ + else \ + gSystem->Unsetenv("TZ"); \ + } +#endif #endif //Podd_Database_h_ diff --git a/Podd/THaRunBase.cxx b/Podd/THaRunBase.cxx index 97bcbd0a..12170b9f 100644 --- a/Podd/THaRunBase.cxx +++ b/Podd/THaRunBase.cxx @@ -22,6 +22,7 @@ #include // std::begin, std::end using namespace std; +using namespace Podd; static const int UNDEFDATE = 19950101; static const char* NOTINIT = "uninitialized run"; @@ -144,7 +145,9 @@ Int_t THaRunBase::Update( const THaEvData* evdata ) if( evdata->IsPrestartEvent() ) { fDataRead |= kDate|kRunNumber|kRunType; if( !fAssumeDate ) { - WithDefaultTZ(fDate.Set( evdata->GetRunTime() )); + UInt_t tloc = evdata->GetRunTime(); + gNeedTZCorrection = (GetTZOffsetToLocal(tloc) != 0); + WithDefaultTZ(fDate.Set(tloc)); fDataSet |= kDate; } SetNumber( evdata->GetRunNum() ); @@ -512,6 +515,7 @@ void THaRunBase::SetDate( UInt_t tloc ) // Set timestamp of this run to 'tloc' which is in Unix time // format (number of seconds since 01 Jan 1970). + gNeedTZCorrection = (GetTZOffsetToLocal(tloc) != 0); WithDefaultTZ(TDatime date( tloc )); SetDate( date ); }