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/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"
     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/Athens",
    114     "Europe/Bucharest",
    115     "Europe/Chisinau",
    116     "Europe/Helsinki",
    117     "Europe/Istanbul",
    118     "Europe/Kiev",
    119     "Europe/Riga",
    120     "Europe/Sofia",
    121     "Europe/Tallinn",
    122     "Europe/Vilnius",
    123     "Asia/Amman",
    124     "Asia/Beirut",
    125     "Asia/Jerusalem",
    126     "Africa/Nairobi",
    127     "Asia/Baghdad",
    128     "Asia/Riyadh",
    129     "Asia/Kuwait",
    130     "Europe/Minsk",
    131     "Asia/Tehran",
    132     "Europe/Moscow",
    133     "Asia/Dubai",
    134     "Asia/Tbilisi",
    135     "Indian/Mauritius",
    136     "Asia/Baku",
    137     "Asia/Yerevan",
    138     "Asia/Kabul",
    139     "Asia/Karachi",
    140     "Asia/Ashgabat",
    141     "Asia/Oral",
    142     "Asia/Calcutta",
    143     "Asia/Colombo",
    144     "Asia/Katmandu",
    145     "Asia/Yekaterinburg",
    146     "Asia/Almaty",
    147     "Asia/Dhaka",
    148     "Asia/Rangoon",
    149     "Asia/Bangkok",
    150     "Asia/Jakarta",
    151     "Asia/Omsk",
    152     "Asia/Novosibirsk",
    153     "Asia/Ho_Chi_Minh",
    154     "Asia/Phnom_Penh",
    155     "Asia/Vientiane",
    156     "Asia/Shanghai",
    157     "Asia/Hong_Kong",
    158     "Asia/Kuala_Lumpur",
    159     "Asia/Singapore",
    160     "Asia/Manila",
    161     "Asia/Taipei",
    162     "Asia/Makassar",
    163     "Asia/Krasnoyarsk",
    164     "Australia/Perth",
    165     "Australia/Eucla",
    166     "Asia/Irkutsk",
    167     "Asia/Seoul",
    168     "Asia/Tokyo",
    169     "Asia/Jayapura",
    170     "Australia/Darwin",
    171     "Australia/Adelaide",
    172     "Asia/Yakutsk",
    173     "Pacific/Guam",
    174     "Australia/Brisbane",
    175     "Australia/Hobart",
    176     "Australia/Sydney",
    177     "Pacific/Port_Moresby",
    178     "Asia/Vladivostok",
    179     "Asia/Sakhalin",
    180     "Asia/Magadan",
    181     "Pacific/Fiji",
    182     "Pacific/Majuro",
    183     "Pacific/Auckland",
    184     "Pacific/Tongatapu",
    185     "Pacific/Apia",
    186     "Pacific/Kiritimati",
    187 };
    188 
    189 std::string GetTimezoneIDAsString() {
    190   // Compare with chromiumos/src/platform/init/ui.conf which fixes certain
    191   // incorrect states of the timezone symlink on startup. Thus errors occuring
    192   // here should be rather contrived.
    193 
    194   // Look at kTimezoneSymlink, see which timezone we are symlinked to.
    195   char buf[256];
    196   const ssize_t len = readlink(kTimezoneSymlink, buf,
    197                                sizeof(buf)-1);
    198   if (len == -1) {
    199     LOG(ERROR) << "GetTimezoneID: Cannot read timezone symlink "
    200                << kTimezoneSymlink;
    201     return std::string();
    202   }
    203 
    204   std::string timezone(buf, len);
    205   // Remove kTimezoneFilesDir from the beginning.
    206   if (timezone.find(kTimezoneFilesDir) != 0) {
    207     LOG(ERROR) << "GetTimezoneID: Timezone symlink is wrong "
    208                << timezone;
    209     return std::string();
    210   }
    211 
    212   return timezone.substr(strlen(kTimezoneFilesDir));
    213 }
    214 
    215 void SetTimezoneIDFromString(const std::string& id) {
    216   // Change the kTimezoneSymlink symlink to the path for this timezone.
    217   // We want to do this in an atomic way. So we are going to create the symlink
    218   // at kTimezoneSymlink2 and then move it to kTimezoneSymlink
    219 
    220   base::FilePath timezone_symlink(kTimezoneSymlink);
    221   base::FilePath timezone_symlink2(kTimezoneSymlink2);
    222   base::FilePath timezone_file(kTimezoneFilesDir + id);
    223 
    224   // Make sure timezone_file exists.
    225   if (!base::PathExists(timezone_file)) {
    226     LOG(ERROR) << "SetTimezoneID: Cannot find timezone file "
    227                << timezone_file.value();
    228     return;
    229   }
    230 
    231   // Delete old symlink2 if it exists.
    232   base::DeleteFile(timezone_symlink2, false);
    233 
    234   // Create new symlink2.
    235   if (symlink(timezone_file.value().c_str(),
    236               timezone_symlink2.value().c_str()) == -1) {
    237     LOG(ERROR) << "SetTimezoneID: Unable to create symlink "
    238                << timezone_symlink2.value() << " to " << timezone_file.value();
    239     return;
    240   }
    241 
    242   // Move symlink2 to symlink.
    243   if (!base::ReplaceFile(timezone_symlink2, timezone_symlink, NULL)) {
    244     LOG(ERROR) << "SetTimezoneID: Unable to move symlink "
    245                << timezone_symlink2.value() << " to "
    246                << timezone_symlink.value();
    247   }
    248 }
    249 
    250 // Common code of the TimezoneSettings implementations.
    251 class TimezoneSettingsBaseImpl : public chromeos::system::TimezoneSettings {
    252  public:
    253   virtual ~TimezoneSettingsBaseImpl();
    254 
    255   // TimezoneSettings implementation:
    256   virtual const icu::TimeZone& GetTimezone() OVERRIDE;
    257   virtual string16 GetCurrentTimezoneID() OVERRIDE;
    258   virtual void SetTimezoneFromID(const string16& timezone_id) OVERRIDE;
    259   virtual void AddObserver(Observer* observer) OVERRIDE;
    260   virtual void RemoveObserver(Observer* observer) OVERRIDE;
    261   virtual const std::vector<icu::TimeZone*>& GetTimezoneList() const OVERRIDE;
    262 
    263  protected:
    264   TimezoneSettingsBaseImpl();
    265 
    266   // Returns |timezone| if it is an element of |timezones_|.
    267   // Otherwise, returns a timezone from |timezones_|, if such exists, that has
    268   // the same rule as the given |timezone|.
    269   // Otherwise, returns NULL.
    270   // Note multiple timezones with the same time zone offset may exist
    271   // e.g.
    272   //   US/Pacific == America/Los_Angeles
    273   const icu::TimeZone* GetKnownTimezoneOrNull(
    274       const icu::TimeZone& timezone) const;
    275 
    276   ObserverList<Observer> observers_;
    277   std::vector<icu::TimeZone*> timezones_;
    278   scoped_ptr<icu::TimeZone> timezone_;
    279 
    280  private:
    281   DISALLOW_COPY_AND_ASSIGN(TimezoneSettingsBaseImpl);
    282 };
    283 
    284 // The TimezoneSettings implementation used in production.
    285 class TimezoneSettingsImpl : public TimezoneSettingsBaseImpl {
    286  public:
    287   // TimezoneSettings implementation:
    288   virtual void SetTimezone(const icu::TimeZone& timezone) OVERRIDE;
    289 
    290   static TimezoneSettingsImpl* GetInstance();
    291 
    292  private:
    293   friend struct DefaultSingletonTraits<TimezoneSettingsImpl>;
    294 
    295   TimezoneSettingsImpl();
    296 
    297   DISALLOW_COPY_AND_ASSIGN(TimezoneSettingsImpl);
    298 };
    299 
    300 // The stub TimezoneSettings implementation used on Linux desktop.
    301 class TimezoneSettingsStubImpl : public TimezoneSettingsBaseImpl {
    302  public:
    303   // TimezoneSettings implementation:
    304   virtual void SetTimezone(const icu::TimeZone& timezone) OVERRIDE;
    305 
    306   static TimezoneSettingsStubImpl* GetInstance();
    307 
    308  private:
    309   friend struct DefaultSingletonTraits<TimezoneSettingsStubImpl>;
    310 
    311   TimezoneSettingsStubImpl();
    312 
    313   DISALLOW_COPY_AND_ASSIGN(TimezoneSettingsStubImpl);
    314 };
    315 
    316 TimezoneSettingsBaseImpl::~TimezoneSettingsBaseImpl() {
    317   STLDeleteElements(&timezones_);
    318 }
    319 
    320 const icu::TimeZone& TimezoneSettingsBaseImpl::GetTimezone() {
    321   return *timezone_.get();
    322 }
    323 
    324 string16 TimezoneSettingsBaseImpl::GetCurrentTimezoneID() {
    325   return chromeos::system::TimezoneSettings::GetTimezoneID(GetTimezone());
    326 }
    327 
    328 void TimezoneSettingsBaseImpl::SetTimezoneFromID(const string16& timezone_id) {
    329   scoped_ptr<icu::TimeZone> timezone(icu::TimeZone::createTimeZone(
    330       icu::UnicodeString(timezone_id.c_str(), timezone_id.size())));
    331   SetTimezone(*timezone);
    332 }
    333 
    334 void TimezoneSettingsBaseImpl::AddObserver(Observer* observer) {
    335   observers_.AddObserver(observer);
    336 }
    337 
    338 void TimezoneSettingsBaseImpl::RemoveObserver(Observer* observer) {
    339   observers_.RemoveObserver(observer);
    340 }
    341 
    342 const std::vector<icu::TimeZone*>&
    343 TimezoneSettingsBaseImpl::GetTimezoneList() const {
    344   return timezones_;
    345 }
    346 
    347 TimezoneSettingsBaseImpl::TimezoneSettingsBaseImpl() {
    348   for (size_t i = 0; i < arraysize(kTimeZones); ++i) {
    349     timezones_.push_back(icu::TimeZone::createTimeZone(
    350         icu::UnicodeString(kTimeZones[i], -1, US_INV)));
    351   }
    352 }
    353 
    354 const icu::TimeZone* TimezoneSettingsBaseImpl::GetKnownTimezoneOrNull(
    355     const icu::TimeZone& timezone) const {
    356   const icu::TimeZone* known_timezone = NULL;
    357   for (std::vector<icu::TimeZone*>::const_iterator iter = timezones_.begin();
    358        iter != timezones_.end(); ++iter) {
    359     const icu::TimeZone* entry = *iter;
    360     if (*entry == timezone)
    361       return entry;
    362     if (entry->hasSameRules(timezone))
    363       known_timezone = entry;
    364   }
    365 
    366   // May return NULL if we did not find a matching timezone in our list.
    367   return known_timezone;
    368 }
    369 
    370 void TimezoneSettingsImpl::SetTimezone(const icu::TimeZone& timezone) {
    371   // Replace |timezone| by a known timezone with the same rules. If none exists
    372   // go on with |timezone|.
    373   const icu::TimeZone* known_timezone = GetKnownTimezoneOrNull(timezone);
    374   if (!known_timezone)
    375     known_timezone = &timezone;
    376 
    377   timezone_.reset(known_timezone->clone());
    378   std::string id = UTF16ToUTF8(GetTimezoneID(*known_timezone));
    379   VLOG(1) << "Setting timezone to " << id;
    380   // It's safe to change the timezone config files in the background as the
    381   // following operations don't depend on the completion of the config change.
    382   base::WorkerPool::GetTaskRunner(true /* task is slow */)->
    383       PostTask(FROM_HERE, base::Bind(&SetTimezoneIDFromString, id));
    384   icu::TimeZone::setDefault(*known_timezone);
    385   FOR_EACH_OBSERVER(Observer, observers_, TimezoneChanged(*known_timezone));
    386 }
    387 
    388 // static
    389 TimezoneSettingsImpl* TimezoneSettingsImpl::GetInstance() {
    390   return Singleton<TimezoneSettingsImpl,
    391                    DefaultSingletonTraits<TimezoneSettingsImpl> >::get();
    392 }
    393 
    394 TimezoneSettingsImpl::TimezoneSettingsImpl() {
    395   std::string id = GetTimezoneIDAsString();
    396   if (id.empty()) {
    397     id = kFallbackTimeZoneId;
    398     LOG(ERROR) << "Got an empty string for timezone, default to '" << id;
    399   }
    400 
    401   timezone_.reset(icu::TimeZone::createTimeZone(
    402       icu::UnicodeString::fromUTF8(id)));
    403 
    404   // Store a known timezone equivalent to id in |timezone_|.
    405   const icu::TimeZone* known_timezone = GetKnownTimezoneOrNull(*timezone_);
    406   if (known_timezone != NULL && *known_timezone != *timezone_)
    407     // Not necessary to update the filesystem because |known_timezone| has the
    408     // same rules.
    409     timezone_.reset(known_timezone->clone());
    410 
    411   icu::TimeZone::setDefault(*timezone_);
    412   VLOG(1) << "Timezone initially set to " << id;
    413 }
    414 
    415 void TimezoneSettingsStubImpl::SetTimezone(const icu::TimeZone& timezone) {
    416   // Replace |timezone| by a known timezone with the same rules. If none exists
    417   // go on with |timezone|.
    418   const icu::TimeZone* known_timezone = GetKnownTimezoneOrNull(timezone);
    419   if (!known_timezone)
    420     known_timezone = &timezone;
    421 
    422   timezone_.reset(known_timezone->clone());
    423   icu::TimeZone::setDefault(*known_timezone);
    424   FOR_EACH_OBSERVER(Observer, observers_, TimezoneChanged(*known_timezone));
    425 }
    426 
    427 // static
    428 TimezoneSettingsStubImpl* TimezoneSettingsStubImpl::GetInstance() {
    429   return Singleton<TimezoneSettingsStubImpl,
    430       DefaultSingletonTraits<TimezoneSettingsStubImpl> >::get();
    431 }
    432 
    433 TimezoneSettingsStubImpl::TimezoneSettingsStubImpl() {
    434   timezone_.reset(icu::TimeZone::createDefault());
    435   const icu::TimeZone* known_timezone = GetKnownTimezoneOrNull(*timezone_);
    436   if (known_timezone != NULL && *known_timezone != *timezone_)
    437     timezone_.reset(known_timezone->clone());
    438 }
    439 
    440 }  // namespace
    441 
    442 namespace chromeos {
    443 namespace system {
    444 
    445 TimezoneSettings::Observer::~Observer() {}
    446 
    447 // static
    448 TimezoneSettings* TimezoneSettings::GetInstance() {
    449   if (base::SysInfo::IsRunningOnChromeOS()) {
    450     return TimezoneSettingsImpl::GetInstance();
    451   } else {
    452     return TimezoneSettingsStubImpl::GetInstance();
    453   }
    454 }
    455 
    456 // static
    457 string16 TimezoneSettings::GetTimezoneID(const icu::TimeZone& timezone) {
    458   icu::UnicodeString id;
    459   timezone.getID(id);
    460   return string16(id.getBuffer(), id.length());
    461 }
    462 
    463 }  // namespace system
    464 }  // namespace chromeos
    465