2
0
Эх сурвалжийг харах

first pass at moving UnzonedRange to DateRange

Adam Shaw 7 жил өмнө
parent
commit
f6e07ee186

+ 2 - 27
src/Calendar.ts

@@ -9,7 +9,6 @@ import OptionsManager from './OptionsManager'
 import ViewSpecManager from './ViewSpecManager'
 import View from './View'
 import Theme from './theme/Theme'
-import UnzonedRange from './models/UnzonedRange'
 import { getThemeSystemClass } from './theme/ThemeRegistry'
 import { RangeInput, OptionsInput, EventObjectInput, EventSourceInput } from './types/input-types'
 import { getLocale } from './datelib/locale'
@@ -23,6 +22,7 @@ import reselector from './util/reselector'
 import { assignTo } from './util/object'
 import { RenderForceFlags } from './component/Component'
 import browserContext from './common/browser-context'
+import { rangeContainsMarker } from './datelib/date-range'
 
 
 export default class Calendar {
@@ -845,7 +845,7 @@ export default class Calendar {
     let props = {
       title: view.title,
       activeButton: view.type,
-      isTodayEnabled: todayInfo.isValid && !dateProfile.currentUnzonedRange.containsDate(now),
+      isTodayEnabled: todayInfo.isValid && !rangeContainsMarker(dateProfile.currentUnzonedRange, now),
       isPrevEnabled: prevInfo.isValid,
       isNextEnabled: nextInfo.isValid
     }
@@ -970,31 +970,6 @@ export default class Calendar {
   }
 
 
-  // will return `null` if invalid range
-  parseUnzonedRange(rangeInput: RangeInput): UnzonedRange {
-    let start = null
-    let end = null
-
-    if (rangeInput.start) {
-      start = this.dateEnv.createMarker(rangeInput.start)
-    }
-
-    if (rangeInput.end) {
-      end = this.dateEnv.createMarker(rangeInput.end)
-    }
-
-    if (!start && !end) {
-      return null
-    }
-
-    if (start && end && end < start) {
-      return null
-    }
-
-    return new UnzonedRange(start, end)
-  }
-
-
   // Event-Date Utilities
   // -----------------------------------------------------------------------------------------------------------------
 

+ 26 - 26
src/DateProfileGenerator.ts

@@ -1,16 +1,16 @@
 import View from './View'
-import UnzonedRange from './models/UnzonedRange'
 import { DateMarker, startOfDay, addDays } from './datelib/marker'
 import { Duration, createDuration, getWeeksFromInput, asRoughDays, asRoughMs, greatestDurationDenominator } from './datelib/duration'
+import { DateRange, OpenDateRange, constrainMarkerToRange, intersectRanges, rangesIntersect } from './datelib/date-range'
 
 
 export interface DateProfile {
-  validUnzonedRange: UnzonedRange
-  currentUnzonedRange: UnzonedRange
+  validUnzonedRange: DateRange
+  currentUnzonedRange: DateRange
   currentRangeUnit: string
   isRangeAllDay: boolean
-  activeUnzonedRange: UnzonedRange
-  renderUnzonedRange: UnzonedRange
+  activeUnzonedRange: DateRange
+  renderUnzonedRange: DateRange
   minTime: Duration
   maxTime: Duration
   isValid: boolean
@@ -73,20 +73,20 @@ export default class DateProfileGenerator {
   // Optional direction param indicates whether the date is being incremented/decremented
   // from its previous value. decremented = -1, incremented = 1 (default).
   build(date: DateMarker, direction?, forceToValid = false): DateProfile {
-    let validUnzonedRange
+    let validUnzonedRange: DateRange
     let minTime = null
     let maxTime = null
     let currentInfo
     let isRangeAllDay
-    let renderUnzonedRange
-    let activeUnzonedRange
+    let renderUnzonedRange: DateRange
+    let activeUnzonedRange: DateRange
     let isValid
 
     validUnzonedRange = this.buildValidRange()
     validUnzonedRange = this.trimHiddenDays(validUnzonedRange)
 
     if (forceToValid) {
-      date = validUnzonedRange.constrainDate(date)
+      date = constrainMarkerToRange(date, validUnzonedRange)
     }
 
     currentInfo = this.buildCurrentRangeInfo(date, direction)
@@ -97,24 +97,24 @@ export default class DateProfileGenerator {
       isRangeAllDay
     )
     renderUnzonedRange = this.trimHiddenDays(renderUnzonedRange)
-    activeUnzonedRange = renderUnzonedRange.clone()
+    activeUnzonedRange = renderUnzonedRange
 
     if (!this.opt('showNonCurrentDates')) {
-      activeUnzonedRange = activeUnzonedRange.intersect(currentInfo.unzonedRange)
+      activeUnzonedRange = intersectRanges(activeUnzonedRange, currentInfo.unzonedRange)
     }
 
     minTime = createDuration(this.opt('minTime'))
     maxTime = createDuration(this.opt('maxTime'))
     activeUnzonedRange = this.adjustActiveRange(activeUnzonedRange, minTime, maxTime)
-    activeUnzonedRange = activeUnzonedRange.intersect(validUnzonedRange) // might return null
+    activeUnzonedRange = intersectRanges(activeUnzonedRange, validUnzonedRange) // might return null
 
     if (activeUnzonedRange) {
-      date = activeUnzonedRange.constrainDate(date)
+      date = constrainMarkerToRange(date, activeUnzonedRange)
     }
 
     // it's invalid if the originally requested date is not contained,
     // or if the range is completely outside of the valid range.
-    isValid = currentInfo.unzonedRange.intersectsWith(validUnzonedRange)
+    isValid = rangesIntersect(currentInfo.unzonedRange, validUnzonedRange)
 
     return {
       // constraint for where prev/next operations can go and where events can be dragged/resized to.
@@ -158,9 +158,9 @@ export default class DateProfileGenerator {
   // Builds an object with optional start/end properties.
   // Indicates the minimum/maximum dates to display.
   // not responsible for trimming hidden days.
-  buildValidRange() {
-    return this._view.getUnzonedRangeOption('validRange', this._view.calendar.getNow()) ||
-      new UnzonedRange() // completely open-ended
+  buildValidRange(): OpenDateRange {
+    return this._view.getRangeOption('validRange', this._view.calendar.getNow()) ||
+      { start: null, end: null } // completely open-ended
   }
 
 
@@ -202,10 +202,10 @@ export default class DateProfileGenerator {
 
   // Returns a new activeUnzonedRange to have time values (un-ambiguate)
   // minTime or maxTime causes the range to expand.
-  adjustActiveRange(unzonedRange: UnzonedRange, minTime: Duration, maxTime: Duration) {
+  adjustActiveRange(range: DateRange, minTime: Duration, maxTime: Duration) {
     const dateEnv = this._view.calendar.dateEnv
-    let start = unzonedRange.start
-    let end = unzonedRange.end
+    let start = range.start
+    let end = range.end
 
     if (this._view.usesMinMaxTime) {
 
@@ -223,7 +223,7 @@ export default class DateProfileGenerator {
       }
     }
 
-    return new UnzonedRange(start, end)
+    return { start, end }
   }
 
 
@@ -270,7 +270,7 @@ export default class DateProfileGenerator {
     function computeRes() {
       start = dateEnv.startOf(date, alignment)
       end = dateEnv.add(start, duration)
-      res = new UnzonedRange(start, end)
+      res = { start, end }
     }
 
     computeRes()
@@ -308,7 +308,7 @@ export default class DateProfileGenerator {
       }
     } while (runningCount < dayCount)
 
-    return new UnzonedRange(start, end)
+    return { start, end }
   }
 
 
@@ -316,7 +316,7 @@ export default class DateProfileGenerator {
   // which is a way to define the currentUnzonedRange and activeUnzonedRange at the same time.
   buildCustomVisibleRange(date: DateMarker) {
     const dateEnv = this._view.calendar.dateEnv
-    let visibleUnzonedRange = this._view.getUnzonedRangeOption('visibleRange', dateEnv.toDate(date))
+    let visibleUnzonedRange = this._view.getRangeOption('visibleRange', dateEnv.toDate(date))
 
     if (visibleUnzonedRange && (visibleUnzonedRange.start == null || visibleUnzonedRange.end == null)) {
       return null
@@ -329,8 +329,8 @@ export default class DateProfileGenerator {
   // Computes the range that will represent the element/cells for *rendering*,
   // but which may have voided days/times.
   // not responsible for trimming hidden days.
-  buildRenderRange(currentUnzonedRange: UnzonedRange, currentRangeUnit, isRangeAllDay) {
-    return currentUnzonedRange.clone()
+  buildRenderRange(currentUnzonedRange: DateRange, currentRangeUnit, isRangeAllDay) {
+    return currentUnzonedRange
   }
 
 

+ 6 - 6
src/View.ts

@@ -3,11 +3,11 @@ import { parseFieldSpecs } from './util/misc'
 import Calendar from './Calendar'
 import { default as DateProfileGenerator, DateProfile } from './DateProfileGenerator'
 import DateComponent from './component/DateComponent'
-import UnzonedRange from './models/UnzonedRange'
 import { DateMarker, addDays, addMs, diffWholeDays } from './datelib/marker'
 import { createDuration } from './datelib/duration'
 import { createFormatter } from './datelib/formatting'
 import { default as EmitterMixin, EmitterInterface } from './common/EmitterMixin'
+import { OpenDateRange, parseRange, DateRange, rangesEqual } from './datelib/date-range'
 
 
 /* An abstract class from which other views inherit from
@@ -156,7 +156,7 @@ export default abstract class View extends DateComponent {
 
     if ( // reuse current reference if possible, for rendering optimization
       this.dateProfile &&
-      this.dateProfile.activeUnzonedRange.equals(dateProfile.activeUnzonedRange)
+      rangesEqual(this.dateProfile.activeUnzonedRange, dateProfile.activeUnzonedRange)
     ) {
       return this.dateProfile
     }
@@ -382,7 +382,7 @@ export default abstract class View extends DateComponent {
   // Arguments after name will be forwarded to a hypothetical function value
   // WARNING: passed-in arguments will be given to generator functions as-is and can cause side-effects.
   // Always clone your objects if you fear mutation.
-  getUnzonedRangeOption(name, ...otherArgs) {
+  getRangeOption(name, ...otherArgs): OpenDateRange {
     let val = this.opt(name)
 
     if (typeof val === 'function') {
@@ -390,7 +390,7 @@ export default abstract class View extends DateComponent {
     }
 
     if (val) {
-      return this.calendar.parseUnzonedRange(val)
+      return parseRange(val, this.calendar.dateEnv)
     }
   }
 
@@ -428,7 +428,7 @@ export default abstract class View extends DateComponent {
 
   // Remove days from the beginning and end of the range that are computed as hidden.
   // If the whole range is trimmed off, returns null
-  trimHiddenDays(inputUnzonedRange) {
+  trimHiddenDays(inputUnzonedRange): DateRange | null {
     let start = inputUnzonedRange.start
     let end = inputUnzonedRange.end
 
@@ -441,7 +441,7 @@ export default abstract class View extends DateComponent {
     }
 
     if (start == null || end == null || start < end) {
-      return new UnzonedRange(start, end)
+      return { start, end }
     }
 
     return null

+ 13 - 12
src/agenda/TimeGrid.ts

@@ -2,7 +2,7 @@ import { htmlEscape } from '../util/html'
 import { htmlToElement, findElements, createElement, removeElement, applyStyle } from '../util/dom-manip'
 import { default as DayTableMixin, DayTableInterface } from '../component/DayTableMixin'
 import CoordCache from '../common/CoordCache'
-import UnzonedRange from '../models/UnzonedRange'
+import { DateRange, intersectRanges } from '../datelib/date-range'
 import TimeGridEventRenderer from './TimeGridEventRenderer'
 import TimeGridHelperRenderer from './TimeGridHelperRenderer'
 import TimeGridFillRenderer from './TimeGridFillRenderer'
@@ -46,7 +46,7 @@ export default class TimeGrid extends DateComponent {
   view: any // TODO: make more general and/or remove
   helperRenderer: any
 
-  dayRanges: any // UnzonedRange[], of start-end of each day
+  dayRanges: DateRange[] // of start-end of each day
   slotDuration: Duration // duration of a "slot", a distinct time segment on given day, visualized by lines
   snapDuration: Duration // granularity of time for dragging and selecting
   snapsPerSlot: any
@@ -82,7 +82,7 @@ export default class TimeGrid extends DateComponent {
 
 
   // Slices up the given span (unzoned start/end with other misc data) into an array of segments
-  rangeToSegs(range: UnzonedRange): Seg[] {
+  rangeToSegs(range: DateRange): Seg[] {
     let segs = this.sliceRangeByTimes(range)
     let i
 
@@ -111,7 +111,7 @@ export default class TimeGrid extends DateComponent {
 
     for (dayIndex = 0; dayIndex < this.daysPerRow; dayIndex++) {
 
-      segRange = unzonedRange.intersect(this.dayRanges[dayIndex])
+      segRange = intersectRanges(unzonedRange, this.dayRanges[dayIndex])
 
       if (segRange) {
         segs.push({
@@ -295,10 +295,10 @@ export default class TimeGrid extends DateComponent {
     let dateEnv = this.getDateEnv()
 
     this.dayRanges = this.dayDates.map(function(dayDate) {
-      return new UnzonedRange(
-        dateEnv.add(dayDate, dateProfile.minTime),
-        dateEnv.add(dayDate, dateProfile.maxTime)
-      )
+      return {
+        start: dateEnv.add(dayDate, dateProfile.minTime),
+        end: dateEnv.add(dayDate, dateProfile.maxTime)
+      }
     })
 
     if (this.headContainerEl) {
@@ -436,9 +436,10 @@ export default class TimeGrid extends DateComponent {
 
     // seg system might be overkill, but it handles scenario where line needs to be rendered
     //  more than once because of columns with the same date (resources columns for example)
-    let segs = this.rangeToSegs(
-      new UnzonedRange(date, addMs(date, 1)), // protect against null range
-    )
+    let segs = this.rangeToSegs({
+      start: date,
+      end: addMs(date, 1) // protect against null range
+    })
     let top = this.computeDateTop(date)
     let nodes = []
     let i
@@ -593,7 +594,7 @@ export default class TimeGrid extends DateComponent {
         return {
           component: this,
           dateSpan: {
-            range: new UnzonedRange(start, end),
+            range: { start, end },
             isAllDay: false
           },
           dayEl: this.colEls[colIndex],

+ 4 - 4
src/basic/BasicViewDateProfileGenerator.ts

@@ -1,14 +1,14 @@
-import UnzonedRange from '../models/UnzonedRange'
 import DateProfileGenerator from '../DateProfileGenerator'
 import { addWeeks } from '../datelib/marker'
+import { DateRange } from '../datelib/date-range'
 
 
 export default class BasicViewDateProfileGenerator extends DateProfileGenerator {
 
   // Computes the date range that will be rendered.
-  buildRenderRange(currentUnzonedRange, currentRangeUnit, isRangeAllDay) {
+  buildRenderRange(currentUnzonedRange, currentRangeUnit, isRangeAllDay): DateRange {
     let dateEnv = this._view.calendar.dateEnv
-    let renderUnzonedRange = super.buildRenderRange(currentUnzonedRange, currentRangeUnit, isRangeAllDay) // an UnzonedRange
+    let renderUnzonedRange = super.buildRenderRange(currentUnzonedRange, currentRangeUnit, isRangeAllDay)
     let start = renderUnzonedRange.start
     let end = renderUnzonedRange.end
     let endOfWeek
@@ -24,7 +24,7 @@ export default class BasicViewDateProfileGenerator extends DateProfileGenerator
       }
     }
 
-    return new UnzonedRange(start, end)
+    return { start, end }
   }
 
 }

+ 5 - 5
src/basic/DayGrid.ts

@@ -10,7 +10,6 @@ import { computeRect } from '../util/dom-geom'
 import View from '../View'
 import CoordCache from '../common/CoordCache'
 import Popover from '../common/Popover'
-import UnzonedRange from '../models/UnzonedRange'
 import { default as DayTableMixin, DayTableInterface } from '../component/DayTableMixin'
 import DayGridEventRenderer from './DayGridEventRenderer'
 import DayGridHelperRenderer from './DayGridHelperRenderer'
@@ -21,6 +20,7 @@ import DateComponent, { Seg } from '../component/DateComponent'
 import { EventStore } from '../structs/event-store'
 import DayTile from './DayTile'
 import { Hit } from '../interactions/HitDragging'
+import { DateRange, rangeContainsMarker, intersectRanges } from '../datelib/date-range'
 
 const DAY_NUM_FORMAT = createFormatter({ day: 'numeric' })
 const WEEK_NUM_FORMAT = createFormatter({ week: 'numeric' })
@@ -79,7 +79,7 @@ export default class DayGrid extends DateComponent {
 
 
   // Slices up the given span (unzoned start/end with other misc data) into an array of segments
-  rangeToSegs(range: UnzonedRange): Seg[] {
+  rangeToSegs(range: DateRange): Seg[] {
     let segs = this.sliceRangeByRow(range)
 
     for (let i = 0; i < segs.length; i++) {
@@ -243,7 +243,7 @@ export default class DayGrid extends DateComponent {
     let view = this.view
     let dateEnv = this.getDateEnv()
     let html = ''
-    let isDateValid = this.dateProfile.activeUnzonedRange.containsDate(date) // TODO: called too frequently. cache somehow.
+    let isDateValid = rangeContainsMarker(this.dateProfile.activeUnzonedRange, date) // TODO: called too frequently. cache somehow.
     let isDayNumberVisible = this.getIsDayNumbersVisible() && isDateValid
     let classes
     let weekCalcFirstDow
@@ -656,13 +656,13 @@ export default class DayGrid extends DateComponent {
   resliceDaySegs(segs, dayDate) {
     let dayStart = dayDate
     let dayEnd = addDays(dayStart, 1)
-    let dayRange = new UnzonedRange(dayStart, dayEnd)
+    let dayRange = { start: dayStart, end: dayEnd }
     let newSegs = []
 
     for (let seg of segs) {
       let eventRange = seg.eventRange
       let origRange = eventRange.range
-      let slicedRange = origRange.intersect(dayRange)
+      let slicedRange = intersectRanges(origRange, dayRange)
 
       if (slicedRange) {
         newSegs.push(

+ 1 - 2
src/basic/MonthViewDateProfileGenerator.ts

@@ -1,5 +1,4 @@
 import BasicViewDateProfileGenerator from './BasicViewDateProfileGenerator'
-import UnzonedRange from '../models/UnzonedRange'
 import { addWeeks, diffWeeks } from '../datelib/marker'
 
 
@@ -20,7 +19,7 @@ export default class MonthViewDateProfileGenerator extends BasicViewDateProfileG
       end = addWeeks(end, 6 - rowCnt)
     }
 
-    return new UnzonedRange(start, end)
+    return { start, end }
   }
 
 }

+ 7 - 8
src/component/DateComponent.ts

@@ -7,7 +7,6 @@ import { DateProfile } from '../DateProfileGenerator'
 import { DateMarker, DAY_IDS, addDays, startOfDay, diffDays, diffWholeDays } from '../datelib/marker'
 import { Duration, createDuration } from '../datelib/duration'
 import { DateSpan } from '../structs/date-span'
-import UnzonedRange from '../models/UnzonedRange'
 import { EventRenderRange, sliceEventStore } from '../component/event-rendering'
 import { EventStore } from '../structs/event-store'
 import { BusinessHoursDef, buildBusinessHours } from '../structs/business-hours'
@@ -18,6 +17,7 @@ import { assignTo } from '../util/object'
 import browserContext from '../common/browser-context'
 import { Hit } from '../interactions/HitDragging'
 import { computeVisibleDayRange } from '../util/misc'
+import { DateRange, rangeContainsMarker } from '../datelib/date-range'
 
 
 export interface DateComponentRenderState {
@@ -830,7 +830,7 @@ export default abstract class DateComponent extends Component {
 
 
   // must implement if want to use many of the rendering utils
-  rangeToSegs(range: UnzonedRange, isAllDay: boolean): Seg[] {
+  rangeToSegs(range: DateRange, isAllDay: boolean): Seg[] {
     return []
   }
 
@@ -931,7 +931,7 @@ export default abstract class DateComponent extends Component {
     let todayStart: DateMarker
     let todayEnd: DateMarker
 
-    if (!this.dateProfile.activeUnzonedRange.containsDate(date)) {
+    if (!rangeContainsMarker(this.dateProfile.activeUnzonedRange, date)) {
       classes.push('fc-disabled-day') // TODO: jQuery UI theme?
     } else {
       classes.push('fc-' + DAY_IDS[date.getUTCDay()])
@@ -983,15 +983,14 @@ export default abstract class DateComponent extends Component {
 
 
   // Returns the date range of the full days the given range visually appears to occupy.
-  // Returns a plain object with start/end, NOT an UnzonedRange!
-  computeDayRange(unzonedRange): UnzonedRange {
-    return computeVisibleDayRange(unzonedRange, this.nextDayThreshold)
+  computeDayRange(range): DateRange {
+    return computeVisibleDayRange(range, this.nextDayThreshold)
   }
 
 
   // Does the given range visually appear to occupy more than one day?
-  isMultiDayRange(unzonedRange) {
-    let dayRange = this.computeDayRange(unzonedRange)
+  isMultiDayRange(range) {
+    let dayRange = this.computeDayRange(range)
 
     return diffDays(dayRange.start, dayRange.end) > 1
   }

+ 6 - 6
src/component/DayTableMixin.ts

@@ -3,7 +3,7 @@ import { prependToElement, appendToElement } from '../util/dom-manip'
 import Mixin from '../common/Mixin'
 import { DateMarker, DAY_IDS, addDays, diffDays } from '../datelib/marker'
 import { createFormatter } from '../datelib/formatting'
-import UnzonedRange from '../models/UnzonedRange'
+import { DateRange, rangeContainsMarker } from '../datelib/date-range'
 
 export interface DayTableInterface {
   dayDates: DateMarker[]
@@ -16,7 +16,7 @@ export interface DayTableInterface {
   renderBgTrHtml(row)
   bookendCells(trEl: HTMLElement)
   getCellDate(row, col)
-  getCellRange(row, col): UnzonedRange
+  getCellRange(row, col): DateRange
   sliceRangeByDay(unzonedRange)
   sliceRangeByRow(unzonedRange)
   renderIntroHtml()
@@ -109,11 +109,11 @@ export default class DayTableMixin extends Mixin implements DayTableInterface {
 
 
   // Computes the ambiguously-timed date range for the given cell
-  getCellRange(row, col) {
+  getCellRange(row, col): DateRange {
     let start = this.getCellDate(row, col)
     let end = addDays(start, 1)
 
-    return new UnzonedRange(start, end)
+    return { start, end }
   }
 
 
@@ -321,7 +321,7 @@ export default class DayTableMixin extends Mixin implements DayTableInterface {
     let view = t.view
     let dateEnv = t.getDateEnv()
     let dateProfile = t.dateProfile
-    let isDateValid = dateProfile.activeUnzonedRange.containsDate(date) // TODO: called too frequently. cache somehow.
+    let isDateValid =  rangeContainsMarker(dateProfile.activeUnzonedRange, date) // TODO: called too frequently. cache somehow.
     let classNames = [
       'fc-day-header',
       view.calendar.theme.getClass('widgetHeader')
@@ -412,7 +412,7 @@ export default class DayTableMixin extends Mixin implements DayTableInterface {
     let view = t.view
     let dateEnv = t.getDateEnv()
     let dateProfile = t.dateProfile
-    let isDateValid = dateProfile.activeUnzonedRange.containsDate(date) // TODO: called too frequently. cache somehow.
+    let isDateValid = rangeContainsMarker(dateProfile.activeUnzonedRange, date) // TODO: called too frequently. cache somehow.
     let classes = t.getDayClasses(date)
 
     classes.unshift('fc-day', view.calendar.theme.getClass('widgetContent'))

+ 8 - 8
src/component/event-rendering.ts

@@ -1,21 +1,21 @@
-import UnzonedRange from '../models/UnzonedRange'
 import { EventDef, EventInstance } from '../structs/event'
 import { EventStore } from '../structs/event-store'
+import { DateRange, invertRanges } from '../datelib/date-range'
 
 export interface EventRenderRange {
   eventDef: EventDef
   eventInstance?: EventInstance
-  range: UnzonedRange
+  range: DateRange
 }
 
 
 /*
-Does not slice ranges via windowRange into new ranges, but instead,
+Does not slice ranges via framingRange into new ranges, but instead,
 keeps fg event ranges intact but more importantly slices inverse-BG events.
 */
-export function sliceEventStore(eventStore: EventStore, windowRange: UnzonedRange) {
-  let inverseBgByGroupId: { [groupId: string]: UnzonedRange[] } = {}
-  let inverseBgByDefId: { [defId: string]: UnzonedRange[] } = {}
+export function sliceEventStore(eventStore: EventStore, framingRange: DateRange) {
+  let inverseBgByGroupId: { [groupId: string]: DateRange[] } = {}
+  let inverseBgByDefId: { [defId: string]: DateRange[] } = {}
   let defByGroupId: { [groupId: string]: EventDef } = {}
   let renderRanges: EventRenderRange[] = []
 
@@ -56,7 +56,7 @@ export function sliceEventStore(eventStore: EventStore, windowRange: UnzonedRang
 
   for (let groupId in inverseBgByGroupId) {
     let ranges = inverseBgByGroupId[groupId]
-    let invertedRanges = UnzonedRange.invertRanges(ranges, windowRange)
+    let invertedRanges = invertRanges(ranges, framingRange)
 
     for (let invertedRange of invertedRanges) {
       let def = defByGroupId[groupId]
@@ -70,7 +70,7 @@ export function sliceEventStore(eventStore: EventStore, windowRange: UnzonedRang
 
   for (let defId in inverseBgByDefId) {
     let ranges = inverseBgByDefId[defId]
-    let invertedRanges = UnzonedRange.invertRanges(ranges, windowRange)
+    let invertedRanges = invertRanges(ranges, framingRange)
 
     for (let invertedRange of invertedRanges) {
       renderRanges.push({

+ 2 - 2
src/event-sources/json-feed-event-source.ts

@@ -1,8 +1,8 @@
-import UnzonedRange from '../models/UnzonedRange'
 import * as request from 'superagent'
 import { assignTo } from '../util/object'
 import Calendar from '../Calendar'
 import { registerEventSourceDef } from '../structs/event-source'
+import { DateRange } from '../datelib/date-range'
 
 interface JsonFeedMeta {
   url: string
@@ -65,7 +65,7 @@ registerEventSourceDef({
 
 })
 
-function buildRequestParams(meta: JsonFeedMeta, range: UnzonedRange, calendar: Calendar) {
+function buildRequestParams(meta: JsonFeedMeta, range: DateRange, calendar: Calendar) {
   const dateEnv = calendar.dateEnv
   let startParam
   let endParam

+ 1 - 1
src/exports.ts

@@ -72,7 +72,7 @@ export {
 } from './util/dom-geom'
 
 export { default as EmitterMixin, EmitterInterface } from './common/EmitterMixin'
-export { default as UnzonedRange } from './models/UnzonedRange'
+export { DateRange, rangeContainsMarker, intersectRanges } from './datelib/date-range'
 export { defineThemeSystem } from './theme/ThemeRegistry'
 export { default as Mixin } from './common/Mixin'
 export { default as CoordCache } from './common/CoordCache'

+ 1 - 2
src/interactions-external/ExternalElementDragging.ts

@@ -4,7 +4,6 @@ import browserContext from '../common/browser-context'
 import { PointerDragEvent } from '../dnd/PointerDragging'
 import { parseEventDef, createEventInstance, EventDef, EventInstance } from '../structs/event'
 import { EventStore, createEmptyEventStore } from '../structs/event-store'
-import UnzonedRange from '../models/UnzonedRange'
 import * as externalHooks from '../exports'
 import { DateSpan } from '../structs/date-span'
 import Calendar from '../Calendar'
@@ -168,7 +167,7 @@ function computeEventForDateSpan(dateSpan: DateSpan, dragMeta: DragMeta, calenda
     calendar.dateEnv.add(start, dragMeta.duration) :
     calendar.getDefaultEventEnd(dateSpan.isAllDay, start)
 
-  let instance = createEventInstance(def.defId, new UnzonedRange(start, end))
+  let instance = createEventInstance(def.defId, { start, end })
 
   return { def, instance }
 }

+ 1 - 2
src/interactions/DateSelecting.ts

@@ -3,7 +3,6 @@ import { elementClosest } from '../util/dom-manip'
 import DateComponent from '../component/DateComponent'
 import HitDragging, { Hit } from './HitDragging'
 import { DateSpan } from '../structs/date-span'
-import UnzonedRange from '../models/UnzonedRange'
 import { PointerDragEvent } from '../dnd/PointerDragging'
 import FeaturefulElementDragging from '../dnd/FeaturefulElementDragging'
 import browserContext from '../common/browser-context'
@@ -118,7 +117,7 @@ function computeSelection(dateSpan0: DateSpan, dateSpan1: DateSpan): DateSpan {
   ms.sort(compareNumbers)
 
   return {
-    range: new UnzonedRange(ms[0], ms[3]),
+    range: { start: ms[0], end: ms[3] },
     isAllDay: dateSpan0.isAllDay
   }
 }

+ 2 - 2
src/interactions/EventResizing.ts

@@ -2,13 +2,13 @@ import { default as DateComponent, Seg } from '../component/DateComponent'
 import HitDragging, { isHitsEqual, Hit } from './HitDragging'
 import { EventMutation, applyMutationToEventStore } from '../structs/event-mutation'
 import { elementClosest } from '../util/dom-manip'
-import UnzonedRange from '../models/UnzonedRange'
 import FeaturefulElementDragging from '../dnd/FeaturefulElementDragging'
 import { PointerDragEvent } from '../dnd/PointerDragging'
 import { getElSeg } from '../component/renderers/EventRenderer'
 import { EventInstance } from '../structs/event'
 import { EventStore, getRelatedEvents } from '../structs/event-store'
 import { diffDates } from '../util/misc'
+import { DateRange } from '../datelib/date-range'
 
 export default class EventDragging {
 
@@ -129,7 +129,7 @@ export default class EventDragging {
 
 }
 
-function computeMutation(hit0: Hit, hit1: Hit, isFromStart: boolean, instanceRange: UnzonedRange): EventMutation | null {
+function computeMutation(hit0: Hit, hit1: Hit, isFromStart: boolean, instanceRange: DateRange): EventMutation | null {
   let dateEnv = hit0.component.getDateEnv()
   let date0 = hit0.dateSpan.range.start
   let date1 = hit1.dateSpan.range.start

+ 10 - 10
src/list/ListView.ts

@@ -1,12 +1,12 @@
 import { htmlToElement, createElement } from '../util/dom-manip'
 import { htmlEscape } from '../util/html'
 import { subtractInnerElHeight } from '../util/misc'
-import UnzonedRange from '../models/UnzonedRange'
 import View from '../View'
 import Scroller from '../common/Scroller'
 import ListEventRenderer from './ListEventRenderer'
 import { DateMarker, addDays, startOfDay } from '../datelib/marker'
 import { createFormatter } from '../datelib/formatting'
+import { DateRange, intersectRanges } from '../datelib/date-range'
 
 /*
 Responsible for the scroller, and forwarding event-related actions into the "grid".
@@ -22,7 +22,7 @@ export default class ListView extends View {
   contentEl: HTMLElement
 
   dayDates: DateMarker[]
-  dayRanges: UnzonedRange[] // start/end of each day
+  dayRanges: DateRange[] // start/end of each day
 
 
   constructor(calendar, viewSpec) {
@@ -74,17 +74,17 @@ export default class ListView extends View {
     let dateProfile = this.dateProfile
     let dayStart = startOfDay(dateProfile.renderUnzonedRange.start)
     let viewEnd = dateProfile.renderUnzonedRange.end
-    let dayDates = []
-    let dayRanges = []
+    let dayDates: DateMarker[] = []
+    let dayRanges: DateRange[] = []
 
     while (dayStart < viewEnd) {
 
       dayDates.push(dayStart)
 
-      dayRanges.push(new UnzonedRange(
-        dayStart,
-        addDays(dayStart, 1)
-      ))
+      dayRanges.push({
+        start: dayStart,
+        end: addDays(dayStart, 1)
+      })
 
       dayStart = addDays(dayStart, 1)
     }
@@ -97,7 +97,7 @@ export default class ListView extends View {
 
 
   // slices by day
-  rangeToSegs(range: UnzonedRange, isAllDay: boolean) {
+  rangeToSegs(range: DateRange, isAllDay: boolean) {
     let dateEnv = this.getDateEnv()
     let dayRanges = this.dayRanges
     let dayIndex
@@ -106,7 +106,7 @@ export default class ListView extends View {
     let segs = []
 
     for (dayIndex = 0; dayIndex < dayRanges.length; dayIndex++) {
-      segRange = range.intersect(dayRanges[dayIndex])
+      segRange = intersectRanges(range, dayRanges[dayIndex])
 
       if (segRange) {
         seg = {

+ 0 - 137
src/models/UnzonedRange.ts

@@ -1,137 +0,0 @@
-import { DateMarker } from '../datelib/marker'
-
-export default class UnzonedRange {
-
-  start: DateMarker // if null, no start constraint
-  end: DateMarker // if null, no end constraint
-
-
-  constructor(start?: DateMarker, end?: DateMarker) {
-
-    if (start) {
-      this.start = start
-    }
-
-    if (end) {
-      this.end = end
-    }
-  }
-
-
-  /*
-  SIDEEFFECT: will mutate eventRanges.
-  Will return a new array result.
-  Only works for non-open-ended ranges.
-  */
-  static invertRanges(ranges: UnzonedRange[], constraintRange: UnzonedRange) {
-    let invertedRanges = []
-    let start = constraintRange.start // the end of the previous range. the start of the new range
-    let i
-    let dateRange
-
-    // ranges need to be in order. required for our date-walking algorithm
-    ranges.sort(compareUnzonedRanges)
-
-    for (i = 0; i < ranges.length; i++) {
-      dateRange = ranges[i]
-
-      // add the span of time before the event (if there is any)
-      if (dateRange.start > start) { // compare millisecond time (skip any ambig logic)
-        invertedRanges.push(
-          new UnzonedRange(start, dateRange.start)
-        )
-      }
-
-      if (dateRange.end > start) {
-        start = dateRange.end
-      }
-    }
-
-    // add the span of time after the last event (if there is any)
-    if (start < constraintRange.end) { // compare millisecond time (skip any ambig logic)
-      invertedRanges.push(
-        new UnzonedRange(start, constraintRange.end)
-      )
-    }
-
-    return invertedRanges
-  }
-
-
-  intersect(otherRange: UnzonedRange): UnzonedRange {
-    let start = this.start
-    let end = this.end
-    let newRange = null
-
-    if (otherRange.start != null) {
-      if (start == null) {
-        start = otherRange.start
-      } else {
-        start = new Date(Math.max(start.valueOf(), otherRange.start.valueOf()))
-      }
-    }
-
-    if (otherRange.end != null) {
-      if (end == null) {
-        end = otherRange.end
-      } else {
-        end = new Date(Math.min(end.valueOf(), otherRange.end.valueOf()))
-      }
-    }
-
-    if (start == null || end == null || start < end) {
-      newRange = new UnzonedRange(start, end)
-    }
-
-    return newRange
-  }
-
-
-  intersectsWith(otherRange: UnzonedRange) {
-    return (this.end == null || otherRange.start == null || this.end > otherRange.start) &&
-      (this.start == null || otherRange.end == null || this.start < otherRange.end)
-  }
-
-
-  // `date` can be a Date, or a millisecond time.
-  containsDate(date: Date) {
-    return (this.start == null || date >= this.start) &&
-      (this.end == null || date < this.end)
-  }
-
-
-  // If the given date is not within the given range, move it inside.
-  // (If it's past the end, make it one millisecond before the end).
-  constrainDate(date: Date): Date {
-
-    if (this.start != null && date < this.start) {
-      return this.start
-    }
-
-    if (this.end != null && date >= this.end) {
-      return new Date(this.end.valueOf() - 1)
-    }
-
-    return date
-  }
-
-
-  equals(otherRange) {
-    return (this.start == null ? null : this.start.valueOf()) === (otherRange.start == null ? null : otherRange.start.valueOf()) &&
-      (this.end == null ? null : this.end.valueOf()) === (otherRange.end == null ? null : otherRange.end.valueOf())
-  }
-
-
-  clone() {
-    return new UnzonedRange(this.start, this.end)
-  }
-
-}
-
-
-/*
-Only works for non-open-ended ranges.
-*/
-function compareUnzonedRanges(range1: UnzonedRange, range2: UnzonedRange) {
-  return range1.start.valueOf() - range2.start.valueOf() // earlier ranges go first
-}

+ 2 - 2
src/structs/business-hours.ts

@@ -1,8 +1,8 @@
 import Calendar from '../Calendar'
-import UnzonedRange from '../models/UnzonedRange'
 import { assignTo } from '../util/object'
 import { EventInput } from './event'
 import { EventStore, parseEventStore } from './event-store'
+import { DateRange } from '../datelib/date-range'
 
 /*
 Utils for converting raw business hour input into an EventStore,
@@ -22,7 +22,7 @@ const DEF_DEFAULTS = {
 export function buildBusinessHours(
   input: BusinessHoursDef,
   isAllDay: boolean,
-  framingRange: UnzonedRange,
+  framingRange: DateRange,
   calendar: Calendar
 ): EventStore {
   return parseEventStore(

+ 4 - 4
src/structs/date-span.ts

@@ -1,4 +1,4 @@
-import UnzonedRange from '../models/UnzonedRange'
+import { DateRange, rangesEqual } from '../datelib/date-range'
 import { DateInput, DateEnv } from '../datelib/env'
 import { refineProps } from '../util/misc'
 
@@ -15,7 +15,7 @@ export interface DateSpanInput {
 }
 
 export interface DateSpan {
-  range: UnzonedRange
+  range: DateRange
   isAllDay: boolean
   [otherProp: string]: any
 }
@@ -40,7 +40,7 @@ export function parseDateSpan(raw: DateSpanInput, dateEnv: DateEnv): DateSpan |
     }
 
     // use this leftover object as the selection object
-    leftovers.range = new UnzonedRange(startMeta.marker, endMeta.marker)
+    leftovers.range = { start: startMeta.marker, end: endMeta.marker }
     leftovers.isAllDay = isAllDay
 
     return leftovers
@@ -51,7 +51,7 @@ export function parseDateSpan(raw: DateSpanInput, dateEnv: DateEnv): DateSpan |
 
 export function isDateSpansEqual(span0: DateSpan, span1: DateSpan): boolean {
 
-  if (!span0.range.equals(span1.range)) {
+  if (!rangesEqual(span0.range, span1.range)) {
     return false
   }
 

+ 13 - 14
src/structs/event-mutation.ts

@@ -1,4 +1,3 @@
-import UnzonedRange from '../models/UnzonedRange'
 import { Duration } from '../datelib/duration'
 import { EventStore, createEmptyEventStore } from './event-store'
 import { EventDef, EventInstance } from './event'
@@ -58,29 +57,29 @@ function applyMutationToEventInstance(
   let dateEnv = calendar.dateEnv
   let forceAllDay = mutation.standardProps && mutation.standardProps.isAllDay === true
   let clearEnd = mutation.standardProps && mutation.standardProps.hasEnd === false
-  let copy = assignTo({}, eventInstance)
+  let copy = assignTo({}, eventInstance) as EventInstance
 
   if (forceAllDay) {
     copy.range = computeAlignedDayRange(copy.range)
   }
 
   if (mutation.startDelta) {
-    copy.range = new UnzonedRange(
-      dateEnv.add(copy.range.start, mutation.startDelta),
-      copy.range.end
-    )
+    copy.range = {
+      start: dateEnv.add(copy.range.start, mutation.startDelta),
+      end: copy.range.end
+    }
   }
 
   if (clearEnd) {
-    copy.range = new UnzonedRange(
-      copy.range.start,
-      calendar.getDefaultEventEnd(eventDef.isAllDay, copy.range.start)
-    )
+    copy.range = {
+      start: copy.range.start,
+      end: calendar.getDefaultEventEnd(eventDef.isAllDay, copy.range.start)
+    }
   } else if (mutation.endDelta) {
-    copy.range = new UnzonedRange(
-      copy.range.start,
-      dateEnv.add(copy.range.end, mutation.endDelta),
-    )
+    copy.range = {
+      start: copy.range.start,
+      end: dateEnv.add(copy.range.end, mutation.endDelta),
+    }
   }
 
   return copy

+ 3 - 3
src/structs/event-source.ts

@@ -1,8 +1,8 @@
-import UnzonedRange from '../models/UnzonedRange'
 import { ClassNameInput, parseClassName } from '../util/html'
 import { refineProps } from '../util/misc'
 import { EventInput } from './event'
 import Calendar from '../Calendar'
+import { DateRange } from '../datelib/date-range'
 
 /*
 Parsing and normalization of the EventSource data type, which defines how event data is fetched.
@@ -40,7 +40,7 @@ export interface EventSource {
   publicId: string
   isFetching: boolean
   latestFetchId: string
-  fetchRange: UnzonedRange | null
+  fetchRange: DateRange | null
   allDayDefault: boolean | null
   eventDataTransform: EventInputTransformer
   editable: boolean | null
@@ -63,7 +63,7 @@ export type EventSourceFetcher = (
   arg: {
     eventSource: EventSource
     calendar: Calendar
-    range: UnzonedRange
+    range: DateRange
   },
   success: EventSourceSuccessHandler,
   failure: EventSourceFailureHandler

+ 2 - 2
src/structs/event-store.ts

@@ -1,8 +1,8 @@
-import UnzonedRange from '../models/UnzonedRange'
 import { EventInput, EventDefHash, EventInstanceHash, parseEventDef, parseEventDateSpan, createEventInstance } from './event'
 import { expandRecurring } from './recurring-event'
 import Calendar from '../Calendar'
 import { assignTo } from '../util/object'
+import { DateRange } from '../datelib/date-range'
 
 /*
 A data structure that encapsulates EventDefs and EventInstances.
@@ -18,7 +18,7 @@ export interface EventStore {
 export function parseEventStore(
   rawEvents: EventInput[],
   sourceId: string,
-  fetchRange: UnzonedRange,
+  fetchRange: DateRange,
   calendar: Calendar,
   dest: EventStore = createEmptyEventStore() // specify this arg to append to an existing EventStore
 ): EventStore {

+ 5 - 5
src/structs/event.ts

@@ -1,9 +1,9 @@
 import { refineProps } from '../util/misc'
 import { parseClassName, ClassNameInput } from '../util/html'
 import { DateInput } from '../datelib/env'
-import UnzonedRange from '../models/UnzonedRange'
 import Calendar from '../Calendar'
 import { assignTo } from '../util/object'
+import { DateRange } from '../datelib/date-range'
 
 /*
 Utils for parsing event-input data. Each util parses a subset of the event-input's data.
@@ -66,7 +66,7 @@ export interface EventDef {
 export interface EventInstance {
   instanceId: string
   defId: string
-  range: UnzonedRange
+  range: DateRange
   forcedStartTzo: number | null
   forcedEndTzo: number | null
 }
@@ -76,7 +76,7 @@ export interface EventInstance {
 export interface EventDateSpan {
   isAllDay: boolean
   hasEnd: boolean
-  range: UnzonedRange
+  range: DateRange
   forcedStartTzo: number | null
   forcedEndTzo: number | null
 }
@@ -142,7 +142,7 @@ export function parseEventDef(raw: EventNonDateInput, sourceId: string, isAllDay
 
 export function createEventInstance(
   defId: string,
-  range: UnzonedRange,
+  range: DateRange,
   forcedStartTzo: number | null = null,
   forcedEndTzo: number | null = null
 ): EventInstance {
@@ -214,7 +214,7 @@ export function parseEventDateSpan(
   return {
     isAllDay,
     hasEnd,
-    range: new UnzonedRange(startMeta.marker, endMarker),
+    range: { start: startMeta.marker, end: endMarker },
     forcedStartTzo: startMeta.forcedTzo,
     forcedEndTzo: endMeta ? endMeta.forcedTzo : null
   }

+ 5 - 5
src/structs/recurring-event-simple.ts

@@ -3,9 +3,9 @@ import { Duration, createDuration } from '../datelib/duration'
 import { arrayToHash } from '../util/object'
 import { refineProps } from '../util/misc'
 import { registerRecurringExpander, RecurringEventDateSpans } from './recurring-event'
-import UnzonedRange from '../models/UnzonedRange'
 import Calendar from '../Calendar'
 import { EventInput } from './event'
+import { DateRange } from '../datelib/date-range'
 
 /*
 An implementation of recurring events that only supports every-day or weekly recurrences.
@@ -18,7 +18,7 @@ const SIMPLE_RECURRING_PROPS = {
 }
 
 registerRecurringExpander(
-  function(rawEvent: EventInput, framingRange: UnzonedRange, calendar: Calendar, leftoverProps: object): RecurringEventDateSpans | null {
+  function(rawEvent: EventInput, framingRange: DateRange, calendar: Calendar, leftoverProps: object): RecurringEventDateSpans | null {
     if (
       rawEvent.daysOfWeek ||
       rawEvent.startTime != null ||
@@ -47,9 +47,9 @@ function expandRanges(
   daysOfWeek: number[] | null,
   startTime: Duration | null,
   endTime: Duration | null,
-  framingRange: UnzonedRange,
+  framingRange: DateRange,
   calendar: Calendar
-): UnzonedRange[] {
+): DateRange[] {
   let dateEnv = calendar.dateEnv
   let dowHash: { [num: string]: true } | null = daysOfWeek ? arrayToHash(daysOfWeek) : null
   let dayMarker = startOfDay(framingRange.start)
@@ -80,7 +80,7 @@ function expandRanges(
         )
       }
 
-      instanceRanges.push(new UnzonedRange(instanceStart, instanceEnd))
+      instanceRanges.push({ start: instanceStart, end: instanceEnd })
     }
 
     dayMarker = addDays(dayMarker, 1)

+ 4 - 4
src/structs/recurring-event.ts

@@ -1,6 +1,6 @@
-import UnzonedRange from '../models/UnzonedRange'
 import Calendar from '../Calendar'
 import { EventInput } from './event'
+import { DateRange } from '../datelib/date-range'
 
 /*
 The plugin system for defining how a recurring event is expanded into individual instances.
@@ -9,12 +9,12 @@ The plugin system for defining how a recurring event is expanded into individual
 export interface RecurringEventDateSpans {
   isAllDay: boolean
   hasEnd: boolean
-  ranges: UnzonedRange[]
+  ranges: DateRange[]
 }
 
 export type RecurringExpander = (
   rawEvent: EventInput,
-  range: UnzonedRange,
+  range: DateRange,
   calendar: Calendar,
   leftovers: object
 ) => RecurringEventDateSpans | null
@@ -28,7 +28,7 @@ export function registerRecurringExpander(expander: RecurringExpander) {
 
 export function expandRecurring(
   rawEvent: EventInput,
-  range: UnzonedRange,
+  range: DateRange,
   calendar: Calendar,
   leftovers?: object
 ): RecurringEventDateSpans | null {

+ 5 - 5
src/util/misc.ts

@@ -4,7 +4,7 @@ import { preventDefault } from './dom-event'
 import { DateMarker, startOfDay, addDays, diffDays, diffDayAndTime } from '../datelib/marker'
 import { Duration, asRoughMs, createDuration } from '../datelib/duration'
 import { DateEnv } from '../datelib/env'
-import UnzonedRange from '../models/UnzonedRange'
+import { DateRange } from '../datelib/date-range'
 
 
 /* FullCalendar-specific DOM Utilities
@@ -454,16 +454,16 @@ export function refineProps(rawProps: GenericHash, processors: GenericHash, defa
 
 // given a timed range, computes an all-day range that has the same exact duration,
 // but whose start time is aligned with the start of the day.
-export function computeAlignedDayRange(range: UnzonedRange): UnzonedRange {
+export function computeAlignedDayRange(range: DateRange): DateRange {
   let dayCnt = Math.floor(diffDays(range.start, range.end)) || 1
   let start = startOfDay(range.start)
   let end = addDays(start, dayCnt)
-  return new UnzonedRange(start, end)
+  return { start, end }
 }
 
 
 // given a timed range, computes an all-day range based on how for the end date bleeds into the next day
-export function computeVisibleDayRange(unzonedRange: UnzonedRange, nextDayThreshold: Duration): UnzonedRange {
+export function computeVisibleDayRange(unzonedRange: DateRange, nextDayThreshold: Duration): DateRange {
   let startDay: DateMarker = startOfDay(unzonedRange.start) // the beginning of the day the range starts
   let end: DateMarker = unzonedRange.end
   let endDay: DateMarker = startOfDay(end)
@@ -481,7 +481,7 @@ export function computeVisibleDayRange(unzonedRange: UnzonedRange, nextDayThresh
     endDay = addDays(startDay, 1)
   }
 
-  return new UnzonedRange(startDay, endDay)
+  return { start: startDay, end: endDay }
 }