Commit 12a433a7 authored by Carsten Schoenert's avatar Carsten Schoenert
Browse files

New upstream version 91.2.1

parent 3c888442
......@@ -398,6 +398,9 @@ var calImipBar = {
* @returns {Boolean} true, if the action succeeded
*/
executeAction(aParticipantStatus, aResponse) {
// control to avoid processing _execAction on later user changes on the item
let isFirstProcessing = true;
/**
* Internal function to trigger an scheduling operation
*
......@@ -425,6 +428,7 @@ var calImipBar = {
let opListener = {
QueryInterface: ChromeUtils.generateQI(["calIOperationListener"]),
onOperationComplete(aCalendar, aStatus, aOperationType, aId, aDetail) {
isFirstProcessing = false;
if (Components.isSuccessCode(aStatus) && isDeclineCounter) {
// TODO: move the DECLINECOUNTER stuff to actionFunc
aItipItem.getItemList().forEach(aItem => {
......@@ -574,8 +578,6 @@ var calImipBar = {
if (saveitems.length > 0) {
let methods = { receivedMethod: "PUBLISH", responseMethod: "PUBLISH" };
let newItipItem = cal.itip.getModifiedItipItem(calImipBar.itipItem, saveitems, methods);
// control to avoid processing _execAction on later user changes on the item
let isFirstProcessing = true;
// setup callback and trigger re-processing
let storeCopy = function(aItipItem, aRc, aActionFunc, aFoundItems) {
if (isFirstProcessing && aActionFunc && Components.isSuccessCode(aRc)) {
......@@ -583,7 +585,6 @@ var calImipBar = {
}
};
cal.itip.processItipItem(newItipItem, storeCopy);
isFirstProcessing = false;
}
// we stop here to not process the original item
return false;
......
......@@ -21,6 +21,8 @@ ChromeUtils.defineModuleGetter(this, "cal", "resource:///modules/calendar/calUti
const EXPORTED_SYMBOLS = ["calprint"]; /* exported calprint */
const weekInfoService = cal.getWeekInfoService();
var calprint = {
ensureInitialized() {
// Deliberate no-op. By calling this function from outside, you've ensured
......@@ -49,17 +51,15 @@ var calprint = {
}
document.getElementById("tasks-list-box").hidden = true;
let items = await getItems(startDate, endDate, filter, notDueTasks);
switch (type) {
case "list":
await listView.draw(document, items);
await listView.draw(document, startDate, endDate, filter, notDueTasks);
break;
case "monthGrid":
await monthGridView.draw(document, startDate, endDate, items);
await monthGridView.draw(document, startDate, endDate, filter, notDueTasks);
break;
case "weekPlanner":
await weekPlannerView.draw(document, startDate, endDate, items);
await weekPlannerView.draw(document, startDate, endDate, filter, notDueTasks);
break;
}
},
......@@ -226,12 +226,26 @@ function getItems(startDate, endDate, filter, notDueTasks) {
});
}
/**
* A simple list of calendar items.
*/
let listView = {
async draw(document, items) {
/**
* Create the list view.
*
* @param {HTMLDocument} document
* @param {calIDateTime} startDate - the first day of the months to be displayed
* @param {calIDateTime} endDate - the first day of the month AFTER the
* months to be displayed
* @param {integer} filter - calICalendar ITEM_FILTER flags
* @param {boolean} notDueTasks - if true, include tasks with no due date
*/
async draw(document, startDate, endDate, filter, notDueTasks) {
let container = document.getElementById("list-container");
let listItemTemplate = document.getElementById("list-item-template");
// Sort items.
// Get and sort items.
let items = await getItems(startDate, endDate, filter, notDueTasks);
items.sort((a, b) => {
let start_a = a[cal.dtz.startDateProp(a)];
if (!start_a) {
......@@ -244,6 +258,7 @@ let listView = {
return start_a.compare(start_b);
});
// Display the items.
for (let item of items) {
let itemNode = listItemTemplate.content.firstElementChild.cloneNode(true);
......@@ -264,16 +279,16 @@ let listView = {
}
};
let startDate = item[cal.dtz.startDateProp(item)];
let endDate = item[cal.dtz.endDateProp(item)];
if (startDate || endDate) {
let itemStartDate = item[cal.dtz.startDateProp(item)];
let itemEndDate = item[cal.dtz.endDateProp(item)];
if (itemStartDate || itemEndDate) {
// This is a task with a start or due date, format accordingly
let prefixWhen = cal.l10n.getCalString("htmlPrefixWhen");
itemNode.querySelector(".intervalkey").textContent = prefixWhen;
let startNode = itemNode.querySelector(".dtstart");
let dateString = cal.dtz.formatter.formatItemInterval(item);
startNode.setAttribute("title", startDate ? startDate.icalString : "none");
startNode.setAttribute("title", itemStartDate ? itemStartDate.icalString : "none");
startNode.textContent = dateString;
} else {
let row = itemNode.querySelector(".intervalrow");
......@@ -297,25 +312,58 @@ let listView = {
container.appendChild(itemNode);
}
// Set the page title.
let startMonth = cal.l10n.formatMonth(startDate.month + 1, "calendar", "monthInYear");
let startMonthTitle = cal.l10n.getCalString("monthInYear", [startMonth, startDate.year]);
endDate.day--;
let endMonth = cal.l10n.formatMonth(endDate.month + 1, "calendar", "monthInYear");
let endMonthTitle = cal.l10n.getCalString("monthInYear", [endMonth, endDate.year]);
if (startMonthTitle == endMonthTitle) {
document.title = startMonthTitle;
} else {
document.title = `${startMonthTitle} – ${endMonthTitle}`;
}
},
};
/**
* A layout with one calendar month per page.
*/
let monthGridView = {
dayTable: {},
async draw(document, start, end, items) {
let startDate = this.normalizeStartDate(start);
let endDate = this.normalizeEndDate(end);
/**
* Create the month grid view.
*
* @param {HTMLDocument} document
* @param {calIDateTime} startDate - the first day of the months to be displayed
* @param {calIDateTime} endDate - the first day of the month AFTER the
* months to be displayed
* @param {integer} filter - calICalendar ITEM_FILTER flags
* @param {boolean} notDueTasks - if true, include tasks with no due date
*/
async draw(document, startDate, endDate, filter, notDueTasks) {
let container = document.getElementById("month-container");
// Now set up all the months we need to
// Draw the month grid(s).
let current = startDate.clone();
do {
container.appendChild(this.drawMonth(document, current));
current.month += 1;
} while (current.compare(endDate) <= 0);
} while (current.compare(endDate) < 0);
// Extend the date range to include adjacent days that will be printed.
startDate = weekInfoService.getStartOfWeek(startDate);
// Get the end of the week containing the last day of the month, not the
// week containing the first day of the next month.
endDate.day--;
endDate = weekInfoService.getEndOfWeek(endDate);
endDate.day++; // Add a day to include items from the last day.
// Get and display the items.
let items = await getItems(startDate, endDate, filter, notDueTasks);
let defaultTimezone = cal.dtz.defaultTimezone;
for (let item of items) {
let itemStartDate = item[cal.dtz.startDateProp(item)] || item[cal.dtz.endDateProp(item)];
......@@ -331,66 +379,33 @@ let monthGridView = {
let boxDate = itemStartDate.clone();
boxDate.isDate = true;
for (boxDate; boxDate.compare(itemEndDate) < (itemEndDate.isDate ? 0 : 1); boxDate.day++) {
// Ignore items outside of the range, i.e tasks without start date
// where the end date is somewhere else.
if (start && end && boxDate && (boxDate.compare(start) < 0 || boxDate.compare(end) >= 0)) {
continue;
}
for (let dayBox of this.dayTable[boxDate.icalString]) {
addItemToDaybox(document, item, boxDate, dayBox.querySelector(".items"));
let boxDateString = boxDate.icalString;
if (boxDateString in this.dayTable) {
for (let dayBox of this.dayTable[boxDateString]) {
addItemToDaybox(document, item, boxDate, dayBox.querySelector(".items"));
}
}
}
}
},
normalizeStartDate(start) {
// Make sure the start date is really a date.
let startDate = start.clone();
startDate.isDate = true;
// Find out if the start date is also shown in the first week of the
// following month. This means we can spare a month printout.
let firstDayOfNextMonth = startDate.clone();
firstDayOfNextMonth.day = 1;
firstDayOfNextMonth.month++;
if (
cal
.getWeekInfoService()
.getStartOfWeek(firstDayOfNextMonth)
.compare(startDate) <= 0
) {
startDate = firstDayOfNextMonth;
// Set the page title.
let months = container.querySelectorAll("table");
if (months.length == 1) {
document.title = months[0].querySelector(".month-title").textContent;
} else {
startDate = startDate.startOfMonth;
}
return startDate;
},
normalizeEndDate(end) {
// Copy end date, which is exclusive. For our calculations, we will
// only be handling dates and the formatToHtml() code is much cleaner with
// the range being inclusive.
let endDate = end.clone();
endDate.isDate = true;
// Find out if the end date is also shown in the last week of the
// previous month. This also means we can spare a month printout.
let lastDayOfPreviousMonth = endDate.clone();
lastDayOfPreviousMonth.month--;
lastDayOfPreviousMonth = lastDayOfPreviousMonth.endOfMonth;
if (
cal
.getWeekInfoService()
.getEndOfWeek(lastDayOfPreviousMonth)
.compare(endDate) >= 0
) {
endDate = lastDayOfPreviousMonth;
document.title =
months[0].querySelector(".month-title").textContent +
" – " +
months[months.length - 1].querySelector(".month-title").textContent;
}
return endDate;
},
/**
* Create one month from the template.
*
* @param {HTMLDocument} document
* @param {calIDateTime} startOfMonth - the first day of the month
*/
drawMonth(document, startOfMonth) {
let monthTemplate = document.getElementById("month-template");
let month = monthTemplate.content.firstElementChild.cloneNode(true);
......@@ -410,7 +425,6 @@ let monthGridView = {
}
// Set up each week
let weekInfoService = cal.getWeekInfoService();
let endOfMonthView = weekInfoService.getEndOfWeek(startOfMonth.endOfMonth);
let startOfMonthView = weekInfoService.getStartOfWeek(startOfMonth);
let mainMonth = startOfMonth.month;
......@@ -426,6 +440,14 @@ let monthGridView = {
return month;
},
/**
* Create one week from the template.
*
* @param {HTMLDocument} document
* @param {calIDateTime} startOfWeek - the first day of the week
* @param {number} mainMonth - the month that this week is being added to
* (for marking days that are in adjacent months)
*/
drawWeek(document, startOfWeek, mainMonth) {
const weekdayMap = [
"d0sundaysoff",
......@@ -468,26 +490,32 @@ let monthGridView = {
},
};
/**
* A layout with seven days per page. The week layout is NOT aware of the
* start-of-week preferences. It always begins on a Monday.
*/
let weekPlannerView = {
dayTable: {},
async draw(document, start, end, items) {
/**
* Create the week planner view.
*
* @param {HTMLDocument} document
* @param {calIDateTime} startDate - the Monday of the first week to be displayed
* @param {calIDateTime} endDate - the Monday AFTER the last week to be displayed
* @param {integer} filter - calICalendar ITEM_FILTER flags
* @param {boolean} notDueTasks - if true, include tasks with no due date
*/
async draw(document, startDate, endDate, filter, notDueTasks) {
let container = document.getElementById("week-container");
// Table that maps YYYY-MM-DD to the DOM node container where items are to be added
let weekInfoService = cal.getWeekInfoService();
// Make sure to create tables from start to end, if passed
if (start && end) {
for (
let current = weekInfoService.getStartOfWeek(start);
current.compare(end) < 0;
current.day += 7
) {
container.appendChild(this.drawWeek(document, current));
}
// Draw the week grid(s).
for (let current = startDate.clone(); current.compare(endDate) < 0; current.day += 7) {
container.appendChild(this.drawWeek(document, current));
}
// Get and display the items.
let items = await getItems(startDate, endDate, filter, notDueTasks);
let defaultTimezone = cal.dtz.defaultTimezone;
for (let item of items) {
let itemStartDate = item[cal.dtz.startDateProp(item)] || item[cal.dtz.endDateProp(item)];
......@@ -503,18 +531,32 @@ let weekPlannerView = {
let boxDate = itemStartDate.clone();
boxDate.isDate = true;
for (boxDate; boxDate.compare(itemEndDate) < (itemEndDate.isDate ? 0 : 1); boxDate.day++) {
// Ignore items outside of the range, i.e tasks without start date
// where the end date is somewhere else.
if (start && end && boxDate && (boxDate.compare(start) < 0 || boxDate.compare(end) >= 0)) {
continue;
let boxDateString = boxDate.icalString;
if (boxDateString in this.dayTable) {
addItemToDaybox(document, item, boxDate, this.dayTable[boxDateString]);
}
addItemToDaybox(document, item, boxDate, this.dayTable[boxDate.icalString]);
}
}
// Set the page title.
let weeks = container.querySelectorAll("table");
if (weeks.length == 1) {
document.title = cal.l10n.getCalString("singleLongCalendarWeek", [weeks[0].number]);
} else {
document.title = cal.l10n.getCalString("severalLongCalendarWeeks", [
weeks[0].number,
weeks[weeks.length - 1].number,
]);
}
},
drawWeek(document, startOfWeek) {
/**
* Create one week from the template.
*
* @param {HTMLDocument} document
* @param {calIDateTime} monday - the Monday of the week
*/
drawWeek(document, monday) {
// In the order they appear on the page.
const weekdayMap = [
"d1mondaysoff",
......@@ -529,24 +571,19 @@ let weekPlannerView = {
let weekTemplate = document.getElementById("week-template");
let week = weekTemplate.content.firstElementChild.cloneNode(true);
week.item = startOfWeek.clone();
// Set up the week number title
let weekInfo = cal.getWeekInfoService();
let weekno = weekInfo.getWeekTitle(startOfWeek);
let weekTitle = cal.l10n.getCalString("WeekTitle", [weekno]);
week.rows[0].cells[0].firstElementChild.textContent = weekTitle;
week.number = weekInfoService.getWeekTitle(monday);
week.querySelector(".week-title").textContent = cal.l10n.getCalString("WeekTitle", [
week.number,
]);
// Set up the day boxes
let defaultTimezone = cal.dtz.defaultTimezone;
let currentDate = startOfWeek.clone();
let currentDate = monday.clone();
for (let i = 0; i < 7; i++) {
let day = week.rows[1].cells[i];
let titleNode = day.querySelector(".day-title");
titleNode.textContent = cal.dtz.formatter.formatDateLong(
currentDate.getInTimezone(defaultTimezone)
);
titleNode.textContent = cal.dtz.formatter.formatDateLong(currentDate);
this.dayTable[currentDate.icalString] = day.querySelector(".items");
......
......@@ -905,7 +905,10 @@ function deletePrefBranch(id) {
*/
function maybeRefreshCalendar(calendar) {
if (!calendar.getProperty("disabled") && calendar.canRefresh) {
calendar.refresh();
let refreshInterval = calendar.getProperty("refreshInterval");
if (refreshInterval != "0") {
calendar.refresh();
}
}
}
......
......@@ -481,3 +481,6 @@ pref.timezone.Asia.Qostanay=Asia/Qostanay
#added with 2.2020a
pref.timezone.America.Nuuk=America/Nuuk
#added with 2.2021c
pref.timezone.Pacific.Kanton=Pacific/Kanton
......@@ -35,7 +35,7 @@ function CalDavCalendar() {
this.unmappedProperties = [];
this.mUriParams = null;
this.mItemInfoCache = {};
this.mDisabled = false;
this.mDisabledByDavError = false;
this.mCalHomeSet = null;
this.mInboxUrl = null;
this.mOutboxUrl = null;
......@@ -360,7 +360,7 @@ CalDavCalendar.prototype = {
return "caldav";
},
mDisabled: true,
mDisabledByDavError: true,
mCalendarUserAddress: null,
get calendarUserAddress() {
......@@ -1283,7 +1283,7 @@ CalDavCalendar.prototype = {
cal.LOG(`CalDAV: Disabling calendar ${this.name} due to 404`);
notifyListener(Cr.NS_ERROR_FAILURE);
return;
} else if (response.ok && this.mDisabled) {
} else if (response.ok && this.mDisabledByDavError) {
// Looks like the calendar is there again, check its resource
// type first.
this.checkDavResourceType(aChangeLogListener);
......@@ -1354,7 +1354,7 @@ CalDavCalendar.prototype = {
* calendars.
*/
getUpdatedItems(aUri, aChangeLogListener) {
if (this.mDisabled) {
if (this.mDisabledByDavError) {
// check if maybe our calendar has become available
this.checkDavResourceType(aChangeLogListener);
return;
......@@ -1620,13 +1620,14 @@ CalDavCalendar.prototype = {
let resourceType = response.firstProps["D:resourcetype"] || new Set();
if (resourceType.has("C:calendar")) {
// This is a valid calendar resource
if (this.mDisabled) {
this.mDisabled = false;
this.mReadOnly = false;
if (this.mDisabledByDavError) {
this.mDisabledByDavError = false;
}
let privs = response.firstProps["D:current-user-privilege-set"];
if (privs && privs instanceof Set) {
// Don't clear this.readOnly, only set it. The user may have write
// privileges but not want to use them.
if (!this.readOnly && privs && privs instanceof Set) {
this.readOnly = ![
"D:write",
"D:write-content",
......@@ -1968,8 +1969,7 @@ CalDavCalendar.prototype = {
return;
}
localizedMessage = cal.l10n.getCalString(message, [this.mUri.spec]);
this.mReadOnly = true;
this.mDisabled = true;
this.mDisabledByDavError = true;
this.notifyError(aErrNo, localizedMessage);
this.notifyError(
modificationError ? Ci.calIErrors.MODIFICATION_FAILED : Ci.calIErrors.READ_FAILED,
......
......@@ -385,7 +385,7 @@ class CalDavWebDavSyncHandler extends XMLResponseHandler {
QueryInterface = ChromeUtils.generateQI(["nsIRequestObserver", "nsIStreamListener"]);
async doWebDAVSync() {
if (this.calendar.mDisabled) {
if (this.calendar.mDisabledByDavError) {
// check if maybe our calendar has become available
this.calendar.checkDavResourceType(this.changeLogListener);
return;
......@@ -763,7 +763,7 @@ class CalDavMultigetSyncHandler extends XMLResponseHandler {
QueryInterface = ChromeUtils.generateQI(["nsIRequestObserver", "nsIStreamListener"]);
doMultiGet() {
if (this.calendar.mDisabled) {
if (this.calendar.mDisabledByDavError) {
// check if maybe our calendar has become available
this.calendar.checkDavResourceType(this.changeLogListener);
return;
......
......@@ -289,7 +289,12 @@ class ICSDetector {
// The content type header may include a charset, so use 'string.includes'.
if (response.ok) {
let header = response.getHeader("Content-Type");
if (header.includes("text/calendar") || header.includes("application/ics")) {
if (
header.includes("text/calendar") ||
header.includes("application/ics") ||
(response.text && response.text.includes("BEGIN:VCALENDAR"))
) {
let target = response.uri;
cal.LOG(`[calICSProvider] ${target.spec} has valid content type (via ${method} request)`);
return [this.handleCalendar(target)];
......
......@@ -2249,7 +2249,7 @@ CalStorageCalendar.prototype = {
// put Google's HTML description in the right place
//
fixGoogleCalendarDescriptionIfNeeded(item) {
if (item.id.endsWith("@google.com")) {
if (item.id && item.id.endsWith("@google.com")) {
let description = item.getProperty("DESCRIPTION");
if (description) {
let altrep = item.getPropertyParameter("DESCRIPTION", "ALTREP");
......
{
"version": "2.2020d",
"version": "2.2021a",
"aliases": [
"AUS Central Standard Time",
"AUS Eastern Standard Time",
......@@ -29,6 +29,7 @@
"Atlantic/Faeroe",
"Atlantic/Jan_Mayen",
"Aus Central W. Standard Time",
"Australia/Currie",
"Azerbaijan Standard Time",
"Azores Standard Time",
"Bahia Standard Time",
......@@ -482,7 +483,6 @@
"Australia/Adelaide",
"Australia/Brisbane",
"Australia/Broken_Hill",
"Australia/Currie",
"Australia/Darwin",
"Australia/Eucla",
"Australia/Hobart",
......
{
"version": "2.2021a",
"version": "2.2021c",
"aliases": {
"AUS Central Standard Time": {
"aliasTo": "Australia/Darwin"
......@@ -346,6 +346,9 @@
"Pacific Standard Time (Mexico)": {
"aliasTo": "America/Santa_Isabel"
},
"Pacific/Enderbury": {
"aliasTo": "Pacific/Kanton"
},
"Pacific/Johnston": {
"aliasTo": "Pacific/Honolulu"
},
......@@ -2107,8 +2110,9 @@
},
"Asia/Amman": {
"ics": [
"BEGIN:DAYLIGHT\r\nTZOFFSETFROM:+0200\r\nTZOFFSETTO:+0300\r\nTZNAME:EEST\r\nDTSTART:19700326T235959\r\nRRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=-1TH\r\nEND:DAYLIGHT",
"BEGIN:STANDARD\r\nTZOFFSETFROM:+0300\r\nTZOFFSETTO:+0200\r\nTZNAME:EET\r\nDTSTART:19701030T010000\r\nRRULE:FREQ=YEARLY;BYMONTH=10;BYDAY=-1FR\r\nEND:STANDARD"
"BEGIN:STANDARD\r\nTZOFFSETFROM:+0300\r\nTZOFFSETTO:+0200\r\nTZNAME:EET\r\nDTSTART:19701030T010000\r\nRRULE:FREQ=YEARLY;BYMONTH=10;BYDAY=-1FR\r\nEND:STANDARD",
"BEGIN:DAYLIGHT\r\nTZOFFSETFROM:+0300\r\nTZOFFSETTO:+0300\r\nTZNAME:EEST\r\nDTSTART:20220224T235959\r\nRRULE:FREQ=YEARLY;BYMONTH=2;BYDAY=-1TH\r\nEND:DAYLIGHT",
"BEGIN:DAYLIGHT\r\nTZOFFSETFROM:+0200\r\nTZOFFSETTO:+0300\r\nTZNAME:EEST\r\nDTSTART:20180329T235959\r\nRDATE:20180329T235959\r\nRDATE:20190328T235959\r\nRDATE:20200326T235959\r\nRDATE:20210325T235959\r\nEND:DAYLIGHT"
],
"latitude": "+0315700",
"longitude": "+0355600"
......@@ -3409,8 +3413,8 @@
},
"Pacific/Apia": {
"ics": [
"BEGIN:STANDARD\r\nTZOFFSETFROM:+1400\r\nTZOFFSETTO:+1300\r\nTZNAME:+13\r\nDTSTART:19700405T040000\r\nRRULE:FREQ=YEARLY;BYMONTH=4;BYDAY=1SU\r\nEND:STANDARD",
"BEGIN:DAYLIGHT\r\nTZOFFSETFROM:+1300\r\nTZOFFSETTO:+1400\r\nTZNAME:+14\r\nDTSTART:19700927T030000\r\nRRULE:FREQ=YEARLY;BYMONTH=9;BYDAY=-1SU\r\nEND:DAYLIGHT"
"BEGIN:STANDARD\r\nTZOFFSETFROM:+1400\r\nTZOFFSETTO:+1300\r\nTZNAME:+13\r\nDTSTART:20180401T040000\r\nRDATE:20180401T040000\r\nRDATE:20190407T040000\r\nRDATE:20200405T040000\r\nRDATE:20210404T040000\r\nEND:STANDARD",
"BEGIN:DAYLIGHT\r\nTZOFFSETFROM:+1300\r\nTZOFFSETTO:+1400\r\nTZNAME:+14\r\nDTSTART:19700101T000000\r\nRDATE:19700101T000000\r\nRDATE:20180930T030000\r\nRDATE:20190929T030000\r\nRDATE:20200927T030000\r\nEND:DAYLIGHT"
],
"latitude": "-0135000",
"longitude": "-1714400"
......@@ -3462,13 +3466,6 @@
"latitude": "-0174000",
"longitude": "+1682500"
},
"Pacific/Enderbury": {
"ics": [
"BEGIN:STANDARD\r\nTZOFFSETFROM:+1300\r\nTZOFFSETTO:+1300\r\nTZNAME:+13\r\nDTSTART:19700101T000000\r\nEND:STANDARD"
],
"latitude": "-0030800",
"longitude": "-1710500"
},
"Pacific/Fakaofo": {
"ics": [
"BEGIN:STANDARD\r\nTZOFFSETFROM:+1300\r\nTZOFFSETTO:+1300\r\nTZNAME:+13\r\nDTSTART:19700101T000000\r\nEND:STANDARD"
......@@ -3527,6 +3524,13 @@
"latitude": "+0211825",
"longitude": "-1575130"
},
"Pacific/Kanton": {
"ics": [
"BEGIN:STANDARD\r\nTZOFFSETFROM:+1300\r\nTZOFFSETTO:+1300\r\nTZNAME:+13\r\nDTSTART:19700101T000000\r\nEND:STANDARD"
],
"latitude": "-0024700",
"longitude": "-1714300"
},
"Pacific/Kiritimati": {
"ics": [
"BEGIN:STANDARD\r\nTZOFFSETFROM:+1400\r\nTZOFFSETTO:+1400\r\nTZNAME:+14\r\nDTSTART:19700101T000000\r\nEND:STANDARD"
......@@ -3667,8 +3671,8 @@
"ics": [
"BEGIN:STANDARD\r\nTZOFFSETFROM:+1300\r\nTZOFFSETTO:+1300\r\nTZNAME:+13\r\nDTSTART:19700101T000000\r\nEND:STANDARD"
],
"latitude": "-0211000",