Add sub-controls for Hack array compat runtime checks
[hiphop-php.git] / hphp / runtime / base / timezone.cpp
blob25e573e5d41dd6463318f8a83fc9867904bdacae
1 /*
2 +----------------------------------------------------------------------+
3 | HipHop for PHP |
4 +----------------------------------------------------------------------+
5 | Copyright (c) 2010-present Facebook, Inc. (http://www.facebook.com) |
6 +----------------------------------------------------------------------+
7 | This source file is subject to version 3.01 of the PHP license, |
8 | that is bundled with this package in the file LICENSE, and is |
9 | available through the world-wide-web at the following url: |
10 | http://www.php.net/license/3_01.txt |
11 | If you did not receive a copy of the PHP license and are unable to |
12 | obtain it through the world-wide-web, please send a note to |
13 | license@php.net so we can mail you a copy immediately. |
14 +----------------------------------------------------------------------+
17 #include "hphp/runtime/base/timezone.h"
19 #include <folly/AtomicHashArray.h>
21 #include "hphp/runtime/base/array-init.h"
22 #include "hphp/runtime/base/builtin-functions.h"
23 #include "hphp/runtime/base/datetime.h"
24 #include "hphp/runtime/base/execution-context.h"
25 #include "hphp/runtime/base/runtime-error.h"
27 #include "hphp/util/functional.h"
28 #include "hphp/util/logger.h"
29 #include "hphp/util/lock.h"
30 #include "hphp/util/text-util.h"
32 namespace HPHP {
34 IMPLEMENT_RESOURCE_ALLOCATION(TimeZone)
35 ///////////////////////////////////////////////////////////////////////////////
37 struct GuessedTimeZone {
38 std::string m_tzid;
40 GuessedTimeZone() {
41 time_t the_time = time(0);
42 struct tm tmbuf;
43 struct tm *ta = localtime_r(&the_time, &tmbuf);
44 const char *tzid = nullptr;
45 #ifndef _MSC_VER
46 // TODO: Fixme under MSVC!
47 if (ta) {
48 tzid = timelib_timezone_id_from_abbr(ta->tm_zone, ta->tm_gmtoff,
49 ta->tm_isdst);
51 #endif
52 if (!tzid) {
53 tzid = "UTC";
55 m_tzid = tzid;
58 static GuessedTimeZone s_guessed_timezone;
59 static Mutex s_tzdb_mutex;
60 static std::atomic<const timelib_tzdb*> s_tzdb_cache { nullptr };
62 ///////////////////////////////////////////////////////////////////////////////
63 // statics
65 struct TimeZoneData {
66 TimeZoneData() : Database(nullptr) {}
68 const timelib_tzdb *Database;
70 static THREAD_LOCAL(TimeZoneData, s_timezone_data);
72 struct ahm_eqstr {
73 bool operator()(const char* a, const char* b) {
74 return intptr_t(a) > 0 && (strcmp(a, b) == 0);
78 using TimeZoneCache =
79 folly::AtomicHashArray<const char*, timelib_tzinfo*, cstr_hash, ahm_eqstr>;
80 using TimeZoneCacheEntry = std::pair<const char*, timelib_tzinfo*>;
82 TimeZoneCache* s_tzCache;
84 using TimeZoneValidityCache =
85 folly::AtomicHashArray<const char*, bool, cstr_hash, ahm_eqstr>;
86 using TimeZoneValidityCacheEntry = std::pair<const char*, bool>;
88 TimeZoneValidityCache* s_tzvCache;
90 void timezone_init() {
91 // Allocate enough space to cache all possible timezones, if needed.
92 constexpr size_t kMaxTimeZoneCache = 1000;
93 s_tzCache = TimeZoneCache::create(kMaxTimeZoneCache).release();
94 s_tzvCache = TimeZoneValidityCache::create(kMaxTimeZoneCache).release();
97 const timelib_tzdb* (*timezone_raw_get_tzdb)() = timelib_builtin_db;
99 const timelib_tzdb* timezone_get_tzdb() {
100 if (s_tzdb_cache.load() == nullptr) {
101 Lock tzdbLock(s_tzdb_mutex);
102 if (s_tzdb_cache.load() == nullptr) {
103 s_tzdb_cache = (*timezone_raw_get_tzdb)();
106 if (s_tzdb_cache == nullptr) {
107 raise_error("Couldn't load tzdata");
109 return s_tzdb_cache;
112 const timelib_tzdb *TimeZone::GetDatabase() {
113 const timelib_tzdb *&Database = s_timezone_data->Database;
114 if (Database == nullptr) {
115 Database = timezone_get_tzdb();
117 return Database;
120 timelib_tzinfo* TimeZone::GetTimeZoneInfoRaw(char* name,
121 const timelib_tzdb* db) {
122 auto const it = s_tzCache->find(name);
123 if (it != s_tzCache->end()) {
124 return it->second;
127 auto tzi = timelib_parse_tzfile(name, db);
128 if (!tzi) {
129 char* tzid = timelib_timezone_id_from_abbr(name, -1, 0);
130 if (tzid) {
131 tzi = timelib_parse_tzfile(tzid, db);
135 if (tzi) {
136 auto key = strdup(name);
137 auto result = s_tzCache->insert(TimeZoneCacheEntry(key, tzi));
138 if (!result.second) {
139 // The cache should never fill up since tzinfos are finite.
140 always_assert(result.first != s_tzCache->end());
141 // A collision occurred, so we don't need our strdup'ed key.
142 free(key);
143 timelib_tzinfo_dtor(tzi);
144 tzi = result.first->second;
148 return tzi;
151 bool TimeZone::IsValid(const char* name) {
152 return timelib_timezone_id_is_valid((char*)name, GetDatabase());
155 String TimeZone::CurrentName() {
156 /* Checking configure timezone */
157 auto& tz = RID().getTimeZone();
158 if (!tz.empty()) {
159 return String(tz);
162 /* Check environment variable */
163 char *env = getenv("TZ");
164 if (env && *env && IsValid(env)) {
165 return String(env, CopyString);
168 return String(s_guessed_timezone.m_tzid);
171 req::ptr<TimeZone> TimeZone::Current() {
172 return req::make<TimeZone>(CurrentName());
175 bool TimeZone::SetCurrent(const char* name) {
176 bool valid;
177 auto const it = s_tzvCache->find(name);
178 if (it != s_tzvCache->end()) {
179 valid = it->second;
180 } else {
181 valid = IsValid(name);
183 auto key = strdup(name);
184 auto result = s_tzvCache->insert(TimeZoneValidityCacheEntry(key, valid));
185 if (!result.second) {
186 // The cache is full or a collision occurred, and we don't need our
187 // strdup'ed key.
188 free(key);
192 if (!valid) {
193 raise_notice("Timezone ID '%s' is invalid", name);
194 return false;
196 RID().setTimeZone(name);
197 return true;
200 const StaticString
201 s_dst("dst"),
202 s_offset("offset"),
203 s_timezone_id("timezone_id"),
204 s_ts("ts"),
205 s_time("time"),
206 s_isdst("isdst"),
207 s_abbr("abbr"),
208 s_country_code("country_code"),
209 s_latitude("latitude"),
210 s_longitude("longitude"),
211 s_comments("comments");
213 Array TimeZone::GetAbbreviations() {
214 Array ret;
215 for (const timelib_tz_lookup_table *entry =
216 timelib_timezone_abbreviations_list(); entry->name; entry++) {
217 ArrayInit element(3, ArrayInit::Map{});
218 element.set(s_dst, (bool)entry->type);
219 element.set(s_offset, entry->gmtoffset);
220 if (entry->full_tz_name) {
221 element.set(s_timezone_id, String(entry->full_tz_name, CopyString));
222 } else {
223 element.set(s_timezone_id, uninit_null());
225 auto const lval = ret.lvalAt(String(entry->name));
226 forceToArray(lval).append(element.toArray());
228 return ret;
231 String TimeZone::AbbreviationToName(String abbr, int utcoffset /* = -1 */,
232 int isdst /* = 1 */) {
233 if (isdst != 0 && isdst != 1) {
234 isdst = -1;
236 return String(timelib_timezone_id_from_abbr(abbr.data(), utcoffset,
237 isdst),
238 CopyString);
241 ///////////////////////////////////////////////////////////////////////////////
242 // class TimeZone
244 TimeZone::TimeZone() {
245 m_tzi = nullptr;
248 TimeZone::TimeZone(const String& name) {
249 m_tzi = GetTimeZoneInfoRaw((char*)name.data(), GetDatabase());
252 TimeZone::TimeZone(timelib_tzinfo *tzi) {
253 m_tzi = tzi;
256 req::ptr<TimeZone> TimeZone::cloneTimeZone() const {
257 return req::make<TimeZone>(m_tzi);
260 String TimeZone::name() const {
261 if (!m_tzi) return String();
262 return String(m_tzi->name, CopyString);
265 String TimeZone::abbr(int type /* = 0 */) const {
266 if (!m_tzi) return String();
267 return String(&m_tzi->timezone_abbr[m_tzi->type[type].abbr_idx], CopyString);
270 int TimeZone::offset(int64_t timestamp) const {
271 if (!m_tzi) return 0;
273 timelib_time_offset *offset =
274 timelib_get_time_zone_info(timestamp, m_tzi);
275 int ret = offset->offset;
276 timelib_time_offset_dtor(offset);
277 return ret;
280 bool TimeZone::dst(int64_t timestamp) const {
281 if (!m_tzi) return false;
283 timelib_time_offset *offset =
284 timelib_get_time_zone_info(timestamp, m_tzi);
285 bool ret = offset->is_dst;
286 timelib_time_offset_dtor(offset);
287 return ret;
290 Array TimeZone::transitions(int64_t timestamp_begin, /* = k_PHP_INT_MIN */
291 int64_t timestamp_end /* = k_PHP_INT_MAX */) const {
292 Array ret;
293 if (m_tzi) {
294 uint32_t timecnt;
295 timecnt = m_tzi->bit32.timecnt;
296 uint32_t lastBefore = 0;
297 for (uint32_t i = 0;
298 i < timecnt && m_tzi->trans && m_tzi->trans[i] <= timestamp_begin;
299 ++i) {
300 lastBefore = i;
302 // If explicitly provided a timestamp to the ret array
303 // and always make sure there is at least one returned value
304 if (!m_tzi->trans ||
305 timestamp_begin >= timestamp_end || (
306 (timestamp_begin != k_PHP_INT_MIN || timestamp_end != k_PHP_INT_MAX) &&
307 timestamp_begin != m_tzi->trans[lastBefore])) {
308 auto dt = req::make<DateTime>(
309 timestamp_begin, req::make<TimeZone>("UTC"));
310 int index = m_tzi->trans ? m_tzi->trans_idx[lastBefore] : 0;
311 ttinfo &offset = m_tzi->type[index];
312 const char *abbr = m_tzi->timezone_abbr + offset.abbr_idx;
313 ret.append(make_map_array(
314 s_ts, timestamp_begin,
315 s_time, dt->toString(DateTime::DateFormat::ISO8601),
316 s_offset, offset.offset,
317 s_isdst, (bool)offset.isdst,
318 s_abbr, String(abbr, CopyString)
321 for (uint32_t i = lastBefore;
322 i < timecnt && m_tzi->trans && m_tzi->trans[i] < timestamp_end;
323 ++i) {
324 int timestamp = m_tzi->trans[i];
325 if (timestamp_begin <= timestamp) {
326 int index = m_tzi->trans_idx[i];
327 auto dt = req::make<DateTime>(timestamp, req::make<TimeZone>("UTC"));
328 ttinfo &offset = m_tzi->type[index];
329 const char *abbr = m_tzi->timezone_abbr + offset.abbr_idx;
331 ret.append(make_map_array(
332 s_ts, timestamp,
333 s_time, dt->toString(DateTime::DateFormat::ISO8601),
334 s_offset, offset.offset,
335 s_isdst, (bool)offset.isdst,
336 s_abbr, String(abbr, CopyString)
341 return ret;
344 Array TimeZone::getLocation() const {
345 Array ret;
346 if (!m_tzi) return ret;
348 ret.set(s_country_code, String(m_tzi->location.country_code, CopyString));
349 ret.set(s_latitude, m_tzi->location.latitude);
350 ret.set(s_longitude, m_tzi->location.longitude);
351 ret.set(s_comments, String(m_tzi->location.comments, CopyString));
353 return ret;
356 ///////////////////////////////////////////////////////////////////////////////