/** * Calendar Script * Creates a calendar widget which can be used to select the date * Can be paired with Picker (https://yourcmc.ru/git/vitalif-js/selectbox/) * to create a Calendar-based text input * * (c) Vitaliy Filippov 2011+ * Repository: http://yourcmc.ru/git/vitalif-js/calendar * Version: 2021-10-17 * License: Dual-license MPL 2.0+ or GNU LGPL 3.0+ */ import React from 'react'; export default class Calendar extends React.PureComponent { // Configuration static defaultProps = { monthNames: ["Январь","Февраль","Март","Апрель","Май","Июнь","Июль","Август","Сентябрь","Октябрь","Ноябрь","Декабрь"], closeLabel: 'Закрыть', weekdays: ["Пн","Вт","Ср","Чт","Пт","Сб","Вс"], weekdayIds: ['Mon','Tue','Wed','Thu','Fri','Sat','Sun'], sunday: 6, selectboxes: false, // true: use selectboxes for year and month, false: show months and years in table minDate: null, // minimum date maxDate: null, // maximum date minYear: -70, // range of displayed years if selectboxes==true maxYear: 10, format: 'd.m.Y', // either d.m.Y or Y-m-d, other formats are not supported time: false, // include time startMode: 'days', } constructor(props) { super(props); const selected = this.parseValue() || new Date(); this.state = { mode: props.startMode || 'days', year: selected.getFullYear(), month: selected.getMonth(), }; } render() { return (
{this.state.mode == 'months' ? this.renderMonths() : null} {this.state.mode == 'years' ? this.renderYears() : null} {this.state.mode == 'days' ? this.renderDays() : null} {this.props.closeLabel}
); } /// Called when the user clicks on a date in the calendar. selectDate(year, month, day) { let time = this.props.value; if (!time) time = [ 0, 0, 0 ]; else if (time instanceof Date) time = [ time.getHours(), time.getMinutes(), time.getSeconds() ]; else { time = (''+time).split(/\s+/, 2)[1]; time = time ? time.split(/:/) : [ 0, 0, 0 ]; } month = Number(month)+1; if (!this.props.format) { // Safari does not understand new Date('YYYY-MM-DD HH:MM:SS') time = new Date(year-0, month-1, day-0, time[0]-0, time[1]-0, time[2]-0); } else { if (month < 10) month = '0'+month; if (day < 10) day = '0'+day; time = time.map(t => t.length == 1 ? '0'+t : t).join(':'); time = (this.props.format == 'Y-m-d' ? year+'-'+month+'-'+day : day+'.'+month+'.'+year) + (this.props.time ? ' '+time : ''); } this.props.onChange(time); this.props.hide && this.props.hide(); } parseValue(update) { if (!this.prevProps || this.props.value != this.prevProps.value) { if (this.props.value instanceof Date) this.selected = this.props.value; else { this.selected = null; let date_in_input = (''+this.props.value).replace(/\s+.*$/, ''); // Remove time if (date_in_input) { // date format is HARDCODE let date_parts = date_in_input.split("-"); if (date_parts.length == 3) { // Y-m-d date_parts[1]--; // Month starts with 0 this.selected = new Date(date_parts[0], date_parts[1], date_parts[2]); } else if (date_parts.length == 1) { date_parts = date_in_input.split('.'); if (date_parts.length == 3) { // d.m.Y date_parts[1]--; // Month starts with 0 this.selected = new Date(date_parts[2], date_parts[1], date_parts[0]); } } if (isNaN(this.selected)) this.selected = null; } } if (update) { setImmediate(() => this.setState({ year: this.selected.getFullYear(), month: this.selected.getMonth(), })); } this.prevProps = this.props; } return this.selected; } showMonths(year) { this.setState({ year, mode: 'months' }); } showYears(year) { this.setState({ year, mode: 'years' }); } showDays(year, month) { this.setState({ year, month, mode: 'days' }); } renderMonths() { let year = this.state.year; let today = this.props.today || new Date(); let cur_y = today.getFullYear(); let cur_m = today.getMonth(); let selected = this.parseValue(true); let sel_m = selected && selected.getFullYear() == year ? selected.getMonth() : -1; let months = [ [ 0, 1, 2 ], [ 3, 4, 5 ], [ 6, 7, 8 ], [ 9, 10, 11 ] ]; return ( {months.map((g, idx) => ( {g.map(i => ( ))} ))}
this.showMonths(year-1)} title={(year-1)} className='prev'> this.showYears(year)}>{year} this.showMonths(year+1)} title={(year+1)} className='next'>
cur_y || year == cur_y && i > cur_m ? 'future' : 'today')) + (i == sel_m ? ' selected' : '')}> this.showDays(year, i)}> {this.props.monthNames[i]}
); } renderYears() { let year = this.state.year; let beg = year & ~15; let today = this.props.today || new Date(); let cur_y = today.getFullYear(); let selected = this.parseValue(true); let sel_y = selected ? selected.getFullYear() : -1; return ( {[0, 1, 2, 3].map(r => ( {[0, 1, 2, 3].map(j => { let i = beg + j + r*4; let class_name = (i < cur_y ? 'past' : (i > cur_y ? 'future' : 'today')) + (i == sel_y ? ' selected' : ''); return (); })} ))}
this.showYears(year-16)} title={(beg-16)+" - "+(beg-1)} className='prev'> {beg+' - '+(beg+15)} this.showYears(year+16)} title={(beg+16)+" - "+(beg+31)} className='next'>
this.showMonths(i)}>{i}
); } _yearOptions(min, max, year) { let r = []; for (let i = min; i < max; i++) r.push(); return r; } /// Creates a calendar with the date given in the argument as the selected date. renderDays() { let { year, month } = this.state; let { selectboxes, sunday, monthNames } = this.props; let selected = this.parseValue(true); let today = this.props.today || new Date(); // Display the table let next_month = month+1; let next_month_year = year; if (next_month >= 12) { next_month = 0; next_month_year++; } let previous_month = month-1; let previous_month_year = year; if (previous_month < 0) { previous_month = 11; previous_month_year--; } let current_year = today.getFullYear(); // Get the first day of this month let first_day = new Date(year, month, 1); let start_day = (first_day.getDay()+sunday)%7; let d = 1; let flag = 0; // Leap year support let days_in_this_month = (month == 2 ? (!(year % 4) && ((year % 100) || !(year % 400)) ? 29 : 28) : ((month < 7) == !(month & 1) ? 31 : 30)); const all_diff = (year - today.getFullYear()) || (month - today.getMonth()); const minDate = this.props.minDate === 'today' ? today : this.props.minDate; const maxDate = this.props.maxDate === 'today' ? today : this.props.maxDate; const month_disabled = minDate && (year < minDate.getFullYear() || year == minDate.getFullYear() && month < minDate.getMonth()) || maxDate && (year > maxDate.getFullYear() || year == maxDate.getFullYear() && month > maxDate.getMonth()); const min_md = minDate && year == minDate.getFullYear() && month == minDate.getMonth() ? minDate.getDate() : null; const max_md = maxDate && year == maxDate.getFullYear() && month == maxDate.getMonth() ? maxDate.getDate() : null; const sel_day = selected && year == selected.getFullYear() && month == selected.getMonth() ? selected.getDate() : -1; return ( {this.props.weekdays.map((name, idx) => ())} {[0, 1, 2, 3, 4].map(i => ( (i*7 < days_in_this_month+start_day ? {[0, 1, 2, 3, 4, 5, 6].map(j => { let d = i*7+j+1-start_day; let visible = (i > 0 || j >= start_day) && (d <= days_in_this_month); if (visible) { let class_name = 'days'; let diff = all_diff || (d - today.getDate()); let disabled = month_disabled || min_md !== null && d < min_md || max_md !== null && d > max_md; if (diff < 0) class_name += ' past'; else if (!diff) class_name += ' today'; else class_name += ' future'; if (d == sel_day) class_name += ' selected'; if (disabled) class_name += ' disabled'; class_name += ' '+this.props.weekdayIds[j].toLowerCase(); return (); } else return (); })} : null) ))}
this.showDays(previous_month_year, previous_month)} title={monthNames[previous_month]+" "+previous_month_year} className='prev'> {!selectboxes ? [ this.showMonths(year, month)}>{monthNames[month]}, this.showYears(year)}>{year} ] : [ , ] } this.showDays(next_month_year,next_month)} title={this.props.monthNames[next_month]+" "+next_month_year} className='next'>
{name}
this.selectDate(year, month, d)}>{d}  
); } }