diff --git a/plugins/discourse-local-dates/assets/javascripts/discourse-local-dates.js.es6 b/plugins/discourse-local-dates/assets/javascripts/discourse-local-dates.js.es6
deleted file mode 100644
index aba41b68cef..00000000000
--- a/plugins/discourse-local-dates/assets/javascripts/discourse-local-dates.js.es6
+++ /dev/null
@@ -1,357 +0,0 @@
-// discourse-skip-module
-(function($) {
- const DATE_TEMPLATE = `
-
-
-
-
- `;
-
- const PREVIEW_TEMPLATE = `
-
-
-
-
- `;
-
- 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",
- `${htmlPreview}
`
- )
- .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);
diff --git a/plugins/discourse-local-dates/assets/javascripts/initializers/discourse-local-dates.js.es6 b/plugins/discourse-local-dates/assets/javascripts/initializers/discourse-local-dates.js.es6
index 352ea1159e6..7b3faee0c3c 100644
--- a/plugins/discourse-local-dates/assets/javascripts/initializers/discourse-local-dates.js.es6
+++ b/plugins/discourse-local-dates/assets/javascripts/initializers/discourse-local-dates.js.es6
@@ -1,11 +1,19 @@
import { withPluginApi } from "discourse/lib/plugin-api";
import showModal from "discourse/lib/show-modal";
+import LocalDateBuilder from "../lib/local-date-builder";
+
+const DATE_TEMPLATE = `
+
+
+
+
+`;
function initializeDiscourseLocalDates(api) {
api.decorateCooked(
- $elem => {
- $(".discourse-local-date", $elem).applyLocalDates();
- },
+ $elem => $(".discourse-local-date", $elem).applyLocalDates(),
{ id: "discourse-local-date" }
);
@@ -37,6 +45,69 @@ export default {
initialize(container) {
const siteSettings = container.lookup("site-settings:main");
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);
}
}
diff --git a/plugins/discourse-local-dates/assets/javascripts/lib/date-with-zone-helper.js.es6 b/plugins/discourse-local-dates/assets/javascripts/lib/date-with-zone-helper.js.es6
new file mode 100644
index 00000000000..9ffc6edb173
--- /dev/null
+++ b/plugins/discourse-local-dates/assets/javascripts/lib/date-with-zone-helper.js.es6
@@ -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);
+ }
+}
diff --git a/plugins/discourse-local-dates/assets/javascripts/lib/local-date-builder.js.es6 b/plugins/discourse-local-dates/assets/javascripts/lib/local-date-builder.js.es6
new file mode 100644
index 00000000000..3f81303cbf1
--- /dev/null
+++ b/plugins/discourse-local-dates/assets/javascripts/lib/local-date-builder.js.es6
@@ -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)})`;
+ }
+}
diff --git a/plugins/discourse-local-dates/plugin.rb b/plugins/discourse-local-dates/plugin.rb
index d4ac0160df5..b4fdae639fc 100644
--- a/plugins/discourse-local-dates/plugin.rb
+++ b/plugins/discourse-local-dates/plugin.rb
@@ -6,7 +6,6 @@
# author: Joffrey Jaffeux
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 'moment.js', :vendored_core_pretty_text
register_asset 'moment-timezone.js', :vendored_core_pretty_text
diff --git a/plugins/discourse-local-dates/test/javascripts/acceptance/local-dates-test.js.es6 b/plugins/discourse-local-dates/test/javascripts/acceptance/local-dates-test.js.es6
deleted file mode 100644
index 6d2a17f2a35..00000000000
--- a/plugins/discourse-local-dates/test/javascripts/acceptance/local-dates-test.js.es6
+++ /dev/null
@@ -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 = `");
-}
-
-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");
-});
diff --git a/plugins/discourse-local-dates/test/javascripts/lib/date-with-zone-helper-test.js.es6 b/plugins/discourse-local-dates/test/javascripts/lib/date-with-zone-helper-test.js.es6
new file mode 100644
index 00000000000..4f3351935a9
--- /dev/null
+++ b/plugins/discourse-local-dates/test/javascripts/lib/date-with-zone-helper-test.js.es6
@@ -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"
+ );
+});
diff --git a/plugins/discourse-local-dates/test/javascripts/lib/local-date-builder-test.js.es6 b/plugins/discourse-local-dates/test/javascripts/lib/local-date-builder-test.js.es6
new file mode 100644
index 00000000000..7895ca01cdf
--- /dev/null
+++ b/plugins/discourse-local-dates/test/javascripts/lib/local-date-builder-test.js.es6
@@ -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"
+ }
+ ]
+ }
+ );
+ });
+});