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