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 #endif
51 #endif
52
53 #include <cstdlib>
54 #include <cstring>
55 #include <string>
56
57 #include "time_zone_fixed.h"
58 #include "time_zone_impl.h"
59
60 namespace absl {
61 ABSL_NAMESPACE_BEGIN
62 namespace time_internal {
63 namespace cctz {
64
65 namespace {
66 #if defined(USE_WIN32_LOCAL_TIME_ZONE)
67 // Calls the WinRT Calendar.GetTimeZone method to obtain the IANA ID of the
68 // local time zone. Returns an empty vector in case of an error.
win32_local_time_zone(const HMODULE combase)69 std::string win32_local_time_zone(const HMODULE combase) {
70 std::string result;
71 const auto ro_activate_instance =
72 reinterpret_cast<decltype(&RoActivateInstance)>(
73 GetProcAddress(combase, "RoActivateInstance"));
74 if (!ro_activate_instance) {
75 return result;
76 }
77 const auto windows_create_string_reference =
78 reinterpret_cast<decltype(&WindowsCreateStringReference)>(
79 GetProcAddress(combase, "WindowsCreateStringReference"));
80 if (!windows_create_string_reference) {
81 return result;
82 }
83 const auto windows_delete_string =
84 reinterpret_cast<decltype(&WindowsDeleteString)>(
85 GetProcAddress(combase, "WindowsDeleteString"));
86 if (!windows_delete_string) {
87 return result;
88 }
89 const auto windows_get_string_raw_buffer =
90 reinterpret_cast<decltype(&WindowsGetStringRawBuffer)>(
91 GetProcAddress(combase, "WindowsGetStringRawBuffer"));
92 if (!windows_get_string_raw_buffer) {
93 return result;
94 }
95
96 // The string returned by WindowsCreateStringReference doesn't need to be
97 // deleted.
98 HSTRING calendar_class_id;
99 HSTRING_HEADER calendar_class_id_header;
100 HRESULT hr = windows_create_string_reference(
101 RuntimeClass_Windows_Globalization_Calendar,
102 sizeof(RuntimeClass_Windows_Globalization_Calendar) / sizeof(wchar_t) - 1,
103 &calendar_class_id_header, &calendar_class_id);
104 if (FAILED(hr)) {
105 return result;
106 }
107
108 IInspectable* calendar;
109 hr = ro_activate_instance(calendar_class_id, &calendar);
110 if (FAILED(hr)) {
111 return result;
112 }
113
114 ABI::Windows::Globalization::ITimeZoneOnCalendar* time_zone;
115 hr = calendar->QueryInterface(IID_PPV_ARGS(&time_zone));
116 if (FAILED(hr)) {
117 calendar->Release();
118 return result;
119 }
120
121 HSTRING tz_hstr;
122 hr = time_zone->GetTimeZone(&tz_hstr);
123 if (SUCCEEDED(hr)) {
124 UINT32 wlen;
125 const PCWSTR tz_wstr = windows_get_string_raw_buffer(tz_hstr, &wlen);
126 if (tz_wstr) {
127 const int size =
128 WideCharToMultiByte(CP_UTF8, 0, tz_wstr, static_cast<int>(wlen),
129 nullptr, 0, nullptr, nullptr);
130 result.resize(static_cast<size_t>(size));
131 WideCharToMultiByte(CP_UTF8, 0, tz_wstr, static_cast<int>(wlen),
132 &result[0], size, nullptr, nullptr);
133 }
134 windows_delete_string(tz_hstr);
135 }
136 time_zone->Release();
137 calendar->Release();
138 return result;
139 }
140 #endif
141 } // namespace
142
name() const143 std::string time_zone::name() const { return effective_impl().Name(); }
144
lookup(const time_point<seconds> & tp) const145 time_zone::absolute_lookup time_zone::lookup(
146 const time_point<seconds>& tp) const {
147 return effective_impl().BreakTime(tp);
148 }
149
lookup(const civil_second & cs) const150 time_zone::civil_lookup time_zone::lookup(const civil_second& cs) const {
151 return effective_impl().MakeTime(cs);
152 }
153
next_transition(const time_point<seconds> & tp,civil_transition * trans) const154 bool time_zone::next_transition(const time_point<seconds>& tp,
155 civil_transition* trans) const {
156 return effective_impl().NextTransition(tp, trans);
157 }
158
prev_transition(const time_point<seconds> & tp,civil_transition * trans) const159 bool time_zone::prev_transition(const time_point<seconds>& tp,
160 civil_transition* trans) const {
161 return effective_impl().PrevTransition(tp, trans);
162 }
163
version() const164 std::string time_zone::version() const { return effective_impl().Version(); }
165
description() const166 std::string time_zone::description() const {
167 return effective_impl().Description();
168 }
169
effective_impl() const170 const time_zone::Impl& time_zone::effective_impl() const {
171 if (impl_ == nullptr) {
172 // Dereferencing an implicit-UTC time_zone is expected to be
173 // rare, so we don't mind paying a small synchronization cost.
174 return *time_zone::Impl::UTC().impl_;
175 }
176 return *impl_;
177 }
178
load_time_zone(const std::string & name,time_zone * tz)179 bool load_time_zone(const std::string& name, time_zone* tz) {
180 return time_zone::Impl::LoadTimeZone(name, tz);
181 }
182
utc_time_zone()183 time_zone utc_time_zone() {
184 return time_zone::Impl::UTC(); // avoid name lookup
185 }
186
fixed_time_zone(const seconds & offset)187 time_zone fixed_time_zone(const seconds& offset) {
188 time_zone tz;
189 load_time_zone(FixedOffsetToName(offset), &tz);
190 return tz;
191 }
192
local_time_zone()193 time_zone local_time_zone() {
194 const char* zone = ":localtime";
195 #if defined(__ANDROID__)
196 char sysprop[PROP_VALUE_MAX];
197 if (__system_property_get("persist.sys.timezone", sysprop) > 0) {
198 zone = sysprop;
199 }
200 #endif
201 #if defined(__APPLE__)
202 std::vector<char> buffer;
203 CFTimeZoneRef tz_default = CFTimeZoneCopyDefault();
204 if (CFStringRef tz_name = CFTimeZoneGetName(tz_default)) {
205 CFStringEncoding encoding = kCFStringEncodingUTF8;
206 CFIndex length = CFStringGetLength(tz_name);
207 CFIndex max_size = CFStringGetMaximumSizeForEncoding(length, encoding) + 1;
208 buffer.resize(static_cast<size_t>(max_size));
209 if (CFStringGetCString(tz_name, &buffer[0], max_size, encoding)) {
210 zone = &buffer[0];
211 }
212 }
213 CFRelease(tz_default);
214 #endif
215 #if defined(__Fuchsia__)
216 std::string primary_tz;
217 [&]() {
218 // Note: We can't use the synchronous FIDL API here because it doesn't
219 // allow timeouts; if the FIDL call failed, local_time_zone() would never
220 // return.
221
222 const zx::duration kTimeout = zx::msec(500);
223
224 // Don't attach to the thread because otherwise the thread's dispatcher
225 // would be set to null when the loop is destroyed, causing any other FIDL
226 // code running on the same thread to crash.
227 async::Loop loop(&kAsyncLoopConfigNeverAttachToThread);
228
229 fuchsia::intl::PropertyProviderHandle handle;
230 zx_status_t status = fdio_service_connect_by_name(
231 fuchsia::intl::PropertyProvider::Name_,
232 handle.NewRequest().TakeChannel().release());
233 if (status != ZX_OK) {
234 return;
235 }
236
237 fuchsia::intl::PropertyProviderPtr intl_provider;
238 status = intl_provider.Bind(std::move(handle), loop.dispatcher());
239 if (status != ZX_OK) {
240 return;
241 }
242
243 intl_provider->GetProfile(
244 [&loop, &primary_tz](fuchsia::intl::Profile profile) {
245 if (!profile.time_zones().empty()) {
246 primary_tz = profile.time_zones()[0].id;
247 }
248 loop.Quit();
249 });
250 loop.Run(zx::deadline_after(kTimeout));
251 }();
252
253 if (!primary_tz.empty()) {
254 zone = primary_tz.c_str();
255 }
256 #endif
257 #if defined(USE_WIN32_LOCAL_TIME_ZONE)
258 // Use the WinRT Calendar class to get the local time zone. This feature is
259 // available on Windows 10 and later. The library is dynamically linked to
260 // maintain binary compatibility with Windows XP - Windows 7. On Windows 8,
261 // The combase.dll API functions are available but the RoActivateInstance
262 // call will fail for the Calendar class.
263 std::string winrt_tz;
264 const HMODULE combase =
265 LoadLibraryEx(_T("combase.dll"), nullptr, LOAD_LIBRARY_SEARCH_SYSTEM32);
266 if (combase) {
267 const auto ro_initialize = reinterpret_cast<decltype(&::RoInitialize)>(
268 GetProcAddress(combase, "RoInitialize"));
269 const auto ro_uninitialize = reinterpret_cast<decltype(&::RoUninitialize)>(
270 GetProcAddress(combase, "RoUninitialize"));
271 if (ro_initialize && ro_uninitialize) {
272 const HRESULT hr = ro_initialize(RO_INIT_MULTITHREADED);
273 // RPC_E_CHANGED_MODE means that a previous RoInitialize call specified
274 // a different concurrency model. The WinRT runtime is initialized and
275 // should work for our purpose here, but we should *not* call
276 // RoUninitialize because it's a failure.
277 if (SUCCEEDED(hr) || hr == RPC_E_CHANGED_MODE) {
278 winrt_tz = win32_local_time_zone(combase);
279 if (SUCCEEDED(hr)) {
280 ro_uninitialize();
281 }
282 }
283 }
284 FreeLibrary(combase);
285 }
286 if (!winrt_tz.empty()) {
287 zone = winrt_tz.c_str();
288 }
289 #endif
290
291 // Allow ${TZ} to override to default zone.
292 char* tz_env = nullptr;
293 #if defined(_MSC_VER)
294 _dupenv_s(&tz_env, nullptr, "TZ");
295 #else
296 tz_env = std::getenv("TZ");
297 #endif
298 if (tz_env) zone = tz_env;
299
300 // We only support the "[:]<zone-name>" form.
301 if (*zone == ':') ++zone;
302
303 // Map "localtime" to a system-specific name, but
304 // allow ${LOCALTIME} to override the default name.
305 char* localtime_env = nullptr;
306 if (strcmp(zone, "localtime") == 0) {
307 #if defined(_MSC_VER)
308 // System-specific default is just "localtime".
309 _dupenv_s(&localtime_env, nullptr, "LOCALTIME");
310 #else
311 zone = "/etc/localtime"; // System-specific default.
312 localtime_env = std::getenv("LOCALTIME");
313 #endif
314 if (localtime_env) zone = localtime_env;
315 }
316
317 const std::string name = zone;
318 #if defined(_MSC_VER)
319 free(localtime_env);
320 free(tz_env);
321 #endif
322
323 time_zone tz;
324 load_time_zone(name, &tz); // Falls back to UTC.
325 // TODO: Follow the RFC3339 "Unknown Local Offset Convention" and
326 // arrange for %z to generate "-0000" when we don't know the local
327 // offset because the load_time_zone() failed and we're using UTC.
328 return tz;
329 }
330
331 } // namespace cctz
332 } // namespace time_internal
333 ABSL_NAMESPACE_END
334 } // namespace absl
335