lib/core/CTimezone_Windows.cc (146 lines of code) (raw):
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0 and the following additional limitation. Functionality enabled by the
* files subject to the Elastic License 2.0 may only be used in production when
* invoked by an Elasticsearch process with a license key installed that permits
* use of machine learning features. You may not use this file except in
* compliance with the Elastic License 2.0 and the foregoing additional
* limitation.
*/
#include <core/CTimezone.h>
#include <core/CLogger.h>
#include <core/CResourceLocator.h>
#include <core/CScopedFastLock.h>
#include <core/CSetEnv.h>
#include <boost/date_time/gregorian/gregorian.hpp>
#include <boost/date_time/posix_time/posix_time.hpp>
#include <exception>
#include <ctype.h>
#include <errno.h>
#include <stdlib.h>
#include <string.h>
namespace {
// To ensure the singleton is constructed before multiple threads may require it
// call instance() during the static initialisation phase of the program. Of
// course, the instance may already be constructed before this if another static
// object has used it.
const ml::core::CTimezone& DO_NOT_USE_THIS_VARIABLE = ml::core::CTimezone::instance();
}
namespace ml {
namespace core {
CTimezone::CTimezone() {
CScopedFastLock lock(m_Mutex);
// We never want to use the Visual C++ runtime library's timezone switching
// functionality, as it's appallingly bad. Therefore, make sure the TZ
// environment variable is unset, so that the operating system settings are
// obtained and used by the C runtime. Timezones other than the current
// operating system timezone will be dealt with using Boost.
if (::getenv("TZ") != 0) {
::_putenv_s("TZ", "");
}
::_tzset();
// Try to load the Boost timezone database
std::string path(CResourceLocator::resourceDir());
path += "/date_time_zonespec.csv";
try {
m_TimezoneDb.load_from_file(path);
} catch (std::exception& ex) {
LOG_ERROR(<< "Failed to load Boost timezone database from " << path
<< " : " << ex.what());
}
}
CTimezone::~CTimezone() {
}
CTimezone& CTimezone::instance() {
static CTimezone instance;
return instance;
}
const std::string& CTimezone::timezoneName() const {
CScopedFastLock lock(m_Mutex);
return m_Name;
}
bool CTimezone::timezoneName(const std::string& name) {
CScopedFastLock lock(m_Mutex);
if (name.empty()) {
m_Timezone.reset();
m_Name.clear();
return true;
}
m_Timezone = m_TimezoneDb.time_zone_from_region(name);
if (m_Timezone == 0) {
LOG_ERROR(<< "Unable to set timezone to " << name
<< " - operating system timezone settings will be used instead");
m_Name.clear();
return false;
}
m_Name = name;
return true;
}
bool CTimezone::setTimezone(const std::string& timezone) {
return CTimezone::instance().timezoneName(timezone);
}
std::string CTimezone::stdAbbrev() const {
CScopedFastLock lock(m_Mutex);
if (m_Timezone == 0) {
return _tzname[0];
}
return m_Timezone->std_zone_abbrev();
}
std::string CTimezone::dstAbbrev() const {
CScopedFastLock lock(m_Mutex);
if (m_Timezone == 0) {
return _tzname[1];
}
return m_Timezone->has_dst() ? m_Timezone->dst_zone_abbrev()
: m_Timezone->std_zone_abbrev();
}
core_t::TTime CTimezone::localToUtc(struct tm& localTime) const {
CScopedFastLock lock(m_Mutex);
if (m_Timezone == 0) {
// We're using operating system timezone settings, so use the C
// runtime's result
return ::mktime(&localTime);
}
// The timezone for this program has been explicitly set, and might not
// be the same as the operating system timezone, so use Boost
static const boost::posix_time::ptime EPOCH(boost::gregorian::date(1970, 1, 1));
boost::gregorian::date dateIn(boost::gregorian::date_from_tm(localTime));
boost::posix_time::time_duration timeIn(
static_cast<boost::posix_time::time_duration::hour_type>(localTime.tm_hour),
static_cast<boost::posix_time::time_duration::min_type>(localTime.tm_min),
static_cast<boost::posix_time::time_duration::sec_type>(localTime.tm_sec));
boost::posix_time::time_duration diff;
try {
boost::local_time::local_date_time boostLocal(
dateIn, timeIn, m_Timezone, boost::local_time::local_date_time::EXCEPTION_ON_ERROR);
diff = boostLocal.utc_time() - EPOCH;
localTime.tm_isdst = (boostLocal.is_dst() ? 1 : 0);
} catch (boost::local_time::ambiguous_result&) {
// If we get an ambiguous time, assume it's standard, not daylight
// savings
boost::local_time::local_date_time boostLocal(dateIn, timeIn, m_Timezone, false);
diff = boostLocal.utc_time() - EPOCH;
localTime.tm_isdst = 0;
} catch (std::exception& ex) {
// Any other exception represents an error in the input
LOG_ERROR(<< "Error converting local time to UTC : " << ex.what());
errno = EINVAL;
return 0;
}
return diff.total_seconds();
}
bool CTimezone::utcToLocal(core_t::TTime utcTime, struct tm& localTime) const {
CScopedFastLock lock(m_Mutex);
if (m_Timezone == 0) {
// We're using operating system timezone settings, so use the C runtime
if (::localtime_s(&localTime, &utcTime) != 0) {
return false;
}
return true;
}
// The timezone for this program has been explicitly set, and might not
// be the same as the operating system timezone, so use Boost
boost::posix_time::ptime boostUtc(boost::posix_time::from_time_t(utcTime));
boost::local_time::local_date_time boostLocal(boostUtc, m_Timezone);
localTime = boost::local_time::to_tm(boostLocal);
return true;
}
bool CTimezone::dateFields(core_t::TTime utcTime,
int& daysSinceSunday,
int& dayOfMonth,
int& daysSinceJanuary1st,
int& monthsSinceJanuary,
int& yearsSince1900,
int& secondsSinceMidnight) const {
daysSinceSunday = -1;
dayOfMonth = -1;
daysSinceJanuary1st = -1;
monthsSinceJanuary = -1;
yearsSince1900 = -1;
secondsSinceMidnight = -1;
struct tm result;
// core_t::TTime holds an epoch time (UTC)
if (this->utcToLocal(utcTime, result)) {
daysSinceSunday = result.tm_wday;
dayOfMonth = result.tm_mday;
monthsSinceJanuary = result.tm_mon;
daysSinceJanuary1st = result.tm_yday;
yearsSince1900 = result.tm_year;
secondsSinceMidnight = 3600 * result.tm_hour + 60 * result.tm_min +
result.tm_sec;
return true;
}
return false;
}
}
}