mirror of
https://github.com/discourse/discourse.git
synced 2025-04-17 06:19:04 +08:00
REFACTOR: local dates to improve reliability with DST and recurrence (#9379)
This commit improves testing and separates local-date generation from dates with zone manipulations.
This commit is contained in:
parent
b824898f61
commit
25f95af418
@ -1,357 +0,0 @@
|
|||||||
// discourse-skip-module
|
|
||||||
(function($) {
|
|
||||||
const DATE_TEMPLATE = `
|
|
||||||
<span>
|
|
||||||
<svg class="fa d-icon d-icon-globe-americas svg-icon" xmlns="http://www.w3.org/2000/svg">
|
|
||||||
<use xlink:href="#globe-americas"></use>
|
|
||||||
</svg>
|
|
||||||
<span class="relative-time"></span>
|
|
||||||
</span>
|
|
||||||
`;
|
|
||||||
|
|
||||||
const PREVIEW_TEMPLATE = `
|
|
||||||
<div class='preview'>
|
|
||||||
<span class='timezone'></span>
|
|
||||||
<span class='date-time'></span>
|
|
||||||
</div>
|
|
||||||
`;
|
|
||||||
|
|
||||||
function processElement($element, options = {}) {
|
|
||||||
clearTimeout(this.timeout);
|
|
||||||
|
|
||||||
const utc = moment().utc();
|
|
||||||
const dateTime = options.time
|
|
||||||
? `${options.date} ${options.time}`
|
|
||||||
: options.date;
|
|
||||||
|
|
||||||
let displayedTimezone;
|
|
||||||
if (options.time) {
|
|
||||||
displayedTimezone = options.displayedTimezone || moment.tz.guess();
|
|
||||||
} else {
|
|
||||||
displayedTimezone =
|
|
||||||
options.displayedTimezone || options.timezone || moment.tz.guess();
|
|
||||||
}
|
|
||||||
|
|
||||||
// if timezone given we convert date and time from given zone to Etc/UTC
|
|
||||||
let utcDateTime;
|
|
||||||
if (options.timezone) {
|
|
||||||
utcDateTime = _applyZoneToDateTime(dateTime, options.timezone);
|
|
||||||
} else {
|
|
||||||
utcDateTime = moment.utc(dateTime);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (utcDateTime < utc) {
|
|
||||||
// if event is in the past we want to bump it no next occurrence when
|
|
||||||
// recurring is set
|
|
||||||
if (options.recurring) {
|
|
||||||
utcDateTime = _applyRecurrence(utcDateTime, options);
|
|
||||||
} else {
|
|
||||||
$element.addClass("past");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// once we have the correct UTC date we want
|
|
||||||
// we adjust it to watching user timezone
|
|
||||||
const adjustedDateTime = utcDateTime.tz(displayedTimezone);
|
|
||||||
|
|
||||||
const previews = _generatePreviews(
|
|
||||||
adjustedDateTime.clone(),
|
|
||||||
displayedTimezone,
|
|
||||||
options
|
|
||||||
);
|
|
||||||
const textPreview = _generateTextPreview(previews);
|
|
||||||
const htmlPreview = _generateHtmlPreview(previews);
|
|
||||||
|
|
||||||
const formatedDateTime = _applyFormatting(
|
|
||||||
adjustedDateTime,
|
|
||||||
displayedTimezone,
|
|
||||||
options
|
|
||||||
);
|
|
||||||
|
|
||||||
$element
|
|
||||||
.html(DATE_TEMPLATE)
|
|
||||||
.attr("aria-label", textPreview)
|
|
||||||
.attr(
|
|
||||||
"data-html-tooltip",
|
|
||||||
`<div class="locale-dates-previews">${htmlPreview}</div>`
|
|
||||||
)
|
|
||||||
.addClass("cooked-date")
|
|
||||||
.find(".relative-time")
|
|
||||||
.text(formatedDateTime);
|
|
||||||
|
|
||||||
this.timeout = setTimeout(
|
|
||||||
() => processElement($element, options),
|
|
||||||
60 * 1000
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
function _formatTimezone(timezone) {
|
|
||||||
return timezone
|
|
||||||
.replace("_", " ")
|
|
||||||
.replace("Etc/", "")
|
|
||||||
.split("/");
|
|
||||||
}
|
|
||||||
|
|
||||||
function _zoneWithoutPrefix(timezone) {
|
|
||||||
const parts = _formatTimezone(timezone);
|
|
||||||
return parts[1] || parts[0];
|
|
||||||
}
|
|
||||||
|
|
||||||
function _applyZoneToDateTime(dateTime, timezone) {
|
|
||||||
return moment.tz(dateTime, timezone).utc();
|
|
||||||
}
|
|
||||||
|
|
||||||
function _translateCalendarKey(time, key) {
|
|
||||||
const translated = I18n.t(`discourse_local_dates.relative_dates.${key}`, {
|
|
||||||
time: "LT"
|
|
||||||
});
|
|
||||||
|
|
||||||
if (time) {
|
|
||||||
return translated
|
|
||||||
.split("LT")
|
|
||||||
.map(w => `[${w}]`)
|
|
||||||
.join("LT");
|
|
||||||
} else {
|
|
||||||
return `[${translated.replace(" LT", "")}]`;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function _calendarFormats(time) {
|
|
||||||
return {
|
|
||||||
sameDay: _translateCalendarKey(time, "today"),
|
|
||||||
nextDay: _translateCalendarKey(time, "tomorrow"),
|
|
||||||
lastDay: _translateCalendarKey(time, "yesterday"),
|
|
||||||
sameElse: "L"
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
function _isEqualZones(timezoneA, timezoneB) {
|
|
||||||
if ((timezoneA || timezoneB) && (!timezoneA || !timezoneB)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (timezoneA.includes(timezoneB) || timezoneB.includes(timezoneA)) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
moment.tz(timezoneA).utcOffset() === moment.tz(timezoneB).utcOffset()
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
function _applyFormatting(dateTime, displayedTimezone, options) {
|
|
||||||
if (options.countdown) {
|
|
||||||
const diffTime = dateTime.diff(moment());
|
|
||||||
if (diffTime < 0) {
|
|
||||||
return I18n.t("discourse_local_dates.relative_dates.countdown.passed");
|
|
||||||
} else {
|
|
||||||
return moment.duration(diffTime).humanize();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const sameTimezone = _isEqualZones(displayedTimezone, moment.tz.guess());
|
|
||||||
const inCalendarRange = dateTime.isBetween(
|
|
||||||
moment().subtract(2, "days"),
|
|
||||||
moment()
|
|
||||||
.add(1, "days")
|
|
||||||
.endOf("day")
|
|
||||||
);
|
|
||||||
|
|
||||||
if (options.calendar && inCalendarRange) {
|
|
||||||
if (sameTimezone) {
|
|
||||||
if (options.time) {
|
|
||||||
dateTime = dateTime.calendar(null, _calendarFormats(options.time));
|
|
||||||
} else {
|
|
||||||
dateTime = dateTime.calendar(null, _calendarFormats(null));
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
dateTime = dateTime.format(options.format);
|
|
||||||
dateTime = dateTime.replace("TZ", "");
|
|
||||||
dateTime = `${dateTime} (${_zoneWithoutPrefix(displayedTimezone)})`;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (options.time) {
|
|
||||||
dateTime = dateTime.format(options.format);
|
|
||||||
|
|
||||||
if (options.displayedTimezone && !sameTimezone) {
|
|
||||||
dateTime = dateTime.replace("TZ", "");
|
|
||||||
dateTime = `${dateTime} (${_zoneWithoutPrefix(displayedTimezone)})`;
|
|
||||||
} else {
|
|
||||||
dateTime = dateTime.replace(
|
|
||||||
"TZ",
|
|
||||||
_formatTimezone(displayedTimezone).join(": ")
|
|
||||||
);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
dateTime = dateTime.format(options.format);
|
|
||||||
|
|
||||||
if (!sameTimezone) {
|
|
||||||
dateTime = dateTime.replace("TZ", "");
|
|
||||||
dateTime = `${dateTime} (${_zoneWithoutPrefix(displayedTimezone)})`;
|
|
||||||
} else {
|
|
||||||
dateTime = dateTime.replace(
|
|
||||||
"TZ",
|
|
||||||
_zoneWithoutPrefix(displayedTimezone)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return dateTime;
|
|
||||||
}
|
|
||||||
|
|
||||||
function _applyRecurrence(dateTime, { recurring, timezone }) {
|
|
||||||
const parts = recurring.split(".");
|
|
||||||
const count = parseInt(parts[0], 10);
|
|
||||||
const type = parts[1];
|
|
||||||
const diff = moment().diff(dateTime, type);
|
|
||||||
const add = Math.ceil(diff + count);
|
|
||||||
|
|
||||||
// we create new moment object from format
|
|
||||||
// to ensure it's created in user context
|
|
||||||
const wasDST = moment(dateTime.format()).isDST();
|
|
||||||
let dateTimeWithRecurrence = moment(dateTime).add(add, type);
|
|
||||||
const isDST = moment(dateTimeWithRecurrence.format()).isDST();
|
|
||||||
|
|
||||||
// these dates are more or less DST "certain"
|
|
||||||
const noDSTOffset = moment
|
|
||||||
.tz({ month: 0, day: 1 }, timezone || "Etc/UTC")
|
|
||||||
.utcOffset();
|
|
||||||
const withDSTOffset = moment
|
|
||||||
.tz({ month: 5, day: 1 }, timezone || "Etc/UTC")
|
|
||||||
.utcOffset();
|
|
||||||
|
|
||||||
// we remove the DST offset present when the date was created,
|
|
||||||
// and add current DST offset
|
|
||||||
if (!wasDST && isDST) {
|
|
||||||
dateTimeWithRecurrence.add(-withDSTOffset + noDSTOffset, "minutes");
|
|
||||||
}
|
|
||||||
|
|
||||||
// we add the DST offset present when the date was created,
|
|
||||||
// and remove current DST offset
|
|
||||||
if (wasDST && !isDST) {
|
|
||||||
dateTimeWithRecurrence.add(withDSTOffset - noDSTOffset, "minutes");
|
|
||||||
}
|
|
||||||
|
|
||||||
return dateTimeWithRecurrence;
|
|
||||||
}
|
|
||||||
|
|
||||||
function _createDateTimeRange(dateTime, timezone) {
|
|
||||||
const dt = moment(dateTime).tz(timezone);
|
|
||||||
|
|
||||||
return [dt.format("LLL"), "→", dt.add(24, "hours").format("LLL")].join(" ");
|
|
||||||
}
|
|
||||||
|
|
||||||
function _generatePreviews(dateTime, displayedTimezone, options) {
|
|
||||||
const previewedTimezones = [];
|
|
||||||
const watchingUserTimezone = moment.tz.guess();
|
|
||||||
const timezones = options.timezones.filter(
|
|
||||||
timezone =>
|
|
||||||
!_isEqualZones(timezone, watchingUserTimezone) &&
|
|
||||||
!_isEqualZones(timezone, options.timezone)
|
|
||||||
);
|
|
||||||
|
|
||||||
previewedTimezones.push({
|
|
||||||
timezone: watchingUserTimezone,
|
|
||||||
current: true,
|
|
||||||
dateTime: options.time
|
|
||||||
? moment(dateTime)
|
|
||||||
.tz(watchingUserTimezone)
|
|
||||||
.format("LLL")
|
|
||||||
: _createDateTimeRange(dateTime, watchingUserTimezone)
|
|
||||||
});
|
|
||||||
|
|
||||||
if (
|
|
||||||
options.timezone &&
|
|
||||||
displayedTimezone === watchingUserTimezone &&
|
|
||||||
options.timezone !== displayedTimezone &&
|
|
||||||
!_isEqualZones(displayedTimezone, options.timezone)
|
|
||||||
) {
|
|
||||||
timezones.unshift(options.timezone);
|
|
||||||
}
|
|
||||||
|
|
||||||
Array.from(new Set(timezones.filter(Boolean))).forEach(timezone => {
|
|
||||||
if (_isEqualZones(timezone, displayedTimezone)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (_isEqualZones(timezone, watchingUserTimezone)) {
|
|
||||||
timezone = watchingUserTimezone;
|
|
||||||
}
|
|
||||||
|
|
||||||
previewedTimezones.push({
|
|
||||||
timezone,
|
|
||||||
dateTime: options.time
|
|
||||||
? moment(dateTime)
|
|
||||||
.tz(timezone)
|
|
||||||
.format("LLL")
|
|
||||||
: _createDateTimeRange(dateTime, timezone)
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!previewedTimezones.length) {
|
|
||||||
previewedTimezones.push({
|
|
||||||
timezone: "Etc/UTC",
|
|
||||||
dateTime: options.time
|
|
||||||
? moment(dateTime)
|
|
||||||
.tz("Etc/UTC")
|
|
||||||
.format("LLL")
|
|
||||||
: _createDateTimeRange(dateTime, "Etc/UTC")
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return _.uniq(previewedTimezones, "timezone");
|
|
||||||
}
|
|
||||||
|
|
||||||
function _generateTextPreview(previews) {
|
|
||||||
return previews
|
|
||||||
.map(preview => {
|
|
||||||
const formatedZone = _zoneWithoutPrefix(preview.timezone);
|
|
||||||
|
|
||||||
if (preview.dateTime.match(/TZ/)) {
|
|
||||||
return preview.dateTime.replace(/TZ/, formatedZone);
|
|
||||||
} else {
|
|
||||||
return `${formatedZone} ${preview.dateTime}`;
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.join(", ");
|
|
||||||
}
|
|
||||||
|
|
||||||
function _generateHtmlPreview(previews) {
|
|
||||||
return previews
|
|
||||||
.map(preview => {
|
|
||||||
const $template = $(PREVIEW_TEMPLATE);
|
|
||||||
|
|
||||||
if (preview.current) $template.addClass("current");
|
|
||||||
|
|
||||||
$template.find(".timezone").text(_zoneWithoutPrefix(preview.timezone));
|
|
||||||
$template.find(".date-time").text(preview.dateTime);
|
|
||||||
return $template[0].outerHTML;
|
|
||||||
})
|
|
||||||
.join("");
|
|
||||||
}
|
|
||||||
|
|
||||||
$.fn.applyLocalDates = function() {
|
|
||||||
return this.each(function() {
|
|
||||||
const $element = $(this);
|
|
||||||
|
|
||||||
const options = {};
|
|
||||||
options.time = $element.attr("data-time");
|
|
||||||
options.date = $element.attr("data-date");
|
|
||||||
options.recurring = $element.attr("data-recurring");
|
|
||||||
options.timezones = (
|
|
||||||
$element.attr("data-timezones") ||
|
|
||||||
Discourse.SiteSettings.discourse_local_dates_default_timezones ||
|
|
||||||
"Etc/UTC"
|
|
||||||
).split("|");
|
|
||||||
options.timezone = $element.attr("data-timezone");
|
|
||||||
options.calendar = ($element.attr("data-calendar") || "on") === "on";
|
|
||||||
options.displayedTimezone = $element.attr("data-displayed-timezone");
|
|
||||||
options.format =
|
|
||||||
$element.attr("data-format") || (options.time ? "LLL" : "LL");
|
|
||||||
options.countdown = $element.attr("data-countdown");
|
|
||||||
|
|
||||||
processElement($element, options);
|
|
||||||
});
|
|
||||||
};
|
|
||||||
})(jQuery);
|
|
@ -1,11 +1,19 @@
|
|||||||
import { withPluginApi } from "discourse/lib/plugin-api";
|
import { withPluginApi } from "discourse/lib/plugin-api";
|
||||||
import showModal from "discourse/lib/show-modal";
|
import showModal from "discourse/lib/show-modal";
|
||||||
|
import LocalDateBuilder from "../lib/local-date-builder";
|
||||||
|
|
||||||
|
const DATE_TEMPLATE = `
|
||||||
|
<span>
|
||||||
|
<svg class="fa d-icon d-icon-globe-americas svg-icon" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<use xlink:href="#globe-americas"></use>
|
||||||
|
</svg>
|
||||||
|
<span class="relative-time"></span>
|
||||||
|
</span>
|
||||||
|
`;
|
||||||
|
|
||||||
function initializeDiscourseLocalDates(api) {
|
function initializeDiscourseLocalDates(api) {
|
||||||
api.decorateCooked(
|
api.decorateCooked(
|
||||||
$elem => {
|
$elem => $(".discourse-local-date", $elem).applyLocalDates(),
|
||||||
$(".discourse-local-date", $elem).applyLocalDates();
|
|
||||||
},
|
|
||||||
{ id: "discourse-local-date" }
|
{ id: "discourse-local-date" }
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -37,6 +45,69 @@ export default {
|
|||||||
initialize(container) {
|
initialize(container) {
|
||||||
const siteSettings = container.lookup("site-settings:main");
|
const siteSettings = container.lookup("site-settings:main");
|
||||||
if (siteSettings.discourse_local_dates_enabled) {
|
if (siteSettings.discourse_local_dates_enabled) {
|
||||||
|
$.fn.applyLocalDates = function() {
|
||||||
|
return this.each(function() {
|
||||||
|
const opts = {};
|
||||||
|
const dataset = this.dataset;
|
||||||
|
opts.time = dataset.time;
|
||||||
|
opts.date = dataset.date;
|
||||||
|
opts.recurring = dataset.recurring;
|
||||||
|
opts.timezones = (
|
||||||
|
dataset.timezones ||
|
||||||
|
siteSettings.discourse_local_dates_default_timezones ||
|
||||||
|
"Etc/UTC"
|
||||||
|
)
|
||||||
|
.split("|")
|
||||||
|
.filter(Boolean);
|
||||||
|
opts.timezone = dataset.timezone;
|
||||||
|
opts.calendar = (dataset.calendar || "on") === "on";
|
||||||
|
opts.displayedTimezone = dataset.displayedTimezone;
|
||||||
|
opts.format = dataset.format || (opts.time ? "LLL" : "LL");
|
||||||
|
opts.countdown = dataset.countdown;
|
||||||
|
|
||||||
|
const localDateBuilder = new LocalDateBuilder(
|
||||||
|
opts,
|
||||||
|
moment.tz.guess()
|
||||||
|
).build();
|
||||||
|
|
||||||
|
const htmlPreviews = localDateBuilder.previews.map(preview => {
|
||||||
|
const previewNode = document.createElement("div");
|
||||||
|
previewNode.classList.add("preview");
|
||||||
|
if (preview.current) {
|
||||||
|
previewNode.classList.add("current");
|
||||||
|
}
|
||||||
|
|
||||||
|
const timezoneNode = document.createElement("span");
|
||||||
|
timezoneNode.classList.add("timezone");
|
||||||
|
timezoneNode.innerText = preview.timezone;
|
||||||
|
previewNode.appendChild(timezoneNode);
|
||||||
|
|
||||||
|
const dateTimeNode = document.createElement("span");
|
||||||
|
dateTimeNode.classList.add("date-time");
|
||||||
|
dateTimeNode.innerText = preview.formated;
|
||||||
|
previewNode.appendChild(dateTimeNode);
|
||||||
|
|
||||||
|
return previewNode;
|
||||||
|
});
|
||||||
|
|
||||||
|
const previewsNode = document.createElement("div");
|
||||||
|
previewsNode.classList.add("locale-dates-previews");
|
||||||
|
htmlPreviews.forEach(htmlPreview =>
|
||||||
|
previewsNode.appendChild(htmlPreview)
|
||||||
|
);
|
||||||
|
|
||||||
|
this.innerHTML = DATE_TEMPLATE;
|
||||||
|
this.setAttribute("aria-label", localDateBuilder.textPreview);
|
||||||
|
this.dataset.htmlTooltip = previewsNode.outerHTML;
|
||||||
|
this.classList.add("cooked-date");
|
||||||
|
if (localDateBuilder.pastEvent) {
|
||||||
|
this.classList.add("past");
|
||||||
|
}
|
||||||
|
const relativeTime = this.querySelector(".relative-time");
|
||||||
|
relativeTime.innerText = localDateBuilder.formated;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
withPluginApi("0.8.8", initializeDiscourseLocalDates);
|
withPluginApi("0.8.8", initializeDiscourseLocalDates);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,89 @@
|
|||||||
|
const { getProperties } = Ember;
|
||||||
|
|
||||||
|
/*
|
||||||
|
DateWithZoneHelper provides a limited list of helpers
|
||||||
|
to manipulate a moment object with timezones
|
||||||
|
|
||||||
|
- add(count unit) adds a COUNT of UNITS to a date
|
||||||
|
- subtract(count unit) subtracts a COUNT of UNITS to a date
|
||||||
|
- format(format) formats a date with zone in a consitent way, optional moment format
|
||||||
|
- isDST() allows to know if a date in a specified timezone is currently under DST
|
||||||
|
- datetimeWithZone(timezone) returns a new moment object with timezone applied
|
||||||
|
- datetime returns the moment object
|
||||||
|
- repetitionsBetweenDates(duration, date) return the number of repertitions of
|
||||||
|
duration between two dates, eg for duration: "1.weeks", "2.months"...
|
||||||
|
*/
|
||||||
|
export default class DateWithZoneHelper {
|
||||||
|
constructor(params = {}) {
|
||||||
|
this.timezone = params.timezone || "UTC";
|
||||||
|
this.localTimezone = params.localTimezone || moment.tz.guess();
|
||||||
|
|
||||||
|
this.datetime = moment.tz(
|
||||||
|
getProperties(params, [
|
||||||
|
"year",
|
||||||
|
"month",
|
||||||
|
"day",
|
||||||
|
"hour",
|
||||||
|
"minute",
|
||||||
|
"second"
|
||||||
|
]),
|
||||||
|
this.timezone
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
isDST() {
|
||||||
|
return this.datetime.tz(this.localTimezone).isDST();
|
||||||
|
}
|
||||||
|
|
||||||
|
repetitionsBetweenDates(duration, date) {
|
||||||
|
const [count, unit] = duration.split(".");
|
||||||
|
const diff = this.datetime.diff(date, unit);
|
||||||
|
const repetitions = diff / parseInt(count, 10);
|
||||||
|
return Math.abs((Math.round(repetitions * 10) / 10).toFixed(1));
|
||||||
|
}
|
||||||
|
|
||||||
|
add(count, unit) {
|
||||||
|
return this._fromDatetime(
|
||||||
|
this.datetime.clone().add(count, unit),
|
||||||
|
this.timezone,
|
||||||
|
this.localTimezone
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
subtract(count, unit) {
|
||||||
|
return this._fromDatetime(
|
||||||
|
this.datetime.clone().subtract(count, unit),
|
||||||
|
this.timezone,
|
||||||
|
this.localTimezone
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
datetimeWithZone(timezone) {
|
||||||
|
return this.datetime.clone().tz(timezone);
|
||||||
|
}
|
||||||
|
|
||||||
|
format(format) {
|
||||||
|
if (format) {
|
||||||
|
return this.datetime.tz(this.localTimezone).format(format);
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.datetime.tz(this.localTimezone).toISOString(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
static fromDatetime(datetime, timezone, localTimezone) {
|
||||||
|
return new DateWithZoneHelper({
|
||||||
|
year: datetime.year(),
|
||||||
|
month: datetime.month(),
|
||||||
|
day: datetime.date(),
|
||||||
|
hour: datetime.hour(),
|
||||||
|
minute: datetime.minute(),
|
||||||
|
second: datetime.second(),
|
||||||
|
timezone,
|
||||||
|
localTimezone
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
_fromDatetime(datetime, timezone, localTimezone) {
|
||||||
|
return DateWithZoneHelper.fromDatetime(datetime, timezone, localTimezone);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,243 @@
|
|||||||
|
import DateWithZoneHelper from "./date-with-zone-helper";
|
||||||
|
|
||||||
|
const TIME_FORMAT = "LLL";
|
||||||
|
const DATE_FORMAT = "LL";
|
||||||
|
const RANGE_SEPARATOR = "→";
|
||||||
|
|
||||||
|
export default class LocalDateBuilder {
|
||||||
|
constructor(params = {}, localTimezone) {
|
||||||
|
this.time = params.time;
|
||||||
|
this.date = params.date;
|
||||||
|
this.recurring = params.recurring;
|
||||||
|
this.timezones = Array.from(
|
||||||
|
new Set((params.timezones || []).filter(Boolean))
|
||||||
|
);
|
||||||
|
this.timezone = params.timezone || "UTC";
|
||||||
|
this.calendar =
|
||||||
|
typeof params.calendar === "undefined" ? true : params.calendar;
|
||||||
|
this.displayedTimezone = params.displayedTimezone;
|
||||||
|
this.format = params.format || (this.time ? TIME_FORMAT : DATE_FORMAT);
|
||||||
|
this.countdown = params.countdown;
|
||||||
|
this.localTimezone = localTimezone;
|
||||||
|
}
|
||||||
|
|
||||||
|
build() {
|
||||||
|
const [year, month, day] = this.date.split("-").map(x => parseInt(x, 10));
|
||||||
|
const [hour, minute] = (this.time || "")
|
||||||
|
.split(":")
|
||||||
|
.map(x => (x ? parseInt(x, 10) : undefined));
|
||||||
|
|
||||||
|
let displayedTimezone;
|
||||||
|
if (this.time) {
|
||||||
|
displayedTimezone = this.displayedTimezone || this.localTimezone;
|
||||||
|
} else {
|
||||||
|
displayedTimezone =
|
||||||
|
this.displayedTimezone || this.localTimezone || this.timezone;
|
||||||
|
}
|
||||||
|
|
||||||
|
let localDate = new DateWithZoneHelper({
|
||||||
|
year,
|
||||||
|
month: month ? month - 1 : null,
|
||||||
|
day,
|
||||||
|
hour,
|
||||||
|
minute,
|
||||||
|
timezone: this.timezone,
|
||||||
|
localTimezone: this.localTimezone
|
||||||
|
});
|
||||||
|
|
||||||
|
if (this.recurring) {
|
||||||
|
const [count, type] = this.recurring.split(".");
|
||||||
|
|
||||||
|
const repetitionsForType = localDate.repetitionsBetweenDates(
|
||||||
|
this.recurring,
|
||||||
|
moment.tz(this.localTimezone)
|
||||||
|
);
|
||||||
|
|
||||||
|
localDate = localDate.add(repetitionsForType + parseInt(count, 10), type);
|
||||||
|
}
|
||||||
|
|
||||||
|
const previews = this._generatePreviews(localDate, displayedTimezone);
|
||||||
|
|
||||||
|
return {
|
||||||
|
pastEvent:
|
||||||
|
!this.recurring &&
|
||||||
|
moment.tz(this.localTimezone).isAfter(localDate.datetime),
|
||||||
|
formated: this._applyFormatting(localDate, displayedTimezone),
|
||||||
|
previews,
|
||||||
|
textPreview: this._generateTextPreviews(previews)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
_generateTextPreviews(previews) {
|
||||||
|
return previews
|
||||||
|
.map(preview => {
|
||||||
|
const formatedZone = this._zoneWithoutPrefix(preview.timezone);
|
||||||
|
return `${formatedZone} ${preview.formated}`;
|
||||||
|
})
|
||||||
|
.join(", ");
|
||||||
|
}
|
||||||
|
|
||||||
|
_generatePreviews(localDate, displayedTimezone) {
|
||||||
|
const previewedTimezones = [];
|
||||||
|
|
||||||
|
const timezones = this.timezones.filter(
|
||||||
|
timezone =>
|
||||||
|
!this._isEqualZones(timezone, this.localTimezone) &&
|
||||||
|
!this._isEqualZones(timezone, this.timezone)
|
||||||
|
);
|
||||||
|
|
||||||
|
previewedTimezones.push({
|
||||||
|
timezone: this.localTimezone,
|
||||||
|
current: true,
|
||||||
|
formated: this._createDateTimeRange(localDate, this.time)
|
||||||
|
});
|
||||||
|
|
||||||
|
if (
|
||||||
|
this.timezone &&
|
||||||
|
displayedTimezone === this.localTimezone &&
|
||||||
|
this.timezone !== displayedTimezone &&
|
||||||
|
!this._isEqualZones(displayedTimezone, this.timezone)
|
||||||
|
) {
|
||||||
|
timezones.unshift(this.timezone);
|
||||||
|
}
|
||||||
|
|
||||||
|
timezones.forEach(timezone => {
|
||||||
|
if (this._isEqualZones(timezone, displayedTimezone)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this._isEqualZones(timezone, this.localTimezone)) {
|
||||||
|
timezone = this.localTimezone;
|
||||||
|
}
|
||||||
|
|
||||||
|
previewedTimezones.push({
|
||||||
|
timezone,
|
||||||
|
formated: this._createDateTimeRange(
|
||||||
|
localDate.datetimeWithZone(timezone),
|
||||||
|
this.time
|
||||||
|
)
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!previewedTimezones.length) {
|
||||||
|
previewedTimezones.push({
|
||||||
|
timezone: "Etc/UTC",
|
||||||
|
formated: this._createDateTimeRange(
|
||||||
|
localDate.datetimeWithZone("Etc/UTC"),
|
||||||
|
this.time
|
||||||
|
)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return previewedTimezones.uniqBy("timezone");
|
||||||
|
}
|
||||||
|
|
||||||
|
_isEqualZones(timezoneA, timezoneB) {
|
||||||
|
if ((timezoneA || timezoneB) && (!timezoneA || !timezoneB)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (timezoneA.includes(timezoneB) || timezoneB.includes(timezoneA)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
moment.tz(timezoneA).utcOffset() === moment.tz(timezoneB).utcOffset()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
_createDateTimeRange(startRange, time) {
|
||||||
|
// if a time has been given we do not attempt to automatically create a range
|
||||||
|
// instead we show only one date with a format showing the time
|
||||||
|
if (time) {
|
||||||
|
return startRange.format(TIME_FORMAT);
|
||||||
|
} else {
|
||||||
|
const endRange = startRange.add(24, "hours");
|
||||||
|
return [
|
||||||
|
startRange.format(this.format || "LLLL"),
|
||||||
|
RANGE_SEPARATOR,
|
||||||
|
endRange.format(this.format || "LLLL")
|
||||||
|
].join(" ");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_applyFormatting(localDate, displayedTimezone) {
|
||||||
|
if (this.countdown) {
|
||||||
|
const diffTime = moment.tz(this.localTimezone).diff(localDate.datetime);
|
||||||
|
|
||||||
|
if (diffTime < 0) {
|
||||||
|
return moment.duration(diffTime).humanize();
|
||||||
|
} else {
|
||||||
|
return I18n.t("discourse_local_dates.relative_dates.countdown.passed");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const sameTimezone = this._isEqualZones(
|
||||||
|
displayedTimezone,
|
||||||
|
this.localTimezone
|
||||||
|
);
|
||||||
|
|
||||||
|
if (this.calendar) {
|
||||||
|
const inCalendarRange = moment
|
||||||
|
.tz(this.localTimezone)
|
||||||
|
.isBetween(
|
||||||
|
localDate.subtract(2, "day").datetime,
|
||||||
|
localDate.add(1, "day").datetime.endOf("day")
|
||||||
|
);
|
||||||
|
|
||||||
|
if (inCalendarRange && sameTimezone) {
|
||||||
|
return localDate.datetime.calendar(
|
||||||
|
moment.tz(this.localTimezone),
|
||||||
|
this._calendarFormats(this.time ? this.time : null)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!sameTimezone) {
|
||||||
|
return this._formatWithZone(localDate, displayedTimezone, this.format);
|
||||||
|
}
|
||||||
|
|
||||||
|
return localDate.format(this.format);
|
||||||
|
}
|
||||||
|
|
||||||
|
_calendarFormats(time) {
|
||||||
|
return {
|
||||||
|
sameDay: this._translateCalendarKey(time, "today"),
|
||||||
|
nextDay: this._translateCalendarKey(time, "tomorrow"),
|
||||||
|
lastDay: this._translateCalendarKey(time, "yesterday"),
|
||||||
|
sameElse: "L"
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
_translateCalendarKey(time, key) {
|
||||||
|
const translated = I18n.t(`discourse_local_dates.relative_dates.${key}`, {
|
||||||
|
time: "LT"
|
||||||
|
});
|
||||||
|
|
||||||
|
if (time) {
|
||||||
|
return translated
|
||||||
|
.split("LT")
|
||||||
|
.map(w => `[${w}]`)
|
||||||
|
.join("LT");
|
||||||
|
} else {
|
||||||
|
return `[${translated.replace(" LT", "")}]`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_formatTimezone(timezone) {
|
||||||
|
return timezone
|
||||||
|
.replace("_", " ")
|
||||||
|
.replace("Etc/", "")
|
||||||
|
.split("/");
|
||||||
|
}
|
||||||
|
|
||||||
|
_zoneWithoutPrefix(timezone) {
|
||||||
|
const [part1, part2] = this._formatTimezone(timezone);
|
||||||
|
return part2 || part1;
|
||||||
|
}
|
||||||
|
|
||||||
|
_formatWithZone(localDate, displayedTimezone, format) {
|
||||||
|
let formated = localDate.datetimeWithZone(displayedTimezone).format(format);
|
||||||
|
return `${formated} (${this._zoneWithoutPrefix(displayedTimezone)})`;
|
||||||
|
}
|
||||||
|
}
|
@ -6,7 +6,6 @@
|
|||||||
# author: Joffrey Jaffeux
|
# author: Joffrey Jaffeux
|
||||||
hide_plugin if self.respond_to?(:hide_plugin)
|
hide_plugin if self.respond_to?(:hide_plugin)
|
||||||
|
|
||||||
register_asset 'javascripts/discourse-local-dates.js.es6'
|
|
||||||
register_asset 'stylesheets/common/discourse-local-dates.scss'
|
register_asset 'stylesheets/common/discourse-local-dates.scss'
|
||||||
register_asset 'moment.js', :vendored_core_pretty_text
|
register_asset 'moment.js', :vendored_core_pretty_text
|
||||||
register_asset 'moment-timezone.js', :vendored_core_pretty_text
|
register_asset 'moment-timezone.js', :vendored_core_pretty_text
|
||||||
|
@ -1,463 +0,0 @@
|
|||||||
import { acceptance } from "helpers/qunit-helpers";
|
|
||||||
|
|
||||||
const sandbox = sinon.createSandbox();
|
|
||||||
|
|
||||||
acceptance("Local Dates", {
|
|
||||||
loggedIn: true,
|
|
||||||
settings: {
|
|
||||||
discourse_local_dates_enabled: true,
|
|
||||||
discourse_local_dates_default_timezones: "Europe/Paris|America/Los_Angeles"
|
|
||||||
},
|
|
||||||
beforeEach() {
|
|
||||||
freezeDateAndZone();
|
|
||||||
},
|
|
||||||
afterEach() {
|
|
||||||
sandbox.restore();
|
|
||||||
moment.tz.setDefault();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
const DEFAULT_DATE = "2018-06-20";
|
|
||||||
const DEFAULT_ZONE = "Europe/Paris";
|
|
||||||
const DEFAULT_ZONE_FORMATED = DEFAULT_ZONE.split("/")[1];
|
|
||||||
|
|
||||||
function advance(count, unit = "days") {
|
|
||||||
return moment(DEFAULT_DATE)
|
|
||||||
.add(count, unit)
|
|
||||||
.format("YYYY-MM-DD");
|
|
||||||
}
|
|
||||||
|
|
||||||
function rewind(count, unit = "days") {
|
|
||||||
return moment(DEFAULT_DATE)
|
|
||||||
.subtract(count, unit)
|
|
||||||
.format("YYYY-MM-DD");
|
|
||||||
}
|
|
||||||
|
|
||||||
function freezeDateAndZone(date, zone, cb) {
|
|
||||||
date = date || DEFAULT_DATE;
|
|
||||||
zone = zone || DEFAULT_ZONE;
|
|
||||||
|
|
||||||
sandbox.restore();
|
|
||||||
sandbox.stub(moment.tz, "guess");
|
|
||||||
moment.tz.guess.returns(zone);
|
|
||||||
moment.tz.setDefault(zone);
|
|
||||||
|
|
||||||
const now = moment(date).valueOf();
|
|
||||||
sandbox.useFakeTimers(now);
|
|
||||||
|
|
||||||
if (cb) {
|
|
||||||
cb();
|
|
||||||
|
|
||||||
moment.tz.guess.returns(DEFAULT_ZONE);
|
|
||||||
moment.tz.setDefault(DEFAULT_ZONE);
|
|
||||||
sandbox.useFakeTimers(moment(DEFAULT_DATE).valueOf());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function generateHTML(options = {}) {
|
|
||||||
let output = `<span class="discourse-local-date past cooked-date"`;
|
|
||||||
|
|
||||||
output += ` data-date="${options.date || DEFAULT_DATE}"`;
|
|
||||||
if (options.format) output += ` data-format="${options.format}"`;
|
|
||||||
if (options.timezones) output += ` data-timezones="${options.timezones}"`;
|
|
||||||
if (options.time) output += ` data-time="${options.time}"`;
|
|
||||||
output += ` data-timezone="${options.timezone || DEFAULT_ZONE}"`;
|
|
||||||
if (options.calendar) output += ` data-calendar="${options.calendar}"`;
|
|
||||||
if (options.recurring) output += ` data-recurring="${options.recurring}"`;
|
|
||||||
if (options.displayedTimezone)
|
|
||||||
output += ` data-displayed-timezone="${options.displayedTimezone}"`;
|
|
||||||
|
|
||||||
return (output += "></span>");
|
|
||||||
}
|
|
||||||
|
|
||||||
test("default format - time specified", assert => {
|
|
||||||
const html = generateHTML({ date: advance(3), time: "02:00" });
|
|
||||||
const transformed = $(html).applyLocalDates();
|
|
||||||
|
|
||||||
assert.equal(
|
|
||||||
transformed.text().trim(),
|
|
||||||
"June 23, 2018 2:00 AM",
|
|
||||||
"it uses moment LLL format"
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
test("default format - no time specified", assert => {
|
|
||||||
let html = generateHTML({ date: advance(3) });
|
|
||||||
let transformed = $(html).applyLocalDates();
|
|
||||||
|
|
||||||
assert.equal(
|
|
||||||
transformed.text().trim(),
|
|
||||||
"June 23, 2018",
|
|
||||||
"it uses moment LL format as default if not time is specified"
|
|
||||||
);
|
|
||||||
|
|
||||||
freezeDateAndZone(advance(1), "Pacific/Auckland", () => {
|
|
||||||
html = generateHTML({ date: advance(3) });
|
|
||||||
transformed = $(html).applyLocalDates();
|
|
||||||
|
|
||||||
assert.equal(
|
|
||||||
transformed.text().trim(),
|
|
||||||
`June 23, 2018 (${DEFAULT_ZONE_FORMATED})`,
|
|
||||||
"it appends creator timezone if watching user timezone is different"
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
freezeDateAndZone(advance(1), "Europe/Vienna", () => {
|
|
||||||
html = generateHTML({ date: advance(3) });
|
|
||||||
transformed = $(html).applyLocalDates();
|
|
||||||
|
|
||||||
assert.equal(
|
|
||||||
transformed.text().trim(),
|
|
||||||
"June 23, 2018",
|
|
||||||
"it doesn’t append timezone if different but with the same utc offset"
|
|
||||||
);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
test("today", assert => {
|
|
||||||
const html = generateHTML({ time: "16:00" });
|
|
||||||
const transformed = $(html).applyLocalDates();
|
|
||||||
|
|
||||||
assert.equal(transformed.text().trim(), "Today 4:00 PM", "it display Today");
|
|
||||||
});
|
|
||||||
|
|
||||||
test("today - no time", assert => {
|
|
||||||
const html = generateHTML();
|
|
||||||
const transformed = $(html).applyLocalDates();
|
|
||||||
|
|
||||||
assert.equal(
|
|
||||||
transformed.text().trim(),
|
|
||||||
"Today",
|
|
||||||
"it display Today without time"
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
test("yesterday", assert => {
|
|
||||||
const html = generateHTML({ date: rewind(1), time: "16:00" });
|
|
||||||
const transformed = $(html).applyLocalDates();
|
|
||||||
|
|
||||||
assert.equal(
|
|
||||||
transformed.text().trim(),
|
|
||||||
"Yesterday 4:00 PM",
|
|
||||||
"it displays yesterday"
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
test("yesterday - no time", assert => {
|
|
||||||
const html = generateHTML({ date: rewind(1) });
|
|
||||||
const transformed = $(html).applyLocalDates();
|
|
||||||
|
|
||||||
assert.equal(
|
|
||||||
transformed.text().trim(),
|
|
||||||
"Yesterday",
|
|
||||||
"it displays yesterday without time"
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
test("tomorrow", assert => {
|
|
||||||
const html = generateHTML({ date: advance(1), time: "16:00" });
|
|
||||||
const transformed = $(html).applyLocalDates();
|
|
||||||
|
|
||||||
assert.equal(
|
|
||||||
transformed.text().trim(),
|
|
||||||
"Tomorrow 4:00 PM",
|
|
||||||
"it displays tomorrow"
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
test("tomorrow - no time", assert => {
|
|
||||||
const html = generateHTML({ date: advance(1) });
|
|
||||||
const transformed = $(html).applyLocalDates();
|
|
||||||
|
|
||||||
assert.equal(
|
|
||||||
transformed.text().trim(),
|
|
||||||
"Tomorrow",
|
|
||||||
"it displays tomorrow without time"
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
test("today - no time with different zones", assert => {
|
|
||||||
const html = generateHTML();
|
|
||||||
let transformed = $(html).applyLocalDates();
|
|
||||||
|
|
||||||
assert.equal(
|
|
||||||
transformed.text().trim(),
|
|
||||||
"Today",
|
|
||||||
"it displays today without time"
|
|
||||||
);
|
|
||||||
|
|
||||||
freezeDateAndZone(rewind(12, "hours"), "Pacific/Auckland", () => {
|
|
||||||
transformed = $(html).applyLocalDates();
|
|
||||||
assert.equal(
|
|
||||||
transformed.text().trim(),
|
|
||||||
`June 20, 2018 (${DEFAULT_ZONE_FORMATED})`,
|
|
||||||
"it displays the date without calendar and creator timezone"
|
|
||||||
);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
test("calendar off", assert => {
|
|
||||||
const html = generateHTML({ calendar: "off", time: "16:00" });
|
|
||||||
const transformed = $(html).applyLocalDates();
|
|
||||||
|
|
||||||
assert.equal(
|
|
||||||
transformed.text().trim(),
|
|
||||||
"June 20, 2018 4:00 PM",
|
|
||||||
"it displays the date without Today"
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
test("recurring", assert => {
|
|
||||||
const html = generateHTML({ recurring: "1.week", time: "16:00" });
|
|
||||||
let transformed = $(html).applyLocalDates();
|
|
||||||
|
|
||||||
assert.equal(
|
|
||||||
transformed.text().trim(),
|
|
||||||
"Today 4:00 PM",
|
|
||||||
"it displays the next occurrence"
|
|
||||||
);
|
|
||||||
|
|
||||||
freezeDateAndZone(advance(1), null, () => {
|
|
||||||
transformed = $(html).applyLocalDates();
|
|
||||||
|
|
||||||
assert.equal(
|
|
||||||
transformed.text().trim(),
|
|
||||||
"June 27, 2018 4:00 PM",
|
|
||||||
"it displays the next occurrence"
|
|
||||||
);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
test("format", assert => {
|
|
||||||
const html = generateHTML({
|
|
||||||
date: advance(3),
|
|
||||||
format: "YYYY | MM - DD"
|
|
||||||
});
|
|
||||||
const transformed = $(html).applyLocalDates();
|
|
||||||
|
|
||||||
assert.equal(
|
|
||||||
transformed.text().trim(),
|
|
||||||
"2018 | 06 - 23",
|
|
||||||
"it uses the given format"
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
test("displayedTimezone", assert => {
|
|
||||||
let html = generateHTML({
|
|
||||||
date: advance(3),
|
|
||||||
displayedTimezone: "America/Chicago",
|
|
||||||
time: "16:00"
|
|
||||||
});
|
|
||||||
let transformed = $(html).applyLocalDates();
|
|
||||||
|
|
||||||
assert.equal(
|
|
||||||
transformed.text().trim(),
|
|
||||||
"June 23, 2018 9:00 AM (Chicago)",
|
|
||||||
"it displays timezone when different from watching user"
|
|
||||||
);
|
|
||||||
|
|
||||||
html = generateHTML({
|
|
||||||
date: advance(3),
|
|
||||||
displayedTimezone: DEFAULT_ZONE,
|
|
||||||
time: "16:00"
|
|
||||||
});
|
|
||||||
|
|
||||||
transformed = $(html).applyLocalDates();
|
|
||||||
assert.equal(
|
|
||||||
transformed.text().trim(),
|
|
||||||
"June 23, 2018 4:00 PM",
|
|
||||||
"it doesn’t display timezone when same from watching user"
|
|
||||||
);
|
|
||||||
|
|
||||||
html = generateHTML({ displayedTimezone: "Etc/UTC" });
|
|
||||||
transformed = $(html).applyLocalDates();
|
|
||||||
|
|
||||||
assert.equal(
|
|
||||||
transformed.text().trim(),
|
|
||||||
"June 19, 2018 (UTC)",
|
|
||||||
"it displays timezone and drops calendar mode when timezone is different from watching user"
|
|
||||||
);
|
|
||||||
|
|
||||||
html = generateHTML({ displayedTimezone: DEFAULT_ZONE });
|
|
||||||
transformed = $(html).applyLocalDates();
|
|
||||||
|
|
||||||
assert.equal(
|
|
||||||
transformed.text().trim(),
|
|
||||||
"Today",
|
|
||||||
"it doesn’t display timezone and doesn’t drop calendar mode when timezone is same from watching user"
|
|
||||||
);
|
|
||||||
|
|
||||||
html = generateHTML({
|
|
||||||
timezone: "America/Chicago"
|
|
||||||
});
|
|
||||||
transformed = $(html).applyLocalDates();
|
|
||||||
|
|
||||||
assert.equal(
|
|
||||||
transformed.text().trim(),
|
|
||||||
"June 20, 2018 (Chicago)",
|
|
||||||
"it uses timezone when displayedTimezone is not set"
|
|
||||||
);
|
|
||||||
|
|
||||||
html = generateHTML();
|
|
||||||
transformed = $(html).applyLocalDates();
|
|
||||||
|
|
||||||
assert.equal(
|
|
||||||
transformed.text().trim(),
|
|
||||||
"Today",
|
|
||||||
"it uses user’s timezone when displayedTimezone and timezone are not set"
|
|
||||||
);
|
|
||||||
|
|
||||||
html = generateHTML({
|
|
||||||
timezone: "America/Chicago",
|
|
||||||
displayedTimezone: "Pacific/Auckland"
|
|
||||||
});
|
|
||||||
transformed = $(html).applyLocalDates();
|
|
||||||
|
|
||||||
assert.equal(
|
|
||||||
transformed.text().trim(),
|
|
||||||
"June 20, 2018 (Auckland)",
|
|
||||||
"it uses displayedTimezone over timezone"
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
test("tooltip", assert => {
|
|
||||||
let html = generateHTML({ timezone: "America/Chicago" });
|
|
||||||
let transformed = $(html).applyLocalDates();
|
|
||||||
let htmlToolip = transformed.attr("data-html-tooltip");
|
|
||||||
let currentUserPreview = $(htmlToolip).find(".preview.current");
|
|
||||||
let timezone = currentUserPreview.find(".timezone").text();
|
|
||||||
let dateTime = currentUserPreview.find(".date-time").text();
|
|
||||||
|
|
||||||
assert.equal(
|
|
||||||
timezone,
|
|
||||||
DEFAULT_ZONE_FORMATED,
|
|
||||||
"it adds watching user timezone as preview"
|
|
||||||
);
|
|
||||||
assert.equal(
|
|
||||||
dateTime,
|
|
||||||
"June 20, 2018 7:00 AM → June 21, 2018 7:00 AM",
|
|
||||||
"it creates a range adjusted to watching user timezone"
|
|
||||||
);
|
|
||||||
|
|
||||||
freezeDateAndZone(DEFAULT_DATE, "Pacific/Auckland", () => {
|
|
||||||
html = generateHTML({ timezone: "Pacific/Auckland" });
|
|
||||||
transformed = $(html).applyLocalDates();
|
|
||||||
htmlToolip = transformed.attr("data-html-tooltip");
|
|
||||||
currentUserPreview = $(htmlToolip).find(".preview.current");
|
|
||||||
|
|
||||||
assert.ok(
|
|
||||||
exists(currentUserPreview),
|
|
||||||
"it creates an entry if watching user has the same timezone than creator"
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
html = generateHTML({
|
|
||||||
timezones: "Etc/UTC",
|
|
||||||
timezone: "America/Chicago",
|
|
||||||
time: "14:00:00"
|
|
||||||
});
|
|
||||||
transformed = $(html).applyLocalDates();
|
|
||||||
htmlToolip = transformed.attr("data-html-tooltip");
|
|
||||||
|
|
||||||
assert.ok(
|
|
||||||
exists($(htmlToolip).find(".preview.current")),
|
|
||||||
"doesn’t create current timezone when displayed timezone equals watching user timezone"
|
|
||||||
);
|
|
||||||
|
|
||||||
let $firstPreview = $(htmlToolip).find(".preview:nth-child(2)");
|
|
||||||
dateTime = $firstPreview.find(".date-time").text();
|
|
||||||
timezone = $firstPreview.find(".timezone").text();
|
|
||||||
assert.equal(
|
|
||||||
dateTime,
|
|
||||||
"June 20, 2018 2:00 PM",
|
|
||||||
"it doesn’t create range if time has been set"
|
|
||||||
);
|
|
||||||
assert.equal(timezone, "Chicago", "it adds the timezone of the creator");
|
|
||||||
|
|
||||||
let $secondPreview = $(htmlToolip).find(".preview:nth-child(3)");
|
|
||||||
dateTime = $secondPreview.find(".date-time").text();
|
|
||||||
timezone = $secondPreview.find(".timezone").text();
|
|
||||||
assert.equal(timezone, "UTC", "Etc/UTC is rewritten to UTC");
|
|
||||||
|
|
||||||
freezeDateAndZone(moment("2018-11-26 21:00:00"), "Europe/Vienna", () => {
|
|
||||||
html = generateHTML({
|
|
||||||
date: "2018-11-22",
|
|
||||||
timezone: "America/Chicago",
|
|
||||||
time: "14:00"
|
|
||||||
});
|
|
||||||
transformed = $(html).applyLocalDates();
|
|
||||||
htmlToolip = transformed.attr("data-html-tooltip");
|
|
||||||
|
|
||||||
$firstPreview = $(htmlToolip).find(".preview:nth-child(2)");
|
|
||||||
|
|
||||||
assert.equal(
|
|
||||||
$firstPreview.find(".timezone").text(),
|
|
||||||
"Chicago",
|
|
||||||
"it adds the creator timezone to the previews"
|
|
||||||
);
|
|
||||||
assert.equal(
|
|
||||||
$firstPreview.find(".date-time").text(),
|
|
||||||
"November 22, 2018 2:00 PM",
|
|
||||||
"it adds the creator timezone to the previews"
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
freezeDateAndZone(DEFAULT_DATE, "Europe/Vienna", () => {
|
|
||||||
html = generateHTML({
|
|
||||||
date: "2018-11-22",
|
|
||||||
timezone: "America/Chicago",
|
|
||||||
timezones: "Europe/Paris"
|
|
||||||
});
|
|
||||||
transformed = $(html).applyLocalDates();
|
|
||||||
htmlToolip = transformed.attr("data-html-tooltip");
|
|
||||||
|
|
||||||
$firstPreview = $(htmlToolip)
|
|
||||||
.find(".preview")
|
|
||||||
.first();
|
|
||||||
|
|
||||||
assert.equal(
|
|
||||||
$firstPreview.find(".timezone").text(),
|
|
||||||
"Vienna",
|
|
||||||
"it rewrites timezone with same offset and different name than watching user"
|
|
||||||
);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
test("test utils", assert => {
|
|
||||||
assert.equal(
|
|
||||||
moment().format("LLLL"),
|
|
||||||
moment(DEFAULT_DATE).format("LLLL"),
|
|
||||||
"it has defaults"
|
|
||||||
);
|
|
||||||
|
|
||||||
assert.equal(moment.tz.guess(), DEFAULT_ZONE, "it has defaults");
|
|
||||||
|
|
||||||
freezeDateAndZone(advance(1), DEFAULT_ZONE, () => {
|
|
||||||
assert.equal(
|
|
||||||
moment().format("LLLL"),
|
|
||||||
moment(DEFAULT_DATE)
|
|
||||||
.add(1, "days")
|
|
||||||
.format("LLLL"),
|
|
||||||
"it applies new time"
|
|
||||||
);
|
|
||||||
assert.equal(moment.tz.guess(), DEFAULT_ZONE);
|
|
||||||
});
|
|
||||||
|
|
||||||
assert.equal(
|
|
||||||
moment().format("LLLL"),
|
|
||||||
moment(DEFAULT_DATE).format("LLLL"),
|
|
||||||
"it restores time"
|
|
||||||
);
|
|
||||||
|
|
||||||
freezeDateAndZone(advance(1), "Pacific/Auckland", () => {
|
|
||||||
assert.equal(
|
|
||||||
moment().format("LLLL"),
|
|
||||||
moment(DEFAULT_DATE)
|
|
||||||
.add(1, "days")
|
|
||||||
.format("LLLL")
|
|
||||||
);
|
|
||||||
assert.equal(moment.tz.guess(), "Pacific/Auckland", "it applies new zone");
|
|
||||||
});
|
|
||||||
|
|
||||||
assert.equal(moment.tz.guess(), DEFAULT_ZONE, "it restores zone");
|
|
||||||
});
|
|
@ -0,0 +1,168 @@
|
|||||||
|
import DateWithZoneHelper from "./date-with-zone-helper";
|
||||||
|
|
||||||
|
const PARIS = "Europe/Paris";
|
||||||
|
const SYDNEY = "Australia/Sydney";
|
||||||
|
|
||||||
|
QUnit.module("lib:date-with-zone-helper");
|
||||||
|
|
||||||
|
function buildDateHelper(params = {}) {
|
||||||
|
return new DateWithZoneHelper({
|
||||||
|
year: params.year || 2020,
|
||||||
|
day: params.day || 22,
|
||||||
|
month: params.month || 2,
|
||||||
|
hour: params.hour || 10,
|
||||||
|
minute: params.minute || 5,
|
||||||
|
timezone: params.timezone,
|
||||||
|
localTimezone: PARIS
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
QUnit.test("#format", assert => {
|
||||||
|
let date = buildDateHelper({
|
||||||
|
day: 15,
|
||||||
|
month: 2,
|
||||||
|
hour: 15,
|
||||||
|
minute: 36,
|
||||||
|
timezone: PARIS
|
||||||
|
});
|
||||||
|
assert.equal(date.format(), "2020-03-15T15:36:00.000+01:00");
|
||||||
|
});
|
||||||
|
|
||||||
|
QUnit.test("#repetitionsBetweenDates", assert => {
|
||||||
|
let date;
|
||||||
|
|
||||||
|
date = buildDateHelper({
|
||||||
|
day: 15,
|
||||||
|
month: 1,
|
||||||
|
hour: 15,
|
||||||
|
minute: 36,
|
||||||
|
timezone: PARIS
|
||||||
|
});
|
||||||
|
assert.equal(
|
||||||
|
date.repetitionsBetweenDates(
|
||||||
|
"1.hour",
|
||||||
|
moment.tz("2020-02-15 15:36", SYDNEY)
|
||||||
|
),
|
||||||
|
10,
|
||||||
|
"it correctly finds difference between timezones"
|
||||||
|
);
|
||||||
|
|
||||||
|
date = buildDateHelper({
|
||||||
|
day: 15,
|
||||||
|
month: 1,
|
||||||
|
hour: 15,
|
||||||
|
minute: 36,
|
||||||
|
timezone: PARIS
|
||||||
|
});
|
||||||
|
assert.equal(
|
||||||
|
date.repetitionsBetweenDates(
|
||||||
|
"1.minute",
|
||||||
|
moment.tz("2020-02-15 15:36", PARIS)
|
||||||
|
),
|
||||||
|
0,
|
||||||
|
"it correctly finds no difference"
|
||||||
|
);
|
||||||
|
|
||||||
|
date = buildDateHelper({
|
||||||
|
day: 15,
|
||||||
|
month: 1,
|
||||||
|
hour: 15,
|
||||||
|
minute: 36,
|
||||||
|
timezone: PARIS
|
||||||
|
});
|
||||||
|
assert.equal(
|
||||||
|
date.repetitionsBetweenDates(
|
||||||
|
"1.minute",
|
||||||
|
moment.tz("2020-02-15 15:37", PARIS)
|
||||||
|
),
|
||||||
|
1,
|
||||||
|
"it correctly finds no difference"
|
||||||
|
);
|
||||||
|
|
||||||
|
date = buildDateHelper({
|
||||||
|
day: 15,
|
||||||
|
month: 1,
|
||||||
|
hour: 15,
|
||||||
|
minute: 36,
|
||||||
|
timezone: PARIS
|
||||||
|
});
|
||||||
|
assert.equal(
|
||||||
|
date.repetitionsBetweenDates(
|
||||||
|
"2.minute",
|
||||||
|
moment.tz("2020-02-15 15:41", PARIS)
|
||||||
|
),
|
||||||
|
2.5,
|
||||||
|
"it correctly finds difference with a multiplicator"
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
QUnit.test("#add", assert => {
|
||||||
|
let date;
|
||||||
|
let futureLocalDate;
|
||||||
|
|
||||||
|
date = buildDateHelper({
|
||||||
|
day: 19,
|
||||||
|
month: 2,
|
||||||
|
hour: 15,
|
||||||
|
minute: 36,
|
||||||
|
timezone: PARIS
|
||||||
|
});
|
||||||
|
|
||||||
|
assert.notOk(date.isDST());
|
||||||
|
futureLocalDate = date.add(8, "months");
|
||||||
|
assert.notOk(futureLocalDate.isDST());
|
||||||
|
assert.equal(
|
||||||
|
futureLocalDate.format(),
|
||||||
|
"2020-11-19T15:36:00.000+01:00",
|
||||||
|
"it correctly adds from a !isDST date to a !isDST date"
|
||||||
|
);
|
||||||
|
|
||||||
|
date = buildDateHelper({
|
||||||
|
day: 25,
|
||||||
|
month: 3,
|
||||||
|
hour: 15,
|
||||||
|
minute: 36,
|
||||||
|
timezone: PARIS
|
||||||
|
});
|
||||||
|
assert.ok(date.isDST());
|
||||||
|
futureLocalDate = date.add(1, "year");
|
||||||
|
assert.ok(futureLocalDate.isDST());
|
||||||
|
assert.equal(
|
||||||
|
futureLocalDate.format(),
|
||||||
|
"2021-04-25T15:36:00.000+02:00",
|
||||||
|
"it correctly adds from a isDST date to a isDST date"
|
||||||
|
);
|
||||||
|
|
||||||
|
date = buildDateHelper({
|
||||||
|
day: 25,
|
||||||
|
month: 2,
|
||||||
|
hour: 15,
|
||||||
|
minute: 36,
|
||||||
|
timezone: PARIS
|
||||||
|
});
|
||||||
|
assert.notOk(date.isDST());
|
||||||
|
futureLocalDate = date.add(1, "week");
|
||||||
|
assert.ok(futureLocalDate.isDST());
|
||||||
|
assert.equal(
|
||||||
|
futureLocalDate.format(),
|
||||||
|
"2020-04-01T15:36:00.000+02:00",
|
||||||
|
"it correctly adds from a !isDST date to a isDST date"
|
||||||
|
);
|
||||||
|
|
||||||
|
date = buildDateHelper({
|
||||||
|
day: 1,
|
||||||
|
month: 3,
|
||||||
|
hour: 15,
|
||||||
|
minute: 36,
|
||||||
|
timezone: PARIS
|
||||||
|
});
|
||||||
|
|
||||||
|
assert.ok(date.isDST());
|
||||||
|
futureLocalDate = date.add(8, "months");
|
||||||
|
assert.notOk(futureLocalDate.isDST());
|
||||||
|
assert.equal(
|
||||||
|
futureLocalDate.format(),
|
||||||
|
"2020-12-01T15:36:00.000+01:00",
|
||||||
|
"it correctly adds from a isDST date to a !isDST date"
|
||||||
|
);
|
||||||
|
});
|
@ -0,0 +1,420 @@
|
|||||||
|
import LocalDateBuilder from "./local-date-builder";
|
||||||
|
|
||||||
|
const UTC = "Etc/UTC";
|
||||||
|
const SYDNEY = "Australia/Sydney";
|
||||||
|
const LOS_ANGELES = "America/Los_Angeles";
|
||||||
|
const PARIS = "Europe/Paris";
|
||||||
|
const LAGOS = "Africa/Lagos";
|
||||||
|
const LONDON = "Europe/London";
|
||||||
|
|
||||||
|
QUnit.module("lib:local-date-builder");
|
||||||
|
|
||||||
|
const sandbox = sinon.createSandbox();
|
||||||
|
|
||||||
|
function freezeTime({ date, timezone }, cb) {
|
||||||
|
date = date || "2020-01-22 10:34";
|
||||||
|
const newTimezone = timezone || PARIS;
|
||||||
|
const previousZone = moment.tz.guess();
|
||||||
|
const now = moment.tz(date, newTimezone).valueOf();
|
||||||
|
|
||||||
|
sandbox.useFakeTimers(now);
|
||||||
|
sandbox.stub(moment.tz, "guess");
|
||||||
|
moment.tz.guess.returns(newTimezone);
|
||||||
|
moment.tz.setDefault(newTimezone);
|
||||||
|
|
||||||
|
cb();
|
||||||
|
|
||||||
|
moment.tz.guess.returns(previousZone);
|
||||||
|
moment.tz.setDefault(previousZone);
|
||||||
|
sandbox.restore();
|
||||||
|
}
|
||||||
|
|
||||||
|
QUnit.assert.buildsCorrectDate = function(options, expected, message) {
|
||||||
|
const localTimezone = options.localTimezone || PARIS;
|
||||||
|
delete options.localTimezone;
|
||||||
|
|
||||||
|
const localDateBuilder = new LocalDateBuilder(
|
||||||
|
Object.assign(
|
||||||
|
{},
|
||||||
|
{
|
||||||
|
date: "2020-03-22"
|
||||||
|
},
|
||||||
|
options
|
||||||
|
),
|
||||||
|
localTimezone
|
||||||
|
);
|
||||||
|
|
||||||
|
if (expected.formated) {
|
||||||
|
this.test.assert.equal(
|
||||||
|
localDateBuilder.build().formated,
|
||||||
|
expected.formated,
|
||||||
|
message || "it formates the date correctly"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (expected.previews) {
|
||||||
|
this.test.assert.deepEqual(
|
||||||
|
localDateBuilder.build().previews,
|
||||||
|
expected.previews,
|
||||||
|
message || "it formates the previews correctly"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
QUnit.test("date", assert => {
|
||||||
|
freezeTime({ date: "2020-03-11" }, () => {
|
||||||
|
assert.buildsCorrectDate(
|
||||||
|
{ date: "2020-03-22", timezone: PARIS },
|
||||||
|
{ formated: "March 22, 2020" },
|
||||||
|
"it displays the date without time"
|
||||||
|
);
|
||||||
|
|
||||||
|
assert.buildsCorrectDate(
|
||||||
|
{ date: "2020-04-11", time: "11:00" },
|
||||||
|
{ formated: "April 11, 2020 1:00 PM" },
|
||||||
|
"it displays the date with time"
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
QUnit.test("option[format]", assert => {
|
||||||
|
freezeTime({ date: "2020-03-11" }, () => {
|
||||||
|
assert.buildsCorrectDate(
|
||||||
|
{ format: "YYYY" },
|
||||||
|
{ formated: "2020" },
|
||||||
|
"it uses custom format"
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
QUnit.test("option[displayedTimezone]", assert => {
|
||||||
|
freezeTime({}, () => {
|
||||||
|
assert.buildsCorrectDate(
|
||||||
|
{ displayedTimezone: SYDNEY },
|
||||||
|
{ formated: "March 22, 2020 (Sydney)" },
|
||||||
|
"it displays the timezone if the timezone is different from the date"
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
freezeTime({}, () => {
|
||||||
|
assert.buildsCorrectDate(
|
||||||
|
{ displayedTimezone: PARIS, timezone: PARIS },
|
||||||
|
{ formated: "March 22, 2020" },
|
||||||
|
"it doesn't display the timezone if the timezone is the same than the date"
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
freezeTime({}, () => {
|
||||||
|
assert.buildsCorrectDate(
|
||||||
|
{ timezone: UTC, displayedTimezone: UTC },
|
||||||
|
{ formated: "March 22, 2020 (UTC)" },
|
||||||
|
"it replaces `Etc/`"
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
freezeTime({}, () => {
|
||||||
|
assert.buildsCorrectDate(
|
||||||
|
{ timezone: LOS_ANGELES, displayedTimezone: LOS_ANGELES },
|
||||||
|
{ formated: "March 22, 2020 (Los Angeles)" },
|
||||||
|
"it removes prefix and replaces `_`"
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
QUnit.test("option[timezone]", assert => {
|
||||||
|
freezeTime({}, () => {
|
||||||
|
assert.buildsCorrectDate(
|
||||||
|
{ timezone: SYDNEY, displayedTimezone: PARIS },
|
||||||
|
{ formated: "March 21, 2020" },
|
||||||
|
"it correctly parses a date with the given timezone context"
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
QUnit.test("option[recurring]", assert => {
|
||||||
|
freezeTime({ date: "2020-04-06 06:00", timezone: LAGOS }, () => {
|
||||||
|
assert.buildsCorrectDate(
|
||||||
|
{
|
||||||
|
date: "2019-11-25",
|
||||||
|
time: "11:00",
|
||||||
|
timezone: PARIS,
|
||||||
|
displayedTimezone: LAGOS,
|
||||||
|
recurring: "1.weeks"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
formated: "April 6, 2020 10:00 AM (Lagos)"
|
||||||
|
},
|
||||||
|
"it correctly formats a recurring date starting from a !isDST timezone to a isDST timezone date when displayed to a user using a timezone with no DST"
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
freezeTime({ date: "2020-04-06 01:00", timezone: SYDNEY }, () => {
|
||||||
|
assert.buildsCorrectDate(
|
||||||
|
{
|
||||||
|
date: "2020-03-09",
|
||||||
|
time: "02:00",
|
||||||
|
timezone: UTC,
|
||||||
|
recurring: "1.weeks",
|
||||||
|
calendar: false,
|
||||||
|
displayedTimezone: SYDNEY
|
||||||
|
},
|
||||||
|
{
|
||||||
|
formated: "April 6, 2020 12:00 PM (Sydney)"
|
||||||
|
},
|
||||||
|
"it correctly formats a recurring date spanning over weeks"
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
freezeTime({ date: "2020-04-07 22:00" }, () => {
|
||||||
|
assert.buildsCorrectDate(
|
||||||
|
{
|
||||||
|
date: "2019-11-25",
|
||||||
|
time: "11:00",
|
||||||
|
recurring: "1.weeks",
|
||||||
|
timezone: PARIS
|
||||||
|
},
|
||||||
|
{
|
||||||
|
formated: "April 13, 2020 11:00 AM"
|
||||||
|
},
|
||||||
|
"it correctly adds from a !isDST date to a isDST date"
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
freezeTime({ date: "2020-04-06 10:59" }, () => {
|
||||||
|
assert.buildsCorrectDate(
|
||||||
|
{
|
||||||
|
date: "2020-03-30",
|
||||||
|
time: "11:00",
|
||||||
|
recurring: "1.weeks",
|
||||||
|
timezone: PARIS
|
||||||
|
},
|
||||||
|
{
|
||||||
|
formated: "Today 11:00 AM"
|
||||||
|
},
|
||||||
|
"it works to the minute"
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
freezeTime({ date: "2020-04-06 11:01" }, () => {
|
||||||
|
assert.buildsCorrectDate(
|
||||||
|
{
|
||||||
|
date: "2020-03-30",
|
||||||
|
time: "11:00",
|
||||||
|
recurring: "1.weeks",
|
||||||
|
timezone: PARIS
|
||||||
|
},
|
||||||
|
{
|
||||||
|
formated: "April 13, 2020 11:00 AM"
|
||||||
|
},
|
||||||
|
"it works to the minute"
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
QUnit.test("option[countown]", assert => {
|
||||||
|
freezeTime({ date: "2020-03-21 23:59" }, () => {
|
||||||
|
assert.buildsCorrectDate(
|
||||||
|
{
|
||||||
|
countdown: true,
|
||||||
|
timezone: PARIS
|
||||||
|
},
|
||||||
|
{ formated: "a minute" },
|
||||||
|
"it shows the time remaining"
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
freezeTime({ date: "2020-03-22 00:01" }, () => {
|
||||||
|
assert.buildsCorrectDate(
|
||||||
|
{
|
||||||
|
countdown: true,
|
||||||
|
timezone: PARIS
|
||||||
|
},
|
||||||
|
{
|
||||||
|
formated: I18n.t(
|
||||||
|
"discourse_local_dates.relative_dates.countdown.passed"
|
||||||
|
)
|
||||||
|
},
|
||||||
|
"it shows the date has passed"
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
QUnit.test("option[calendar]", assert => {
|
||||||
|
freezeTime({ date: "2020-03-23 23:00" }, () => {
|
||||||
|
assert.buildsCorrectDate(
|
||||||
|
{ date: "2020-03-22", time: "23:59", timezone: PARIS },
|
||||||
|
{ formated: "Yesterday 11:59 PM" },
|
||||||
|
"it drops calendar mode when event date is more than one day before current date"
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
freezeTime({ date: "2020-03-20 23:59" }, () =>
|
||||||
|
assert.buildsCorrectDate(
|
||||||
|
{ date: "2020-03-21", time: "00:00", timezone: PARIS },
|
||||||
|
{ formated: "Tomorrow 12:00 AM" }
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
freezeTime({ date: "2020-03-20 23:59" }, () => {
|
||||||
|
assert.buildsCorrectDate(
|
||||||
|
{ date: "2020-03-21", time: "23:59", timezone: PARIS },
|
||||||
|
{ formated: "Tomorrow 11:59 PM" }
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
freezeTime({ date: "2020-03-21 00:00" }, () =>
|
||||||
|
assert.buildsCorrectDate(
|
||||||
|
{ date: "2020-03-21", time: "23:00", timezone: PARIS },
|
||||||
|
{ formated: "Today 11:00 PM" }
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
freezeTime({ date: "2020-03-22 23:59" }, () =>
|
||||||
|
assert.buildsCorrectDate(
|
||||||
|
{ date: "2020-03-21", time: "23:59", timezone: PARIS },
|
||||||
|
{ formated: "Yesterday 11:59 PM" }
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
freezeTime({ date: "2020-03-22 23:59" }, () =>
|
||||||
|
assert.buildsCorrectDate(
|
||||||
|
{ date: "2020-03-21", time: "23:59", timezone: PARIS },
|
||||||
|
{ formated: "Yesterday 11:59 PM" }
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
freezeTime({ date: "2020-03-22 23:59" }, () =>
|
||||||
|
assert.buildsCorrectDate(
|
||||||
|
{ calendar: false, date: "2020-03-21", time: "23:59", timezone: PARIS },
|
||||||
|
{ formated: "March 21, 2020 11:59 PM" },
|
||||||
|
"it doesn't use calendar when disabled"
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
freezeTime({ date: "2020-03-24 01:00" }, () =>
|
||||||
|
assert.buildsCorrectDate(
|
||||||
|
{ date: "2020-03-21", timezone: PARIS },
|
||||||
|
{ formated: "March 21, 2020" },
|
||||||
|
"it stops formating out of calendar range"
|
||||||
|
)
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
QUnit.test("previews", assert => {
|
||||||
|
freezeTime({ date: "2020-03-22" }, () => {
|
||||||
|
assert.buildsCorrectDate(
|
||||||
|
{ timezone: PARIS },
|
||||||
|
{
|
||||||
|
previews: [
|
||||||
|
{
|
||||||
|
current: true,
|
||||||
|
formated: "March 22, 2020 → March 23, 2020",
|
||||||
|
timezone: "Europe/Paris"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
freezeTime({ date: "2020-03-22", timezone: PARIS }, () => {
|
||||||
|
assert.buildsCorrectDate(
|
||||||
|
{ timezone: PARIS, timezones: [SYDNEY] },
|
||||||
|
{
|
||||||
|
previews: [
|
||||||
|
{
|
||||||
|
current: true,
|
||||||
|
formated: "March 22, 2020 → March 23, 2020",
|
||||||
|
timezone: "Europe/Paris"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
formated: "March 23, 2020 → March 23, 2020",
|
||||||
|
timezone: "Australia/Sydney"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
freezeTime({ date: "2020-03-22", timezone: PARIS }, () => {
|
||||||
|
assert.buildsCorrectDate(
|
||||||
|
{ timezone: PARIS, displayedTimezone: LOS_ANGELES },
|
||||||
|
{
|
||||||
|
previews: [
|
||||||
|
{
|
||||||
|
current: true,
|
||||||
|
formated: "March 22, 2020 → March 23, 2020",
|
||||||
|
timezone: "Europe/Paris"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
freezeTime({ date: "2020-03-22", timezone: PARIS }, () => {
|
||||||
|
assert.buildsCorrectDate(
|
||||||
|
{ timezone: PARIS, isplayedTimezone: PARIS },
|
||||||
|
{
|
||||||
|
previews: [
|
||||||
|
{
|
||||||
|
current: true,
|
||||||
|
formated: "March 22, 2020 → March 23, 2020",
|
||||||
|
timezone: "Europe/Paris"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
freezeTime({ date: "2020-03-22", timezone: PARIS }, () => {
|
||||||
|
assert.buildsCorrectDate(
|
||||||
|
{ timezone: PARIS, timezones: [PARIS] },
|
||||||
|
{
|
||||||
|
previews: [
|
||||||
|
{
|
||||||
|
current: true,
|
||||||
|
formated: "March 22, 2020 → March 23, 2020",
|
||||||
|
timezone: "Europe/Paris"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
freezeTime({ date: "2020-03-22", timezone: PARIS }, () => {
|
||||||
|
assert.buildsCorrectDate(
|
||||||
|
{ time: "11:34", timezone: PARIS, timezones: [PARIS] },
|
||||||
|
{
|
||||||
|
previews: [
|
||||||
|
{
|
||||||
|
current: true,
|
||||||
|
formated: "March 22, 2020 11:34 AM",
|
||||||
|
timezone: "Europe/Paris"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
freezeTime({ date: "2020-04-06", timezone: PARIS }, () => {
|
||||||
|
assert.buildsCorrectDate(
|
||||||
|
{ timezone: PARIS, date: "2020-04-07", timezones: [LONDON, LAGOS] },
|
||||||
|
{
|
||||||
|
previews: [
|
||||||
|
{
|
||||||
|
current: true,
|
||||||
|
formated: "April 7, 2020 → April 8, 2020",
|
||||||
|
timezone: "Europe/Paris"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
formated: "April 7, 2020 → April 7, 2020",
|
||||||
|
timezone: "Europe/London"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
formated: "April 7, 2020 → April 7, 2020",
|
||||||
|
timezone: "Africa/Lagos"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
Loading…
x
Reference in New Issue
Block a user