diff --git a/docs/app/Examples/modules/Datetime/Types/DateRangeExample.js b/docs/app/Examples/modules/Datetime/Types/DateRangeExample.js index 04788b8695..4c73da54b8 100644 --- a/docs/app/Examples/modules/Datetime/Types/DateRangeExample.js +++ b/docs/app/Examples/modules/Datetime/Types/DateRangeExample.js @@ -1,5 +1,5 @@ import React from 'react' -import { Datetime } from 'semantic-ui-react' +import {Datetime} from 'semantic-ui-react' const DateRangeExample = () => ( <Datetime.Range diff --git a/docs/app/Examples/modules/Datetime/Types/DateTimeExampleDateOnly.js b/docs/app/Examples/modules/Datetime/Types/DateTimeExampleDateOnly.js index 7ec328db6e..d5482b5a9b 100644 --- a/docs/app/Examples/modules/Datetime/Types/DateTimeExampleDateOnly.js +++ b/docs/app/Examples/modules/Datetime/Types/DateTimeExampleDateOnly.js @@ -2,7 +2,9 @@ import React from 'react' import { Datetime } from 'semantic-ui-react' const DateTimeExampleDateOnly = () => ( - <Datetime time={false} placeholder='Select Date' /> + <Datetime + time={false} + placeholder='Select Date' /> ) export default DateTimeExampleDateOnly diff --git a/docs/app/Examples/modules/Datetime/Types/DateTimeExampleMoment.js b/docs/app/Examples/modules/Datetime/Types/DateTimeExampleMoment.js new file mode 100644 index 0000000000..b03456c3d3 --- /dev/null +++ b/docs/app/Examples/modules/Datetime/Types/DateTimeExampleMoment.js @@ -0,0 +1,13 @@ +import React from 'react' +import { Datetime } from 'semantic-ui-react' +import moment from 'moment-timezone'; +import 'moment/locale/en-gb'; + +const DateTimeExampleMoment = () => ( + <Datetime + time + dateHandler='moment' + defaultValue={moment.tz('2017-04-24T12:35', 'Europe/London')} /> +) + +export default DateTimeExampleMoment diff --git a/docs/app/Examples/modules/Datetime/Types/index.js b/docs/app/Examples/modules/Datetime/Types/index.js index 88b6461377..6eec70d33f 100644 --- a/docs/app/Examples/modules/Datetime/Types/index.js +++ b/docs/app/Examples/modules/Datetime/Types/index.js @@ -11,6 +11,11 @@ const DatetimeTypesExamples = () => ( description='A full Date and Time selector, with initial value of the current date and time' examplePath='modules/Datetime/Types/DateTimeExampleFull' /> + <ComponentExample + title='Date and Time using Moment-Timezone dates' + description='' + examplePath='modules/Datetime/Types/DateTimeExampleMoment' + /> <ComponentExample title='Date' description='A date only Date selector' diff --git a/src/lib/customPropTypes.js b/src/lib/customPropTypes.js index 5560abe641..37f2ffa734 100644 --- a/src/lib/customPropTypes.js +++ b/src/lib/customPropTypes.js @@ -343,7 +343,7 @@ export const DateValue = (props, propName, componentName) => { return new Date(value) != 'Invalid Date' && !isNaN(new Date(value)) ? null : new Error(propName + ' in ' + componentName + ' cannot be parsed as a date') } else if (typeof value === 'object') { - return value.getDate != undefined ? null : + return value.getDate != undefined || value._isAMomentObject != undefined ? null : new Error(propName + ' in ' + componentName + ' is not a Date or a string parsable date') } } diff --git a/src/modules/Datetime/Calendar.js b/src/modules/Datetime/Calendar.js index f4cd6776ad..d4776dda10 100644 --- a/src/modules/Datetime/Calendar.js +++ b/src/modules/Datetime/Calendar.js @@ -1,4 +1,4 @@ -import React, { PropTypes } from 'react' +import React, { PropTypes, Component } from 'react' import CalendarMenu from './CalendarMenu' import Month from './Month' import Months from './Months' @@ -7,7 +7,7 @@ import Hours from './Hours' import Minutes from './Minutes' import { - AutoControlledComponent as Component, + //AutoControlledComponent as Component, customPropTypes, META, } from '../../lib' @@ -83,6 +83,7 @@ export default class Calendar extends Component { /** Current value as a Date object or a string that can be parsed into one. */ value: customPropTypes.DateValue, + dateHandler: PropTypes.func } static defaultProps = { @@ -91,31 +92,39 @@ export default class Calendar extends Component { date: true, time: true, range: false, + mode: 'day', + selectionStart: null, + selectionEnd: null } - static autoControlledProps = [ - 'value', - 'mode', - 'selectionStart', - 'selectionEnd', - ] + // static autoControlledProps = [ + // 'value', + // 'mode', + // 'selectionStart', + // 'selectionEnd', + // ] constructor(props) { super(props) - this.state = { - value: new Date(), - mode: this.getInitialMode(props), - } - } + const {dateHandler} = props + this.Date = dateHandler + // const initialValue = new this.Date(new Date()).getDate() + // this.state = { + // value: initialValue, + // mode: this.getInitialMode(props), + // } - getInitialMode(props) { - const { date, time } = props - return !date && time ? 'hour' : 'day' } - getYear = () => this.state.value.getFullYear() - getMonth = () => this.state.value.getMonth() - getHour = () => this.state.value.getHours() + // getInitialMode(props) { + // const { date, time } = props + // return !date && time ? 'hour' : 'day' + // } + + getYear = () => new this.Date(this.props.value).year() + getMonth = () => new this.Date(this.props.value).month() + getHour = () => new this.Date(this.props.value).hours() + getDate = () => new this.Date(this.props.value).day() getMonthName() { const { content } = this.props @@ -123,85 +132,66 @@ export default class Calendar extends Component { } setMonth = (e, props) => { + console.log("Calendar setMonth()", props) e.stopPropagation() const { value, page } = props + const { onDateSelect } = this.props const nextMode = 'day' - const date = new Date(this.state.value) + const date = new this.Date(this.props.value) const month = !value && page - ? date.getMonth() + page + ? date.month() + page : value - date.setMonth(month) - this.trySetState({ - value: date, - mode: nextMode, - }) - if (this.props.onChangeMonth) { - this.props.onChangeMonth(date) - } + date.month(month) + onDateSelect(e, date.getDate(), nextMode) + // this.trySetState({ + // value: date.getDate(), + // mode: nextMode, + // }) + // if (this.props.onChangeMonth) { + // this.props.onChangeMonth(date.day()) + // } } setYear = (e, year, nextMode = 'day') => { e.stopPropagation() - const date = new Date(this.state.value) - date.setYear(year) - this.trySetState({ - value: date, - mode: nextMode, - }) + const {value, onDateSelect} = this.props + const date = new this.Date(value) + date.year(year) + onDateSelect(e, date.getDate(), nextMode) } setHour = (e, hour, nextMode = 'minute') => { e.stopPropagation() - const date = new Date(this.state.value) - date.setHours(hour) - this.trySetState({ - value: date, - mode: nextMode, - }) + const {value, onDateSelect} = this.props + const date = new this.Date(value) + date.hours(hour) + onDateSelect(e, date.getDate(), nextMode) } setMinute = (e, minute) => { e.stopPropagation() - const { onDateSelect } = this.props - const date = new Date(this.state.value) - date.setMinutes(minute) - const extraState = {} - if (this.props.range) { - extraState.mode = 'day' - } - this.trySetState({ - value: date, - ...extraState, - }) - if (onDateSelect) { - onDateSelect(e, new Date(date)) - } + const { onDateSelect, value } = this.props + const date = new this.Date(value) + date.minutes(minute) + const nextMode = this.props.range ? ' day' : null + onDateSelect(e, date.getDate(), nextMode) } setDay = (e, day) => { e.stopPropagation() - const date = new Date(this.state.value) - date.setDate(day) - const { onDateSelect, time } = this.props - const nextMode = time ? 'hour' : this.state.mode - const rangeState = {} - if (this.props.range) { - rangeState.selectionStart = date - } - this.trySetState({ - value: date, - mode: nextMode, - ...rangeState, - }) - if (!time && onDateSelect) { - onDateSelect(e, new Date(date)) - } + const { onDateSelect, time, range, value, mode } = this.props + const date = new this.Date(value) + date.day(day) + + const selectedDate = date.getDate() + const nextMode = time ? 'hour' : null + onDateSelect(e, selectedDate, nextMode, range ? date : null) } page = (direction, e) => { e.stopPropagation() - const { mode } = this.state + const { mode } = this.props switch (mode) { case 'day': this.setMonth(e, { page: direction }) @@ -228,20 +218,28 @@ export default class Calendar extends Component { */ changeMode = (mode, e) => { e.stopPropagation() - this.trySetState({ mode }) + const {value, onDateSelect} = this.props + onDateSelect(e, value, mode) } /** * Returns the calendar body content */ getBodyContent() { - const { content, firstDayOfWeek, disabledDates } = this.props - const { mode, value, selectionStart, selectionEnd } = this.state + const { + content, + firstDayOfWeek, + disabledDates, + mode, + value, + selectionStart, + selectionEnd } = this.props switch (mode) { case 'day': return ( <Month firstDayOfWeek={firstDayOfWeek} + dateHandler={this.Date} content={content} onClick={this.setDay} date={value} @@ -269,13 +267,13 @@ export default class Calendar extends Component { } render() { - const { date } = this.props - const { mode, value } = this.state + const { date, mode, value } = this.props + const calendarDay = this.getDate() return ( <div style={style}> {date && ( <CalendarMenu - date={value} + value={calendarDay} monthName={this.getMonthName()} year={this.getYear()} mode={mode} diff --git a/src/modules/Datetime/CalendarMenu.js b/src/modules/Datetime/CalendarMenu.js index 7e1ae62368..fc356c77f1 100644 --- a/src/modules/Datetime/CalendarMenu.js +++ b/src/modules/Datetime/CalendarMenu.js @@ -24,9 +24,8 @@ export default class CalendarMenu extends Component { /** Year **/ year: PropTypes.number, - // TODO consider using `value` instead - /** Current date **/ - date: PropTypes.any, + /** Current day of the month **/ + value: PropTypes.number, /** Current calendar mode **/ mode: PropTypes.oneOf(['minute', 'hour', 'day', 'month', 'year']), @@ -42,7 +41,7 @@ export default class CalendarMenu extends Component { onPrevious: PropTypes.func, /** Called when paginating to the next month. */ - onNext: PropTypes.func, + onNext: PropTypes.func } static _meta = { @@ -50,10 +49,10 @@ export default class CalendarMenu extends Component { parent: 'Datetime', type: META.TYPES.MODULE, } - + render() { const { - date, + value, mode, monthName, onChangeMode, @@ -75,7 +74,7 @@ export default class CalendarMenu extends Component { ), _.includes(mode, ['hour', 'minute']) && ( <Menu.Item key='hour-minute' onClick={onChangeMode.bind(null, 'month')}> - {monthName} {date.getDate()} + {monthName} {value} </Menu.Item> ), _.includes(mode, ['day', 'month', 'hour', 'minute']) && ( diff --git a/src/modules/Datetime/DateRange.js b/src/modules/Datetime/DateRange.js index 1f54f780b9..ee2fc08e66 100644 --- a/src/modules/Datetime/DateRange.js +++ b/src/modules/Datetime/DateRange.js @@ -7,11 +7,12 @@ import { META, } from '../../lib' -import { defaultDateFormatter, defaultTimeFormatter } from '../../lib/dateUtils' +//import { defaultDateFormatter, defaultTimeFormatter } from '../../lib/dateUtils' import Calendar from './Calendar' import Input from '../../elements/Input' import Popup from '../Popup' import Grid from '../../collections/Grid' +import {getDateHandlerClass} from './handlers' const debug = makeDebugger('datetime') @@ -27,6 +28,9 @@ export default class DateRange extends Component { } static propTypes = { + /** An element type to render as (string or function). */ + as: customPropTypes.as, + /** * Textual content for the various text element of the calendar. * { @@ -52,6 +56,7 @@ export default class DateRange extends Component { * am: 'AM', * pm: 'PM', * } + * @type {Object} */ content: PropTypes.object, @@ -60,17 +65,26 @@ export default class DateRange extends Component { /** * A function that will return a Date object as a formatted string in the - * current locale. By default the Date will formatted as YYYY-MM-DD. + * current locale. By default the Date will formatted as YYYY-MM-DD + * @type {function} */ - // TODO add signature dateFormatter: PropTypes.func, + /** A disabled dropdown menu or item does not allow user interaction. */ + disabled: PropTypes.bool, + + /** An array of dates that should be marked disabled in the calendar. */ + disabledDates: PropTypes.arrayOf(customPropTypes.DateValue), + /** initial value for left and right months **/ defaultMonths: PropTypes.arrayOf(PropTypes.number), /** Initial value of open. */ defaultOpen: PropTypes.bool, + /** Initial value as an array of Date object or a string that can be parsed into one. */ + defaultValue: PropTypes.arrayOf(customPropTypes.DateValue), + /** Default value for rangeFocus. */ defaultRangeFocus: PropTypes.number, @@ -80,12 +94,6 @@ export default class DateRange extends Component { /** The initial value for selectionStart. */ defaultSelectionStart: customPropTypes.DateValue, - /** Initial value as an array of Date object or a string that can be parsed into one. */ - defaultValue: PropTypes.arrayOf(customPropTypes.DateValue), - - /** A disabled dropdown menu or item does not allow user interaction. */ - disabled: PropTypes.bool, - /** An errored dropdown can alert a user to a problem. */ error: PropTypes.bool, @@ -98,9 +106,6 @@ export default class DateRange extends Component { PropTypes.object, ]), - /** An array of dates that should be marked disabled in the calendar. */ - disabledDates: PropTypes.arrayOf(customPropTypes.DateValue), - /** Do not allow dates after maxDate. */ maxDate: customPropTypes.DateValue, @@ -165,10 +170,12 @@ export default class DateRange extends Component { 'rangeFocus', 'selectionStart', 'selectionEnd', + 'mode' ] static defaultProps = { icon: 'calendar', + dateHandler: 'native', content: { daysShort: ['S', 'M', 'T', 'W', 'T', 'F', 'S'], daysFull: ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'], @@ -192,12 +199,37 @@ export default class DateRange extends Component { am: 'AM', pm: 'PM', }, - dateFormatter: defaultDateFormatter, - timeFormatter: defaultTimeFormatter, + disabledDates: [], + dateFormatter: null, //defaultDateFormatter, + timeFormatter: null, //defaultTimeFormatter, date: true, time: false, } + constructor(props) { + super(props) + const { + dateHandler, + dateFormatter, + timeFormatter, + timeZone + } = this.props + // set Date as the date handler for this instance + this.Date = getDateHandlerClass(dateHandler, { + dateFormatter, + timeFormatter, + timeZone + }) + this.state = { + mode: this.getInitialMode() + } + } + + getInitialMode() { + const { date, time } = this.props + return !date && time ? 'hour' : 'day' + } + open = (e) => { debug('open()') @@ -216,7 +248,10 @@ export default class DateRange extends Component { const { onClose } = this.props if (onClose) onClose(e, this.props) - this.trySetState({ open: false }) + this.trySetState({ + open: false, + mode: this.getInitialMode() + }) } toggle = (e) => this.state.open ? this.close(e) : this.open(e) @@ -280,23 +315,18 @@ export default class DateRange extends Component { /** * Return a formatted date or date/time string */ - getFormattedDate(value = this.state.value) { - const formatted = [] - if (!value) return '' - - const { date, time, dateFormatter, timeFormatter } = this.props - value.forEach((item) => { - if (item) { - if (date && time) { - formatted.push(`${dateFormatter(item)} ${timeFormatter(item)}`) - } else if (!date && time) { - formatted.push(timeFormatter(item)) - } - formatted.push(dateFormatter(item)) - } - }) - return formatted.join(' ') - } + getFormattedDate(value) { + value = value || this.state.value + const { date, time, dateFormatter, timeFormatter } = this.props + const _date = new this.Date(value) + if (date && time) { + return _date.format() + } else if (!date && time) { + return _date.formatTime(value) + } else { + return _date.formatDate(value) + } + } /** * Get a 2 element array to determine the left and right @@ -348,6 +378,7 @@ export default class DateRange extends Component { const { open, value, + mode, selectionStart, selectionEnd, } = this.state @@ -365,7 +396,6 @@ export default class DateRange extends Component { value={this.getFormattedDate(value)} /> ) - return ( <Popup flowing @@ -386,6 +416,7 @@ export default class DateRange extends Component { <Grid.Column> <Calendar value={months[0]} + dateHandler={this.Date} content={this.props.content} onDateSelect={this.handleDateSelection.bind(this, 0)} onChangeMonth={this.handleMonthChange.bind(this, 0)} @@ -403,6 +434,7 @@ export default class DateRange extends Component { <Grid.Column> <Calendar value={months[1]} + dateHandler={this.Date} content={this.props.content} onDateSelect={this.handleDateSelection.bind(this, 1)} onChangeMonth={this.handleMonthChange.bind(this, 1)} diff --git a/src/modules/Datetime/Datetime.js b/src/modules/Datetime/Datetime.js index 7b403f111e..0275f288e1 100644 --- a/src/modules/Datetime/Datetime.js +++ b/src/modules/Datetime/Datetime.js @@ -7,11 +7,12 @@ import { META, } from '../../lib' -import { defaultDateFormatter, defaultTimeFormatter } from '../../lib/dateUtils' +//import { defaultDateFormatter, defaultTimeFormatter } from '../../lib/dateUtils' import Calendar from './Calendar' import DateRange from './DateRange' import Input from '../../elements/Input/Input' import Popup from '../Popup/Popup' +import {getDateHandlerClass} from './handlers' const debug = makeDebugger('datetime') @@ -149,15 +150,22 @@ export default class Datetime extends Component { /** Current value as a Date object or a string that can be parsed into one. */ value: customPropTypes.DateValue, + /** Placeholder text. */ + dateHandler: PropTypes.string, + timeZone: PropTypes.string, + defaultMode: PropTypes.string, + mode: PropTypes.string } static autoControlledProps = [ 'open', 'value', + 'mode' ] static defaultProps = { icon: 'calendar', + dateHandler: 'native', content: { daysShort: ['S', 'M', 'T', 'W', 'T', 'F', 'S'], daysFull: ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'], @@ -182,12 +190,36 @@ export default class Datetime extends Component { pm: 'PM', }, disabledDates: [], - dateFormatter: defaultDateFormatter, - timeFormatter: defaultTimeFormatter, + dateFormatter: null, //defaultDateFormatter, + timeFormatter: null, //defaultTimeFormatter, date: true, time: true, } + constructor(props) { + super(props) + const { + dateHandler, + dateFormatter, + timeFormatter, + timeZone + } = this.props + // set Date as the date handler for this instance + this.Date = getDateHandlerClass(dateHandler, { + dateFormatter, + timeFormatter, + timeZone + }) + this.state = { + mode: this.getInitialMode() + } + } + + getInitialMode() { + const { date, time } = this.props + return !date && time ? 'hour' : 'day' + } + open = (e) => { debug('open()') @@ -203,7 +235,10 @@ export default class Datetime extends Component { const { onClose } = this.props if (onClose) onClose(e, this.props) - this.trySetState({ open: false }) + this.trySetState({ + open: false, + mode: this.getInitialMode() + }) } toggle = (e) => this.state.open ? this.close(e) : this.open(e) @@ -224,15 +259,29 @@ export default class Datetime extends Component { this.close(e) } - handleDateSelection = (e, date) => { + handleDateSelection = (e, date, nextMode, rangeStart) => { debug('handleDateSelection()', date, e) + console.log('handleDateSelection', date, nextMode, rangeStart) + //const _date = new this.Date(date) e.stopPropagation() e.nativeEvent.stopImmediatePropagation() - const selectedDate = new Date(date) + //const selectedDate = _date.getDate() this.trySetState({ - value: selectedDate, + value: date, + mode: nextMode + }) + if (!nextMode) { + this.close() + } + } + + onSetMonth = (value, nextMode) => { + debug('onSetMonth()', value, nextMode) + console.log('onSetMonth', value, nextMode) + this.trySetState({ + value: value, + mode: nextMode }) - this.close() } /** @@ -241,14 +290,14 @@ export default class Datetime extends Component { getFormattedDate(value) { value = value || this.state.value const { date, time, dateFormatter, timeFormatter } = this.props - + const _date = new this.Date(value) if (date && time) { - return `${dateFormatter(value)} ${timeFormatter(value)}` + return _date.format() } else if (!date && time) { - return timeFormatter(value) + return _date.formatTime(value) + } else { + return _date.formatDate(value) } - - return dateFormatter(value) } render() { @@ -267,8 +316,7 @@ export default class Datetime extends Component { minDate, disabledDates, } = this.props - const { open, value } = this.state - + const { open, value, mode } = this.state const inputElement = ( <Input type='text' @@ -281,7 +329,6 @@ export default class Datetime extends Component { value={this.getFormattedDate(value)} /> ) - return ( <Popup on='click' @@ -298,14 +345,18 @@ export default class Datetime extends Component { closeOnDocumentClick={false} > <Calendar + mode={mode} content={content} + dateHandler={this.Date} onDateSelect={this.handleDateSelection} + onChangeMonth={this.onSetMonth} timeFormatter={timeFormatter} firstDayOfWeek={firstDayOfWeek} time={time} date={date} minDate={minDate} disabledDates={disabledDates} + value={value} /> </Popup> ) diff --git a/src/modules/Datetime/Month.js b/src/modules/Datetime/Month.js index 80105a94a3..49eb6f3c0e 100644 --- a/src/modules/Datetime/Month.js +++ b/src/modules/Datetime/Month.js @@ -35,6 +35,7 @@ export default class Month extends Component { /** Dates at or after selectionStart are marked as selected. */ selectionStart: customPropTypes.DateValue, + dateHandler: PropTypes.any } static _meta = { @@ -49,6 +50,8 @@ export default class Month extends Component { constructor(props) { super(props) + const {dateHandler} = props + this.Date = dateHandler this.state = { selectionStart: props.selectionStart, selectionEnd: props.selectionEnd, @@ -81,14 +84,6 @@ export default class Month extends Component { return labels.map((day, index) => <Table.HeaderCell key={index}>{day}</Table.HeaderCell>) } - getDateString(date) { - return `${date.getFullYear()}${date.getMonth()}${date.getDate()}` - } - - getDateStrings(dates) { - return dates.map(date => this.getDateString(date)) - } - /** * Return a 42 element array (number of cells in the calendar month), * populated with DayCell instances of either days of the current month, @@ -97,14 +92,16 @@ export default class Month extends Component { getDays() { const { date, onClick, disabledDates } = this.props const { selectionStart, selectionEnd } = this.state - const firstDay = utils.getFirstOfMonth(date) - const firstWeekDay = firstDay.getDay() - const daysInMonth = utils.daysInMonth(date) - const lastMonth = utils.lastMonth(date) - const prevDaysInMonth = utils.daysInMonth(lastMonth) + console.log('disabltd', disabledDates) + const _date = new this.Date(date) + const firstDay = _date.getFirstOfMonth() + const firstWeekDay = _date.getWeekDay(firstDay) + const daysInMonth = _date.daysInMonth() + const lastMonth = new this.Date(_date.lastMonth()) + const prevDaysInMonth = lastMonth.daysInMonth() // get a list of disabled date signatures const hasDisabledDates = disabledDates.length > 0 - const disabledDateSig = this.getDateStrings(disabledDates) + const disabledDateSig = _date.getDateStrings(disabledDates) // 42 days in a calendar block will be enough to wrap a full month const monthCells = _.range(0, 42) // The real first day in relation to the sequene of calendar days (array index) @@ -116,25 +113,26 @@ export default class Month extends Component { let day = 0 let nextDay = 0 return monthCells.map((cell, index) => { - const dayCellDate = new Date(firstDay) + const dayCellDate = new this.Date(firstDay) const dayParams = { - index: cell, + index: cell } + //debugger if (cell >= realFirstWeekDay && day < daysInMonth) { dayParams.day = day += 1 } else if (cell < realFirstWeekDay) { dayParams.day = prevDaysInMonth - realFirstWeekDay + cell + 1 dayParams.disabled = true - dayCellDate.setMonth(lastMonth.getMonth()) + dayCellDate.month(lastMonth.month()) } else if (cell > daysInMonth) { dayParams.day = nextDay += 1 dayParams.disabled = true - dayCellDate.setMonth(dayCellDate.getMonth() + 1) + dayCellDate.month(dayCellDate.month() + 1) } dayParams.onClick = (e) => { onClick(e, dayParams.day) } - dayCellDate.setDate(dayParams.day) + dayCellDate.day(dayParams.day) dayParams.date = dayCellDate if (selectionStart) { dayParams.onMouseOver = () => { @@ -146,7 +144,7 @@ export default class Month extends Component { dayParams.selected = this.isCellSelected(dayCellDate, selectionStart, selectionEnd) if (hasDisabledDates && !dayParams.disabled && - disabledDateSig.indexOf(this.getDateString(dayParams.date)) > -1) { + disabledDateSig.indexOf(_date.getDateString(dayCellDate.getDate())) > -1) { dayParams.disabled = true } return dayParams diff --git a/src/modules/Datetime/handlers/base.js b/src/modules/Datetime/handlers/base.js new file mode 100644 index 0000000000..ffbea7f1b6 --- /dev/null +++ b/src/modules/Datetime/handlers/base.js @@ -0,0 +1,78 @@ +/** + * A root class for Date Handlers. + * Any new Date handler can be plugged in if it supprots the interface + * provided by this base class. + */ + +export default class BaseDateHandler { + /** + * The constructor receives a date or date parsable string as value + * It can also receive a custom date/time formatter functions and + * an optional timezone where relevant. + */ + constructor(date, dateFormatter=null, timeFormatter=null, timeZone=null) { + this.date = date; + this._dateFormatter = dateFormatter + this._timeFormatter = timeFormatter + } + + /** + * Returns a string formatted date/time value + */ + format(formatString) {} + + /** + * Returns a date formatted string + */ + formatDate(formatString) {} + + /** + * Return a time formatted string + */ + formatTime(formatString) {} + + /** + * Returns yesterday's date + */ + yesterday() {} + + /** + * Returns tomorrow's date + */ + tomorrow() {} + + /** + * Return a native Date object + */ + getDate() {} + + /** + * Get or set the year of the date + */ + year(value) {} + + /** + * Get or set the month of the date + */ + month(value) {} + + /** + * Get or set the day (date) of the date + */ + day(value) {} + + /** + * Get or set the hours of the date + */ + hours(value) {} + + /** + * Get or set the minutes of the date + */ + minutes(value) {} + + /** + * Get or set the seconds of the date + */ + seconds(value) {} +} diff --git a/src/modules/Datetime/handlers/index.js b/src/modules/Datetime/handlers/index.js new file mode 100644 index 0000000000..89be2514ec --- /dev/null +++ b/src/modules/Datetime/handlers/index.js @@ -0,0 +1,19 @@ +import {getNativeDateHandler} from './native' +import {getMomentDateHandler} from './moment' + +export const dateHandlers = { + 'moment': getMomentDateHandler, + 'native': getNativeDateHandler +} + +export function registerDateHandler(handler, func) { + dateHandlers[handler] = func +} + +export function getDateHandlerClass(handler, settings={}) { + return dateHandlers[handler](settings) +} + +export function getDateHandler(handler, date, settings={}) { + return new getDateHandlerClass(handler, settings)(date) +} diff --git a/src/modules/Datetime/handlers/moment.js b/src/modules/Datetime/handlers/moment.js new file mode 100644 index 0000000000..9c49662845 --- /dev/null +++ b/src/modules/Datetime/handlers/moment.js @@ -0,0 +1,209 @@ +import moment from 'moment-timezone' +/** + * A Date handler using moment.js to manage accessing Date values + * using moment or moment-timezone objects. + * The handler is pluggable and supports creating, reading and formatting + * date values. + * Note that a moment-timezone can facilitate date/time values in different + * timezones which are not relying on the local machine's time zone setting. + */ +export function getMomentDateHandler(settings={}) { + class DateHandler { + static settings = settings + constructor(date) { + if (!date) { + date = moment.tz() + } + if (date.tz && date.tz()) { + this.timeZone = date.tz() + } else { + this.timeZone = settings.timeZone || null + } + this.set(date) + } + + /** + * Return the settings as provided by the closure + */ + getSettings() { + return settings + } + + /** + * re/set a new date on this instance + */ + set(date) { + if (date._isAMomentObject) { + this.date = date + } else { + if (this.timeZone) { + this.date = moment.tz(date, this.timeZone) + } else { + this.date = moment(date) + } + } + } + + /** + * Returns a string formatted date/time value + */ + format(formatString) { + if (this.date) { + return `${this.formatDate()} ${this.formatTime()}` + } else { + return '' + } + } + + /** + * Returns a date formatted string + */ + formatDate(formatString) { + if (this.date) { + const settings = this.getSettings() + if (settings.dateFormatter) { + return settings.dateFormatter(this.date) + } + return this.date.format('L') + } else { + return '' + } + } + + /** + * Return a time formatted string + */ + formatTime(formatString) { + if (this.date) { + const settings = this.getSettings() + if (settings.timeFormatter) { + return settings.timeFormatter(this.date) + } + return this.date.format('LT') + } else { + return '' + } + } + + /** + * Returns a date of the first of the current month. + * A new instance is created as moment mutates the date + * when calling manipulation methods. + */ + getFirstOfMonth(date) { + date = date || this.date + const newDate = moment.tz(date, date.tz()) + return newDate.startOf('month') + } + + getWeekDay(date) { + date = date || this.date + return date.day() + } + + /** + * Returns the number of days in the month + */ + daysInMonth(date) { + date = date || this.date + return date.daysInMonth() + } + + /** + * Create a new date, one month ago from current date. + * A new instance is created as moment mutates the date + * when calling manipulation methods. + */ + lastMonth(date) { + date = date || this.date + const newDate = moment.tz(date, date.tz()) + return newDate.subtract(1, 'months') + } + + /** + * Returns a YYYYMMDD string representation of this date. + * Used to create date signatures to determine disabled dates in a + * calendar month + */ + getDateString(date) { + date = date || this.date + return date.format('YYYYMMDD') + } + + /** + * Returns a list of YYYYMMMDD date signatures for a list of dates + */ + getDateStrings(dates) { + if (dates && dates.length) { + return dates.map(date => this.getDateString(date)) + } + return [] + } + + /** + * Returns a new moment of yesterday's date + */ + yesterday() { + const newDate = moment.tz(this.date, this.date.tz()) + return newDate.subtract(1, 'days') + } + + /** + * Returns a new moment of tomorrow's date + */ + tomorrow() { + const newDate = moment.tz(this.date, this.date.tz()) + return newDate.add(1, 'days') + } + + /** + * Return a native Date object + */ + getDate() { + return this.date + } + + /** + * Get or set the year of the date + */ + year(value) { + return this.date.year(value) + } + + /** + * Get or set the month of the date + */ + month(value) { + return this.date.month(value) + } + + /** + * Get or set the date of the date + */ + day(value) { + return this.date.date(value) + } + + /** + * Get or set the hours of the date + */ + hours(value) { + return this.date.hours(value) + } + + /** + * Get or set the minutes of the date + */ + minutes(value) { + return this.date.minutes(value) + } + + /** + * Get or set the seconds of the date + */ + seconds(value) { + return this.date.seconds(value) + } + } + return DateHandler +} diff --git a/src/modules/Datetime/handlers/native.js b/src/modules/Datetime/handlers/native.js new file mode 100644 index 0000000000..d965761801 --- /dev/null +++ b/src/modules/Datetime/handlers/native.js @@ -0,0 +1,220 @@ +/** + * A native javascript Date handler to manage accessing Date values + * using Date objects. It is used as a replacement Date object when storing + * state. + * The handler is pluggable and supports creating, reading and formatting + * date values. + * Note that a native javascript Date takes on the timezone of the browser + * and therefore is less suitable to support dates from varying time zones. + */ + +export function getNativeDateHandler(settings={}) { + class DateHandler { + static settings = settings + constructor(date) { + this.set(date) + } + + /** + * Return the settings as provided by the closure + */ + getSettings() { + return settings + } + + /** + * re/set a new date on this instance + */ + set(date) { + if (typeof(date) == 'string' && date.trim().length == 0) { + this.date = new Date() + } else { + this.date = new Date(date) + } + return this + } + + /** + * Pad a number with a zero if it's one digit + * @param {number} n + * @return {string} Returns the number padded with a zero if below 10 + */ + zeroPad(n) { + return (n < 10 ? '0' : '') + n + } + + /** + * Returns a string formatted date/time value + */ + format(formatString) { + if (this.date) { + return `${this.formatDate()} ${this.formatTime()}` + } else { + return '' + } + } + + /** + * Returns a date formatted string + */ + formatDate(formatString) { + if (this.date && this.date != 'Invalid Date') { + const settings = this.getSettings() + if (settings.dateFormatter) { + return settings.dateFormatter(this.date) + } + return `${this.date.getFullYear()}-${this.zeroPad(this.date.getMonth() + 1)}-${this.zeroPad(this.date.getDate())}` + } else { + return '' + } + } + + /** + * Return a time formatted string + */ + formatTime(formatString) { + if (this.date) { + const settings = this.getSettings() + if (settings.timeFormatter) { + return settings.timeFormatter(this.date) + } + return `${this.zeroPad(this.date.getHours())}:${this.zeroPad(this.date.getMinutes())}` + } else { + return '' + } + } + + getFirstOfMonth(date) { + date = date || this.date + return new Date(date.getFullYear(), date.getMonth(), 1) + } + + getWeekDay(date) { + date = date || this.date + return date.getDay() + } + + daysInMonth(date) { + date = date || this.date + return new Date(date.getFullYear(), date.getMonth() + 1, 0).getDate() + } + + /** + * Returns a YYYYMMDD string representation of this date. + * Used to create date signatures to determine disabled dates in a + * calendar month + */ + getDateString(date) { + date = date || this.date + return `${date.getFullYear()}${date.getMonth()}${date.getDate()}` + } + + /** + * Returns a list of YYYYMMMDD date signatures for a list of dates + */ + getDateStrings(dates) { + if (dates && dates.length) { + return dates.map(date => this.getDateString(date)) + } + return [] + } + + /** + * Return a date from the last month + */ + lastMonth(date) { + date = date || this.date + const _date = new Date(date) + _date.setMonth(date.getMonth() - 1) + return _date + } + + + /** + * Returns yesterday's date + */ + yesterday() { + const _date = new Date(this.date) + _date.setDate(this.date.getDate() - 1) + return _date + } + + /** + * Returns tomorrow's date + */ + tomorrow() { + const _date = new Date(this.date) + _date.setDate(this.date.getDate() + 1) + return _date + } + + /** + * Return a native Date object + */ + getDate() { + return this.date + } + + /** + * Get or set the year of the date + */ + year(value) { + if (value) { + this.date.setFullYear(value) + } + return this.date.getFullYear() + } + + /** + * Get or set the month of the date + */ + month(value) { + if (value) { + this.date.setMonth(value) + } + return this.date.getMonth() + } + + /** + * Get or set the calendar date of the date + */ + day(value) { + if (value) { + this.date.setDate(value) + } + return this.date.getDate() + } + + /** + * Get or set the hours of the date + */ + hours(value) { + if (value) { + this.date.setHours(value) + } + console.log("NATIVE", this.date, this.date.getHours()) + return this.date.getHours() + } + + /** + * Get or set the minutes of the date + */ + minutes(value) { + if (value) { + this.date.setMinutes(value) + } + return this.date.getMinutes() + } + + /** + * Get or set the seconds of the date + */ + seconds(value) { + if (value) { + this.date.setSeconds(value) + } + return this.date.getSeconds() + } + } + return DateHandler +}