Coverage Report

Created: 2025-07-12 06:45

/src/cctz/src/time_zone_lookup.cc
Line
Count
Source (jump to first uncovered line)
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 "cctz/time_zone.h"
16
17
#if defined(__ANDROID__)
18
#include <sys/system_properties.h>
19
#endif
20
21
#if defined(__APPLE__)
22
#include <CoreFoundation/CFTimeZone.h>
23
#include <vector>
24
#endif
25
26
#if defined(__Fuchsia__)
27
#include <fuchsia/intl/cpp/fidl.h>
28
#include <lib/async-loop/cpp/loop.h>
29
#include <lib/fdio/directory.h>
30
#include <zircon/types.h>
31
#endif
32
33
#if defined(_WIN32)
34
// Include only when <icu.h> is available.
35
// https://learn.microsoft.com/en-us/windows/win32/intl/international-components-for-unicode--icu-
36
// https://devblogs.microsoft.com/oldnewthing/20210527-00/?p=105255
37
#if defined(__has_include)
38
#if __has_include(<icu.h>)
39
#define USE_WIN32_LOCAL_TIME_ZONE
40
#include <windows.h>
41
#pragma push_macro("_WIN32_WINNT")
42
#pragma push_macro("NTDDI_VERSION")
43
// Minimum _WIN32_WINNT and NTDDI_VERSION to use ucal_getTimeZoneIDForWindowsID
44
#undef _WIN32_WINNT
45
#define _WIN32_WINNT 0x0A00  // == _WIN32_WINNT_WIN10
46
#undef NTDDI_VERSION
47
#define NTDDI_VERSION 0x0A000004  // == NTDDI_WIN10_RS3
48
#include <icu.h>
49
#pragma pop_macro("NTDDI_VERSION")
50
#pragma pop_macro("_WIN32_WINNT")
51
#include <timezoneapi.h>
52
#include <atomic>
53
#endif  // __has_include(<icu.h>)
54
#endif  // __has_include
55
#endif  // _WIN32
56
57
#include <array>
58
#include <cstdint>
59
#include <cstdlib>
60
#include <cstring>
61
#include <string>
62
63
#include "time_zone_fixed.h"
64
#include "time_zone_impl.h"
65
66
namespace cctz {
67
68
namespace {
69
#if defined(USE_WIN32_LOCAL_TIME_ZONE)
70
// True if we have already failed to load the API.
71
static std::atomic_bool g_ucal_getTimeZoneIDForWindowsIDUnavailable;
72
static std::atomic<decltype(ucal_getTimeZoneIDForWindowsID)*>
73
    g_ucal_getTimeZoneIDForWindowsIDRef;
74
75
std::string win32_local_time_zone() {
76
  // If we have already failed to load the API, then just give up.
77
  if (g_ucal_getTimeZoneIDForWindowsIDUnavailable.load()) {
78
    return "";
79
  }
80
81
  auto ucal_getTimeZoneIDForWindowsIDFunc =
82
      g_ucal_getTimeZoneIDForWindowsIDRef.load();
83
  if (ucal_getTimeZoneIDForWindowsIDFunc == nullptr) {
84
    // If we have already failed to load the API, then just give up.
85
    if (g_ucal_getTimeZoneIDForWindowsIDUnavailable.load()) {
86
      return "";
87
    }
88
89
    const HMODULE icudll = ::LoadLibraryExW(L"icu.dll", nullptr,
90
                                            LOAD_LIBRARY_SEARCH_SYSTEM32);
91
92
    if (icudll == nullptr) {
93
      g_ucal_getTimeZoneIDForWindowsIDUnavailable.store(true);
94
      return "";
95
    }
96
97
    ucal_getTimeZoneIDForWindowsIDFunc =
98
        reinterpret_cast<decltype(ucal_getTimeZoneIDForWindowsID)*>(
99
            ::GetProcAddress(icudll, "ucal_getTimeZoneIDForWindowsID"));
100
101
    if (ucal_getTimeZoneIDForWindowsIDFunc == nullptr) {
102
      g_ucal_getTimeZoneIDForWindowsIDUnavailable.store(true);
103
      return "";
104
    }
105
    // store-race is not a problem here, because ::GetProcAddress() returns the
106
    // same address for the same function in the same DLL.
107
    g_ucal_getTimeZoneIDForWindowsIDRef.store(
108
        ucal_getTimeZoneIDForWindowsIDFunc);
109
110
    // We intentionally do not call ::FreeLibrary() here to avoid frequent DLL
111
    // loadings and unloading. As "icu.dll" is a system library, keeping it on
112
    // memory is supposed to have no major drawback.
113
  }
114
115
  DYNAMIC_TIME_ZONE_INFORMATION info = {};
116
  if (::GetDynamicTimeZoneInformation(&info) == TIME_ZONE_ID_INVALID) {
117
    return "";
118
  }
119
120
  std::array<UChar, 128> buffer;
121
  UErrorCode status = U_ZERO_ERROR;
122
  const auto num_chars_in_buffer = ucal_getTimeZoneIDForWindowsIDFunc(
123
      reinterpret_cast<const UChar*>(info.TimeZoneKeyName), -1, nullptr,
124
      buffer.data(), static_cast<int32_t>(buffer.size()), &status);
125
  if (status != U_ZERO_ERROR || num_chars_in_buffer <= 0 ||
126
      num_chars_in_buffer > static_cast<int32_t>(buffer.size())) {
127
    return "";
128
  }
129
130
  const int num_bytes_in_utf8 = ::WideCharToMultiByte(
131
      CP_UTF8, 0, reinterpret_cast<const wchar_t*>(buffer.data()),
132
      static_cast<int>(num_chars_in_buffer), nullptr, 0, nullptr, nullptr);
133
  std::string local_time_str;
134
  local_time_str.resize(static_cast<size_t>(num_bytes_in_utf8));
135
  ::WideCharToMultiByte(CP_UTF8, 0, reinterpret_cast<const wchar_t*>(buffer.data()),
136
                        static_cast<int>(num_chars_in_buffer),
137
                        &local_time_str[0], num_bytes_in_utf8, nullptr,
138
                        nullptr);
139
  return local_time_str;
140
}
141
#endif  // USE_WIN32_LOCAL_TIME_ZONE
142
}  // namespace
143
144
0
std::string time_zone::name() const {
145
0
  return effective_impl().Name();
146
0
}
147
148
time_zone::absolute_lookup time_zone::lookup(
149
8.64k
    const time_point<seconds>& tp) const {
150
8.64k
  return effective_impl().BreakTime(tp);
151
8.64k
}
152
153
12.8k
time_zone::civil_lookup time_zone::lookup(const civil_second& cs) const {
154
12.8k
  return effective_impl().MakeTime(cs);
155
12.8k
}
156
157
bool time_zone::next_transition(const time_point<seconds>& tp,
158
0
                                civil_transition* trans) const {
159
0
  return effective_impl().NextTransition(tp, trans);
160
0
}
161
162
bool time_zone::prev_transition(const time_point<seconds>& tp,
163
0
                                civil_transition* trans) const {
164
0
  return effective_impl().PrevTransition(tp, trans);
165
0
}
166
167
0
std::string time_zone::version() const {
168
0
  return effective_impl().Version();
169
0
}
170
171
0
std::string time_zone::description() const {
172
0
  return effective_impl().Description();
173
0
}
174
175
21.5k
const time_zone::Impl& time_zone::effective_impl() const {
176
21.5k
  if (impl_ == nullptr) {
177
    // Dereferencing an implicit-UTC time_zone is expected to be
178
    // rare, so we don't mind paying a small synchronization cost.
179
0
    return *time_zone::Impl::UTC().impl_;
180
0
  }
181
21.5k
  return *impl_;
182
21.5k
}
183
184
8.16k
bool load_time_zone(const std::string& name, time_zone* tz) {
185
8.16k
  return time_zone::Impl::LoadTimeZone(name, tz);
186
8.16k
}
187
188
165
time_zone utc_time_zone() {
189
165
  return time_zone::Impl::UTC();  // avoid name lookup
190
165
}
191
192
0
time_zone fixed_time_zone(const seconds& offset) {
193
0
  time_zone tz;
194
0
  load_time_zone(FixedOffsetToName(offset), &tz);
195
0
  return tz;
196
0
}
197
198
0
time_zone local_time_zone() {
199
0
  const char* zone = ":localtime";
200
#if defined(__ANDROID__)
201
  char sysprop[PROP_VALUE_MAX];
202
  if (__system_property_get("persist.sys.timezone", sysprop) > 0) {
203
    zone = sysprop;
204
  }
205
#endif
206
#if defined(__APPLE__)
207
  std::vector<char> buffer;
208
  CFTimeZoneRef tz_default = CFTimeZoneCopyDefault();
209
  if (CFStringRef tz_name = CFTimeZoneGetName(tz_default)) {
210
    CFStringEncoding encoding = kCFStringEncodingUTF8;
211
    CFIndex length = CFStringGetLength(tz_name);
212
    CFIndex max_size = CFStringGetMaximumSizeForEncoding(length, encoding) + 1;
213
    buffer.resize(static_cast<size_t>(max_size));
214
    if (CFStringGetCString(tz_name, &buffer[0], max_size, encoding)) {
215
      zone = &buffer[0];
216
    }
217
  }
218
  CFRelease(tz_default);
219
#endif
220
#if defined(__Fuchsia__)
221
  std::string primary_tz;
222
  [&]() {
223
    // Note: We can't use the synchronous FIDL API here because it doesn't
224
    // allow timeouts; if the FIDL call failed, local_time_zone() would never
225
    // return.
226
227
    const zx::duration kTimeout = zx::msec(500);
228
229
    // Don't attach to the thread because otherwise the thread's dispatcher
230
    // would be set to null when the loop is destroyed, causing any other FIDL
231
    // code running on the same thread to crash.
232
    async::Loop loop(&kAsyncLoopConfigNeverAttachToThread);
233
234
    fuchsia::intl::PropertyProviderHandle handle;
235
    zx_status_t status = fdio_service_connect_by_name(
236
        fuchsia::intl::PropertyProvider::Name_,
237
        handle.NewRequest().TakeChannel().release());
238
    if (status != ZX_OK) {
239
      return;
240
    }
241
242
    fuchsia::intl::PropertyProviderPtr intl_provider;
243
    status = intl_provider.Bind(std::move(handle), loop.dispatcher());
244
    if (status != ZX_OK) {
245
      return;
246
    }
247
248
    intl_provider->GetProfile(
249
        [&loop, &primary_tz](fuchsia::intl::Profile profile) {
250
          if (!profile.time_zones().empty()) {
251
            primary_tz = profile.time_zones()[0].id;
252
          }
253
          loop.Quit();
254
        });
255
    loop.Run(zx::deadline_after(kTimeout));
256
  }();
257
258
  if (!primary_tz.empty()) {
259
    zone = primary_tz.c_str();
260
  }
261
#endif
262
#if defined(USE_WIN32_LOCAL_TIME_ZONE)
263
  std::string win32_tz = win32_local_time_zone();
264
  if (!win32_tz.empty()) {
265
    zone = win32_tz.c_str();
266
  }
267
#endif
268
269
  // Allow ${TZ} to override to default zone.
270
0
  char* tz_env = nullptr;
271
#if defined(_MSC_VER)
272
  _dupenv_s(&tz_env, nullptr, "TZ");
273
#else
274
0
  tz_env = std::getenv("TZ");
275
0
#endif
276
0
  if (tz_env) zone = tz_env;
277
278
  // We only support the "[:]<zone-name>" form.
279
0
  if (*zone == ':') ++zone;
280
281
  // Map "localtime" to a system-specific name, but
282
  // allow ${LOCALTIME} to override the default name.
283
0
  char* localtime_env = nullptr;
284
0
  if (strcmp(zone, "localtime") == 0) {
285
#if defined(_MSC_VER)
286
    // System-specific default is just "localtime".
287
    _dupenv_s(&localtime_env, nullptr, "LOCALTIME");
288
#else
289
0
    zone = "/etc/localtime";  // System-specific default.
290
0
    localtime_env = std::getenv("LOCALTIME");
291
0
#endif
292
0
    if (localtime_env) zone = localtime_env;
293
0
  }
294
295
0
  const std::string name = zone;
296
#if defined(_MSC_VER)
297
  free(localtime_env);
298
  free(tz_env);
299
#endif
300
301
0
  time_zone tz;
302
0
  load_time_zone(name, &tz);  // Falls back to UTC.
303
  // TODO: Follow the RFC3339 "Unknown Local Offset Convention" and
304
  // arrange for %z to generate "-0000" when we don't know the local
305
  // offset because the load_time_zone() failed and we're using UTC.
306
0
  return tz;
307
0
}
308
309
}  // namespace cctz