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/file_util.h"
11 #include "base/files/file_path.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",
108 "Africa/Brazzaville",
112 "Africa/Johannesburg",
113 "Europe/Kaliningrad",
144 "Asia/Yekaterinburg",
178 "Australia/Adelaide",
180 "Australia/Brisbane",
184 "Pacific/Port_Moresby",
191 "Pacific/Kiritimati",
194 std::string GetTimezoneIDAsString() {
195 // Compare with chromiumos/src/platform/init/ui.conf which fixes certain
196 // incorrect states of the timezone symlink on startup. Thus errors occuring
197 // here should be rather contrived.
199 // Look at kTimezoneSymlink, see which timezone we are symlinked to.
201 const ssize_t len = readlink(kTimezoneSymlink, buf,
204 LOG(ERROR) << "GetTimezoneID: Cannot read timezone symlink "
206 return std::string();
209 std::string timezone(buf, len);
210 // Remove kTimezoneFilesDir from the beginning.
211 if (timezone.find(kTimezoneFilesDir) != 0) {
212 LOG(ERROR) << "GetTimezoneID: Timezone symlink is wrong "
214 return std::string();
217 return timezone.substr(strlen(kTimezoneFilesDir));
220 void SetTimezoneIDFromString(const std::string& id) {
221 // Change the kTimezoneSymlink symlink to the path for this timezone.
222 // We want to do this in an atomic way. So we are going to create the symlink
223 // at kTimezoneSymlink2 and then move it to kTimezoneSymlink
225 base::FilePath timezone_symlink(kTimezoneSymlink);
226 base::FilePath timezone_symlink2(kTimezoneSymlink2);
227 base::FilePath timezone_file(kTimezoneFilesDir + id);
229 // Make sure timezone_file exists.
230 if (!base::PathExists(timezone_file)) {
231 LOG(ERROR) << "SetTimezoneID: Cannot find timezone file "
232 << timezone_file.value();
236 // Delete old symlink2 if it exists.
237 base::DeleteFile(timezone_symlink2, false);
239 // Create new symlink2.
240 if (symlink(timezone_file.value().c_str(),
241 timezone_symlink2.value().c_str()) == -1) {
242 LOG(ERROR) << "SetTimezoneID: Unable to create symlink "
243 << timezone_symlink2.value() << " to " << timezone_file.value();
247 // Move symlink2 to symlink.
248 if (!base::ReplaceFile(timezone_symlink2, timezone_symlink, NULL)) {
249 LOG(ERROR) << "SetTimezoneID: Unable to move symlink "
250 << timezone_symlink2.value() << " to "
251 << timezone_symlink.value();
255 // Common code of the TimezoneSettings implementations.
256 class TimezoneSettingsBaseImpl : public chromeos::system::TimezoneSettings {
258 virtual ~TimezoneSettingsBaseImpl();
260 // TimezoneSettings implementation:
261 virtual const icu::TimeZone& GetTimezone() OVERRIDE;
262 virtual base::string16 GetCurrentTimezoneID() OVERRIDE;
263 virtual void SetTimezoneFromID(const base::string16& timezone_id) OVERRIDE;
264 virtual void AddObserver(Observer* observer) OVERRIDE;
265 virtual void RemoveObserver(Observer* observer) OVERRIDE;
266 virtual const std::vector<icu::TimeZone*>& GetTimezoneList() const OVERRIDE;
269 TimezoneSettingsBaseImpl();
271 // Returns |timezone| if it is an element of |timezones_|.
272 // Otherwise, returns a timezone from |timezones_|, if such exists, that has
273 // the same rule as the given |timezone|.
274 // Otherwise, returns NULL.
275 // Note multiple timezones with the same time zone offset may exist
277 // US/Pacific == America/Los_Angeles
278 const icu::TimeZone* GetKnownTimezoneOrNull(
279 const icu::TimeZone& timezone) const;
281 ObserverList<Observer> observers_;
282 std::vector<icu::TimeZone*> timezones_;
283 scoped_ptr<icu::TimeZone> timezone_;
286 DISALLOW_COPY_AND_ASSIGN(TimezoneSettingsBaseImpl);
289 // The TimezoneSettings implementation used in production.
290 class TimezoneSettingsImpl : public TimezoneSettingsBaseImpl {
292 // TimezoneSettings implementation:
293 virtual void SetTimezone(const icu::TimeZone& timezone) OVERRIDE;
295 static TimezoneSettingsImpl* GetInstance();
298 friend struct DefaultSingletonTraits<TimezoneSettingsImpl>;
300 TimezoneSettingsImpl();
302 DISALLOW_COPY_AND_ASSIGN(TimezoneSettingsImpl);
305 // The stub TimezoneSettings implementation used on Linux desktop.
306 class TimezoneSettingsStubImpl : public TimezoneSettingsBaseImpl {
308 // TimezoneSettings implementation:
309 virtual void SetTimezone(const icu::TimeZone& timezone) OVERRIDE;
311 static TimezoneSettingsStubImpl* GetInstance();
314 friend struct DefaultSingletonTraits<TimezoneSettingsStubImpl>;
316 TimezoneSettingsStubImpl();
318 DISALLOW_COPY_AND_ASSIGN(TimezoneSettingsStubImpl);
321 TimezoneSettingsBaseImpl::~TimezoneSettingsBaseImpl() {
322 STLDeleteElements(&timezones_);
325 const icu::TimeZone& TimezoneSettingsBaseImpl::GetTimezone() {
326 return *timezone_.get();
329 base::string16 TimezoneSettingsBaseImpl::GetCurrentTimezoneID() {
330 return chromeos::system::TimezoneSettings::GetTimezoneID(GetTimezone());
333 void TimezoneSettingsBaseImpl::SetTimezoneFromID(
334 const base::string16& timezone_id) {
335 scoped_ptr<icu::TimeZone> timezone(icu::TimeZone::createTimeZone(
336 icu::UnicodeString(timezone_id.c_str(), timezone_id.size())));
337 SetTimezone(*timezone);
340 void TimezoneSettingsBaseImpl::AddObserver(Observer* observer) {
341 observers_.AddObserver(observer);
344 void TimezoneSettingsBaseImpl::RemoveObserver(Observer* observer) {
345 observers_.RemoveObserver(observer);
348 const std::vector<icu::TimeZone*>&
349 TimezoneSettingsBaseImpl::GetTimezoneList() const {
353 TimezoneSettingsBaseImpl::TimezoneSettingsBaseImpl() {
354 for (size_t i = 0; i < arraysize(kTimeZones); ++i) {
355 timezones_.push_back(icu::TimeZone::createTimeZone(
356 icu::UnicodeString(kTimeZones[i], -1, US_INV)));
360 const icu::TimeZone* TimezoneSettingsBaseImpl::GetKnownTimezoneOrNull(
361 const icu::TimeZone& timezone) const {
362 const icu::TimeZone* known_timezone = NULL;
363 for (std::vector<icu::TimeZone*>::const_iterator iter = timezones_.begin();
364 iter != timezones_.end(); ++iter) {
365 const icu::TimeZone* entry = *iter;
366 if (*entry == timezone)
368 if (entry->hasSameRules(timezone))
369 known_timezone = entry;
372 // May return NULL if we did not find a matching timezone in our list.
373 return known_timezone;
376 void TimezoneSettingsImpl::SetTimezone(const icu::TimeZone& timezone) {
377 // Replace |timezone| by a known timezone with the same rules. If none exists
378 // go on with |timezone|.
379 const icu::TimeZone* known_timezone = GetKnownTimezoneOrNull(timezone);
381 known_timezone = &timezone;
383 timezone_.reset(known_timezone->clone());
384 std::string id = base::UTF16ToUTF8(GetTimezoneID(*known_timezone));
385 VLOG(1) << "Setting timezone to " << id;
386 // It's safe to change the timezone config files in the background as the
387 // following operations don't depend on the completion of the config change.
388 base::WorkerPool::GetTaskRunner(true /* task is slow */)->
389 PostTask(FROM_HERE, base::Bind(&SetTimezoneIDFromString, id));
390 icu::TimeZone::setDefault(*known_timezone);
391 FOR_EACH_OBSERVER(Observer, observers_, TimezoneChanged(*known_timezone));
395 TimezoneSettingsImpl* TimezoneSettingsImpl::GetInstance() {
396 return Singleton<TimezoneSettingsImpl,
397 DefaultSingletonTraits<TimezoneSettingsImpl> >::get();
400 TimezoneSettingsImpl::TimezoneSettingsImpl() {
401 std::string id = GetTimezoneIDAsString();
403 id = kFallbackTimeZoneId;
404 LOG(ERROR) << "Got an empty string for timezone, default to '" << id;
407 timezone_.reset(icu::TimeZone::createTimeZone(
408 icu::UnicodeString::fromUTF8(id)));
410 // Store a known timezone equivalent to id in |timezone_|.
411 const icu::TimeZone* known_timezone = GetKnownTimezoneOrNull(*timezone_);
412 if (known_timezone != NULL && *known_timezone != *timezone_)
413 // Not necessary to update the filesystem because |known_timezone| has the
415 timezone_.reset(known_timezone->clone());
417 icu::TimeZone::setDefault(*timezone_);
418 VLOG(1) << "Timezone initially set to " << id;
421 void TimezoneSettingsStubImpl::SetTimezone(const icu::TimeZone& timezone) {
422 // Replace |timezone| by a known timezone with the same rules. If none exists
423 // go on with |timezone|.
424 const icu::TimeZone* known_timezone = GetKnownTimezoneOrNull(timezone);
426 known_timezone = &timezone;
428 timezone_.reset(known_timezone->clone());
429 icu::TimeZone::setDefault(*known_timezone);
430 FOR_EACH_OBSERVER(Observer, observers_, TimezoneChanged(*known_timezone));
434 TimezoneSettingsStubImpl* TimezoneSettingsStubImpl::GetInstance() {
435 return Singleton<TimezoneSettingsStubImpl,
436 DefaultSingletonTraits<TimezoneSettingsStubImpl> >::get();
439 TimezoneSettingsStubImpl::TimezoneSettingsStubImpl() {
440 timezone_.reset(icu::TimeZone::createDefault());
441 const icu::TimeZone* known_timezone = GetKnownTimezoneOrNull(*timezone_);
442 if (known_timezone != NULL && *known_timezone != *timezone_)
443 timezone_.reset(known_timezone->clone());
451 TimezoneSettings::Observer::~Observer() {}
454 TimezoneSettings* TimezoneSettings::GetInstance() {
455 if (base::SysInfo::IsRunningOnChromeOS()) {
456 return TimezoneSettingsImpl::GetInstance();
458 return TimezoneSettingsStubImpl::GetInstance();
463 base::string16 TimezoneSettings::GetTimezoneID(const icu::TimeZone& timezone) {
464 icu::UnicodeString id;
466 return base::string16(id.getBuffer(), id.length());
469 } // namespace system
470 } // namespace chromeos