1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
|
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
var EXPORTED_SYMBOLS = ["CalTimezoneService"];
var { AppConstants } = ChromeUtils.importESModule("resource://gre/modules/AppConstants.sys.mjs");
var { cal } = ChromeUtils.import("resource:///modules/calendar/calUtils.jsm");
var { ICAL, unwrapSingle } = ChromeUtils.import("resource:///modules/calendar/Ical.jsm");
const { CalTimezone } = ChromeUtils.import("resource:///modules/CalTimezone.jsm");
const TIMEZONE_CHANGED_TOPIC = "default-timezone-changed";
// CalTimezoneService acts as an implementation of both ICAL.TimezoneService and
// the XPCOM calITimezoneService used for providing timezone objects to calendar
// code.
function CalTimezoneService() {
this.wrappedJSObject = this;
this._timezoneDatabase = Cc["@mozilla.org/calendar/timezone-database;1"].getService(
Ci.calITimezoneDatabase
);
this.mZones = new Map();
this.mZoneIds = [];
ICAL.TimezoneService = this.wrappedJSObject;
}
var calTimezoneServiceClassID = Components.ID("{e736f2bd-7640-4715-ab35-887dc866c587}");
var calTimezoneServiceInterfaces = [Ci.calITimezoneService, Ci.calIStartupService];
CalTimezoneService.prototype = {
mDefaultTimezone: null,
mVersion: null,
mZones: null,
mZoneIds: null,
classID: calTimezoneServiceClassID,
QueryInterface: cal.generateQI(["calITimezoneService", "calIStartupService"]),
classInfo: cal.generateCI({
classID: calTimezoneServiceClassID,
contractID: "@mozilla.org/calendar/timezone-service;1",
classDescription: "Calendar Timezone Service",
interfaces: calTimezoneServiceInterfaces,
flags: Ci.nsIClassInfo.SINGLETON,
}),
// ical.js TimezoneService methods
has(id) {
return this.getTimezone(id) != null;
},
get(id) {
return id ? unwrapSingle(ICAL.Timezone, this.getTimezone(id)) : null;
},
remove() {},
register() {},
// calIStartupService methods
startup(aCompleteListener) {
// Fetch list of supported canonical timezone IDs from the backing database
this.mZoneIds = this._timezoneDatabase.getCanonicalTimezoneIds();
// Fetch the version of the backing database
this.mVersion = this._timezoneDatabase.version;
cal.LOG("[CalTimezoneService] Timezones version " + this.version + " loaded");
// Set up zones for special values
const utc = new CalTimezone(ICAL.Timezone.utcTimezone);
this.mZones.set("UTC", utc);
const floating = new CalTimezone(ICAL.Timezone.localTimezone);
this.mZones.set("floating", floating);
// Initialize default timezone and, if unset, user timezone prefs
this._initDefaultTimezone();
// Watch for changes in system timezone or related user preferences
Services.prefs.addObserver("calendar.timezone.useSystemTimezone", this);
Services.prefs.addObserver("calendar.timezone.local", this);
Services.obs.addObserver(this, TIMEZONE_CHANGED_TOPIC);
// Notify the startup service that startup is complete
if (aCompleteListener) {
aCompleteListener.onResult(null, Cr.NS_OK);
}
},
shutdown(aCompleteListener) {
Services.obs.removeObserver(this, TIMEZONE_CHANGED_TOPIC);
Services.prefs.removeObserver("calendar.timezone.local", this);
Services.prefs.removeObserver("calendar.timezone.useSystemTimezone", this);
aCompleteListener.onResult(null, Cr.NS_OK);
},
// calITimezoneService methods
get UTC() {
return this.mZones.get("UTC");
},
get floating() {
return this.mZones.get("floating");
},
getTimezone(tzid) {
if (!tzid) {
cal.ERROR("Unknown timezone requested\n" + cal.STACK(10));
return null;
}
if (tzid.startsWith("/mozilla.org/")) {
// We know that our former tzids look like "/mozilla.org/<dtstamp>/continent/..."
// The ending of the mozilla prefix is the index of that slash before the
// continent. Therefore, we start looking for the prefix-ending slash
// after position 13.
tzid = tzid.substring(tzid.indexOf("/", 13) + 1);
}
// Per the IANA timezone database, "Z" is _not_ an alias for UTC, but our
// previous list of zones included it and Ical.js at a minimum is expecting
// it to be valid
if (tzid === "Z") {
return this.mZones.get("UTC");
}
// First check our cache of timezones
let timezone = this.mZones.get(tzid);
if (!timezone) {
// The requested timezone is not in the cache; ask the backing database
// for the timezone definition
const tzdef = this._timezoneDatabase.getTimezoneDefinition(tzid);
if (!tzdef) {
cal.ERROR(`Could not find definition for ${tzid}`);
return null;
}
timezone = new CalTimezone(
ICAL.Timezone.fromData({
tzid,
component: tzdef,
})
);
// Cache the resulting timezone
this.mZones.set(tzid, timezone);
}
return timezone;
},
get timezoneIds() {
return this.mZoneIds;
},
get version() {
return this.mVersion;
},
_initDefaultTimezone() {
// If the "use system timezone" preference is unset, we default to enabling
// it if the user's system supports it
let isSetSystemTimezonePref = Services.prefs.prefHasUserValue(
"calendar.timezone.useSystemTimezone"
);
if (!isSetSystemTimezonePref) {
let canUseSystemTimezone = AppConstants.MOZ_CAN_FOLLOW_SYSTEM_TIME;
Services.prefs.setBoolPref("calendar.timezone.useSystemTimezone", canUseSystemTimezone);
}
this._updateDefaultTimezone();
},
_updateDefaultTimezone() {
let prefUseSystemTimezone = Services.prefs.getBoolPref(
"calendar.timezone.useSystemTimezone",
true
);
let prefTzid = Services.prefs.getStringPref("calendar.timezone.local", null);
let tzid;
if (prefUseSystemTimezone || prefTzid === null || prefTzid === "floating") {
// If we do not have a timezone preference set, we default to using the
// system time; we may also do this if the user has set their preferences
// accordingly
tzid = Intl.DateTimeFormat().resolvedOptions().timeZone;
} else {
tzid = prefTzid;
}
// Update default timezone and preference if necessary
if (!this.mDefaultTimezone || this.mDefaultTimezone.tzid != tzid) {
this.mDefaultTimezone = this.getTimezone(tzid);
cal.ASSERT(this.mDefaultTimezone, `Timezone not found: ${tzid}`);
Services.obs.notifyObservers(null, "defaultTimezoneChanged");
if (this.mDefaultTimezone.tzid != prefTzid) {
Services.prefs.setStringPref("calendar.timezone.local", this.mDefaultTimezone.tzid);
}
}
},
get defaultTimezone() {
// We expect this to be initialized when the service comes up and updated if
// the underlying default changes
return this.mDefaultTimezone;
},
observe(aSubject, aTopic, aData) {
// Update the default timezone if the system timezone has changed; we
// expect the update function to decide if actually making the change is
// appropriate based on user prefs
if (aTopic == TIMEZONE_CHANGED_TOPIC) {
this._updateDefaultTimezone();
} else if (
aTopic == "nsPref:changed" &&
(aData == "calendar.timezone.useSystemTimezone" || aData == "calendar.timezone.local")
) {
// We may get a bogus second update from the timezone pref if its change
// is a result of the system timezone changing, but it should settle, and
// trying to guard against it is full of corner cases
this._updateDefaultTimezone();
}
},
};
|