Bug 1791220 - Use cal.dtz for formatting the duration of an invitation. r=aleca,leftmostcat

Differential Revision: https://phabricator.services.mozilla.com/D157570

--HG--
extra : absorb_source : 3ee4dfc3613b454bad4d628a6af8248c62fd54ad
This commit is contained in:
Lasana Murray 2022-10-05 21:49:45 +13:00
Родитель 8c1e764a0d
Коммит 0bcca834d4
6 изменённых файлов: 404 добавлений и 186 удалений

Просмотреть файл

@ -8,21 +8,6 @@
// Wrap in a block to prevent leaking to window scope.
{
const dateFormat = new Intl.DateTimeFormat(undefined, {
month: "long",
day: "2-digit",
year: "numeric",
});
const dayFormat = new Intl.DateTimeFormat(undefined, { weekday: "long" });
const timeFormat = new Intl.DateTimeFormat(undefined, {
timeStyle: "short",
});
const fmtDate = date => dateFormat.format(date);
const fmtDay = date => dayFormat.format(date);
const fmtTime = date => timeFormat.format(date);
/**
* Base element providing boilerplate for shadow root initialisation.
*/
@ -164,17 +149,8 @@
* @type {calIEvent}
*/
set item(item) {
let when = this.shadowRoot.getElementById("when");
let startDatetime = document.createElement("calendar-invitation-datetime");
startDatetime.datetime = item.startDate;
when.appendChild(startDatetime);
if (item.endDate) {
let endDateTime = document.createElement("calendar-invitation-datetime");
endDateTime.datetime = item.endDate;
when.appendChild(endDateTime);
}
let interval = this.shadowRoot.getElementById("interval");
interval.item = item;
this.shadowRoot
.getElementById("location")
@ -195,39 +171,30 @@
customElements.define("calendar-invitation-panel-properties", InvitationPanelProperties);
/**
* InvitationDatetime displays the formatted date and time of the event in the
* format: "Tuesday, February 24, 2022" using the Intl.DateTimeFormat API.
* InvitationInterval displays the formatted interval of the event. Formatting
* relies on cal.dtz.formatter.formatIntervalParts().
*/
class InvitationDatetime extends BaseInvitationElement {
class InvitationInterval extends BaseInvitationElement {
constructor() {
super("calendarInvitationDatetime");
super("calendarInvitationInterval");
}
/**
* Set to display a date and time.
* @type {calIDateTIme}
* The item whose interval to show.
* @type {calIEvent}
*/
set datetime(datetime) {
let date = cal.dtz.dateTimeToJsDate(datetime);
set item(value) {
let [startDate, endDate] = cal.dtz.formatter.getItemDates(value);
let timezone = startDate.timezone.displayName;
let parts = cal.dtz.formatter.formatIntervalParts(startDate, endDate);
document.l10n.setAttributes(
this.shadowRoot.getElementById("date"),
"calendar-invitation-datetime-date",
{ dayOfWeek: fmtDay(date), date: fmtDate(date) }
this.shadowRoot.getElementById("interval"),
`calendar-invitation-interval-${parts.type}`,
{ ...parts, timezone }
);
document.l10n.setAttributes(
this.shadowRoot.getElementById("time"),
"calendar-invitation-datetime-time",
{ time: fmtTime(date), timezone: datetime.timezone.displayName }
);
}
disconnectedCallback() {
document.l10n.disconnectRoot(this.shadowRoot);
}
}
customElements.define("calendar-invitation-datetime", InvitationDatetime);
customElements.define("calendar-invitation-interval", InvitationInterval);
const partStatOrder = ["ACCEPTED", "DECLINED", "TENTATIVE", "NEEDS-ACTION"];

Просмотреть файл

@ -113,7 +113,9 @@
<tbody>
<tr>
<th data-l10n-id="calendar-invitation-panel-prop-title-when"></th>
<td id="when" class="calendar-invitation-when"></td>
<td id="when" class="calendar-invitation-when">
<calendar-invitation-interval id="interval"/>
</td>
</tr>
<tr>
<th data-l10n-id="calendar-invitation-panel-prop-title-location"></th>
@ -134,15 +136,9 @@
</table>
</template>
<!-- Template for <calendar-invitation-datetime/> -->
<template id="calendarInvitationDatetime" xmlns="http://www.w3.org/1999/xhtml">
<div id="datetime" class="calendar-invitation-datetime">
<div id="date"
class="calendar-invitation-datetime-date"></div>
<div id="time"
class="calendar-invitation-datetime-time"
data-l10n-id="calendar-invitation-datetime-time"></div>
</div>
<!-- Template for <calendar-invitation-interval/> -->
<template id="calendarInvitationInterval" xmlns="http://www.w3.org/1999/xhtml">
<div id="interval"></div>
</template>
<!-- Template for <calendar-invitation-partstat-summary/> -->

Просмотреть файл

@ -175,99 +175,221 @@ var formatter = {
},
/**
* Format a date/time interval. The returned string may assume that the dates are so close to each
* other, that it can leave out some parts of the part string denoting the end date.
* Format a date/time interval to a string. The returned string may assume
* that the dates are so close to each other, that it can leave out some parts
* of the part string denoting the end date.
*
* @param {calIDateTime} aStartDate The start of the interval.
* @param {calIDateTime} aEndDate The end of the interval.
* @return {string} A string describing the interval in a legible form.
* @param {calIDateTime} startDate - The start of the interval.
* @param {calIDateTime} endDate - The end of the interval.
* @return {string} - A string describing the interval in a legible form.
*/
formatInterval(aStartDate, aEndDate) {
// Check for tasks without start and/or due date
if (aEndDate == null && aStartDate == null) {
return lazy.cal.l10n.getCalString("datetimeIntervalTaskWithoutDate");
} else if (aEndDate == null) {
let startDateString = this.formatDate(aStartDate);
let startTime = this.formatTime(aStartDate);
return lazy.cal.l10n.getCalString("datetimeIntervalTaskWithoutDueDate", [
startDateString,
startTime,
]);
} else if (aStartDate == null) {
let endDateString = this.formatDate(aEndDate);
let endTime = this.formatTime(aEndDate);
return lazy.cal.l10n.getCalString("datetimeIntervalTaskWithoutStartDate", [
endDateString,
endTime,
]);
formatInterval(startDate, endDate) {
let format = this.formatIntervalParts(startDate, endDate);
switch (format.type) {
case "task-without-dates":
return lazy.cal.l10n.getCalString("datetimeIntervalTaskWithoutDate");
case "task-without-due-date":
return lazy.cal.l10n.getCalString("datetimeIntervalTaskWithoutDueDate", [
format.startDate,
format.startTime,
]);
case "task-without-start-date":
return lazy.cal.l10n.getCalString("datetimeIntervalTaskWithoutStartDate", [
format.endDate,
format.endTime,
]);
case "all-day":
return format.startDate;
case "all-day-between-years":
return lazy.cal.l10n.getCalString("daysIntervalBetweenYears", [
format.startMonth,
format.startDay,
format.startYear,
format.endMonth,
format.endDay,
format.endYear,
]);
case "all-day-in-month":
return lazy.cal.l10n.getCalString("daysIntervalInMonth", [
format.month,
format.startDay,
format.endDay,
format.year,
]);
case "all-day-between-months":
return lazy.cal.l10n.getCalString("daysIntervalBetweenMonths", [
format.startMonth,
format.startDay,
format.endMonth,
format.endDay,
format.year,
]);
case "same-date-time":
return lazy.cal.l10n.getCalString("datetimeIntervalOnSameDateTime", [
format.startDate,
format.startTime,
]);
case "same-day":
return lazy.cal.l10n.getCalString("datetimeIntervalOnSameDay", [
format.startDate,
format.startTime,
format.endTime,
]);
case "several-days":
return lazy.cal.l10n.getCalString("datetimeIntervalOnSeveralDays", [
format.startDate,
format.startTime,
format.endDate,
format.endTime,
]);
default:
return "";
}
},
/**
* Object used to describe the parts of a formatted interval.
* @typedef {object} IntervalParts
* @property {string} type
* Used to distinguish IntervalPart results.
* @property {string?} startDate
* The full date of the start of the interval.
* @property {string?} startTime
* The time part of the start of the interval.
* @property {string?} startDay
* The day (of the month) the interval starts on.
* @property {string?} startMonth
* The month the interval starts on.
* @property {string?} startYear
* The year interval starts on.
* @property {string?} endDate
* The full date of the end of the interval.
* @property {string?} endTime
* The time part of the end of the interval.
* @property {string?} endDay
* The day (of the month) the interval ends on.
* @property {string?} endMonth
* The month the interval ends on.
* @property {string?} endYear
* The year interval ends on.
* @property {string?} month
* The month the interval occurs in when the start is all day and the
* interval does not span multiple months.
* @property {string?} year
* The year the interval occurs in when the the start is all day and the
* interval does not span multiple years.
*/
/**
* Format a date interval into various parts suitable for building
* strings that describe the interval. This result may leave out some parts of
* either date based on the closeness of the two.
*
* @param {calIDateTime} startDate The start of the interval.
* @param {calIDateTime} endDate The end of the interval.
* @return {IntervalParts} An object to be used to create an
* interval string.
*/
formatIntervalParts(startDate, endDate) {
if (endDate == null && startDate == null) {
return { type: "task-without-dates" };
}
if (endDate == null) {
return {
type: "task-without-due-date",
startDate: this.formatDate(startDate),
startTime: this.formatTime(startDate),
};
}
if (startDate == null) {
return {
type: "task-without-start-date",
endDate: this.formatDate(endDate),
endTime: this.formatTime(endDate),
};
}
// Here there are only events or tasks with both start and due date.
// make sure start and end use the same timezone when formatting intervals:
let endDate = aEndDate.getInTimezone(aStartDate.timezone);
let testdate = aStartDate.clone();
let testdate = startDate.clone();
testdate.isDate = true;
let originalEndDate = endDate.clone();
endDate = endDate.getInTimezone(startDate.timezone);
let sameDay = testdate.compare(endDate) == 0;
if (aStartDate.isDate) {
if (startDate.isDate) {
// All-day interval, so we should leave out the time part
if (sameDay) {
return this.formatDateLong(aStartDate);
return {
type: "all-day",
startDate: this.formatDateLong(startDate),
};
}
let startDay = this.formatDayWithOrdinal(aStartDate.day);
let startYear = aStartDate.year;
let startDay = this.formatDayWithOrdinal(startDate.day);
let startYear = String(startDate.year);
let endDay = this.formatDayWithOrdinal(endDate.day);
let endYear = endDate.year;
if (aStartDate.year != endDate.year) {
let startMonthName = lazy.cal.l10n.formatMonth(
aStartDate.month + 1,
"calendar",
"daysIntervalBetweenYears"
);
let endMonthName = lazy.cal.l10n.formatMonth(
aEndDate.month + 1,
"calendar",
"daysIntervalBetweenYears"
);
return lazy.cal.l10n.getCalString("daysIntervalBetweenYears", [
startMonthName,
let endYear = String(endDate.year);
if (startDate.year != endDate.year) {
return {
type: "all-day-between-years",
startDay,
startMonth: lazy.cal.l10n.formatMonth(
startDate.month + 1,
"calendar",
"daysIntervalBetweenYears"
),
startYear,
endMonthName,
endDay,
endMonth: lazy.cal.l10n.formatMonth(
originalEndDate.month + 1,
"calendar",
"daysIntervalBetweenYears"
),
endYear,
]);
} else if (aStartDate.month == endDate.month) {
let startMonthName = lazy.cal.l10n.formatMonth(
aStartDate.month + 1,
"calendar",
"daysIntervalInMonth"
);
return lazy.cal.l10n.getCalString("daysIntervalInMonth", [
startMonthName,
startDay,
endDay,
endYear,
]);
};
}
let startMonthName = lazy.cal.l10n.formatMonth(
aStartDate.month + 1,
"calendar",
"daysIntervalBetweenMonths"
);
let endMonthName = lazy.cal.l10n.formatMonth(
aEndDate.month + 1,
"calendar",
"daysIntervalBetweenMonths"
);
return lazy.cal.l10n.getCalString("daysIntervalBetweenMonths", [
startMonthName,
if (startDate.month == endDate.month) {
return {
type: "all-day-in-month",
startDay,
month: lazy.cal.l10n.formatMonth(startDate.month + 1, "calendar", "daysIntervalInMonth"),
endDay,
year: endYear,
};
}
return {
type: "all-day-between-months",
startDay,
endMonthName,
startMonth: lazy.cal.l10n.formatMonth(
startDate.month + 1,
"calendar",
"daysIntervalBetweenMonths"
),
endDay,
endYear,
]);
endMonth: lazy.cal.l10n.formatMonth(
originalEndDate.month + 1,
"calendar",
"daysIntervalBetweenMonths"
),
year: endYear,
};
}
let startDateString = this.formatDate(aStartDate);
let startTime = this.formatTime(aStartDate);
let startDateString = this.formatDate(startDate);
let startTime = this.formatTime(startDate);
let endDateString = this.formatDate(endDate);
let endTime = this.formatTime(endDate);
// non-allday, so need to return date and time
@ -276,28 +398,32 @@ var formatter = {
if (startTime == endTime) {
// End time is on the same time as start, so we can leave out the end time
// "5 Jan 2006 13:00"
return lazy.cal.l10n.getCalString("datetimeIntervalOnSameDateTime", [
startDateString,
return {
type: "same-date-time",
startDate: startDateString,
startTime,
]);
};
}
// still include end time
// "5 Jan 2006 13:00 - 17:00"
return lazy.cal.l10n.getCalString("datetimeIntervalOnSameDay", [
startDateString,
return {
type: "same-day",
startDate: startDateString,
startTime,
endTime,
]);
};
}
// Spanning multiple days, so need to include date and time
// for start and end
// "5 Jan 2006 13:00 - 7 Jan 2006 9:00"
return lazy.cal.l10n.getCalString("datetimeIntervalOnSeveralDays", [
startDateString,
return {
type: "several-days",
startDate: startDateString,
startTime,
endDateString,
endDate: endDateString,
endTime,
]);
};
},
/**
@ -314,6 +440,32 @@ var formatter = {
return aDay + dayOrdinalSymbol;
},
/**
* Helper to get the start/end dates for a given item.
*
* @param {calIItemBase} item - The item to get the dates for.
* @return {[calIDateTime, calIDateTime]} An array with start and end date.
*/
getItemDates(item) {
let start = item[lazy.cal.dtz.startDateProp(item)];
let end = item[lazy.cal.dtz.endDateProp(item)];
let kDefaultTimezone = lazy.cal.dtz.defaultTimezone;
// Check for tasks without start and/or due date
if (start) {
start = start.getInTimezone(kDefaultTimezone);
}
if (end) {
end = end.getInTimezone(kDefaultTimezone);
}
// EndDate is exclusive. For all-day events, we need to subtract one day,
// to get into a format that's understandable.
if (start && start.isDate && end) {
end.day -= 1;
}
return [start, end];
},
/**
* Format an interval that is defined by an item with the default timezone.
*
@ -321,7 +473,7 @@ var formatter = {
* @return {string} The formatted item interval.
*/
formatItemInterval(aItem) {
return this.formatInterval(...getItemDates(aItem));
return this.formatInterval(...this.getItemDates(aItem));
},
/**
@ -331,7 +483,7 @@ var formatter = {
* @return {string} The formatted item interval.
*/
formatItemTimeInterval(aItem) {
return this.formatTimeInterval(...getItemDates(aItem));
return this.formatTimeInterval(...this.getItemDates(aItem));
},
/**
@ -448,29 +600,3 @@ function inTimezone(date, formatOptions) {
lazy.cal.dtz.dateTimeToJsDate(date)
);
}
/**
* Helper to get the start/end dates for a given item.
*
* @param {calIItemBase} aItem The item to get the dates for.
* @return {[calIDateTime, calIDateTime]} An array with start and end date.
*/
function getItemDates(aItem) {
let start = aItem[lazy.cal.dtz.startDateProp(aItem)];
let end = aItem[lazy.cal.dtz.endDateProp(aItem)];
let kDefaultTimezone = lazy.cal.dtz.defaultTimezone;
// Check for tasks without start and/or due date
if (start) {
start = start.getInTimezone(kDefaultTimezone);
}
if (end) {
end = end.getInTimezone(kDefaultTimezone);
}
// EndDate is exclusive. For all-day events, we need to subtract one day,
// to get into a format that's understandable.
if (start && start.isDate && end) {
end.day -= 1;
}
return [start, end];
}

Просмотреть файл

@ -96,14 +96,6 @@
flex-direction: column;
}
calendar-invitation-datetime + calendar-invitation-datetime {
margin-top: 0.5em;
}
.calendar-invitation-datetime-time {
font-weight: 800;
}
.calendar-invitation-panel-partstat-breakdown:before {
content: "("
}

Просмотреть файл

@ -38,15 +38,54 @@ calendar-invitation-panel-prop-title-when = When:
calendar-invitation-panel-prop-title-location = Location:
# Example: Friday, September 16, 2022
# Variables:
# $dayOfWeek (String) - The day of the week for a given date.
# $date (String) - The date example: Tuesday, February 24, 2022.
calendar-invitation-datetime-date = { $dayOfWeek }, { $date }
# $startDate (String) - The date (without time) the event starts on.
calendar-invitation-interval-all-day = { $startDate }
# Variables:
# $time (String) - The time part of a datetime using the "short" timeStyle.
# $timezone (String) - The timezone info for the datetime.
calendar-invitation-datetime-time = { $time } ({ $timezone })
# Example: September 16, 2022 – September 16, 2023
# $startMonth (String) - The month the interval starts.
# $startDay (String) - The day of the month the interval starts.
# $startYear (String) - The year the interval starts.
# $endMonth (String) - The month the interval ends.
# $endDay (String) - The day of the month the interval ends.
# $endYear (String) - The year the interval ends.
calendar-invitation-interval-all-day-between-years = { $startMonth } { $startDay }, { $startYear } – { $endMonth } { $endDay }, { $endYear }
# Example: September 16 – 20, 2022
# $month (String) - The month the interval is in.
# $startDay (String) - The day of the month the interval starts.
# $endDay (String) - The day of the month the interval ends.
# $year (String) - The year the interval is in.
calendar-invitation-interval-all-day-in-month = { $month } { $startDay } – { $endDay }, { $year }
# Example: September 16 – October 20, 2022
# $startMonth (String) - The month the interval starts.
# $startDay (String) - The day of the month the interval starts.
# $endDay (String) - The day of the month the interval ends.
# $year (String) - The year the interval is in.
calendar-invitation-interval-all-day-between-months = { $startMonth } { $startDay } – { $endMonth } { $endDay }, { $year }
# Example: Friday, September 16, 2022 15:00 America/Port of Spain
# $startDate (String) - The date the interval starts.
# $startTime (String) - The time the interval starts.
# $timezone (String) - The timezone the interval is in.
calendar-invitation-interval-same-date-time = { $startDate } <b>{ $startTime }</b> { $timezone }
# Example: Friday, September 16, 2022 14:00 – 16:00 America/Port of Spain
# $startDate (String) - The date the interval starts.
# $startTime (String) - The time the interval starts.
# $endTime (String) - The time the interval ends.
# $timezone (String) - The timezone the interval is in.
calendar-invitation-interval-same-day = { $startDate } <b>{ $startTime }</b> – <b>{ $endTime }</b> { $timezone }
# Example: Friday, September 16, 2022 14:00 – Tuesday, September 20, 2022 16:00 America/Port of Spain
# $startDate (String) - The date the interval starts.
# $startTime (String) - The time the interval starts.
# $startDate (String) - The date the interval ends.
# $endTime (String) - The time the interval ends.
# $timezone (String) - The timezone the interval is in.
calendar-invitation-interval-several-days = { $startDate } <b>{ $startTime }</b> – { $endDate } <b>{ $endTime }</b> { $timezone }
calendar-invitation-panel-prop-title-attendees = Attendees:

Просмотреть файл

@ -461,3 +461,101 @@ add_task(async function formatTime_test() {
// let's reset the preferences
Services.prefs.setStringPref("calendar.timezone.local", tzlocal);
});
add_task(async function formatInterval_test() {
let data = [
//1: task-without-dates
{
input: {},
expected: "no start or due date",
},
//2: task-without-due-date
{
input: { start: "20220916T140000Z" },
expected: "start date Friday, September 16, 2022 14:00",
},
//3: task-without-start-date
{
input: { end: "20220916T140000Z" },
expected: "due date Friday, September 16, 2022 14:00",
},
//4: all-day
{
input: {
start: "20220916T140000Z",
end: "20220916T140000Z",
allDay: true,
},
expected: "Friday, September 16, 2022",
},
//5: all-day-between-years
{
input: {
start: "20220916T140000Z",
end: "20230916T140000Z",
allDay: true,
},
expected: "September 16, 2022 – September 16, 2023",
},
//6: all-day-in-month
{
input: {
start: "20220916T140000Z",
end: "20220920T140000Z",
allDay: true,
},
expected: "September 16 – 20, 2022",
},
//7: all-day-between-months
{
input: {
start: "20220916T140000Z",
end: "20221020T140000Z",
allDay: true,
},
expected: "September 16 – October 20, 2022",
},
//8: same-date-time
{
input: {
start: "20220916T140000Z",
end: "20220916T140000Z",
},
expected: "Friday, September 16, 2022 14:00",
},
//9: same-day
{
input: {
start: "20220916T140000Z",
end: "20220916T160000Z",
},
expected: "Friday, September 16, 2022 14:00 – 16:00",
},
//10: several-days
{
input: {
start: "20220916T140000Z",
end: "20220920T160000Z",
},
expected: "Friday, September 16, 2022 14:00 – Tuesday, September 20, 2022 16:00",
},
];
let i = 0;
for (let test of data) {
i++;
let startDate = test.input.start ? cal.createDateTime(test.input.start) : null;
let endDate = test.input.end ? cal.createDateTime(test.input.end) : null;
if (test.input.allDay) {
startDate.isDate = true;
}
let formatted = formatter.formatInterval(startDate, endDate);
equal(
test.expected,
formatted,
"(test #" + i + ": result '" + formatted + "', expected '" + test.expected + "')"
);
}
});