time_zone_lookup.cc (10828B)
1 // Copyright 2016 Google Inc. All Rights Reserved. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // https://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 #include "absl/base/config.h" 16 #include "absl/time/internal/cctz/include/cctz/time_zone.h" 17 18 #if defined(__ANDROID__) 19 #include <sys/system_properties.h> 20 #endif 21 22 #if defined(__APPLE__) 23 #include <CoreFoundation/CFTimeZone.h> 24 25 #include <vector> 26 #endif 27 28 #if defined(__Fuchsia__) 29 #include <fuchsia/intl/cpp/fidl.h> 30 #include <lib/async-loop/cpp/loop.h> 31 #include <lib/fdio/directory.h> 32 #include <zircon/types.h> 33 #endif 34 35 #if defined(_WIN32) 36 #include <sdkddkver.h> 37 // Include only when the SDK is for Windows 10 (and later), and the binary is 38 // targeted for Windows XP and later. 39 // Note: The Windows SDK added windows.globalization.h file for Windows 10, but 40 // MinGW did not add it until NTDDI_WIN10_NI (SDK version 10.0.22621.0). 41 #if ((defined(_WIN32_WINNT_WIN10) && !defined(__MINGW32__)) || \ 42 (defined(NTDDI_WIN10_NI) && NTDDI_VERSION >= NTDDI_WIN10_NI)) && \ 43 (_WIN32_WINNT >= _WIN32_WINNT_WINXP) 44 #define USE_WIN32_LOCAL_TIME_ZONE 45 #include <roapi.h> 46 #include <tchar.h> 47 #include <wchar.h> 48 #include <windows.globalization.h> 49 #include <windows.h> 50 #include <winstring.h> 51 #endif 52 #endif 53 54 #include <cstdlib> 55 #include <cstring> 56 #include <string> 57 58 #include "absl/time/internal/cctz/src/time_zone_fixed.h" 59 #include "absl/time/internal/cctz/src/time_zone_impl.h" 60 61 namespace absl { 62 ABSL_NAMESPACE_BEGIN 63 namespace time_internal { 64 namespace cctz { 65 66 namespace { 67 #if defined(USE_WIN32_LOCAL_TIME_ZONE) 68 // Calls the WinRT Calendar.GetTimeZone method to obtain the IANA ID of the 69 // local time zone. Returns an empty vector in case of an error. 70 std::string win32_local_time_zone(const HMODULE combase) { 71 std::string result; 72 const auto ro_activate_instance = 73 reinterpret_cast<decltype(&RoActivateInstance)>( 74 GetProcAddress(combase, "RoActivateInstance")); 75 if (!ro_activate_instance) { 76 return result; 77 } 78 const auto windows_create_string_reference = 79 reinterpret_cast<decltype(&WindowsCreateStringReference)>( 80 GetProcAddress(combase, "WindowsCreateStringReference")); 81 if (!windows_create_string_reference) { 82 return result; 83 } 84 const auto windows_delete_string = 85 reinterpret_cast<decltype(&WindowsDeleteString)>( 86 GetProcAddress(combase, "WindowsDeleteString")); 87 if (!windows_delete_string) { 88 return result; 89 } 90 const auto windows_get_string_raw_buffer = 91 reinterpret_cast<decltype(&WindowsGetStringRawBuffer)>( 92 GetProcAddress(combase, "WindowsGetStringRawBuffer")); 93 if (!windows_get_string_raw_buffer) { 94 return result; 95 } 96 97 // The string returned by WindowsCreateStringReference doesn't need to be 98 // deleted. 99 HSTRING calendar_class_id; 100 HSTRING_HEADER calendar_class_id_header; 101 HRESULT hr = windows_create_string_reference( 102 RuntimeClass_Windows_Globalization_Calendar, 103 sizeof(RuntimeClass_Windows_Globalization_Calendar) / sizeof(wchar_t) - 1, 104 &calendar_class_id_header, &calendar_class_id); 105 if (FAILED(hr)) { 106 return result; 107 } 108 109 IInspectable* calendar; 110 hr = ro_activate_instance(calendar_class_id, &calendar); 111 if (FAILED(hr)) { 112 return result; 113 } 114 115 ABI::Windows::Globalization::ITimeZoneOnCalendar* time_zone; 116 hr = calendar->QueryInterface(IID_PPV_ARGS(&time_zone)); 117 if (FAILED(hr)) { 118 calendar->Release(); 119 return result; 120 } 121 122 HSTRING tz_hstr; 123 hr = time_zone->GetTimeZone(&tz_hstr); 124 if (SUCCEEDED(hr)) { 125 UINT32 wlen; 126 const PCWSTR tz_wstr = windows_get_string_raw_buffer(tz_hstr, &wlen); 127 if (tz_wstr) { 128 const int size = 129 WideCharToMultiByte(CP_UTF8, 0, tz_wstr, static_cast<int>(wlen), 130 nullptr, 0, nullptr, nullptr); 131 result.resize(static_cast<size_t>(size)); 132 WideCharToMultiByte(CP_UTF8, 0, tz_wstr, static_cast<int>(wlen), 133 &result[0], size, nullptr, nullptr); 134 } 135 windows_delete_string(tz_hstr); 136 } 137 time_zone->Release(); 138 calendar->Release(); 139 return result; 140 } 141 #endif 142 } // namespace 143 144 std::string time_zone::name() const { return effective_impl().Name(); } 145 146 time_zone::absolute_lookup time_zone::lookup( 147 const time_point<seconds>& tp) const { 148 return effective_impl().BreakTime(tp); 149 } 150 151 time_zone::civil_lookup time_zone::lookup(const civil_second& cs) const { 152 return effective_impl().MakeTime(cs); 153 } 154 155 bool time_zone::next_transition(const time_point<seconds>& tp, 156 civil_transition* trans) const { 157 return effective_impl().NextTransition(tp, trans); 158 } 159 160 bool time_zone::prev_transition(const time_point<seconds>& tp, 161 civil_transition* trans) const { 162 return effective_impl().PrevTransition(tp, trans); 163 } 164 165 std::string time_zone::version() const { return effective_impl().Version(); } 166 167 std::string time_zone::description() const { 168 return effective_impl().Description(); 169 } 170 171 const time_zone::Impl& time_zone::effective_impl() const { 172 if (impl_ == nullptr) { 173 // Dereferencing an implicit-UTC time_zone is expected to be 174 // rare, so we don't mind paying a small synchronization cost. 175 return *time_zone::Impl::UTC().impl_; 176 } 177 return *impl_; 178 } 179 180 bool load_time_zone(const std::string& name, time_zone* tz) { 181 return time_zone::Impl::LoadTimeZone(name, tz); 182 } 183 184 time_zone utc_time_zone() { 185 return time_zone::Impl::UTC(); // avoid name lookup 186 } 187 188 time_zone fixed_time_zone(const seconds& offset) { 189 time_zone tz; 190 load_time_zone(FixedOffsetToName(offset), &tz); 191 return tz; 192 } 193 194 time_zone local_time_zone() { 195 const char* zone = ":localtime"; 196 #if defined(__ANDROID__) 197 char sysprop[PROP_VALUE_MAX]; 198 if (__system_property_get("persist.sys.timezone", sysprop) > 0) { 199 zone = sysprop; 200 } 201 #endif 202 #if defined(__APPLE__) 203 std::vector<char> buffer; 204 CFTimeZoneRef tz_default = CFTimeZoneCopyDefault(); 205 if (CFStringRef tz_name = CFTimeZoneGetName(tz_default)) { 206 CFStringEncoding encoding = kCFStringEncodingUTF8; 207 CFIndex length = CFStringGetLength(tz_name); 208 CFIndex max_size = CFStringGetMaximumSizeForEncoding(length, encoding) + 1; 209 buffer.resize(static_cast<size_t>(max_size)); 210 if (CFStringGetCString(tz_name, &buffer[0], max_size, encoding)) { 211 zone = &buffer[0]; 212 } 213 } 214 CFRelease(tz_default); 215 #endif 216 #if defined(__Fuchsia__) 217 std::string primary_tz; 218 [&]() { 219 // Note: We can't use the synchronous FIDL API here because it doesn't 220 // allow timeouts; if the FIDL call failed, local_time_zone() would never 221 // return. 222 223 const zx::duration kTimeout = zx::msec(500); 224 225 // Don't attach to the thread because otherwise the thread's dispatcher 226 // would be set to null when the loop is destroyed, causing any other FIDL 227 // code running on the same thread to crash. 228 async::Loop loop(&kAsyncLoopConfigNeverAttachToThread); 229 230 fuchsia::intl::PropertyProviderHandle handle; 231 zx_status_t status = fdio_service_connect_by_name( 232 fuchsia::intl::PropertyProvider::Name_, 233 handle.NewRequest().TakeChannel().release()); 234 if (status != ZX_OK) { 235 return; 236 } 237 238 fuchsia::intl::PropertyProviderPtr intl_provider; 239 status = intl_provider.Bind(std::move(handle), loop.dispatcher()); 240 if (status != ZX_OK) { 241 return; 242 } 243 244 intl_provider->GetProfile( 245 [&loop, &primary_tz](fuchsia::intl::Profile profile) { 246 if (!profile.time_zones().empty()) { 247 primary_tz = profile.time_zones()[0].id; 248 } 249 loop.Quit(); 250 }); 251 loop.Run(zx::deadline_after(kTimeout)); 252 }(); 253 254 if (!primary_tz.empty()) { 255 zone = primary_tz.c_str(); 256 } 257 #endif 258 #if defined(USE_WIN32_LOCAL_TIME_ZONE) 259 // Use the WinRT Calendar class to get the local time zone. This feature is 260 // available on Windows 10 and later. The library is dynamically linked to 261 // maintain binary compatibility with Windows XP - Windows 7. On Windows 8, 262 // The combase.dll API functions are available but the RoActivateInstance 263 // call will fail for the Calendar class. 264 std::string winrt_tz; 265 const HMODULE combase = 266 LoadLibraryEx(_T("combase.dll"), nullptr, LOAD_LIBRARY_SEARCH_SYSTEM32); 267 if (combase) { 268 const auto ro_initialize = reinterpret_cast<decltype(&::RoInitialize)>( 269 GetProcAddress(combase, "RoInitialize")); 270 const auto ro_uninitialize = reinterpret_cast<decltype(&::RoUninitialize)>( 271 GetProcAddress(combase, "RoUninitialize")); 272 if (ro_initialize && ro_uninitialize) { 273 const HRESULT hr = ro_initialize(RO_INIT_MULTITHREADED); 274 // RPC_E_CHANGED_MODE means that a previous RoInitialize call specified 275 // a different concurrency model. The WinRT runtime is initialized and 276 // should work for our purpose here, but we should *not* call 277 // RoUninitialize because it's a failure. 278 if (SUCCEEDED(hr) || hr == RPC_E_CHANGED_MODE) { 279 winrt_tz = win32_local_time_zone(combase); 280 if (SUCCEEDED(hr)) { 281 ro_uninitialize(); 282 } 283 } 284 } 285 FreeLibrary(combase); 286 } 287 if (!winrt_tz.empty()) { 288 zone = winrt_tz.c_str(); 289 } 290 #endif 291 292 // Allow ${TZ} to override to default zone. 293 char* tz_env = nullptr; 294 #if defined(_MSC_VER) 295 _dupenv_s(&tz_env, nullptr, "TZ"); 296 #else 297 tz_env = std::getenv("TZ"); 298 #endif 299 if (tz_env) zone = tz_env; 300 301 // We only support the "[:]<zone-name>" form. 302 if (*zone == ':') ++zone; 303 304 // Map "localtime" to a system-specific name, but 305 // allow ${LOCALTIME} to override the default name. 306 char* localtime_env = nullptr; 307 if (strcmp(zone, "localtime") == 0) { 308 #if defined(_MSC_VER) 309 // System-specific default is just "localtime". 310 _dupenv_s(&localtime_env, nullptr, "LOCALTIME"); 311 #else 312 zone = "/etc/localtime"; // System-specific default. 313 localtime_env = std::getenv("LOCALTIME"); 314 #endif 315 if (localtime_env) zone = localtime_env; 316 } 317 318 const std::string name = zone; 319 #if defined(_MSC_VER) 320 free(localtime_env); 321 free(tz_env); 322 #endif 323 324 time_zone tz; 325 load_time_zone(name, &tz); // Falls back to UTC. 326 // TODO: Follow the RFC3339 "Unknown Local Offset Convention" and 327 // arrange for %z to generate "-0000" when we don't know the local 328 // offset because the load_time_zone() failed and we're using UTC. 329 return tz; 330 } 331 332 } // namespace cctz 333 } // namespace time_internal 334 ABSL_NAMESPACE_END 335 } // namespace absl