fonteva-design-guide
Version:
## Dev, Build and Test
815 lines (727 loc) • 35.4 kB
JavaScript
/**
* Created by alain on 8/6/2020.
*/
import { LightningElement, api } from 'lwc';
import LOCALE from '@salesforce/i18n/locale';
import { groupMMDDYYYY, groupDDMMYYYY } from './dateFormatsLocales';
/** labels */
import IsNotALeapYear from '@salesforce/label/c.Pfm_Date_Not_A_Leap_Year';
import InvalidMonthMin from '@salesforce/label/c.Pfm_Date_Invalid_Min_Month';
import InvalidMonthMax from '@salesforce/label/c.Pfm_Date_Invalid_Max_Month';
import InvalidDayInMonth from '@salesforce/label/c.Pfm_Date_Invalid_Day_In_Month';
import InvalidMinDay from '@salesforce/label/c.Pfm_Date_Invalid_Min_Day';
import InvalidMinAllowableYear from '@salesforce/label/c.Pfm_Date_Invalid_Min_Allowable_Year';
import InvalidMaxAllowableYear from '@salesforce/label/c.Pfm_Date_Invalid_Max_Allowable_Year';
import EnterDateInFormat from '@salesforce/label/c.Pfm_Date_Enter_Date_In_Format';
export default class PfmInputDate extends LightningElement {
delimiter;
locale;
minYear;
maxYear;
// deprecated in favor of "value"
existingDate;
value;
required;
// exposed for unit tests
chosenMinYear;
chosenMaxYear;
fullDateSnapshot;
isValid;
chosenDelimiter;
chosenLocale;
allowableDelimitersRegexStr;
localeFormat;
shouldAddDelimiters;
shouldReposition;
lastSelectionStart;
dateFormatForCountries;
errors;
feedbackElement;
connectedCallback() {
// allowable delimiters used to separate month, day, and year from one another
const allowableDelimiters = ['/', '-', '_', '|'];
this.chosenDelimiter = allowableDelimiters.indexOf(this.delimiter) > -1 ? this.delimiter : '/';
// get country from passed value, or from the locale set in Salesforce, or default to 'us'
if(this.locale) {
this.chosenLocale = this.locale.toLowerCase();
}
else if(LOCALE) {
if(LOCALE.split('-').length == 1) {
this.chosenLocale = LOCALE.split('-')[0].toLowerCase();
}
else if(LOCALE.split('-').length == 2) {
this.chosenLocale = LOCALE.split('-')[1].toLowerCase();
}
}
else {
this.chosenLocale = 'us';
}
this.chosenMinYear = this.minYear || 1900;
this.chosenMaxYear = this.maxYear || 2100;
this.isValid = true;
// regular expression portion for the allowable delimiter characters
this.allowableDelimitersRegexStr = '';
// the format currently used for the date entered (e.g. MM/DD/YYYY) - defaults to that of US
this.localeFormat = 'MM' + this.chosenDelimiter + 'DD' + this.chosenDelimiter + 'YYYY';
// flag to re-insert the delimiters into the raw value of the date
this.shouldAddDelimiters = false;
// flag to re-position the user's cursor somewhere in the date field
this.shouldReposition = false;
// last position of the user's cursor within the date field
this.lastSelectionStart = 0;
// contains different variations of MM/DD/YYYY and what countries utilize them
this.dateFormatForCountries = new Map();
// list of all current user errors
this.errors = [];
// if minimum year is greater than maximum year
// or if either is not a number
// or if the minimum year is less than 1900
// or if the maximum year is greater than 2100
// then set both to current year
if (
this.chosenMinYear > this.chosenMaxYear ||
isNaN(this.chosenMinYear) ||
isNaN(this.chosenMaxYear) ||
this.chosenMinYear < 1900 ||
this.chosenMaxYear > 2100
) {
this.chosenMinYear = this.chosenMaxYear = new Date().getFullYear();
}
// build the regular expression portion for the allowable delimiters
for (let i = 0; i < allowableDelimiters.length; i++) {
this.allowableDelimitersRegexStr += '\\' + allowableDelimiters[i];
}
this.allowableDelimitersRegexStr = '[' + this.allowableDelimitersRegexStr + ']';
// set key/value pairs where key is the MM+delimiter+DD+delimiter+YYYY format and the value is a list of participating countries (via their abbreviations)
this.dateFormatForCountries.set('MM' + this.chosenDelimiter + 'DD' + this.chosenDelimiter + 'YYYY', groupMMDDYYYY);
this.dateFormatForCountries.set('DD' + this.chosenDelimiter + 'MM' + this.chosenDelimiter + 'YYYY', groupDDMMYYYY);
// look through all supported date formats
this.dateFormatForCountries.forEach((countries, dateFormat) => {
// if a country is found for a given format
if (countries.indexOf(this.chosenLocale) > -1) {
// set the appropriate placeholder format for the date input field
this.localeFormat = dateFormat;
}
});
}
updateValue(value) {
if(value) {
this.value = value;
this.updateDateField();
}
}
updateDateField() {
this.feedbackElement = this.template.querySelector('.slds-form-element__help');
// break up the passed in date into day, month, and year
const existingYear = this.value ? this.value.split('-')[0] : 0;
const existingMonth = this.value ? this.value.split('-')[1] : 0;
const existingDay = this.value ? this.value.split('-')[2] : 0;
// if default month, day, and year are passed into the component and are numbers
if (Number(existingMonth) > 0 && Number(existingDay) > 0 && Number(existingYear) > 0) {
// ensure that month, day, and year are adjusted to their proper limits when going beyond them
let month, day, year;
const adjustMonth = this.adjustMonth(existingMonth);
if (adjustMonth.isAdjusted) {
this.logToScreen(this.feedbackElement, adjustMonth.feedback + '<br>');
month = this.adjustMonth(existingMonth).month;
} else {
month = existingMonth;
}
const adjustDay = this.adjustDay(existingDay, month);
if (adjustDay.isAdjusted) {
this.logToScreen(this.feedbackElement, this.feedbackElement.innerHTML + adjustDay.feedback + '<br>');
day = adjustDay.day;
} else {
day = existingDay;
}
const adjustYear = this.adjustYear(existingYear);
if (adjustYear.isAdjusted) {
this.logToScreen(this.feedbackElement, this.feedbackElement.innerHTML + adjustYear.feedback + '<br>');
year = adjustYear.year;
} else {
year = existingYear;
}
// if existing day greater than 28 in february and when not in a leap year
if (!this.isLeapYear(year) && +month === 2 && +day > 28) {
// adjust the day to the maximum of 28
const new_day = 28;
const feedback = IsNotALeapYear.replace('{0}', year).replace('{1}', new_day);
// message to user about year not being a leap year
this.logToScreen(this.feedbackElement, this.feedbackElement.innerHTML + feedback);
day = new_day;
}
// add leading 0's to a month or day that is under 10
month = month < 10 && month[0] != '0' ? '0' + month : month;
day = day < 10 && day[0] != '0' ? '0' + day : day;
// get the raw format of the locale (e.g. MMDDYYYY)
const rawLocaleFormat = this.removeDelimiters(this.localeFormat);
// populate the date field with the provided date and in the right locale format
if (rawLocaleFormat === 'MMDDYYYY') {
this.template.querySelector('input').value =
month + this.chosenDelimiter + day + this.chosenDelimiter + year;
} else if (rawLocaleFormat === 'DDMMYYYY') {
this.template.querySelector('input').value =
day + this.chosenDelimiter + month + this.chosenDelimiter + year;
}
// set validity of input field to true
this.isValid = true;
// remove html error flag
this.template.querySelector('.slds-form-element').classList.remove('slds-has-error');
}
}
renderedCallback() {
this.updateDateField();
}
handleDateChange() {
let self = this;
const evt = new CustomEvent('datechange', {
detail: self.getDateInSFFormat()
});
this.dispatchEvent(evt);
}
// slated to react to non-input keys such as backspace and delete
handleKeyDown(evt) {
// get keyboard key pressed
const userInput = evt.key;
// date field
const dateField = evt.target;
// only digit representation of entered date including leading 0's
let rawDate = this.removeDelimiters(dateField.value);
// ignore space bar
if (evt.key === ' ' || evt.key === 'Spacebar') {
evt.preventDefault();
}
// ignore any 1-length character that is not a number
if (/[^0-9]/g.test(userInput) && userInput.length === 1) {
evt.preventDefault();
}
// flag adding delimiters only for entered numbers
if (/[0-9]/g.test(userInput)) {
this.shouldAddDelimiters = true;
} else {
this.shouldAddDelimiters = false;
}
const selectionStart = dateField.selectionStart;
// if user hits backspace
if (evt.key === 'Backspace') {
// if user has not selected any characters
if (dateField.selectionEnd - dateField.selectionStart === 0) {
// if input field cursor is at the end of the input right after a delimiter
if (
(rawDate.length === 2 && dateField.selectionStart === 3) ||
(rawDate.length === 4 && dateField.selectionStart === 6)
) {
// take out one more character (that of the delimiter) on top of whatever is being deleted by the backspace
dateField.value = dateField.value.slice(0, -1);
}
// if user is about to backspace into a delimiter
else if (dateField.value[dateField.selectionStart - 1] === this.chosenDelimiter) {
// prevent natural order of deletion
evt.preventDefault();
// save where the cursor was at last
this.lastSelectionStart = dateField.selectionStart;
// get all date characters up to right before character to be deleted
const beforeRange = dateField.value.substring(0, dateField.selectionStart - 2);
// get all date characters after the deleted character to end of input
const afterRange = dateField.value.substring(dateField.selectionStart, dateField.value.length);
// join the before and after range parts of the date input, sanitize them, and re-introduce the delimiters. then, use it to replace the current content in the date input field
dateField.value = this.addDelimiters(
this.removeDelimiters(beforeRange + afterRange),
this.chosenDelimiter
);
// set the cursor to just right before the deleted character
dateField.setSelectionRange(this.lastSelectionStart - 2, this.lastSelectionStart - 2);
}
// if user is about to backspace away from a delimiter on its right
else if (dateField.value[dateField.selectionStart] === this.chosenDelimiter) {
// prevent natural order of deletion
evt.preventDefault();
// get the position of the user's cursor
const selectionStart = dateField.selectionStart;
// get all date characters up to right before character to be deleted
const beforeRange = dateField.value.substring(0, dateField.selectionStart - 1);
// get all date characters after the deleted character to end of input
const afterRange = dateField.value.substring(dateField.selectionStart, dateField.value.length);
// join the before and after range parts of the date input and store as the new date text
dateField.value = beforeRange + afterRange;
// set the cursor to the position right before the deleted character
dateField.setSelectionRange(selectionStart - 1, selectionStart - 1);
// flag to add delimiters to the date input
this.shouldAddDelimiters = true;
} else if (dateField.value.length > 0) {
// prevent natural order of deletion
evt.preventDefault();
// save where the cursor was at last
this.lastSelectionStart = dateField.selectionStart;
// get all date characters up to right before character to be deleted
const beforeRange = dateField.value.substring(0, dateField.selectionStart - 1);
// get all date characters after the deleted character to end of input
const afterRange = dateField.value.substring(dateField.selectionStart, dateField.value.length);
// join the before and after range parts of the date input, sanitize them, and re-introduce the delimiters. then, use it to replace the current content in the date input field
dateField.value = this.addDelimiters(
this.removeDelimiters(beforeRange + afterRange),
this.chosenDelimiter
);
// set the cursor to just right before the deleted character
dateField.setSelectionRange(this.lastSelectionStart - 1, this.lastSelectionStart - 1);
}
// if user selects 1 or more characters
} else {
this.shouldAddDelimiters = true;
this.shouldReposition = true;
}
// if user selects entire string in date field or if date field is empty
if (dateField.selectionEnd - dateField.selectionStart === dateField.value.length) {
// do not flag the cursor to be repositioned
this.shouldReposition = false;
}
}
// if the user presses the delete key
if (evt.key === 'Delete' || evt.key === 'Del') {
// if the user's cursor is right behind a delimiter and the user has not selected any characters
if (
dateField.value[dateField.selectionStart] === this.chosenDelimiter &&
dateField.selectionStart === dateField.selectionEnd
) {
// prevent natural order of deletion
evt.preventDefault();
// save where the cursor was at last
this.lastSelectionStart = dateField.selectionStart;
// get all date characters up to right before character to be deleted
const beforeRange = dateField.value.substring(0, dateField.selectionStart);
// get all date characters after the deleted character to end of input
const afterRange = dateField.value.substring(dateField.selectionStart + 2, dateField.value.length);
// join the before and after range parts of the date input, sanitize them, and re-introduce the delimiters. then, use it to replace the current content in the date input field
dateField.value = this.addDelimiters(
this.removeDelimiters(beforeRange + afterRange),
this.chosenDelimiter
);
// set the cursor's position to right after the next delimiter
dateField.setSelectionRange(this.lastSelectionStart + 1, this.lastSelectionStart + 1);
} else {
// if user selected entire string in date field or if date field is empty
if (
dateField.selectionEnd - dateField.selectionStart === dateField.value.length ||
dateField.value.length <= 1
) {
// do not flag the cursor to be repositioned
this.shouldReposition = false;
} else {
// else flag the cursor to be repositioned
this.shouldReposition = true;
}
// set flag to add delimiters back to the date input
this.shouldAddDelimiters = true;
}
}
// get the last position of the user's cursor
this.lastSelectionStart = dateField.selectionStart;
// get the last snapshot of the value in the date field
this.fullDateSnapshot = dateField.value;
}
// handle the digit inputs that made it into the date input field
handleInput(evt) {
// date field
const dateField = evt.target;
// get the raw form of the inputted date (e.g. 01012020)
let rawDate = this.removeDelimiters(dateField.value);
// get the result of the date format validator
const formatResult = this.validateDateFormat(rawDate, this.allowableDelimitersRegexStr, this.localeFormat);
// if a delimiter was inputted into the beginning of the date field
if (dateField.value[0] === this.chosenDelimiter) {
// then remove delimiter
evt.target.value = dateField.value.replace(this.chosenDelimiter, '');
// set cursor to beginning of date input field
dateField.setSelectionRange(0, 0);
}
if (this.shouldAddDelimiters) {
// remove anything that is not a digit from the inputted date
rawDate = this.sanitize(rawDate);
// add the delimiters and populate the date field accordingly
evt.target.value = this.addDelimiters(rawDate, this.chosenDelimiter);
// reset addDelimiters flag
this.shouldAddDelimiters = false;
}
// if the cursor needs to be repositioned
if (this.shouldReposition) {
// set the position to that which was last set in the keydown event
dateField.setSelectionRange(this.lastSelectionStart, this.lastSelectionStart);
// reset shouldReposition flag
this.shouldReposition = false;
}
// if the digits in the date exceed the limit
if (rawDate.length > 8) {
// replace the value in the date field to that of the last snapshot
evt.target.value = this.fullDateSnapshot;
// set the position to that which was last set in the keydown event
dateField.setSelectionRange(this.lastSelectionStart, this.lastSelectionStart);
// nothing left to do here exit callback
return;
}
// if the date has an invalid format
if (formatResult.status === false) {
// let the user know
this.logToScreen(this.feedbackElement, formatResult.msg);
} else {
// otherwise clear all messages to the user
this.logToScreen(this.feedbackElement, '');
// check for the actual date value constraints and auto-adjust the user's input to conform to date standards
this.adjustForDateConstraints(rawDate, evt.target);
}
// if the date input field is empty
if (rawDate.length === 0) {
// clear all messages to the user
this.logToScreen(this.feedbackElement, '');
}
// get the last snapshot of the value in the date field
this.fullDateSnapshot = dateField.value;
}
// handle the last event of pressing a key
handleKeyUp(evt) {
// reference to date field
const dateField = evt.target;
// validate the format of the date
const result = this.validateDateFormat(dateField.value, this.allowableDelimitersRegexStr, this.localeFormat);
// set the current validity of the input field
this.isValid = result.status || dateField.value.length === 0 ? true : false;
if (this.isValid) {
// remove html error flag
this.template.querySelector('.slds-form-element').classList.remove('slds-has-error');
} else {
// add html error flag
this.template.querySelector('.slds-form-element').classList.add('slds-has-error');
}
// if the date has been partially entered
if (dateField.value.length > 0 && dateField.value.length < 10 && !result.status) {
// indicate to user that date is not in proper format
this.logToScreen(this.feedbackElement, result.msg);
}
// if the date field is empty show no messages to the user
if (dateField.value.length === 0) {
this.logToScreen(this.feedbackElement, '');
// remove html error flag
this.template.querySelector('.slds-form-element').classList.remove('slds-has-error');
}
this.handleDateChange();
}
/**
* Logs feedback inside of a given HTML element
* @param {HTMLInputElement} el The HTML element that hosts the message
* @param {string} msg The message to be passed to the HTML element
*/
logToScreen(el, msg) {
// existing error message
const existingErrorInDateField = this.template.querySelector('.slds-form-element__help').innerHTML;
// if the message is different from that which already exists in the error feedback element
if (msg !== existingErrorInDateField) {
el.innerHTML = msg;
// add html error flag
this.template.querySelector('.slds-form-element').classList.add('slds-has-error');
}
}
/**
* Returns the current validity state of the date input
* @return {boolean} denoting whether the date is valid or not
*/
checkValidity() {
const input = this.template.querySelector('input');
if (this.required
&& input
&& !input.value) {
return false;
}
return this.isValid;
}
/**
* slated to report validity in human readable text
* wrapper function implemented to conform to behavior set by pfmInput.js, validate()
*/
reportValidity() {
// get the date input value from the field
const dateInputVal = this.template.querySelector('input').value;
// get the result of validating the date against the proper format
const dateValidationResult = this.validateDateFormat(
dateInputVal,
this.allowableDelimitersRegexStr,
this.localeFormat
);
return dateValidationResult.msg;
}
// disable pasting into the date field
handlePaste(evt) {
evt.preventDefault();
}
/**
* Returns fully entered date in Salesforce's format
* @return {string} date in YYYY-MM-DD format
*/
getDateInSFFormat() {
// get current locale (e.g. us, uk, etc.)
const localeFormat = this.removeDelimiters(this.localeFormat);
// split the day, month, and year into an array
const splitDate = this.fullDateSnapshot.split(this.chosenDelimiter);
// get the date without any delimiters (e.g. 12202021)
const rawDate = this.removeDelimiters(this.fullDateSnapshot);
// return null when date has not been fully entered
if (rawDate.length < 8) {
return null;
}
let SFDate;
// get the correct order of day, month, and year depending on the locale's format
if (localeFormat === 'MMDDYYYY') {
SFDate = splitDate[2] + '-' + splitDate[0] + '-' + splitDate[1];
} else if (localeFormat === 'DDMMYYYY') {
SFDate = splitDate[2] + '-' + splitDate[1] + '-' + splitDate[0];
}
return SFDate;
}
/**
* Returns input stripped from delimiters
* @param {string} input The date as appears in the date field
* @return {string} input stripped of all delimiters
*/
removeDelimiters(input) {
if(typeof input !== 'string') {
return null;
}
const allowableDelimitersRegex = new RegExp(this.allowableDelimitersRegexStr, 'g');
return input.replace(allowableDelimitersRegex, '');
}
/**
* Returns input stripped from non-numbers
* @param {string} input The date as appears in the date field
* @return {string} input stripped of all non-numbers
*/
sanitize(input) {
return typeof input === 'string' ? input.replace(/\D/g, ''):null;
}
/**
* Returns input with delimiters added
* @param {string} input The date in its digit raw form
* @return {string} input with added delimiters
*/
addDelimiters(input, delimiter) {
if(typeof input !== 'string') {
return null;
}
if (input.length < 2) {
return input;
} else if (input.length >= 2 && input.length < 4) {
return input.slice(0, 2) + delimiter + (input[2] || '') + (input[3] || '');
} else if (input.length >= 4 && input.length <= 8) {
return (
input.slice(0, 2) +
delimiter +
input.slice(2, 4) +
delimiter +
(input[4] || '') +
(input[5] || '') +
(input[6] || '') +
(input[7] || '')
);
}
}
/**
* Returns an object with a status and message for the user's inputted date
* @param {string} input The date in its non-sanitized form
* @param {string} input The date in its non-sanitized form
* @param {string} input The date in its non-sanitized form
* @return {Object} object with a status and message
*/
validateDateFormat(input, delimiterRegexStr, format) {
if(typeof input !== 'string') {
return {
status:null,
feedback:null
};
}
// build regular expression string that validates date input
const dateRegexStr = '\\d{2}' + delimiterRegexStr + '?' + '\\d{2}' + delimiterRegexStr + '?' + '\\d{4}';
// build a regular expression object
const dateRegex = new RegExp(dateRegexStr);
const feedback = EnterDateInFormat.replace('{0}', format);
// if date input conforms to regular expression and length is > 0
if (dateRegex.test(input) === false) {
return {
status: false,
msg: feedback
};
}
// if date input does not validate
else {
return {
status: true,
msg: ''
};
}
}
/**
* Runs a full check on the validity of the entered date and re-adjusts user input where needed
* @param {string} rawDate The date in its digit form
* @param {HTMLInputElement} dateField Reference to the date field in the DOM
*/
adjustForDateConstraints(rawDate, dateField) {
let day, month, year;
// get year of inputted date as 4 digits
year = rawDate.slice(4, 8);
// if current format is MM DD YYYY (with delimiters instead of spaces)
if (this.localeFormat === 'MM' + this.chosenDelimiter + 'DD' + this.chosenDelimiter + 'YYYY') {
// get month as 2 digits
month = rawDate.slice(0, 2);
// get day as 2 digits
day = rawDate.slice(2, 4);
}
// if current format is DD MM YYYY (with delimiters instead of spaces)
else if (this.localeFormat === 'DD' + this.chosenDelimiter + 'MM' + this.chosenDelimiter + 'YYYY') {
// get month as 2 digits
month = rawDate.slice(2, 4);
// get day as 2 digits
day = rawDate.slice(0, 2);
}
const adjustMonth = this.adjustMonth(month);
if (adjustMonth.isAdjusted) {
this.logToScreen(this.feedbackElement, adjustMonth.feedback + '<br>');
// adjust month if beyond boundaries
month = this.adjustMonth(month).month;
}
const adjustDay = this.adjustDay(day, month);
if (adjustDay.isAdjusted) {
this.logToScreen(this.feedbackElement, this.feedbackElement.innerHTML + adjustDay.feedback + '<br>');
day = adjustDay.day;
}
const adjustYear = this.adjustYear(year);
if (adjustYear.isAdjusted) {
this.logToScreen(this.feedbackElement, this.feedbackElement.innerHTML + adjustYear.feedback + '<br>');
// adjust year within the specified min and max range
year = adjustYear.year;
}
// if user entered a day greater than 28 in february and when not in a leap year
if (!this.isLeapYear(year) && +month === 2 && +day > 28) {
// adjust the day to the maximum of 28
const new_day = 28;
const feedback = IsNotALeapYear.replace('{0}', year).replace('{1}', new_day);
// message to user about year not being a leap year
this.logToScreen(this.feedbackElement, this.feedbackElement.innerHTML + feedback);
day = new_day;
}
// if current format is MM DD YYYY (with delimiters instead of spaces)
if (this.localeFormat === 'MM' + this.chosenDelimiter + 'DD' + this.chosenDelimiter + 'YYYY') {
// store the newly adjusted day, month, and year with their delimiters
dateField.value = month + this.chosenDelimiter + day + this.chosenDelimiter + year;
}
// if current format is DD MM YYYY (with delimiters instead of spaces)
else if (this.localeFormat === 'DD' + this.chosenDelimiter + 'MM' + this.chosenDelimiter + 'YYYY') {
// store the newly adjusted day, month, and year with their delimiters
dateField.value = day + this.chosenDelimiter + month + this.chosenDelimiter + year;
}
}
/**
* Returns an adjusted version of the inputted month
* @param {month} The month in its digit form
* @return {object} contains status, feedback, and adjusted month
*/
adjustMonth(month) {
let adjusted = false;
let feedback = '';
// if month is > 12 then adjust to 12
if (+month > 12) {
feedback = InvalidMonthMax.replace('{0}', month);
month = '12';
adjusted = true;
}
// if month is 0 then adjust to 1
if (+month === 0) {
feedback = InvalidMonthMin.replace('{0}', month);
month = '01';
adjusted = true;
}
return {
isAdjusted: adjusted,
feedback: feedback,
month: month
};
}
/**
* Returns an adjusted version of the inputted day given its month
* @param {number} day The day in its digit form
* @param {number} month The month in its digit form
* @return {object} contains status, feedback, and adjusted day
*/
adjustDay(day, month) {
let adjusted = false;
let feedback = '';
// convert month to integer
month = +month;
// if inputted day exceeds limit for its month
if (+day > this.getMaxDayInMonth(month)) {
const max_day = this.getMaxDayInMonth(month);
feedback = InvalidDayInMonth.replace('{0}', day).replace('{1}', month).replace('{2}', max_day);
day = max_day.toString();
adjusted = true;
}
// if inputted day is 0
if (+day === 0) {
feedback = InvalidMinDay.replace('{0}', day);
day = '01';
adjusted = true;
}
return {
isAdjusted: adjusted,
feedback: feedback,
day: day
};
}
/**
* Returns an adjusted version of the inputted year
* @param {number} year The year in its digit form
* @return {object} contains status, feedback, and adjusted year
*/
adjustYear(year) {
let adjusted = false;
let feedback = '';
// set minimum allowable year
const minAllowableYear = +this.chosenMinYear;
// set maximum allowable year
const maxAllowableYear = +this.chosenMaxYear;
// adjust year if inputted year is less than allowable minimum
if (+year < minAllowableYear) {
feedback = InvalidMinAllowableYear.replace('{0}', year).replace('{1}', minAllowableYear);
year = minAllowableYear;
adjusted = true;
}
// adjust year if inputted year is greater than allowable maximum
if (+year > maxAllowableYear) {
feedback = InvalidMaxAllowableYear.replace('{0}', year).replace('{1}', maxAllowableYear);
year = maxAllowableYear;
adjusted = true;
}
return {
isAdjusted: adjusted,
feedback: feedback,
year: year
};
}
/**
* Returns a number specifiying the maximum day a month can have
* @param {number} month The month in digits
* @return {number} maximum day for a given month
*/
getMaxDayInMonth(month) {
if(typeof month !== 'number') {
return null;
}
// pick a leap year (1904) to get the possible maximum day in any given month
return new Date(1904, +month, 0).getDate();
}
/**
* Returns the leap year status of the inputted year
* @param {number} year The year in its digit form
* @return {boolean} whether the inputted year is a leap year
*/
isLeapYear(year) {
if(isNaN(Number(year))) {
return null;
}
return year % 100 === 0 ? year % 400 === 0 : year % 4 === 0;
}
}