From 17e733b6a8fff9943b940bfe6815d8d1d9f5e4b6 Mon Sep 17 00:00:00 2001 From: David Taylor Date: Tue, 26 Jul 2022 11:14:22 +0100 Subject: [PATCH] DEV: Add optional timezone support to date-time-input-range (#17654) This allows consumers to pass in, and receive, timestamps for a different timezone. Previously, attempting this would lead to very strange behavior which would become worse the further the input timestamp's timezone was from the browser's timezone. The default behavior is unchanged - the browser's timezone will be assumed. --- .../discourse/app/components/date-input.js | 15 +++++-- .../app/components/date-time-input-range.js | 2 +- .../app/components/date-time-input.js | 36 +++++++++------ .../discourse/app/components/time-input.js | 19 +++++--- .../components/date-time-input-range.hbs | 4 +- .../components/date-time-input-range-test.js | 44 +++++++++++++++++-- 6 files changed, 92 insertions(+), 28 deletions(-) diff --git a/app/assets/javascripts/discourse/app/components/date-input.js b/app/assets/javascripts/discourse/app/components/date-input.js index ca6140d1dfa..16f12351120 100644 --- a/app/assets/javascripts/discourse/app/components/date-input.js +++ b/app/assets/javascripts/discourse/app/components/date-input.js @@ -52,7 +52,9 @@ export default Component.extend({ this._picker = picker; if (this._picker && this.date) { - this._picker.setDate(moment(this.date).toDate(), true); + const parsedDate = + this.date instanceof moment ? this.date : moment(this.date); + this._picker.setDate(parsedDate.toDate(), true); } }); }); @@ -62,11 +64,18 @@ export default Component.extend({ this._super(...arguments); if (this._picker && this.date) { - this._picker.setDate(moment(this.date).toDate(), true); + const parsedDate = + this.date instanceof moment ? this.date : moment(this.date); + this._picker.setDate(parsedDate.toDate(), true); } if (this._picker && this.relativeDate) { - this._picker.setMinDate(moment(this.relativeDate).toDate(), true); + const parsedRelativeDate = + this.relativeDate instanceof moment + ? this.relativeDate + : moment(this.relativeDate); + + this._picker.setMinDate(parsedRelativeDate.toDate(), true); } if (this._picker && !this.date) { diff --git a/app/assets/javascripts/discourse/app/components/date-time-input-range.js b/app/assets/javascripts/discourse/app/components/date-time-input-range.js index 6e5ff73a6b2..bea9f2795b7 100644 --- a/app/assets/javascripts/discourse/app/components/date-time-input-range.js +++ b/app/assets/javascripts/discourse/app/components/date-time-input-range.js @@ -23,7 +23,7 @@ export default Component.extend({ const diff = {}; if (options.prop === "from") { - if (value && value.isAfter(this.to)) { + if (this.to && value?.isAfter(this.to)) { diff[options.prop] = value; diff["to"] = value.clone().add(1, "hour"); } else { diff --git a/app/assets/javascripts/discourse/app/components/date-time-input.js b/app/assets/javascripts/discourse/app/components/date-time-input.js index 2093b71c6ef..864c1aeacbb 100644 --- a/app/assets/javascripts/discourse/app/components/date-time-input.js +++ b/app/assets/javascripts/discourse/app/components/date-time-input.js @@ -28,16 +28,19 @@ export default Component.extend({ ? this.date : this.relativeDate ? this.relativeDate - : moment(); + : moment.tz(this.resolvedTimezone); this.onChange( - moment({ - year: date.year(), - month: date.month(), - day: date.date(), - hours: time.hours, - minutes: time.minutes, - }) + moment.tz( + { + year: date.year(), + month: date.month(), + day: date.date(), + hours: time.hours, + minutes: time.minutes, + }, + this.resolvedTimezone + ) ); } }, @@ -49,15 +52,22 @@ export default Component.extend({ return; } - this.onChange && - this.onChange( - moment({ + this.onChange?.( + moment.tz( + { year: date.year(), month: date.month(), day: date.date(), hours: this.hours || 0, minutes: this.minutes || 0, - }) - ); + }, + this.resolvedTimezone + ) + ); + }, + + @computed + get resolvedTimezone() { + return this.timezone || moment.tz.guess(); }, }); diff --git a/app/assets/javascripts/discourse/app/components/time-input.js b/app/assets/javascripts/discourse/app/components/time-input.js index 1bda92bf5e1..3489388dc9a 100644 --- a/app/assets/javascripts/discourse/app/components/time-input.js +++ b/app/assets/javascripts/discourse/app/components/time-input.js @@ -66,7 +66,7 @@ export default Component.extend({ minimumTime: computed("relativeDate", "date", function () { if (this.relativeDate) { if (this.date) { - if (this.date.diff(this.relativeDate, "minutes") > 1440) { + if (!this.date.isSame(this.relativeDate, "day")) { return 0; } else { return this.relativeDate.hours() * 60 + this.relativeDate.minutes(); @@ -116,11 +116,18 @@ export default Component.extend({ let name = convertMinutesToString(option); let label; - if (this.minimumTime) { - const diff = option - this.minimumTime; - label = htmlSafe( - `${name} (${convertMinutesToDurationString(diff)})` - ); + if (this.date && this.relativeDate) { + const diff = this.date + .clone() + .startOf("day") + .add(option, "minutes") + .diff(this.relativeDate, "minutes"); + + if (diff < 1440) { + label = htmlSafe( + `${name} (${convertMinutesToDurationString(diff)})` + ); + } } return { diff --git a/app/assets/javascripts/discourse/app/templates/components/date-time-input-range.hbs b/app/assets/javascripts/discourse/app/templates/components/date-time-input-range.hbs index 7135271cbcc..9c4db3a0c73 100644 --- a/app/assets/javascripts/discourse/app/templates/components/date-time-input-range.hbs +++ b/app/assets/javascripts/discourse/app/templates/components/date-time-input-range.hbs @@ -1,3 +1,3 @@ - + - + diff --git a/app/assets/javascripts/discourse/tests/integration/components/date-time-input-range-test.js b/app/assets/javascripts/discourse/tests/integration/components/date-time-input-range-test.js index 22f5ab0ecee..b08a59bb2dd 100644 --- a/app/assets/javascripts/discourse/tests/integration/components/date-time-input-range-test.js +++ b/app/assets/javascripts/discourse/tests/integration/components/date-time-input-range-test.js @@ -1,8 +1,9 @@ import { module, test } from "qunit"; import { setupRenderingTest } from "discourse/tests/helpers/component-test"; -import { render } from "@ember/test-helpers"; +import { fillIn, render } from "@ember/test-helpers"; import { query } from "discourse/tests/helpers/qunit-helpers"; import { hbs } from "ember-cli-htmlbars"; +import selectKit from "discourse/tests/helpers/select-kit-helper"; function fromDateInput() { return query(".from.d-date-time-input .date-picker"); @@ -26,15 +27,52 @@ module("Integration | Component | date-time-input-range", function (hooks) { setupRenderingTest(hooks); test("default", async function (assert) { - this.setProperties({ from: DEFAULT_DATE_TIME, to: null }); + this.setProperties({ state: { from: DEFAULT_DATE_TIME, to: null } }); await render( - hbs`` + hbs`` ); assert.strictEqual(fromDateInput().value, "2019-01-29"); assert.strictEqual(fromTimeInput().dataset.name, "14:45"); assert.strictEqual(toDateInput().value, ""); assert.strictEqual(toTimeInput().dataset.name, "--:--"); + + await fillIn(toDateInput(), "2019-01-29"); + const toTimeSelectKit = selectKit(".to .d-time-input .select-kit"); + await toTimeSelectKit.expand(); + let rows = toTimeSelectKit.rows(); + assert.equal(rows[0].dataset.name, "14:45"); + assert.equal(rows[rows.length - 1].dataset.name, "23:45"); + await toTimeSelectKit.collapse(); + + await fillIn(toDateInput(), "2019-01-30"); + await toTimeSelectKit.expand(); + rows = toTimeSelectKit.rows(); + + assert.equal(rows[0].dataset.name, "00:00"); + assert.equal(rows[rows.length - 1].dataset.name, "23:45"); + }); + + test("timezone support", async function (assert) { + this.setProperties({ + state: { from: moment.tz(DEFAULT_DATE_TIME, "Europe/Paris"), to: null }, + }); + + await render( + hbs`` + ); + + assert.strictEqual(fromDateInput().value, "2019-01-29"); + assert.strictEqual(fromTimeInput().dataset.name, "15:45"); + assert.strictEqual(toDateInput().value, ""); + assert.strictEqual(toTimeInput().dataset.name, "--:--"); + + await fillIn(toDateInput(), "2019-01-29"); + const toTimeSelectKit = selectKit(".to .d-time-input .select-kit"); + await toTimeSelectKit.expand(); + await toTimeSelectKit.selectRowByName("19:15"); + + assert.equal(this.state.to.toString(), "Tue Jan 29 2019 19:15:00 GMT+0100"); }); });