Home | History | Annotate | Download | only in system
      1 // Copyright (c) 2012 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 "chrome/browser/chromeos/system/timezone_settings.h"
      6 
      7 #include <string>
      8 
      9 #include "base/bind.h"
     10 #include "base/chromeos/chromeos_version.h"
     11 #include "base/file_util.h"
     12 #include "base/files/file_path.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 "content/public/browser/browser_thread.h"
     21 #include "content/public/browser/render_process_host.h"
     22 #include "content/public/browser/render_view_host.h"
     23 #include "content/public/browser/render_widget_host.h"
     24 #include "third_party/icu/source/i18n/unicode/timezone.h"
     25 
     26 using content::BrowserThread;
     27 
     28 namespace {
     29 
     30 // The filepath to the timezone file that symlinks to the actual timezone file.
     31 const char kTimezoneSymlink[] = "/var/lib/timezone/localtime";
     32 const char kTimezoneSymlink2[] = "/var/lib/timezone/localtime2";
     33 
     34 // The directory that contains all the timezone files. So for timezone
     35 // "US/Pacific", the actual timezone file is: "/usr/share/zoneinfo/US/Pacific"
     36 const char kTimezoneFilesDir[] = "/usr/share/zoneinfo/";
     37 
     38 // Fallback time zone ID used in case of an unexpected error.
     39 const char kFallbackTimeZoneId[] = "America/Los_Angeles";
     40 
     41 // TODO(jungshik): Using Enumerate method in ICU gives 600+ timezones.
     42 // Even after filtering out duplicate entries with a strict identity check,
     43 // we still have 400+ zones. Relaxing the criteria for the timezone
     44 // identity is likely to cut down the number to < 100. Until we
     45 // come up with a better list, we hard-code the following list. It came from
     46 // from Android initially, but more entries have been added.
     47 static const char* kTimeZones[] = {
     48     "Pacific/Midway",
     49     "Pacific/Honolulu",
     50     "America/Anchorage",
     51     "America/Los_Angeles",
     52     "America/Vancouver",
     53     "America/Tijuana",
     54     "America/Phoenix",
     55     "America/Denver",
     56     "America/Edmonton",
     57     "America/Chihuahua",
     58     "America/Regina",
     59     "America/Costa_Rica",
     60     "America/Chicago",
     61     "America/Mexico_City",
     62     "America/Winnipeg",
     63     "America/Bogota",
     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/Sao_Paulo",
     73     "America/Araguaina",
     74     "America/Argentina/Buenos_Aires",
     75     "America/Argentina/San_Luis",
     76     "America/Montevideo",
     77     "America/Godthab",
     78     "Atlantic/South_Georgia",
     79     "Atlantic/Cape_Verde",
     80     "Atlantic/Azores",
     81     "Africa/Casablanca",
     82     "Europe/London",
     83     "Europe/Dublin",
     84     "Europe/Amsterdam",
     85     "Europe/Belgrade",
     86     "Europe/Berlin",
     87     "Europe/Brussels",
     88     "Europe/Madrid",
     89     "Europe/Paris",
     90     "Europe/Rome",
     91     "Europe/Stockholm",
     92     "Europe/Sarajevo",
     93     "Europe/Vienna",
     94     "Europe/Warsaw",
     95     "Europe/Zurich",
     96     "Africa/Windhoek",
     97     "Africa/Lagos",
     98     "Africa/Brazzaville",
     99     "Africa/Cairo",
    100     "Africa/Harare",
    101     "Africa/Maputo",
    102     "Africa/Johannesburg",
    103     "Europe/Helsinki",
    104     "Europe/Athens",
    105     "Asia/Amman",
    106     "Asia/Beirut",
    107     "Asia/Jerusalem",
    108     "Europe/Minsk",
    109     "Asia/Baghdad",
    110     "Asia/Riyadh",
    111     "Asia/Kuwait",
    112     "Africa/Nairobi",
    113     "Asia/Tehran",
    114     "Europe/Moscow",
    115     "Asia/Dubai",
    116     "Asia/Tbilisi",
    117     "Indian/Mauritius",
    118     "Asia/Baku",
    119     "Asia/Yerevan",
    120     "Asia/Kabul",
    121     "Asia/Karachi",
    122     "Asia/Ashgabat",
    123     "Asia/Oral",
    124     "Asia/Calcutta",
    125     "Asia/Colombo",
    126     "Asia/Katmandu",
    127     "Asia/Yekaterinburg",
    128     "Asia/Almaty",
    129     "Asia/Dhaka",
    130     "Asia/Rangoon",
    131     "Asia/Bangkok",
    132     "Asia/Jakarta",
    133     "Asia/Omsk",
    134     "Asia/Novosibirsk",
    135     "Asia/Shanghai",
    136     "Asia/Hong_Kong",
    137     "Asia/Kuala_Lumpur",
    138     "Asia/Singapore",
    139     "Asia/Manila",
    140     "Asia/Taipei",
    141     "Asia/Makassar",
    142     "Asia/Krasnoyarsk",
    143     "Australia/Perth",
    144     "Australia/Eucla",
    145     "Asia/Irkutsk",
    146     "Asia/Seoul",
    147     "Asia/Tokyo",
    148     "Asia/Jayapura",
    149     "Australia/Adelaide",
    150     "Australia/Darwin",
    151     "Australia/Brisbane",
    152     "Australia/Hobart",
    153     "Australia/Sydney",
    154     "Asia/Yakutsk",
    155     "Pacific/Guam",
    156     "Pacific/Port_Moresby",
    157     "Asia/Vladivostok",
    158     "Asia/Sakhalin",
    159     "Asia/Magadan",
    160     "Pacific/Auckland",
    161     "Pacific/Fiji",
    162     "Pacific/Majuro",
    163     "Pacific/Tongatapu",
    164     "Pacific/Apia",
    165     "Pacific/Kiritimati",
    166 };
    167 
    168 std::string GetTimezoneIDAsString() {
    169   // Compare with chromiumos/src/platform/init/ui.conf which fixes certain
    170   // incorrect states of the timezone symlink on startup. Thus errors occuring
    171   // here should be rather contrived.
    172 
    173   // Look at kTimezoneSymlink, see which timezone we are symlinked to.
    174   char buf[256];
    175   const ssize_t len = readlink(kTimezoneSymlink, buf,
    176                                sizeof(buf)-1);
    177   if (len == -1) {
    178     LOG(ERROR) << "GetTimezoneID: Cannot read timezone symlink "
    179                << kTimezoneSymlink;
    180     return std::string();
    181   }
    182 
    183   std::string timezone(buf, len);
    184   // Remove kTimezoneFilesDir from the beginning.
    185   if (timezone.find(kTimezoneFilesDir) != 0) {
    186     LOG(ERROR) << "GetTimezoneID: Timezone symlink is wrong "
    187                << timezone;
    188     return std::string();
    189   }
    190 
    191   return timezone.substr(strlen(kTimezoneFilesDir));
    192 }
    193 
    194 void SetTimezoneIDFromString(const std::string& id) {
    195   // Change the kTimezoneSymlink symlink to the path for this timezone.
    196   // We want to do this in an atomic way. So we are going to create the symlink
    197   // at kTimezoneSymlink2 and then move it to kTimezoneSymlink
    198 
    199   base::FilePath timezone_symlink(kTimezoneSymlink);
    200   base::FilePath timezone_symlink2(kTimezoneSymlink2);
    201   base::FilePath timezone_file(kTimezoneFilesDir + id);
    202 
    203   // Make sure timezone_file exists.
    204   if (!base::PathExists(timezone_file)) {
    205     LOG(ERROR) << "SetTimezoneID: Cannot find timezone file "
    206                << timezone_file.value();
    207     return;
    208   }
    209 
    210   // Delete old symlink2 if it exists.
    211   base::DeleteFile(timezone_symlink2, false);
    212 
    213   // Create new symlink2.
    214   if (symlink(timezone_file.value().c_str(),
    215               timezone_symlink2.value().c_str()) == -1) {
    216     LOG(ERROR) << "SetTimezoneID: Unable to create symlink "
    217                << timezone_symlink2.value() << " to " << timezone_file.value();
    218     return;
    219   }
    220 
    221   // Move symlink2 to symlink.
    222   if (!base::ReplaceFile(timezone_symlink2, timezone_symlink, NULL)) {
    223     LOG(ERROR) << "SetTimezoneID: Unable to move symlink "
    224                << timezone_symlink2.value() << " to "
    225                << timezone_symlink.value();
    226   }
    227 }
    228 
    229 // Common code of the TimezoneSettings implementations.
    230 class TimezoneSettingsBaseImpl : public chromeos::system::TimezoneSettings {
    231  public:
    232   virtual ~TimezoneSettingsBaseImpl();
    233 
    234   // TimezoneSettings implementation:
    235   virtual const icu::TimeZone& GetTimezone() OVERRIDE;
    236   virtual string16 GetCurrentTimezoneID() OVERRIDE;
    237   virtual void SetTimezoneFromID(const string16& timezone_id) OVERRIDE;
    238   virtual void AddObserver(Observer* observer) OVERRIDE;
    239   virtual void RemoveObserver(Observer* observer) OVERRIDE;
    240   virtual const std::vector<icu::TimeZone*>& GetTimezoneList() const OVERRIDE;
    241 
    242  protected:
    243   TimezoneSettingsBaseImpl();
    244 
    245   // Returns |timezone| if it is an element of |timezones_|.
    246   // Otherwise, returns a timezone from |timezones_|, if such exists, that has
    247   // the same rule as the given |timezone|.
    248   // Otherwise, returns NULL.
    249   // Note multiple timezones with the same time zone offset may exist
    250   // e.g.
    251   //   US/Pacific == America/Los_Angeles
    252   const icu::TimeZone* GetKnownTimezoneOrNull(
    253       const icu::TimeZone& timezone) const;
    254 
    255   // Notifies each renderer of the change in timezone to reset cached
    256   // information stored in v8 to accelerate date operations.
    257   void NotifyRenderers();
    258 
    259   ObserverList<Observer> observers_;
    260   std::vector<icu::TimeZone*> timezones_;
    261   scoped_ptr<icu::TimeZone> timezone_;
    262 
    263  private:
    264   DISALLOW_COPY_AND_ASSIGN(TimezoneSettingsBaseImpl);
    265 };
    266 
    267 // The TimezoneSettings implementation used in production.
    268 class TimezoneSettingsImpl : public TimezoneSettingsBaseImpl {
    269  public:
    270   // TimezoneSettings implementation:
    271   virtual void SetTimezone(const icu::TimeZone& timezone) OVERRIDE;
    272 
    273   static TimezoneSettingsImpl* GetInstance();
    274 
    275  private:
    276   friend struct DefaultSingletonTraits<TimezoneSettingsImpl>;
    277 
    278   TimezoneSettingsImpl();
    279 
    280   DISALLOW_COPY_AND_ASSIGN(TimezoneSettingsImpl);
    281 };
    282 
    283 // The stub TimezoneSettings implementation used on Linux desktop.
    284 class TimezoneSettingsStubImpl : public TimezoneSettingsBaseImpl {
    285  public:
    286   // TimezoneSettings implementation:
    287   virtual void SetTimezone(const icu::TimeZone& timezone) OVERRIDE;
    288 
    289   static TimezoneSettingsStubImpl* GetInstance();
    290 
    291  private:
    292   friend struct DefaultSingletonTraits<TimezoneSettingsStubImpl>;
    293 
    294   TimezoneSettingsStubImpl();
    295 
    296   DISALLOW_COPY_AND_ASSIGN(TimezoneSettingsStubImpl);
    297 };
    298 
    299 TimezoneSettingsBaseImpl::~TimezoneSettingsBaseImpl() {
    300   STLDeleteElements(&timezones_);
    301 }
    302 
    303 const icu::TimeZone& TimezoneSettingsBaseImpl::GetTimezone() {
    304   return *timezone_.get();
    305 }
    306 
    307 string16 TimezoneSettingsBaseImpl::GetCurrentTimezoneID() {
    308   return chromeos::system::TimezoneSettings::GetTimezoneID(GetTimezone());
    309 }
    310 
    311 void TimezoneSettingsBaseImpl::SetTimezoneFromID(const string16& timezone_id) {
    312   scoped_ptr<icu::TimeZone> timezone(icu::TimeZone::createTimeZone(
    313       icu::UnicodeString(timezone_id.c_str(), timezone_id.size())));
    314   SetTimezone(*timezone);
    315   NotifyRenderers();
    316 }
    317 
    318 void TimezoneSettingsBaseImpl::AddObserver(Observer* observer) {
    319   observers_.AddObserver(observer);
    320 }
    321 
    322 void TimezoneSettingsBaseImpl::RemoveObserver(Observer* observer) {
    323   observers_.RemoveObserver(observer);
    324 }
    325 
    326 const std::vector<icu::TimeZone*>&
    327 TimezoneSettingsBaseImpl::GetTimezoneList() const {
    328   return timezones_;
    329 }
    330 
    331 TimezoneSettingsBaseImpl::TimezoneSettingsBaseImpl() {
    332   for (size_t i = 0; i < arraysize(kTimeZones); ++i) {
    333     timezones_.push_back(icu::TimeZone::createTimeZone(
    334         icu::UnicodeString(kTimeZones[i], -1, US_INV)));
    335   }
    336 }
    337 
    338 const icu::TimeZone* TimezoneSettingsBaseImpl::GetKnownTimezoneOrNull(
    339     const icu::TimeZone& timezone) const {
    340   const icu::TimeZone* known_timezone = NULL;
    341   for (std::vector<icu::TimeZone*>::const_iterator iter = timezones_.begin();
    342        iter != timezones_.end(); ++iter) {
    343     const icu::TimeZone* entry = *iter;
    344     if (*entry == timezone)
    345       return entry;
    346     if (entry->hasSameRules(timezone))
    347       known_timezone = entry;
    348   }
    349 
    350   // May return NULL if we did not find a matching timezone in our list.
    351   return known_timezone;
    352 }
    353 
    354 void TimezoneSettingsBaseImpl::NotifyRenderers() {
    355   content::RenderWidgetHost::List widgets =
    356       content::RenderWidgetHost::GetRenderWidgetHosts();
    357   for (size_t i = 0; i < widgets.size(); ++i) {
    358     if (widgets[i]->IsRenderView()) {
    359       content::RenderViewHost* view = content::RenderViewHost::From(widgets[i]);
    360       view->NotifyTimezoneChange();
    361     }
    362   }
    363 }
    364 
    365 void TimezoneSettingsImpl::SetTimezone(const icu::TimeZone& timezone) {
    366   // Replace |timezone| by a known timezone with the same rules. If none exists
    367   // go on with |timezone|.
    368   const icu::TimeZone* known_timezone = GetKnownTimezoneOrNull(timezone);
    369   if (!known_timezone)
    370     known_timezone = &timezone;
    371 
    372   timezone_.reset(known_timezone->clone());
    373   std::string id = UTF16ToUTF8(GetTimezoneID(*known_timezone));
    374   VLOG(1) << "Setting timezone to " << id;
    375   // It's safe to change the timezone config files in the background as the
    376   // following operations don't depend on the completion of the config change.
    377   BrowserThread::PostBlockingPoolTask(FROM_HERE,
    378                                       base::Bind(&SetTimezoneIDFromString, id));
    379   icu::TimeZone::setDefault(*known_timezone);
    380   FOR_EACH_OBSERVER(Observer, observers_, TimezoneChanged(*known_timezone));
    381 }
    382 
    383 // static
    384 TimezoneSettingsImpl* TimezoneSettingsImpl::GetInstance() {
    385   return Singleton<TimezoneSettingsImpl,
    386                    DefaultSingletonTraits<TimezoneSettingsImpl> >::get();
    387 }
    388 
    389 TimezoneSettingsImpl::TimezoneSettingsImpl() {
    390   std::string id = GetTimezoneIDAsString();
    391   if (id.empty()) {
    392     id = kFallbackTimeZoneId;
    393     LOG(ERROR) << "Got an empty string for timezone, default to '" << id;
    394   }
    395 
    396   timezone_.reset(icu::TimeZone::createTimeZone(
    397       icu::UnicodeString::fromUTF8(id)));
    398 
    399   // Store a known timezone equivalent to id in |timezone_|.
    400   const icu::TimeZone* known_timezone = GetKnownTimezoneOrNull(*timezone_);
    401   if (known_timezone != NULL && *known_timezone != *timezone_)
    402     // Not necessary to update the filesystem because |known_timezone| has the
    403     // same rules.
    404     timezone_.reset(known_timezone->clone());
    405 
    406   icu::TimeZone::setDefault(*timezone_);
    407   VLOG(1) << "Timezone initially set to " << id;
    408 }
    409 
    410 void TimezoneSettingsStubImpl::SetTimezone(const icu::TimeZone& timezone) {
    411   // Replace |timezone| by a known timezone with the same rules. If none exists
    412   // go on with |timezone|.
    413   const icu::TimeZone* known_timezone = GetKnownTimezoneOrNull(timezone);
    414   if (!known_timezone)
    415     known_timezone = &timezone;
    416 
    417   timezone_.reset(known_timezone->clone());
    418   icu::TimeZone::setDefault(*known_timezone);
    419   FOR_EACH_OBSERVER(Observer, observers_, TimezoneChanged(*known_timezone));
    420 }
    421 
    422 // static
    423 TimezoneSettingsStubImpl* TimezoneSettingsStubImpl::GetInstance() {
    424   return Singleton<TimezoneSettingsStubImpl,
    425       DefaultSingletonTraits<TimezoneSettingsStubImpl> >::get();
    426 }
    427 
    428 TimezoneSettingsStubImpl::TimezoneSettingsStubImpl() {
    429   timezone_.reset(icu::TimeZone::createDefault());
    430   const icu::TimeZone* known_timezone = GetKnownTimezoneOrNull(*timezone_);
    431   if (known_timezone != NULL && *known_timezone != *timezone_)
    432     timezone_.reset(known_timezone->clone());
    433 }
    434 
    435 }  // namespace
    436 
    437 namespace chromeos {
    438 namespace system {
    439 
    440 TimezoneSettings::Observer::~Observer() {}
    441 
    442 // static
    443 TimezoneSettings* TimezoneSettings::GetInstance() {
    444   if (base::chromeos::IsRunningOnChromeOS()) {
    445     return TimezoneSettingsImpl::GetInstance();
    446   } else {
    447     return TimezoneSettingsStubImpl::GetInstance();
    448   }
    449 }
    450 
    451 // static
    452 string16 TimezoneSettings::GetTimezoneID(const icu::TimeZone& timezone) {
    453   icu::UnicodeString id;
    454   timezone.getID(id);
    455   return string16(id.getBuffer(), id.length());
    456 }
    457 
    458 }  // namespace system
    459 }  // namespace chromeos
    460