diff --git a/include/znc/User.h b/include/znc/User.h index f781d460..a2a4baa7 100644 --- a/include/znc/User.h +++ b/include/znc/User.h @@ -122,6 +122,7 @@ public: void SetTimestampFormat(const CString& s) { m_sTimestampFormat = s; } void SetTimestampAppend(bool b) { m_bAppendTimestamp = b; } void SetTimestampPrepend(bool b) { m_bPrependTimestamp = b; } + void SetTimezone(const CString& s) { m_sTimezone = s; } void SetTimezoneOffset(float b) { m_fTimezoneOffset = b; } void SetJoinTries(unsigned int i) { m_uMaxJoinTries = i; } void SetSkinName(const CString& s) { m_sSkinName = s; } @@ -160,6 +161,7 @@ public: unsigned int GetBufferCount() const; bool KeepBuffer() const; bool IsBeingDeleted() const { return m_bBeingDeleted; } + CString GetTimezone() const { return m_sTimezone; } float GetTimezoneOffset() const { return m_fTimezoneOffset; } unsigned long long BytesRead() const { return m_uBytesRead; } unsigned long long BytesWritten() const { return m_uBytesWritten; } @@ -184,6 +186,7 @@ protected: CString m_sQuitMsg; MCString m_mssCTCPReplies; CString m_sTimestampFormat; + CString m_sTimezone; float m_fTimezoneOffset; eHashType m_eHashType; diff --git a/include/znc/Utils.h b/include/znc/Utils.h index 210ac608..bda9f480 100644 --- a/include/znc/Utils.h +++ b/include/znc/Utils.h @@ -73,6 +73,9 @@ public: static void GenerateCert(FILE *pOut, const CString& sHost = ""); #endif /* HAVE_LIBSSL */ + static CString CTime(time_t t, const CString& sTZ); + static CString FormatTime(time_t t, const CString& sFormat, const CString& sTZ); + private: protected: }; diff --git a/modules/admin.cpp b/modules/admin.cpp index 7b279454..3a7c7db2 100644 --- a/modules/admin.cpp +++ b/modules/admin.cpp @@ -58,6 +58,7 @@ class CAdminMod : public CModule { {"KeepBuffer", boolean}, {"Password", str}, {"JoinTries", integer}, + {"Timezone", str}, {"TimezoneOffset", doublenum}, {"Admin", boolean}, {"AppendTimestamp", boolean}, @@ -155,6 +156,8 @@ class CAdminMod : public CModule { PutModule("KeepBuffer = " + CString(pUser->KeepBuffer())); else if (sVar == "jointries") PutModule("JoinTries = " + CString(pUser->JoinTries())); + else if (sVar == "timezone") + PutModule("Timezone = " + pUser->GetTimezone()); else if (sVar == "timezoneoffset") PutModule("TimezoneOffset = " + CString(pUser->GetTimezoneOffset())); else if (sVar == "appendtimestamp") @@ -268,6 +271,10 @@ class CAdminMod : public CModule { pUser->SetJoinTries(i); PutModule("JoinTries = " + CString(pUser->JoinTries())); } + else if (sVar == "timezone") { + pUser->SetTimezone(sValue); + PutModule("Timezone = " + pUser->GetTimezone()); + } else if (sVar == "timezoneoffset") { double d = sValue.ToDouble(); pUser->SetTimezoneOffset(d); diff --git a/modules/listsockets.cpp b/modules/listsockets.cpp index 66729803..bb055ee0 100644 --- a/modules/listsockets.cpp +++ b/modules/listsockets.cpp @@ -155,7 +155,7 @@ public: CString GetCreatedTime(Csock* pSocket) { unsigned long long iStartTime = pSocket->GetStartTime(); time_t iTime = iStartTime / 1000; - return FormatTime("%Y-%m-%d %H:%M:%S", iTime); + return CUtils::FormatTime(iTime, "%Y-%m-%d %H:%M:%S", m_pUser->GetTimezone()); } CString GetLocalHost(Csock* pSocket, bool bShowHosts) { @@ -241,20 +241,6 @@ public: virtual ~CListSockets() { } - CString FormatTime(const CString& sFormat, time_t tm = 0) const { - char szTimestamp[1024]; - - if (tm == 0) { - tm = time(NULL); - } - - // offset is in hours - tm += (time_t)(m_pUser->GetTimezoneOffset() * 60 * 60); - strftime(szTimestamp, sizeof(szTimestamp) / sizeof(char), - sFormat.c_str(), localtime(&tm)); - - return szTimestamp; - } }; USERMODULEDEFS(CListSockets, "List active sockets") diff --git a/modules/log.cpp b/modules/log.cpp index 89ca58fd..fe828bba 100644 --- a/modules/log.cpp +++ b/modules/log.cpp @@ -58,21 +58,15 @@ void CLogMod::PutLog(const CString& sLine, const CString& sWindow /*= "Status"*/ { CString sPath; time_t curtime; - tm* timeinfo; - char buffer[1024]; time(&curtime); - // Don't forget the user's timezone offset (which is in hours and we want seconds) - curtime += (time_t) (m_pUser->GetTimezoneOffset() * 60 * 60); - timeinfo = localtime(&curtime); - // Generate file name - if (!strftime(buffer, sizeof(buffer), m_sLogPath.c_str(), timeinfo)) + sPath = CUtils::FormatTime(curtime, m_sLogPath, m_pUser->GetTimezone()); + if (sPath.empty()) { DEBUG("Could not format log path [" << sPath << "]"); return; } - sPath = buffer; // $WINDOW has to be handled last, since it can contain % sPath.Replace("$NETWORK", (m_pNetwork ? m_pNetwork->GetName() : "znc")); @@ -92,10 +86,7 @@ void CLogMod::PutLog(const CString& sLine, const CString& sWindow /*= "Status"*/ if (!CFile::Exists(sLogDir)) CDir::MakeDir(sLogDir); if (LogFile.Open(O_WRONLY | O_APPEND | O_CREAT)) { - snprintf(buffer, sizeof(buffer), "[%02d:%02d:%02d] ", - timeinfo->tm_hour, timeinfo->tm_min, timeinfo->tm_sec); - - LogFile.Write(buffer + sLine + "\n"); + LogFile.Write(CUtils::FormatTime(curtime, "[%H:%M:%S] ", m_pUser->GetTimezone()) + sLine + "\n"); } else DEBUG("Could not open log file [" << sPath << "]: " << strerror(errno)); } diff --git a/modules/simple_away.cpp b/modules/simple_away.cpp index 17f1d1dd..f3570d81 100644 --- a/modules/simple_away.cpp +++ b/modules/simple_away.cpp @@ -190,9 +190,7 @@ private: sReason = SIMPLE_AWAY_DEFAULT_REASON; time_t iTime = time(NULL); - iTime += (time_t)(m_pUser->GetTimezoneOffset() * 60 * 60); // offset is in hours - CString sTime = ctime(&iTime); - sTime.Trim(); + CString sTime = CUtils::CTime(iTime, m_pUser->GetTimezone()); sReason.Replace("%s", sTime); return sReason; diff --git a/modules/webadmin.cpp b/modules/webadmin.cpp index 8ea25449..0338d61c 100644 --- a/modules/webadmin.cpp +++ b/modules/webadmin.cpp @@ -230,6 +230,7 @@ public: pNewUser->SetMultiClients(WebSock.GetParam("multiclients").ToBool()); pNewUser->SetTimestampAppend(WebSock.GetParam("appendtimestamp").ToBool()); pNewUser->SetTimestampPrepend(WebSock.GetParam("prependtimestamp").ToBool()); + pNewUser->SetTimezone(WebSock.GetParam("timezone")); pNewUser->SetTimezoneOffset(WebSock.GetParam("timezoneoffset").ToDouble()); pNewUser->SetJoinTries(WebSock.GetParam("jointries").ToUInt()); @@ -918,6 +919,7 @@ public: Tmpl["DefaultChanModes"] = pUser->GetDefaultChanModes(); Tmpl["BufferCount"] = CString(pUser->GetBufferCount()); Tmpl["TimestampFormat"] = pUser->GetTimestampFormat(); + Tmpl["Timezone"] = pUser->GetTimezone(); Tmpl["TimezoneOffset"] = CString(pUser->GetTimezoneOffset()); Tmpl["JoinTries"] = CString(pUser->JoinTries()); diff --git a/src/User.cpp b/src/User.cpp index 12f021d3..05995305 100644 --- a/src/User.cpp +++ b/src/User.cpp @@ -62,6 +62,7 @@ CUser::CUser(const CString& sUserName) // set path that depends on the user name: m_sUserPath = CZNC::Get().GetUserPath() + "/" + m_sUserName; + m_sTimezone = ""; m_fTimezoneOffset = 0; m_sNick = m_sCleanUserName; m_sIdent = m_sCleanUserName; @@ -219,6 +220,9 @@ bool CUser::ParseConfig(CConfig* pConfig, CString& sError) { return false; } } + if (pConfig->FindStringEntry("timezone", sValue)) { + SetTimezone(sValue); + } if (pConfig->FindStringEntry("timezoneoffset", sValue)) { SetTimezoneOffset(sValue.ToDouble()); } @@ -474,16 +478,7 @@ CString CUser::ExpandString(const CString& sStr) const { } CString& CUser::ExpandString(const CString& sStr, CString& sRet) const { - // offset is in hours, so * 60 * 60 gets us seconds - time_t iUserTime = time(NULL) + (time_t)(m_fTimezoneOffset * 60 * 60); - char *szTime = ctime(&iUserTime); - CString sTime; - - if (szTime) { - sTime = szTime; - // ctime() adds a trailing newline - sTime.Trim(); - } + CString sTime = CUtils::CTime(time(NULL), m_sTimezone); sRet = sStr; sRet.Replace("%user%", GetUserName()); @@ -511,20 +506,16 @@ CString CUser::AddTimestamp(const CString& sStr) const { } CString CUser::AddTimestamp(time_t tm, const CString& sStr) const { - char szTimestamp[1024]; CString sRet = sStr; if (!GetTimestampFormat().empty() && (m_bAppendTimestamp || m_bPrependTimestamp)) { - tm += (time_t)(m_fTimezoneOffset * 60 * 60); // offset is in hours - size_t i = strftime(szTimestamp, sizeof(szTimestamp), GetTimestampFormat().c_str(), localtime(&tm)); - // If strftime returns 0, an error occured in format, or result is empty - // In both cases just don't prepend/append anything to our string - if (0 == i) { + CString sTimestamp = CUtils::FormatTime(tm, GetTimestampFormat(), m_sTimezone); + if (sTimestamp.empty()) { return sRet; } if (m_bPrependTimestamp) { - sRet = szTimestamp; + sRet = sTimestamp; sRet += " " + sStr; } if (m_bAppendTimestamp) { @@ -533,7 +524,7 @@ CString CUser::AddTimestamp(time_t tm, const CString& sStr) const { // which turns off all previous attributes, including color, bold, underline, and italics. sRet += "\x0F "; - sRet += szTimestamp; + sRet += sTimestamp; } } @@ -678,6 +669,7 @@ bool CUser::Clone(const CUser& User, CString& sErrorRet, bool bCloneNetworks) { SetTimestampAppend(User.GetTimestampAppend()); SetTimestampPrepend(User.GetTimestampPrepend()); SetTimestampFormat(User.GetTimestampFormat()); + SetTimezone(User.GetTimezone()); SetTimezoneOffset(User.GetTimezoneOffset()); // !Flags @@ -828,6 +820,7 @@ CConfig CUser::ToConfig() { config.AddKeyValuePair("TimestampFormat", GetTimestampFormat()); config.AddKeyValuePair("AppendTimestamp", CString(GetTimestampAppend())); config.AddKeyValuePair("PrependTimestamp", CString(GetTimestampPrepend())); + config.AddKeyValuePair("Timezone", m_sTimezone); config.AddKeyValuePair("TimezoneOffset", CString(m_fTimezoneOffset)); config.AddKeyValuePair("JoinTries", CString(m_uMaxJoinTries)); diff --git a/src/Utils.cpp b/src/Utils.cpp index e205c00a..4e39774b 100644 --- a/src/Utils.cpp +++ b/src/Utils.cpp @@ -339,6 +339,64 @@ void CUtils::PrintStatus(bool bSuccess, const CString& sMessage) { fflush(stdout); } +CString CUtils::CTime(time_t t, const CString& sTZ) { + char s[30] = {}; // should have at least 26 bytes + if (sTZ.empty()) { + ctime_r(&t, s); + // ctime() adds a trailing newline + return CString(s).Trim_n(); + } + + // backup old value + char* oldTZ = getenv("TZ"); + if (oldTZ) oldTZ = strdup(oldTZ); + setenv("TZ", sTZ.c_str(), 1); + tzset(); + + ctime_r(&t, s); + + // restore old value + if (oldTZ) { + setenv("TZ", oldTZ, 1); + free(oldTZ); + } else { + unsetenv("TZ"); + } + tzset(); + + return CString(s).Trim_n(); +} + +CString CUtils::FormatTime(time_t t, const CString& sFormat, const CString& sTZ) { + char s[1024] = {}; + tm m; + if (sTZ.empty()) { + localtime_r(&t, &m); + strftime(s, sizeof(s), sFormat.c_str(), &m); + return s; + } + + // backup old value + char* oldTZ = getenv("TZ"); + if (oldTZ) oldTZ = strdup(oldTZ); + setenv("TZ", sTZ.c_str(), 1); + tzset(); + + localtime_r(&t, &m); + strftime(s, sizeof(s), sFormat.c_str(), &m); + + // restore old value + if (oldTZ) { + setenv("TZ", oldTZ, 1); + free(oldTZ); + } else { + unsetenv("TZ"); + } + tzset(); + + return s; +} + bool CTable::AddColumn(const CString& sName) { for (unsigned int a = 0; a < m_vsHeaders.size(); a++) { if (m_vsHeaders[a].Equals(sName)) {