• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
GetTimezoneIDAsString()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 
SetTimezoneIDFromString(const std::string & id)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 
~TimezoneSettingsBaseImpl()316 TimezoneSettingsBaseImpl::~TimezoneSettingsBaseImpl() {
317   STLDeleteElements(&timezones_);
318 }
319 
GetTimezone()320 const icu::TimeZone& TimezoneSettingsBaseImpl::GetTimezone() {
321   return *timezone_.get();
322 }
323 
GetCurrentTimezoneID()324 string16 TimezoneSettingsBaseImpl::GetCurrentTimezoneID() {
325   return chromeos::system::TimezoneSettings::GetTimezoneID(GetTimezone());
326 }
327 
SetTimezoneFromID(const string16 & timezone_id)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 
AddObserver(Observer * observer)334 void TimezoneSettingsBaseImpl::AddObserver(Observer* observer) {
335   observers_.AddObserver(observer);
336 }
337 
RemoveObserver(Observer * observer)338 void TimezoneSettingsBaseImpl::RemoveObserver(Observer* observer) {
339   observers_.RemoveObserver(observer);
340 }
341 
342 const std::vector<icu::TimeZone*>&
GetTimezoneList() const343 TimezoneSettingsBaseImpl::GetTimezoneList() const {
344   return timezones_;
345 }
346 
TimezoneSettingsBaseImpl()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 
GetKnownTimezoneOrNull(const icu::TimeZone & timezone) const354 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 
SetTimezone(const icu::TimeZone & timezone)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
GetInstance()389 TimezoneSettingsImpl* TimezoneSettingsImpl::GetInstance() {
390   return Singleton<TimezoneSettingsImpl,
391                    DefaultSingletonTraits<TimezoneSettingsImpl> >::get();
392 }
393 
TimezoneSettingsImpl()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 
SetTimezone(const icu::TimeZone & timezone)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
GetInstance()428 TimezoneSettingsStubImpl* TimezoneSettingsStubImpl::GetInstance() {
429   return Singleton<TimezoneSettingsStubImpl,
430       DefaultSingletonTraits<TimezoneSettingsStubImpl> >::get();
431 }
432 
TimezoneSettingsStubImpl()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 
~Observer()445 TimezoneSettings::Observer::~Observer() {}
446 
447 // static
GetInstance()448 TimezoneSettings* TimezoneSettings::GetInstance() {
449   if (base::SysInfo::IsRunningOnChromeOS()) {
450     return TimezoneSettingsImpl::GetInstance();
451   } else {
452     return TimezoneSettingsStubImpl::GetInstance();
453   }
454 }
455 
456 // static
GetTimezoneID(const icu::TimeZone & timezone)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