UNPKG

jb-date-input

Version:
555 lines (536 loc) 25.6 kB
import { InputTypes, JBDateInputValueObject, ValueTypes, ValueType, InputType, InputtedValueInObject, DateValidResult } from './types'; import { getEmptyValueObject } from './helpers'; import { getYear, getMonth, getTime as getTimeStamp, isLeapYear, getDate } from 'date-fns'; import { newDate, isLeapYear as isJalaliLeapYear, getYear as getJalaliYear, getMonth as getJalaliMonth, getDate as getJalaliDate, getHours, getMinutes, getSeconds, getMilliseconds } from 'date-fns-jalali'; export type DateFactoryConstructorArg = { inputType: InputTypes | null | undefined; valueType: ValueTypes | null | undefined; } export class DateFactory { #valueType:ValueType = "GREGORIAN"; #inputType:InputType = InputTypes.jalali; //here we keep numbers that replace the year,month,day in niche situations #nicheNumbers = { //when year is invalid or empty and we want to show the calendar we need to show the current year or any other base on user config calendarYearOnEmpty: { jalali: DateFactory.todayJalaliYear, gregorian: DateFactory.todayGregorianYear }, calendarMonthOnEmpty: { jalali: DateFactory.todayJalaliMonth, gregorian: DateFactory.todayGregorianMonth } } #valueFormat = 'YYYY-MM-DDTHH:mm:ss.SSS[Z]'; get valueFormat() { return this.#valueFormat; } set valueFormat(valueFormat: string) { this.#valueFormat = valueFormat; } get nicheNumbers() { return this.#nicheNumbers; } get yearOnEmptyBaseOnInputType() { if (this.#inputType == "JALALI") { return this.#nicheNumbers.calendarYearOnEmpty.jalali; } return this.#nicheNumbers.calendarYearOnEmpty.gregorian; } get monthOnEmptyBaseOnValueType(): number { if (this.valueType == "JALALI") { return this.#nicheNumbers.calendarMonthOnEmpty.jalali; } return this.#nicheNumbers.calendarMonthOnEmpty.gregorian; } get inputType() { return this.#inputType; } get valueType():ValueType{ return this.#valueType; } constructor(args: DateFactoryConstructorArg) { if (args.inputType) { this.#inputType = args.inputType; } if (args.valueType) { this.#valueType = args.valueType; } } setInputType(inputType: InputType) { this.#inputType = inputType; } setValueType(valueType: ValueType) { this.#valueType = valueType; } getYearValueBaseOnInputType(valueObject: JBDateInputValueObject): number | null { if (this.#inputType == InputTypes.jalali) { return valueObject.jalali.year; } return valueObject.gregorian.year; } getMonthValueBaseOnInputType(valueObject: JBDateInputValueObject): number | null { if (this.#inputType == InputTypes.jalali) { return valueObject.jalali.month; } return valueObject.gregorian.month; } getDayValueBaseOnInputType(valueObject: JBDateInputValueObject): number | null { if (this.#inputType == InputTypes.jalali) { return valueObject.jalali.day; } return valueObject.gregorian.day; } getDateFromValueDateString(valueDateString: string): Date | null { let resultDate: Date | null = null; //create min date base on input value type if (this.valueType == "TIME_STAMP") { resultDate = DateFactory.getDateFromTimestamp(parseInt(valueDateString)); } else { const dateValueObj = this.getDateObjectValueBaseOnFormat(valueDateString); const year = parseInt(dateValueObj.year); const month = parseInt(dateValueObj.month); const day = parseInt(dateValueObj.day); //sometimes format set after min value restriction set by user so this object returned null in these scenario we set min after format set again if (dateValueObj !== null && dateValueObj !== undefined && year !== null && month !== null && day !== null) { if (this.valueType == "GREGORIAN") { resultDate = DateFactory.getDateFromGregorian(year, month, day); } if (this.valueType == "JALALI") { resultDate = DateFactory.getDateFromJalali(year, month, day); } } } return resultDate; } getDateValueFromValueObject(valueObject: JBDateInputValueObject):Date | null{ if(valueObject.gregorian.year && valueObject.gregorian.month && valueObject.gregorian.day){ const date = new Date(valueObject.gregorian.year,valueObject.gregorian.month -1 ,valueObject.gregorian.day); return date; } return null; } /** * @description use when user want component value and convert valueObject to user formatted value string base on format and value type */ getDateValueStringFromValueObject(valueObject: JBDateInputValueObject, type = this.valueType): string { //this function convert inputed date to expected format base on valueType const emptyYearString = '0000'; const emptyMonthString = '00'; const emptyDayString = '00'; const hourStr = valueObject.time.hour?.toString().padStart(2,'0')??'00'; const minuteStr = valueObject.time.minute?.toString().padStart(2,'0')??'00'; const secondStr = valueObject.time.second?.toString().padStart(2,'0')??'00'; const millisecondStr = valueObject.time.millisecond?.toString().padStart(3,'0')??'000'; const getGregorianValue = () => { const { year, month, day } = valueObject.gregorian; const yearStr: string = year == null ? emptyYearString : (year < 1000 ? (year < 100 ? (year < 10 ? "000" + year : "00" + year) : "0" + year) : year.toString()); const monthStr: string = month == null ? emptyMonthString : month < 10 ? "0" + month : month.toString(); const dayStr: string = day == null ? emptyDayString : day < 10 ? "0" + day : day.toString(); const value = this.#valueFormat.replace('YYYY', yearStr).replace('MM', monthStr).replace('DD', dayStr) .replace('HH', hourStr).replace('mm', minuteStr).replace('ss', secondStr).replace('SSS', millisecondStr) .replace('[Z]', 'Ž').replace('Z', '+00:00').replace('Ž', 'Z'); return value; }; const getJalaliValue = () => { const { year, month, day } = valueObject.jalali; const yearStr: string = year == null ? emptyYearString : (year < 1000 ? (year < 100 ? (year < 10 ? "000" + year : "00" + year) : "0" + year) : year.toString()); const monthStr: string = month == null ? emptyMonthString : month < 10 ? "0" + month : month.toString(); const dayStr: string = day == null ? emptyDayString : day < 10 ? "0" + day : day.toString(); const value = this.valueFormat.replace('YYYY', yearStr).replace('MM', monthStr).replace('DD', dayStr) .replace('HH', hourStr).replace('mm', minuteStr).replace('ss', secondStr).replace('SSS', millisecondStr) .replace('[Z]', 'Ž').replace('Z', '+00:00').replace('Ž', 'Z'); return value; }; if (typeof valueObject == "object") { switch (type) { case 'GREGORIAN': return getGregorianValue(); case 'JALALI': return getJalaliValue(); case 'TIME_STAMP': if (valueObject.timeStamp) { return valueObject.timeStamp.toString(); } } } //when date is not completely valid return ""; } getCalendarYear(valueObject: JBDateInputValueObject): number { const defaultYear = this.inputType == InputTypes.gregorian ? this.#nicheNumbers.calendarYearOnEmpty.gregorian : this.#nicheNumbers.calendarYearOnEmpty.jalali; return this.getYearValueBaseOnInputType(valueObject) || defaultYear; } getCalendarMonth(valueObject: JBDateInputValueObject): number { const defaultMonth = this.inputType == InputTypes.gregorian ? this.#nicheNumbers.calendarMonthOnEmpty.gregorian : this.#nicheNumbers.calendarMonthOnEmpty.jalali; return this.getMonthValueBaseOnInputType(valueObject) || defaultMonth; } getCalendarDay(valueObject: JBDateInputValueObject): number | null { return this.getDayValueBaseOnInputType(valueObject); } setCalendarDefaultDateView(year: number, month: number, inputType: InputType = this.#inputType) { if (inputType == InputTypes.gregorian) { this.#nicheNumbers.calendarYearOnEmpty.gregorian = year; this.#nicheNumbers.calendarMonthOnEmpty.gregorian = month; } else if (inputType == InputTypes.jalali) { this.#nicheNumbers.calendarYearOnEmpty.jalali = year; this.#nicheNumbers.calendarMonthOnEmpty.jalali = month; } } getDateValueObjectBaseOnInputType(year: number, month: number, day: number, oldYear: number | null, oldMonth: number | null, hour?:number, minute?:number, second?:number, millisecond?:number): JBDateInputValueObject { if (this.#inputType == InputTypes.gregorian) { return this.#getDateValueFromGregorian(year, month, day, oldYear, oldMonth,hour,minute,second,millisecond); } if (this.#inputType == InputTypes.jalali) { return this.#getDateValueFromJalali(year, month, day, oldYear, oldMonth,hour,minute,second,millisecond); } console.error("INVALID_INPUT_TYPE"); return getEmptyValueObject(); } getDateValueObjectBaseOnValueType(year: number, month: number, day: number, oldYear: number | null, oldMonth: number | null, hour?:number, minute?:number, second?:number, millisecond?:number): JBDateInputValueObject { if (this.#valueType == "GREGORIAN" ||this.#valueType == "TIME_STAMP" ) { return this.#getDateValueFromGregorian(year, month, day, oldYear, oldMonth,hour,minute,second,millisecond); } if (this.#valueType == "JALALI") { return this.#getDateValueFromJalali(year, month, day, oldYear, oldMonth,hour,minute,second,millisecond); } console.error("INVALID_INPUT_TYPE"); return getEmptyValueObject(); } getDateValueObjectFromTimeStamp(timestamp: number): JBDateInputValueObject { const date = new Date(timestamp); if(!isFinite(+date)){ //if date was invalid return getEmptyValueObject(); } return this.getDateObjectValueFromDateValue(date); } /** * @description this function return date object base on javascript Date type */ getDateObjectValueFromDateValue(inputValue: Date): JBDateInputValueObject { const result: JBDateInputValueObject = { gregorian: { year: getYear(inputValue), month: getMonth(inputValue) + 1, day: getDate(inputValue) }, jalali: { year: getJalaliYear(inputValue), month: getJalaliMonth(inputValue) + 1, day: getJalaliDate(inputValue) }, timeStamp: getTimeStamp(inputValue), time:{ hour:getHours(inputValue), minute:getMinutes(inputValue), second:getSeconds(inputValue), millisecond:getMilliseconds(inputValue) }, }; return result; } static checkJalaliDateValidation(jalaliYear: number, jalaliMonth: number, jalaliDay: number) { //check if jalali date is valid const result: DateValidResult = { isValid: true, error: null }; //this function check date itself validation not user setted validation if (isNaN(jalaliYear)) { result.isValid = false; result.error = "INVALID_YEAR"; } if (isNaN(jalaliMonth)) { result.isValid = false; result.error = "INVALID_MONTH"; } if (isNaN(jalaliDay)) { result.isValid = false; result.error = "INVALID_DAY"; } if (jalaliMonth < 1) { result.isValid = false; result.error = "INVALID_MIN_MONTH_NUMBER"; } if (jalaliDay < 1) { result.isValid = false; result.error = "INVALID_MIN_DAY_NUMBER"; } if (jalaliMonth > 12) { result.isValid = false; result.error = "INVALID_MAX_MONTH_NUMBER"; } if (jalaliDay > 31) { result.isValid = false; result.error = "INVALID_MAX_DAY_NUMBER"; } if (jalaliYear < 1000) { result.isValid = false; result.error = "INVALID_MIN_YEAR_NUMBER"; } if (jalaliYear > 9999) { result.isValid = false; result.error = "INVALID_MAX_YEAR_NUMBER"; } if (jalaliMonth > 6 && jalaliMonth < 12) { if (jalaliDay > 30) { result.isValid = false; result.error = "INVALID_DAY_IN_MONTH"; } } if (jalaliMonth == 12) { if (jalaliDay > 30) { result.isValid = false; result.error = "INVALID_DAY_IN_MONTH"; } } if (result.isValid && jalaliMonth == 12) { //if everything was ok then we check for leap year if (jalaliMonth == 12 && jalaliDay == 30) { const date = DateFactory.getDateFromJalali(jalaliYear, jalaliMonth, jalaliDay); if (!isJalaliLeapYear(date)) { result.isValid = false; result.error = "INVALID_DAY_FOR_LEAP"; } } } return result; } static checkGregorianDateValidation(gregorianYear: number, gregorianMonth: number, gregorianDay: number) { const result: DateValidResult = { isValid: true, error: null }; //this function check date itself validation not user setted validation if (isNaN(gregorianYear)) { result.isValid = false; result.error = "INVALID_YEAR"; } if (isNaN(gregorianMonth)) { result.isValid = false; result.error = "INVALID_MONTH"; } if (isNaN(gregorianDay)) { result.isValid = false; result.error = "INVALID_DAY"; } if (gregorianMonth < 1) { result.isValid = false; result.error = "INVALID_MIN_MONTH_NUMBER"; } if (gregorianDay < 1) { result.isValid = false; result.error = "INVALID_MIN_DAY_NUMBER"; } if (gregorianMonth > 12) { result.isValid = false; result.error = "INVALID_MAX_MONTH_NUMBER"; } if (gregorianDay > 31) { result.isValid = false; result.error = "INVALID_MAX_DAY_NUMBER"; } if (gregorianYear < 1000) { result.isValid = false; result.error = "INVALID_MIN_YEAR_NUMBER"; } if (gregorianYear > 9999) { result.isValid = false; result.error = "INVALID_MAX_YEAR_NUMBER"; } if ([2, 4, 6, 9, 11].includes(gregorianDay)) { //month has less than 31 day if (gregorianDay > 30) { result.isValid = false; result.error = "INVALID_DAY_IN_MONTH"; } } if (gregorianMonth == 2 && gregorianDay > 28) { if (gregorianDay == 29) { const date = DateFactory.getDateFromGregorian(gregorianYear, gregorianMonth, gregorianDay); if (!isLeapYear(date)) { result.isValid = false; result.error = "INVALID_DAY_FOR_LEAP"; } } else { result.isValid = false; result.error = "INVALID_DAY_IN_MONTH"; } } return result; } #getDateValueFromGregorian(gregorianYear: number, gregorianMonth: number, gregorianDay: number, oldGregorianYear: number | null, oldGregorianMonth: number | null, hour?:number, minute?:number, second?:number, millisecond?:number): JBDateInputValueObject { const valueObject: JBDateInputValueObject = getEmptyValueObject(); const dateValidationResult = DateFactory.checkGregorianDateValidation(gregorianYear, gregorianMonth, gregorianDay); if (!dateValidationResult.isValid) { if (dateValidationResult.error == "INVALID_MIN_DAY_NUMBER") { return this.#getDateValueFromGregorian(gregorianYear, gregorianMonth, 1, oldGregorianYear, oldGregorianMonth,hour,minute,second,millisecond); } if (dateValidationResult.error == "INVALID_MIN_MONTH_NUMBER") { return this.#getDateValueFromGregorian(gregorianYear, 1, gregorianDay, oldGregorianYear, oldGregorianMonth,hour,minute,second,millisecond); } if (dateValidationResult.error == "INVALID_MIN_YEAR_NUMBER") { return this.#getDateValueFromGregorian(1900, gregorianMonth, gregorianDay, oldGregorianYear, oldGregorianMonth,hour,minute,second,millisecond); } if (dateValidationResult.error == "INVALID_MAX_DAY_NUMBER") { return this.#getDateValueFromGregorian(gregorianYear, gregorianMonth, 31, oldGregorianYear, oldGregorianMonth,hour,minute,second,millisecond); } if (dateValidationResult.error == "INVALID_MAX_MONTH_NUMBER") { return this.#getDateValueFromGregorian(gregorianYear, 12, gregorianDay, oldGregorianYear, oldGregorianMonth,hour,minute,second,millisecond); } if (dateValidationResult.error == "INVALID_MAX_YEAR_NUMBER") { return this.#getDateValueFromGregorian(9000, gregorianMonth, gregorianDay, oldGregorianYear, oldGregorianMonth,hour,minute,second,millisecond); } if (dateValidationResult.error == "INVALID_DAY_IN_MONTH") { if (oldGregorianMonth != gregorianMonth && gregorianDay > 29) { //if we update to 30days month when day set to 31 we substrc day to 30 instead of prevent user from updating month return this.#getDateValueFromGregorian(gregorianYear, gregorianMonth, gregorianDay - 1, oldGregorianYear, oldGregorianMonth,hour,minute,second,millisecond); } } if (dateValidationResult.error == "INVALID_DAY_FOR_LEAP") { //if it was leap year and calender go to next year in 30 esfand if (oldGregorianYear != gregorianYear && gregorianDay == 29) { //if we update year and prev year was kabiseh so new year cant update, we update day to 29 esfand and let user change year smootly without block return this.#getDateValueFromGregorian(gregorianYear, gregorianMonth, gregorianDay - 1, oldGregorianYear, oldGregorianMonth,hour,minute,second,millisecond); } } return getEmptyValueObject(); } const date = DateFactory.getDateFromGregorian(gregorianYear, gregorianMonth, gregorianDay,hour,minute,second,millisecond); valueObject.gregorian = { year: getYear(date), month: getMonth(date) + 1, day: getDate(date) }; valueObject.jalali = { year: getJalaliYear(date), month: getJalaliMonth(date) + 1, day: getJalaliDate(date) }; valueObject.timeStamp = getTimeStamp(date); valueObject.time = { hour:hour, minute:minute, second:second, millisecond:millisecond, }; return valueObject; } #getDateValueFromJalali(jalaliYear: number, jalaliMonth: number, jalaliDay: number, oldJalaliYear: number | null, oldJalaliMonth: number | null, hour?:number, minute?:number, second?:number, millisecond?:number): JBDateInputValueObject { const valueObject = getEmptyValueObject(); const dateValidationResult = DateFactory.checkJalaliDateValidation(jalaliYear, jalaliMonth, jalaliDay); if (!dateValidationResult.isValid) { if (dateValidationResult.error == "INVALID_MIN_DAY_NUMBER") { return this.#getDateValueFromJalali(jalaliYear, jalaliMonth, 1, oldJalaliYear, oldJalaliMonth,hour,minute,second,millisecond); } if (dateValidationResult.error == "INVALID_MIN_MONTH_NUMBER") { return this.#getDateValueFromJalali(jalaliYear, 1, jalaliDay, oldJalaliYear, oldJalaliMonth,hour,minute,second,millisecond); } if (dateValidationResult.error == "INVALID_MIN_YEAR_NUMBER") { return this.#getDateValueFromJalali(1300, jalaliMonth, jalaliDay, oldJalaliYear, oldJalaliMonth,hour,minute,second,millisecond); } if (dateValidationResult.error == "INVALID_MAX_DAY_NUMBER") { return this.#getDateValueFromJalali(jalaliYear, jalaliMonth, 31, oldJalaliYear, oldJalaliMonth,hour,minute,second,millisecond); } if (dateValidationResult.error == "INVALID_MAX_MONTH_NUMBER") { return this.#getDateValueFromJalali(jalaliYear, 12, jalaliDay, oldJalaliYear, oldJalaliMonth,hour,minute,second,millisecond); } if (dateValidationResult.error == "INVALID_MAX_YEAR_NUMBER") { return this.#getDateValueFromJalali(9999, jalaliMonth, jalaliDay, oldJalaliYear, oldJalaliMonth,hour,minute,second,millisecond); } if (dateValidationResult.error == "INVALID_DAY_IN_MONTH") { if (oldJalaliMonth != jalaliMonth && jalaliDay == 31) { //if we update to 30days month when day set to 31 we substr day to 30 instead of prevent user from updating month return this.#getDateValueFromJalali(jalaliYear, jalaliMonth, jalaliDay - 1, oldJalaliYear, oldJalaliMonth,hour,minute,second,millisecond); } } if (dateValidationResult.error == "INVALID_DAY_FOR_LEAP") { //if it was leap year and calender go to next year in 30 esfand if (oldJalaliYear != jalaliYear && jalaliDay == 30) { //if we update year and prev year was kabiseh so new year cant update, we update day to 39 esfand and let user change year smootly without block return this.#getDateValueFromJalali(jalaliYear, jalaliMonth, jalaliDay - 1, oldJalaliYear, oldJalaliMonth,hour,minute,second,millisecond); } } return getEmptyValueObject(); } const date = DateFactory.getDateFromJalali(jalaliYear, jalaliMonth, jalaliDay,hour,minute,second,millisecond); valueObject.gregorian = { year: getYear(date), month: getMonth(date) + 1, day: getDate(date) }; valueObject.jalali = { year: getJalaliYear(date), month: getJalaliMonth(date) + 1, day: getJalaliDate(date) }; valueObject.timeStamp = getTimeStamp(date); valueObject.time = { hour:hour, minute:minute, second:second, millisecond:millisecond, }; return valueObject; } getDateObjectValueBaseOnFormat(valueString: string, format: string = this.#valueFormat): InputtedValueInObject { const res = DateFactory.#executeFormatAndExtractValue(valueString, format); const dateInObject: InputtedValueInObject = { year: null, month: null, day: null, hour:null, millisecond:null, minute:null, second:null }; if (res && res.groups) { dateInObject.year = res.groups.year; dateInObject.month = res.groups.month; dateInObject.day = res.groups.day; dateInObject.hour = res.groups.hour; dateInObject.minute = res.groups.minute; dateInObject.second = res.groups.second; dateInObject.millisecond = res.groups.millisecond; } return dateInObject; } static #executeFormatAndExtractValue(value: string, format: string) { const regexString = format.replace('YYYY', '(?<year>[\\d]{4})').replace('MM', '(?<month>[\\d]{2})').replace('DD', '(?<day>[\\d]{2})') .replace('HH', '(?<hour>[\\d]{2})').replace('mm', '(?<minute>[\\d]{2})').replace('ss', '(?<second>[\\d]{2})').replace('SSS', '(?<millisecond>[\\d]{3})') .replace('[Z]', 'Ž').replace('Z', '(?<zone>([\\+,-]\\d{2}:\\d{2}))').replace('Ž', 'Z'); const regex = new RegExp(regexString, 'g'); const res = regex.exec(value); return res; } static getDate(year: number, month: number, day: number, inputType: InputType, hour?:number, minute?:number, second?:number, millisecond?:number): Date { if (inputType == InputTypes.jalali) { return DateFactory.getDateFromJalali(year, month, day); } return DateFactory.getDateFromGregorian(year, month, day); } static getDateFromGregorian(year: number, month: number, day: number, hour?:number, minute?:number, second?:number, millisecond?:number): Date { return new Date(year, month - 1, day,hour,minute,second,millisecond); } static getDateFromJalali(year: number, month: number, day: number, hour?:number, minute?:number, second?:number, millisecond?:number): Date { const date = newDate(year, month - 1, day,hour,minute,second,millisecond); return date; } static getDateFromTimestamp(timestamp: number): Date { return new Date(timestamp); } static get todayGregorianYear(): number { return getYear(new Date()); } static get todayJalaliYear(): number { const year = getJalaliYear(new Date()); return year; } static get todayGregorianMonth(): number { return getMonth(new Date()) + 1; } static get todayJalaliMonth(): number { return getJalaliMonth(new Date()) + 1; } }