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 base::string16 GetCurrentTimezoneID() OVERRIDE;
    258   virtual void SetTimezoneFromID(const base::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 base::string16 TimezoneSettingsBaseImpl::GetCurrentTimezoneID() {
    325   return chromeos::system::TimezoneSettings::GetTimezoneID(GetTimezone());
    326 }
    327 
    328 void TimezoneSettingsBaseImpl::SetTimezoneFromID(
    329     const base::string16& timezone_id) {
    330   scoped_ptr<icu::TimeZone> timezone(icu::TimeZone::createTimeZone(
    331       icu::UnicodeString(timezone_id.c_str(), timezone_id.size())));
    332   SetTimezone(*timezone);
    333 }
    334 
    335 void TimezoneSettingsBaseImpl::AddObserver(Observer* observer) {
    336   observers_.AddObserver(observer);
    337 }
    338 
    339 void TimezoneSettingsBaseImpl::RemoveObserver(Observer* observer) {
    340   observers_.RemoveObserver(observer);
    341 }
    342 
    343 const std::vector<icu::TimeZone*>&
    344 TimezoneSettingsBaseImpl::GetTimezoneList() const {
    345   return timezones_;
    346 }
    347 
    348 TimezoneSettingsBaseImpl::TimezoneSettingsBaseImpl() {
    349   for (size_t i = 0; i < arraysize(kTimeZones); ++i) {
    350     timezones_.push_back(icu::TimeZone::createTimeZone(
    351         icu::UnicodeString(kTimeZones[i], -1, US_INV)));
    352   }
    353 }
    354 
    355 const icu::TimeZone* TimezoneSettingsBaseImpl::GetKnownTimezoneOrNull(
    356     const icu::TimeZone& timezone) const {
    357   const icu::TimeZone* known_timezone = NULL;
    358   for (std::vector<icu::TimeZone*>::const_iterator iter = timezones_.begin();
    359        iter != timezones_.end(); ++iter) {
    360     const icu::TimeZone* entry = *iter;
    361     if (*entry == timezone)
    362       return entry;
    363     if (entry->hasSameRules(timezone))
    364       known_timezone = entry;
    365   }
    366 
    367   // May return NULL if we did not find a matching timezone in our list.
    368   return known_timezone;
    369 }
    370 
    371 void TimezoneSettingsImpl::SetTimezone(const icu::TimeZone& timezone) {
    372   // Replace |timezone| by a known timezone with the same rules. If none exists
    373   // go on with |timezone|.
    374   const icu::TimeZone* known_timezone = GetKnownTimezoneOrNull(timezone);
    375   if (!known_timezone)
    376     known_timezone = &timezone;
    377 
    378   timezone_.reset(known_timezone->clone());
    379   std::string id = base::UTF16ToUTF8(GetTimezoneID(*known_timezone));
    380   VLOG(1) << "Setting timezone to " << id;
    381   // It's safe to change the timezone config files in the background as the
    382   // following operations don't depend on the completion of the config change.
    383   base::WorkerPool::GetTaskRunner(true /* task is slow */)->
    384       PostTask(FROM_HERE, base::Bind(&SetTimezoneIDFromString, id));
    385   icu::TimeZone::setDefault(*known_timezone);
    386   FOR_EACH_OBSERVER(Observer, observers_, TimezoneChanged(*known_timezone));
    387 }
    388 
    389 // static
    390 TimezoneSettingsImpl* TimezoneSettingsImpl::GetInstance() {
    391   return Singleton<TimezoneSettingsImpl,
    392                    DefaultSingletonTraits<TimezoneSettingsImpl> >::get();
    393 }
    394 
    395 TimezoneSettingsImpl::TimezoneSettingsImpl() {
    396   std::string id = GetTimezoneIDAsString();
    397   if (id.empty()) {
    398     id = kFallbackTimeZoneId;
    399     LOG(ERROR) << "Got an empty string for timezone, default to '" << id;
    400   }
    401 
    402   timezone_.reset(icu::TimeZone::createTimeZone(
    403       icu::UnicodeString::fromUTF8(id)));
    404 
    405   // Store a known timezone equivalent to id in |timezone_|.
    406   const icu::TimeZone* known_timezone = GetKnownTimezoneOrNull(*timezone_);
    407   if (known_timezone != NULL && *known_timezone != *timezone_)
    408     // Not necessary to update the filesystem because |known_timezone| has the
    409     // same rules.
    410     timezone_.reset(known_timezone->clone());
    411 
    412   icu::TimeZone::setDefault(*timezone_);
    413   VLOG(1) << "Timezone initially set to " << id;
    414 }
    415 
    416 void TimezoneSettingsStubImpl::SetTimezone(const icu::TimeZone& timezone) {
    417   // Replace |timezone| by a known timezone with the same rules. If none exists
    418   // go on with |timezone|.
    419   const icu::TimeZone* known_timezone = GetKnownTimezoneOrNull(timezone);
    420   if (!known_timezone)
    421     known_timezone = &timezone;
    422 
    423   timezone_.reset(known_timezone->clone());
    424   icu::TimeZone::setDefault(*known_timezone);
    425   FOR_EACH_OBSERVER(Observer, observers_, TimezoneChanged(*known_timezone));
    426 }
    427 
    428 // static
    429 TimezoneSettingsStubImpl* TimezoneSettingsStubImpl::GetInstance() {
    430   return Singleton<TimezoneSettingsStubImpl,
    431       DefaultSingletonTraits<TimezoneSettingsStubImpl> >::get();
    432 }
    433 
    434 TimezoneSettingsStubImpl::TimezoneSettingsStubImpl() {
    435   timezone_.reset(icu::TimeZone::createDefault());
    436   const icu::TimeZone* known_timezone = GetKnownTimezoneOrNull(*timezone_);
    437   if (known_timezone != NULL && *known_timezone != *timezone_)
    438     timezone_.reset(known_timezone->clone());
    439 }
    440 
    441 }  // namespace
    442 
    443 namespace chromeos {
    444 namespace system {
    445 
    446 TimezoneSettings::Observer::~Observer() {}
    447 
    448 // static
    449 TimezoneSettings* TimezoneSettings::GetInstance() {
    450   if (base::SysInfo::IsRunningOnChromeOS()) {
    451     return TimezoneSettingsImpl::GetInstance();
    452   } else {
    453     return TimezoneSettingsStubImpl::GetInstance();
    454   }
    455 }
    456 
    457 // static
    458 base::string16 TimezoneSettings::GetTimezoneID(const icu::TimeZone& timezone) {
    459   icu::UnicodeString id;
    460   timezone.getID(id);
    461   return base::string16(id.getBuffer(), id.length());
    462 }
    463 
    464 }  // namespace system
    465 }  // namespace chromeos
    466