Home | History | Annotate | Download | only in settings
      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.
      4 
      5 #include "chromeos/settings/timezone_settings.h"
      6 
      7 #include <string>
      8 
      9 #include "base/bind.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"
     24 
     25 namespace {
     26 
     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";
     30 
     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/";
     34 
     35 // Fallback time zone ID used in case of an unexpected error.
     36 const char kFallbackTimeZoneId[] = "America/Los_Angeles";
     37 
     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[] = {
     45     "Pacific/Midway",
     46     "Pacific/Honolulu",
     47     "America/Anchorage",
     48     "America/Los_Angeles",
     49     "America/Vancouver",
     50     "America/Tijuana",
     51     "America/Phoenix",
     52     "America/Chihuahua",
     53     "America/Denver",
     54     "America/Edmonton",
     55     "America/Mazatlan",
     56     "America/Regina",
     57     "America/Costa_Rica",
     58     "America/Chicago",
     59     "America/Mexico_City",
     60     "America/Winnipeg",
     61     "Pacific/Easter",
     62     "America/Bogota",
     63     "America/Lima",
     64     "America/New_York",
     65     "America/Toronto",
     66     "America/Caracas",
     67     "America/Barbados",
     68     "America/Halifax",
     69     "America/Manaus",
     70     "America/Santiago",
     71     "America/St_Johns",
     72     "America/Araguaina",
     73     "America/Argentina/Buenos_Aires",
     74     "America/Argentina/San_Luis",
     75     "America/Sao_Paulo",
     76     "America/Montevideo",
     77     "America/Godthab",
     78     "Atlantic/South_Georgia",
     79     "Atlantic/Cape_Verde",
     80     "Atlantic/Azores",
     81     "Atlantic/Reykjavik",
     82     "Atlantic/St_Helena",
     83     "Africa/Casablanca",
     84     "Atlantic/Faroe",
     85     "Europe/Dublin",
     86     "Europe/Lisbon",
     87     "Europe/London",
     88     "Europe/Amsterdam",
     89     "Europe/Belgrade",
     90     "Europe/Berlin",
     91     "Europe/Brussels",
     92     "Europe/Budapest",
     93     "Europe/Copenhagen",
     94     "Europe/Ljubljana",
     95     "Europe/Madrid",
     96     "Europe/Oslo",
     97     "Europe/Paris",
     98     "Europe/Prague",
     99     "Europe/Rome",
    100     "Europe/Stockholm",
    101     "Europe/Sarajevo",
    102     "Europe/Tirane",
    103     "Europe/Vienna",
    104     "Europe/Warsaw",
    105     "Europe/Zurich",
    106     "Africa/Windhoek",
    107     "Africa/Lagos",
    108     "Africa/Brazzaville",
    109     "Africa/Cairo",
    110     "Africa/Harare",
    111     "Africa/Maputo",
    112     "Africa/Johannesburg",
    113     "Europe/Kaliningrad",
    114     "Europe/Athens",
    115     "Europe/Bucharest",
    116     "Europe/Chisinau",
    117     "Europe/Helsinki",
    118     "Europe/Istanbul",
    119     "Europe/Kiev",
    120     "Europe/Riga",
    121     "Europe/Sofia",
    122     "Europe/Tallinn",
    123     "Europe/Vilnius",
    124     "Asia/Amman",
    125     "Asia/Beirut",
    126     "Asia/Jerusalem",
    127     "Africa/Nairobi",
    128     "Asia/Baghdad",
    129     "Asia/Riyadh",
    130     "Asia/Kuwait",
    131     "Europe/Minsk",
    132     "Europe/Moscow",
    133     "Asia/Tehran",
    134     "Europe/Samara",
    135     "Asia/Dubai",
    136     "Asia/Tbilisi",
    137     "Indian/Mauritius",
    138     "Asia/Baku",
    139     "Asia/Yerevan",
    140     "Asia/Kabul",
    141     "Asia/Karachi",
    142     "Asia/Ashgabat",
    143     "Asia/Oral",
    144     "Asia/Yekaterinburg",
    145     "Asia/Calcutta",
    146     "Asia/Colombo",
    147     "Asia/Katmandu",
    148     "Asia/Omsk",
    149     "Asia/Almaty",
    150     "Asia/Dhaka",
    151     "Asia/Novosibirsk",
    152     "Asia/Rangoon",
    153     "Asia/Bangkok",
    154     "Asia/Jakarta",
    155     "Asia/Krasnoyarsk",
    156     "Asia/Novokuznetsk",
    157     "Asia/Ho_Chi_Minh",
    158     "Asia/Phnom_Penh",
    159     "Asia/Vientiane",
    160     "Asia/Shanghai",
    161     "Asia/Hong_Kong",
    162     "Asia/Kuala_Lumpur",
    163     "Asia/Singapore",
    164     "Asia/Manila",
    165     "Asia/Taipei",
    166     "Asia/Makassar",
    167     "Asia/Irkutsk",
    168     "Asia/Yakutsk",
    169     "Australia/Perth",
    170     "Australia/Eucla",
    171     "Asia/Seoul",
    172     "Asia/Tokyo",
    173     "Asia/Jayapura",
    174     "Asia/Sakhalin",
    175     "Asia/Vladivostok",
    176     "Asia/Magadan",
    177     "Australia/Darwin",
    178     "Australia/Adelaide",
    179     "Pacific/Guam",
    180     "Australia/Brisbane",
    181     "Australia/Hobart",
    182     "Australia/Sydney",
    183     "Asia/Anadyr",
    184     "Pacific/Port_Moresby",
    185     "Asia/Kamchatka",
    186     "Pacific/Fiji",
    187     "Pacific/Majuro",
    188     "Pacific/Auckland",
    189     "Pacific/Tongatapu",
    190     "Pacific/Apia",
    191     "Pacific/Kiritimati",
    192 };
    193 
    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.
    198 
    199   // Look at kTimezoneSymlink, see which timezone we are symlinked to.
    200   char buf[256];
    201   const ssize_t len = readlink(kTimezoneSymlink, buf,
    202                                sizeof(buf)-1);
    203   if (len == -1) {
    204     LOG(ERROR) << "GetTimezoneID: Cannot read timezone symlink "
    205                << kTimezoneSymlink;
    206     return std::string();
    207   }
    208 
    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 "
    213                << timezone;
    214     return std::string();
    215   }
    216 
    217   return timezone.substr(strlen(kTimezoneFilesDir));
    218 }
    219 
    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
    224 
    225   base::FilePath timezone_symlink(kTimezoneSymlink);
    226   base::FilePath timezone_symlink2(kTimezoneSymlink2);
    227   base::FilePath timezone_file(kTimezoneFilesDir + id);
    228 
    229   // Make sure timezone_file exists.
    230   if (!base::PathExists(timezone_file)) {
    231     LOG(ERROR) << "SetTimezoneID: Cannot find timezone file "
    232                << timezone_file.value();
    233     return;
    234   }
    235 
    236   // Delete old symlink2 if it exists.
    237   base::DeleteFile(timezone_symlink2, false);
    238 
    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();
    244     return;
    245   }
    246 
    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();
    252   }
    253 }
    254 
    255 // Common code of the TimezoneSettings implementations.
    256 class TimezoneSettingsBaseImpl : public chromeos::system::TimezoneSettings {
    257  public:
    258   virtual ~TimezoneSettingsBaseImpl();
    259 
    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;
    267 
    268  protected:
    269   TimezoneSettingsBaseImpl();
    270 
    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
    276   // e.g.
    277   //   US/Pacific == America/Los_Angeles
    278   const icu::TimeZone* GetKnownTimezoneOrNull(
    279       const icu::TimeZone& timezone) const;
    280 
    281   ObserverList<Observer> observers_;
    282   std::vector<icu::TimeZone*> timezones_;
    283   scoped_ptr<icu::TimeZone> timezone_;
    284 
    285  private:
    286   DISALLOW_COPY_AND_ASSIGN(TimezoneSettingsBaseImpl);
    287 };
    288 
    289 // The TimezoneSettings implementation used in production.
    290 class TimezoneSettingsImpl : public TimezoneSettingsBaseImpl {
    291  public:
    292   // TimezoneSettings implementation:
    293   virtual void SetTimezone(const icu::TimeZone& timezone) OVERRIDE;
    294 
    295   static TimezoneSettingsImpl* GetInstance();
    296 
    297  private:
    298   friend struct DefaultSingletonTraits<TimezoneSettingsImpl>;
    299 
    300   TimezoneSettingsImpl();
    301 
    302   DISALLOW_COPY_AND_ASSIGN(TimezoneSettingsImpl);
    303 };
    304 
    305 // The stub TimezoneSettings implementation used on Linux desktop.
    306 class TimezoneSettingsStubImpl : public TimezoneSettingsBaseImpl {
    307  public:
    308   // TimezoneSettings implementation:
    309   virtual void SetTimezone(const icu::TimeZone& timezone) OVERRIDE;
    310 
    311   static TimezoneSettingsStubImpl* GetInstance();
    312 
    313  private:
    314   friend struct DefaultSingletonTraits<TimezoneSettingsStubImpl>;
    315 
    316   TimezoneSettingsStubImpl();
    317 
    318   DISALLOW_COPY_AND_ASSIGN(TimezoneSettingsStubImpl);
    319 };
    320 
    321 TimezoneSettingsBaseImpl::~TimezoneSettingsBaseImpl() {
    322   STLDeleteElements(&timezones_);
    323 }
    324 
    325 const icu::TimeZone& TimezoneSettingsBaseImpl::GetTimezone() {
    326   return *timezone_.get();
    327 }
    328 
    329 base::string16 TimezoneSettingsBaseImpl::GetCurrentTimezoneID() {
    330   return chromeos::system::TimezoneSettings::GetTimezoneID(GetTimezone());
    331 }
    332 
    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);
    338 }
    339 
    340 void TimezoneSettingsBaseImpl::AddObserver(Observer* observer) {
    341   observers_.AddObserver(observer);
    342 }
    343 
    344 void TimezoneSettingsBaseImpl::RemoveObserver(Observer* observer) {
    345   observers_.RemoveObserver(observer);
    346 }
    347 
    348 const std::vector<icu::TimeZone*>&
    349 TimezoneSettingsBaseImpl::GetTimezoneList() const {
    350   return timezones_;
    351 }
    352 
    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)));
    357   }
    358 }
    359 
    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)
    367       return entry;
    368     if (entry->hasSameRules(timezone))
    369       known_timezone = entry;
    370   }
    371 
    372   // May return NULL if we did not find a matching timezone in our list.
    373   return known_timezone;
    374 }
    375 
    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);
    380   if (!known_timezone)
    381     known_timezone = &timezone;
    382 
    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));
    392 }
    393 
    394 // static
    395 TimezoneSettingsImpl* TimezoneSettingsImpl::GetInstance() {
    396   return Singleton<TimezoneSettingsImpl,
    397                    DefaultSingletonTraits<TimezoneSettingsImpl> >::get();
    398 }
    399 
    400 TimezoneSettingsImpl::TimezoneSettingsImpl() {
    401   std::string id = GetTimezoneIDAsString();
    402   if (id.empty()) {
    403     id = kFallbackTimeZoneId;
    404     LOG(ERROR) << "Got an empty string for timezone, default to '" << id;
    405   }
    406 
    407   timezone_.reset(icu::TimeZone::createTimeZone(
    408       icu::UnicodeString::fromUTF8(id)));
    409 
    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
    414     // same rules.
    415     timezone_.reset(known_timezone->clone());
    416 
    417   icu::TimeZone::setDefault(*timezone_);
    418   VLOG(1) << "Timezone initially set to " << id;
    419 }
    420 
    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);
    425   if (!known_timezone)
    426     known_timezone = &timezone;
    427 
    428   timezone_.reset(known_timezone->clone());
    429   icu::TimeZone::setDefault(*known_timezone);
    430   FOR_EACH_OBSERVER(Observer, observers_, TimezoneChanged(*known_timezone));
    431 }
    432 
    433 // static
    434 TimezoneSettingsStubImpl* TimezoneSettingsStubImpl::GetInstance() {
    435   return Singleton<TimezoneSettingsStubImpl,
    436       DefaultSingletonTraits<TimezoneSettingsStubImpl> >::get();
    437 }
    438 
    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());
    444 }
    445 
    446 }  // namespace
    447 
    448 namespace chromeos {
    449 namespace system {
    450 
    451 TimezoneSettings::Observer::~Observer() {}
    452 
    453 // static
    454 TimezoneSettings* TimezoneSettings::GetInstance() {
    455   if (base::SysInfo::IsRunningOnChromeOS()) {
    456     return TimezoneSettingsImpl::GetInstance();
    457   } else {
    458     return TimezoneSettingsStubImpl::GetInstance();
    459   }
    460 }
    461 
    462 // static
    463 base::string16 TimezoneSettings::GetTimezoneID(const icu::TimeZone& timezone) {
    464   icu::UnicodeString id;
    465   timezone.getID(id);
    466   return base::string16(id.getBuffer(), id.length());
    467 }
    468 
    469 }  // namespace system
    470 }  // namespace chromeos
    471