zmp-react
Version:
Build full featured iOS & Android apps using ZMP & React
516 lines (485 loc) • 16.5 kB
JavaScript
import { getMonthlyDay } from './utils';
import { VALID_DATE_PICKER_COLUMN_FORMAT } from './constants';
function initPickerColumnsIndex() {
const colsIndex = {};
this.columns.forEach((col, indx) => {
if (col.name) {
colsIndex[col.name] = indx;
}
});
return colsIndex;
}
class DateTime {
constructor(config = {}) {
this.config = {
el: config.el || '',
dateFormat:
config.dateFormat || (config.dateTimePicker ? 'dd/mm/yyyy hh::mm A' : 'dd/mm/yyyy'),
is24Hours: config.is24Hours || false,
dateTimePicker: config.dateTimePicker || false,
datePicker: config.datePicker || !config.dateTimePicker,
startYear: config.startYear || 1900,
endYear: config.endYear || 2200,
startDate: config.startDate,
endDate: config.endDate,
defaultValue: config.defaultValue || null,
value: config.value || null,
minStep: config.minStep || 1,
locale: config.locale || 'vi-VN',
datePickerColumns: config.datePickerColumns,
};
this.isUpdating = false;
this.initializing = false;
this.dateTime = config.defaultValue || config.value || new Date();
this.dayFormatter = new Intl.DateTimeFormat(this.config.locale, { day: '2-digit' });
this.monthFormatter = new Intl.DateTimeFormat(this.config.locale, {
month: config.dateTimePicker ? 'short' : 'long',
});
this.longMonthFormatter = new Intl.DateTimeFormat(this.config.locale, { month: 'long' });
this.shortMonthFormatter = new Intl.DateTimeFormat(this.config.locale, { month: 'short' });
this.yearFormatter = new Intl.DateTimeFormat(this.config.locale, { year: 'numeric' });
this.createPickerColumns = this.createPickerColumns.bind(this);
this.setValue = this.setValue.bind(this);
this.columns = this.createPickerColumns();
const initIndex = initPickerColumnsIndex.bind(this);
this.pickerColumnsIndex = initIndex();
this.setDate = this.setDate.bind(this);
this.setInitializing = this.setInitializing.bind(this);
this.getDateRange = this.getDateRange.bind(this);
this.getPosition = this.getPosition.bind(this);
}
setValue(values) {
const is12Hours = !this.config.is24Hours;
let isPM = false;
if (is12Hours) {
const ampmIndex = Object.keys(this.pickerColumnsIndex).findIndex((col) => col === '12Hours');
if (ampmIndex >= 0) {
isPM = values[values.length - 1] === 'pm';
}
}
Object.keys(this.pickerColumnsIndex).forEach((key, i) => {
switch (key) {
case 'YYYY':
this.dateTime.setFullYear(values[i]);
break;
case 'MM':
this.dateTime.setMonth(values[i]);
break;
case 'DD':
this.dateTime.setDate(values[i]);
break;
case 'hh':
{
let hour = parseFloat(values[i]);
if (isPM) {
hour = hour < 12 ? hour + 12 : hour;
} else {
hour = hour === 12 ? 0 : hour;
}
this.dateTime.setHours(hour);
}
break;
case 'mm':
this.dateTime.setMinutes(values[i]);
break;
default:
break;
}
});
}
setDate(date) {
this.dateTime = date;
}
setInitializing(init) {
this.initializing = init;
}
getValue() {
return this.dateTime;
}
getDateRange() {
const result = {};
if (this.config.startDate) {
const startDate = new Date(this.config.startDate);
result.startDate = startDate.getDate();
result.startMonth = startDate.getMonth() + 1;
result.startYear = startDate.getFullYear();
}
if (this.config.endDate) {
const endDate = new Date(this.config.endDate);
result.endDate = endDate.getDate();
result.endMonth = endDate.getMonth() + 1;
result.endYear = endDate.getFullYear();
}
return result;
}
// get current picked date is at start or end of range
getPosition({ currentMonth, currentYear } = {}) {
const { startMonth, startYear, endMonth, endYear } = this.getDateRange();
const curMonth = currentMonth || this.dateTime.getMonth() + 1;
const curYear = currentYear || this.dateTime.getFullYear();
return {
isStartMonth: curYear === startYear && curMonth <= startMonth,
isEndMonth: curYear === endYear && curMonth >= endMonth,
isStartYear: curYear <= startYear,
isEndYear: curYear >= endYear,
};
}
getPickerValues() {
const values = [];
const dateTime = this.dateTime;
const year = dateTime.getFullYear();
const month = dateTime.getMonth();
const day = dateTime.getDate();
const hour = dateTime.getHours();
const minutes = dateTime.getMinutes();
Object.keys(this.pickerColumnsIndex).forEach((key, i) => {
switch (key) {
case 'YYYY':
values[i] = year;
break;
case 'MM':
values[i] = month;
break;
case 'DD':
values[i] = day;
break;
case 'hh': {
if (!this.config.is24Hours) {
if (hour >= 1 && hour <= 11) {
values[i] = hour;
} else if (hour === 0 || hour === 12) {
values[i] = '12';
} else {
values[i] = hour - 12;
}
} else {
values[i] = hour;
}
break;
}
case 'mm':
values[i] = minutes;
break;
case '12Hours': {
if (hour >= 0 && hour <= 11) {
values[i] = 'am';
} else {
values[i] = 'pm';
}
break;
}
default:
break;
}
});
return values;
}
updateDateColumns() {
const days = {
values: [],
displayValues: [],
name: 'DD',
};
const { startDate, endDate } = this.getDateRange();
const { isEndMonth, isStartMonth } = this.getPosition();
const curMonth = this.dateTime.getMonth() + 1;
const curYear = this.dateTime.getFullYear();
const monthlyDays = getMonthlyDay(curYear, curMonth);
const start = isStartMonth ? startDate : 1;
const end = isEndMonth ? endDate : monthlyDays;
for (let l = start; l <= end; l += 1) {
days.values.push(l);
days.displayValues.push(l);
}
this.columns = this.columns.map((col) => {
if (col.name === 'DD') {
return days;
}
return col;
});
}
updateMonthColumns() {
const months = {
values: [],
displayValues: [],
name: 'MM',
};
const { startMonth, endMonth } = this.getDateRange();
const { isEndYear, isStartYear } = this.getPosition();
const start = isStartYear ? startMonth - 1 : 0;
const end = isEndYear ? endMonth - 1 : 11;
for (let l = start; l <= end; l += 1) {
months.values.push(l);
const monthName = this.monthFormatter.format(new Date('0001-01-01').setMonth(l));
months.displayValues.push(monthName);
}
this.columns = this.columns.map((col) => {
if (col.name === 'MM') {
return months;
}
return col;
});
}
createPickerColumns() {
const config = this.config;
const defaultPickerFormat = config.dateTimePicker ? 'YYYY-MM-DD hh:mm' : 'YYYY-MM-DD';
let customPickerFormat;
if (
config.datePickerColumns &&
typeof config.datePickerColumns === 'string' &&
VALID_DATE_PICKER_COLUMN_FORMAT.includes(config.datePickerColumns.toUpperCase())
) {
customPickerFormat = config.datePickerColumns.toUpperCase();
}
if (config.dateTimePicker && customPickerFormat) {
customPickerFormat = `${customPickerFormat} hh:mm`;
}
const dateFormatArr = customPickerFormat
? customPickerFormat.split(/-|\/|\s|:/g)
: defaultPickerFormat.split(/-|\/|\s|:/g);
const { startDate, startMonth, startYear, endDate, endMonth, endYear } = this.getDateRange();
const formatArr = dateFormatArr;
const len = formatArr.length;
const columns = [];
const date = this.dateTime;
for (let i = 0; i < len; i += 1) {
const f = formatArr[i];
if (f === 'YYYY') {
const years = {
values: [],
displayValues: [],
name: f,
onChange: (picker, value) => {
if (this.initializing) {
return;
}
this.isUpdating = true;
const dateIndex = this.pickerColumnsIndex.DD;
const monthIndex = this.pickerColumnsIndex.MM;
const pickedYear = parseInt(value, 10);
const { isEndYear, isStartYear } = this.getPosition({
currentYear: pickedYear,
});
let currentMonth = parseInt(picker.cols[monthIndex].value, 10) + 1;
if (isStartYear && currentMonth < startMonth) {
currentMonth = startMonth;
}
if (isEndYear && currentMonth > endMonth) {
currentMonth = endMonth;
}
const { isStartMonth, isEndMonth } = this.getPosition({
currentYear: pickedYear,
currentMonth,
});
const maxDay = getMonthlyDay(pickedYear, currentMonth);
let currentDay =
parseInt(picker.cols[dateIndex].value, 10) > maxDay
? maxDay
: parseInt(picker.cols[dateIndex].value, 10);
if (isStartMonth && currentDay < startDate) {
currentDay = startDate;
}
if (isEndMonth && currentDay > endDate) {
currentDay = endDate;
}
this.dateTime.setFullYear(value, currentMonth - 1, currentDay);
this.updateDateColumns();
this.updateMonthColumns();
if (picker.cols[monthIndex] && picker.cols[monthIndex].replaceValues) {
picker.cols[monthIndex].replaceValues(
this.columns[monthIndex].values,
this.columns[monthIndex].displayValues,
);
}
if (picker.cols[monthIndex] && picker.cols[monthIndex].setValue) {
picker.cols[monthIndex].setValue(currentMonth - 1, 0);
}
if (picker.cols[dateIndex] && picker.cols[dateIndex].replaceValues) {
picker.cols[dateIndex].replaceValues(
this.columns[dateIndex].values,
this.columns[dateIndex].displayValues,
);
}
if (picker.cols[dateIndex] && picker.cols[dateIndex].setValue) {
picker.cols[dateIndex].setValue(currentDay, 0);
}
this.isUpdating = false;
},
};
const start = startYear || config.startYear;
const end = endYear || config.endYear;
for (let j = start; j <= end; j += 1) {
years.values.push(j);
years.displayValues.push(this.yearFormatter.format(new Date().setFullYear(j)));
}
columns.push(years);
} else if (f === 'MM') {
const months = {
values: [],
displayValues: [],
name: f,
onChange: (picker, value) => {
if (this.initializing) {
return;
}
const pickedMonth = parseInt(value, 10) + 1;
const { isEndMonth, isStartMonth } = this.getPosition({ currentMonth: pickedMonth });
this.isUpdating = true;
const index = this.pickerColumnsIndex.DD;
const maxDay = getMonthlyDay(this.dateTime.getFullYear(), pickedMonth);
let currentDay = picker.cols[index].value > maxDay ? maxDay : picker.cols[index].value;
if (isEndMonth && currentDay >= endDate) {
currentDay = endDate;
}
if (isStartMonth && currentDay <= startDate) {
currentDay = startDate;
}
this.dateTime.setMonth(value, currentDay);
this.updateDateColumns();
if (picker.cols[index] && picker.cols[index].replaceValues) {
picker.cols[index].replaceValues(
this.columns[index].values,
this.columns[index].displayValues,
);
}
if (picker.cols[index] && picker.cols[index].setValue) {
picker.cols[index].setValue(currentDay, 0);
}
this.isUpdating = false;
},
};
const { isEndYear, isStartYear } = this.getPosition();
let start = 0;
let end = 11;
if (isStartYear) {
start = startMonth - 1;
}
if (isEndYear) {
end = endMonth - 1;
}
for (let k = start; k <= end; k += 1) {
months.values.push(k);
const monthName = this.monthFormatter.format(new Date('0001-01-01').setMonth(k));
months.displayValues.push(monthName);
}
columns.push(months);
} else if (f === 'DD') {
const days = { values: [], displayValues: [], name: f };
const { isEndMonth, isStartMonth } = this.getPosition();
let end = getMonthlyDay(date.getFullYear(), date.getMonth() + 1);
let start = 1;
if (isStartMonth) {
start = startDate;
}
if (isEndMonth) {
end = endDate;
}
for (let l = start; l <= end; l += 1) {
days.values.push(l);
days.displayValues.push(this.dayFormatter.format(new Date('0001-01-01').setDate(l)));
}
columns.push(days);
} else if (f === 'hh') {
if (config.dateTimePicker) {
columns.push({
divider: true,
content: ' ',
});
}
const hours = {
values: [],
displayValues: [],
name: f,
};
const maxHour = config.is24Hours ? 23 : 12;
const minHour = config.is24Hours ? 0 : 1;
for (let m = minHour; m <= maxHour; m += 1) {
hours.values.push(m);
hours.displayValues.push(m < 10 ? `0${m}` : m);
}
columns.push(hours);
} else if (f === 'mm') {
const minutes = {
values: [],
displayValues: [],
name: f,
};
for (let n = 0; n <= 59; n += config.minStep) {
minutes.values.push(n);
minutes.displayValues.push(n < 10 ? `0${n}` : n);
}
columns.push(minutes);
}
}
if (!config.is24Hours && !config.datePicker) {
columns.push({
name: '12Hours',
values: ['am', 'pm'],
displayValues: ['AM', 'PM'],
});
}
return columns;
}
formatDate(d) {
const calendar = this;
const date = new Date(d);
const year = date.getFullYear();
const month = date.getMonth();
const month1 = month + 1;
const day = date.getDate();
const { dateFormat, locale, dateTimePicker } = calendar.config;
function twoDigits(number) {
return number < 10 ? `0${number}` : number;
}
if (typeof dateFormat === 'string') {
const tokens = {
yyyy: year,
yy: String(year).substring(2),
mm: twoDigits(month1),
m: month1,
MM: this.longMonthFormatter.format(date),
M: this.shortMonthFormatter.format(date),
dd: twoDigits(day),
d: day,
};
if (dateTimePicker) {
const hours = date.getHours();
const minutes = date.getMinutes();
const seconds = date.getSeconds();
let hours12 = hours;
if (hours > 12) hours12 = hours - 12;
if (hours === 0) hours12 = 12;
const a = hours >= 12 && hours !== 0 ? 'pm' : 'am';
Object.assign(tokens, {
HH: twoDigits(hours),
H: hours,
hh: twoDigits(hours12),
h: hours12,
ss: twoDigits(seconds),
s: seconds,
':mm': twoDigits(minutes),
':m': minutes,
a,
A: a.toUpperCase(),
});
}
const regexp = new RegExp(
Object.keys(tokens)
.map((t) => `(${t})`)
.join('|'),
'g',
);
return dateFormat.replace(regexp, (token) => {
if (token in tokens) return tokens[token];
return token;
});
}
if (typeof dateFormat === 'function') {
return dateFormat(date);
}
// Intl Object
const formatter = new Intl.DateTimeFormat(locale, dateFormat);
return formatter.format(date);
}
}
export default DateTime;