import { DateMarker , startOfDay , addDays } from './datelib/marker' ; import { Duration , createDuration , asRoughDays , asRoughMs , greatestDurationDenominator } from './datelib/duration' ; import { DateRange , OpenDateRange , constrainMarkerToRange , intersectRanges , rangesIntersect , parseRange , DateRangeInput , } from './datelib/date-range' ; import { DateEnv , DateInput } from './datelib/env' ; import { computeVisibleDayRange } from './util/date' ; import { getNow } from './reducers/current-date' ; import { CalendarApi } from './CalendarApi' ; export interface DateProfile { currentRange : DateRange ; currentRangeUnit : string ; isRangeAllDay : boolean ; validRange : OpenDateRange ; activeRange : DateRange | null ; renderRange : DateRange ; slotMinTime : Duration ; slotMaxTime : Duration ; isValid : boolean ; dateIncrement : Duration ; } export interface DateProfileGeneratorProps extends DateProfileOptions { dateProfileGeneratorClass : DateProfileGeneratorClass ; duration : Duration ; durationUnit : string ; usesMinMaxTime : boolean ; dateEnv : DateEnv ; calendarApi : CalendarApi ; } export interface DateProfileOptions { slotMinTime : Duration ; slotMaxTime : Duration ; showNonCurrentDates ? : boolean ; dayCount ? : number ; dateAlignment ? : string ; dateIncrement ? : Duration ; hiddenDays ? : number [ ] ; weekends ? : boolean ; nowInput ? : DateInput | ( ( ) => DateInput ) ; validRangeInput ? : DateRangeInput | ( ( this : CalendarApi , nowDate : Date ) => DateRangeInput ) ; visibleRangeInput ? : DateRangeInput | ( ( this : CalendarApi , nowDate : Date ) => DateRangeInput ) ; monthMode ? : boolean ; fixedWeekCount ? : boolean ; } export type DateProfileGeneratorClass = { new ( props ) : DateProfileGenerator ; } ; export class DateProfileGenerator { nowDate : DateMarker ; isHiddenDayHash : boolean [ ] ; constructor ( protected props ) { this . nowDate = getNow ( props . nowInput , props . dateEnv ) ; this . initHiddenDays ( ) ; } buildPrev ( currentDateProfile , currentDate , forceToValid ? ) { let { dateEnv } = this . props ; let prevDate = dateEnv . subtract ( dateEnv . startOf ( currentDate , currentDateProfile . currentRangeUnit ) , currentDateProfile . dateIncrement ) ; return this . build ( prevDate , - 1 , forceToValid ) ; } buildNext ( currentDateProfile , currentDate , forceToValid ? ) { let { dateEnv } = this . props ; let nextDate = dateEnv . add ( dateEnv . startOf ( currentDate , currentDateProfile . currentRangeUnit ) , currentDateProfile . dateIncrement ) ; return this . build ( nextDate , 1 , forceToValid ) ; } build ( currentDate , direction ? , forceToValid = true ) { let { props } = this ; let validRange ; let currentInfo ; let isRangeAllDay ; let renderRange ; let activeRange ; let isValid ; validRange = this . buildValidRange ( ) ; validRange = this . trimHiddenDays ( validRange ) ; if ( forceToValid ) { currentDate = constrainMarkerToRange ( currentDate , validRange ) ; } currentInfo = this . buildCurrentRangeInfo ( currentDate , direction ) ; isRangeAllDay = /^(year|month|week|day)$/ . test ( currentInfo . unit ) ; renderRange = this . buildRenderRange ( this . trimHiddenDays ( currentInfo . range ) , currentInfo . unit , isRangeAllDay ) ; renderRange = this . trimHiddenDays ( renderRange ) ; activeRange = renderRange ; if ( ! props . showNonCurrentDates ) { activeRange = intersectRanges ( activeRange , currentInfo . range ) ; } activeRange = this . adjustActiveRange ( activeRange ) ; activeRange = intersectRanges ( activeRange , validRange ) ; isValid = rangesIntersect ( currentInfo . range , validRange ) ; return { validRange , currentRange : currentInfo . range , currentRangeUnit : currentInfo . unit , isRangeAllDay , activeRange , renderRange , slotMinTime : props . slotMinTime , slotMaxTime : props . slotMaxTime , isValid , dateIncrement : this . buildDateIncrement ( currentInfo . duration ) , } ; } buildValidRange ( ) { let input = this . props . validRangeInput ; let simpleInput = typeof input === 'function' ? input . call ( this . props . calendarApi , this . nowDate ) : input ; return this . refineRange ( simpleInput ) || { start : null , end : null } ; } buildCurrentRangeInfo ( date , direction ) { let { props } = this ; let duration = null ; let unit = null ; let range = null ; let dayCount ; if ( props . duration ) { duration = props . duration ; unit = props . durationUnit ; range = this . buildRangeFromDuration ( date , direction , duration , unit ) ; } else if ( ( dayCount = this . props . dayCount ) ) { unit = 'day' ; range = this . buildRangeFromDayCount ( date , direction , dayCount ) ; } else if ( ( range = this . buildCustomVisibleRange ( date ) ) ) { unit = props . dateEnv . greatestWholeUnit ( range . start , range . end ) . unit ; } else { duration = this . getFallbackDuration ( ) ; unit = greatestDurationDenominator ( duration ) . unit ; range = this . buildRangeFromDuration ( date , direction , duration , unit ) ; } return { duration , unit , range } ; } getFallbackDuration ( ) { return createDuration ( { day : 1 } ) ; } adjustActiveRange ( range ) { let { dateEnv , usesMinMaxTime , slotMinTime , slotMaxTime } = this . props ; let { start , end } = range ; if ( usesMinMaxTime ) { if ( asRoughDays ( slotMinTime ) < 0 ) { start = startOfDay ( start ) ; start = dateEnv . add ( start , slotMinTime ) ; } if ( asRoughDays ( slotMaxTime ) > 1 ) { end = startOfDay ( end ) ; end = addDays ( end , - 1 ) ; end = dateEnv . add ( end , slotMaxTime ) ; } } return { start , end } ; } buildRangeFromDuration ( date , direction , duration , unit ) { let { dateEnv , dateAlignment } = this . props ; let start ; let end ; let res ; if ( ! dateAlignment ) { let { dateIncrement } = this . props ; if ( dateIncrement ) { if ( asRoughMs ( dateIncrement ) < asRoughMs ( duration ) ) { dateAlignment = greatestDurationDenominator ( dateIncrement ) . unit ; } else { dateAlignment = unit ; } } else { dateAlignment = unit ; } } if ( asRoughDays ( duration ) <= 1 ) { if ( this . isHiddenDay ( start ) ) { start = this . skipHiddenDays ( start , direction ) ; start = startOfDay ( start ) ; } } function computeRes ( ) { start = dateEnv . startOf ( date , dateAlignment ) ; end = dateEnv . add ( start , duration ) ; res = { start , end } ; } computeRes ( ) ; if ( ! this . trimHiddenDays ( res ) ) { date = this . skipHiddenDays ( date , direction ) ; computeRes ( ) ; } return res ; } buildRangeFromDayCount ( date , direction , dayCount ) { let { dateEnv , dateAlignment } = this . props ; let runningCount = 0 ; let start = date ; let end ; if ( dateAlignment ) { start = dateEnv . startOf ( start , dateAlignment ) ; } start = startOfDay ( start ) ; start = this . skipHiddenDays ( start , direction ) ; end = start ; do { end = addDays ( end , 1 ) ; if ( ! this . isHiddenDay ( end ) ) { runningCount += 1 ; } } while ( runningCount < dayCount ) ; return { start , end } ; } buildCustomVisibleRange ( date ) { let { props } = this ; let input = props . visibleRangeInput ; let simpleInput = typeof input === 'function' ? input . call ( props . calendarApi , props . dateEnv . toDate ( date ) ) : input ; let range = this . refineRange ( simpleInput ) ; if ( range && ( range . start == null || range . end == null ) ) { return null ; } return range ; } buildRenderRange ( currentRange , currentRangeUnit , isRangeAllDay ) { return currentRange ; } buildDateIncrement ( fallback ) { let { dateIncrement } = this . props ; let customAlignment ; if ( dateIncrement ) { return dateIncrement ; } if ( ( customAlignment = this . props . dateAlignment ) ) { return createDuration ( 1 , customAlignment ) ; } if ( fallback ) { return fallback ; } return createDuration ( { days : 1 } ) ; } refineRange ( rangeInput ) { if ( rangeInput ) { let range = parseRange ( rangeInput , this . props . dateEnv ) ; if ( range ) { range = computeVisibleDayRange ( range ) ; } return range ; } return null ; } initHiddenDays ( ) { let hiddenDays = this . props . hiddenDays || [ ] ; let isHiddenDayHash = [ ] ; let dayCnt = 0 ; let i ; if ( this . props . weekends === false ) { hiddenDays . push ( 0 , 6 ) ; } for ( i = 0 ; i < 7 ; i += 1 ) { if ( ! ( isHiddenDayHash [ i ] = hiddenDays . indexOf ( i ) !== - 1 ) ) { dayCnt += 1 ; } } if ( ! dayCnt ) { throw new Error ( 'invalid hiddenDays' ) ; } this . isHiddenDayHash = isHiddenDayHash ; } trimHiddenDays ( range ) { let { start , end } = range ; if ( start ) { start = this . skipHiddenDays ( start ) ; } if ( end ) { end = this . skipHiddenDays ( end , - 1 , true ) ; } if ( start == null || end == null || start < end ) { return { start , end } ; } return null ; } isHiddenDay ( day ) { if ( day instanceof Date ) { day = day . getUTCDay ( ) ; } return this . isHiddenDayHash [ day ] ; } skipHiddenDays ( date , inc = 1 , isExclusive = false ) { while ( this . isHiddenDayHash [ ( date . getUTCDay ( ) + ( isExclusive ? inc : 0 ) + 7 ) % 7 ] ) { date = addDays ( date , inc ) ; } return date ; } }