2 +----------------------------------------------------------------------+
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"
34 IMPLEMENT_RESOURCE_ALLOCATION(TimeZone
)
35 ///////////////////////////////////////////////////////////////////////////////
37 struct GuessedTimeZone
{
41 time_t the_time
= time(0);
43 struct tm
*ta
= localtime_r(&the_time
, &tmbuf
);
44 const char *tzid
= nullptr;
46 // TODO: Fixme under MSVC!
48 tzid
= timelib_timezone_id_from_abbr(ta
->tm_zone
, ta
->tm_gmtoff
,
58 static GuessedTimeZone s_guessed_timezone
;
59 static Mutex s_tzdb_mutex
;
60 static std::atomic
<const timelib_tzdb
*> s_tzdb_cache
{ nullptr };
62 ///////////////////////////////////////////////////////////////////////////////
66 TimeZoneData() : Database(nullptr) {}
68 const timelib_tzdb
*Database
;
70 static THREAD_LOCAL(TimeZoneData
, s_timezone_data
);
73 bool operator()(const char* a
, const char* b
) {
74 return intptr_t(a
) > 0 && (strcmp(a
, b
) == 0);
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");
112 const timelib_tzdb
*TimeZone::GetDatabase() {
113 const timelib_tzdb
*&Database
= s_timezone_data
->Database
;
114 if (Database
== nullptr) {
115 Database
= timezone_get_tzdb();
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()) {
127 auto tzi
= timelib_parse_tzfile(name
, db
);
129 char* tzid
= timelib_timezone_id_from_abbr(name
, -1, 0);
131 tzi
= timelib_parse_tzfile(tzid
, db
);
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.
143 timelib_tzinfo_dtor(tzi
);
144 tzi
= result
.first
->second
;
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();
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
) {
177 auto const it
= s_tzvCache
->find(name
);
178 if (it
!= s_tzvCache
->end()) {
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
193 raise_notice("Timezone ID '%s' is invalid", name
);
196 RID().setTimeZone(name
);
203 s_timezone_id("timezone_id"),
208 s_country_code("country_code"),
209 s_latitude("latitude"),
210 s_longitude("longitude"),
211 s_comments("comments");
213 Array
TimeZone::GetAbbreviations() {
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
));
223 element
.set(s_timezone_id
, uninit_null());
225 auto const lval
= ret
.lvalAt(String(entry
->name
));
226 forceToArray(lval
).append(element
.toArray());
231 String
TimeZone::AbbreviationToName(String abbr
, int utcoffset
/* = -1 */,
232 int isdst
/* = 1 */) {
233 if (isdst
!= 0 && isdst
!= 1) {
236 return String(timelib_timezone_id_from_abbr(abbr
.data(), utcoffset
,
241 ///////////////////////////////////////////////////////////////////////////////
244 TimeZone::TimeZone() {
248 TimeZone::TimeZone(const String
& name
) {
249 m_tzi
= GetTimeZoneInfoRaw((char*)name
.data(), GetDatabase());
252 TimeZone::TimeZone(timelib_tzinfo
*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
);
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
);
290 Array
TimeZone::transitions(int64_t timestamp_begin
, /* = k_PHP_INT_MIN */
291 int64_t timestamp_end
/* = k_PHP_INT_MAX */) const {
295 timecnt
= m_tzi
->bit32
.timecnt
;
296 uint32_t lastBefore
= 0;
298 i
< timecnt
&& m_tzi
->trans
&& m_tzi
->trans
[i
] <= timestamp_begin
;
302 // If explicitly provided a timestamp to the ret array
303 // and always make sure there is at least one returned value
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
;
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(
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
)
344 Array
TimeZone::getLocation() const {
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
));
356 ///////////////////////////////////////////////////////////////////////////////