diff --git a/spec/coffeescripts/util/coupleTimeFieldsSpec.js b/spec/coffeescripts/util/coupleTimeFieldsSpec.js
index 361b5437ec8..3ad1b37a625 100644
--- a/spec/coffeescripts/util/coupleTimeFieldsSpec.js
+++ b/spec/coffeescripts/util/coupleTimeFieldsSpec.js
@@ -19,7 +19,7 @@
import london from 'timezone/Europe/London'
import * as tz from '@canvas/datetime'
import coupleTimeFields from '@canvas/calendar/jquery/coupleTimeFields'
-import DatetimeField from '@canvas/datetime/jquery/DatetimeField'
+import DatetimeField, {PARSE_RESULTS} from '@canvas/datetime/jquery/DatetimeField'
import $ from 'jquery'
import 'jquery-migrate'
import fakeENV from 'helpers/fakeENV'
@@ -80,7 +80,7 @@ test('leaves invalid start alone', function () {
this.end.setTime(fixed)
coupleTimeFields(this.$start, this.$end)
equal(this.$start.val(), 'invalid')
- equal(this.start.invalid, true)
+ equal(this.start.valid, PARSE_RESULTS.ERROR)
})
test('leaves invalid end alone', function () {
@@ -89,7 +89,7 @@ test('leaves invalid end alone', function () {
this.end.setFromValue()
coupleTimeFields(this.$start, this.$end)
equal(this.$end.val(), 'invalid')
- equal(this.end.invalid, true)
+ equal(this.end.valid, PARSE_RESULTS.ERROR)
})
test('interprets time as occurring on date', function () {
@@ -156,7 +156,7 @@ test('leaves invalid start alone', function () {
this.end.setTime(fixed)
this.$end.trigger('blur')
equal(this.$start.val(), 'invalid')
- equal(this.start.invalid, true)
+ equal(this.start.valid, PARSE_RESULTS.ERROR)
})
test('leaves invalid end alone', function () {
@@ -165,7 +165,7 @@ test('leaves invalid end alone', function () {
this.end.setFromValue()
this.$start.trigger('blur')
equal(this.$end.val(), 'invalid')
- equal(this.end.invalid, true)
+ equal(this.end.valid, PARSE_RESULTS.ERROR)
})
test('does not rewrite blurred input', function () {
diff --git a/spec/coffeescripts/widgets/DatetimeFieldSpec.js b/spec/coffeescripts/widgets/DatetimeFieldSpec.js
index 417663286b0..c43d87e1145 100644
--- a/spec/coffeescripts/widgets/DatetimeFieldSpec.js
+++ b/spec/coffeescripts/widgets/DatetimeFieldSpec.js
@@ -20,6 +20,7 @@ import DatetimeField, {
DATE_FORMAT_OPTIONS,
DATETIME_FORMAT_OPTIONS,
TIME_FORMAT_OPTIONS,
+ PARSE_RESULTS,
} from '@canvas/datetime/jquery/DatetimeField'
import '@canvas/datetime/jquery'
import $ from 'jquery'
@@ -32,8 +33,8 @@ import juneau from 'timezone/America/Juneau'
import fakeENV from 'helpers/fakeENV'
import moment from 'moment'
-const moonwalk = new Date('1969-07-21T02:56:00Z')
-const john_glenn = new Date('1962-02-20T14:47:39')
+const challenger = new Date('1986-01-28T16:39:00Z')
+const columbia = new Date('2003-02-01T13:59:00Z')
QUnit.module('processTimeOptions', {
setup() {
@@ -222,9 +223,9 @@ test('should add hidden input when requested', function () {
})
test('should initialize from the inputdate data', function () {
- this.$field.data('inputdate', '1969-07-21T02:56:00Z')
+ this.$field.data('inputdate', '1986-01-28T16:39:00Z')
const field = new DatetimeField(this.$field, {})
- equal(field.$suggest.text(), 'Sun, Jul 20, 1969, 9:56 PM')
+ equal(field.$suggest.text(), 'Tue, Jan 28, 1986, 11:39 AM')
})
test('should initialize from the initial value attribute if present, and then remove it', function () {
@@ -239,8 +240,8 @@ test('should initialize from the initial value attribute if present, and then re
test('should tie it to update on keyup only', function () {
const field = new DatetimeField(this.$field, {})
- this.$field.val('Jul 21, 1969 5:56am').trigger('keyup')
- equal(field.$suggest.text(), 'Mon, Jul 21, 1969, 5:56 AM')
+ this.$field.val('Jan 28, 1986 5:56am').trigger('keyup')
+ equal(field.$suggest.text(), 'Tue, Jan 28, 1986, 5:56 AM')
})
QUnit.module('setFromValue', {
@@ -269,9 +270,9 @@ test('should set data fields', function () {
})
test('should set suggest text', function () {
- this.$field.val('Jul 21, 1969 at 2:56am')
+ this.$field.val('Jan 28, 1986 at 2:56am')
this.field.setFromValue()
- equal(this.field.$suggest.text(), 'Mon, Jul 21, 1969, 2:56 AM')
+ equal(this.field.$suggest.text(), 'Tue, Jan 28, 1986, 2:56 AM')
})
QUnit.module('parseValue', {
@@ -282,15 +283,15 @@ QUnit.module('parseValue', {
})
test('sets @fudged according to browser (fudged) timezone', function () {
- this.$field.val(tz.format(moonwalk, '%b %-e, %Y at %-l:%M%P')).change()
+ this.$field.val(tz.format(challenger, '%b %-e, %Y at %-l:%M%P')).change()
this.field.parseValue()
- equal(+this.field.fudged, +$.fudgeDateForProfileTimezone(moonwalk))
+ equal(+this.field.fudged, +$.fudgeDateForProfileTimezone(challenger))
})
test('sets @datetime according to profile timezone', function () {
- this.$field.val(tz.format(moonwalk, '%b %-e, %Y at %-l:%M%P')).change()
+ this.$field.val(tz.format(challenger, '%b %-e, %Y at %-l:%M%P')).change()
this.field.parseValue()
- equal(+this.field.datetime, +moonwalk)
+ equal(+this.field.datetime, +challenger)
})
test('sets @showTime true by default', function () {
@@ -300,14 +301,14 @@ test('sets @showTime true by default', function () {
})
test('sets @showTime false when value is midnight in profile timezone', function () {
- this.$field.val('Jan 1, 1970 at 12:00am').change()
+ this.$field.val('Jan 1, 1990 at 12:00am').change()
this.field.parseValue()
equal(this.field.showTime, false)
})
test('sets @showTime true for midnight if @alwaysShowTime', function () {
this.field.alwaysShowTime = true
- this.$field.val('Jan 1, 1970 at 12:00am').change()
+ this.$field.val('Jan 1, 1990 at 12:00am').change()
this.field.parseValue()
equal(this.field.showTime, true)
})
@@ -320,17 +321,17 @@ test('sets @showTime false for non-midnight if not @allowTime', function () {
})
test('sets not @blank and not @invalid on valid input', function () {
- this.$field.val('Jan 1, 1970 at 12:00am').change()
+ this.$field.val('Jan 1, 1990 at 12:00am').change()
this.field.parseValue()
equal(this.field.blank, false)
- equal(this.field.invalid, false)
+ equal(this.field.valid, PARSE_RESULTS.VALID)
})
test('sets @blank and not @invalid and null dates when no input', function () {
this.$field.val('').change()
this.field.parseValue()
equal(this.field.blank, true)
- equal(this.field.invalid, false)
+ equal(this.field.valid, PARSE_RESULTS.VALID)
equal(this.field.datetime, null)
equal(this.field.fudged, null)
})
@@ -339,7 +340,7 @@ test('sets @invalid and not @blank and null dates when invalid input', function
this.$field.val('invalid').change()
this.field.parseValue()
equal(this.field.blank, false)
- equal(this.field.invalid, true)
+ equal(this.field.valid, PARSE_RESULTS.ERROR)
equal(this.field.datetime, null)
equal(this.field.fudged, null)
})
@@ -363,18 +364,18 @@ test('interprets bare numbers >= 8 in time-only fields as 24-hour', function ()
test('interprets time-only fields as occurring on implicit date if set', function () {
this.field.showDate = false
- this.field.setDate(moonwalk)
+ this.field.setDate(challenger)
this.$field.val('12PM').change()
this.field.parseValue()
- equal(tz.format(this.field.datetime, '%F %T'), `${tz.format(moonwalk, '%F ')}12:00:00`)
+ equal(tz.format(this.field.datetime, '%F %T'), `${tz.format(challenger, '%F ')}12:00:00`)
})
test('setDate changes the date of an existing time field', function () {
this.field.showDate = false
- this.field.setDate(moonwalk)
+ this.field.setDate(challenger)
this.$field.val('12PM').change()
- this.field.setDate(john_glenn)
- equal(tz.format(this.field.datetime, '%F %T'), `${tz.format(john_glenn, '%F ')}12:00:00`)
+ this.field.setDate(columbia)
+ equal(tz.format(this.field.datetime, '%F %T'), `${tz.format(columbia, '%F ')}12:00:00`)
})
QUnit.module('updateData', {
@@ -388,10 +389,10 @@ QUnit.module('updateData', {
fakeENV.setup({TIMEZONE: 'America/Detroit'})
this.$field = $('')
- this.$field.val('Jan 1, 1970 at 12:01am')
+ this.$field.val('Jan 1, 1990 at 12:01am')
this.field = new DatetimeField(this.$field, {})
- this.field.datetime = moonwalk
- this.field.fudged = $.fudgeDateForProfileTimezone(moonwalk)
+ this.field.datetime = challenger
+ this.field.fudged = $.fudgeDateForProfileTimezone(challenger)
},
teardown() {
@@ -407,7 +408,7 @@ test('sets date field to fudged time', function () {
test('sets unfudged-date field to actual time', function () {
this.field.updateData()
- equal(+this.$field.data('unfudged-date'), +moonwalk)
+ equal(+this.$field.data('unfudged-date'), +challenger)
})
test('sets invalid field', function () {
@@ -428,16 +429,16 @@ test('sets value of hiddenInput, if present, to fudged time as ISO8601', functio
test('sets time-* to fudged, 12-hour values', function () {
this.field.updateData()
- equal(this.$field.data('time-hour'), '9')
- equal(this.$field.data('time-minute'), '56')
- equal(this.$field.data('time-ampm'), 'PM')
+ equal(this.$field.data('time-hour'), '11')
+ equal(this.$field.data('time-minute'), '39')
+ equal(this.$field.data('time-ampm'), 'AM')
})
test('sets time-* to fudged, 24-hour values', function () {
ENV.LOCALE = 'pt-BR'
this.field.updateData()
- equal(this.$field.data('time-hour'), '21')
- equal(this.$field.data('time-minute'), '56')
+ equal(this.$field.data('time-hour'), '11')
+ equal(this.$field.data('time-minute'), '39')
equal(this.$field.data('time-ampm'), null)
})
@@ -458,7 +459,7 @@ test('clear time-* to null if blank', function () {
})
test('clear time-* to null if invalid', function () {
- this.field.invalid = true
+ this.field.valid = PARSE_RESULTS.ERROR
this.field.updateData()
equal(this.$field.data('time-hour'), null)
})
@@ -510,7 +511,7 @@ test('omits course suggest text if formatSuggestContext is empty', function () {
test('adds invalid_datetime class to suggest if invalid', function () {
this.field.updateSuggest()
ok(!this.field.$suggest.hasClass('invalid_datetime'))
- this.field.invalid = true
+ this.field.valid = PARSE_RESULTS.ERROR
this.field.updateSuggest()
ok(this.field.$suggest.hasClass('invalid_datetime'))
})
@@ -533,6 +534,12 @@ test('should alert screenreader on an invalid parse no matter what', function ()
ok(this.field.debouncedSRFME.withArgs("That's not a date!").called)
})
+test('should alert on an impossible year entered', function () {
+ this.$field.val('Jul 19 1964')
+ this.$field.change()
+ ok(this.field.debouncedSRFME.withArgs('Year is too far in the past.').called)
+})
+
test('flashes suggest text to screenreader on typed input', function () {
const value = 'suggested value'
this.field.formatSuggest = () => value
@@ -573,7 +580,7 @@ QUnit.module('formatSuggest', {
})
fakeENV.setup({TIMEZONE: 'America/Detroit'})
this.$field = $('')
- this.$field.val('Jul 20, 1969 at 9:56pm')
+ this.$field.val('Jan 28, 1986 at 11:39am')
this.field = new DatetimeField(this.$field, {})
},
@@ -584,7 +591,7 @@ QUnit.module('formatSuggest', {
})
test('returns result formatted in profile timezone', function () {
- equal(this.field.formatSuggest(), 'Sun, Jul 20, 1969, 9:56 PM')
+ equal(this.field.formatSuggest(), 'Tue, Jan 28, 1986, 11:39 AM')
})
test('returns "" if @blank', function () {
@@ -593,23 +600,23 @@ test('returns "" if @blank', function () {
})
test('returns error message if @invalid', function () {
- this.field.invalid = true
+ this.field.valid = PARSE_RESULTS.ERROR
equal(this.field.formatSuggest(), this.field.parseError)
})
test('returns date only if @showTime false', function () {
this.field.showTime = false
- equal(this.field.formatSuggest(), 'Sun, Jul 20, 1969')
+ equal(this.field.formatSuggest(), 'Tue, Jan 28, 1986')
})
test('returns time only if @showDate false', function () {
this.field.showDate = false
- equal(this.field.formatSuggest(), '9:56 PM')
+ equal(this.field.formatSuggest(), '11:39 AM')
})
test('localizes formatting of dates and times', function () {
ENV.LOCALE = 'pt-BR'
- equal(this.field.formatSuggest(), 'dom., 20 de jul. de 1969, 21:56')
+ equal(this.field.formatSuggest(), 'ter., 28 de jan. de 1986, 11:39')
})
QUnit.module('formatSuggestContext', {
@@ -623,7 +630,7 @@ QUnit.module('formatSuggestContext', {
})
fakeENV.setup({TIMEZONE: 'America/Detroit', CONTEXT_TIMEZONE: 'America/Juneau'})
this.$field = $('')
- this.$field.val('Jul 20, 1969 at 9:56pm')
+ this.$field.val('Jan 28, 1986 at 11:39am')
this.field = new DatetimeField(this.$field, {})
},
@@ -634,7 +641,7 @@ QUnit.module('formatSuggestContext', {
})
test('returns result formatted in course timezone', function () {
- equal(this.field.formatSuggestContext(), 'Sun, Jul 20, 1969, 7:56 PM')
+ equal(this.field.formatSuggestContext(), 'Tue, Jan 28, 1986, 7:39 AM')
})
test('returns "" if @blank', function () {
@@ -643,7 +650,7 @@ test('returns "" if @blank', function () {
})
test('returns "" if @invalid', function () {
- this.field.invalid = true
+ this.field.valid = PARSE_RESULTS.ERROR
equal(this.field.formatSuggestContext(), '')
})
@@ -654,7 +661,7 @@ test('returns "" if @showTime false', function () {
test('returns time only if @showDate false', function () {
this.field.showDate = false
- equal(this.field.formatSuggestContext(), '7:56 PM')
+ equal(this.field.formatSuggestContext(), '7:39 AM')
})
QUnit.module('normalizeValue', {
@@ -740,30 +747,30 @@ test('sets to blank with null value', function () {
equal(this.field.datetime, null)
equal(this.field.fudged, null)
equal(this.field.blank, true)
- equal(this.field.invalid, false)
+ equal(this.field.valid, PARSE_RESULTS.VALID)
equal(this.$field.val(), '')
})
test('treats value as unfudged', function () {
- this.field.setFormattedDatetime(moonwalk, DATETIME_FORMAT_OPTIONS)
- equal(+this.field.datetime, +moonwalk)
- equal(+this.field.fudged, +$.fudgeDateForProfileTimezone(moonwalk))
+ this.field.setFormattedDatetime(challenger, DATETIME_FORMAT_OPTIONS)
+ equal(+this.field.datetime, +challenger)
+ equal(+this.field.fudged, +$.fudgeDateForProfileTimezone(challenger))
equal(this.field.blank, false)
- equal(this.field.invalid, false)
- equal(this.$field.val(), 'Sun, Jul 20, 1969, 9:56 PM')
+ equal(this.field.valid, PARSE_RESULTS.VALID)
+ equal(this.$field.val(), 'Tue, Jan 28, 1986, 11:39 AM')
})
test('formats value into val() according to date/time requests', function () {
- this.field.setFormattedDatetime(moonwalk, DATE_FORMAT_OPTIONS)
- equal(this.$field.val(), 'Sun, Jul 20, 1969')
- this.field.setFormattedDatetime(moonwalk, TIME_FORMAT_OPTIONS)
- equal(this.$field.val(), '9:56 PM')
+ this.field.setFormattedDatetime(challenger, DATE_FORMAT_OPTIONS)
+ equal(this.$field.val(), 'Tue, Jan 28, 1986')
+ this.field.setFormattedDatetime(challenger, TIME_FORMAT_OPTIONS)
+ equal(this.$field.val(), '11:39 AM')
})
test('localizes value', function () {
ENV.LOCALE = 'de'
- this.field.setFormattedDatetime(moonwalk, DATETIME_FORMAT_OPTIONS)
- equal(this.$field.val(), 'So., 20. Juli 1969, 21:56')
+ this.field.setFormattedDatetime(challenger, DATETIME_FORMAT_OPTIONS)
+ equal(this.$field.val(), 'Di., 28. Jan. 1986, 11:39')
})
QUnit.module('setDate/setTime/setDatetime', {
@@ -786,16 +793,16 @@ QUnit.module('setDate/setTime/setDatetime', {
})
test('setDate formats into val() with just date', function () {
- this.field.setDate(moonwalk)
- equal(this.$field.val(), 'Sun, Jul 20, 1969')
+ this.field.setDate(challenger)
+ equal(this.$field.val(), 'Tue, Jan 28, 1986')
})
test('setTime formats into val() with just time', function () {
- this.field.setTime(moonwalk)
- equal(this.$field.val(), '9:56 PM')
+ this.field.setTime(challenger)
+ equal(this.$field.val(), '11:39 AM')
})
test('setDatetime formats into val() with full date and time', function () {
- this.field.setDatetime(moonwalk)
- equal(this.$field.val(), 'Sun, Jul 20, 1969, 9:56 PM')
+ this.field.setDatetime(challenger)
+ equal(this.$field.val(), 'Tue, Jan 28, 1986, 11:39 AM')
})
diff --git a/ui/features/course_settings/react/components/CourseAvailabilityOptions.jsx b/ui/features/course_settings/react/components/CourseAvailabilityOptions.jsx
index 426957220fa..687887283aa 100644
--- a/ui/features/course_settings/react/components/CourseAvailabilityOptions.jsx
+++ b/ui/features/course_settings/react/components/CourseAvailabilityOptions.jsx
@@ -79,9 +79,9 @@ export default function CourseAvailabilityOptions({canManage, viewPastLocked, vi
const formatDate = date => tz.format(date, 'date.formats.full')
- const parseDate = (date, tz) => {
+ const parseDate = (date, originTZ) => {
const dateObj = new Date(date)
- const parsedDate = changeTimezone(dateObj, {originTZ: tz, desiredTZ: ENV.TIMEZONE})
+ const parsedDate = changeTimezone(dateObj, {originTZ, desiredTZ: ENV.TIMEZONE})
return formatDate(parsedDate)
}
diff --git a/ui/shared/datetime/jquery/DatetimeField.js b/ui/shared/datetime/jquery/DatetimeField.js
index 291bdf55e47..1d80fa9a3fd 100644
--- a/ui/shared/datetime/jquery/DatetimeField.js
+++ b/ui/shared/datetime/jquery/DatetimeField.js
@@ -40,14 +40,23 @@ const DATE_FORMAT_OPTIONS = {
year: 'numeric',
}
+const EARLIEST_YEAR = 1980 // do not allow any manually entered year before this
+
+const PARSE_RESULTS = {
+ VALID: 0,
+ ERROR: 1,
+ BAD_YEAR: 2,
+}
+
const DATETIME_FORMAT_OPTIONS = {...DATE_FORMAT_OPTIONS, ...TIME_FORMAT_OPTIONS}
Object.freeze(TIME_FORMAT_OPTIONS)
Object.freeze(DATE_FORMAT_OPTIONS)
Object.freeze(DATETIME_FORMAT_OPTIONS)
+Object.freeze(PARSE_RESULTS)
// for tests only
-export {TIME_FORMAT_OPTIONS, DATE_FORMAT_OPTIONS, DATETIME_FORMAT_OPTIONS}
+export {TIME_FORMAT_OPTIONS, DATE_FORMAT_OPTIONS, DATETIME_FORMAT_OPTIONS, PARSE_RESULTS}
function formatter(zone, formatOptions = DATETIME_FORMAT_OPTIONS) {
const options = {...formatOptions}
@@ -149,6 +158,10 @@ export default class DatetimeField {
}
}
+ invalid() {
+ return this.valid !== PARSE_RESULTS.VALID
+ }
+
processTimeOptions(options) {
// default undefineds to false
let {timeOnly, dateOnly} = options
@@ -285,26 +298,29 @@ export default class DatetimeField {
}
parseValue(val) {
+ const previousDate = this.datetime
if (typeof val === 'undefined' && this.$field.data('inputdate')) {
const inputdate = this.$field.data('inputdate')
this.datetime = inputdate instanceof Date ? inputdate : new Date(inputdate)
this.blank = false
- this.invalid = this.datetime === null
+ this.valid = PARSE_RESULTS.VALID
+ if (this.datetime === null) this.valid = PARSE_RESULTS.ERROR
+ if (this.datetime && this.datetime.getFullYear() < EARLIEST_YEAR)
+ this.valid = PARSE_RESULTS.BAD_YEAR
this.$field.data('inputdate', null)
} else {
- const previousDate = this.datetime
- if (val) {
- this.setFormattedDatetime(val, TIME_FORMAT_OPTIONS)
- }
+ if (val) this.setFormattedDatetime(val, this.intlFormatType())
const value = this.normalizeValue(this.$field.val())
this.datetime = tz.parse(value)
this.blank = !value
- this.invalid = !this.blank && this.datetime === null
- // If the date is invalid, revert to the previous date
- if (this.invalid) {
- this.datetime = previousDate
- }
+ this.valid = PARSE_RESULTS.VALID
+ if (!this.blank && this.datetime === null) this.valid = PARSE_RESULTS.ERROR
+ if (this.datetime && this.datetime.getFullYear() < EARLIEST_YEAR)
+ this.valid = PARSE_RESULTS.BAD_YEAR
}
+ // If the date is invalid, revert to the previous date
+ if (this.invalid()) this.datetime = previousDate
+
if (this.datetime && !this.showDate && this.implicitDate) {
this.datetime = tz.mergeTimeAndDate(this.datetime, this.implicitDate)
}
@@ -326,7 +342,7 @@ export default class DatetimeField {
this.fudged = null
this.$field.val('')
}
- this.invalid = false
+ this.valid = PARSE_RESULTS.VALID
this.showTime = this.alwaysShowTime || (this.allowTime && !tz.isMidnight(this.datetime))
this.update()
this.updateSuggest(false)
@@ -344,7 +360,7 @@ export default class DatetimeField {
date: this.fudged,
iso8601,
blank: this.blank,
- invalid: this.invalid,
+ invalid: this.invalid(),
})
if (this.$hiddenInput) {
@@ -354,7 +370,7 @@ export default class DatetimeField {
// date_fields and time_fields don't have timepicker data fields
if (!(this.showDate && this.allowTime)) return
- if (this.invalid || this.blank || !this.showTime) {
+ if (this.invalid() || this.blank || !this.showTime) {
this.$field.data({
'time-hour': null,
'time-minute': null,
@@ -384,8 +400,8 @@ export default class DatetimeField {
}
this.$contextSuggest.text(contextText).show()
}
- this.$suggest.toggleClass('invalid_datetime', this.invalid).text(localText)
- if (show || this.$contextSuggest || this.invalid) {
+ this.$suggest.toggleClass('invalid_datetime', this.invalid()).text(localText)
+ if (show || this.$contextSuggest || this.invalid()) {
this.$suggest.show()
return
}
@@ -407,7 +423,7 @@ export default class DatetimeField {
}
updateAria() {
- this.$field.attr('aria-invalid', !!this.invalid)
+ this.$field.attr('aria-invalid', this.invalid())
}
intlFormatType() {
@@ -417,13 +433,13 @@ export default class DatetimeField {
}
formatSuggest() {
- if (this.invalid) return this.parseError
+ if (this.invalid()) return this.parseError
if (this.blank) return ''
return formatter(ENV.TIMEZONE, this.intlFormatType()).format(this.datetime)
}
formatSuggestContext() {
- if (this.invalid || !this.showTime || this.blank) return ''
+ if (this.invalid() || !this.showTime || this.blank) return ''
return formatter(this.contextTimezone, this.intlFormatType()).format(this.datetime)
}
@@ -438,6 +454,7 @@ export default class DatetimeField {
}
get parseError() {
+ if (this.valid === PARSE_RESULTS.BAD_YEAR) return I18n.t('Year is too far in the past.')
return I18n.t('errors.not_a_date', "That's not a date!")
}
}
diff --git a/ui/shared/datetime/react/components/DateInput.tsx b/ui/shared/datetime/react/components/DateInput.tsx
index a2fa7f00405..839fd99f42f 100644
--- a/ui/shared/datetime/react/components/DateInput.tsx
+++ b/ui/shared/datetime/react/components/DateInput.tsx
@@ -49,6 +49,8 @@ type BlurReturn = SyntheticEvent | KeyboardEvent
const I18n = useI18nScope('app_shared_components_canvas_date_time')
+const EARLIEST_YEAR = 1980 // do not allow any manually entered year before this
+
export type CanvasDateInputProps = {
/**
* Represents the initial date to be selected. May be `undefined` for no selected date.
@@ -288,10 +290,22 @@ export default function CanvasDateInput({
if (isShowingCalendar && withRunningValue) handleHideCalendar()
const newDate = tz.parse(value, timezone)
if (newDate) {
+ const year = newDate.getFullYear()
+ if (year < EARLIEST_YEAR) {
+ setInternalMessages([
+ {
+ type: 'error',
+ text: I18n.t('Year %{year} is too far in the past', {year: String(year)}),
+ },
+ ])
+ return
+ }
const msgs: Messages = withRunningValue ? [{type: 'success', text: formatDate(newDate)}] : []
setRenderedMoment(moment.tz(newDate, timezone))
setInternalMessages(msgs)
- } else if (value === '') {
+ return
+ }
+ if (value === '') {
setInternalMessages([])
} else {
const text = invalidText(value)