1 // Copyright 2013 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 #include "chromeos/settings/timezone_settings.h"
10 #include "base/files/file_path.h"
11 #include "base/files/file_util.h"
12 #include "base/location.h"
13 #include "base/logging.h"
14 #include "base/memory/scoped_ptr.h"
15 #include "base/memory/singleton.h"
16 #include "base/observer_list.h"
17 #include "base/stl_util.h"
18 #include "base/strings/string_util.h"
19 #include "base/strings/utf_string_conversions.h"
20 #include "base/sys_info.h"
21 #include "base/task_runner.h"
22 #include "base/threading/worker_pool.h"
23 #include "third_party/icu/source/i18n/unicode/timezone.h"
27 // The filepath to the timezone file that symlinks to the actual timezone file.
28 const char kTimezoneSymlink
[] = "/var/lib/timezone/localtime";
29 const char kTimezoneSymlink2
[] = "/var/lib/timezone/localtime2";
31 // The directory that contains all the timezone files. So for timezone
32 // "US/Pacific", the actual timezone file is: "/usr/share/zoneinfo/US/Pacific"
33 const char kTimezoneFilesDir
[] = "/usr/share/zoneinfo/";
35 // Fallback time zone ID used in case of an unexpected error.
36 const char kFallbackTimeZoneId
[] = "America/Los_Angeles";
38 // TODO(jungshik): Using Enumerate method in ICU gives 600+ timezones.
39 // Even after filtering out duplicate entries with a strict identity check,
40 // we still have 400+ zones. Relaxing the criteria for the timezone
41 // identity is likely to cut down the number to < 100. Until we
42 // come up with a better list, we hard-code the following list. It came from
43 // from Android initially, but more entries have been added.
44 static const char* kTimeZones
[] = {
48 "America/Los_Angeles",
59 "America/Mexico_City",
73 "America/Argentina/Buenos_Aires",
74 "America/Argentina/San_Luis",
78 "Atlantic/South_Georgia",
79 "Atlantic/Cape_Verde",
112 "Africa/Brazzaville",
116 "Africa/Johannesburg",
117 "Europe/Kaliningrad",
148 "Asia/Yekaterinburg",
182 "Australia/Adelaide",
184 "Australia/Brisbane",
188 "Pacific/Port_Moresby",
195 "Pacific/Kiritimati",
198 std::string
GetTimezoneIDAsString() {
199 // Compare with chromiumos/src/platform/init/ui.conf which fixes certain
200 // incorrect states of the timezone symlink on startup. Thus errors occuring
201 // here should be rather contrived.
203 // Look at kTimezoneSymlink, see which timezone we are symlinked to.
205 const ssize_t len
= readlink(kTimezoneSymlink
, buf
,
208 LOG(ERROR
) << "GetTimezoneID: Cannot read timezone symlink "
210 return std::string();
213 std::string
timezone(buf
, len
);
214 // Remove kTimezoneFilesDir from the beginning.
215 if (timezone
.find(kTimezoneFilesDir
) != 0) {
216 LOG(ERROR
) << "GetTimezoneID: Timezone symlink is wrong "
218 return std::string();
221 return timezone
.substr(strlen(kTimezoneFilesDir
));
224 void SetTimezoneIDFromString(const std::string
& id
) {
225 // Change the kTimezoneSymlink symlink to the path for this timezone.
226 // We want to do this in an atomic way. So we are going to create the symlink
227 // at kTimezoneSymlink2 and then move it to kTimezoneSymlink
229 base::FilePath
timezone_symlink(kTimezoneSymlink
);
230 base::FilePath
timezone_symlink2(kTimezoneSymlink2
);
231 base::FilePath
timezone_file(kTimezoneFilesDir
+ id
);
233 // Make sure timezone_file exists.
234 if (!base::PathExists(timezone_file
)) {
235 LOG(ERROR
) << "SetTimezoneID: Cannot find timezone file "
236 << timezone_file
.value();
240 // Delete old symlink2 if it exists.
241 base::DeleteFile(timezone_symlink2
, false);
243 // Create new symlink2.
244 if (symlink(timezone_file
.value().c_str(),
245 timezone_symlink2
.value().c_str()) == -1) {
246 LOG(ERROR
) << "SetTimezoneID: Unable to create symlink "
247 << timezone_symlink2
.value() << " to " << timezone_file
.value();
251 // Move symlink2 to symlink.
252 if (!base::ReplaceFile(timezone_symlink2
, timezone_symlink
, NULL
)) {
253 LOG(ERROR
) << "SetTimezoneID: Unable to move symlink "
254 << timezone_symlink2
.value() << " to "
255 << timezone_symlink
.value();
259 // Common code of the TimezoneSettings implementations.
260 class TimezoneSettingsBaseImpl
: public chromeos::system::TimezoneSettings
{
262 virtual ~TimezoneSettingsBaseImpl();
264 // TimezoneSettings implementation:
265 virtual const icu::TimeZone
& GetTimezone() override
;
266 virtual base::string16
GetCurrentTimezoneID() override
;
267 virtual void SetTimezoneFromID(const base::string16
& timezone_id
) override
;
268 virtual void AddObserver(Observer
* observer
) override
;
269 virtual void RemoveObserver(Observer
* observer
) override
;
270 virtual const std::vector
<icu::TimeZone
*>& GetTimezoneList() const override
;
273 TimezoneSettingsBaseImpl();
275 // Returns |timezone| if it is an element of |timezones_|.
276 // Otherwise, returns a timezone from |timezones_|, if such exists, that has
277 // the same rule as the given |timezone|.
278 // Otherwise, returns NULL.
279 // Note multiple timezones with the same time zone offset may exist
281 // US/Pacific == America/Los_Angeles
282 const icu::TimeZone
* GetKnownTimezoneOrNull(
283 const icu::TimeZone
& timezone
) const;
285 ObserverList
<Observer
> observers_
;
286 std::vector
<icu::TimeZone
*> timezones_
;
287 scoped_ptr
<icu::TimeZone
> timezone_
;
290 DISALLOW_COPY_AND_ASSIGN(TimezoneSettingsBaseImpl
);
293 // The TimezoneSettings implementation used in production.
294 class TimezoneSettingsImpl
: public TimezoneSettingsBaseImpl
{
296 // TimezoneSettings implementation:
297 virtual void SetTimezone(const icu::TimeZone
& timezone
) override
;
299 static TimezoneSettingsImpl
* GetInstance();
302 friend struct DefaultSingletonTraits
<TimezoneSettingsImpl
>;
304 TimezoneSettingsImpl();
306 DISALLOW_COPY_AND_ASSIGN(TimezoneSettingsImpl
);
309 // The stub TimezoneSettings implementation used on Linux desktop.
310 class TimezoneSettingsStubImpl
: public TimezoneSettingsBaseImpl
{
312 // TimezoneSettings implementation:
313 virtual void SetTimezone(const icu::TimeZone
& timezone
) override
;
315 static TimezoneSettingsStubImpl
* GetInstance();
318 friend struct DefaultSingletonTraits
<TimezoneSettingsStubImpl
>;
320 TimezoneSettingsStubImpl();
322 DISALLOW_COPY_AND_ASSIGN(TimezoneSettingsStubImpl
);
325 TimezoneSettingsBaseImpl::~TimezoneSettingsBaseImpl() {
326 STLDeleteElements(&timezones_
);
329 const icu::TimeZone
& TimezoneSettingsBaseImpl::GetTimezone() {
330 return *timezone_
.get();
333 base::string16
TimezoneSettingsBaseImpl::GetCurrentTimezoneID() {
334 return chromeos::system::TimezoneSettings::GetTimezoneID(GetTimezone());
337 void TimezoneSettingsBaseImpl::SetTimezoneFromID(
338 const base::string16
& timezone_id
) {
339 scoped_ptr
<icu::TimeZone
> timezone(icu::TimeZone::createTimeZone(
340 icu::UnicodeString(timezone_id
.c_str(), timezone_id
.size())));
341 SetTimezone(*timezone
);
344 void TimezoneSettingsBaseImpl::AddObserver(Observer
* observer
) {
345 observers_
.AddObserver(observer
);
348 void TimezoneSettingsBaseImpl::RemoveObserver(Observer
* observer
) {
349 observers_
.RemoveObserver(observer
);
352 const std::vector
<icu::TimeZone
*>&
353 TimezoneSettingsBaseImpl::GetTimezoneList() const {
357 TimezoneSettingsBaseImpl::TimezoneSettingsBaseImpl() {
358 for (size_t i
= 0; i
< arraysize(kTimeZones
); ++i
) {
359 timezones_
.push_back(icu::TimeZone::createTimeZone(
360 icu::UnicodeString(kTimeZones
[i
], -1, US_INV
)));
364 const icu::TimeZone
* TimezoneSettingsBaseImpl::GetKnownTimezoneOrNull(
365 const icu::TimeZone
& timezone
) const {
366 const icu::TimeZone
* known_timezone
= NULL
;
367 for (std::vector
<icu::TimeZone
*>::const_iterator iter
= timezones_
.begin();
368 iter
!= timezones_
.end(); ++iter
) {
369 const icu::TimeZone
* entry
= *iter
;
370 if (*entry
== timezone
)
372 if (entry
->hasSameRules(timezone
))
373 known_timezone
= entry
;
376 // May return NULL if we did not find a matching timezone in our list.
377 return known_timezone
;
380 void TimezoneSettingsImpl::SetTimezone(const icu::TimeZone
& timezone
) {
381 // Replace |timezone| by a known timezone with the same rules. If none exists
382 // go on with |timezone|.
383 const icu::TimeZone
* known_timezone
= GetKnownTimezoneOrNull(timezone
);
385 known_timezone
= &timezone
;
387 timezone_
.reset(known_timezone
->clone());
388 std::string id
= base::UTF16ToUTF8(GetTimezoneID(*known_timezone
));
389 VLOG(1) << "Setting timezone to " << id
;
390 // It's safe to change the timezone config files in the background as the
391 // following operations don't depend on the completion of the config change.
392 base::WorkerPool::GetTaskRunner(true /* task is slow */)->
393 PostTask(FROM_HERE
, base::Bind(&SetTimezoneIDFromString
, id
));
394 icu::TimeZone::setDefault(*known_timezone
);
395 FOR_EACH_OBSERVER(Observer
, observers_
, TimezoneChanged(*known_timezone
));
399 TimezoneSettingsImpl
* TimezoneSettingsImpl::GetInstance() {
400 return Singleton
<TimezoneSettingsImpl
,
401 DefaultSingletonTraits
<TimezoneSettingsImpl
> >::get();
404 TimezoneSettingsImpl::TimezoneSettingsImpl() {
405 std::string id
= GetTimezoneIDAsString();
407 id
= kFallbackTimeZoneId
;
408 LOG(ERROR
) << "Got an empty string for timezone, default to '" << id
;
411 timezone_
.reset(icu::TimeZone::createTimeZone(
412 icu::UnicodeString::fromUTF8(id
)));
414 // Store a known timezone equivalent to id in |timezone_|.
415 const icu::TimeZone
* known_timezone
= GetKnownTimezoneOrNull(*timezone_
);
416 if (known_timezone
!= NULL
&& *known_timezone
!= *timezone_
)
417 // Not necessary to update the filesystem because |known_timezone| has the
419 timezone_
.reset(known_timezone
->clone());
421 icu::TimeZone::setDefault(*timezone_
);
422 VLOG(1) << "Timezone initially set to " << id
;
425 void TimezoneSettingsStubImpl::SetTimezone(const icu::TimeZone
& timezone
) {
426 // Replace |timezone| by a known timezone with the same rules. If none exists
427 // go on with |timezone|.
428 const icu::TimeZone
* known_timezone
= GetKnownTimezoneOrNull(timezone
);
430 known_timezone
= &timezone
;
432 timezone_
.reset(known_timezone
->clone());
433 icu::TimeZone::setDefault(*known_timezone
);
434 FOR_EACH_OBSERVER(Observer
, observers_
, TimezoneChanged(*known_timezone
));
438 TimezoneSettingsStubImpl
* TimezoneSettingsStubImpl::GetInstance() {
439 return Singleton
<TimezoneSettingsStubImpl
,
440 DefaultSingletonTraits
<TimezoneSettingsStubImpl
> >::get();
443 TimezoneSettingsStubImpl::TimezoneSettingsStubImpl() {
444 timezone_
.reset(icu::TimeZone::createDefault());
445 const icu::TimeZone
* known_timezone
= GetKnownTimezoneOrNull(*timezone_
);
446 if (known_timezone
!= NULL
&& *known_timezone
!= *timezone_
)
447 timezone_
.reset(known_timezone
->clone());
455 TimezoneSettings::Observer::~Observer() {}
458 TimezoneSettings
* TimezoneSettings::GetInstance() {
459 if (base::SysInfo::IsRunningOnChromeOS()) {
460 return TimezoneSettingsImpl::GetInstance();
462 return TimezoneSettingsStubImpl::GetInstance();
467 base::string16
TimezoneSettings::GetTimezoneID(const icu::TimeZone
& timezone
) {
468 icu::UnicodeString id
;
470 return base::string16(id
.getBuffer(), id
.length());
473 } // namespace system
474 } // namespace chromeos