elements past the limit
+ .addClass('fc-limited').get(); // hide elements and get a simple DOM-nodes array
+
+ // iterate though segments in the last allowable level
+ for (i = 0; i < levelSegs.length; i++) {
+ seg = levelSegs[i];
+ emptyCellsUntil(seg.leftCol); // process empty cells before the segment
+
+ // determine *all* segments below `seg` that occupy the same columns
+ colSegsBelow = [];
+ totalSegsBelow = 0;
+ while (col <= seg.rightCol) {
+ cell = this.getCell(row, col);
+ segsBelow = this.getCellSegs(cell, levelLimit);
+ colSegsBelow.push(segsBelow);
+ totalSegsBelow += segsBelow.length;
+ col++;
+ }
+
+ if (totalSegsBelow) { // do we need to replace this segment with one or many "more" links?
+ td = cellMatrix[levelLimit - 1][seg.leftCol]; // the segment's parent cell
+ rowspan = td.attr('rowspan') || 1;
+ segMoreNodes = [];
+
+ // make a replacement for each column the segment occupies. will be one for each colspan
+ for (j = 0; j < colSegsBelow.length; j++) {
+ moreTd = $(' | | ').attr('rowspan', rowspan);
+ segsBelow = colSegsBelow[j];
+ cell = this.getCell(row, seg.leftCol + j);
+ moreLink = this.renderMoreLink(cell, [ seg ].concat(segsBelow)); // count seg as hidden too
+ moreWrap = $('').append(moreLink);
+ moreTd.append(moreWrap);
+ segMoreNodes.push(moreTd[0]);
+ moreNodes.push(moreTd[0]);
+ }
+
+ td.addClass('fc-limited').after($(segMoreNodes)); // hide original and inject replacements
+ limitedNodes.push(td[0]);
+ }
+ }
+
+ emptyCellsUntil(this.colCnt); // finish off the level
+ rowStruct.moreEls = $(moreNodes); // for easy undoing later
+ rowStruct.limitedEls = $(limitedNodes); // for easy undoing later
+ }
+ },
+
+
+ // Reveals all levels and removes all "more"-related elements for a grid's row.
+ // `row` is a row number.
+ unlimitRow: function(row) {
+ var rowStruct = this.rowStructs[row];
+
+ if (rowStruct.moreEls) {
+ rowStruct.moreEls.remove();
+ rowStruct.moreEls = null;
+ }
+
+ if (rowStruct.limitedEls) {
+ rowStruct.limitedEls.removeClass('fc-limited');
+ rowStruct.limitedEls = null;
+ }
+ },
+
+
+ // Renders an element that represents hidden event element for a cell.
+ // Responsible for attaching click handler as well.
+ renderMoreLink: function(cell, hiddenSegs) {
+ var _this = this;
+ var view = this.view;
+
+ return $('')
+ .text(
+ this.getMoreLinkText(hiddenSegs.length)
+ )
+ .on('click', function(ev) {
+ var clickOption = view.opt('eventLimitClick');
+ var date = cell.start;
+ var moreEl = $(this);
+ var dayEl = _this.getCellDayEl(cell);
+ var allSegs = _this.getCellSegs(cell);
+
+ // rescope the segments to be within the cell's date
+ var reslicedAllSegs = _this.resliceDaySegs(allSegs, date);
+ var reslicedHiddenSegs = _this.resliceDaySegs(hiddenSegs, date);
+
+ if (typeof clickOption === 'function') {
+ // the returned value can be an atomic option
+ clickOption = view.trigger('eventLimitClick', null, {
+ date: date,
+ dayEl: dayEl,
+ moreEl: moreEl,
+ segs: reslicedAllSegs,
+ hiddenSegs: reslicedHiddenSegs
+ }, ev);
+ }
+
+ if (clickOption === 'popover') {
+ _this.showSegPopover(cell, moreEl, reslicedAllSegs);
+ }
+ else if (typeof clickOption === 'string') { // a view name
+ view.calendar.zoomTo(date, clickOption);
+ }
+ });
+ },
+
+
+ // Reveals the popover that displays all events within a cell
+ showSegPopover: function(cell, moreLink, segs) {
+ var _this = this;
+ var view = this.view;
+ var moreWrap = moreLink.parent(); // the wrapper around the
+ var topEl; // the element we want to match the top coordinate of
+ var options;
+
+ if (this.rowCnt == 1) {
+ topEl = view.el; // will cause the popover to cover any sort of header
+ }
+ else {
+ topEl = this.rowEls.eq(cell.row); // will align with top of row
+ }
+
+ options = {
+ className: 'fc-more-popover',
+ content: this.renderSegPopoverContent(cell, segs),
+ parentEl: this.el,
+ top: topEl.offset().top,
+ autoHide: true, // when the user clicks elsewhere, hide the popover
+ viewportConstrain: view.opt('popoverViewportConstrain'),
+ hide: function() {
+ // kill everything when the popover is hidden
+ _this.segPopover.removeElement();
+ _this.segPopover = null;
+ _this.popoverSegs = null;
+ }
+ };
+
+ // Determine horizontal coordinate.
+ // We use the moreWrap instead of the to avoid border confusion.
+ if (this.isRTL) {
+ options.right = moreWrap.offset().left + moreWrap.outerWidth() + 1; // +1 to be over cell border
+ }
+ else {
+ options.left = moreWrap.offset().left - 1; // -1 to be over cell border
+ }
+
+ this.segPopover = new Popover(options);
+ this.segPopover.show();
+ },
+
+
+ // Builds the inner DOM contents of the segment popover
+ renderSegPopoverContent: function(cell, segs) {
+ var view = this.view;
+ var isTheme = view.opt('theme');
+ var title = cell.start.format(view.opt('dayPopoverFormat'));
+ var content = $(
+ '' +
+ ''
+ );
+ var segContainer = content.find('.fc-event-container');
+ var i;
+
+ // render each seg's `el` and only return the visible segs
+ segs = this.renderFgSegEls(segs, true); // disableResizing=true
+ this.popoverSegs = segs;
+
+ for (i = 0; i < segs.length; i++) {
+
+ // because segments in the popover are not part of a grid coordinate system, provide a hint to any
+ // grids that want to do drag-n-drop about which cell it came from
+ segs[i].cell = cell;
+
+ segContainer.append(segs[i].el);
+ }
+
+ return content;
+ },
+
+
+ // Given the events within an array of segment objects, reslice them to be in a single day
+ resliceDaySegs: function(segs, dayDate) {
+
+ // build an array of the original events
+ var events = $.map(segs, function(seg) {
+ return seg.event;
+ });
+
+ var dayStart = dayDate.clone().stripTime();
+ var dayEnd = dayStart.clone().add(1, 'days');
+ var dayRange = { start: dayStart, end: dayEnd };
+
+ // slice the events with a custom slicing function
+ segs = this.eventsToSegs(
+ events,
+ function(range) {
+ var seg = intersectionToSeg(range, dayRange); // undefind if no intersection
+ return seg ? [ seg ] : []; // must return an array of segments
+ }
+ );
+
+ // force an order because eventsToSegs doesn't guarantee one
+ segs.sort(compareSegs);
+
+ return segs;
+ },
+
+
+ // Generates the text that should be inside a "more" link, given the number of events it represents
+ getMoreLinkText: function(num) {
+ var opt = this.view.opt('eventLimitText');
+
+ if (typeof opt === 'function') {
+ return opt(num);
+ }
+ else {
+ return '+' + num + ' ' + opt;
+ }
+ },
+
+
+ // Returns segments within a given cell.
+ // If `startLevel` is specified, returns only events including and below that level. Otherwise returns all segs.
+ getCellSegs: function(cell, startLevel) {
+ var segMatrix = this.rowStructs[cell.row].segMatrix;
+ var level = startLevel || 0;
+ var segs = [];
+ var seg;
+
+ while (level < segMatrix.length) {
+ seg = segMatrix[level][cell.col];
+ if (seg) {
+ segs.push(seg);
+ }
+ level++;
+ }
+
+ return segs;
+ }
+
+});
+
+;;
+
+/* A component that renders one or more columns of vertical time slots
+----------------------------------------------------------------------------------------------------------------------*/
+
+var TimeGrid = Grid.extend({
+
+ slotDuration: null, // duration of a "slot", a distinct time segment on given day, visualized by lines
+ snapDuration: null, // granularity of time for dragging and selecting
+ minTime: null, // Duration object that denotes the first visible time of any given day
+ maxTime: null, // Duration object that denotes the exclusive visible end time of any given day
+ colDates: null, // whole-day dates for each column. left to right
+ axisFormat: null, // formatting string for times running along vertical axis
+
+ dayEls: null, // cells elements in the day-row background
+ slatEls: null, // elements running horizontally across all columns
+
+ slatTops: null, // an array of top positions, relative to the container. last item holds bottom of last slot
+
+ helperEl: null, // cell skeleton element for rendering the mock event "helper"
+
+ businessHourSegs: null,
+
+
+ constructor: function() {
+ Grid.apply(this, arguments); // call the super-constructor
+ this.processOptions();
+ },
+
+
+ // Renders the time grid into `this.el`, which should already be assigned.
+ // Relies on the view's colCnt. In the future, this component should probably be self-sufficient.
+ renderDates: function() {
+ this.el.html(this.renderHtml());
+ this.dayEls = this.el.find('.fc-day');
+ this.slatEls = this.el.find('.fc-slats tr');
+ },
+
+
+ renderBusinessHours: function() {
+ var events = this.view.calendar.getBusinessHoursEvents();
+ this.businessHourSegs = this.renderFill('businessHours', this.eventsToSegs(events), 'bgevent');
+ },
+
+
+ // Renders the basic HTML skeleton for the grid
+ renderHtml: function() {
+ return '' +
+ '' +
+ ' ' +
+ this.rowHtml('slotBg') + // leverages RowRenderer, which will call slotBgCellHtml
+ ' ' +
+ ' ' +
+ '' +
+ ' ' +
+ this.slatRowHtml() +
+ ' ' +
+ ' ';
+ },
+
+
+ // Renders the HTML for a vertical background cell behind the slots.
+ // This method is distinct from 'bg' because we wanted a new `rowType` so the View could customize the rendering.
+ slotBgCellHtml: function(cell) {
+ return this.bgCellHtml(cell);
+ },
+
+
+ // Generates the HTML for the horizontal "slats" that run width-wise. Has a time axis on a side. Depends on RTL.
+ slatRowHtml: function() {
+ var view = this.view;
+ var isRTL = this.isRTL;
+ var html = '';
+ var slotNormal = this.slotDuration.asMinutes() % 15 === 0;
+ var slotTime = moment.duration(+this.minTime); // wish there was .clone() for durations
+ var slotDate; // will be on the view's first day, but we only care about its time
+ var minutes;
+ var axisHtml;
+
+ // Calculate the time for each slot
+ while (slotTime < this.maxTime) {
+ slotDate = this.start.clone().time(slotTime); // will be in UTC but that's good. to avoid DST issues
+ minutes = slotDate.minutes();
+
+ axisHtml =
+ ' | ' +
+ ((!slotNormal || !minutes) ? // if irregular slot duration, or on the hour, then display the time
+ '' + // for matchCellWidths
+ htmlEscape(slotDate.format(this.axisFormat)) +
+ '' :
+ ''
+ ) +
+ ' | ';
+
+ html +=
+ ' ' +
+ (!isRTL ? axisHtml : '') +
+ ' | ' +
+ (isRTL ? axisHtml : '') +
+ " ";
+
+ slotTime.add(this.slotDuration);
+ }
+
+ return html;
+ },
+
+
+ /* Options
+ ------------------------------------------------------------------------------------------------------------------*/
+
+
+ // Parses various options into properties of this object
+ processOptions: function() {
+ var view = this.view;
+ var slotDuration = view.opt('slotDuration');
+ var snapDuration = view.opt('snapDuration');
+
+ slotDuration = moment.duration(slotDuration);
+ snapDuration = snapDuration ? moment.duration(snapDuration) : slotDuration;
+
+ this.slotDuration = slotDuration;
+ this.snapDuration = snapDuration;
+ this.cellDuration = snapDuration; // for Grid system
+
+ this.minTime = moment.duration(view.opt('minTime'));
+ this.maxTime = moment.duration(view.opt('maxTime'));
+
+ this.axisFormat = view.opt('axisFormat') || view.opt('smallTimeFormat');
+ },
+
+
+ // Computes a default column header formatting string if `colFormat` is not explicitly defined
+ computeColHeadFormat: function() {
+ if (this.colCnt > 1) { // multiple days, so full single date string WON'T be in title text
+ return this.view.opt('dayOfMonthFormat'); // "Sat 12/10"
+ }
+ else { // single day, so full single date string will probably be in title text
+ return 'dddd'; // "Saturday"
+ }
+ },
+
+
+ // Computes a default event time formatting string if `timeFormat` is not explicitly defined
+ computeEventTimeFormat: function() {
+ return this.view.opt('noMeridiemTimeFormat'); // like "6:30" (no AM/PM)
+ },
+
+
+ // Computes a default `displayEventEnd` value if one is not expliclty defined
+ computeDisplayEventEnd: function() {
+ return true;
+ },
+
+
+ /* Cell System
+ ------------------------------------------------------------------------------------------------------------------*/
+
+
+ rangeUpdated: function() {
+ var view = this.view;
+ var colDates = [];
+ var date;
+
+ date = this.start.clone();
+ while (date.isBefore(this.end)) {
+ colDates.push(date.clone());
+ date.add(1, 'day');
+ date = view.skipHiddenDays(date);
+ }
+
+ if (this.isRTL) {
+ colDates.reverse();
+ }
+
+ this.colDates = colDates;
+ this.colCnt = colDates.length;
+ this.rowCnt = Math.ceil((this.maxTime - this.minTime) / this.snapDuration); // # of vertical snaps
+ },
+
+
+ // Given a cell object, generates its start date. Returns a reference-free copy.
+ computeCellDate: function(cell) {
+ var date = this.colDates[cell.col];
+ var time = this.computeSnapTime(cell.row);
+
+ date = this.view.calendar.rezoneDate(date); // give it a 00:00 time
+ date.time(time);
+
+ return date;
+ },
+
+
+ // Retrieves the element representing the given column
+ getColEl: function(col) {
+ return this.dayEls.eq(col);
+ },
+
+
+ /* Dates
+ ------------------------------------------------------------------------------------------------------------------*/
+
+
+ // Given a row number of the grid, representing a "snap", returns a time (Duration) from its start-of-day
+ computeSnapTime: function(row) {
+ return moment.duration(this.minTime + this.snapDuration * row);
+ },
+
+
+ // Slices up a date range by column into an array of segments
+ rangeToSegs: function(range) {
+ var colCnt = this.colCnt;
+ var segs = [];
+ var seg;
+ var col;
+ var colDate;
+ var colRange;
+
+ // normalize :(
+ range = {
+ start: range.start.clone().stripZone(),
+ end: range.end.clone().stripZone()
+ };
+
+ for (col = 0; col < colCnt; col++) {
+ colDate = this.colDates[col]; // will be ambig time/timezone
+ colRange = {
+ start: colDate.clone().time(this.minTime),
+ end: colDate.clone().time(this.maxTime)
+ };
+ seg = intersectionToSeg(range, colRange); // both will be ambig timezone
+ if (seg) {
+ seg.col = col;
+ segs.push(seg);
+ }
+ }
+
+ return segs;
+ },
+
+
+ /* Coordinates
+ ------------------------------------------------------------------------------------------------------------------*/
+
+
+ updateSize: function(isResize) { // NOT a standard Grid method
+ this.computeSlatTops();
+
+ if (isResize) {
+ this.updateSegVerticals();
+ }
+ },
+
+
+ // Computes the top/bottom coordinates of each "snap" rows
+ computeRowCoords: function() {
+ var originTop = this.el.offset().top;
+ var items = [];
+ var i;
+ var item;
+
+ for (i = 0; i < this.rowCnt; i++) {
+ item = {
+ top: originTop + this.computeTimeTop(this.computeSnapTime(i))
+ };
+ if (i > 0) {
+ items[i - 1].bottom = item.top;
+ }
+ items.push(item);
+ }
+ item.bottom = item.top + this.computeTimeTop(this.computeSnapTime(i));
+
+ return items;
+ },
+
+
+ // Computes the top coordinate, relative to the bounds of the grid, of the given date.
+ // A `startOfDayDate` must be given for avoiding ambiguity over how to treat midnight.
+ computeDateTop: function(date, startOfDayDate) {
+ return this.computeTimeTop(
+ moment.duration(
+ date.clone().stripZone() - startOfDayDate.clone().stripTime()
+ )
+ );
+ },
+
+
+ // Computes the top coordinate, relative to the bounds of the grid, of the given time (a Duration).
+ computeTimeTop: function(time) {
+ var slatCoverage = (time - this.minTime) / this.slotDuration; // floating-point value of # of slots covered
+ var slatIndex;
+ var slatRemainder;
+ var slatTop;
+ var slatBottom;
+
+ // constrain. because minTime/maxTime might be customized
+ slatCoverage = Math.max(0, slatCoverage);
+ slatCoverage = Math.min(this.slatEls.length, slatCoverage);
+
+ slatIndex = Math.floor(slatCoverage); // an integer index of the furthest whole slot
+ slatRemainder = slatCoverage - slatIndex;
+ slatTop = this.slatTops[slatIndex]; // the top position of the furthest whole slot
+
+ if (slatRemainder) { // time spans part-way into the slot
+ slatBottom = this.slatTops[slatIndex + 1];
+ return slatTop + (slatBottom - slatTop) * slatRemainder; // part-way between slots
+ }
+ else {
+ return slatTop;
+ }
+ },
+
+
+ // Queries each `slatEl` for its position relative to the grid's container and stores it in `slatTops`.
+ // Includes the the bottom of the last slat as the last item in the array.
+ computeSlatTops: function() {
+ var tops = [];
+ var top;
+
+ this.slatEls.each(function(i, node) {
+ top = $(node).position().top;
+ tops.push(top);
+ });
+
+ tops.push(top + this.slatEls.last().outerHeight()); // bottom of the last slat
+
+ this.slatTops = tops;
+ },
+
+
+ /* Event Drag Visualization
+ ------------------------------------------------------------------------------------------------------------------*/
+
+
+ // Renders a visual indication of an event being dragged over the specified date(s).
+ // dropLocation's end might be null, as well as `seg`. See Grid::renderDrag for more info.
+ // A returned value of `true` signals that a mock "helper" event has been rendered.
+ renderDrag: function(dropLocation, seg) {
+
+ if (seg) { // if there is event information for this drag, render a helper event
+ this.renderRangeHelper(dropLocation, seg);
+ this.applyDragOpacity(this.helperEl);
+
+ return true; // signal that a helper has been rendered
+ }
+ else {
+ // otherwise, just render a highlight
+ this.renderHighlight(this.eventRangeToSegs(dropLocation));
+ }
+ },
+
+
+ // Unrenders any visual indication of an event being dragged
+ unrenderDrag: function() {
+ this.unrenderHelper();
+ this.unrenderHighlight();
+ },
+
+
+ /* Event Resize Visualization
+ ------------------------------------------------------------------------------------------------------------------*/
+
+
+ // Renders a visual indication of an event being resized
+ renderEventResize: function(range, seg) {
+ this.renderRangeHelper(range, seg);
+ },
+
+
+ // Unrenders any visual indication of an event being resized
+ unrenderEventResize: function() {
+ this.unrenderHelper();
+ },
+
+
+ /* Event Helper
+ ------------------------------------------------------------------------------------------------------------------*/
+
+
+ // Renders a mock "helper" event. `sourceSeg` is the original segment object and might be null (an external drag)
+ renderHelper: function(event, sourceSeg) {
+ var segs = this.eventsToSegs([ event ]);
+ var tableEl;
+ var i, seg;
+ var sourceEl;
+
+ segs = this.renderFgSegEls(segs); // assigns each seg's el and returns a subset of segs that were rendered
+ tableEl = this.renderSegTable(segs);
+
+ // Try to make the segment that is in the same row as sourceSeg look the same
+ for (i = 0; i < segs.length; i++) {
+ seg = segs[i];
+ if (sourceSeg && sourceSeg.col === seg.col) {
+ sourceEl = sourceSeg.el;
+ seg.el.css({
+ left: sourceEl.css('left'),
+ right: sourceEl.css('right'),
+ 'margin-left': sourceEl.css('margin-left'),
+ 'margin-right': sourceEl.css('margin-right')
+ });
+ }
+ }
+
+ this.helperEl = $(' ')
+ .append(tableEl)
+ .appendTo(this.el);
+ },
+
+
+ // Unrenders any mock helper event
+ unrenderHelper: function() {
+ if (this.helperEl) {
+ this.helperEl.remove();
+ this.helperEl = null;
+ }
+ },
+
+
+ /* Selection
+ ------------------------------------------------------------------------------------------------------------------*/
+
+
+ // Renders a visual indication of a selection. Overrides the default, which was to simply render a highlight.
+ renderSelection: function(range) {
+ if (this.view.opt('selectHelper')) { // this setting signals that a mock helper event should be rendered
+ this.renderRangeHelper(range);
+ }
+ else {
+ this.renderHighlight(this.selectionRangeToSegs(range));
+ }
+ },
+
+
+ // Unrenders any visual indication of a selection
+ unrenderSelection: function() {
+ this.unrenderHelper();
+ this.unrenderHighlight();
+ },
+
+
+ /* Fill System (highlight, background events, business hours)
+ ------------------------------------------------------------------------------------------------------------------*/
+
+
+ // Renders a set of rectangles over the given time segments.
+ // Only returns segments that successfully rendered.
+ renderFill: function(type, segs, className) {
+ var segCols;
+ var skeletonEl;
+ var trEl;
+ var col, colSegs;
+ var tdEl;
+ var containerEl;
+ var dayDate;
+ var i, seg;
+
+ if (segs.length) {
+
+ segs = this.renderFillSegEls(type, segs); // assignes `.el` to each seg. returns successfully rendered segs
+ segCols = this.groupSegCols(segs); // group into sub-arrays, and assigns 'col' to each seg
+
+ className = className || type.toLowerCase();
+ skeletonEl = $(
+ ' '
+ );
+ trEl = skeletonEl.find('tr');
+
+ for (col = 0; col < segCols.length; col++) {
+ colSegs = segCols[col];
+ tdEl = $(' | ').appendTo(trEl);
+
+ if (colSegs.length) {
+ containerEl = $(' ').appendTo(tdEl);
+ dayDate = this.colDates[col];
+
+ for (i = 0; i < colSegs.length; i++) {
+ seg = colSegs[i];
+ containerEl.append(
+ seg.el.css({
+ top: this.computeDateTop(seg.start, dayDate),
+ bottom: -this.computeDateTop(seg.end, dayDate) // the y position of the bottom edge
+ })
+ );
+ }
+ }
+ }
+
+ this.bookendCells(trEl, type);
+
+ this.el.append(skeletonEl);
+ this.elsByFill[type] = skeletonEl;
+ }
+
+ return segs;
+ }
+
+});
+
+;;
+
+/* Event-rendering methods for the TimeGrid class
+----------------------------------------------------------------------------------------------------------------------*/
+
+TimeGrid.mixin({
+
+ eventSkeletonEl: null, // has cells with event-containers, which contain absolutely positioned event elements
+
+
+ // Renders the given foreground event segments onto the grid
+ renderFgSegs: function(segs) {
+ segs = this.renderFgSegEls(segs); // returns a subset of the segs. segs that were actually rendered
+
+ this.el.append(
+ this.eventSkeletonEl = $(' ')
+ .append(this.renderSegTable(segs))
+ );
+
+ return segs; // return only the segs that were actually rendered
+ },
+
+
+ // Unrenders all currently rendered foreground event segments
+ unrenderFgSegs: function(segs) {
+ if (this.eventSkeletonEl) {
+ this.eventSkeletonEl.remove();
+ this.eventSkeletonEl = null;
+ }
+ },
+
+
+ // Renders and returns the portion of the event-skeleton.
+ // Returns an object with properties 'tbodyEl' and 'segs'.
+ renderSegTable: function(segs) {
+ var tableEl = $('');
+ var trEl = tableEl.find('tr');
+ var segCols;
+ var i, seg;
+ var col, colSegs;
+ var containerEl;
+
+ segCols = this.groupSegCols(segs); // group into sub-arrays, and assigns 'col' to each seg
+
+ this.computeSegVerticals(segs); // compute and assign top/bottom
+
+ for (col = 0; col < segCols.length; col++) { // iterate each column grouping
+ colSegs = segCols[col];
+ placeSlotSegs(colSegs); // compute horizontal coordinates, z-index's, and reorder the array
+
+ containerEl = $('');
+
+ // assign positioning CSS and insert into container
+ for (i = 0; i < colSegs.length; i++) {
+ seg = colSegs[i];
+ seg.el.css(this.generateSegPositionCss(seg));
+
+ // if the height is short, add a className for alternate styling
+ if (seg.bottom - seg.top < 30) {
+ seg.el.addClass('fc-short');
+ }
+
+ containerEl.append(seg.el);
+ }
+
+ trEl.append($(' | ').append(containerEl));
+ }
+
+ this.bookendCells(trEl, 'eventSkeleton');
+
+ return tableEl;
+ },
+
+
+ // Refreshes the CSS top/bottom coordinates for each segment element. Probably after a window resize/zoom.
+ // Repositions business hours segs too, so not just for events. Maybe shouldn't be here.
+ updateSegVerticals: function() {
+ var allSegs = (this.segs || []).concat(this.businessHourSegs || []);
+ var i;
+
+ this.computeSegVerticals(allSegs);
+
+ for (i = 0; i < allSegs.length; i++) {
+ allSegs[i].el.css(
+ this.generateSegVerticalCss(allSegs[i])
+ );
+ }
+ },
+
+
+ // For each segment in an array, computes and assigns its top and bottom properties
+ computeSegVerticals: function(segs) {
+ var i, seg;
+
+ for (i = 0; i < segs.length; i++) {
+ seg = segs[i];
+ seg.top = this.computeDateTop(seg.start, seg.start);
+ seg.bottom = this.computeDateTop(seg.end, seg.start);
+ }
+ },
+
+
+ // Renders the HTML for a single event segment's default rendering
+ fgSegHtml: function(seg, disableResizing) {
+ var view = this.view;
+ var event = seg.event;
+ var isDraggable = view.isEventDraggable(event);
+ var isResizableFromStart = !disableResizing && seg.isStart && view.isEventResizableFromStart(event);
+ var isResizableFromEnd = !disableResizing && seg.isEnd && view.isEventResizableFromEnd(event);
+ var classes = this.getSegClasses(seg, isDraggable, isResizableFromStart || isResizableFromEnd);
+ var skinCss = cssToStr(this.getEventSkinCss(event));
+ var timeText;
+ var fullTimeText; // more verbose time text. for the print stylesheet
+ var startTimeText; // just the start time text
+
+ classes.unshift('fc-time-grid-event', 'fc-v-event');
+
+ if (view.isMultiDayEvent(event)) { // if the event appears to span more than one day...
+ // Don't display time text on segments that run entirely through a day.
+ // That would appear as midnight-midnight and would look dumb.
+ // Otherwise, display the time text for the *segment's* times (like 6pm-midnight or midnight-10am)
+ if (seg.isStart || seg.isEnd) {
+ timeText = this.getEventTimeText(seg);
+ fullTimeText = this.getEventTimeText(seg, 'LT');
+ startTimeText = this.getEventTimeText(seg, null, false); // displayEnd=false
+ }
+ } else {
+ // Display the normal time text for the *event's* times
+ timeText = this.getEventTimeText(event);
+ fullTimeText = this.getEventTimeText(event, 'LT');
+ startTimeText = this.getEventTimeText(event, null, false); // displayEnd=false
+ }
+
+ return '' +
+ '' +
+ (timeText ?
+ ' ' +
+ '' + htmlEscape(timeText) + '' +
+ ' ' :
+ ''
+ ) +
+ (event.title ?
+ ' ' +
+ htmlEscape(event.title) +
+ ' ' :
+ ''
+ ) +
+ ' ' +
+ '' +
+ /* TODO: write CSS for this
+ (isResizableFromStart ?
+ '' :
+ ''
+ ) +
+ */
+ (isResizableFromEnd ?
+ '' :
+ ''
+ ) +
+ '';
+ },
+
+
+ // Generates an object with CSS properties/values that should be applied to an event segment element.
+ // Contains important positioning-related properties that should be applied to any event element, customized or not.
+ generateSegPositionCss: function(seg) {
+ var shouldOverlap = this.view.opt('slotEventOverlap');
+ var backwardCoord = seg.backwardCoord; // the left side if LTR. the right side if RTL. floating-point
+ var forwardCoord = seg.forwardCoord; // the right side if LTR. the left side if RTL. floating-point
+ var props = this.generateSegVerticalCss(seg); // get top/bottom first
+ var left; // amount of space from left edge, a fraction of the total width
+ var right; // amount of space from right edge, a fraction of the total width
+
+ if (shouldOverlap) {
+ // double the width, but don't go beyond the maximum forward coordinate (1.0)
+ forwardCoord = Math.min(1, backwardCoord + (forwardCoord - backwardCoord) * 2);
+ }
+
+ if (this.isRTL) {
+ left = 1 - forwardCoord;
+ right = backwardCoord;
+ }
+ else {
+ left = backwardCoord;
+ right = 1 - forwardCoord;
+ }
+
+ props.zIndex = seg.level + 1; // convert from 0-base to 1-based
+ props.left = left * 100 + '%';
+ props.right = right * 100 + '%';
+
+ if (shouldOverlap && seg.forwardPressure) {
+ // add padding to the edge so that forward stacked events don't cover the resizer's icon
+ props[this.isRTL ? 'marginLeft' : 'marginRight'] = 10 * 2; // 10 is a guesstimate of the icon's width
+ }
+
+ return props;
+ },
+
+
+ // Generates an object with CSS properties for the top/bottom coordinates of a segment element
+ generateSegVerticalCss: function(seg) {
+ return {
+ top: seg.top,
+ bottom: -seg.bottom // flipped because needs to be space beyond bottom edge of event container
+ };
+ },
+
+
+ // Given a flat array of segments, return an array of sub-arrays, grouped by each segment's col
+ groupSegCols: function(segs) {
+ var segCols = [];
+ var i;
+ for (i = 0; i < this.colCnt; i++) {
+ segCols.push([]);
+ }
-}
+ for (i = 0; i < segs.length; i++) {
+ segCols[segs[i].col].push(segs[i]);
+ }
-;;
+ return segCols;
+ }
-fc.sourceNormalizers = [];
-fc.sourceFetchers = [];
+});
-var ajaxDefaults = {
- dataType: 'json',
- cache: false
-};
-var eventGUID = 1;
+// Given an array of segments that are all in the same column, sets the backwardCoord and forwardCoord on each.
+// NOTE: Also reorders the given array by date!
+function placeSlotSegs(segs) {
+ var levels;
+ var level0;
+ var i;
+ segs.sort(compareSegs); // order by date
+ levels = buildSlotSegLevels(segs);
+ computeForwardSlotSegs(levels);
-function EventManager(options) { // assumed to be a calendar
- var t = this;
-
-
- // exports
- t.isFetchNeeded = isFetchNeeded;
- t.fetchEvents = fetchEvents;
- t.addEventSource = addEventSource;
- t.removeEventSource = removeEventSource;
- t.updateEvent = updateEvent;
- t.renderEvent = renderEvent;
- t.removeEvents = removeEvents;
- t.clientEvents = clientEvents;
- t.mutateEvent = mutateEvent;
-
-
- // imports
- var trigger = t.trigger;
- var getView = t.getView;
- var reportEvents = t.reportEvents;
- var getEventEnd = t.getEventEnd;
-
-
- // locals
- var stickySource = { events: [] };
- var sources = [ stickySource ];
- var rangeStart, rangeEnd;
- var currentFetchID = 0;
- var pendingSourceCnt = 0;
- var loadingLevel = 0;
- var cache = [];
+ if ((level0 = levels[0])) {
+ for (i = 0; i < level0.length; i++) {
+ computeSlotSegPressures(level0[i]);
+ }
- $.each(
- (options.events ? [ options.events ] : []).concat(options.eventSources || []),
- function(i, sourceInput) {
- var source = buildEventSource(sourceInput);
- if (source) {
- sources.push(source);
- }
+ for (i = 0; i < level0.length; i++) {
+ computeSlotSegCoords(level0[i], 0, 0);
}
- );
-
-
-
- /* Fetching
- -----------------------------------------------------------------------------*/
-
-
- function isFetchNeeded(start, end) {
- return !rangeStart || // nothing has been fetched yet?
- // or, a part of the new range is outside of the old range? (after normalizing)
- start.clone().stripZone() < rangeStart.clone().stripZone() ||
- end.clone().stripZone() > rangeEnd.clone().stripZone();
}
-
-
- function fetchEvents(start, end) {
- rangeStart = start;
- rangeEnd = end;
- cache = [];
- var fetchID = ++currentFetchID;
- var len = sources.length;
- pendingSourceCnt = len;
- for (var i=0; i seg2.top && seg1.top < seg2.bottom;
+}
- if (typeof source.className === 'string') {
- // TODO: repeat code, same code for event classNames
- source.className = source.className.split(/\s+/);
- }
- }
- if (source) {
+// A cmp function for determining which forward segment to rely on more when computing coordinates.
+function compareForwardSlotSegs(seg1, seg2) {
+ // put higher-pressure first
+ return seg2.forwardPressure - seg1.forwardPressure ||
+ // put segments that are closer to initial edge first (and favor ones with no coords yet)
+ (seg1.backwardCoord || 0) - (seg2.backwardCoord || 0) ||
+ // do normal sorting...
+ compareSegs(seg1, seg2);
+}
- // for array sources, we convert to standard Event Objects up front
- if ($.isArray(source.events)) {
- source.events = $.map(source.events, function(eventInput) {
- return buildEvent(eventInput, source);
- });
- }
+;;
- for (i=0; i 1) {
+ return 'll'; // multi-day range. shorter, like "Sep 9 - 10 2014"
}
- }
-
-
- function popLoading() {
- if (!(--loadingLevel)) {
- trigger('loading', null, false, getView());
+ else {
+ return 'LL'; // one day. longer, like "September 9 2014"
}
- }
-
-
-
- /* Event Normalization
- -----------------------------------------------------------------------------*/
+ },
+
+
+ // Utility for formatting a range. Accepts a range object, formatting string, and optional separator.
+ // Displays all-day ranges naturally, with an inclusive end. Takes the current isRTL into account.
+ formatRange: function(range, formatStr, separator) {
+ var end = range.end;
+
+ if (!end.hasTime()) { // all-day?
+ end = end.clone().subtract(1); // convert to inclusive. last ms of previous day
+ }
+
+ return formatRange(range.start, end, formatStr, separator, this.opt('isRTL'));
+ },
+
+
+ /* Rendering
+ ------------------------------------------------------------------------------------------------------------------*/
+
+
+ // Sets the container element that the view should render inside of.
+ // Does other DOM-related initializations.
+ setElement: function(el) {
+ this.el = el;
+ this.bindGlobalHandlers();
+ },
+
+
+ // Removes the view's container element from the DOM, clearing any content beforehand.
+ // Undoes any other DOM-related attachments.
+ removeElement: function() {
+ this.clear(); // clears all content
+
+ // clean up the skeleton
+ if (this.isSkeletonRendered) {
+ this.unrenderSkeleton();
+ this.isSkeletonRendered = false;
+ }
+
+ this.unbindGlobalHandlers();
+
+ this.el.remove();
- function buildEvent(data, source) { // source may be undefined!
- var out = {};
- var start;
- var end;
- var allDay;
- var allDayDefault;
+ // NOTE: don't null-out this.el in case the View was destroyed within an API callback.
+ // We don't null-out the View's other jQuery element references upon destroy,
+ // so we shouldn't kill this.el either.
+ },
- if (options.eventDataTransform) {
- data = options.eventDataTransform(data);
- }
- if (source && source.eventDataTransform) {
- data = source.eventDataTransform(data);
- }
- start = t.moment(data.start || data.date); // "date" is an alias for "start"
- if (!start.isValid()) {
- return;
- }
+ // Does everything necessary to display the view centered around the given date.
+ // Does every type of rendering EXCEPT rendering events.
+ // Is asychronous and returns a promise.
+ display: function(date) {
+ var _this = this;
+ var scrollState = null;
- end = null;
- if (data.end) {
- end = t.moment(data.end);
- if (!end.isValid()) {
- return;
- }
+ if (this.displaying) {
+ scrollState = this.queryScroll();
}
- allDay = data.allDay;
- if (allDay === undefined) {
- allDayDefault = firstDefined(
- source ? source.allDayDefault : undefined,
- options.allDayDefault
+ return this.clear().then(function() { // clear the content first (async)
+ return (
+ _this.displaying =
+ $.when(_this.displayView(date)) // displayView might return a promise
+ .then(function() {
+ _this.forceScroll(_this.computeInitialScroll(scrollState));
+ _this.triggerRender();
+ })
);
- if (allDayDefault !== undefined) {
- // use the default
- allDay = allDayDefault;
- }
- else {
- // all dates need to have ambig time for the event to be considered allDay
- allDay = !start.hasTime() && (!end || !end.hasTime());
- }
- }
+ });
+ },
- // normalize the date based on allDay
- if (allDay) {
- // neither date should have a time
- if (start.hasTime()) {
- start.stripTime();
- }
- if (end && end.hasTime()) {
- end.stripTime();
- }
+
+ // Does everything necessary to clear the content of the view.
+ // Clears dates and events. Does not clear the skeleton.
+ // Is asychronous and returns a promise.
+ clear: function() {
+ var _this = this;
+ var displaying = this.displaying;
+
+ if (displaying) { // previously displayed, or in the process of being displayed?
+ return displaying.then(function() { // wait for the display to finish
+ _this.displaying = null;
+ _this.clearEvents();
+ return _this.clearView(); // might return a promise. chain it
+ });
}
else {
- // force a time/zone up the dates
- if (!start.hasTime()) {
- start = t.rezoneDate(start);
- }
- if (end && !end.hasTime()) {
- end = t.rezoneDate(end);
- }
+ return $.when(); // an immediately-resolved promise
}
+ },
- // Copy all properties over to the resulting object.
- // The special-case properties will be copied over afterwards.
- $.extend(out, data);
- if (source) {
- out.source = source;
+ // Displays the view's non-event content, such as date-related content or anything required by events.
+ // Renders the view's non-content skeleton if necessary.
+ // Can be asynchronous and return a promise.
+ displayView: function(date) {
+ if (!this.isSkeletonRendered) {
+ this.renderSkeleton();
+ this.isSkeletonRendered = true;
}
+ this.setDate(date);
+ if (this.render) {
+ this.render(); // TODO: deprecate
+ }
+ this.renderDates();
+ this.updateSize();
+ this.renderBusinessHours(); // might need coordinates, so should go after updateSize()
+ },
- out._id = data._id || (data.id === undefined ? '_fc' + eventGUID++ : data.id + '');
- if (data.className) {
- if (typeof data.className == 'string') {
- out.className = data.className.split(/\s+/);
- }
- else { // assumed to be an array
- out.className = data.className;
- }
- }
- else {
- out.className = [];
+ // Unrenders the view content that was rendered in displayView.
+ // Can be asynchronous and return a promise.
+ clearView: function() {
+ this.unselect();
+ this.triggerUnrender();
+ this.unrenderBusinessHours();
+ this.unrenderDates();
+ if (this.destroy) {
+ this.destroy(); // TODO: deprecate
}
+ },
- out.allDay = allDay;
- out.start = start;
- out.end = end;
- if (options.forceEventDuration && !out.end) {
- out.end = getEventEnd(out);
- }
+ // Renders the basic structure of the view before any content is rendered
+ renderSkeleton: function() {
+ // subclasses should implement
+ },
- backupEventDates(out);
- return out;
- }
+ // Unrenders the basic structure of the view
+ unrenderSkeleton: function() {
+ // subclasses should implement
+ },
+ // Renders the view's date-related content (like cells that represent days/times).
+ // Assumes setRange has already been called and the skeleton has already been rendered.
+ renderDates: function() {
+ // subclasses should implement
+ },
- /* Event Modification Math
- -----------------------------------------------------------------------------------------*/
+ // Unrenders the view's date-related content
+ unrenderDates: function() {
+ // subclasses should override
+ },
- // Modify the date(s) of an event and make this change propagate to all other events with
- // the same ID (related repeating events).
- //
- // If `newStart`/`newEnd` are not specified, the "new" dates are assumed to be `event.start` and `event.end`.
- // The "old" dates to be compare against are always `event._start` and `event._end` (set by EventManager).
- //
- // Returns an object with delta information and a function to undo all operations.
- //
- function mutateEvent(event, newStart, newEnd) {
- var oldAllDay = event._allDay;
- var oldStart = event._start;
- var oldEnd = event._end;
- var clearEnd = false;
- var newAllDay;
- var dateDelta;
- var durationDelta;
- var undoFunc;
- // if no new dates were passed in, compare against the event's existing dates
- if (!newStart && !newEnd) {
- newStart = event.start;
- newEnd = event.end;
- }
+ // Renders business-hours onto the view. Assumes updateSize has already been called.
+ renderBusinessHours: function() {
+ // subclasses should implement
+ },
- // NOTE: throughout this function, the initial values of `newStart` and `newEnd` are
- // preserved. These values may be undefined.
- // detect new allDay
- if (event.allDay != oldAllDay) { // if value has changed, use it
- newAllDay = event.allDay;
- }
- else { // otherwise, see if any of the new dates are allDay
- newAllDay = !(newStart || newEnd).hasTime();
- }
+ // Unrenders previously-rendered business-hours
+ unrenderBusinessHours: function() {
+ // subclasses should implement
+ },
- // normalize the new dates based on allDay
- if (newAllDay) {
- if (newStart) {
- newStart = newStart.clone().stripTime();
- }
- if (newEnd) {
- newEnd = newEnd.clone().stripTime();
- }
- }
- // compute dateDelta
- if (newStart) {
- if (newAllDay) {
- dateDelta = dayishDiff(newStart, oldStart.clone().stripTime()); // treat oldStart as allDay
- }
- else {
- dateDelta = dayishDiff(newStart, oldStart);
- }
- }
+ // Signals that the view's content has been rendered
+ triggerRender: function() {
+ this.trigger('viewRender', this, this, this.el);
+ },
+
+
+ // Signals that the view's content is about to be unrendered
+ triggerUnrender: function() {
+ this.trigger('viewDestroy', this, this, this.el);
+ },
+
+
+ // Binds DOM handlers to elements that reside outside the view container, such as the document
+ bindGlobalHandlers: function() {
+ $(document).on('mousedown', this.documentMousedownProxy);
+ },
+
+
+ // Unbinds DOM handlers from elements that reside outside the view container
+ unbindGlobalHandlers: function() {
+ $(document).off('mousedown', this.documentMousedownProxy);
+ },
+
+
+ // Initializes internal variables related to theming
+ initThemingProps: function() {
+ var tm = this.opt('theme') ? 'ui' : 'fc';
+
+ this.widgetHeaderClass = tm + '-widget-header';
+ this.widgetContentClass = tm + '-widget-content';
+ this.highlightStateClass = tm + '-state-highlight';
+ },
- if (newAllDay != oldAllDay) {
- // if allDay has changed, always throw away the end
- clearEnd = true;
+
+ /* Dimensions
+ ------------------------------------------------------------------------------------------------------------------*/
+
+
+ // Refreshes anything dependant upon sizing of the container element of the grid
+ updateSize: function(isResize) {
+ var scrollState;
+
+ if (isResize) {
+ scrollState = this.queryScroll();
}
- else if (newEnd) {
- durationDelta = dayishDiff(
- // new duration
- newEnd || t.getDefaultEventEnd(newAllDay, newStart || oldStart),
- newStart || oldStart
- ).subtract(dayishDiff(
- // subtract old duration
- oldEnd || t.getDefaultEventEnd(oldAllDay, oldStart),
- oldStart
- ));
+
+ this.updateHeight(isResize);
+ this.updateWidth(isResize);
+
+ if (isResize) {
+ this.setScroll(scrollState);
}
+ },
- undoFunc = mutateEvents(
- clientEvents(event._id), // get events with this ID
- clearEnd,
- newAllDay,
- dateDelta,
- durationDelta
- );
- return {
- dateDelta: dateDelta,
- durationDelta: durationDelta,
- undo: undoFunc
- };
- }
+ // Refreshes the horizontal dimensions of the calendar
+ updateWidth: function(isResize) {
+ // subclasses should implement
+ },
- // Modifies an array of events in the following ways (operations are in order):
- // - clear the event's `end`
- // - convert the event to allDay
- // - add `dateDelta` to the start and end
- // - add `durationDelta` to the event's duration
- //
- // Returns a function that can be called to undo all the operations.
- //
- function mutateEvents(events, clearEnd, forceAllDay, dateDelta, durationDelta) {
- var isAmbigTimezone = t.getIsAmbigTimezone();
- var undoFunctions = [];
+ // Refreshes the vertical dimensions of the calendar
+ updateHeight: function(isResize) {
+ var calendar = this.calendar; // we poll the calendar for height information
- $.each(events, function(i, event) {
- var oldAllDay = event._allDay;
- var oldStart = event._start;
- var oldEnd = event._end;
- var newAllDay = forceAllDay != null ? forceAllDay : oldAllDay;
- var newStart = oldStart.clone();
- var newEnd = (!clearEnd && oldEnd) ? oldEnd.clone() : null;
-
- // NOTE: this function is responsible for transforming `newStart` and `newEnd`,
- // which were initialized to the OLD values first. `newEnd` may be null.
-
- // normlize newStart/newEnd to be consistent with newAllDay
- if (newAllDay) {
- newStart.stripTime();
- if (newEnd) {
- newEnd.stripTime();
- }
- }
- else {
- if (!newStart.hasTime()) {
- newStart = t.rezoneDate(newStart);
- }
- if (newEnd && !newEnd.hasTime()) {
- newEnd = t.rezoneDate(newEnd);
- }
- }
+ this.setHeight(
+ calendar.getSuggestedViewHeight(),
+ calendar.isHeightAuto()
+ );
+ },
- // ensure we have an end date if necessary
- if (!newEnd && (options.forceEventDuration || +durationDelta)) {
- newEnd = t.getDefaultEventEnd(newAllDay, newStart);
- }
- // translate the dates
- newStart.add(dateDelta);
- if (newEnd) {
- newEnd.add(dateDelta).add(durationDelta);
- }
+ // Updates the vertical dimensions of the calendar to the specified height.
+ // if `isAuto` is set to true, height becomes merely a suggestion and the view should use its "natural" height.
+ setHeight: function(height, isAuto) {
+ // subclasses should implement
+ },
- // if the dates have changed, and we know it is impossible to recompute the
- // timezone offsets, strip the zone.
- if (isAmbigTimezone) {
- if (+dateDelta || +durationDelta) {
- newStart.stripZone();
- if (newEnd) {
- newEnd.stripZone();
- }
- }
- }
- event.allDay = newAllDay;
- event.start = newStart;
- event.end = newEnd;
- backupEventDates(event);
+ /* Scroller
+ ------------------------------------------------------------------------------------------------------------------*/
- undoFunctions.push(function() {
- event.allDay = oldAllDay;
- event.start = oldStart;
- event.end = oldEnd;
- backupEventDates(event);
- });
- });
- return function() {
- for (var i=0; i=0; i--) {
- res = obj[parts[i].toLowerCase()];
- if (res !== undefined) {
- return res;
- }
- }
- return obj['default'];
-}
+ /* Event Resizing
+ ------------------------------------------------------------------------------------------------------------------*/
-function htmlEscape(s) {
- return (s + '').replace(/&/g, '&')
- .replace(//g, '>')
- .replace(/'/g, ''')
- .replace(/"/g, '"')
- .replace(/\n/g, ' ');
-}
+ // Computes if the given event is allowed to be resized from its starting edge
+ isEventResizableFromStart: function(event) {
+ return this.opt('eventResizableFromStart') && this.isEventResizable(event);
+ },
-function stripHTMLEntities(text) {
- return text.replace(/&.*?;/g, '');
-}
+ // Computes if the given event is allowed to be resized from its ending edge
+ isEventResizableFromEnd: function(event) {
+ return this.isEventResizable(event);
+ },
-function disableTextSelection(element) {
- element
- .attr('unselectable', 'on')
- .css('MozUserSelect', 'none')
- .bind('selectstart.ui', function() { return false; });
-}
+ // Computes if the given event is allowed to be resized by the user at all
+ isEventResizable: function(event) {
+ var source = event.source || {};
-/*
-function enableTextSelection(element) {
- element
- .attr('unselectable', 'off')
- .css('MozUserSelect', '')
- .unbind('selectstart.ui');
-}
-*/
+ return firstDefined(
+ event.durationEditable,
+ source.durationEditable,
+ this.opt('eventDurationEditable'),
+ event.editable,
+ source.editable,
+ this.opt('editable')
+ );
+ },
+
+
+ // Must be called when an event in the view has been resized to a new length
+ reportEventResize: function(event, resizeLocation, largeUnit, el, ev) {
+ var calendar = this.calendar;
+ var mutateResult = calendar.mutateEvent(event, resizeLocation, largeUnit);
+ var undoFunc = function() {
+ mutateResult.undo();
+ calendar.reportEventChange();
+ };
+
+ this.triggerEventResize(event, mutateResult.durationDelta, undoFunc, el, ev);
+ calendar.reportEventChange(); // will rerender events
+ },
+
+
+ // Triggers event-resize handlers that have subscribed via the API
+ triggerEventResize: function(event, durationDelta, undoFunc, el, ev) {
+ this.trigger('eventResize', el[0], event, durationDelta, undoFunc, ev, {}); // {} = jqui dummy
+ },
+
+
+ /* Selection
+ ------------------------------------------------------------------------------------------------------------------*/
+
+
+ // Selects a date range on the view. `start` and `end` are both Moments.
+ // `ev` is the native mouse event that begin the interaction.
+ select: function(range, ev) {
+ this.unselect(ev);
+ this.renderSelection(range);
+ this.reportSelection(range, ev);
+ },
-function markFirstLast(e) { // TODO: use CSS selectors instead
- e.children()
- .removeClass('fc-first fc-last')
- .filter(':first-child')
- .addClass('fc-first')
- .end()
- .filter(':last-child')
- .addClass('fc-last');
-}
+ // Renders a visual indication of the selection
+ renderSelection: function(range) {
+ // subclasses should implement
+ },
-function getSkinCss(event, opt) {
- var source = event.source || {};
- var eventColor = event.color;
- var sourceColor = source.color;
- var optionColor = opt('eventColor');
- var backgroundColor =
- event.backgroundColor ||
- eventColor ||
- source.backgroundColor ||
- sourceColor ||
- opt('eventBackgroundColor') ||
- optionColor;
- var borderColor =
- event.borderColor ||
- eventColor ||
- source.borderColor ||
- sourceColor ||
- opt('eventBorderColor') ||
- optionColor;
- var textColor =
- event.textColor ||
- source.textColor ||
- opt('eventTextColor');
- var statements = [];
- if (backgroundColor) {
- statements.push('background-color:' + backgroundColor);
- }
- if (borderColor) {
- statements.push('border-color:' + borderColor);
- }
- if (textColor) {
- statements.push('color:' + textColor);
- }
- return statements.join(';');
-}
+ // Called when a new selection is made. Updates internal state and triggers handlers.
+ reportSelection: function(range, ev) {
+ this.isSelected = true;
+ this.triggerSelect(range, ev);
+ },
-function applyAll(functions, thisObj, args) {
- if ($.isFunction(functions)) {
- functions = [ functions ];
- }
- if (functions) {
- var i;
- var ret;
- for (i=0; i bool)
+ var dayCnt = 0;
+ var i;
- if (parseAsUTC) {
- output = moment.utc.apply(moment, args);
- }
- else {
- output = moment.apply(null, args);
+ if (this.opt('weekends') === false) {
+ hiddenDays.push(0, 6); // 0=sunday, 6=saturday
}
- if (isAmbigTime) {
- output._ambigTime = true;
- output._ambigZone = true; // ambiguous time always means ambiguous zone
- }
- else if (parseZone) { // let's record the inputted zone somehow
- if (isAmbigZone) {
- output._ambigZone = true;
- }
- else if (isSingleString) {
- output.zone(input); // if not a valid zone, will assign UTC
+ for (i = 0; i < 7; i++) {
+ if (
+ !(isHiddenDayHash[i] = $.inArray(i, hiddenDays) !== -1)
+ ) {
+ dayCnt++;
}
}
- }
- return new FCMoment(output);
-}
+ if (!dayCnt) {
+ throw 'invalid hiddenDays'; // all days were hidden? bad.
+ }
-// Our subclass of Moment.
-// Accepts an object with the internal Moment properties that should be copied over to
-// `this` object (most likely another Moment object). The values in this data must not
-// be referenced by anything else (two moments sharing a Date object for example).
-function FCMoment(internalData) {
- extend(this, internalData);
-}
+ this.isHiddenDayHash = isHiddenDayHash;
+ },
-// Chain the prototype to Moment's
-FCMoment.prototype = createObject(moment.fn);
-// We need this because Moment's implementation won't create an FCMoment,
-// nor will it copy over the ambig flags.
-FCMoment.prototype.clone = function() {
- return makeMoment([ this ]);
-};
+ // Is the current day hidden?
+ // `day` is a day-of-week index (0-6), or a Moment
+ isHiddenDay: function(day) {
+ if (moment.isMoment(day)) {
+ day = day.day();
+ }
+ return this.isHiddenDayHash[day];
+ },
-// Time-of-day
-// -------------------------------------------------------------------------------------------------
+ // Incrementing the current day until it is no longer a hidden day, returning a copy.
+ // If the initial value of `date` is not a hidden day, don't do anything.
+ // Pass `isExclusive` as `true` if you are dealing with an end date.
+ // `inc` defaults to `1` (increment one day forward each time)
+ skipHiddenDays: function(date, inc, isExclusive) {
+ var out = date.clone();
+ inc = inc || 1;
+ while (
+ this.isHiddenDayHash[(out.day() + (isExclusive ? inc : 0) + 7) % 7]
+ ) {
+ out.add(inc, 'days');
+ }
+ return out;
+ },
-// GETTER
-// Returns a Duration with the hours/minutes/seconds/ms values of the moment.
-// If the moment has an ambiguous time, a duration of 00:00 will be returned.
-//
-// SETTER
-// You can supply a Duration, a Moment, or a Duration-like argument.
-// When setting the time, and the moment has an ambiguous time, it then becomes unambiguous.
-FCMoment.prototype.time = function(time) {
- if (time == null) { // getter
- return moment.duration({
- hours: this.hours(),
- minutes: this.minutes(),
- seconds: this.seconds(),
- milliseconds: this.milliseconds()
- });
- }
- else { // setter
- delete this._ambigTime; // mark that the moment now has a time
+ // Returns the date range of the full days the given range visually appears to occupy.
+ // Returns a new range object.
+ computeDayRange: function(range) {
+ var startDay = range.start.clone().stripTime(); // the beginning of the day the range starts
+ var end = range.end;
+ var endDay = null;
+ var endTimeMS;
- if (!moment.isDuration(time) && !moment.isMoment(time)) {
- time = moment.duration(time);
+ if (end) {
+ endDay = end.clone().stripTime(); // the beginning of the day the range exclusively ends
+ endTimeMS = +end.time(); // # of milliseconds into `endDay`
+
+ // If the end time is actually inclusively part of the next day and is equal to or
+ // beyond the next day threshold, adjust the end to be the exclusive end of `endDay`.
+ // Otherwise, leaving it as inclusive will cause it to exclude `endDay`.
+ if (endTimeMS && endTimeMS >= this.nextDayThreshold) {
+ endDay.add(1, 'days');
+ }
}
- // The day value should cause overflow (so 24 hours becomes 00:00:00 of next day).
- // Only for Duration times, not Moment times.
- var dayHours = 0;
- if (moment.isDuration(time)) {
- dayHours = Math.floor(time.asDays()) * 24;
+ // If no end was specified, or if it is within `startDay` but not past nextDayThreshold,
+ // assign the default duration of one day.
+ if (!end || endDay <= startDay) {
+ endDay = startDay.clone().add(1, 'days');
}
- // We need to set the individual fields.
- // Can't use startOf('day') then add duration. In case of DST at start of day.
- return this.hours(dayHours + time.hours())
- .minutes(time.minutes())
- .seconds(time.seconds())
- .milliseconds(time.milliseconds());
- }
-};
+ return { start: startDay, end: endDay };
+ },
-// Converts the moment to UTC, stripping out its time-of-day and timezone offset,
-// but preserving its YMD. A moment with a stripped time will display no time
-// nor timezone offset when .format() is called.
-FCMoment.prototype.stripTime = function() {
- var a = this.toArray(); // year,month,date,hours,minutes,seconds as an array
- // set the internal UTC flag
- moment.fn.utc.call(this); // call the original method, because we don't want to affect _ambigZone
+ // Does the given event visually appear to occupy more than one day?
+ isMultiDayEvent: function(event) {
+ var range = this.computeDayRange(event); // event is range-ish
- this.year(a[0]) // TODO: find a way to do this in one shot
- .month(a[1])
- .date(a[2])
- .hours(0)
- .minutes(0)
- .seconds(0)
- .milliseconds(0);
+ return range.end.diff(range.start, 'days') > 1;
+ }
- // Mark the time as ambiguous. This needs to happen after the .utc() call, which calls .zone(), which
- // clears all ambig flags. Same concept with the .year/month/date calls in the case of moment-timezone.
- this._ambigTime = true;
- this._ambigZone = true; // if ambiguous time, also ambiguous timezone offset
+});
- return this; // for chaining
-};
+;;
-// Returns if the moment has a non-ambiguous time (boolean)
-FCMoment.prototype.hasTime = function() {
- return !this._ambigTime;
-};
+var Calendar = fc.Calendar = Class.extend({
+ dirDefaults: null, // option defaults related to LTR or RTL
+ langDefaults: null, // option defaults related to current locale
+ overrides: null, // option overrides given to the fullCalendar constructor
+ options: null, // all defaults combined with overrides
+ viewSpecCache: null, // cache of view definitions
+ view: null, // current View object
+ header: null,
+ loadingLevel: 0, // number of simultaneous loading tasks
-// Timezone
-// -------------------------------------------------------------------------------------------------
-// Converts the moment to UTC, stripping out its timezone offset, but preserving its
-// YMD and time-of-day. A moment with a stripped timezone offset will display no
-// timezone offset when .format() is called.
-FCMoment.prototype.stripZone = function() {
- var a = this.toArray(); // year,month,date,hours,minutes,seconds as an array
- var wasAmbigTime = this._ambigTime;
-
- moment.fn.utc.call(this); // set the internal UTC flag
-
- this.year(a[0]) // TODO: find a way to do this in one shot
- .month(a[1])
- .date(a[2])
- .hours(a[3])
- .minutes(a[4])
- .seconds(a[5])
- .milliseconds(a[6]);
-
- if (wasAmbigTime) {
- // the above call to .utc()/.zone() unfortunately clears the ambig flags, so reassign
- this._ambigTime = true;
- }
+ // a lot of this class' OOP logic is scoped within this constructor function,
+ // but in the future, write individual methods on the prototype.
+ constructor: Calendar_constructor,
- // Mark the zone as ambiguous. This needs to happen after the .utc() call, which calls .zone(), which
- // clears all ambig flags. Same concept with the .year/month/date calls in the case of moment-timezone.
- this._ambigZone = true;
- return this; // for chaining
-};
+ // Subclasses can override this for initialization logic after the constructor has been called
+ initialize: function() {
+ },
-// Returns of the moment has a non-ambiguous timezone offset (boolean)
-FCMoment.prototype.hasZone = function() {
- return !this._ambigZone;
-};
-// this method implicitly marks a zone
-FCMoment.prototype.zone = function(tzo) {
+ // Initializes `this.options` and other important options-related objects
+ initOptions: function(overrides) {
+ var lang, langDefaults;
+ var isRTL, dirDefaults;
- if (tzo != null) {
- // FYI, the delete statements need to be before the .zone() call or else chaos ensues
- // for reasons I don't understand.
- delete this._ambigTime;
- delete this._ambigZone;
- }
+ // converts legacy options into non-legacy ones.
+ // in the future, when this is removed, don't use `overrides` reference. make a copy.
+ overrides = massageOverrides(overrides);
- return moment.fn.zone.apply(this, arguments);
-};
+ lang = overrides.lang;
+ langDefaults = langOptionHash[lang];
+ if (!langDefaults) {
+ lang = Calendar.defaults.lang;
+ langDefaults = langOptionHash[lang] || {};
+ }
-// this method implicitly marks a zone
-FCMoment.prototype.local = function() {
- var a = this.toArray(); // year,month,date,hours,minutes,seconds as an array
- var wasAmbigZone = this._ambigZone;
+ isRTL = firstDefined(
+ overrides.isRTL,
+ langDefaults.isRTL,
+ Calendar.defaults.isRTL
+ );
+ dirDefaults = isRTL ? Calendar.rtlDefaults : {};
+
+ this.dirDefaults = dirDefaults;
+ this.langDefaults = langDefaults;
+ this.overrides = overrides;
+ this.options = mergeOptions([ // merge defaults and overrides. lowest to highest precedence
+ Calendar.defaults, // global defaults
+ dirDefaults,
+ langDefaults,
+ overrides
+ ]);
+ populateInstanceComputableOptions(this.options);
+
+ this.viewSpecCache = {}; // somewhat unrelated
+ },
- // will happen anyway via .local()/.zone(), but don't want to rely on internal implementation
- delete this._ambigTime;
- delete this._ambigZone;
- moment.fn.local.apply(this, arguments);
+ // Gets information about how to create a view. Will use a cache.
+ getViewSpec: function(viewType) {
+ var cache = this.viewSpecCache;
- if (wasAmbigZone) {
- // If the moment was ambiguously zoned, the date fields were stored as UTC.
- // We want to preserve these, but in local time.
- this.year(a[0]) // TODO: find a way to do this in one shot
- .month(a[1])
- .date(a[2])
- .hours(a[3])
- .minutes(a[4])
- .seconds(a[5])
- .milliseconds(a[6]);
- }
+ return cache[viewType] || (cache[viewType] = this.buildViewSpec(viewType));
+ },
- return this; // for chaining
-};
-// this method implicitly marks a zone
-FCMoment.prototype.utc = function() {
+ // Given a duration singular unit, like "week" or "day", finds a matching view spec.
+ // Preference is given to views that have corresponding buttons.
+ getUnitViewSpec: function(unit) {
+ var viewTypes;
+ var i;
+ var spec;
- // will happen anyway via .local()/.zone(), but don't want to rely on internal implementation
- delete this._ambigTime;
- delete this._ambigZone;
+ if ($.inArray(unit, intervalUnits) != -1) {
- return moment.fn.utc.apply(this, arguments);
-};
+ // put views that have buttons first. there will be duplicates, but oh well
+ viewTypes = this.header.getViewsWithButtons();
+ $.each(fc.views, function(viewType) { // all views
+ viewTypes.push(viewType);
+ });
+ for (i = 0; i < viewTypes.length; i++) {
+ spec = this.getViewSpec(viewTypes[i]);
+ if (spec) {
+ if (spec.singleUnit == unit) {
+ return spec;
+ }
+ }
+ }
+ }
+ },
-// Formatting
-// -------------------------------------------------------------------------------------------------
-FCMoment.prototype.format = function() {
- if (arguments[0]) {
- return formatDate(this, arguments[0]); // our extended formatting
- }
- if (this._ambigTime) {
- return momentFormat(this, 'YYYY-MM-DD');
- }
- if (this._ambigZone) {
- return momentFormat(this, 'YYYY-MM-DD[T]HH:mm:ss');
- }
- return momentFormat(this); // default moment original formatting
-};
+ // Builds an object with information on how to create a given view
+ buildViewSpec: function(requestedViewType) {
+ var viewOverrides = this.overrides.views || {};
+ var specChain = []; // for the view. lowest to highest priority
+ var defaultsChain = []; // for the view. lowest to highest priority
+ var overridesChain = []; // for the view. lowest to highest priority
+ var viewType = requestedViewType;
+ var spec; // for the view
+ var overrides; // for the view
+ var duration;
+ var unit;
+
+ // iterate from the specific view definition to a more general one until we hit an actual View class
+ while (viewType) {
+ spec = fcViews[viewType];
+ overrides = viewOverrides[viewType];
+ viewType = null; // clear. might repopulate for another iteration
+
+ if (typeof spec === 'function') { // TODO: deprecate
+ spec = { 'class': spec };
+ }
-FCMoment.prototype.toISOString = function() {
- if (this._ambigTime) {
- return momentFormat(this, 'YYYY-MM-DD');
- }
- if (this._ambigZone) {
- return momentFormat(this, 'YYYY-MM-DD[T]HH:mm:ss');
- }
- return moment.fn.toISOString.apply(this, arguments);
-};
+ if (spec) {
+ specChain.unshift(spec);
+ defaultsChain.unshift(spec.defaults || {});
+ duration = duration || spec.duration;
+ viewType = viewType || spec.type;
+ }
+ if (overrides) {
+ overridesChain.unshift(overrides); // view-specific option hashes have options at zero-level
+ duration = duration || overrides.duration;
+ viewType = viewType || overrides.type;
+ }
+ }
-// Querying
-// -------------------------------------------------------------------------------------------------
+ spec = mergeProps(specChain);
+ spec.type = requestedViewType;
+ if (!spec['class']) {
+ return false;
+ }
-// Is the moment within the specified range? `end` is exclusive.
-FCMoment.prototype.isWithin = function(start, end) {
- var a = commonlyAmbiguate([ this, start, end ]);
- return a[0] >= a[1] && a[0] < a[2];
-};
+ if (duration) {
+ duration = moment.duration(duration);
+ if (duration.valueOf()) { // valid?
+ spec.duration = duration;
+ unit = computeIntervalUnit(duration);
-// Make these query methods work with ambiguous moments
-$.each([
- 'isBefore',
- 'isAfter',
- 'isSame'
-], function(i, methodName) {
- FCMoment.prototype[methodName] = function(input, units) {
- var a = commonlyAmbiguate([ this, input ]);
- return moment.fn[methodName].call(a[0], a[1], units);
- };
-});
+ // view is a single-unit duration, like "week" or "day"
+ // incorporate options for this. lowest priority
+ if (duration.as(unit) === 1) {
+ spec.singleUnit = unit;
+ overridesChain.unshift(viewOverrides[unit] || {});
+ }
+ }
+ }
+ spec.defaults = mergeOptions(defaultsChain);
+ spec.overrides = mergeOptions(overridesChain);
-// Misc Internals
-// -------------------------------------------------------------------------------------------------
+ this.buildViewSpecOptions(spec);
+ this.buildViewSpecButtonText(spec, requestedViewType);
-// given an array of moment-like inputs, return a parallel array w/ moments similarly ambiguated.
-// for example, of one moment has ambig time, but not others, all moments will have their time stripped.
-function commonlyAmbiguate(inputs) {
- var outputs = [];
- var anyAmbigTime = false;
- var anyAmbigZone = false;
- var i;
+ return spec;
+ },
- for (i=0; i "MMMM D YYYY"
- formatStr = date1.lang().longDateFormat(formatStr) || formatStr;
- // BTW, this is not important for `formatDate` because it is impossible to put custom tokens
- // or non-zero areas in Moment's localized format strings.
+
+ // Exports
+ // -----------------------------------------------------------------------------------
- separator = separator || ' - ';
+ t.render = render;
+ t.destroy = destroy;
+ t.refetchEvents = refetchEvents;
+ t.reportEvents = reportEvents;
+ t.reportEventChange = reportEventChange;
+ t.rerenderEvents = renderEvents; // `renderEvents` serves as a rerender. an API method
+ t.changeView = renderView; // `renderView` will switch to another view
+ t.select = select;
+ t.unselect = unselect;
+ t.prev = prev;
+ t.next = next;
+ t.prevYear = prevYear;
+ t.nextYear = nextYear;
+ t.today = today;
+ t.gotoDate = gotoDate;
+ t.incrementDate = incrementDate;
+ t.zoomTo = zoomTo;
+ t.getDate = getDate;
+ t.getCalendar = getCalendar;
+ t.getView = getView;
+ t.option = option;
+ t.trigger = trigger;
- return formatRangeWithChunks(
- date1,
- date2,
- getFormatStringChunks(formatStr),
- separator,
- isRTL
- );
-}
-fc.formatRange = formatRange; // expose
-function formatRangeWithChunks(date1, date2, chunks, separator, isRTL) {
- var chunkStr; // the rendering of the chunk
- var leftI;
- var leftStr = '';
- var rightI;
- var rightStr = '';
- var middleI;
- var middleStr1 = '';
- var middleStr2 = '';
- var middleStr = '';
+ // Language-data Internals
+ // -----------------------------------------------------------------------------------
+ // Apply overrides to the current language's data
- // Start at the leftmost side of the formatting string and continue until you hit a token
- // that is not the same between dates.
- for (leftI=0; leftIleftI; rightI--) {
- chunkStr = formatSimilarChunk(date1, date2, chunks[rightI]);
- if (chunkStr === false) {
- break;
+ // assign a normalized value, to be used by our .week() moment extension
+ localeData._fullCalendar_weekCalc = (function(weekCalc) {
+ if (typeof weekCalc === 'function') {
+ return weekCalc;
}
- rightStr = chunkStr + rightStr;
- }
+ else if (weekCalc === 'local') {
+ return weekCalc;
+ }
+ else if (weekCalc === 'iso' || weekCalc === 'ISO') {
+ return 'ISO';
+ }
+ })(options.weekNumberCalculation);
- // The area in the middle is different for both of the dates.
- // Collect them distinctly so we can jam them together later.
- for (middleI=leftI; middleI<=rightI; middleI++) {
- middleStr1 += formatDateWithChunk(date1, chunks[middleI]);
- middleStr2 += formatDateWithChunk(date2, chunks[middleI]);
- }
- if (middleStr1 || middleStr2) {
- if (isRTL) {
- middleStr = middleStr2 + separator + middleStr1;
+
+ // Calendar-specific Date Utilities
+ // -----------------------------------------------------------------------------------
+
+
+ t.defaultAllDayEventDuration = moment.duration(options.defaultAllDayEventDuration);
+ t.defaultTimedEventDuration = moment.duration(options.defaultTimedEventDuration);
+
+
+ // Builds a moment using the settings of the current calendar: timezone and language.
+ // Accepts anything the vanilla moment() constructor accepts.
+ t.moment = function() {
+ var mom;
+
+ if (options.timezone === 'local') {
+ mom = fc.moment.apply(null, arguments);
+
+ // Force the moment to be local, because fc.moment doesn't guarantee it.
+ if (mom.hasTime()) { // don't give ambiguously-timed moments a local zone
+ mom.local();
+ }
+ }
+ else if (options.timezone === 'UTC') {
+ mom = fc.moment.utc.apply(null, arguments); // process as UTC
}
else {
- middleStr = middleStr1 + separator + middleStr2;
+ mom = fc.moment.parseZone.apply(null, arguments); // let the input decide the zone
}
- }
- return leftStr + middleStr + rightStr;
-}
+ if ('_locale' in mom) { // moment 2.8 and above
+ mom._locale = localeData;
+ }
+ else { // pre-moment-2.8
+ mom._lang = localeData;
+ }
+ return mom;
+ };
-var similarUnitMap = {
- Y: 'year',
- M: 'month',
- D: 'day', // day of month
- d: 'day', // day of week
- // prevents a separator between anything time-related...
- A: 'second', // AM/PM
- a: 'second', // am/pm
- T: 'second', // A/P
- t: 'second', // a/p
- H: 'second', // hour (24)
- h: 'second', // hour (12)
- m: 'second', // minute
- s: 'second' // second
-};
-// TODO: week maybe?
+ // Returns a boolean about whether or not the calendar knows how to calculate
+ // the timezone offset of arbitrary dates in the current timezone.
+ t.getIsAmbigTimezone = function() {
+ return options.timezone !== 'local' && options.timezone !== 'UTC';
+ };
-// Given a formatting chunk, and given that both dates are similar in the regard the
-// formatting chunk is concerned, format date1 against `chunk`. Otherwise, return `false`.
-function formatSimilarChunk(date1, date2, chunk) {
- var token;
- var unit;
- if (typeof chunk === 'string') { // a literal string
- return chunk;
- }
- else if ((token = chunk.token)) {
- unit = similarUnitMap[token.charAt(0)];
- // are the dates the same for this unit of measurement?
- if (unit && date1.isSame(date2, unit)) {
- return momentFormat(date1, token); // would be the same if we used `date2`
- // BTW, don't support custom tokens
+ // Returns a copy of the given date in the current timezone of it is ambiguously zoned.
+ // This will also give the date an unambiguous time.
+ t.rezoneDate = function(date) {
+ return t.moment(date.toArray());
+ };
+
+
+ // Returns a moment for the current date, as defined by the client's computer,
+ // or overridden by the `now` option.
+ t.getNow = function() {
+ var now = options.now;
+ if (typeof now === 'function') {
+ now = now();
}
- }
+ return t.moment(now);
+ };
+
+
+ // Get an event's normalized end date. If not present, calculate it from the defaults.
+ t.getEventEnd = function(event) {
+ if (event.end) {
+ return event.end.clone();
+ }
+ else {
+ return t.getDefaultEventEnd(event.allDay, event.start);
+ }
+ };
+
+
+ // Given an event's allDay status and start date, return swhat its fallback end date should be.
+ t.getDefaultEventEnd = function(allDay, start) { // TODO: rename to computeDefaultEventEnd
+ var end = start.clone();
- return false; // the chunk is NOT the same for the two dates
- // BTW, don't support splitting on non-zero areas
-}
+ if (allDay) {
+ end.stripTime().add(t.defaultAllDayEventDuration);
+ }
+ else {
+ end.add(t.defaultTimedEventDuration);
+ }
+ if (t.getIsAmbigTimezone()) {
+ end.stripZone(); // we don't know what the tzo should be
+ }
-// Chunking Utils
-// -------------------------------------------------------------------------------------------------
+ return end;
+ };
-var formatStringChunkCache = {};
+ // Produces a human-readable string for the given duration.
+ // Side-effect: changes the locale of the given duration.
+ t.humanizeDuration = function(duration) {
+ return (duration.locale || duration.lang).call(duration, options.lang) // works moment-pre-2.8
+ .humanize();
+ };
-function getFormatStringChunks(formatStr) {
- if (formatStr in formatStringChunkCache) {
- return formatStringChunkCache[formatStr];
- }
- return (formatStringChunkCache[formatStr] = chunkFormatString(formatStr));
-}
+
+ // Imports
+ // -----------------------------------------------------------------------------------
-// Break the formatting string into an array of chunks
-function chunkFormatString(formatStr) {
- var chunks = [];
- var chunker = /\[([^\]]*)\]|\(([^\)]*)\)|(LT|(\w)\4*o?)|([^\w\[\(]+)/g; // TODO: more descrimination
- var match;
+ EventManager.call(t, options);
+ var isFetchNeeded = t.isFetchNeeded;
+ var fetchEvents = t.fetchEvents;
- while ((match = chunker.exec(formatStr))) {
- if (match[1]) { // a literal string inside [ ... ]
- chunks.push(match[1]);
- }
- else if (match[2]) { // non-zero formatting inside ( ... )
- chunks.push({ maybe: chunkFormatString(match[2]) });
- }
- else if (match[3]) { // a formatting token
- chunks.push({ token: match[3] });
- }
- else if (match[5]) { // an unenclosed literal string
- chunks.push(match[5]);
- }
- }
- return chunks;
-}
-;;
+ // Locals
+ // -----------------------------------------------------------------------------------
-fcViews.month = MonthView;
-function MonthView(element, calendar) {
- var t = this;
-
+ var _element = element[0];
+ var header;
+ var headerElement;
+ var content;
+ var tm; // for making theme classes
+ var currentView; // NOTE: keep this in sync with this.view
+ var viewsByType = {}; // holds all instantiated view instances, current or not
+ var suggestedViewHeight;
+ var windowResizeProxy; // wraps the windowResize function
+ var ignoreWindowResize = 0;
+ var date;
+ var events = [];
- // exports
- t.incrementDate = incrementDate;
- t.render = render;
- // imports
- BasicView.call(t, element, calendar, 'month');
+ // Main Rendering
+ // -----------------------------------------------------------------------------------
- function incrementDate(date, delta) {
- return date.clone().stripTime().add('months', delta).startOf('month');
+ if (options.defaultDate != null) {
+ date = t.moment(options.defaultDate);
}
+ else {
+ date = t.getNow();
+ }
+
+
+ function render() {
+ if (!content) {
+ initialRender();
+ }
+ else if (elementVisible()) {
+ // mainly for the public API
+ calcSize();
+ renderView();
+ }
+ }
+
+
+ function initialRender() {
+ tm = options.theme ? 'ui' : 'fc';
+ element.addClass('fc');
+ if (options.isRTL) {
+ element.addClass('fc-rtl');
+ }
+ else {
+ element.addClass('fc-ltr');
+ }
- function render(date) {
-
- t.intervalStart = date.clone().stripTime().startOf('month');
- t.intervalEnd = t.intervalStart.clone().add('months', 1);
-
- t.start = t.intervalStart.clone();
- t.start = t.skipHiddenDays(t.start); // move past the first week if no visible days
- t.start.startOf('week');
- t.start = t.skipHiddenDays(t.start); // move past the first invisible days of the week
+ if (options.theme) {
+ element.addClass('ui-widget');
+ }
+ else {
+ element.addClass('fc-unthemed');
+ }
- t.end = t.intervalEnd.clone();
- t.end = t.skipHiddenDays(t.end, -1, true); // move in from the last week if no visible days
- t.end.add('days', (7 - t.end.weekday()) % 7); // move to end of week if not already
- t.end = t.skipHiddenDays(t.end, -1, true); // move in from the last invisible days of the week
+ content = $("").prependTo(element);
- var rowCnt = Math.ceil( // need to ceil in case there are hidden days
- t.end.diff(t.start, 'weeks', true) // returnfloat=true
- );
- if (t.opt('weekMode') == 'fixed') {
- t.end.add('weeks', 6 - rowCnt);
- rowCnt = 6;
+ header = t.header = new Header(t, options);
+ headerElement = header.render();
+ if (headerElement) {
+ element.prepend(headerElement);
}
- t.title = calendar.formatDate(t.intervalStart, t.opt('titleFormat'));
+ renderView(options.defaultView);
- t.renderBasic(rowCnt, t.getCellsPerWeek(), true);
+ if (options.handleWindowResize) {
+ windowResizeProxy = debounce(windowResize, options.windowResizeDelay); // prevents rapid calls
+ $(window).resize(windowResizeProxy);
+ }
}
-}
+ function destroy() {
-;;
+ if (currentView) {
+ currentView.removeElement();
-fcViews.basicWeek = BasicWeekView;
+ // NOTE: don't null-out currentView/t.view in case API methods are called after destroy.
+ // It is still the "current" view, just not rendered.
+ }
-function BasicWeekView(element, calendar) { // TODO: do a WeekView mixin
- var t = this;
+ header.removeElement();
+ content.remove();
+ element.removeClass('fc fc-ltr fc-rtl fc-unthemed ui-widget');
+
+ if (windowResizeProxy) {
+ $(window).unbind('resize', windowResizeProxy);
+ }
+ }
- // exports
- t.incrementDate = incrementDate;
- t.render = render;
+ function elementVisible() {
+ return element.is(':visible');
+ }
- // imports
- BasicView.call(t, element, calendar, 'basicWeek');
+ // View Rendering
+ // -----------------------------------------------------------------------------------
- function incrementDate(date, delta) {
- return date.clone().stripTime().add('weeks', delta).startOf('week');
- }
+ // Renders a view because of a date change, view-type change, or for the first time.
+ // If not given a viewType, keep the current view but render different dates.
+ function renderView(viewType) {
+ ignoreWindowResize++;
- function render(date) {
+ // if viewType is changing, remove the old view's rendering
+ if (currentView && viewType && currentView.type !== viewType) {
+ header.deactivateButton(currentView.type);
+ freezeContentHeight(); // prevent a scroll jump when view element is removed
+ currentView.removeElement();
+ currentView = t.view = null;
+ }
- t.intervalStart = date.clone().stripTime().startOf('week');
- t.intervalEnd = t.intervalStart.clone().add('weeks', 1);
+ // if viewType changed, or the view was never created, create a fresh view
+ if (!currentView && viewType) {
+ currentView = t.view =
+ viewsByType[viewType] ||
+ (viewsByType[viewType] = t.instantiateView(viewType));
- t.start = t.skipHiddenDays(t.intervalStart);
- t.end = t.skipHiddenDays(t.intervalEnd, -1, true);
+ currentView.setElement(
+ $("").appendTo(content)
+ );
+ header.activateButton(viewType);
+ }
- t.title = calendar.formatRange(
- t.start,
- t.end.clone().subtract(1), // make inclusive by subtracting 1 ms
- t.opt('titleFormat'),
- ' \u2014 ' // emphasized dash
- );
+ if (currentView) {
- t.renderBasic(1, t.getCellsPerWeek(), false);
- }
-
-
-}
+ // in case the view should render a period of time that is completely hidden
+ date = currentView.massageCurrentDate(date);
-;;
+ // render or rerender the view
+ if (
+ !currentView.displaying ||
+ !date.isWithin(currentView.intervalStart, currentView.intervalEnd) // implicit date window change
+ ) {
+ if (elementVisible()) {
-fcViews.basicDay = BasicDayView;
+ freezeContentHeight();
+ currentView.display(date);
+ unfreezeContentHeight(); // immediately unfreeze regardless of whether display is async
-function BasicDayView(element, calendar) { // TODO: make a DayView mixin
- var t = this;
-
-
- // exports
- t.incrementDate = incrementDate;
- t.render = render;
-
-
- // imports
- BasicView.call(t, element, calendar, 'basicDay');
+ // need to do this after View::render, so dates are calculated
+ updateHeaderTitle();
+ updateTodayButton();
+ getAndRenderEvents();
+ }
+ }
+ }
- function incrementDate(date, delta) {
- var out = date.clone().stripTime().add('days', delta);
- out = t.skipHiddenDays(out, delta < 0 ? -1 : 1);
- return out;
+ unfreezeContentHeight(); // undo any lone freezeContentHeight calls
+ ignoreWindowResize--;
}
+
+
+ // Resizing
+ // -----------------------------------------------------------------------------------
- function render(date) {
- t.start = t.intervalStart = date.clone().stripTime();
- t.end = t.intervalEnd = t.start.clone().add('days', 1);
+ t.getSuggestedViewHeight = function() {
+ if (suggestedViewHeight === undefined) {
+ calcSize();
+ }
+ return suggestedViewHeight;
+ };
- t.title = calendar.formatDate(t.start, t.opt('titleFormat'));
- t.renderBasic(1, 1, false);
- }
+ t.isHeightAuto = function() {
+ return options.contentHeight === 'auto' || options.height === 'auto';
+ };
-}
+ function updateSize(shouldRecalc) {
+ if (elementVisible()) {
-;;
+ if (shouldRecalc) {
+ _calcSize();
+ }
-setDefaults({
- weekMode: 'fixed'
-});
+ ignoreWindowResize++;
+ currentView.updateSize(true); // isResize=true. will poll getSuggestedViewHeight() and isHeightAuto()
+ ignoreWindowResize--;
+ return true; // signal success
+ }
+ }
-function BasicView(element, calendar, viewName) {
- var t = this;
-
-
- // exports
- t.renderBasic = renderBasic;
- t.setHeight = setHeight;
- t.setWidth = setWidth;
- t.renderDayOverlay = renderDayOverlay;
- t.defaultSelectionEnd = defaultSelectionEnd;
- t.renderSelection = renderSelection;
- t.clearSelection = clearSelection;
- t.reportDayClick = reportDayClick; // for selection (kinda hacky)
- t.dragStart = dragStart;
- t.dragStop = dragStop;
- t.getHoverListener = function() { return hoverListener; };
- t.colLeft = colLeft;
- t.colRight = colRight;
- t.colContentLeft = colContentLeft;
- t.colContentRight = colContentRight;
- t.getIsCellAllDay = function() { return true; };
- t.allDayRow = allDayRow;
- t.getRowCnt = function() { return rowCnt; };
- t.getColCnt = function() { return colCnt; };
- t.getColWidth = function() { return colWidth; };
- t.getDaySegmentContainer = function() { return daySegmentContainer; };
-
-
- // imports
- View.call(t, element, calendar, viewName);
- OverlayManager.call(t);
- SelectionManager.call(t);
- BasicEventRenderer.call(t);
- var opt = t.opt;
- var trigger = t.trigger;
- var renderOverlay = t.renderOverlay;
- var clearOverlays = t.clearOverlays;
- var daySelectionMousedown = t.daySelectionMousedown;
- var cellToDate = t.cellToDate;
- var dateToCell = t.dateToCell;
- var rangeToSegments = t.rangeToSegments;
- var formatDate = calendar.formatDate;
- var calculateWeekNumber = calendar.calculateWeekNumber;
-
-
- // locals
-
- var table;
- var head;
- var headCells;
- var body;
- var bodyRows;
- var bodyCells;
- var bodyFirstCells;
- var firstRowCellInners;
- var firstRowCellContentInners;
- var daySegmentContainer;
-
- var viewWidth;
- var viewHeight;
- var colWidth;
- var weekNumberWidth;
-
- var rowCnt, colCnt;
- var showNumbers;
- var coordinateGrid;
- var hoverListener;
- var colPositions;
- var colContentPositions;
-
- var tm;
- var colFormat;
- var showWeekNumbers;
-
-
-
- /* Rendering
- ------------------------------------------------------------*/
-
-
- disableTextSelection(element.addClass('fc-grid'));
-
-
- function renderBasic(_rowCnt, _colCnt, _showNumbers) {
- rowCnt = _rowCnt;
- colCnt = _colCnt;
- showNumbers = _showNumbers;
- updateOptions();
- if (!body) {
- buildEventContainer();
+ function calcSize() {
+ if (elementVisible()) {
+ _calcSize();
}
-
- buildTable();
}
- function updateOptions() {
- tm = opt('theme') ? 'ui' : 'fc';
- colFormat = opt('columnFormat');
- showWeekNumbers = opt('weekNumbers');
+ function _calcSize() { // assumes elementVisible
+ if (typeof options.contentHeight === 'number') { // exists and not 'auto'
+ suggestedViewHeight = options.contentHeight;
+ }
+ else if (typeof options.height === 'number') { // exists and not 'auto'
+ suggestedViewHeight = options.height - (headerElement ? headerElement.outerHeight(true) : 0);
+ }
+ else {
+ suggestedViewHeight = Math.round(content.width() / Math.max(options.aspectRatio, .5));
+ }
}
- function buildEventContainer() {
- daySegmentContainer =
- $("")
- .appendTo(element);
+ function windowResize(ev) {
+ if (
+ !ignoreWindowResize &&
+ ev.target === window && // so we don't process jqui "resize" events that have bubbled up
+ currentView.start // view has already been rendered
+ ) {
+ if (updateSize(true)) {
+ currentView.trigger('windowResize', _element);
+ }
+ }
}
- function buildTable() {
- var html = buildTableHTML();
-
- if (table) {
- table.remove();
- }
- table = $(html).appendTo(element);
-
- head = table.find('thead');
- headCells = head.find('.fc-day-header');
- body = table.find('tbody');
- bodyRows = body.find('tr');
- bodyCells = body.find('.fc-day');
- bodyFirstCells = bodyRows.find('td:first-child');
+
+ /* Event Fetching/Rendering
+ -----------------------------------------------------------------------------*/
+ // TODO: going forward, most of this stuff should be directly handled by the view
- firstRowCellInners = bodyRows.eq(0).find('.fc-day > div');
- firstRowCellContentInners = bodyRows.eq(0).find('.fc-day-content > div');
-
- markFirstLast(head.add(head.find('tr'))); // marks first+last tr/th's
- markFirstLast(bodyRows); // marks first+last td's
- bodyRows.eq(0).addClass('fc-first');
- bodyRows.filter(':last').addClass('fc-last');
-
- bodyCells.each(function(i, _cell) {
- var date = cellToDate(
- Math.floor(i / colCnt),
- i % colCnt
- );
- trigger('dayRender', t, date, $(_cell));
- });
- dayBind(bodyCells);
+ function refetchEvents() { // can be called as an API method
+ destroyEvents(); // so that events are cleared before user starts waiting for AJAX
+ fetchAndRenderEvents();
}
-
- /* HTML Building
- -----------------------------------------------------------*/
-
-
- function buildTableHTML() {
- var html =
- "" +
- buildHeadHTML() +
- buildBodyHTML() +
- " ";
-
- return html;
+ function renderEvents() { // destroys old events if previously rendered
+ if (elementVisible()) {
+ freezeContentHeight();
+ currentView.displayEvents(events);
+ unfreezeContentHeight();
+ }
}
- function buildHeadHTML() {
- var headerClass = tm + "-widget-header";
- var html = '';
- var col;
- var date;
-
- html += "";
+ function destroyEvents() {
+ freezeContentHeight();
+ currentView.clearEvents();
+ unfreezeContentHeight();
+ }
+
- if (showWeekNumbers) {
- html +=
- "";
+ function getAndRenderEvents() {
+ if (!options.lazyFetching || isFetchNeeded(currentView.start, currentView.end)) {
+ fetchAndRenderEvents();
}
-
- for (col=0; col ";
-
- return html;
}
- function buildBodyHTML() {
- var contentClass = tm + "-widget-content";
- var html = '';
- var row;
- var col;
- var date;
+ function fetchAndRenderEvents() {
+ fetchEvents(currentView.start, currentView.end);
+ // ... will call reportEvents
+ // ... which will call renderEvents
+ }
- html += "";
+
+ // called when event data arrives
+ function reportEvents(_events) {
+ events = _events;
+ renderEvents();
+ }
- for (row=0; row";
+ // called when a single event's data has been changed
+ function reportEventChange() {
+ renderEvents();
+ }
- if (showWeekNumbers) {
- date = cellToDate(row, 0);
- html +=
- "" +
- " " +
- htmlEscape(calculateWeekNumber(date)) +
- " " +
- " | ";
- }
- for (col=0; col";
- }
+ /* Header Updating
+ -----------------------------------------------------------------------------*/
- html += "";
- return html;
+ function updateHeaderTitle() {
+ header.updateTitle(currentView.title);
}
- function buildCellHTML(date) { // date assumed to have stripped time
- var month = t.intervalStart.month();
- var today = calendar.getNow().stripTime();
- var html = '';
- var contentClass = tm + "-widget-content";
- var classNames = [
- 'fc-day',
- 'fc-' + dayIDs[date.day()],
- contentClass
- ];
-
- if (date.month() != month) {
- classNames.push('fc-other-month');
- }
- if (date.isSame(today, 'day')) {
- classNames.push(
- 'fc-today',
- tm + '-state-highlight'
- );
- }
- else if (date < today) {
- classNames.push('fc-past');
+ function updateTodayButton() {
+ var now = t.getNow();
+ if (now.isWithin(currentView.intervalStart, currentView.intervalEnd)) {
+ header.disableButton('today');
}
else {
- classNames.push('fc-future');
- }
-
- html +=
- "" +
- "";
-
- if (showNumbers) {
- html += " " + date.date() + " ";
+ header.enableButton('today');
}
-
- html +=
- " " +
- " " +
- " | ";
-
- return html;
}
+
-
- /* Dimensions
- -----------------------------------------------------------*/
-
+ /* Selection
+ -----------------------------------------------------------------------------*/
- function setHeight(height) {
- viewHeight = height;
-
- var bodyHeight = Math.max(viewHeight - head.height(), 0);
- var rowHeight;
- var rowHeightLast;
- var cell;
-
- if (opt('weekMode') == 'variable') {
- rowHeight = rowHeightLast = Math.floor(bodyHeight / (rowCnt==1 ? 2 : 6));
- }else{
- rowHeight = Math.floor(bodyHeight / rowCnt);
- rowHeightLast = bodyHeight - rowHeight * (rowCnt-1);
- }
-
- bodyFirstCells.each(function(i, _cell) {
- if (i < rowCnt) {
- cell = $(_cell);
- cell.find('> div').css(
- 'min-height',
- (i==rowCnt-1 ? rowHeightLast : rowHeight) - vsides(cell)
- );
- }
- });
-
+
+ function select(start, end) {
+ currentView.select(
+ t.buildSelectRange.apply(t, arguments)
+ );
}
-
- function setWidth(width) {
- viewWidth = width;
- colPositions.clear();
- colContentPositions.clear();
- weekNumberWidth = 0;
- if (showWeekNumbers) {
- weekNumberWidth = head.find('th.fc-week-number').outerWidth();
+ function unselect() { // safe to be called before renderView
+ if (currentView) {
+ currentView.unselect();
}
-
- colWidth = Math.floor((viewWidth - weekNumberWidth) / colCnt);
- setOuterWidth(headCells, colWidth);
}
- /* Day clicking and binding
- -----------------------------------------------------------*/
+ /* Date
+ -----------------------------------------------------------------------------*/
- function dayBind(days) {
- days.click(dayClick)
- .mousedown(daySelectionMousedown);
+ function prev() {
+ date = currentView.computePrevDate(date);
+ renderView();
}
- function dayClick(ev) {
- if (!opt('selectable')) { // if selectable, SelectionManager will worry about dayClick
- var date = calendar.moment($(this).data('date'));
- trigger('dayClick', this, date, ev);
- }
+ function next() {
+ date = currentView.computeNextDate(date);
+ renderView();
}
-
- /* Semi-transparent Overlay Helpers
- ------------------------------------------------------*/
- // TODO: should be consolidated with AgendaView's methods
-
-
- function renderDayOverlay(overlayStart, overlayEnd, refreshCoordinateGrid) { // overlayEnd is exclusive
-
- if (refreshCoordinateGrid) {
- coordinateGrid.build();
- }
-
- var segments = rangeToSegments(overlayStart, overlayEnd);
-
- for (var i=0; i "September 2014"
+ monthYearFormat: function(dpOptions) {
+ return dpOptions.showMonthAfterYear ?
+ 'YYYY[' + dpOptions.yearSuffix + '] MMMM' :
+ 'MMMM YYYY[' + dpOptions.yearSuffix + ']';
+ }
-// TODO: make it work in quirks mode (event corners, all-day height)
-// TODO: test liquid width, especially in IE6
+};
+var momComputableOptions = {
-function AgendaView(element, calendar, viewName) {
- var t = this;
-
-
- // exports
- t.renderAgenda = renderAgenda;
- t.setWidth = setWidth;
- t.setHeight = setHeight;
- t.afterRender = afterRender;
- t.computeDateTop = computeDateTop;
- t.getIsCellAllDay = getIsCellAllDay;
- t.allDayRow = function() { return allDayRow; }; // badly named
- t.getCoordinateGrid = function() { return coordinateGrid; }; // specifically for AgendaEventRenderer
- t.getHoverListener = function() { return hoverListener; };
- t.colLeft = colLeft;
- t.colRight = colRight;
- t.colContentLeft = colContentLeft;
- t.colContentRight = colContentRight;
- t.getDaySegmentContainer = function() { return daySegmentContainer; };
- t.getSlotSegmentContainer = function() { return slotSegmentContainer; };
- t.getSlotContainer = function() { return slotContainer; };
- t.getRowCnt = function() { return 1; };
- t.getColCnt = function() { return colCnt; };
- t.getColWidth = function() { return colWidth; };
- t.getSnapHeight = function() { return snapHeight; };
- t.getSnapDuration = function() { return snapDuration; };
- t.getSlotHeight = function() { return slotHeight; };
- t.getSlotDuration = function() { return slotDuration; };
- t.getMinTime = function() { return minTime; };
- t.getMaxTime = function() { return maxTime; };
- t.defaultSelectionEnd = defaultSelectionEnd;
- t.renderDayOverlay = renderDayOverlay;
- t.renderSelection = renderSelection;
- t.clearSelection = clearSelection;
- t.reportDayClick = reportDayClick; // selection mousedown hack
- t.dragStart = dragStart;
- t.dragStop = dragStop;
-
-
- // imports
- View.call(t, element, calendar, viewName);
- OverlayManager.call(t);
- SelectionManager.call(t);
- AgendaEventRenderer.call(t);
- var opt = t.opt;
- var trigger = t.trigger;
- var renderOverlay = t.renderOverlay;
- var clearOverlays = t.clearOverlays;
- var reportSelection = t.reportSelection;
- var unselect = t.unselect;
- var daySelectionMousedown = t.daySelectionMousedown;
- var slotSegHtml = t.slotSegHtml;
- var cellToDate = t.cellToDate;
- var dateToCell = t.dateToCell;
- var rangeToSegments = t.rangeToSegments;
- var formatDate = calendar.formatDate;
- var calculateWeekNumber = calendar.calculateWeekNumber;
-
-
- // locals
-
- var dayTable;
- var dayHead;
- var dayHeadCells;
- var dayBody;
- var dayBodyCells;
- var dayBodyCellInners;
- var dayBodyCellContentInners;
- var dayBodyFirstCell;
- var dayBodyFirstCellStretcher;
- var slotLayer;
- var daySegmentContainer;
- var allDayTable;
- var allDayRow;
- var slotScroller;
- var slotContainer;
- var slotSegmentContainer;
- var slotTable;
- var selectionHelper;
-
- var viewWidth;
- var viewHeight;
- var axisWidth;
- var colWidth;
- var gutterWidth;
-
- var slotDuration;
- var slotHeight; // TODO: what if slotHeight changes? (see issue 650)
-
- var snapDuration;
- var snapRatio; // ratio of number of "selection" slots to normal slots. (ex: 1, 2, 4)
- var snapHeight; // holds the pixel hight of a "selection" slot
-
- var colCnt;
- var slotCnt;
- var coordinateGrid;
- var hoverListener;
- var colPositions;
- var colContentPositions;
- var slotTopCache = {};
-
- var tm;
- var rtl;
- var minTime;
- var maxTime;
- var colFormat;
-
+ // Produces format strings like "ddd M/D" -> "Fri 9/15"
+ dayOfMonthFormat: function(momOptions, fcOptions) {
+ var format = momOptions.longDateFormat('l'); // for the format like "M/D/YYYY"
-
- /* Rendering
- -----------------------------------------------------------------------------*/
-
-
- disableTextSelection(element.addClass('fc-agenda'));
-
-
- function renderAgenda(c) {
- colCnt = c;
- updateOptions();
+ // strip the year off the edge, as well as other misc non-whitespace chars
+ format = format.replace(/^Y+[^\w\s]*|[^\w\s]*Y+$/g, '');
- if (!dayTable) { // first time rendering?
- buildSkeleton(); // builds day table, slot area, events containers
+ if (fcOptions.isRTL) {
+ format += ' ddd'; // for RTL, add day-of-week to end
}
else {
- buildDayTable(); // rebuilds day table
+ format = 'ddd ' + format; // for LTR, add day-of-week to beginning
}
- }
-
-
- function updateOptions() {
+ return format;
+ },
- tm = opt('theme') ? 'ui' : 'fc';
- rtl = opt('isRTL');
- colFormat = opt('columnFormat');
+ // Produces format strings like "h:mma" -> "6:00pm"
+ mediumTimeFormat: function(momOptions) { // can't be called `timeFormat` because collides with option
+ return momOptions.longDateFormat('LT')
+ .replace(/\s*a$/i, 'a'); // convert AM/PM/am/pm to lowercase. remove any spaces beforehand
+ },
- minTime = moment.duration(opt('minTime'));
- maxTime = moment.duration(opt('maxTime'));
+ // Produces format strings like "h(:mm)a" -> "6pm" / "6:30pm"
+ smallTimeFormat: function(momOptions) {
+ return momOptions.longDateFormat('LT')
+ .replace(':mm', '(:mm)')
+ .replace(/(\Wmm)$/, '($1)') // like above, but for foreign langs
+ .replace(/\s*a$/i, 'a'); // convert AM/PM/am/pm to lowercase. remove any spaces beforehand
+ },
- slotDuration = moment.duration(opt('slotDuration'));
- snapDuration = opt('snapDuration');
- snapDuration = snapDuration ? moment.duration(snapDuration) : slotDuration;
- }
+ // Produces format strings like "h(:mm)t" -> "6p" / "6:30p"
+ extraSmallTimeFormat: function(momOptions) {
+ return momOptions.longDateFormat('LT')
+ .replace(':mm', '(:mm)')
+ .replace(/(\Wmm)$/, '($1)') // like above, but for foreign langs
+ .replace(/\s*a$/i, 't'); // convert to AM/PM/am/pm to lowercase one-letter. remove any spaces beforehand
+ },
+
+ // Produces format strings like "ha" / "H" -> "6pm" / "18"
+ hourFormat: function(momOptions) {
+ return momOptions.longDateFormat('LT')
+ .replace(':mm', '')
+ .replace(/(\Wmm)$/, '') // like above, but for foreign langs
+ .replace(/\s*a$/i, 'a'); // convert AM/PM/am/pm to lowercase. remove any spaces beforehand
+ },
+ // Produces format strings like "h:mm" -> "6:30" (with no AM/PM)
+ noMeridiemTimeFormat: function(momOptions) {
+ return momOptions.longDateFormat('LT')
+ .replace(/\s*a$/i, ''); // remove trailing AM/PM
+ }
+};
- /* Build DOM
- -----------------------------------------------------------------------*/
+// options that should be computed off live calendar options (considers override options)
+var instanceComputableOptions = { // TODO: best place for this? related to lang?
- function buildSkeleton() {
- var s;
- var headerClass = tm + "-widget-header";
- var contentClass = tm + "-widget-content";
- var slotTime;
- var slotDate;
- var minutes;
- var slotNormal = slotDuration.asMinutes() % 15 === 0;
-
- buildDayTable();
-
- slotLayer =
- $("")
- .appendTo(element);
-
- if (opt('allDaySlot')) {
-
- daySegmentContainer =
- $("")
- .appendTo(slotLayer);
-
- s =
- "" +
- "" +
- "" +
- "" +
- "" +
- " | " +
- "" +
- " " +
- " ";
- allDayTable = $(s).appendTo(slotLayer);
- allDayRow = allDayTable.find('tr');
-
- dayBind(allDayRow.find('td'));
-
- slotLayer.append(
- ""
- );
-
- }else{
-
- daySegmentContainer = $([]); // in jQuery 1.4, we can just do $()
-
- }
-
- slotScroller =
- $("")
- .appendTo(slotLayer);
-
- slotContainer =
- $("")
- .appendTo(slotScroller);
-
- slotSegmentContainer =
- $("")
- .appendTo(slotContainer);
-
- s =
- "" +
- "";
-
- slotTime = moment.duration(+minTime); // i wish there was .clone() for durations
- slotCnt = 0;
- while (slotTime < maxTime) {
- slotDate = t.start.clone().time(slotTime); // will be in UTC but that's good. to avoid DST issues
- minutes = slotDate.minutes();
- s +=
- "" +
- "" +
- "" +
- " " +
- " | " +
- " ";
- slotTime.add(slotDuration);
- slotCnt++;
- }
+ // Produces format strings for results like "Mo 16"
+ smallDayDateFormat: function(options) {
+ return options.isRTL ?
+ 'D dd' :
+ 'dd D';
+ },
- s +=
- "" +
- " ";
+ // Produces format strings for results like "Wk 5"
+ weekFormat: function(options) {
+ return options.isRTL ?
+ 'w[ ' + options.weekNumberTitle + ']' :
+ '[' + options.weekNumberTitle + ' ]w';
+ },
- slotTable = $(s).appendTo(slotContainer);
-
- slotBind(slotTable.find('td'));
+ // Produces format strings for results like "Wk5"
+ smallWeekFormat: function(options) {
+ return options.isRTL ?
+ 'w[' + options.weekNumberTitle + ']' :
+ '[' + options.weekNumberTitle + ']w';
}
+};
+function populateInstanceComputableOptions(options) {
+ $.each(instanceComputableOptions, function(name, func) {
+ if (options[name] == null) {
+ options[name] = func(options);
+ }
+ });
+}
- /* Build Day Table
- -----------------------------------------------------------------------*/
+// Returns moment's internal locale data. If doesn't exist, returns English.
+// Works with moment-pre-2.8
+function getMomentLocaleData(langCode) {
+ var func = moment.localeData || moment.langData;
+ return func.call(moment, langCode) ||
+ func.call(moment, 'en'); // the newer localData could return null, so fall back to en
+}
- function buildDayTable() {
- var html = buildDayTableHTML();
- if (dayTable) {
- dayTable.remove();
- }
- dayTable = $(html).appendTo(element);
+// Initialize English by forcing computation of moment-derived options.
+// Also, sets it as the default.
+fc.lang('en', Calendar.englishDefaults);
- dayHead = dayTable.find('thead');
- dayHeadCells = dayHead.find('th').slice(1, -1); // exclude gutter
- dayBody = dayTable.find('tbody');
- dayBodyCells = dayBody.find('td').slice(0, -1); // exclude gutter
- dayBodyCellInners = dayBodyCells.find('> div');
- dayBodyCellContentInners = dayBodyCells.find('.fc-day-content > div');
+;;
- dayBodyFirstCell = dayBodyCells.eq(0);
- dayBodyFirstCellStretcher = dayBodyCellInners.eq(0);
-
- markFirstLast(dayHead.add(dayHead.find('tr')));
- markFirstLast(dayBody.add(dayBody.find('tr')));
+/* Top toolbar area with buttons and title
+----------------------------------------------------------------------------------------------------------------------*/
+// TODO: rename all header-related things to "toolbar"
- // TODO: now that we rebuild the cells every time, we should call dayRender
- }
+function Header(calendar, options) {
+ var t = this;
+
+ // exports
+ t.render = render;
+ t.removeElement = removeElement;
+ t.updateTitle = updateTitle;
+ t.activateButton = activateButton;
+ t.deactivateButton = deactivateButton;
+ t.disableButton = disableButton;
+ t.enableButton = enableButton;
+ t.getViewsWithButtons = getViewsWithButtons;
+
+ // locals
+ var el = $();
+ var viewsWithButtons = [];
+ var tm;
- function buildDayTableHTML() {
- var html =
- "" +
- buildDayTableHeadHTML() +
- buildDayTableBodyHTML() +
- " ";
+ function render() {
+ var sections = options.header;
- return html;
- }
+ tm = options.theme ? 'ui' : 'fc';
+ if (sections) {
+ el = $("")
+ .append(renderSection('left'))
+ .append(renderSection('right'))
+ .append(renderSection('center'))
+ .append('');
- function buildDayTableHeadHTML() {
- var headerClass = tm + "-widget-header";
- var date;
- var html = '';
- var weekText;
- var col;
+ return el;
+ }
+ }
+
+
+ function removeElement() {
+ el.remove();
+ el = $();
+ }
+
+
+ function renderSection(position) {
+ var sectionEl = $('');
+ var buttonStr = options.header[position];
- html +=
- "" +
- "";
+ if (buttonStr) {
+ $.each(buttonStr.split(' '), function(i) {
+ var groupChildren = $();
+ var isOnlyButtons = true;
+ var groupEl;
- if (opt('weekNumbers')) {
- date = cellToDate(0, 0);
- weekText = calculateWeekNumber(date);
- if (rtl) {
- weekText += opt('weekNumberTitle');
- }
- else {
- weekText = opt('weekNumberTitle') + weekText;
- }
- html +=
- "";
- }
- else {
- html += "";
- }
+ $.each(this.split(','), function(j, buttonName) {
+ var viewSpec;
+ var buttonClick;
+ var overrideText; // text explicitly set by calendar's constructor options. overcomes icons
+ var defaultText;
+ var themeIcon;
+ var normalIcon;
+ var innerHtml;
+ var classes;
+ var button;
- for (col=0; col" +
- htmlEscape(formatDate(date, colFormat)) +
- "";
- }
+ if (buttonName == 'title') {
+ groupChildren = groupChildren.add($(' ')); // we always want it to take up height
+ isOnlyButtons = false;
+ }
+ else {
+ viewSpec = calendar.getViewSpec(buttonName);
- html +=
- "" +
- " " +
- "";
+ if (viewSpec) {
+ buttonClick = function() {
+ calendar.changeView(buttonName);
+ };
+ viewsWithButtons.push(buttonName);
+ overrideText = viewSpec.buttonTextOverride;
+ defaultText = viewSpec.buttonTextDefault;
+ }
+ else if (calendar[buttonName]) { // a calendar method
+ buttonClick = function() {
+ calendar[buttonName]();
+ };
+ overrideText = (calendar.overrides.buttonText || {})[buttonName];
+ defaultText = options.buttonText[buttonName]; // everything else is considered default
+ }
- return html;
- }
+ if (buttonClick) {
+ themeIcon = options.themeButtonIcons[buttonName];
+ normalIcon = options.buttonIcons[buttonName];
- function buildDayTableBodyHTML() {
- var headerClass = tm + "-widget-header"; // TODO: make these when updateOptions() called
- var contentClass = tm + "-widget-content";
- var date;
- var today = calendar.getNow().stripTime();
- var col;
- var cellsHTML;
- var cellHTML;
- var classNames;
- var html = '';
+ if (overrideText) {
+ innerHtml = htmlEscape(overrideText);
+ }
+ else if (themeIcon && options.theme) {
+ innerHtml = "";
+ }
+ else if (normalIcon && !options.theme) {
+ innerHtml = "";
+ }
+ else {
+ innerHtml = htmlEscape(defaultText);
+ }
- html +=
- "" +
- "" +
- "";
+ classes = [
+ 'fc-' + buttonName + '-button',
+ tm + '-button',
+ tm + '-state-default'
+ ];
- cellsHTML = '';
+ button = $( // type="button" so that it doesn't submit a form
+ ''
+ )
+ .click(function() {
+ // don't process clicks for disabled buttons
+ if (!button.hasClass(tm + '-state-disabled')) {
- for (col=0; col" +
- "" +
- "";
+ if (isOnlyButtons) {
+ groupChildren
+ .first().addClass(tm + '-corner-left').end()
+ .last().addClass(tm + '-corner-right').end();
+ }
- cellsHTML += cellHTML;
+ if (groupChildren.length > 1) {
+ groupEl = $('');
+ if (isOnlyButtons) {
+ groupEl.addClass('fc-button-group');
+ }
+ groupEl.append(groupChildren);
+ sectionEl.append(groupEl);
+ }
+ else {
+ sectionEl.append(groupChildren); // 1 or 0 children
+ }
+ });
}
- html += cellsHTML;
- html +=
- " | " +
- " " +
- "";
-
- return html;
+ return sectionEl;
}
-
-
- // TODO: data-date on the cells
-
- /* Dimensions
- -----------------------------------------------------------------------*/
-
+ function updateTitle(text) {
+ el.find('h2').text(text);
+ }
- function setHeight(height) {
- if (height === undefined) {
- height = viewHeight;
- }
- viewHeight = height;
- slotTopCache = {};
- var headHeight = dayBody.position().top;
- var allDayHeight = slotScroller.position().top; // including divider
- var bodyHeight = Math.min( // total body height, including borders
- height - headHeight, // when scrollbars
- slotTable.height() + allDayHeight + 1 // when no scrollbars. +1 for bottom border
- );
-
- dayBodyFirstCellStretcher
- .height(bodyHeight - vsides(dayBodyFirstCell));
-
- slotLayer.css('top', headHeight);
-
- slotScroller.height(bodyHeight - allDayHeight - 1);
-
- // the stylesheet guarantees that the first row has no border.
- // this allows .height() to work well cross-browser.
- var slotHeight0 = slotTable.find('tr:first').height() + 1; // +1 for bottom border
- var slotHeight1 = slotTable.find('tr:eq(1)').height();
- // HACK: i forget why we do this, but i think a cross-browser issue
- slotHeight = (slotHeight0 + slotHeight1) / 2;
-
- snapRatio = slotDuration / snapDuration;
- snapHeight = slotHeight / snapRatio;
+ function activateButton(buttonName) {
+ el.find('.fc-' + buttonName + '-button')
+ .addClass(tm + '-state-active');
+ }
+
+
+ function deactivateButton(buttonName) {
+ el.find('.fc-' + buttonName + '-button')
+ .removeClass(tm + '-state-active');
}
- function setWidth(width) {
- viewWidth = width;
- colPositions.clear();
- colContentPositions.clear();
-
- var axisFirstCells = dayHead.find('th:first');
- if (allDayTable) {
- axisFirstCells = axisFirstCells.add(allDayTable.find('th:first'));
- }
- axisFirstCells = axisFirstCells.add(slotTable.find('th:first'));
-
- axisWidth = 0;
- setOuterWidth(
- axisFirstCells
- .width('')
- .each(function(i, _cell) {
- axisWidth = Math.max(axisWidth, $(_cell).outerWidth());
- }),
- axisWidth
- );
-
- var gutterCells = dayTable.find('.fc-agenda-gutter');
- if (allDayTable) {
- gutterCells = gutterCells.add(allDayTable.find('th.fc-agenda-gutter'));
- }
-
- var slotTableWidth = slotScroller[0].clientWidth; // needs to be done after axisWidth (for IE7)
-
- gutterWidth = slotScroller.width() - slotTableWidth;
- if (gutterWidth) {
- setOuterWidth(gutterCells, gutterWidth);
- gutterCells
- .show()
- .prev()
- .removeClass('fc-last');
- }else{
- gutterCells
- .hide()
- .prev()
- .addClass('fc-last');
- }
-
- colWidth = Math.floor((slotTableWidth - axisWidth) / colCnt);
- setOuterWidth(dayHeadCells.slice(0, -1), colWidth);
+ function disableButton(buttonName) {
+ el.find('.fc-' + buttonName + '-button')
+ .attr('disabled', 'disabled')
+ .addClass(tm + '-state-disabled');
}
+
+ function enableButton(buttonName) {
+ el.find('.fc-' + buttonName + '-button')
+ .removeAttr('disabled')
+ .removeClass(tm + '-state-disabled');
+ }
- /* Scrolling
- -----------------------------------------------------------------------*/
+ function getViewsWithButtons() {
+ return viewsWithButtons;
+ }
+}
- function resetScroll() {
- var top = computeTimeTop(
- moment.duration(opt('scrollTime'))
- ) + 1; // +1 for the border
+;;
- function scroll() {
- slotScroller.scrollTop(top);
- }
+fc.sourceNormalizers = [];
+fc.sourceFetchers = [];
- scroll();
- setTimeout(scroll, 0); // overrides any previous scroll state made by the browser
- }
+var ajaxDefaults = {
+ dataType: 'json',
+ cache: false
+};
+
+var eventGUID = 1;
- function afterRender() { // after the view has been freshly rendered and sized
- resetScroll();
- }
+function EventManager(options) { // assumed to be a calendar
+ var t = this;
+ // exports
+ t.isFetchNeeded = isFetchNeeded;
+ t.fetchEvents = fetchEvents;
+ t.addEventSource = addEventSource;
+ t.removeEventSource = removeEventSource;
+ t.updateEvent = updateEvent;
+ t.renderEvent = renderEvent;
+ t.removeEvents = removeEvents;
+ t.clientEvents = clientEvents;
+ t.mutateEvent = mutateEvent;
+ t.normalizeEventRange = normalizeEventRange;
+ t.normalizeEventRangeTimes = normalizeEventRangeTimes;
+ t.ensureVisibleEventRange = ensureVisibleEventRange;
- /* Slot/Day clicking and binding
- -----------------------------------------------------------------------*/
-
- function dayBind(cells) {
- cells.click(slotClick)
- .mousedown(daySelectionMousedown);
- }
+ // imports
+ var reportEvents = t.reportEvents;
+
+
+ // locals
+ var stickySource = { events: [] };
+ var sources = [ stickySource ];
+ var rangeStart, rangeEnd;
+ var currentFetchID = 0;
+ var pendingSourceCnt = 0;
+ var cache = []; // holds events that have already been expanded
- function slotBind(cells) {
- cells.click(slotClick)
- .mousedown(slotSelectionMousedown);
+ $.each(
+ (options.events ? [ options.events ] : []).concat(options.eventSources || []),
+ function(i, sourceInput) {
+ var source = buildEventSource(sourceInput);
+ if (source) {
+ sources.push(source);
+ }
+ }
+ );
+
+
+
+ /* Fetching
+ -----------------------------------------------------------------------------*/
+
+
+ function isFetchNeeded(start, end) {
+ return !rangeStart || // nothing has been fetched yet?
+ // or, a part of the new range is outside of the old range? (after normalizing)
+ start.clone().stripZone() < rangeStart.clone().stripZone() ||
+ end.clone().stripZone() > rangeEnd.clone().stripZone();
}
- function slotClick(ev) {
- if (!opt('selectable')) { // if selectable, SelectionManager will worry about dayClick
- var col = Math.min(colCnt-1, Math.floor((ev.pageX - dayTable.offset().left - axisWidth) / colWidth));
- var date = cellToDate(0, col);
- var match = this.parentNode.className.match(/fc-slot(\d+)/); // TODO: maybe use data
- if (match) {
- var slotIndex = parseInt(match[1], 10);
- date.add(minTime + slotIndex * slotDuration);
- date = calendar.rezoneDate(date);
- trigger(
- 'dayClick',
- dayBodyCells[col],
- date,
- ev
- );
- }else{
- trigger(
- 'dayClick',
- dayBodyCells[col],
- date,
- ev
- );
- }
+ function fetchEvents(start, end) {
+ rangeStart = start;
+ rangeEnd = end;
+ cache = [];
+ var fetchID = ++currentFetchID;
+ var len = sources.length;
+ pendingSourceCnt = len;
+ for (var i=0; i= 0) {
- date.time(moment.duration(minTime + snapIndex * snapDuration));
- date = calendar.rezoneDate(date);
+ else {
+ event.end = null;
}
- return date;
+ mutateEvent(event, getMiscEventProps(event)); // will handle start/end/allDay normalization
+ reportEvents(cache); // reports event modifications (so we can redraw)
}
- function computeDateTop(date, startOfDayDate) {
- return computeTimeTop(
- moment.duration(
- date.clone().stripZone() - startOfDayDate.clone().stripTime()
- )
- );
- }
+ // Returns a hash of misc event properties that should be copied over to related events.
+ function getMiscEventProps(event) {
+ var props = {};
+ $.each(event, function(name, val) {
+ if (isMiscEventPropName(name)) {
+ if (val !== undefined && isAtomic(val)) { // a defined non-object
+ props[name] = val;
+ }
+ }
+ });
- function computeTimeTop(time) { // time is a duration
+ return props;
+ }
- if (time < minTime) {
- return 0;
- }
- if (time >= maxTime) {
- return slotTable.height();
- }
+ // non-date-related, non-id-related, non-secret
+ function isMiscEventPropName(name) {
+ return !/^_|^(id|allDay|start|end)$/.test(name);
+ }
- var slots = (time - minTime) / slotDuration;
- var slotIndex = Math.floor(slots);
- var slotPartial = slots - slotIndex;
- var slotTop = slotTopCache[slotIndex];
+
+ // returns the expanded events that were created
+ function renderEvent(eventInput, stick) {
+ var abstractEvent = buildEventFromInput(eventInput);
+ var events;
+ var i, event;
- // find the position of the corresponding
- // need to use this tecnhique because not all rows are rendered at same height sometimes.
- if (slotTop === undefined) {
- slotTop = slotTopCache[slotIndex] =
- slotTable.find('tr').eq(slotIndex).find('td div')[0].offsetTop;
- // .eq() is faster than ":eq()" selector
- // [0].offsetTop is faster than .position().top (do we really need this optimization?)
- // a better optimization would be to cache all these divs
- }
+ if (abstractEvent) { // not false (a valid input)
+ events = expandEvent(abstractEvent);
- var top =
- slotTop - 1 + // because first row doesn't have a top border
- slotPartial * slotHeight; // part-way through the row
+ for (i = 0; i < events.length; i++) {
+ event = events[i];
- top = Math.max(top, 0);
+ if (!event.source) {
+ if (stick) {
+ stickySource.events.push(event);
+ event.source = stickySource;
+ }
+ cache.push(event);
+ }
+ }
- return top;
- }
-
-
-
- /* Selection
- ---------------------------------------------------------------------------------*/
+ reportEvents(cache);
-
- function defaultSelectionEnd(start) {
- if (start.hasTime()) {
- return start.clone().add(slotDuration);
- }
- else {
- return start.clone().add('days', 1);
+ return events;
}
+
+ return [];
}
- function renderSelection(start, end) {
- if (start.hasTime() || end.hasTime()) {
- renderSlotSelection(start, end);
+ function removeEvents(filter) {
+ var eventID;
+ var i;
+
+ if (filter == null) { // null or undefined. remove all events
+ filter = function() { return true; }; // will always match
}
- else if (opt('allDaySlot')) {
- renderDayOverlay(start, end, true); // true for refreshing coordinate grid
+ else if (!$.isFunction(filter)) { // an event ID
+ eventID = filter + '';
+ filter = function(event) {
+ return event._id == eventID;
+ };
}
- }
-
-
- function renderSlotSelection(startDate, endDate) {
- var helperOption = opt('selectHelper');
- coordinateGrid.build();
- if (helperOption) {
- var col = dateToCell(startDate).col;
- if (col >= 0 && col < colCnt) { // only works when times are on same day
- var rect = coordinateGrid.rect(0, col, 0, col, slotContainer); // only for horizontal coords
- var top = computeDateTop(startDate, startDate);
- var bottom = computeDateTop(endDate, startDate);
- if (bottom > top) { // protect against selections that are entirely before or after visible range
- rect.top = top;
- rect.height = bottom - top;
- rect.left += 2;
- rect.width -= 5;
- if ($.isFunction(helperOption)) {
- var helperRes = helperOption(startDate, endDate);
- if (helperRes) {
- rect.position = 'absolute';
- selectionHelper = $(helperRes)
- .css(rect)
- .appendTo(slotContainer);
- }
- }else{
- rect.isStart = true; // conside rect a "seg" now
- rect.isEnd = true; //
- selectionHelper = $(slotSegHtml(
- {
- title: '',
- start: startDate,
- end: endDate,
- className: ['fc-select-helper'],
- editable: false
- },
- rect
- ));
- selectionHelper.css('opacity', opt('dragOpacity'));
- }
- if (selectionHelper) {
- slotBind(selectionHelper);
- slotContainer.append(selectionHelper);
- setOuterWidth(selectionHelper, rect.width, true); // needs to be after appended
- setOuterHeight(selectionHelper, rect.height, true);
- }
- }
+
+ // Purge event(s) from our local cache
+ cache = $.grep(cache, filter, true); // inverse=true
+
+ // Remove events from array sources.
+ // This works because they have been converted to official Event Objects up front.
+ // (and as a result, event._id has been calculated).
+ for (i=0; i rangeStart && eventStart < rangeEnd) {
- if (eventStart < rangeStart) {
- segStart = rangeStart.clone();
- isStart = false;
- }
- else {
- segStart = eventStart;
- isStart = true;
- }
+ // Ensures proper values for allDay/start/end. Accepts an Event object, or a plain object with event-ish properties.
+ // NOTE: Will modify the given object.
+ function normalizeEventRange(props) {
- if (eventEnd > rangeEnd) {
- segEnd = rangeEnd.clone();
- isEnd = false;
- }
- else {
- segEnd = eventEnd;
- isEnd = true;
- }
+ normalizeEventRangeTimes(props);
- segs.push({
- event: event,
- start: segStart,
- end: segEnd,
- isStart: isStart,
- isEnd: isEnd
- });
- }
+ if (props.end && !props.end.isAfter(props.start)) {
+ props.end = null;
}
- return segs.sort(compareSlotSegs);
- }
-
-
- // renders events in the 'time slots' at the bottom
- // TODO: when we refactor this, when user returns `false` eventRender, don't have empty space
- // TODO: refactor will include using pixels to detect collisions instead of dates (handy for seg cmp)
-
- function renderSlotSegs(segs, modifiedEventId) {
-
- var i, segCnt=segs.length, seg,
- event,
- top,
- bottom,
- columnLeft,
- columnRight,
- columnWidth,
- width,
- left,
- right,
- html = '',
- eventElements,
- eventElement,
- triggerRes,
- titleElement,
- height,
- slotSegmentContainer = getSlotSegmentContainer(),
- isRTL = opt('isRTL');
-
- // calculate position/dimensions, create html
- for (i=0; i" +
- "" +
- " " +
- htmlEscape(t.getEventTimeText(event)) +
- " " +
- " " +
- htmlEscape(event.title || '') +
- " " +
- " " +
- "";
-
- if (seg.isEnd && isEventResizable(event)) {
- html +=
- "= ";
- }
- html +=
- "" + (url ? "a" : "div") + ">";
- return html;
}
-
-
- function bindSlotSeg(event, eventElement, seg) {
- var timeElement = eventElement.find('div.fc-event-time');
- if (isEventDraggable(event)) {
- draggableSlotEvent(event, eventElement, timeElement);
- }
- if (seg.isEnd && isEventResizable(event)) {
- resizableSlotEvent(event, eventElement, timeElement);
+
+
+ // If `range` is a proper range with a start and end, returns the original object.
+ // If missing an end, computes a new range with an end, computing it as if it were an event.
+ // TODO: make this a part of the event -> eventRange system
+ function ensureVisibleEventRange(range) {
+ var allDay;
+
+ if (!range.end) {
+
+ allDay = range.allDay; // range might be more event-ish than we think
+ if (allDay == null) {
+ allDay = !range.start.hasTime();
+ }
+
+ range = $.extend({}, range); // make a copy, copying over other misc properties
+ range.end = t.getDefaultEventEnd(allDay, range.start);
}
- eventElementHandlers(event, eventElement);
+ return range;
}
-
-
-
- /* Dragging
- -----------------------------------------------------------------------------------*/
-
-
- // when event starts out FULL-DAY
- // overrides DayEventRenderer's version because it needs to account for dragging elements
- // to and from the slot area.
-
- function draggableDayEvent(event, eventElement, seg) {
- var isStart = seg.isStart;
- var origWidth;
- var revert;
- var allDay = true;
- var dayDelta;
-
- var hoverListener = getHoverListener();
- var colWidth = getColWidth();
- var minTime = getMinTime();
- var slotDuration = getSlotDuration();
- var slotHeight = getSlotHeight();
- var snapDuration = getSnapDuration();
- var snapHeight = getSnapHeight();
-
- eventElement.draggable({
- opacity: opt('dragOpacity', 'month'), // use whatever the month view was using
- revertDuration: opt('dragRevertDuration'),
- start: function(ev, ui) {
-
- trigger('eventDragStart', eventElement[0], event, ev, ui);
- hideEvents(event, eventElement);
- origWidth = eventElement.width();
-
- hoverListener.start(function(cell, origCell) {
- clearOverlays();
- if (cell) {
- revert = false;
-
- var origDate = cellToDate(0, origCell.col);
- var date = cellToDate(0, cell.col);
- dayDelta = date.diff(origDate, 'days');
-
- if (!cell.row) { // on full-days
-
- renderDayOverlay(
- event.start.clone().add('days', dayDelta),
- getEventEnd(event).add('days', dayDelta)
- );
- resetElement();
- }
- else { // mouse is over bottom slots
-
- if (isStart) {
- if (allDay) {
- // convert event to temporary slot-event
- eventElement.width(colWidth - 10); // don't use entire width
- setOuterHeight(eventElement, calendar.defaultTimedEventDuration / slotDuration * slotHeight); // the default height
- eventElement.draggable('option', 'grid', [ colWidth, 1 ]);
- allDay = false;
- }
- }
- else {
- revert = true;
- }
- }
- revert = revert || (allDay && !dayDelta);
- }
- else {
- resetElement();
- revert = true;
- }
+ // If the given event is a recurring event, break it down into an array of individual instances.
+ // If not a recurring event, return an array with the single original event.
+ // If given a falsy input (probably because of a failed buildEventFromInput call), returns an empty array.
+ // HACK: can override the recurring window by providing custom rangeStart/rangeEnd (for businessHours).
+ function expandEvent(abstractEvent, _rangeStart, _rangeEnd) {
+ var events = [];
+ var dowHash;
+ var dow;
+ var i;
+ var date;
+ var startTime, endTime;
+ var start, end;
+ var event;
+
+ _rangeStart = _rangeStart || rangeStart;
+ _rangeEnd = _rangeEnd || rangeEnd;
- eventElement.draggable('option', 'revert', revert);
+ if (abstractEvent) {
+ if (abstractEvent._recurring) {
- }, ev, 'drag');
- },
- stop: function(ev, ui) {
- hoverListener.stop();
- clearOverlays();
- trigger('eventDragStop', eventElement[0], event, ev, ui);
-
- if (revert) { // hasn't moved or is out of bounds (draggable has already reverted)
-
- resetElement();
- eventElement.css('filter', ''); // clear IE opacity side-effects
- showEvents(event, eventElement);
+ // make a boolean hash as to whether the event occurs on each day-of-week
+ if ((dow = abstractEvent.dow)) {
+ dowHash = {};
+ for (i = 0; i < dow.length; i++) {
+ dowHash[dow[i]] = true;
+ }
}
- else { // changed!
-
- var eventStart = event.start.clone().add('days', dayDelta); // already assumed to have a stripped time
- var snapTime;
- var snapIndex;
- if (!allDay) {
- snapIndex = Math.round((eventElement.offset().top - getSlotContainer().offset().top) / snapHeight); // why not use ui.offset.top?
- snapTime = moment.duration(minTime + snapIndex * snapDuration);
- eventStart = calendar.rezoneDate(eventStart.clone().time(snapTime));
+
+ // iterate through every day in the current range
+ date = _rangeStart.clone().stripTime(); // holds the date of the current day
+ while (date.isBefore(_rangeEnd)) {
+
+ if (!dowHash || dowHash[date.day()]) { // if everyday, or this particular day-of-week
+
+ startTime = abstractEvent.start; // the stored start and end properties are times (Durations)
+ endTime = abstractEvent.end; // "
+ start = date.clone();
+ end = null;
+
+ if (startTime) {
+ start = start.time(startTime);
+ }
+ if (endTime) {
+ end = date.clone().time(endTime);
+ }
+
+ event = $.extend({}, abstractEvent); // make a copy of the original
+ assignDatesToEvent(
+ start, end,
+ !startTime && !endTime, // allDay?
+ event
+ );
+ events.push(event);
}
- eventDrop(
- eventElement[0],
- event,
- eventStart,
- ev,
- ui
- );
+ date.add(1, 'days');
}
}
- });
- function resetElement() {
- if (!allDay) {
- eventElement
- .width(origWidth)
- .height('')
- .draggable('option', 'grid', null);
- allDay = true;
+ else {
+ events.push(abstractEvent); // return the original event. will be a one-item array
}
}
+
+ return events;
}
-
-
- // when event starts out IN TIMESLOTS
-
- function draggableSlotEvent(event, eventElement, timeElement) {
- var coordinateGrid = t.getCoordinateGrid();
- var colCnt = getColCnt();
- var colWidth = getColWidth();
- var snapHeight = getSnapHeight();
- var snapDuration = getSnapDuration();
-
- // states
- var origPosition; // original position of the element, not the mouse
- var origCell;
- var isInBounds, prevIsInBounds;
- var isAllDay, prevIsAllDay;
- var colDelta, prevColDelta;
- var dayDelta; // derived from colDelta
- var snapDelta, prevSnapDelta; // the number of snaps away from the original position
-
- // newly computed
- var eventStart, eventEnd;
- eventElement.draggable({
- scroll: false,
- grid: [ colWidth, snapHeight ],
- axis: colCnt==1 ? 'y' : false,
- opacity: opt('dragOpacity'),
- revertDuration: opt('dragRevertDuration'),
- start: function(ev, ui) {
-
- trigger('eventDragStart', eventElement[0], event, ev, ui);
- hideEvents(event, eventElement);
-
- coordinateGrid.build();
-
- // initialize states
- origPosition = eventElement.position();
- origCell = coordinateGrid.cell(ev.pageX, ev.pageY);
- isInBounds = prevIsInBounds = true;
- isAllDay = prevIsAllDay = getIsCellAllDay(origCell);
- colDelta = prevColDelta = 0;
- dayDelta = 0;
- snapDelta = prevSnapDelta = 0;
-
- eventStart = null;
- eventEnd = null;
- },
- drag: function(ev, ui) {
-
- // NOTE: this `cell` value is only useful for determining in-bounds and all-day.
- // Bad for anything else due to the discrepancy between the mouse position and the
- // element position while snapping. (problem revealed in PR #55)
- //
- // PS- the problem exists for draggableDayEvent() when dragging an all-day event to a slot event.
- // We should overhaul the dragging system and stop relying on jQuery UI.
- var cell = coordinateGrid.cell(ev.pageX, ev.pageY);
-
- // update states
- isInBounds = !!cell;
- if (isInBounds) {
- isAllDay = getIsCellAllDay(cell);
-
- // calculate column delta
- colDelta = Math.round((ui.position.left - origPosition.left) / colWidth);
- if (colDelta != prevColDelta) {
- // calculate the day delta based off of the original clicked column and the column delta
- var origDate = cellToDate(0, origCell.col);
- var col = origCell.col + colDelta;
- col = Math.max(0, col);
- col = Math.min(colCnt-1, col);
- var date = cellToDate(0, col);
- dayDelta = date.diff(origDate, 'days');
- }
- // calculate minute delta (only if over slots)
- if (!isAllDay) {
- snapDelta = Math.round((ui.position.top - origPosition.top) / snapHeight);
- }
- }
- // any state changes?
- if (
- isInBounds != prevIsInBounds ||
- isAllDay != prevIsAllDay ||
- colDelta != prevColDelta ||
- snapDelta != prevSnapDelta
- ) {
-
- // compute new dates
- if (isAllDay) {
- eventStart = event.start.clone().stripTime().add('days', dayDelta);
- eventEnd = eventStart.clone().add(calendar.defaultAllDayEventDuration);
- }
- else {
- eventStart = event.start.clone().add(snapDelta * snapDuration).add('days', dayDelta);
- eventEnd = getEventEnd(event).add(snapDelta * snapDuration).add('days', dayDelta);
- }
+ /* Event Modification Math
+ -----------------------------------------------------------------------------------------*/
- updateUI();
- // update previous states for next time
- prevIsInBounds = isInBounds;
- prevIsAllDay = isAllDay;
- prevColDelta = colDelta;
- prevSnapDelta = snapDelta;
- }
+ // Modifies an event and all related events by applying the given properties.
+ // Special date-diffing logic is used for manipulation of dates.
+ // If `props` does not contain start/end dates, the updated values are assumed to be the event's current start/end.
+ // All date comparisons are done against the event's pristine _start and _end dates.
+ // Returns an object with delta information and a function to undo all operations.
+ // For making computations in a granularity greater than day/time, specify largeUnit.
+ // NOTE: The given `newProps` might be mutated for normalization purposes.
+ function mutateEvent(event, newProps, largeUnit) {
+ var miscProps = {};
+ var oldProps;
+ var clearEnd;
+ var startDelta;
+ var endDelta;
+ var durationDelta;
+ var undoFunc;
+
+ // diffs the dates in the appropriate way, returning a duration
+ function diffDates(date1, date0) { // date1 - date0
+ if (largeUnit) {
+ return diffByUnit(date1, date0, largeUnit);
+ }
+ else if (newProps.allDay) {
+ return diffDay(date1, date0);
+ }
+ else {
+ return diffDayTime(date1, date0);
+ }
+ }
- // if out-of-bounds, revert when done, and vice versa.
- eventElement.draggable('option', 'revert', !isInBounds);
+ newProps = newProps || {};
- },
- stop: function(ev, ui) {
-
- clearOverlays();
- trigger('eventDragStop', eventElement[0], event, ev, ui);
-
- if (isInBounds && (isAllDay || dayDelta || snapDelta)) { // changed!
- eventDrop(
- eventElement[0],
- event,
- eventStart,
- ev,
- ui
- );
- }
- else { // either no change or out-of-bounds (draggable has already reverted)
+ // normalize new date-related properties
+ if (!newProps.start) {
+ newProps.start = event.start.clone();
+ }
+ if (newProps.end === undefined) {
+ newProps.end = event.end ? event.end.clone() : null;
+ }
+ if (newProps.allDay == null) { // is null or undefined?
+ newProps.allDay = event.allDay;
+ }
+ normalizeEventRange(newProps);
- // reset states for next time, and for updateUI()
- isInBounds = true;
- isAllDay = false;
- colDelta = 0;
- dayDelta = 0;
- snapDelta = 0;
+ // create normalized versions of the original props to compare against
+ // need a real end value, for diffing
+ oldProps = {
+ start: event._start.clone(),
+ end: event._end ? event._end.clone() : t.getDefaultEventEnd(event._allDay, event._start),
+ allDay: newProps.allDay // normalize the dates in the same regard as the new properties
+ };
+ normalizeEventRange(oldProps);
+
+ // need to clear the end date if explicitly changed to null
+ clearEnd = event._end !== null && newProps.end === null;
- updateUI();
- eventElement.css('filter', ''); // clear IE opacity side-effects
+ // compute the delta for moving the start date
+ startDelta = diffDates(newProps.start, oldProps.start);
- // sometimes fast drags make event revert to wrong position, so reset.
- // also, if we dragged the element out of the area because of snapping,
- // but the *mouse* is still in bounds, we need to reset the position.
- eventElement.css(origPosition);
+ // compute the delta for moving the end date
+ if (newProps.end) {
+ endDelta = diffDates(newProps.end, oldProps.end);
+ durationDelta = endDelta.subtract(startDelta);
+ }
+ else {
+ durationDelta = null;
+ }
- showEvents(event, eventElement);
+ // gather all non-date-related properties
+ $.each(newProps, function(name, val) {
+ if (isMiscEventPropName(name)) {
+ if (val !== undefined) {
+ miscProps[name] = val;
}
}
});
- function updateUI() {
- clearOverlays();
- if (isInBounds) {
- if (isAllDay) {
- timeElement.hide();
- eventElement.draggable('option', 'grid', null); // disable grid snapping
- renderDayOverlay(eventStart, eventEnd);
- }
- else {
- updateTimeText();
- timeElement.css('display', ''); // show() was causing display=inline
- eventElement.draggable('option', 'grid', [colWidth, snapHeight]); // re-enable grid snapping
+ // apply the operations to the event and all related events
+ undoFunc = mutateEvents(
+ clientEvents(event._id), // get events with this ID
+ clearEnd,
+ newProps.allDay,
+ startDelta,
+ durationDelta,
+ miscProps
+ );
+
+ return {
+ dateDelta: startDelta,
+ durationDelta: durationDelta,
+ undo: undoFunc
+ };
+ }
+
+
+ // Modifies an array of events in the following ways (operations are in order):
+ // - clear the event's `end`
+ // - convert the event to allDay
+ // - add `dateDelta` to the start and end
+ // - add `durationDelta` to the event's duration
+ // - assign `miscProps` to the event
+ //
+ // Returns a function that can be called to undo all the operations.
+ //
+ // TODO: don't use so many closures. possible memory issues when lots of events with same ID.
+ //
+ function mutateEvents(events, clearEnd, allDay, dateDelta, durationDelta, miscProps) {
+ var isAmbigTimezone = t.getIsAmbigTimezone();
+ var undoFunctions = [];
+
+ // normalize zero-length deltas to be null
+ if (dateDelta && !dateDelta.valueOf()) { dateDelta = null; }
+ if (durationDelta && !durationDelta.valueOf()) { durationDelta = null; }
+
+ $.each(events, function(i, event) {
+ var oldProps;
+ var newProps;
+
+ // build an object holding all the old values, both date-related and misc.
+ // for the undo function.
+ oldProps = {
+ start: event.start.clone(),
+ end: event.end ? event.end.clone() : null,
+ allDay: event.allDay
+ };
+ $.each(miscProps, function(name) {
+ oldProps[name] = event[name];
+ });
+
+ // new date-related properties. work off the original date snapshot.
+ // ok to use references because they will be thrown away when backupEventDates is called.
+ newProps = {
+ start: event._start,
+ end: event._end,
+ allDay: allDay // normalize the dates in the same regard as the new properties
+ };
+ normalizeEventRange(newProps); // massages start/end/allDay
+
+ // strip or ensure the end date
+ if (clearEnd) {
+ newProps.end = null;
+ }
+ else if (durationDelta && !newProps.end) { // the duration translation requires an end date
+ newProps.end = t.getDefaultEventEnd(newProps.allDay, newProps.start);
+ }
+
+ if (dateDelta) {
+ newProps.start.add(dateDelta);
+ if (newProps.end) {
+ newProps.end.add(dateDelta);
}
}
- }
- function updateTimeText() {
- if (eventStart) { // must of had a state change
- timeElement.text(
- t.getEventTimeText(eventStart, event.end ? eventEnd : null)
- // ^
- // only display the new end if there was an old end
- );
+ if (durationDelta) {
+ newProps.end.add(durationDelta); // end already ensured above
}
- }
- }
-
-
-
- /* Resizing
- --------------------------------------------------------------------------------------*/
-
-
- function resizableSlotEvent(event, eventElement, timeElement) {
- var snapDelta, prevSnapDelta;
- var snapHeight = getSnapHeight();
- var snapDuration = getSnapDuration();
- var eventEnd;
-
- eventElement.resizable({
- handles: {
- s: '.ui-resizable-handle'
- },
- grid: snapHeight,
- start: function(ev, ui) {
- snapDelta = prevSnapDelta = 0;
- hideEvents(event, eventElement);
- trigger('eventResizeStart', eventElement[0], event, ev, ui);
- },
- resize: function(ev, ui) {
- // don't rely on ui.size.height, doesn't take grid into account
- snapDelta = Math.round((Math.max(snapHeight, eventElement.height()) - ui.originalSize.height) / snapHeight);
- if (snapDelta != prevSnapDelta) {
- eventEnd = getEventEnd(event).add(snapDuration * snapDelta);
- var text;
- if (snapDelta) { // has there been a change?
- text = t.getEventTimeText(event.start, eventEnd);
- }
- else {
- text = t.getEventTimeText(event); // the original time text
- }
- timeElement.text(text);
- prevSnapDelta = snapDelta;
- }
- },
- stop: function(ev, ui) {
- trigger('eventResizeStop', eventElement[0], event, ev, ui);
- if (snapDelta) {
- eventResize(
- eventElement[0],
- event,
- eventEnd,
- ev,
- ui
- );
- }
- else {
- showEvents(event, eventElement);
- // BUG: if event was really short, need to put title back in span
+ // if the dates have changed, and we know it is impossible to recompute the
+ // timezone offsets, strip the zone.
+ if (
+ isAmbigTimezone &&
+ !newProps.allDay &&
+ (dateDelta || durationDelta)
+ ) {
+ newProps.start.stripZone();
+ if (newProps.end) {
+ newProps.end.stripZone();
}
}
+
+ $.extend(event, miscProps, newProps); // copy over misc props, then date-related props
+ backupEventDates(event); // regenerate internal _start/_end/_allDay
+
+ undoFunctions.push(function() {
+ $.extend(event, oldProps);
+ backupEventDates(event); // regenerate internal _start/_end/_allDay
+ });
});
- }
-
-}
+ return function() {
+ for (var i = 0; i < undoFunctions.length; i++) {
+ undoFunctions[i]();
+ }
+ };
+ }
+ /* Business Hours
+ -----------------------------------------------------------------------------------------*/
-/* Agenda Event Segment Utilities
------------------------------------------------------------------------------*/
+ t.getBusinessHoursEvents = getBusinessHoursEvents;
-// Sets the seg.backwardCoord and seg.forwardCoord on each segment and returns a new
-// list in the order they should be placed into the DOM (an implicit z-index).
-function placeSlotSegs(segs) {
- var levels = buildSlotSegLevels(segs);
- var level0 = levels[0];
- var i;
+ // Returns an array of events as to when the business hours occur in the given view.
+ // Abuse of our event system :(
+ function getBusinessHoursEvents(wholeDay) {
+ var optionVal = options.businessHours;
+ var defaultVal = {
+ className: 'fc-nonbusiness',
+ start: '09:00',
+ end: '17:00',
+ dow: [ 1, 2, 3, 4, 5 ], // monday - friday
+ rendering: 'inverse-background'
+ };
+ var view = t.getView();
+ var eventInput;
+
+ if (optionVal) { // `true` (which means "use the defaults") or an override object
+ eventInput = $.extend(
+ {}, // copy to a new object in either case
+ defaultVal,
+ typeof optionVal === 'object' ? optionVal : {} // override the defaults
+ );
+ }
- computeForwardSlotSegs(levels);
+ if (eventInput) {
- if (level0) {
+ // if a whole-day series is requested, clear the start/end times
+ if (wholeDay) {
+ eventInput.start = null;
+ eventInput.end = null;
+ }
- for (i=0; i= eventStart && range.end <= eventEnd;
+ }
- for (j=0; j eventStart;
}
- return segs;
+
+ t.getEventCache = function() {
+ return cache;
+ };
+
}
-// Find all the segments in `otherSegs` that vertically collide with `seg`.
-// Append into an optionally-supplied `results` array and return.
-function computeSlotSegCollisions(seg, otherSegs, results) {
- results = results || [];
+// Returns a list of events that the given event should be compared against when being considered for a move to
+// the specified range. Attached to the Calendar's prototype because EventManager is a mixin for a Calendar.
+Calendar.prototype.getPeerEvents = function(event, range) {
+ var cache = this.getEventCache();
+ var peerEvents = [];
+ var i, otherEvent;
- for (var i=0; i seg2.start && seg1.start < seg2.end;
+// updates the "backup" properties, which are preserved in order to compute diffs later on.
+function backupEventDates(event) {
+ event._allDay = event.allDay;
+ event._start = event.start.clone();
+ event._end = event.end ? event.end.clone() : null;
}
+;;
-// A cmp function for determining which forward segment to rely on more when computing coordinates.
-function compareForwardSlotSegs(seg1, seg2) {
- // put higher-pressure first
- return seg2.forwardPressure - seg1.forwardPressure ||
- // put segments that are closer to initial edge first (and favor ones with no coords yet)
- (seg1.backwardCoord || 0) - (seg2.backwardCoord || 0) ||
- // do normal sorting...
- compareSlotSegs(seg1, seg2);
-}
-
+/* An abstract class for the "basic" views, as well as month view. Renders one or more rows of day cells.
+----------------------------------------------------------------------------------------------------------------------*/
+// It is a manager for a DayGrid subcomponent, which does most of the heavy lifting.
+// It is responsible for managing width/height.
-// A cmp function for determining which segment should be closer to the initial edge
-// (the left edge on a left-to-right calendar).
-function compareSlotSegs(seg1, seg2) {
- return seg1.start - seg2.start || // earlier start time goes first
- (seg2.end - seg2.start) - (seg1.end - seg1.start) || // tie? longer-duration goes first
- (seg1.event.title || '').localeCompare(seg2.event.title); // tie? alphabetically by title
-}
+var BasicView = View.extend({
+ dayGrid: null, // the main subcomponent that does most of the heavy lifting
-;;
+ dayNumbersVisible: false, // display day numbers on each day cell?
+ weekNumbersVisible: false, // display week numbers along the side?
+ weekNumberWidth: null, // width of all the week-number cells running down the side
-function View(element, calendar, viewName) {
- var t = this;
-
-
- // exports
- t.element = element;
- t.calendar = calendar;
- t.name = viewName;
- t.opt = opt;
- t.trigger = trigger;
- t.isEventDraggable = isEventDraggable;
- t.isEventResizable = isEventResizable;
- t.clearEventData = clearEventData;
- t.reportEventElement = reportEventElement;
- t.triggerEventDestroy = triggerEventDestroy;
- t.eventElementHandlers = eventElementHandlers;
- t.showEvents = showEvents;
- t.hideEvents = hideEvents;
- t.eventDrop = eventDrop;
- t.eventResize = eventResize;
- // t.start, t.end // moments with ambiguous-time
- // t.intervalStart, t.intervalEnd // moments with ambiguous-time
-
-
- // imports
- var reportEventChange = calendar.reportEventChange;
-
-
- // locals
- var eventElementsByID = {}; // eventID mapped to array of jQuery elements
- var eventElementCouples = []; // array of objects, { event, element } // TODO: unify with segment system
- var options = calendar.options;
- var nextDayThreshold = moment.duration(options.nextDayThreshold);
+ headRowEl: null, // the fake row element of the day-of-week header
-
-
-
- function opt(name, viewNameOverride) {
- var v = options[name];
- if ($.isPlainObject(v) && !isForcedAtomicOption(name)) {
- return smartProperty(v, viewNameOverride || viewName);
- }
- return v;
- }
-
- function trigger(name, thisObj) {
- return calendar.trigger.apply(
- calendar,
- [name, thisObj || t].concat(Array.prototype.slice.call(arguments, 2), [t])
- );
- }
-
+ initialize: function() {
+ this.dayGrid = new DayGrid(this);
+ this.coordMap = this.dayGrid.coordMap; // the view's date-to-cell mapping is identical to the subcomponent's
+ },
- /* Event Editable Boolean Calculations
- ------------------------------------------------------------------------------*/
+ // Sets the display range and computes all necessary dates
+ setRange: function(range) {
+ View.prototype.setRange.call(this, range); // call the super-method
-
- function isEventDraggable(event) {
- var source = event.source || {};
- return firstDefined(
- event.startEditable,
- source.startEditable,
- opt('eventStartEditable'),
- event.editable,
- source.editable,
- opt('editable')
- );
- }
-
-
- function isEventResizable(event) { // but also need to make sure the seg.isEnd == true
- var source = event.source || {};
- return firstDefined(
- event.durationEditable,
- source.durationEditable,
- opt('eventDurationEditable'),
- event.editable,
- source.editable,
- opt('editable')
- );
- }
-
-
-
- /* Event Data
- ------------------------------------------------------------------------------*/
+ this.dayGrid.breakOnWeeks = /year|month|week/.test(this.intervalUnit); // do before setRange
+ this.dayGrid.setRange(range);
+ },
- function clearEventData() {
- eventElementsByID = {};
- eventElementCouples = [];
- }
-
-
-
- /* Event Elements
- ------------------------------------------------------------------------------*/
-
-
- // report when view creates an element for an event
- function reportEventElement(event, element) {
- eventElementCouples.push({ event: event, element: element });
- if (eventElementsByID[event._id]) {
- eventElementsByID[event._id].push(element);
- }else{
- eventElementsByID[event._id] = [element];
- }
- }
+ // Compute the value to feed into setRange. Overrides superclass.
+ computeRange: function(date) {
+ var range = View.prototype.computeRange.call(this, date); // get value from the super-method
+ // year and month views should be aligned with weeks. this is already done for week
+ if (/year|month/.test(range.intervalUnit)) {
+ range.start.startOf('week');
+ range.start = this.skipHiddenDays(range.start);
- function triggerEventDestroy() {
- $.each(eventElementCouples, function(i, couple) {
- t.trigger('eventDestroy', couple.event, couple.event, couple.element);
- });
- }
-
-
- // attaches eventClick, eventMouseover, eventMouseout
- function eventElementHandlers(event, eventElement) {
- eventElement
- .click(function(ev) {
- if (!eventElement.hasClass('ui-draggable-dragging') &&
- !eventElement.hasClass('ui-resizable-resizing')) {
- return trigger('eventClick', this, event, ev);
- }
- })
- .hover(
- function(ev) {
- trigger('eventMouseover', this, event, ev);
- },
- function(ev) {
- trigger('eventMouseout', this, event, ev);
- }
- );
- // TODO: don't fire eventMouseover/eventMouseout *while* dragging is occuring (on subject element)
- // TODO: same for resizing
- }
-
-
- function showEvents(event, exceptElement) {
- eachEventElement(event, exceptElement, 'show');
- }
-
-
- function hideEvents(event, exceptElement) {
- eachEventElement(event, exceptElement, 'hide');
- }
-
-
- function eachEventElement(event, exceptElement, funcName) {
- // NOTE: there may be multiple events per ID (repeating events)
- // and multiple segments per event
- var elements = eventElementsByID[event._id],
- i, len = elements.length;
- for (i=0; i 1; // TODO: make grid responsible
+ this.weekNumbersVisible = this.opt('weekNumbers');
+ this.dayGrid.numbersVisible = this.dayNumbersVisible || this.weekNumbersVisible;
-
-
- /* Event Modification Reporting
- ---------------------------------------------------------------------------------*/
+ this.el.addClass('fc-basic-view').html(this.renderHtml());
-
- function eventDrop(el, event, newStart, ev, ui) {
- var mutateResult = calendar.mutateEvent(event, newStart, null);
-
- trigger(
- 'eventDrop',
- el,
- event,
- mutateResult.dateDelta,
- function() {
- mutateResult.undo();
- reportEventChange(event._id);
- },
- ev,
- ui
- );
+ this.headRowEl = this.el.find('thead .fc-row');
- reportEventChange(event._id);
- }
+ this.scrollerEl = this.el.find('.fc-day-grid-container');
+ this.dayGrid.coordMap.containerEl = this.scrollerEl; // constrain clicks/etc to the dimensions of the scroller
+ this.dayGrid.setElement(this.el.find('.fc-day-grid'));
+ this.dayGrid.renderDates(this.hasRigidRows());
+ },
- function eventResize(el, event, newEnd, ev, ui) {
- var mutateResult = calendar.mutateEvent(event, null, newEnd);
- trigger(
- 'eventResize',
- el,
- event,
- mutateResult.durationDelta,
- function() {
- mutateResult.undo();
- reportEventChange(event._id);
- },
- ev,
- ui
- );
+ // Unrenders the content of the view. Since we haven't separated skeleton rendering from date rendering,
+ // always completely kill the dayGrid's rendering.
+ unrenderDates: function() {
+ this.dayGrid.unrenderDates();
+ this.dayGrid.removeElement();
+ },
- reportEventChange(event._id);
- }
+ renderBusinessHours: function() {
+ this.dayGrid.renderBusinessHours();
+ },
- // ====================================================================================================
- // Utilities for day "cells"
- // ====================================================================================================
- // The "basic" views are completely made up of day cells.
- // The "agenda" views have day cells at the top "all day" slot.
- // This was the obvious common place to put these utilities, but they should be abstracted out into
- // a more meaningful class (like DayEventRenderer).
- // ====================================================================================================
+ // Builds the HTML skeleton for the view.
+ // The day-grid component will render inside of a container defined by this HTML.
+ renderHtml: function() {
+ return '' +
+ '' +
+ '' +
+ '' +
+ '' +
+ ' ' +
+ '' +
+ '' +
+ '' +
+ '' +
+ '' +
+ ' | ' +
+ ' ' +
+ '' +
+ ' ';
+ },
- // For determining how a given "cell" translates into a "date":
- //
- // 1. Convert the "cell" (row and column) into a "cell offset" (the # of the cell, cronologically from the first).
- // Keep in mind that column indices are inverted with isRTL. This is taken into account.
- //
- // 2. Convert the "cell offset" to a "day offset" (the # of days since the first visible day in the view).
- //
- // 3. Convert the "day offset" into a "date" (a Moment).
- //
- // The reverse transformation happens when transforming a date into a cell.
+
+ // Generates the HTML that will go before the day-of week header cells.
+ // Queried by the DayGrid subcomponent when generating rows. Ordering depends on isRTL.
+ headIntroHtml: function() {
+ if (this.weekNumbersVisible) {
+ return '' +
+ '';
+ }
+ },
- // exports
- t.isHiddenDay = isHiddenDay;
- t.skipHiddenDays = skipHiddenDays;
- t.getCellsPerWeek = getCellsPerWeek;
- t.dateToCell = dateToCell;
- t.dateToDayOffset = dateToDayOffset;
- t.dayOffsetToCellOffset = dayOffsetToCellOffset;
- t.cellOffsetToCell = cellOffsetToCell;
- t.cellToDate = cellToDate;
- t.cellToCellOffset = cellToCellOffset;
- t.cellOffsetToDayOffset = cellOffsetToDayOffset;
- t.dayOffsetToDate = dayOffsetToDate;
- t.rangeToSegments = rangeToSegments;
-
-
- // internals
- var hiddenDays = opt('hiddenDays') || []; // array of day-of-week indices that are hidden
- var isHiddenDayHash = []; // is the day-of-week hidden? (hash with day-of-week-index -> bool)
- var cellsPerWeek;
- var dayToCellMap = []; // hash from dayIndex -> cellIndex, for one week
- var cellToDayMap = []; // hash from cellIndex -> dayIndex, for one week
- var isRTL = opt('isRTL');
-
-
- // initialize important internal variables
- (function() {
-
- if (opt('weekends') === false) {
- hiddenDays.push(0, 6); // 0=sunday, 6=saturday
+ // Generates the HTML that will go before content-skeleton cells that display the day/week numbers.
+ // Queried by the DayGrid subcomponent. Ordering depends on isRTL.
+ numberIntroHtml: function(row) {
+ if (this.weekNumbersVisible) {
+ return '' +
+ '' +
+ '' + // needed for matchCellWidths
+ this.dayGrid.getCell(row, 0).start.format('w') +
+ '' +
+ ' | ';
}
+ },
- // Loop through a hypothetical week and determine which
- // days-of-week are hidden. Record in both hashes (one is the reverse of the other).
- for (var dayIndex=0, cellIndex=0; dayIndex<7; dayIndex++) {
- dayToCellMap[dayIndex] = cellIndex;
- isHiddenDayHash[dayIndex] = $.inArray(dayIndex, hiddenDays) != -1;
- if (!isHiddenDayHash[dayIndex]) {
- cellToDayMap[cellIndex] = dayIndex;
- cellIndex++;
- }
+
+ // Generates the HTML that goes before the day bg cells for each day-row.
+ // Queried by the DayGrid subcomponent. Ordering depends on isRTL.
+ dayIntroHtml: function() {
+ if (this.weekNumbersVisible) {
+ return ' | ';
}
+ },
- cellsPerWeek = cellIndex;
- if (!cellsPerWeek) {
- throw 'invalid hiddenDays'; // all days were hidden? bad.
+
+ // Generates the HTML that goes before every other type of row generated by DayGrid. Ordering depends on isRTL.
+ // Affects helper-skeleton and highlight-skeleton rows.
+ introHtml: function() {
+ if (this.weekNumbersVisible) {
+ return ' | ';
}
+ },
- })();
+ // Generates the HTML for the s of the "number" row in the DayGrid's content skeleton.
+ // The number row will only exist if either day numbers or week numbers are turned on.
+ numberCellHtml: function(cell) {
+ var date = cell.start;
+ var classes;
- // Is the current day hidden?
- // `day` is a day-of-week index (0-6), or a Moment
- function isHiddenDay(day) {
- if (moment.isMoment(day)) {
- day = day.day();
+ if (!this.dayNumbersVisible) { // if there are week numbers but not day numbers
+ return ' | | '; // will create an empty space above events :(
}
- return isHiddenDayHash[day];
- }
+ classes = this.dayGrid.getDayClasses(date);
+ classes.unshift('fc-day-number');
- function getCellsPerWeek() {
- return cellsPerWeek;
- }
+ return '' +
+ '' +
+ date.date() +
+ ' | ';
+ },
- // Incrementing the current day until it is no longer a hidden day, returning a copy.
- // If the initial value of `date` is not a hidden day, don't do anything.
- // Pass `isExclusive` as `true` if you are dealing with an end date.
- // `inc` defaults to `1` (increment one day forward each time)
- function skipHiddenDays(date, inc, isExclusive) {
- var out = date.clone();
- inc = inc || 1;
- while (
- isHiddenDayHash[(out.day() + (isExclusive ? inc : 0) + 7) % 7]
- ) {
- out.add('days', inc);
+ // Generates an HTML attribute string for setting the width of the week number column, if it is known
+ weekNumberStyleAttr: function() {
+ if (this.weekNumberWidth !== null) {
+ return 'style="width:' + this.weekNumberWidth + 'px"';
}
- return out;
- }
+ return '';
+ },
- //
- // TRANSFORMATIONS: cell -> cell offset -> day offset -> date
- //
+ // Determines whether each row should have a constant height
+ hasRigidRows: function() {
+ var eventLimit = this.opt('eventLimit');
+ return eventLimit && typeof eventLimit !== 'number';
+ },
- // cell -> date (combines all transformations)
- // Possible arguments:
- // - row, col
- // - { row:#, col: # }
- function cellToDate() {
- var cellOffset = cellToCellOffset.apply(null, arguments);
- var dayOffset = cellOffsetToDayOffset(cellOffset);
- var date = dayOffsetToDate(dayOffset);
- return date;
- }
- // cell -> cell offset
- // Possible arguments:
- // - row, col
- // - { row:#, col:# }
- function cellToCellOffset(row, col) {
- var colCnt = t.getColCnt();
+ /* Dimensions
+ ------------------------------------------------------------------------------------------------------------------*/
- // rtl variables. wish we could pre-populate these. but where?
- var dis = isRTL ? -1 : 1;
- var dit = isRTL ? colCnt - 1 : 0;
- if (typeof row == 'object') {
- col = row.col;
- row = row.row;
+ // Refreshes the horizontal dimensions of the view
+ updateWidth: function() {
+ if (this.weekNumbersVisible) {
+ // Make sure all week number cells running down the side have the same width.
+ // Record the width for cells created later.
+ this.weekNumberWidth = matchCellWidths(
+ this.el.find('.fc-week-number')
+ );
}
- var cellOffset = row * colCnt + (col * dis + dit); // column, adjusted for RTL (dis & dit)
-
- return cellOffset;
- }
+ },
- // cell offset -> day offset
- function cellOffsetToDayOffset(cellOffset) {
- var day0 = t.start.day(); // first date's day of week
- cellOffset += dayToCellMap[day0]; // normlize cellOffset to beginning-of-week
- return Math.floor(cellOffset / cellsPerWeek) * 7 + // # of days from full weeks
- cellToDayMap[ // # of days from partial last week
- (cellOffset % cellsPerWeek + cellsPerWeek) % cellsPerWeek // crazy math to handle negative cellOffsets
- ] -
- day0; // adjustment for beginning-of-week normalization
- }
- // day offset -> date
- function dayOffsetToDate(dayOffset) {
- return t.start.clone().add('days', dayOffset);
- }
+ // Adjusts the vertical dimensions of the view to the specified values
+ setHeight: function(totalHeight, isAuto) {
+ var eventLimit = this.opt('eventLimit');
+ var scrollerHeight;
+ // reset all heights to be natural
+ unsetScroller(this.scrollerEl);
+ uncompensateScroll(this.headRowEl);
- //
- // TRANSFORMATIONS: date -> day offset -> cell offset -> cell
- //
+ this.dayGrid.removeSegPopover(); // kill the "more" popover if displayed
- // date -> cell (combines all transformations)
- function dateToCell(date) {
- var dayOffset = dateToDayOffset(date);
- var cellOffset = dayOffsetToCellOffset(dayOffset);
- var cell = cellOffsetToCell(cellOffset);
- return cell;
- }
+ // is the event limit a constant level number?
+ if (eventLimit && typeof eventLimit === 'number') {
+ this.dayGrid.limitRows(eventLimit); // limit the levels first so the height can redistribute after
+ }
- // date -> day offset
- function dateToDayOffset(date) {
- return date.clone().stripTime().diff(t.start, 'days');
- }
+ scrollerHeight = this.computeScrollerHeight(totalHeight);
+ this.setGridHeight(scrollerHeight, isAuto);
- // day offset -> cell offset
- function dayOffsetToCellOffset(dayOffset) {
- var day0 = t.start.day(); // first date's day of week
- dayOffset += day0; // normalize dayOffset to beginning-of-week
- return Math.floor(dayOffset / 7) * cellsPerWeek + // # of cells from full weeks
- dayToCellMap[ // # of cells from partial last week
- (dayOffset % 7 + 7) % 7 // crazy math to handle negative dayOffsets
- ] -
- dayToCellMap[day0]; // adjustment for beginning-of-week normalization
- }
+ // is the event limit dynamically calculated?
+ if (eventLimit && typeof eventLimit !== 'number') {
+ this.dayGrid.limitRows(eventLimit); // limit the levels after the grid's row heights have been set
+ }
- // cell offset -> cell (object with row & col keys)
- function cellOffsetToCell(cellOffset) {
- var colCnt = t.getColCnt();
+ if (!isAuto && setPotentialScroller(this.scrollerEl, scrollerHeight)) { // using scrollbars?
- // rtl variables. wish we could pre-populate these. but where?
- var dis = isRTL ? -1 : 1;
- var dit = isRTL ? colCnt - 1 : 0;
+ compensateScroll(this.headRowEl, getScrollbarWidths(this.scrollerEl));
- var row = Math.floor(cellOffset / colCnt);
- var col = ((cellOffset % colCnt + colCnt) % colCnt) * dis + dit; // column, adjusted for RTL (dis & dit)
- return {
- row: row,
- col: col
- };
- }
+ // doing the scrollbar compensation might have created text overflow which created more height. redo
+ scrollerHeight = this.computeScrollerHeight(totalHeight);
+ this.scrollerEl.height(scrollerHeight);
+ }
+ },
- //
- // Converts a date range into an array of segment objects.
- // "Segments" are horizontal stretches of time, sliced up by row.
- // A segment object has the following properties:
- // - row
- // - cols
- // - isStart
- // - isEnd
- //
- function rangeToSegments(start, end) {
-
- var rowCnt = t.getRowCnt();
- var colCnt = t.getColCnt();
- var segments = []; // array of segments to return
-
- // day offset for given date range
- var rangeDayOffsetStart = dateToDayOffset(start);
- var rangeDayOffsetEnd = dateToDayOffset(end); // an exclusive value
- var endTimeMS = +end.time();
- if (endTimeMS && endTimeMS >= nextDayThreshold) {
- rangeDayOffsetEnd++;
- }
- rangeDayOffsetEnd = Math.max(rangeDayOffsetEnd, rangeDayOffsetStart + 1);
-
- // first and last cell offset for the given date range
- // "last" implies inclusivity
- var rangeCellOffsetFirst = dayOffsetToCellOffset(rangeDayOffsetStart);
- var rangeCellOffsetLast = dayOffsetToCellOffset(rangeDayOffsetEnd) - 1;
-
- // loop through all the rows in the view
- for (var row=0; row") : finalContainer;
- var segments = buildSegments(events);
- var html;
- var elements;
+ /* Selection
+ ------------------------------------------------------------------------------------------------------------------*/
- // calculate the desired `left` and `width` properties on each segment object
- calculateHorizontals(segments);
- // build the HTML string. relies on `left` property
- html = buildHTML(segments);
+ // Renders a visual indication of a selection
+ renderSelection: function(range) {
+ this.dayGrid.renderSelection(range);
+ },
- // render the HTML. innerHTML is considerably faster than jQuery's .html()
- renderContainer[0].innerHTML = html;
- // retrieve the individual elements
- elements = renderContainer.children();
+ // Unrenders a visual indications of a selection
+ unrenderSelection: function() {
+ this.dayGrid.unrenderSelection();
+ }
- // if we were appending, and thus using a temporary container,
- // re-attach elements to the real container.
- if (doAppend) {
- finalContainer.append(elements);
- }
+});
- // assigns each element to `segment.event`, after filtering them through user callbacks
- resolveElements(segments, elements);
+;;
- // Calculate the left and right padding+margin for each element.
- // We need this for setting each element's desired outer width, because of the W3C box model.
- // It's important we do this in a separate pass from acually setting the width on the DOM elements
- // because alternating reading/writing dimensions causes reflow for every iteration.
- segmentElementEach(segments, function(segment, element) {
- segment.hsides = hsides(element, true); // include margins = `true`
- });
+/* A month view with day cells running in rows (one-per-week) and columns
+----------------------------------------------------------------------------------------------------------------------*/
- // Set the width of each element
- segmentElementEach(segments, function(segment, element) {
- element.width(
- Math.max(0, segment.outerWidth - segment.hsides)
- );
- });
+var MonthView = BasicView.extend({
- // Grab each element's outerHeight (setVerticals uses this).
- // To get an accurate reading, it's important to have each element's width explicitly set already.
- segmentElementEach(segments, function(segment, element) {
- segment.outerHeight = element.outerHeight(true); // include margins = `true`
- });
+ // Produces information about what range to display
+ computeRange: function(date) {
+ var range = BasicView.prototype.computeRange.call(this, date); // get value from super-method
+ var rowCnt;
- // Set the top coordinate on each element (requires segment.outerHeight)
- setVerticals(segments, doRowHeights);
+ // ensure 6 weeks
+ if (this.isFixedWeeks()) {
+ rowCnt = Math.ceil(range.end.diff(range.start, 'weeks', true)); // could be partial weeks due to hiddenDays
+ range.end.add(6 - rowCnt, 'weeks');
+ }
- return segments;
- }
+ return range;
+ },
- // Generate an array of "segments" for all events.
- function buildSegments(events) {
- var segments = [];
- for (var i=0; i" +
- "";
- if (!event.allDay && segment.isStart) {
- html +=
- "" +
- htmlEscape(t.getEventTimeText(event)) +
- "";
- }
- html +=
- "" +
- htmlEscape(event.title || '') +
- "" +
- " ";
- if (event.allDay && segment.isEnd && isEventResizable(event)) {
- html +=
- "" +
- " " + // makes hit area a lot better for IE6/7
- " ";
- }
- html += "" + (url ? "a" : "div") + ">";
+/* An abstract class for all agenda-related views. Displays one more columns with time slots running vertically.
+----------------------------------------------------------------------------------------------------------------------*/
+// Is a manager for the TimeGrid subcomponent and possibly the DayGrid subcomponent (if allDaySlot is on).
+// Responsible for managing width/height.
- // TODO:
- // When these elements are initially rendered, they will be briefly visibile on the screen,
- // even though their widths/heights are not set.
- // SOLUTION: initially set them as visibility:hidden ?
+var AgendaView = View.extend({
- return html;
- }
+ timeGrid: null, // the main time-grid subcomponent of this view
+ dayGrid: null, // the "all-day" subcomponent. if all-day is turned off, this will be null
+ axisWidth: null, // the width of the time axis running down the side
- // Associate each segment (an object) with an element (a jQuery object),
- // by setting each `segment.element`.
- // Run each element through the `eventRender` filter, which allows developers to
- // modify an existing element, supply a new one, or cancel rendering.
- function resolveElements(segments, elements) {
- for (var i=0; i underneath
+ bottomRuleEl: null,
+ bottomRuleHeight: null,
- if (triggerRes === false) {
- // if `false`, remove the event from the DOM and don't assign it to `segment.event`
- element.remove();
- }
- else {
- if (triggerRes && triggerRes !== true) {
- // the trigger returned a new element, but not `true` (which means keep the existing element)
-
- // re-assign the important CSS dimension properties that were already assigned in `buildHTMLForSegment`
- triggerRes = $(triggerRes)
- .css({
- position: 'absolute',
- left: segment.left
- });
-
- element.replaceWith(triggerRes);
- element = triggerRes;
- }
- segment.element = element;
- }
- }
- }
+ initialize: function() {
+ this.timeGrid = new TimeGrid(this);
+ if (this.opt('allDaySlot')) { // should we display the "all-day" area?
+ this.dayGrid = new DayGrid(this); // the all-day subcomponent of this view
+ // the coordinate grid will be a combination of both subcomponents' grids
+ this.coordMap = new ComboCoordMap([
+ this.dayGrid.coordMap,
+ this.timeGrid.coordMap
+ ]);
+ }
+ else {
+ this.coordMap = this.timeGrid.coordMap;
+ }
+ },
- /* Top-coordinate Methods
- -------------------------------------------------------------------------------------------------*/
+ /* Rendering
+ ------------------------------------------------------------------------------------------------------------------*/
- // Sets the "top" CSS property for each element.
- // If `doRowHeights` is `true`, also sets each row's first cell to an explicit height,
- // so that if elements vertically overflow, the cell expands vertically to compensate.
- function setVerticals(segments, doRowHeights) {
- var rowContentHeights = calculateVerticals(segments); // also sets segment.top
- var rowContentElements = getRowContentElements(); // returns 1 inner div per row
- var rowContentTops = [];
- var i;
- // Set each row's height by setting height of first inner div
- if (doRowHeights) {
- for (i=0; i that sometimes displays under the time-grid
+ this.bottomRuleEl = $('')
+ .appendTo(this.timeGrid.el); // inject it into the time-grid
- // adjust the columns to account for the segment's height
- for (colI=segment.leftCol; colI<=segment.rightCol; colI++) {
- colHeights[colI] = segment.top + segment.outerHeight;
- }
- }
+ if (this.dayGrid) {
+ this.dayGrid.setElement(this.el.find('.fc-day-grid'));
+ this.dayGrid.renderDates();
- // the tallest column in the row should be the "content height"
- rowContentHeights.push(arrayMax(colHeights));
+ // have the day-grid extend it's coordinate area over the dividing the two grids
+ this.dayGrid.bottomCoordPadding = this.dayGrid.el.next('hr').outerHeight();
}
- return rowContentHeights;
- }
+ this.noScrollRowEls = this.el.find('.fc-row:not(.fc-scroller *)'); // fake rows not within the scroller
+ },
- // Build an array of segment arrays, each representing the segments that will
- // be in a row of the grid, sorted by which event should be closest to the top.
- function buildSegmentRows(segments) {
- var rowCnt = getRowCnt();
- var segmentRows = [];
- var segmentI;
- var segment;
- var rowI;
+ // Unrenders the content of the view. Since we haven't separated skeleton rendering from date rendering,
+ // always completely kill each grid's rendering.
+ unrenderDates: function() {
+ this.timeGrid.unrenderDates();
+ this.timeGrid.removeElement();
- // group segments by row
- for (segmentI=0; segmentI' +
+ '' +
+ '' +
+ '' +
+ ' ' +
+ '' +
+ '' +
+ '' +
+ '' +
+ (this.dayGrid ?
+ '' +
+ '' :
+ ''
+ ) +
+ '' +
+ ' | ' +
+ ' ' +
+ '' +
+ ' ';
+ },
- // Sort an array of segments according to which segment should appear closest to the top
- function sortSegmentRow(segments) {
- var sortedSegments = [];
+ // Generates the HTML that will go before the day-of week header cells.
+ // Queried by the TimeGrid subcomponent when generating rows. Ordering depends on isRTL.
+ headIntroHtml: function() {
+ var date;
+ var weekText;
- // build the subrow array
- var subrows = buildSegmentSubrows(segments);
+ if (this.opt('weekNumbers')) {
+ date = this.timeGrid.getCell(0).start;
+ weekText = date.format(this.opt('smallWeekFormat'));
- // flatten it
- for (var i=0; i | ';
+ },
- // loop through subrows, starting with the topmost, until the segment
- // doesn't collide with other segments.
- for (var j=0; j';
+ },
- // Return an array of jQuery objects for the placeholder content containers of each row.
- // The content containers don't actually contain anything, but their dimensions should match
- // the events that are overlaid on top.
- function getRowContentElements() {
- var i;
- var rowCnt = getRowCnt();
- var rowDivs = [];
- for (i=0; i div');
+ // Generates an HTML attribute string for setting the width of the axis, if it is known
+ axisStyleAttr: function() {
+ if (this.axisWidth !== null) {
+ return 'style="width:' + this.axisWidth + 'px"';
}
- return rowDivs;
- }
+ return '';
+ },
+ /* Dimensions
+ ------------------------------------------------------------------------------------------------------------------*/
- /* Mouse Handlers
- ---------------------------------------------------------------------------------------------------*/
- // TODO: better documentation!
+ updateSize: function(isResize) {
+ this.timeGrid.updateSize(isResize);
- function attachHandlers(segments, modifiedEventId) {
- var segmentContainer = getDaySegmentContainer();
+ View.prototype.updateSize.call(this, isResize); // call the super-method
+ },
- segmentElementEach(segments, function(segment, element, i) {
- var event = segment.event;
- if (event._id === modifiedEventId) {
- bindDaySeg(event, element, segment);
- }else{
- element[0]._fci = i; // for lazySegBind
- }
- });
- lazySegBind(segmentContainer, segments, bindDaySeg);
- }
+ // Refreshes the horizontal dimensions of the view
+ updateWidth: function() {
+ // make all axis cells line up, and record the width so newly created axis cells will have it
+ this.axisWidth = matchCellWidths(this.el.find('.fc-axis'));
+ },
- function bindDaySeg(event, eventElement, segment) {
+ // Adjusts the vertical dimensions of the view to the specified values
+ setHeight: function(totalHeight, isAuto) {
+ var eventLimit;
+ var scrollerHeight;
- if (isEventDraggable(event)) {
- t.draggableDayEvent(event, eventElement, segment); // use `t` so subclasses can override
+ if (this.bottomRuleHeight === null) {
+ // calculate the height of the rule the very first time
+ this.bottomRuleHeight = this.bottomRuleEl.outerHeight();
}
+ this.bottomRuleEl.hide(); // .show() will be called later if this
is necessary
- if (
- event.allDay &&
- segment.isEnd && // only allow resizing on the final segment for an event
- isEventResizable(event)
- ) {
- t.resizableDayEvent(event, eventElement, segment); // use `t` so subclasses can override
- }
-
- // attach all other handlers.
- // needs to be after, because resizableDayEvent might stopImmediatePropagation on click
- eventElementHandlers(event, eventElement);
- }
-
-
- function draggableDayEvent(event, eventElement) {
- var hoverListener = getHoverListener();
- var dayDelta;
- var eventStart;
- eventElement.draggable({
- delay: 50,
- opacity: opt('dragOpacity'),
- revertDuration: opt('dragRevertDuration'),
- start: function(ev, ui) {
- trigger('eventDragStart', eventElement[0], event, ev, ui);
- hideEvents(event, eventElement);
- hoverListener.start(function(cell, origCell, rowDelta, colDelta) {
- eventElement.draggable('option', 'revert', !cell || !rowDelta && !colDelta);
- clearOverlays();
- if (cell) {
- var origCellDate = cellToDate(origCell);
- var cellDate = cellToDate(cell);
- dayDelta = cellDate.diff(origCellDate, 'days');
- eventStart = event.start.clone().add('days', dayDelta);
- renderDayOverlay(
- eventStart,
- getEventEnd(event).add('days', dayDelta)
- );
- }
- else {
- dayDelta = 0;
- }
- }, ev, 'drag');
- },
- stop: function(ev, ui) {
- hoverListener.stop();
- clearOverlays();
- trigger('eventDragStop', eventElement[0], event, ev, ui);
- if (dayDelta) {
- eventDrop(
- eventElement[0],
- event,
- eventStart,
- ev,
- ui
- );
- }
- else {
- eventElement.css('filter', ''); // clear IE opacity side-effects
- showEvents(event, eventElement);
- }
- }
- });
- }
+ // reset all dimensions back to the original state
+ this.scrollerEl.css('overflow', '');
+ unsetScroller(this.scrollerEl);
+ uncompensateScroll(this.noScrollRowEls);
-
- function resizableDayEvent(event, element, segment) {
- var isRTL = opt('isRTL');
- var direction = isRTL ? 'w' : 'e';
- var handle = element.find('.ui-resizable-' + direction); // TODO: stop using this class because we aren't using jqui for this
- var isResizing = false;
-
- // TODO: look into using jquery-ui mouse widget for this stuff
- disableTextSelection(element); // prevent native selection for IE
- element
- .mousedown(function(ev) { // prevent native selection for others
- ev.preventDefault();
- })
- .click(function(ev) {
- if (isResizing) {
- ev.preventDefault(); // prevent link from being visited (only method that worked in IE6)
- ev.stopImmediatePropagation(); // prevent fullcalendar eventClick handler from being called
- // (eventElementHandlers needs to be bound after resizableDayEvent)
- }
- });
-
- handle.mousedown(function(ev) {
- if (ev.which != 1) {
- return; // needs to be left mouse button
- }
- isResizing = true;
- var hoverListener = getHoverListener();
- var elementTop = element.css('top');
- var dayDelta;
- var eventEnd;
- var helpers;
- var eventCopy = $.extend({}, event);
- var minCellOffset = dayOffsetToCellOffset(dateToDayOffset(event.start));
- clearSelection();
- $('body')
- .css('cursor', direction + '-resize')
- .one('mouseup', mouseup);
- trigger('eventResizeStart', element[0], event, ev, {}); // {} is dummy jqui event
- hoverListener.start(function(cell, origCell) {
- if (cell) {
-
- var origCellOffset = cellToCellOffset(origCell);
- var cellOffset = cellToCellOffset(cell);
-
- // don't let resizing move earlier than start date cell
- cellOffset = Math.max(cellOffset, minCellOffset);
-
- dayDelta =
- cellOffsetToDayOffset(cellOffset) -
- cellOffsetToDayOffset(origCellOffset);
-
- eventEnd = getEventEnd(event).add('days', dayDelta); // assumed to already have a stripped time
-
- if (dayDelta) {
- eventCopy.end = eventEnd;
- var oldHelpers = helpers;
- helpers = renderTempDayEvent(eventCopy, segment.row, elementTop);
- helpers = $(helpers); // turn array into a jQuery object
- helpers.find('*').css('cursor', direction + '-resize');
- if (oldHelpers) {
- oldHelpers.remove();
- }
- hideEvents(event);
- }
- else {
- if (helpers) {
- showEvents(event);
- helpers.remove();
- helpers = null;
- }
- }
+ // limit number of events in the all-day area
+ if (this.dayGrid) {
+ this.dayGrid.removeSegPopover(); // kill the "more" popover if displayed
- clearOverlays();
- renderDayOverlay( // coordinate grid already rebuilt with hoverListener.start()
- event.start,
- eventEnd
- // TODO: instead of calling renderDayOverlay() with dates,
- // call _renderDayOverlay (or whatever) with cell offsets.
- );
- }
- }, ev);
-
- function mouseup(ev) {
- trigger('eventResizeStop', element[0], event, ev, {}); // {} is dummy jqui event
- $('body').css('cursor', '');
- hoverListener.stop();
- clearOverlays();
-
- if (dayDelta) {
- eventResize(
- element[0],
- event,
- eventEnd,
- ev,
- {} // dummy jqui event
- );
- // event redraw will clear helpers
- }
- // otherwise, the drag handler already restored the old events
-
- setTimeout(function() { // make this happen after the element's click event
- isResizing = false;
- },0);
+ eventLimit = this.opt('eventLimit');
+ if (eventLimit && typeof eventLimit !== 'number') {
+ eventLimit = AGENDA_ALL_DAY_EVENT_LIMIT; // make sure "auto" goes to a real number
}
- });
- }
-
+ if (eventLimit) {
+ this.dayGrid.limitRows(eventLimit);
+ }
+ }
-}
+ if (!isAuto) { // should we force dimensions of the scroll container, or let the contents be natural height?
+ scrollerHeight = this.computeScrollerHeight(totalHeight);
+ if (setPotentialScroller(this.scrollerEl, scrollerHeight)) { // using scrollbars?
+ // make the all-day and header rows lines up
+ compensateScroll(this.noScrollRowEls, getScrollbarWidths(this.scrollerEl));
-/* Generalized Segment Utilities
--------------------------------------------------------------------------------------------------*/
+ // the scrollbar compensation might have changed text flow, which might affect height, so recalculate
+ // and reapply the desired height to the scroller.
+ scrollerHeight = this.computeScrollerHeight(totalHeight);
+ this.scrollerEl.height(scrollerHeight);
+ }
+ else { // no scrollbars
+ // still, force a height and display the bottom rule (marks the end of day)
+ this.scrollerEl.height(scrollerHeight).css('overflow', 'hidden'); // in case
goes outside
+ this.bottomRuleEl.show();
+ }
+ }
+ },
-function isDaySegmentCollision(segment, otherSegments) {
- for (var i=0; i= segment.leftCol
- ) {
- return true;
- }
- }
- return false;
-}
+ // Computes the initial pre-configured scroll state prior to allowing the user to change it
+ computeInitialScroll: function() {
+ var scrollTime = moment.duration(this.opt('scrollTime'));
+ var top = this.timeGrid.computeTimeTop(scrollTime);
+ // zoom can give weird floating-point values. rather scroll a little bit further
+ top = Math.ceil(top);
-function segmentElementEach(segments, callback) { // TODO: use in AgendaView?
- for (var i=0; i");
+ // A returned value of `true` signals that a mock "helper" event has been rendered.
+ renderDrag: function(dropLocation, seg) {
+ if (dropLocation.start.hasTime()) {
+ return this.timeGrid.renderDrag(dropLocation, seg);
}
- if (e[0].parentNode != parent[0]) {
- e.appendTo(parent);
+ else if (this.dayGrid) {
+ return this.dayGrid.renderDrag(dropLocation, seg);
}
- usedOverlays.push(e.css(rect).show());
- return e;
- }
-
+ },
- function clearOverlays() {
- var e;
- while ((e = usedOverlays.shift())) {
- unusedOverlays.push(e.hide().unbind());
- }
- }
+ unrenderDrag: function() {
+ this.timeGrid.unrenderDrag();
+ if (this.dayGrid) {
+ this.dayGrid.unrenderDrag();
+ }
+ },
-}
-;;
+ /* Selection
+ ------------------------------------------------------------------------------------------------------------------*/
-function CoordinateGrid(buildFunc) {
- var t = this;
- var rows;
- var cols;
-
-
- t.build = function() {
- rows = [];
- cols = [];
- buildFunc(rows, cols);
- };
-
-
- t.cell = function(x, y) {
- var rowCnt = rows.length;
- var colCnt = cols.length;
- var i, r=-1, c=-1;
- for (i=0; i= rows[i][0] && y < rows[i][1]) {
- r = i;
- break;
- }
+ // Renders a visual indication of a selection
+ renderSelection: function(range) {
+ if (range.start.hasTime() || range.end.hasTime()) {
+ this.timeGrid.renderSelection(range);
}
- for (i=0; i= cols[i][0] && x < cols[i][1]) {
- c = i;
- break;
- }
+ else if (this.dayGrid) {
+ this.dayGrid.renderSelection(range);
}
- return (r>=0 && c>=0) ? { row: r, col: c } : null;
- };
-
-
- t.rect = function(row0, col0, row1, col1, originElement) { // row1,col1 is inclusive
- var origin = originElement.offset();
- return {
- top: rows[row0][0] - origin.top,
- left: cols[col0][0] - origin.left,
- width: cols[col1][1] - cols[col0][0],
- height: rows[row1][1] - rows[row0][0]
- };
- };
-
-}
-
-;;
-
-function HoverListener(coordinateGrid) {
+ },
- var t = this;
- var bindType;
- var change;
- var firstCell;
- var cell;
-
-
- t.start = function(_change, ev, _bindType) {
- change = _change;
- firstCell = cell = null;
- coordinateGrid.build();
- mouse(ev);
- bindType = _bindType || 'mousemove';
- $(document).bind(bindType, mouse);
- };
-
-
- function mouse(ev) {
- _fixUIEvent(ev); // see below
- var newCell = coordinateGrid.cell(ev.pageX, ev.pageY);
- if (
- Boolean(newCell) !== Boolean(cell) ||
- newCell && (newCell.row != cell.row || newCell.col != cell.col)
- ) {
- if (newCell) {
- if (!firstCell) {
- firstCell = newCell;
- }
- change(newCell, firstCell, newCell.row-firstCell.row, newCell.col-firstCell.col);
- }else{
- change(newCell, firstCell);
- }
- cell = newCell;
+ // Unrenders a visual indications of a selection
+ unrenderSelection: function() {
+ this.timeGrid.unrenderSelection();
+ if (this.dayGrid) {
+ this.dayGrid.unrenderSelection();
}
}
-
-
- t.stop = function() {
- $(document).unbind(bindType, mouse);
- return cell;
- };
-
-
-}
-
-
-// this fix was only necessary for jQuery UI 1.8.16 (and jQuery 1.7 or 1.7.1)
-// upgrading to jQuery UI 1.8.17 (and using either jQuery 1.7 or 1.7.1) fixed the problem
-// but keep this in here for 1.8.16 users
-// and maybe remove it down the line
+});
-function _fixUIEvent(event) { // for issue 1168
- if (event.pageX === undefined) {
- event.pageX = event.originalEvent.pageX;
- event.pageY = event.originalEvent.pageY;
- }
-}
;;
-function HorizontalPositionCache(getElement) {
+var AGENDA_ALL_DAY_EVENT_LIMIT = 5;
- var t = this,
- elements = {},
- lefts = {},
- rights = {};
-
- function e(i) {
- return (elements[i] = (elements[i] || getElement(i)));
+fcViews.agenda = {
+ 'class': AgendaView,
+ defaults: {
+ allDaySlot: true,
+ allDayText: 'all-day',
+ slotDuration: '00:30:00',
+ minTime: '00:00:00',
+ maxTime: '24:00:00',
+ slotEventOverlap: true // a bad name. confused with overlap/constraint system
}
-
- t.left = function(i) {
- return (lefts[i] = (lefts[i] === undefined ? e(i).position().left : lefts[i]));
- };
-
- t.right = function(i) {
- return (rights[i] = (rights[i] === undefined ? t.left(i) + e(i).width() : rights[i]));
- };
-
- t.clear = function() {
- elements = {};
- lefts = {};
- rights = {};
- };
-
-}
+};
+
+fcViews.agendaDay = {
+ type: 'agenda',
+ duration: { days: 1 }
+};
+fcViews.agendaWeek = {
+ type: 'agenda',
+ duration: { weeks: 1 }
+};
;;
+return fc; // export for Node/CommonJS
});
\ No newline at end of file
diff --git a/src/UI/JsLibraries/lang/placeholder.txt b/src/UI/JsLibraries/locale/placeholder.txt
similarity index 100%
rename from src/UI/JsLibraries/lang/placeholder.txt
rename to src/UI/JsLibraries/locale/placeholder.txt
diff --git a/src/UI/JsLibraries/moment.js b/src/UI/JsLibraries/moment.js
index 83282c6fd..275a3c324 100644
--- a/src/UI/JsLibraries/moment.js
+++ b/src/UI/JsLibraries/moment.js
@@ -1,578 +1,191 @@
//! moment.js
-//! version : 2.7.0
+//! version : 2.10.3
//! authors : Tim Wood, Iskren Chernev, Moment.js contributors
//! license : MIT
//! momentjs.com
-(function (undefined) {
-
- /************************************
- Constants
- ************************************/
-
- var moment,
- VERSION = "2.7.0",
- // the global-scope this is NOT the global object in Node.js
- globalScope = typeof global !== 'undefined' ? global : this,
- oldGlobalMoment,
- round = Math.round,
- i,
-
- YEAR = 0,
- MONTH = 1,
- DATE = 2,
- HOUR = 3,
- MINUTE = 4,
- SECOND = 5,
- MILLISECOND = 6,
-
- // internal storage for language config files
- languages = {},
-
- // moment internal properties
- momentProperties = {
- _isAMomentObject: null,
- _i : null,
- _f : null,
- _l : null,
- _strict : null,
- _tzm : null,
- _isUTC : null,
- _offset : null, // optional. Combine with _isUTC
- _pf : null,
- _lang : null // optional
- },
-
- // check for nodeJS
- hasModule = (typeof module !== 'undefined' && module.exports),
-
- // ASP.NET json date format regex
- aspNetJsonRegex = /^\/?Date\((\-?\d+)/i,
- aspNetTimeSpanJsonRegex = /(\-)?(?:(\d*)\.)?(\d+)\:(\d+)(?:\:(\d+)\.?(\d{3})?)?/,
-
- // from http://docs.closure-library.googlecode.com/git/closure_goog_date_date.js.source.html
- // somewhat more in line with 4.4.3.2 2004 spec, but allows decimal anywhere
- isoDurationRegex = /^(-)?P(?:(?:([0-9,.]*)Y)?(?:([0-9,.]*)M)?(?:([0-9,.]*)D)?(?:T(?:([0-9,.]*)H)?(?:([0-9,.]*)M)?(?:([0-9,.]*)S)?)?|([0-9,.]*)W)$/,
-
- // format tokens
- formattingTokens = /(\[[^\[]*\])|(\\)?(Mo|MM?M?M?|Do|DDDo|DD?D?D?|ddd?d?|do?|w[o|w]?|W[o|W]?|Q|YYYYYY|YYYYY|YYYY|YY|gg(ggg?)?|GG(GGG?)?|e|E|a|A|hh?|HH?|mm?|ss?|S{1,4}|X|zz?|ZZ?|.)/g,
- localFormattingTokens = /(\[[^\[]*\])|(\\)?(LT|LL?L?L?|l{1,4})/g,
-
- // parsing token regexes
- parseTokenOneOrTwoDigits = /\d\d?/, // 0 - 99
- parseTokenOneToThreeDigits = /\d{1,3}/, // 0 - 999
- parseTokenOneToFourDigits = /\d{1,4}/, // 0 - 9999
- parseTokenOneToSixDigits = /[+\-]?\d{1,6}/, // -999,999 - 999,999
- parseTokenDigits = /\d+/, // nonzero number of digits
- parseTokenWord = /[0-9]*['a-z\u00A0-\u05FF\u0700-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]+|[\u0600-\u06FF\/]+(\s*?[\u0600-\u06FF]+){1,2}/i, // any word (or two) characters or numbers including two/three word month in arabic.
- parseTokenTimezone = /Z|[\+\-]\d\d:?\d\d/gi, // +00:00 -00:00 +0000 -0000 or Z
- parseTokenT = /T/i, // T (ISO separator)
- parseTokenTimestampMs = /[\+\-]?\d+(\.\d{1,3})?/, // 123456789 123456789.123
- parseTokenOrdinal = /\d{1,2}/,
-
- //strict parsing regexes
- parseTokenOneDigit = /\d/, // 0 - 9
- parseTokenTwoDigits = /\d\d/, // 00 - 99
- parseTokenThreeDigits = /\d{3}/, // 000 - 999
- parseTokenFourDigits = /\d{4}/, // 0000 - 9999
- parseTokenSixDigits = /[+-]?\d{6}/, // -999,999 - 999,999
- parseTokenSignedNumber = /[+-]?\d+/, // -inf - inf
-
- // iso 8601 regex
- // 0000-00-00 0000-W00 or 0000-W00-0 + T + 00 or 00:00 or 00:00:00 or 00:00:00.000 + +00:00 or +0000 or +00)
- isoRegex = /^\s*(?:[+-]\d{6}|\d{4})-(?:(\d\d-\d\d)|(W\d\d$)|(W\d\d-\d)|(\d\d\d))((T| )(\d\d(:\d\d(:\d\d(\.\d+)?)?)?)?([\+\-]\d\d(?::?\d\d)?|\s*Z)?)?$/,
-
- isoFormat = 'YYYY-MM-DDTHH:mm:ssZ',
-
- isoDates = [
- ['YYYYYY-MM-DD', /[+-]\d{6}-\d{2}-\d{2}/],
- ['YYYY-MM-DD', /\d{4}-\d{2}-\d{2}/],
- ['GGGG-[W]WW-E', /\d{4}-W\d{2}-\d/],
- ['GGGG-[W]WW', /\d{4}-W\d{2}/],
- ['YYYY-DDD', /\d{4}-\d{3}/]
- ],
-
- // iso time formats and regexes
- isoTimes = [
- ['HH:mm:ss.SSSS', /(T| )\d\d:\d\d:\d\d\.\d+/],
- ['HH:mm:ss', /(T| )\d\d:\d\d:\d\d/],
- ['HH:mm', /(T| )\d\d:\d\d/],
- ['HH', /(T| )\d\d/]
- ],
-
- // timezone chunker "+10:00" > ["10", "00"] or "-1530" > ["-15", "30"]
- parseTimezoneChunker = /([\+\-]|\d\d)/gi,
-
- // getter and setter names
- proxyGettersAndSetters = 'Date|Hours|Minutes|Seconds|Milliseconds'.split('|'),
- unitMillisecondFactors = {
- 'Milliseconds' : 1,
- 'Seconds' : 1e3,
- 'Minutes' : 6e4,
- 'Hours' : 36e5,
- 'Days' : 864e5,
- 'Months' : 2592e6,
- 'Years' : 31536e6
- },
-
- unitAliases = {
- ms : 'millisecond',
- s : 'second',
- m : 'minute',
- h : 'hour',
- d : 'day',
- D : 'date',
- w : 'week',
- W : 'isoWeek',
- M : 'month',
- Q : 'quarter',
- y : 'year',
- DDD : 'dayOfYear',
- e : 'weekday',
- E : 'isoWeekday',
- gg: 'weekYear',
- GG: 'isoWeekYear'
- },
-
- camelFunctions = {
- dayofyear : 'dayOfYear',
- isoweekday : 'isoWeekday',
- isoweek : 'isoWeek',
- weekyear : 'weekYear',
- isoweekyear : 'isoWeekYear'
- },
-
- // format function strings
- formatFunctions = {},
-
- // default relative time thresholds
- relativeTimeThresholds = {
- s: 45, //seconds to minutes
- m: 45, //minutes to hours
- h: 22, //hours to days
- dd: 25, //days to month (month == 1)
- dm: 45, //days to months (months > 1)
- dy: 345 //days to year
- },
-
- // tokens to ordinalize and pad
- ordinalizeTokens = 'DDD w W M D d'.split(' '),
- paddedTokens = 'M D H h m s w W'.split(' '),
-
- formatTokenFunctions = {
- M : function () {
- return this.month() + 1;
- },
- MMM : function (format) {
- return this.lang().monthsShort(this, format);
- },
- MMMM : function (format) {
- return this.lang().months(this, format);
- },
- D : function () {
- return this.date();
- },
- DDD : function () {
- return this.dayOfYear();
- },
- d : function () {
- return this.day();
- },
- dd : function (format) {
- return this.lang().weekdaysMin(this, format);
- },
- ddd : function (format) {
- return this.lang().weekdaysShort(this, format);
- },
- dddd : function (format) {
- return this.lang().weekdays(this, format);
- },
- w : function () {
- return this.week();
- },
- W : function () {
- return this.isoWeek();
- },
- YY : function () {
- return leftZeroFill(this.year() % 100, 2);
- },
- YYYY : function () {
- return leftZeroFill(this.year(), 4);
- },
- YYYYY : function () {
- return leftZeroFill(this.year(), 5);
- },
- YYYYYY : function () {
- var y = this.year(), sign = y >= 0 ? '+' : '-';
- return sign + leftZeroFill(Math.abs(y), 6);
- },
- gg : function () {
- return leftZeroFill(this.weekYear() % 100, 2);
- },
- gggg : function () {
- return leftZeroFill(this.weekYear(), 4);
- },
- ggggg : function () {
- return leftZeroFill(this.weekYear(), 5);
- },
- GG : function () {
- return leftZeroFill(this.isoWeekYear() % 100, 2);
- },
- GGGG : function () {
- return leftZeroFill(this.isoWeekYear(), 4);
- },
- GGGGG : function () {
- return leftZeroFill(this.isoWeekYear(), 5);
- },
- e : function () {
- return this.weekday();
- },
- E : function () {
- return this.isoWeekday();
- },
- a : function () {
- return this.lang().meridiem(this.hours(), this.minutes(), true);
- },
- A : function () {
- return this.lang().meridiem(this.hours(), this.minutes(), false);
- },
- H : function () {
- return this.hours();
- },
- h : function () {
- return this.hours() % 12 || 12;
- },
- m : function () {
- return this.minutes();
- },
- s : function () {
- return this.seconds();
- },
- S : function () {
- return toInt(this.milliseconds() / 100);
- },
- SS : function () {
- return leftZeroFill(toInt(this.milliseconds() / 10), 2);
- },
- SSS : function () {
- return leftZeroFill(this.milliseconds(), 3);
- },
- SSSS : function () {
- return leftZeroFill(this.milliseconds(), 3);
- },
- Z : function () {
- var a = -this.zone(),
- b = "+";
- if (a < 0) {
- a = -a;
- b = "-";
- }
- return b + leftZeroFill(toInt(a / 60), 2) + ":" + leftZeroFill(toInt(a) % 60, 2);
- },
- ZZ : function () {
- var a = -this.zone(),
- b = "+";
- if (a < 0) {
- a = -a;
- b = "-";
- }
- return b + leftZeroFill(toInt(a / 60), 2) + leftZeroFill(toInt(a) % 60, 2);
- },
- z : function () {
- return this.zoneAbbr();
- },
- zz : function () {
- return this.zoneName();
- },
- X : function () {
- return this.unix();
- },
- Q : function () {
- return this.quarter();
- }
- },
-
- lists = ['months', 'monthsShort', 'weekdays', 'weekdaysShort', 'weekdaysMin'];
+(function (global, factory) {
+ typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() :
+ typeof define === 'function' && define.amd ? define(factory) :
+ global.moment = factory()
+}(this, function () { 'use strict';
- // Pick the first defined of two or three arguments. dfl comes from
- // default.
- function dfl(a, b, c) {
- switch (arguments.length) {
- case 2: return a != null ? a : b;
- case 3: return a != null ? a : b != null ? b : c;
- default: throw new Error("Implement me");
- }
- }
-
- function defaultParsingFlags() {
- // We need to deep clone this object, and es5 standard is not very
- // helpful.
- return {
- empty : false,
- unusedTokens : [],
- unusedInput : [],
- overflow : -2,
- charsLeftOver : 0,
- nullInput : false,
- invalidMonth : null,
- invalidFormat : false,
- userInvalidated : false,
- iso: false
- };
- }
+ var hookCallback;
- function deprecate(msg, fn) {
- var firstTime = true;
- function printMsg() {
- if (moment.suppressDeprecationWarnings === false &&
- typeof console !== 'undefined' && console.warn) {
- console.warn("Deprecation warning: " + msg);
- }
- }
- return extend(function () {
- if (firstTime) {
- printMsg();
- firstTime = false;
- }
- return fn.apply(this, arguments);
- }, fn);
+ function utils_hooks__hooks () {
+ return hookCallback.apply(null, arguments);
}
- function padToken(func, count) {
- return function (a) {
- return leftZeroFill(func.call(this, a), count);
- };
- }
- function ordinalizeToken(func, period) {
- return function (a) {
- return this.lang().ordinal(func.call(this, a), period);
- };
+ // This is done to register the method called with moment()
+ // without creating circular dependencies.
+ function setHookCallback (callback) {
+ hookCallback = callback;
}
- while (ordinalizeTokens.length) {
- i = ordinalizeTokens.pop();
- formatTokenFunctions[i + 'o'] = ordinalizeToken(formatTokenFunctions[i], i);
- }
- while (paddedTokens.length) {
- i = paddedTokens.pop();
- formatTokenFunctions[i + i] = padToken(formatTokenFunctions[i], 2);
+ function isArray(input) {
+ return Object.prototype.toString.call(input) === '[object Array]';
}
- formatTokenFunctions.DDDD = padToken(formatTokenFunctions.DDD, 3);
-
-
- /************************************
- Constructors
- ************************************/
-
- function Language() {
+ function isDate(input) {
+ return input instanceof Date || Object.prototype.toString.call(input) === '[object Date]';
}
- // Moment prototype object
- function Moment(config) {
- checkOverflow(config);
- extend(this, config);
+ function map(arr, fn) {
+ var res = [], i;
+ for (i = 0; i < arr.length; ++i) {
+ res.push(fn(arr[i], i));
+ }
+ return res;
}
- // Duration Constructor
- function Duration(duration) {
- var normalizedInput = normalizeObjectUnits(duration),
- years = normalizedInput.year || 0,
- quarters = normalizedInput.quarter || 0,
- months = normalizedInput.month || 0,
- weeks = normalizedInput.week || 0,
- days = normalizedInput.day || 0,
- hours = normalizedInput.hour || 0,
- minutes = normalizedInput.minute || 0,
- seconds = normalizedInput.second || 0,
- milliseconds = normalizedInput.millisecond || 0;
-
- // representation for dateAddRemove
- this._milliseconds = +milliseconds +
- seconds * 1e3 + // 1000
- minutes * 6e4 + // 1000 * 60
- hours * 36e5; // 1000 * 60 * 60
- // Because of dateAddRemove treats 24 hours as different from a
- // day when working around DST, we need to store them separately
- this._days = +days +
- weeks * 7;
- // It is impossible translate months into days without knowing
- // which months you are are talking about, so we have to store
- // it separately.
- this._months = +months +
- quarters * 3 +
- years * 12;
-
- this._data = {};
-
- this._bubble();
+ function hasOwnProp(a, b) {
+ return Object.prototype.hasOwnProperty.call(a, b);
}
- /************************************
- Helpers
- ************************************/
-
-
function extend(a, b) {
for (var i in b) {
- if (b.hasOwnProperty(i)) {
+ if (hasOwnProp(b, i)) {
a[i] = b[i];
}
}
- if (b.hasOwnProperty("toString")) {
+ if (hasOwnProp(b, 'toString')) {
a.toString = b.toString;
}
- if (b.hasOwnProperty("valueOf")) {
+ if (hasOwnProp(b, 'valueOf')) {
a.valueOf = b.valueOf;
}
return a;
}
- function cloneMoment(m) {
- var result = {}, i;
- for (i in m) {
- if (m.hasOwnProperty(i) && momentProperties.hasOwnProperty(i)) {
- result[i] = m[i];
- }
- }
+ function create_utc__createUTC (input, format, locale, strict) {
+ return createLocalOrUTC(input, format, locale, strict, true).utc();
+ }
- return result;
+ function defaultParsingFlags() {
+ // We need to deep clone this object.
+ return {
+ empty : false,
+ unusedTokens : [],
+ unusedInput : [],
+ overflow : -2,
+ charsLeftOver : 0,
+ nullInput : false,
+ invalidMonth : null,
+ invalidFormat : false,
+ userInvalidated : false,
+ iso : false
+ };
}
- function absRound(number) {
- if (number < 0) {
- return Math.ceil(number);
- } else {
- return Math.floor(number);
+ function getParsingFlags(m) {
+ if (m._pf == null) {
+ m._pf = defaultParsingFlags();
}
+ return m._pf;
}
- // left zero fill a number
- // see http://jsperf.com/left-zero-filling for performance comparison
- function leftZeroFill(number, targetLength, forceSign) {
- var output = '' + Math.abs(number),
- sign = number >= 0;
+ function valid__isValid(m) {
+ if (m._isValid == null) {
+ var flags = getParsingFlags(m);
+ m._isValid = !isNaN(m._d.getTime()) &&
+ flags.overflow < 0 &&
+ !flags.empty &&
+ !flags.invalidMonth &&
+ !flags.nullInput &&
+ !flags.invalidFormat &&
+ !flags.userInvalidated;
- while (output.length < targetLength) {
- output = '0' + output;
+ if (m._strict) {
+ m._isValid = m._isValid &&
+ flags.charsLeftOver === 0 &&
+ flags.unusedTokens.length === 0 &&
+ flags.bigHour === undefined;
+ }
}
- return (sign ? (forceSign ? '+' : '') : '-') + output;
+ return m._isValid;
}
- // helper function for _.addTime and _.subtractTime
- function addOrSubtractDurationFromMoment(mom, duration, isAdding, updateOffset) {
- var milliseconds = duration._milliseconds,
- days = duration._days,
- months = duration._months;
- updateOffset = updateOffset == null ? true : updateOffset;
-
- if (milliseconds) {
- mom._d.setTime(+mom._d + milliseconds * isAdding);
- }
- if (days) {
- rawSetter(mom, 'Date', rawGetter(mom, 'Date') + days * isAdding);
- }
- if (months) {
- rawMonthSetter(mom, rawGetter(mom, 'Month') + months * isAdding);
+ function valid__createInvalid (flags) {
+ var m = create_utc__createUTC(NaN);
+ if (flags != null) {
+ extend(getParsingFlags(m), flags);
}
- if (updateOffset) {
- moment.updateOffset(mom, days || months);
+ else {
+ getParsingFlags(m).userInvalidated = true;
}
- }
- // check if is an array
- function isArray(input) {
- return Object.prototype.toString.call(input) === '[object Array]';
+ return m;
}
- function isDate(input) {
- return Object.prototype.toString.call(input) === '[object Date]' ||
- input instanceof Date;
- }
+ var momentProperties = utils_hooks__hooks.momentProperties = [];
- // compare two arrays, return the number of differences
- function compareArrays(array1, array2, dontConvert) {
- var len = Math.min(array1.length, array2.length),
- lengthDiff = Math.abs(array1.length - array2.length),
- diffs = 0,
- i;
- for (i = 0; i < len; i++) {
- if ((dontConvert && array1[i] !== array2[i]) ||
- (!dontConvert && toInt(array1[i]) !== toInt(array2[i]))) {
- diffs++;
- }
- }
- return diffs + lengthDiff;
- }
+ function copyConfig(to, from) {
+ var i, prop, val;
- function normalizeUnits(units) {
- if (units) {
- var lowered = units.toLowerCase().replace(/(.)s$/, '$1');
- units = unitAliases[units] || camelFunctions[lowered] || lowered;
+ if (typeof from._isAMomentObject !== 'undefined') {
+ to._isAMomentObject = from._isAMomentObject;
+ }
+ if (typeof from._i !== 'undefined') {
+ to._i = from._i;
+ }
+ if (typeof from._f !== 'undefined') {
+ to._f = from._f;
+ }
+ if (typeof from._l !== 'undefined') {
+ to._l = from._l;
+ }
+ if (typeof from._strict !== 'undefined') {
+ to._strict = from._strict;
+ }
+ if (typeof from._tzm !== 'undefined') {
+ to._tzm = from._tzm;
+ }
+ if (typeof from._isUTC !== 'undefined') {
+ to._isUTC = from._isUTC;
+ }
+ if (typeof from._offset !== 'undefined') {
+ to._offset = from._offset;
+ }
+ if (typeof from._pf !== 'undefined') {
+ to._pf = getParsingFlags(from);
+ }
+ if (typeof from._locale !== 'undefined') {
+ to._locale = from._locale;
}
- return units;
- }
-
- function normalizeObjectUnits(inputObject) {
- var normalizedInput = {},
- normalizedProp,
- prop;
- for (prop in inputObject) {
- if (inputObject.hasOwnProperty(prop)) {
- normalizedProp = normalizeUnits(prop);
- if (normalizedProp) {
- normalizedInput[normalizedProp] = inputObject[prop];
+ if (momentProperties.length > 0) {
+ for (i in momentProperties) {
+ prop = momentProperties[i];
+ val = from[prop];
+ if (typeof val !== 'undefined') {
+ to[prop] = val;
}
}
}
- return normalizedInput;
+ return to;
}
- function makeList(field) {
- var count, setter;
+ var updateInProgress = false;
- if (field.indexOf('week') === 0) {
- count = 7;
- setter = 'day';
- }
- else if (field.indexOf('month') === 0) {
- count = 12;
- setter = 'month';
- }
- else {
- return;
+ // Moment prototype object
+ function Moment(config) {
+ copyConfig(this, config);
+ this._d = new Date(+config._d);
+ // Prevent infinite loop in case updateOffset creates new moment
+ // objects.
+ if (updateInProgress === false) {
+ updateInProgress = true;
+ utils_hooks__hooks.updateOffset(this);
+ updateInProgress = false;
}
+ }
- moment[field] = function (format, index) {
- var i, getter,
- method = moment.fn._lang[field],
- results = [];
-
- if (typeof format === 'number') {
- index = format;
- format = undefined;
- }
-
- getter = function (i) {
- var m = moment().utc().set(setter, i);
- return method.call(moment.fn._lang, m, format || '');
- };
-
- if (index != null) {
- return getter(index);
- }
- else {
- for (i = 0; i < count; i++) {
- results.push(getter(i));
- }
- return results;
- }
- };
+ function isMoment (obj) {
+ return obj instanceof Moment || (obj != null && obj._isAMomentObject != null);
}
function toInt(argumentForCoercion) {
@@ -590,339 +203,252 @@
return value;
}
- function daysInMonth(year, month) {
- return new Date(Date.UTC(year, month + 1, 0)).getUTCDate();
+ function compareArrays(array1, array2, dontConvert) {
+ var len = Math.min(array1.length, array2.length),
+ lengthDiff = Math.abs(array1.length - array2.length),
+ diffs = 0,
+ i;
+ for (i = 0; i < len; i++) {
+ if ((dontConvert && array1[i] !== array2[i]) ||
+ (!dontConvert && toInt(array1[i]) !== toInt(array2[i]))) {
+ diffs++;
+ }
+ }
+ return diffs + lengthDiff;
}
- function weeksInYear(year, dow, doy) {
- return weekOfYear(moment([year, 11, 31 + dow - doy]), dow, doy).week;
+ function Locale() {
}
- function daysInYear(year) {
- return isLeapYear(year) ? 366 : 365;
- }
+ var locales = {};
+ var globalLocale;
- function isLeapYear(year) {
- return (year % 4 === 0 && year % 100 !== 0) || year % 400 === 0;
+ function normalizeLocale(key) {
+ return key ? key.toLowerCase().replace('_', '-') : key;
}
- function checkOverflow(m) {
- var overflow;
- if (m._a && m._pf.overflow === -2) {
- overflow =
- m._a[MONTH] < 0 || m._a[MONTH] > 11 ? MONTH :
- m._a[DATE] < 1 || m._a[DATE] > daysInMonth(m._a[YEAR], m._a[MONTH]) ? DATE :
- m._a[HOUR] < 0 || m._a[HOUR] > 23 ? HOUR :
- m._a[MINUTE] < 0 || m._a[MINUTE] > 59 ? MINUTE :
- m._a[SECOND] < 0 || m._a[SECOND] > 59 ? SECOND :
- m._a[MILLISECOND] < 0 || m._a[MILLISECOND] > 999 ? MILLISECOND :
- -1;
+ // pick the locale from the array
+ // try ['en-au', 'en-gb'] as 'en-au', 'en-gb', 'en', as in move through the list trying each
+ // substring from most specific to least, but move to the next array item if it's a more specific variant than the current root
+ function chooseLocale(names) {
+ var i = 0, j, next, locale, split;
- if (m._pf._overflowDayOfYear && (overflow < YEAR || overflow > DATE)) {
- overflow = DATE;
+ while (i < names.length) {
+ split = normalizeLocale(names[i]).split('-');
+ j = split.length;
+ next = normalizeLocale(names[i + 1]);
+ next = next ? next.split('-') : null;
+ while (j > 0) {
+ locale = loadLocale(split.slice(0, j).join('-'));
+ if (locale) {
+ return locale;
+ }
+ if (next && next.length >= j && compareArrays(split, next, true) >= j - 1) {
+ //the next array item is better than a shallower substring of this one
+ break;
+ }
+ j--;
}
+ i++;
+ }
+ return null;
+ }
- m._pf.overflow = overflow;
+ function loadLocale(name) {
+ var oldLocale = null;
+ // TODO: Find a better way to register and load all the locales in Node
+ if (!locales[name] && typeof module !== 'undefined' &&
+ module && module.exports) {
+ try {
+ oldLocale = globalLocale._abbr;
+ require('./locale/' + name);
+ // because defineLocale currently also sets the global locale, we
+ // want to undo that for lazy loaded locales
+ locale_locales__getSetGlobalLocale(oldLocale);
+ } catch (e) { }
}
+ return locales[name];
}
- function isValid(m) {
- if (m._isValid == null) {
- m._isValid = !isNaN(m._d.getTime()) &&
- m._pf.overflow < 0 &&
- !m._pf.empty &&
- !m._pf.invalidMonth &&
- !m._pf.nullInput &&
- !m._pf.invalidFormat &&
- !m._pf.userInvalidated;
+ // This function will load locale and then set the global locale. If
+ // no arguments are passed in, it will simply return the current global
+ // locale key.
+ function locale_locales__getSetGlobalLocale (key, values) {
+ var data;
+ if (key) {
+ if (typeof values === 'undefined') {
+ data = locale_locales__getLocale(key);
+ }
+ else {
+ data = defineLocale(key, values);
+ }
- if (m._strict) {
- m._isValid = m._isValid &&
- m._pf.charsLeftOver === 0 &&
- m._pf.unusedTokens.length === 0;
+ if (data) {
+ // moment.duration._locale = moment._locale = data;
+ globalLocale = data;
}
}
- return m._isValid;
- }
- function normalizeLanguage(key) {
- return key ? key.toLowerCase().replace('_', '-') : key;
+ return globalLocale._abbr;
}
- // Return a moment from input, that is local/utc/zone equivalent to model.
- function makeAs(input, model) {
- return model._isUTC ? moment(input).zone(model._offset || 0) :
- moment(input).local();
+ function defineLocale (name, values) {
+ if (values !== null) {
+ values.abbr = name;
+ if (!locales[name]) {
+ locales[name] = new Locale();
+ }
+ locales[name].set(values);
+
+ // backwards compat for now: also set the locale
+ locale_locales__getSetGlobalLocale(name);
+
+ return locales[name];
+ } else {
+ // useful for testing
+ delete locales[name];
+ return null;
+ }
}
- /************************************
- Languages
- ************************************/
+ // returns locale data
+ function locale_locales__getLocale (key) {
+ var locale;
+ if (key && key._locale && key._locale._abbr) {
+ key = key._locale._abbr;
+ }
- extend(Language.prototype, {
+ if (!key) {
+ return globalLocale;
+ }
- set : function (config) {
- var prop, i;
- for (i in config) {
- prop = config[i];
- if (typeof prop === 'function') {
- this[i] = prop;
- } else {
- this['_' + i] = prop;
- }
+ if (!isArray(key)) {
+ //short-circuit everything else
+ locale = loadLocale(key);
+ if (locale) {
+ return locale;
}
- },
+ key = [key];
+ }
+
+ return chooseLocale(key);
+ }
- _months : "January_February_March_April_May_June_July_August_September_October_November_December".split("_"),
- months : function (m) {
- return this._months[m.month()];
- },
+ var aliases = {};
- _monthsShort : "Jan_Feb_Mar_Apr_May_Jun_Jul_Aug_Sep_Oct_Nov_Dec".split("_"),
- monthsShort : function (m) {
- return this._monthsShort[m.month()];
- },
+ function addUnitAlias (unit, shorthand) {
+ var lowerCase = unit.toLowerCase();
+ aliases[lowerCase] = aliases[lowerCase + 's'] = aliases[shorthand] = unit;
+ }
- monthsParse : function (monthName) {
- var i, mom, regex;
+ function normalizeUnits(units) {
+ return typeof units === 'string' ? aliases[units] || aliases[units.toLowerCase()] : undefined;
+ }
- if (!this._monthsParse) {
- this._monthsParse = [];
- }
+ function normalizeObjectUnits(inputObject) {
+ var normalizedInput = {},
+ normalizedProp,
+ prop;
- for (i = 0; i < 12; i++) {
- // make the regex if we don't have it already
- if (!this._monthsParse[i]) {
- mom = moment.utc([2000, i]);
- regex = '^' + this.months(mom, '') + '|^' + this.monthsShort(mom, '');
- this._monthsParse[i] = new RegExp(regex.replace('.', ''), 'i');
- }
- // test the regex
- if (this._monthsParse[i].test(monthName)) {
- return i;
+ for (prop in inputObject) {
+ if (hasOwnProp(inputObject, prop)) {
+ normalizedProp = normalizeUnits(prop);
+ if (normalizedProp) {
+ normalizedInput[normalizedProp] = inputObject[prop];
}
}
- },
+ }
- _weekdays : "Sunday_Monday_Tuesday_Wednesday_Thursday_Friday_Saturday".split("_"),
- weekdays : function (m) {
- return this._weekdays[m.day()];
- },
+ return normalizedInput;
+ }
- _weekdaysShort : "Sun_Mon_Tue_Wed_Thu_Fri_Sat".split("_"),
- weekdaysShort : function (m) {
- return this._weekdaysShort[m.day()];
- },
+ function makeGetSet (unit, keepTime) {
+ return function (value) {
+ if (value != null) {
+ get_set__set(this, unit, value);
+ utils_hooks__hooks.updateOffset(this, keepTime);
+ return this;
+ } else {
+ return get_set__get(this, unit);
+ }
+ };
+ }
- _weekdaysMin : "Su_Mo_Tu_We_Th_Fr_Sa".split("_"),
- weekdaysMin : function (m) {
- return this._weekdaysMin[m.day()];
- },
+ function get_set__get (mom, unit) {
+ return mom._d['get' + (mom._isUTC ? 'UTC' : '') + unit]();
+ }
- weekdaysParse : function (weekdayName) {
- var i, mom, regex;
+ function get_set__set (mom, unit, value) {
+ return mom._d['set' + (mom._isUTC ? 'UTC' : '') + unit](value);
+ }
- if (!this._weekdaysParse) {
- this._weekdaysParse = [];
- }
+ // MOMENTS
- for (i = 0; i < 7; i++) {
- // make the regex if we don't have it already
- if (!this._weekdaysParse[i]) {
- mom = moment([2000, 1]).day(i);
- regex = '^' + this.weekdays(mom, '') + '|^' + this.weekdaysShort(mom, '') + '|^' + this.weekdaysMin(mom, '');
- this._weekdaysParse[i] = new RegExp(regex.replace('.', ''), 'i');
- }
- // test the regex
- if (this._weekdaysParse[i].test(weekdayName)) {
- return i;
- }
+ function getSet (units, value) {
+ var unit;
+ if (typeof units === 'object') {
+ for (unit in units) {
+ this.set(unit, units[unit]);
}
- },
-
- _longDateFormat : {
- LT : "h:mm A",
- L : "MM/DD/YYYY",
- LL : "MMMM D YYYY",
- LLL : "MMMM D YYYY LT",
- LLLL : "dddd, MMMM D YYYY LT"
- },
- longDateFormat : function (key) {
- var output = this._longDateFormat[key];
- if (!output && this._longDateFormat[key.toUpperCase()]) {
- output = this._longDateFormat[key.toUpperCase()].replace(/MMMM|MM|DD|dddd/g, function (val) {
- return val.slice(1);
- });
- this._longDateFormat[key] = output;
- }
- return output;
- },
-
- isPM : function (input) {
- // IE8 Quirks Mode & IE7 Standards Mode do not allow accessing strings like arrays
- // Using charAt should be more compatible.
- return ((input + '').toLowerCase().charAt(0) === 'p');
- },
-
- _meridiemParse : /[ap]\.?m?\.?/i,
- meridiem : function (hours, minutes, isLower) {
- if (hours > 11) {
- return isLower ? 'pm' : 'PM';
- } else {
- return isLower ? 'am' : 'AM';
+ } else {
+ units = normalizeUnits(units);
+ if (typeof this[units] === 'function') {
+ return this[units](value);
}
- },
-
- _calendar : {
- sameDay : '[Today at] LT',
- nextDay : '[Tomorrow at] LT',
- nextWeek : 'dddd [at] LT',
- lastDay : '[Yesterday at] LT',
- lastWeek : '[Last] dddd [at] LT',
- sameElse : 'L'
- },
- calendar : function (key, mom) {
- var output = this._calendar[key];
- return typeof output === 'function' ? output.apply(mom) : output;
- },
-
- _relativeTime : {
- future : "in %s",
- past : "%s ago",
- s : "a few seconds",
- m : "a minute",
- mm : "%d minutes",
- h : "an hour",
- hh : "%d hours",
- d : "a day",
- dd : "%d days",
- M : "a month",
- MM : "%d months",
- y : "a year",
- yy : "%d years"
- },
- relativeTime : function (number, withoutSuffix, string, isFuture) {
- var output = this._relativeTime[string];
- return (typeof output === 'function') ?
- output(number, withoutSuffix, string, isFuture) :
- output.replace(/%d/i, number);
- },
- pastFuture : function (diff, output) {
- var format = this._relativeTime[diff > 0 ? 'future' : 'past'];
- return typeof format === 'function' ? format(output) : format.replace(/%s/i, output);
- },
-
- ordinal : function (number) {
- return this._ordinal.replace("%d", number);
- },
- _ordinal : "%d",
-
- preparse : function (string) {
- return string;
- },
+ }
+ return this;
+ }
- postformat : function (string) {
- return string;
- },
+ function zeroFill(number, targetLength, forceSign) {
+ var output = '' + Math.abs(number),
+ sign = number >= 0;
- week : function (mom) {
- return weekOfYear(mom, this._week.dow, this._week.doy).week;
- },
+ while (output.length < targetLength) {
+ output = '0' + output;
+ }
+ return (sign ? (forceSign ? '+' : '') : '-') + output;
+ }
- _week : {
- dow : 0, // Sunday is the first day of the week.
- doy : 6 // The week that contains Jan 1st is the first week of the year.
- },
+ var formattingTokens = /(\[[^\[]*\])|(\\)?(Mo|MM?M?M?|Do|DDDo|DD?D?D?|ddd?d?|do?|w[o|w]?|W[o|W]?|Q|YYYYYY|YYYYY|YYYY|YY|gg(ggg?)?|GG(GGG?)?|e|E|a|A|hh?|HH?|mm?|ss?|S{1,4}|x|X|zz?|ZZ?|.)/g;
- _invalidDate: 'Invalid date',
- invalidDate: function () {
- return this._invalidDate;
- }
- });
+ var localFormattingTokens = /(\[[^\[]*\])|(\\)?(LTS|LT|LL?L?L?|l{1,4})/g;
- // Loads a language definition into the `languages` cache. The function
- // takes a key and optionally values. If not in the browser and no values
- // are provided, it will load the language file module. As a convenience,
- // this function also returns the language values.
- function loadLang(key, values) {
- values.abbr = key;
- if (!languages[key]) {
- languages[key] = new Language();
- }
- languages[key].set(values);
- return languages[key];
- }
+ var formatFunctions = {};
- // Remove a language from the `languages` cache. Mostly useful in tests.
- function unloadLang(key) {
- delete languages[key];
- }
+ var formatTokenFunctions = {};
- // Determines which language definition to use and returns it.
- //
- // With no parameters, it will return the global language. If you
- // pass in a language key, such as 'en', it will return the
- // definition for 'en', so long as 'en' has already been loaded using
- // moment.lang.
- function getLangDefinition(key) {
- var i = 0, j, lang, next, split,
- get = function (k) {
- if (!languages[k] && hasModule) {
- try {
- require('./lang/' + k);
- } catch (e) { }
- }
- return languages[k];
+ // token: 'M'
+ // padded: ['MM', 2]
+ // ordinal: 'Mo'
+ // callback: function () { this.month() + 1 }
+ function addFormatToken (token, padded, ordinal, callback) {
+ var func = callback;
+ if (typeof callback === 'string') {
+ func = function () {
+ return this[callback]();
};
-
- if (!key) {
- return moment.fn._lang;
}
-
- if (!isArray(key)) {
- //short-circuit everything else
- lang = get(key);
- if (lang) {
- return lang;
- }
- key = [key];
+ if (token) {
+ formatTokenFunctions[token] = func;
}
-
- //pick the language from the array
- //try ['en-au', 'en-gb'] as 'en-au', 'en-gb', 'en', as in move through the list trying each
- //substring from most specific to least, but move to the next array item if it's a more specific variant than the current root
- while (i < key.length) {
- split = normalizeLanguage(key[i]).split('-');
- j = split.length;
- next = normalizeLanguage(key[i + 1]);
- next = next ? next.split('-') : null;
- while (j > 0) {
- lang = get(split.slice(0, j).join('-'));
- if (lang) {
- return lang;
- }
- if (next && next.length >= j && compareArrays(split, next, true) >= j - 1) {
- //the next array item is better than a shallower substring of this one
- break;
- }
- j--;
- }
- i++;
+ if (padded) {
+ formatTokenFunctions[padded[0]] = function () {
+ return zeroFill(func.apply(this, arguments), padded[1], padded[2]);
+ };
+ }
+ if (ordinal) {
+ formatTokenFunctions[ordinal] = function () {
+ return this.localeData().ordinal(func.apply(this, arguments), token);
+ };
}
- return moment.fn._lang;
}
- /************************************
- Formatting
- ************************************/
-
-
function removeFormattingTokens(input) {
if (input.match(/\[[\s\S]/)) {
- return input.replace(/^\[|\]$/g, "");
+ return input.replace(/^\[|\]$/g, '');
}
- return input.replace(/\\/g, "");
+ return input.replace(/\\/g, '');
}
function makeFormatFunction(format) {
@@ -937,7 +463,7 @@
}
return function (mom) {
- var output = "";
+ var output = '';
for (i = 0; i < length; i++) {
output += array[i] instanceof Function ? array[i].call(mom, format) : array[i];
}
@@ -947,12 +473,11 @@
// format date using native date object
function formatMoment(m, format) {
-
if (!m.isValid()) {
- return m.lang().invalidDate();
+ return m.localeData().invalidDate();
}
- format = expandFormat(format, m.lang());
+ format = expandFormat(format, m.localeData());
if (!formatFunctions[format]) {
formatFunctions[format] = makeFormatFunction(format);
@@ -961,11 +486,11 @@
return formatFunctions[format](m);
}
- function expandFormat(format, lang) {
+ function expandFormat(format, locale) {
var i = 5;
function replaceLongDateFormatTokens(input) {
- return lang.longDateFormat(input) || input;
+ return locale.longDateFormat(input) || input;
}
localFormattingTokens.lastIndex = 0;
@@ -978,500 +503,292 @@
return format;
}
+ var match1 = /\d/; // 0 - 9
+ var match2 = /\d\d/; // 00 - 99
+ var match3 = /\d{3}/; // 000 - 999
+ var match4 = /\d{4}/; // 0000 - 9999
+ var match6 = /[+-]?\d{6}/; // -999999 - 999999
+ var match1to2 = /\d\d?/; // 0 - 99
+ var match1to3 = /\d{1,3}/; // 0 - 999
+ var match1to4 = /\d{1,4}/; // 0 - 9999
+ var match1to6 = /[+-]?\d{1,6}/; // -999999 - 999999
- /************************************
- Parsing
- ************************************/
-
-
- // get the regex to find the next token
- function getParseRegexForToken(token, config) {
- var a, strict = config._strict;
- switch (token) {
- case 'Q':
- return parseTokenOneDigit;
- case 'DDDD':
- return parseTokenThreeDigits;
- case 'YYYY':
- case 'GGGG':
- case 'gggg':
- return strict ? parseTokenFourDigits : parseTokenOneToFourDigits;
- case 'Y':
- case 'G':
- case 'g':
- return parseTokenSignedNumber;
- case 'YYYYYY':
- case 'YYYYY':
- case 'GGGGG':
- case 'ggggg':
- return strict ? parseTokenSixDigits : parseTokenOneToSixDigits;
- case 'S':
- if (strict) { return parseTokenOneDigit; }
- /* falls through */
- case 'SS':
- if (strict) { return parseTokenTwoDigits; }
- /* falls through */
- case 'SSS':
- if (strict) { return parseTokenThreeDigits; }
- /* falls through */
- case 'DDD':
- return parseTokenOneToThreeDigits;
- case 'MMM':
- case 'MMMM':
- case 'dd':
- case 'ddd':
- case 'dddd':
- return parseTokenWord;
- case 'a':
- case 'A':
- return getLangDefinition(config._l)._meridiemParse;
- case 'X':
- return parseTokenTimestampMs;
- case 'Z':
- case 'ZZ':
- return parseTokenTimezone;
- case 'T':
- return parseTokenT;
- case 'SSSS':
- return parseTokenDigits;
- case 'MM':
- case 'DD':
- case 'YY':
- case 'GG':
- case 'gg':
- case 'HH':
- case 'hh':
- case 'mm':
- case 'ss':
- case 'ww':
- case 'WW':
- return strict ? parseTokenTwoDigits : parseTokenOneOrTwoDigits;
- case 'M':
- case 'D':
- case 'd':
- case 'H':
- case 'h':
- case 'm':
- case 's':
- case 'w':
- case 'W':
- case 'e':
- case 'E':
- return parseTokenOneOrTwoDigits;
- case 'Do':
- return parseTokenOrdinal;
- default :
- a = new RegExp(regexpEscape(unescapeFormat(token.replace('\\', '')), "i"));
- return a;
- }
- }
+ var matchUnsigned = /\d+/; // 0 - inf
+ var matchSigned = /[+-]?\d+/; // -inf - inf
- function timezoneMinutesFromString(string) {
- string = string || "";
- var possibleTzMatches = (string.match(parseTokenTimezone) || []),
- tzChunk = possibleTzMatches[possibleTzMatches.length - 1] || [],
- parts = (tzChunk + '').match(parseTimezoneChunker) || ['-', 0, 0],
- minutes = +(parts[1] * 60) + toInt(parts[2]);
+ var matchOffset = /Z|[+-]\d\d:?\d\d/gi; // +00:00 -00:00 +0000 -0000 or Z
- return parts[0] === '+' ? -minutes : minutes;
- }
+ var matchTimestamp = /[+-]?\d+(\.\d{1,3})?/; // 123456789 123456789.123
- // function to convert string input to date
- function addTimeToArrayFromToken(token, input, config) {
- var a, datePartArray = config._a;
+ // any word (or two) characters or numbers including two/three word month in arabic.
+ var matchWord = /[0-9]*['a-z\u00A0-\u05FF\u0700-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]+|[\u0600-\u06FF\/]+(\s*?[\u0600-\u06FF]+){1,2}/i;
- switch (token) {
- // QUARTER
- case 'Q':
- if (input != null) {
- datePartArray[MONTH] = (toInt(input) - 1) * 3;
- }
- break;
- // MONTH
- case 'M' : // fall through to MM
- case 'MM' :
- if (input != null) {
- datePartArray[MONTH] = toInt(input) - 1;
- }
- break;
- case 'MMM' : // fall through to MMMM
- case 'MMMM' :
- a = getLangDefinition(config._l).monthsParse(input);
- // if we didn't find a month name, mark the date as invalid.
- if (a != null) {
- datePartArray[MONTH] = a;
- } else {
- config._pf.invalidMonth = input;
- }
- break;
- // DAY OF MONTH
- case 'D' : // fall through to DD
- case 'DD' :
- if (input != null) {
- datePartArray[DATE] = toInt(input);
- }
- break;
- case 'Do' :
- if (input != null) {
- datePartArray[DATE] = toInt(parseInt(input, 10));
- }
- break;
- // DAY OF YEAR
- case 'DDD' : // fall through to DDDD
- case 'DDDD' :
- if (input != null) {
- config._dayOfYear = toInt(input);
- }
+ var regexes = {};
- break;
- // YEAR
- case 'YY' :
- datePartArray[YEAR] = moment.parseTwoDigitYear(input);
- break;
- case 'YYYY' :
- case 'YYYYY' :
- case 'YYYYYY' :
- datePartArray[YEAR] = toInt(input);
- break;
- // AM / PM
- case 'a' : // fall through to A
- case 'A' :
- config._isPm = getLangDefinition(config._l).isPM(input);
- break;
- // 24 HOUR
- case 'H' : // fall through to hh
- case 'HH' : // fall through to hh
- case 'h' : // fall through to hh
- case 'hh' :
- datePartArray[HOUR] = toInt(input);
- break;
- // MINUTE
- case 'm' : // fall through to mm
- case 'mm' :
- datePartArray[MINUTE] = toInt(input);
- break;
- // SECOND
- case 's' : // fall through to ss
- case 'ss' :
- datePartArray[SECOND] = toInt(input);
- break;
- // MILLISECOND
- case 'S' :
- case 'SS' :
- case 'SSS' :
- case 'SSSS' :
- datePartArray[MILLISECOND] = toInt(('0.' + input) * 1000);
- break;
- // UNIX TIMESTAMP WITH MS
- case 'X':
- config._d = new Date(parseFloat(input) * 1000);
- break;
- // TIMEZONE
- case 'Z' : // fall through to ZZ
- case 'ZZ' :
- config._useUTC = true;
- config._tzm = timezoneMinutesFromString(input);
- break;
- // WEEKDAY - human
- case 'dd':
- case 'ddd':
- case 'dddd':
- a = getLangDefinition(config._l).weekdaysParse(input);
- // if we didn't get a weekday name, mark the date as invalid
- if (a != null) {
- config._w = config._w || {};
- config._w['d'] = a;
- } else {
- config._pf.invalidWeekday = input;
- }
- break;
- // WEEK, WEEK DAY - numeric
- case 'w':
- case 'ww':
- case 'W':
- case 'WW':
- case 'd':
- case 'e':
- case 'E':
- token = token.substr(0, 1);
- /* falls through */
- case 'gggg':
- case 'GGGG':
- case 'GGGGG':
- token = token.substr(0, 2);
- if (input) {
- config._w = config._w || {};
- config._w[token] = toInt(input);
- }
- break;
- case 'gg':
- case 'GG':
- config._w = config._w || {};
- config._w[token] = moment.parseTwoDigitYear(input);
- }
+ function addRegexToken (token, regex, strictRegex) {
+ regexes[token] = typeof regex === 'function' ? regex : function (isStrict) {
+ return (isStrict && strictRegex) ? strictRegex : regex;
+ };
}
- function dayOfYearFromWeekInfo(config) {
- var w, weekYear, week, weekday, dow, doy, temp, lang;
+ function getParseRegexForToken (token, config) {
+ if (!hasOwnProp(regexes, token)) {
+ return new RegExp(unescapeFormat(token));
+ }
- w = config._w;
- if (w.GG != null || w.W != null || w.E != null) {
- dow = 1;
- doy = 4;
+ return regexes[token](config._strict, config._locale);
+ }
- // TODO: We need to take the current isoWeekYear, but that depends on
- // how we interpret now (local, utc, fixed offset). So create
- // a now version of current config (take local/utc/offset flags, and
- // create now).
- weekYear = dfl(w.GG, config._a[YEAR], weekOfYear(moment(), 1, 4).year);
- week = dfl(w.W, 1);
- weekday = dfl(w.E, 1);
- } else {
- lang = getLangDefinition(config._l);
- dow = lang._week.dow;
- doy = lang._week.doy;
+ // Code from http://stackoverflow.com/questions/3561493/is-there-a-regexp-escape-function-in-javascript
+ function unescapeFormat(s) {
+ return s.replace('\\', '').replace(/\\(\[)|\\(\])|\[([^\]\[]*)\]|\\(.)/g, function (matched, p1, p2, p3, p4) {
+ return p1 || p2 || p3 || p4;
+ }).replace(/[-\/\\^$*+?.()|[\]{}]/g, '\\$&');
+ }
- weekYear = dfl(w.gg, config._a[YEAR], weekOfYear(moment(), dow, doy).year);
- week = dfl(w.w, 1);
+ var tokens = {};
- if (w.d != null) {
- // weekday -- low day numbers are considered next week
- weekday = w.d;
- if (weekday < dow) {
- ++week;
- }
- } else if (w.e != null) {
- // local weekday -- counting starts from begining of week
- weekday = w.e + dow;
- } else {
- // default to begining of week
- weekday = dow;
- }
+ function addParseToken (token, callback) {
+ var i, func = callback;
+ if (typeof token === 'string') {
+ token = [token];
+ }
+ if (typeof callback === 'number') {
+ func = function (input, array) {
+ array[callback] = toInt(input);
+ };
+ }
+ for (i = 0; i < token.length; i++) {
+ tokens[token[i]] = func;
}
- temp = dayOfYearFromWeeks(weekYear, week, weekday, doy, dow);
-
- config._a[YEAR] = temp.year;
- config._dayOfYear = temp.dayOfYear;
}
- // convert an array to a date.
- // the array should mirror the parameters below
- // note: all values past the year are optional and will default to the lowest possible value.
- // [year, month, day , hour, minute, second, millisecond]
- function dateFromConfig(config) {
- var i, date, input = [], currentDate, yearToUse;
+ function addWeekParseToken (token, callback) {
+ addParseToken(token, function (input, array, config, token) {
+ config._w = config._w || {};
+ callback(input, config._w, config, token);
+ });
+ }
- if (config._d) {
- return;
+ function addTimeToArrayFromToken(token, input, config) {
+ if (input != null && hasOwnProp(tokens, token)) {
+ tokens[token](input, config._a, config, token);
}
+ }
- currentDate = currentDateArray(config);
-
- //compute day of the year from weeks and weekdays
- if (config._w && config._a[DATE] == null && config._a[MONTH] == null) {
- dayOfYearFromWeekInfo(config);
- }
+ var YEAR = 0;
+ var MONTH = 1;
+ var DATE = 2;
+ var HOUR = 3;
+ var MINUTE = 4;
+ var SECOND = 5;
+ var MILLISECOND = 6;
- //if the day of the year is set, figure out what it is
- if (config._dayOfYear) {
- yearToUse = dfl(config._a[YEAR], currentDate[YEAR]);
+ function daysInMonth(year, month) {
+ return new Date(Date.UTC(year, month + 1, 0)).getUTCDate();
+ }
- if (config._dayOfYear > daysInYear(yearToUse)) {
- config._pf._overflowDayOfYear = true;
- }
+ // FORMATTING
- date = makeUTCDate(yearToUse, 0, config._dayOfYear);
- config._a[MONTH] = date.getUTCMonth();
- config._a[DATE] = date.getUTCDate();
- }
+ addFormatToken('M', ['MM', 2], 'Mo', function () {
+ return this.month() + 1;
+ });
- // Default to current date.
- // * if no year, month, day of month are given, default to today
- // * if day of month is given, default month and year
- // * if month is given, default only year
- // * if year is given, don't default anything
- for (i = 0; i < 3 && config._a[i] == null; ++i) {
- config._a[i] = input[i] = currentDate[i];
- }
+ addFormatToken('MMM', 0, 0, function (format) {
+ return this.localeData().monthsShort(this, format);
+ });
- // Zero out whatever was not defaulted, including time
- for (; i < 7; i++) {
- config._a[i] = input[i] = (config._a[i] == null) ? (i === 2 ? 1 : 0) : config._a[i];
- }
+ addFormatToken('MMMM', 0, 0, function (format) {
+ return this.localeData().months(this, format);
+ });
- config._d = (config._useUTC ? makeUTCDate : makeDate).apply(null, input);
- // Apply timezone offset from input. The actual zone can be changed
- // with parseZone.
- if (config._tzm != null) {
- config._d.setUTCMinutes(config._d.getUTCMinutes() + config._tzm);
- }
- }
+ // ALIASES
- function dateFromObject(config) {
- var normalizedInput;
+ addUnitAlias('month', 'M');
- if (config._d) {
- return;
- }
+ // PARSING
- normalizedInput = normalizeObjectUnits(config._i);
- config._a = [
- normalizedInput.year,
- normalizedInput.month,
- normalizedInput.day,
- normalizedInput.hour,
- normalizedInput.minute,
- normalizedInput.second,
- normalizedInput.millisecond
- ];
+ addRegexToken('M', match1to2);
+ addRegexToken('MM', match1to2, match2);
+ addRegexToken('MMM', matchWord);
+ addRegexToken('MMMM', matchWord);
- dateFromConfig(config);
- }
+ addParseToken(['M', 'MM'], function (input, array) {
+ array[MONTH] = toInt(input) - 1;
+ });
- function currentDateArray(config) {
- var now = new Date();
- if (config._useUTC) {
- return [
- now.getUTCFullYear(),
- now.getUTCMonth(),
- now.getUTCDate()
- ];
+ addParseToken(['MMM', 'MMMM'], function (input, array, config, token) {
+ var month = config._locale.monthsParse(input, token, config._strict);
+ // if we didn't find a month name, mark the date as invalid.
+ if (month != null) {
+ array[MONTH] = month;
} else {
- return [now.getFullYear(), now.getMonth(), now.getDate()];
+ getParsingFlags(config).invalidMonth = input;
}
- }
+ });
- // date from string and format string
- function makeDateFromStringAndFormat(config) {
+ // LOCALES
- if (config._f === moment.ISO_8601) {
- parseISO(config);
- return;
- }
+ var defaultLocaleMonths = 'January_February_March_April_May_June_July_August_September_October_November_December'.split('_');
+ function localeMonths (m) {
+ return this._months[m.month()];
+ }
- config._a = [];
- config._pf.empty = true;
+ var defaultLocaleMonthsShort = 'Jan_Feb_Mar_Apr_May_Jun_Jul_Aug_Sep_Oct_Nov_Dec'.split('_');
+ function localeMonthsShort (m) {
+ return this._monthsShort[m.month()];
+ }
- // This array is used to make a Date, either with `new Date` or `Date.UTC`
- var lang = getLangDefinition(config._l),
- string = '' + config._i,
- i, parsedInput, tokens, token, skipped,
- stringLength = string.length,
- totalParsedInputLength = 0;
+ function localeMonthsParse (monthName, format, strict) {
+ var i, mom, regex;
- tokens = expandFormat(config._f, lang).match(formattingTokens) || [];
+ if (!this._monthsParse) {
+ this._monthsParse = [];
+ this._longMonthsParse = [];
+ this._shortMonthsParse = [];
+ }
- for (i = 0; i < tokens.length; i++) {
- token = tokens[i];
- parsedInput = (string.match(getParseRegexForToken(token, config)) || [])[0];
- if (parsedInput) {
- skipped = string.substr(0, string.indexOf(parsedInput));
- if (skipped.length > 0) {
- config._pf.unusedInput.push(skipped);
- }
- string = string.slice(string.indexOf(parsedInput) + parsedInput.length);
- totalParsedInputLength += parsedInput.length;
+ for (i = 0; i < 12; i++) {
+ // make the regex if we don't have it already
+ mom = create_utc__createUTC([2000, i]);
+ if (strict && !this._longMonthsParse[i]) {
+ this._longMonthsParse[i] = new RegExp('^' + this.months(mom, '').replace('.', '') + '$', 'i');
+ this._shortMonthsParse[i] = new RegExp('^' + this.monthsShort(mom, '').replace('.', '') + '$', 'i');
}
- // don't parse if it's not a known token
- if (formatTokenFunctions[token]) {
- if (parsedInput) {
- config._pf.empty = false;
- }
- else {
- config._pf.unusedTokens.push(token);
- }
- addTimeToArrayFromToken(token, parsedInput, config);
+ if (!strict && !this._monthsParse[i]) {
+ regex = '^' + this.months(mom, '') + '|^' + this.monthsShort(mom, '');
+ this._monthsParse[i] = new RegExp(regex.replace('.', ''), 'i');
}
- else if (config._strict && !parsedInput) {
- config._pf.unusedTokens.push(token);
+ // test the regex
+ if (strict && format === 'MMMM' && this._longMonthsParse[i].test(monthName)) {
+ return i;
+ } else if (strict && format === 'MMM' && this._shortMonthsParse[i].test(monthName)) {
+ return i;
+ } else if (!strict && this._monthsParse[i].test(monthName)) {
+ return i;
}
}
+ }
- // add remaining unparsed input length to the string
- config._pf.charsLeftOver = stringLength - totalParsedInputLength;
- if (string.length > 0) {
- config._pf.unusedInput.push(string);
- }
+ // MOMENTS
- // handle am pm
- if (config._isPm && config._a[HOUR] < 12) {
- config._a[HOUR] += 12;
- }
- // if is 12 am, change hours to 0
- if (config._isPm === false && config._a[HOUR] === 12) {
- config._a[HOUR] = 0;
+ function setMonth (mom, value) {
+ var dayOfMonth;
+
+ // TODO: Move this out of here!
+ if (typeof value === 'string') {
+ value = mom.localeData().monthsParse(value);
+ // TODO: Another silent failure?
+ if (typeof value !== 'number') {
+ return mom;
+ }
}
- dateFromConfig(config);
- checkOverflow(config);
+ dayOfMonth = Math.min(mom.date(), daysInMonth(mom.year(), value));
+ mom._d['set' + (mom._isUTC ? 'UTC' : '') + 'Month'](value, dayOfMonth);
+ return mom;
}
- function unescapeFormat(s) {
- return s.replace(/\\(\[)|\\(\])|\[([^\]\[]*)\]|\\(.)/g, function (matched, p1, p2, p3, p4) {
- return p1 || p2 || p3 || p4;
- });
+ function getSetMonth (value) {
+ if (value != null) {
+ setMonth(this, value);
+ utils_hooks__hooks.updateOffset(this, true);
+ return this;
+ } else {
+ return get_set__get(this, 'Month');
+ }
}
- // Code from http://stackoverflow.com/questions/3561493/is-there-a-regexp-escape-function-in-javascript
- function regexpEscape(s) {
- return s.replace(/[-\/\\^$*+?.()|[\]{}]/g, '\\$&');
+ function getDaysInMonth () {
+ return daysInMonth(this.year(), this.month());
}
- // date from string and array of format strings
- function makeDateFromStringAndArray(config) {
- var tempConfig,
- bestMoment,
+ function checkOverflow (m) {
+ var overflow;
+ var a = m._a;
- scoreToBeat,
- i,
- currentScore;
+ if (a && getParsingFlags(m).overflow === -2) {
+ overflow =
+ a[MONTH] < 0 || a[MONTH] > 11 ? MONTH :
+ a[DATE] < 1 || a[DATE] > daysInMonth(a[YEAR], a[MONTH]) ? DATE :
+ a[HOUR] < 0 || a[HOUR] > 24 || (a[HOUR] === 24 && (a[MINUTE] !== 0 || a[SECOND] !== 0 || a[MILLISECOND] !== 0)) ? HOUR :
+ a[MINUTE] < 0 || a[MINUTE] > 59 ? MINUTE :
+ a[SECOND] < 0 || a[SECOND] > 59 ? SECOND :
+ a[MILLISECOND] < 0 || a[MILLISECOND] > 999 ? MILLISECOND :
+ -1;
- if (config._f.length === 0) {
- config._pf.invalidFormat = true;
- config._d = new Date(NaN);
- return;
+ if (getParsingFlags(m)._overflowDayOfYear && (overflow < YEAR || overflow > DATE)) {
+ overflow = DATE;
+ }
+
+ getParsingFlags(m).overflow = overflow;
}
- for (i = 0; i < config._f.length; i++) {
- currentScore = 0;
- tempConfig = extend({}, config);
- tempConfig._pf = defaultParsingFlags();
- tempConfig._f = config._f[i];
- makeDateFromStringAndFormat(tempConfig);
+ return m;
+ }
- if (!isValid(tempConfig)) {
- continue;
- }
+ function warn(msg) {
+ if (utils_hooks__hooks.suppressDeprecationWarnings === false && typeof console !== 'undefined' && console.warn) {
+ console.warn('Deprecation warning: ' + msg);
+ }
+ }
- // if there is any input that was not parsed add a penalty for that format
- currentScore += tempConfig._pf.charsLeftOver;
+ function deprecate(msg, fn) {
+ var firstTime = true,
+ msgWithStack = msg + '\n' + (new Error()).stack;
- //or tokens
- currentScore += tempConfig._pf.unusedTokens.length * 10;
+ return extend(function () {
+ if (firstTime) {
+ warn(msgWithStack);
+ firstTime = false;
+ }
+ return fn.apply(this, arguments);
+ }, fn);
+ }
- tempConfig._pf.score = currentScore;
+ var deprecations = {};
- if (scoreToBeat == null || currentScore < scoreToBeat) {
- scoreToBeat = currentScore;
- bestMoment = tempConfig;
- }
+ function deprecateSimple(name, msg) {
+ if (!deprecations[name]) {
+ warn(msg);
+ deprecations[name] = true;
}
-
- extend(config, bestMoment || tempConfig);
}
- // date from iso format
- function parseISO(config) {
- var i, l,
+ utils_hooks__hooks.suppressDeprecationWarnings = false;
+
+ var from_string__isoRegex = /^\s*(?:[+-]\d{6}|\d{4})-(?:(\d\d-\d\d)|(W\d\d$)|(W\d\d-\d)|(\d\d\d))((T| )(\d\d(:\d\d(:\d\d(\.\d+)?)?)?)?([\+\-]\d\d(?::?\d\d)?|\s*Z)?)?$/;
+
+ var isoDates = [
+ ['YYYYYY-MM-DD', /[+-]\d{6}-\d{2}-\d{2}/],
+ ['YYYY-MM-DD', /\d{4}-\d{2}-\d{2}/],
+ ['GGGG-[W]WW-E', /\d{4}-W\d{2}-\d/],
+ ['GGGG-[W]WW', /\d{4}-W\d{2}/],
+ ['YYYY-DDD', /\d{4}-\d{3}/]
+ ];
+
+ // iso time formats and regexes
+ var isoTimes = [
+ ['HH:mm:ss.SSSS', /(T| )\d\d:\d\d:\d\d\.\d+/],
+ ['HH:mm:ss', /(T| )\d\d:\d\d:\d\d/],
+ ['HH:mm', /(T| )\d\d:\d\d/],
+ ['HH', /(T| )\d\d/]
+ ];
+
+ var aspNetJsonRegex = /^\/?Date\((\-?\d+)/i;
+
+ // date from iso format
+ function configFromISO(config) {
+ var i, l,
string = config._i,
- match = isoRegex.exec(string);
+ match = from_string__isoRegex.exec(string);
if (match) {
- config._pf.iso = true;
+ getParsingFlags(config).iso = true;
for (i = 0, l = isoDates.length; i < l; i++) {
if (isoDates[i][1].exec(string)) {
- // match[5] should be "T" or undefined
- config._f = isoDates[i][0] + (match[6] || " ");
+ // match[5] should be 'T' or undefined
+ config._f = isoDates[i][0] + (match[6] || ' ');
break;
}
}
@@ -1481,50 +798,42 @@
break;
}
}
- if (string.match(parseTokenTimezone)) {
- config._f += "Z";
+ if (string.match(matchOffset)) {
+ config._f += 'Z';
}
- makeDateFromStringAndFormat(config);
+ configFromStringAndFormat(config);
} else {
config._isValid = false;
}
}
// date from iso format or fallback
- function makeDateFromString(config) {
- parseISO(config);
+ function configFromString(config) {
+ var matched = aspNetJsonRegex.exec(config._i);
+
+ if (matched !== null) {
+ config._d = new Date(+matched[1]);
+ return;
+ }
+
+ configFromISO(config);
if (config._isValid === false) {
delete config._isValid;
- moment.createFromInputFallback(config);
+ utils_hooks__hooks.createFromInputFallback(config);
}
}
- function makeDateFromInput(config) {
- var input = config._i,
- matched = aspNetJsonRegex.exec(input);
-
- if (input === undefined) {
- config._d = new Date();
- } else if (matched) {
- config._d = new Date(+matched[1]);
- } else if (typeof input === 'string') {
- makeDateFromString(config);
- } else if (isArray(input)) {
- config._a = input.slice(0);
- dateFromConfig(config);
- } else if (isDate(input)) {
- config._d = new Date(+input);
- } else if (typeof(input) === 'object') {
- dateFromObject(config);
- } else if (typeof(input) === 'number') {
- // from milliseconds
- config._d = new Date(input);
- } else {
- moment.createFromInputFallback(config);
+ utils_hooks__hooks.createFromInputFallback = deprecate(
+ 'moment construction falls back to js Date. This is ' +
+ 'discouraged and will be removed in upcoming major ' +
+ 'release. Please refer to ' +
+ 'https://github.com/moment/moment/issues/1407 for more info.',
+ function (config) {
+ config._d = new Date(config._i + (config._useUTC ? ' UTC' : ''));
}
- }
+ );
- function makeDate(y, m, d, h, M, s, ms) {
+ function createDate (y, m, d, h, M, s, ms) {
//can't just apply() to create a date:
//http://stackoverflow.com/questions/181348/instantiating-a-javascript-object-by-calling-prototype-constructor-apply
var date = new Date(y, m, d, h, M, s, ms);
@@ -1536,7 +845,7 @@
return date;
}
- function makeUTCDate(y) {
+ function createUTCDate (y) {
var date = new Date(Date.UTC.apply(null, arguments));
if (y < 1970) {
date.setUTCFullYear(y);
@@ -1544,58 +853,75 @@
return date;
}
- function parseWeekday(input, language) {
- if (typeof input === 'string') {
- if (!isNaN(input)) {
- input = parseInt(input, 10);
- }
- else {
- input = language.weekdaysParse(input);
- if (typeof input !== 'number') {
- return null;
- }
- }
- }
- return input;
+ addFormatToken(0, ['YY', 2], 0, function () {
+ return this.year() % 100;
+ });
+
+ addFormatToken(0, ['YYYY', 4], 0, 'year');
+ addFormatToken(0, ['YYYYY', 5], 0, 'year');
+ addFormatToken(0, ['YYYYYY', 6, true], 0, 'year');
+
+ // ALIASES
+
+ addUnitAlias('year', 'y');
+
+ // PARSING
+
+ addRegexToken('Y', matchSigned);
+ addRegexToken('YY', match1to2, match2);
+ addRegexToken('YYYY', match1to4, match4);
+ addRegexToken('YYYYY', match1to6, match6);
+ addRegexToken('YYYYYY', match1to6, match6);
+
+ addParseToken(['YYYY', 'YYYYY', 'YYYYYY'], YEAR);
+ addParseToken('YY', function (input, array) {
+ array[YEAR] = utils_hooks__hooks.parseTwoDigitYear(input);
+ });
+
+ // HELPERS
+
+ function daysInYear(year) {
+ return isLeapYear(year) ? 366 : 365;
}
- /************************************
- Relative Time
- ************************************/
+ function isLeapYear(year) {
+ return (year % 4 === 0 && year % 100 !== 0) || year % 400 === 0;
+ }
+ // HOOKS
- // helper function for moment.fn.from, moment.fn.fromNow, and moment.duration.fn.humanize
- function substituteTimeAgo(string, number, withoutSuffix, isFuture, lang) {
- return lang.relativeTime(number || 1, !!withoutSuffix, string, isFuture);
- }
-
- function relativeTime(milliseconds, withoutSuffix, lang) {
- var seconds = round(Math.abs(milliseconds) / 1000),
- minutes = round(seconds / 60),
- hours = round(minutes / 60),
- days = round(hours / 24),
- years = round(days / 365),
- args = seconds < relativeTimeThresholds.s && ['s', seconds] ||
- minutes === 1 && ['m'] ||
- minutes < relativeTimeThresholds.m && ['mm', minutes] ||
- hours === 1 && ['h'] ||
- hours < relativeTimeThresholds.h && ['hh', hours] ||
- days === 1 && ['d'] ||
- days <= relativeTimeThresholds.dd && ['dd', days] ||
- days <= relativeTimeThresholds.dm && ['M'] ||
- days < relativeTimeThresholds.dy && ['MM', round(days / 30)] ||
- years === 1 && ['y'] || ['yy', years];
- args[2] = withoutSuffix;
- args[3] = milliseconds > 0;
- args[4] = lang;
- return substituteTimeAgo.apply({}, args);
- }
-
-
- /************************************
- Week of Year
- ************************************/
+ utils_hooks__hooks.parseTwoDigitYear = function (input) {
+ return toInt(input) + (toInt(input) > 68 ? 1900 : 2000);
+ };
+
+ // MOMENTS
+
+ var getSetYear = makeGetSet('FullYear', false);
+
+ function getIsLeapYear () {
+ return isLeapYear(this.year());
+ }
+
+ addFormatToken('w', ['ww', 2], 'wo', 'week');
+ addFormatToken('W', ['WW', 2], 'Wo', 'isoWeek');
+
+ // ALIASES
+
+ addUnitAlias('week', 'w');
+ addUnitAlias('isoWeek', 'W');
+
+ // PARSING
+ addRegexToken('w', match1to2);
+ addRegexToken('ww', match1to2, match2);
+ addRegexToken('W', match1to2);
+ addRegexToken('WW', match1to2, match2);
+
+ addWeekParseToken(['w', 'ww', 'W', 'WW'], function (input, week, config, token) {
+ week[token.substr(0, 1)] = toInt(input);
+ });
+
+ // HELPERS
// firstDayOfWeek 0 = sun, 6 = sat
// the day of the week that starts the week
@@ -1618,16 +944,65 @@
daysToDayOfWeek += 7;
}
- adjustedMoment = moment(mom).add('d', daysToDayOfWeek);
+ adjustedMoment = local__createLocal(mom).add(daysToDayOfWeek, 'd');
return {
week: Math.ceil(adjustedMoment.dayOfYear() / 7),
year: adjustedMoment.year()
};
}
+ // LOCALES
+
+ function localeWeek (mom) {
+ return weekOfYear(mom, this._week.dow, this._week.doy).week;
+ }
+
+ var defaultLocaleWeek = {
+ dow : 0, // Sunday is the first day of the week.
+ doy : 6 // The week that contains Jan 1st is the first week of the year.
+ };
+
+ function localeFirstDayOfWeek () {
+ return this._week.dow;
+ }
+
+ function localeFirstDayOfYear () {
+ return this._week.doy;
+ }
+
+ // MOMENTS
+
+ function getSetWeek (input) {
+ var week = this.localeData().week(this);
+ return input == null ? week : this.add((input - week) * 7, 'd');
+ }
+
+ function getSetISOWeek (input) {
+ var week = weekOfYear(this, 1, 4).week;
+ return input == null ? week : this.add((input - week) * 7, 'd');
+ }
+
+ addFormatToken('DDD', ['DDDD', 3], 'DDDo', 'dayOfYear');
+
+ // ALIASES
+
+ addUnitAlias('dayOfYear', 'DDD');
+
+ // PARSING
+
+ addRegexToken('DDD', match1to3);
+ addRegexToken('DDDD', match3);
+ addParseToken(['DDD', 'DDDD'], function (input, array, config) {
+ config._dayOfYear = toInt(input);
+ });
+
+ // HELPERS
+
//http://en.wikipedia.org/wiki/ISO_week_date#Calculating_a_date_given_the_year.2C_week_number_and_weekday
function dayOfYearFromWeeks(year, week, weekday, firstDayOfWeekOfYear, firstDayOfWeek) {
- var d = makeUTCDate(year, 0, 1).getUTCDay(), daysToAdd, dayOfYear;
+ var d = createUTCDate(year, 0, 1).getUTCDay();
+ var daysToAdd;
+ var dayOfYear;
d = d === 0 ? 7 : d;
weekday = weekday != null ? weekday : firstDayOfWeek;
@@ -1635,976 +1010,2102 @@
dayOfYear = 7 * (week - 1) + (weekday - firstDayOfWeek) + daysToAdd + 1;
return {
- year: dayOfYear > 0 ? year : year - 1,
- dayOfYear: dayOfYear > 0 ? dayOfYear : daysInYear(year - 1) + dayOfYear
+ year : dayOfYear > 0 ? year : year - 1,
+ dayOfYear : dayOfYear > 0 ? dayOfYear : daysInYear(year - 1) + dayOfYear
};
}
- /************************************
- Top Level Functions
- ************************************/
+ // MOMENTS
- function makeMoment(config) {
- var input = config._i,
- format = config._f;
+ function getSetDayOfYear (input) {
+ var dayOfYear = Math.round((this.clone().startOf('day') - this.clone().startOf('year')) / 864e5) + 1;
+ return input == null ? dayOfYear : this.add((input - dayOfYear), 'd');
+ }
- if (input === null || (format === undefined && input === '')) {
- return moment.invalid({nullInput: true});
+ // Pick the first defined of two or three arguments.
+ function defaults(a, b, c) {
+ if (a != null) {
+ return a;
}
+ if (b != null) {
+ return b;
+ }
+ return c;
+ }
- if (typeof input === 'string') {
- config._i = input = getLangDefinition().preparse(input);
+ function currentDateArray(config) {
+ var now = new Date();
+ if (config._useUTC) {
+ return [now.getUTCFullYear(), now.getUTCMonth(), now.getUTCDate()];
+ }
+ return [now.getFullYear(), now.getMonth(), now.getDate()];
+ }
+
+ // convert an array to a date.
+ // the array should mirror the parameters below
+ // note: all values past the year are optional and will default to the lowest possible value.
+ // [year, month, day , hour, minute, second, millisecond]
+ function configFromArray (config) {
+ var i, date, input = [], currentDate, yearToUse;
+
+ if (config._d) {
+ return;
}
- if (moment.isMoment(input)) {
- config = cloneMoment(input);
+ currentDate = currentDateArray(config);
- config._d = new Date(+input._d);
- } else if (format) {
- if (isArray(format)) {
- makeDateFromStringAndArray(config);
- } else {
- makeDateFromStringAndFormat(config);
+ //compute day of the year from weeks and weekdays
+ if (config._w && config._a[DATE] == null && config._a[MONTH] == null) {
+ dayOfYearFromWeekInfo(config);
+ }
+
+ //if the day of the year is set, figure out what it is
+ if (config._dayOfYear) {
+ yearToUse = defaults(config._a[YEAR], currentDate[YEAR]);
+
+ if (config._dayOfYear > daysInYear(yearToUse)) {
+ getParsingFlags(config)._overflowDayOfYear = true;
}
+
+ date = createUTCDate(yearToUse, 0, config._dayOfYear);
+ config._a[MONTH] = date.getUTCMonth();
+ config._a[DATE] = date.getUTCDate();
+ }
+
+ // Default to current date.
+ // * if no year, month, day of month are given, default to today
+ // * if day of month is given, default month and year
+ // * if month is given, default only year
+ // * if year is given, don't default anything
+ for (i = 0; i < 3 && config._a[i] == null; ++i) {
+ config._a[i] = input[i] = currentDate[i];
+ }
+
+ // Zero out whatever was not defaulted, including time
+ for (; i < 7; i++) {
+ config._a[i] = input[i] = (config._a[i] == null) ? (i === 2 ? 1 : 0) : config._a[i];
+ }
+
+ // Check for 24:00:00.000
+ if (config._a[HOUR] === 24 &&
+ config._a[MINUTE] === 0 &&
+ config._a[SECOND] === 0 &&
+ config._a[MILLISECOND] === 0) {
+ config._nextDay = true;
+ config._a[HOUR] = 0;
+ }
+
+ config._d = (config._useUTC ? createUTCDate : createDate).apply(null, input);
+ // Apply timezone offset from input. The actual utcOffset can be changed
+ // with parseZone.
+ if (config._tzm != null) {
+ config._d.setUTCMinutes(config._d.getUTCMinutes() - config._tzm);
+ }
+
+ if (config._nextDay) {
+ config._a[HOUR] = 24;
+ }
+ }
+
+ function dayOfYearFromWeekInfo(config) {
+ var w, weekYear, week, weekday, dow, doy, temp;
+
+ w = config._w;
+ if (w.GG != null || w.W != null || w.E != null) {
+ dow = 1;
+ doy = 4;
+
+ // TODO: We need to take the current isoWeekYear, but that depends on
+ // how we interpret now (local, utc, fixed offset). So create
+ // a now version of current config (take local/utc/offset flags, and
+ // create now).
+ weekYear = defaults(w.GG, config._a[YEAR], weekOfYear(local__createLocal(), 1, 4).year);
+ week = defaults(w.W, 1);
+ weekday = defaults(w.E, 1);
} else {
- makeDateFromInput(config);
+ dow = config._locale._week.dow;
+ doy = config._locale._week.doy;
+
+ weekYear = defaults(w.gg, config._a[YEAR], weekOfYear(local__createLocal(), dow, doy).year);
+ week = defaults(w.w, 1);
+
+ if (w.d != null) {
+ // weekday -- low day numbers are considered next week
+ weekday = w.d;
+ if (weekday < dow) {
+ ++week;
+ }
+ } else if (w.e != null) {
+ // local weekday -- counting starts from begining of week
+ weekday = w.e + dow;
+ } else {
+ // default to begining of week
+ weekday = dow;
+ }
}
+ temp = dayOfYearFromWeeks(weekYear, week, weekday, doy, dow);
- return new Moment(config);
+ config._a[YEAR] = temp.year;
+ config._dayOfYear = temp.dayOfYear;
}
- moment = function (input, format, lang, strict) {
- var c;
+ utils_hooks__hooks.ISO_8601 = function () {};
- if (typeof(lang) === "boolean") {
- strict = lang;
- lang = undefined;
+ // date from string and format string
+ function configFromStringAndFormat(config) {
+ // TODO: Move this to another part of the creation flow to prevent circular deps
+ if (config._f === utils_hooks__hooks.ISO_8601) {
+ configFromISO(config);
+ return;
}
- // object construction must be done this way.
- // https://github.com/moment/moment/issues/1423
- c = {};
- c._isAMomentObject = true;
- c._i = input;
- c._f = format;
- c._l = lang;
- c._strict = strict;
- c._isUTC = false;
- c._pf = defaultParsingFlags();
- return makeMoment(c);
- };
+ config._a = [];
+ getParsingFlags(config).empty = true;
+
+ // This array is used to make a Date, either with `new Date` or `Date.UTC`
+ var string = '' + config._i,
+ i, parsedInput, tokens, token, skipped,
+ stringLength = string.length,
+ totalParsedInputLength = 0;
+
+ tokens = expandFormat(config._f, config._locale).match(formattingTokens) || [];
+
+ for (i = 0; i < tokens.length; i++) {
+ token = tokens[i];
+ parsedInput = (string.match(getParseRegexForToken(token, config)) || [])[0];
+ if (parsedInput) {
+ skipped = string.substr(0, string.indexOf(parsedInput));
+ if (skipped.length > 0) {
+ getParsingFlags(config).unusedInput.push(skipped);
+ }
+ string = string.slice(string.indexOf(parsedInput) + parsedInput.length);
+ totalParsedInputLength += parsedInput.length;
+ }
+ // don't parse if it's not a known token
+ if (formatTokenFunctions[token]) {
+ if (parsedInput) {
+ getParsingFlags(config).empty = false;
+ }
+ else {
+ getParsingFlags(config).unusedTokens.push(token);
+ }
+ addTimeToArrayFromToken(token, parsedInput, config);
+ }
+ else if (config._strict && !parsedInput) {
+ getParsingFlags(config).unusedTokens.push(token);
+ }
+ }
+
+ // add remaining unparsed input length to the string
+ getParsingFlags(config).charsLeftOver = stringLength - totalParsedInputLength;
+ if (string.length > 0) {
+ getParsingFlags(config).unusedInput.push(string);
+ }
+
+ // clear _12h flag if hour is <= 12
+ if (getParsingFlags(config).bigHour === true &&
+ config._a[HOUR] <= 12 &&
+ config._a[HOUR] > 0) {
+ getParsingFlags(config).bigHour = undefined;
+ }
+ // handle meridiem
+ config._a[HOUR] = meridiemFixWrap(config._locale, config._a[HOUR], config._meridiem);
+
+ configFromArray(config);
+ checkOverflow(config);
+ }
+
+
+ function meridiemFixWrap (locale, hour, meridiem) {
+ var isPm;
+
+ if (meridiem == null) {
+ // nothing to do
+ return hour;
+ }
+ if (locale.meridiemHour != null) {
+ return locale.meridiemHour(hour, meridiem);
+ } else if (locale.isPM != null) {
+ // Fallback
+ isPm = locale.isPM(meridiem);
+ if (isPm && hour < 12) {
+ hour += 12;
+ }
+ if (!isPm && hour === 12) {
+ hour = 0;
+ }
+ return hour;
+ } else {
+ // this is not supposed to happen
+ return hour;
+ }
+ }
+
+ function configFromStringAndArray(config) {
+ var tempConfig,
+ bestMoment,
+
+ scoreToBeat,
+ i,
+ currentScore;
+
+ if (config._f.length === 0) {
+ getParsingFlags(config).invalidFormat = true;
+ config._d = new Date(NaN);
+ return;
+ }
+
+ for (i = 0; i < config._f.length; i++) {
+ currentScore = 0;
+ tempConfig = copyConfig({}, config);
+ if (config._useUTC != null) {
+ tempConfig._useUTC = config._useUTC;
+ }
+ tempConfig._f = config._f[i];
+ configFromStringAndFormat(tempConfig);
+
+ if (!valid__isValid(tempConfig)) {
+ continue;
+ }
+
+ // if there is any input that was not parsed add a penalty for that format
+ currentScore += getParsingFlags(tempConfig).charsLeftOver;
+
+ //or tokens
+ currentScore += getParsingFlags(tempConfig).unusedTokens.length * 10;
+
+ getParsingFlags(tempConfig).score = currentScore;
+
+ if (scoreToBeat == null || currentScore < scoreToBeat) {
+ scoreToBeat = currentScore;
+ bestMoment = tempConfig;
+ }
+ }
+
+ extend(config, bestMoment || tempConfig);
+ }
+
+ function configFromObject(config) {
+ if (config._d) {
+ return;
+ }
+
+ var i = normalizeObjectUnits(config._i);
+ config._a = [i.year, i.month, i.day || i.date, i.hour, i.minute, i.second, i.millisecond];
+
+ configFromArray(config);
+ }
+
+ function createFromConfig (config) {
+ var input = config._i,
+ format = config._f,
+ res;
+
+ config._locale = config._locale || locale_locales__getLocale(config._l);
+
+ if (input === null || (format === undefined && input === '')) {
+ return valid__createInvalid({nullInput: true});
+ }
+
+ if (typeof input === 'string') {
+ config._i = input = config._locale.preparse(input);
+ }
+
+ if (isMoment(input)) {
+ return new Moment(checkOverflow(input));
+ } else if (isArray(format)) {
+ configFromStringAndArray(config);
+ } else if (format) {
+ configFromStringAndFormat(config);
+ } else if (isDate(input)) {
+ config._d = input;
+ } else {
+ configFromInput(config);
+ }
+
+ res = new Moment(checkOverflow(config));
+ if (res._nextDay) {
+ // Adding is smart enough around DST
+ res.add(1, 'd');
+ res._nextDay = undefined;
+ }
+
+ return res;
+ }
+
+ function configFromInput(config) {
+ var input = config._i;
+ if (input === undefined) {
+ config._d = new Date();
+ } else if (isDate(input)) {
+ config._d = new Date(+input);
+ } else if (typeof input === 'string') {
+ configFromString(config);
+ } else if (isArray(input)) {
+ config._a = map(input.slice(0), function (obj) {
+ return parseInt(obj, 10);
+ });
+ configFromArray(config);
+ } else if (typeof(input) === 'object') {
+ configFromObject(config);
+ } else if (typeof(input) === 'number') {
+ // from milliseconds
+ config._d = new Date(input);
+ } else {
+ utils_hooks__hooks.createFromInputFallback(config);
+ }
+ }
+
+ function createLocalOrUTC (input, format, locale, strict, isUTC) {
+ var c = {};
+
+ if (typeof(locale) === 'boolean') {
+ strict = locale;
+ locale = undefined;
+ }
+ // object construction must be done this way.
+ // https://github.com/moment/moment/issues/1423
+ c._isAMomentObject = true;
+ c._useUTC = c._isUTC = isUTC;
+ c._l = locale;
+ c._i = input;
+ c._f = format;
+ c._strict = strict;
+
+ return createFromConfig(c);
+ }
+
+ function local__createLocal (input, format, locale, strict) {
+ return createLocalOrUTC(input, format, locale, strict, false);
+ }
+
+ var prototypeMin = deprecate(
+ 'moment().min is deprecated, use moment.min instead. https://github.com/moment/moment/issues/1548',
+ function () {
+ var other = local__createLocal.apply(null, arguments);
+ return other < this ? this : other;
+ }
+ );
+
+ var prototypeMax = deprecate(
+ 'moment().max is deprecated, use moment.max instead. https://github.com/moment/moment/issues/1548',
+ function () {
+ var other = local__createLocal.apply(null, arguments);
+ return other > this ? this : other;
+ }
+ );
+
+ // Pick a moment m from moments so that m[fn](other) is true for all
+ // other. This relies on the function fn to be transitive.
+ //
+ // moments should either be an array of moment objects or an array, whose
+ // first element is an array of moment objects.
+ function pickBy(fn, moments) {
+ var res, i;
+ if (moments.length === 1 && isArray(moments[0])) {
+ moments = moments[0];
+ }
+ if (!moments.length) {
+ return local__createLocal();
+ }
+ res = moments[0];
+ for (i = 1; i < moments.length; ++i) {
+ if (moments[i][fn](res)) {
+ res = moments[i];
+ }
+ }
+ return res;
+ }
+
+ // TODO: Use [].sort instead?
+ function min () {
+ var args = [].slice.call(arguments, 0);
+
+ return pickBy('isBefore', args);
+ }
+
+ function max () {
+ var args = [].slice.call(arguments, 0);
+
+ return pickBy('isAfter', args);
+ }
+
+ function Duration (duration) {
+ var normalizedInput = normalizeObjectUnits(duration),
+ years = normalizedInput.year || 0,
+ quarters = normalizedInput.quarter || 0,
+ months = normalizedInput.month || 0,
+ weeks = normalizedInput.week || 0,
+ days = normalizedInput.day || 0,
+ hours = normalizedInput.hour || 0,
+ minutes = normalizedInput.minute || 0,
+ seconds = normalizedInput.second || 0,
+ milliseconds = normalizedInput.millisecond || 0;
+
+ // representation for dateAddRemove
+ this._milliseconds = +milliseconds +
+ seconds * 1e3 + // 1000
+ minutes * 6e4 + // 1000 * 60
+ hours * 36e5; // 1000 * 60 * 60
+ // Because of dateAddRemove treats 24 hours as different from a
+ // day when working around DST, we need to store them separately
+ this._days = +days +
+ weeks * 7;
+ // It is impossible translate months into days without knowing
+ // which months you are are talking about, so we have to store
+ // it separately.
+ this._months = +months +
+ quarters * 3 +
+ years * 12;
+
+ this._data = {};
+
+ this._locale = locale_locales__getLocale();
+
+ this._bubble();
+ }
+
+ function isDuration (obj) {
+ return obj instanceof Duration;
+ }
+
+ function offset (token, separator) {
+ addFormatToken(token, 0, 0, function () {
+ var offset = this.utcOffset();
+ var sign = '+';
+ if (offset < 0) {
+ offset = -offset;
+ sign = '-';
+ }
+ return sign + zeroFill(~~(offset / 60), 2) + separator + zeroFill(~~(offset) % 60, 2);
+ });
+ }
+
+ offset('Z', ':');
+ offset('ZZ', '');
+
+ // PARSING
+
+ addRegexToken('Z', matchOffset);
+ addRegexToken('ZZ', matchOffset);
+ addParseToken(['Z', 'ZZ'], function (input, array, config) {
+ config._useUTC = true;
+ config._tzm = offsetFromString(input);
+ });
+
+ // HELPERS
+
+ // timezone chunker
+ // '+10:00' > ['10', '00']
+ // '-1530' > ['-15', '30']
+ var chunkOffset = /([\+\-]|\d\d)/gi;
+
+ function offsetFromString(string) {
+ var matches = ((string || '').match(matchOffset) || []);
+ var chunk = matches[matches.length - 1] || [];
+ var parts = (chunk + '').match(chunkOffset) || ['-', 0, 0];
+ var minutes = +(parts[1] * 60) + toInt(parts[2]);
+
+ return parts[0] === '+' ? minutes : -minutes;
+ }
+
+ // Return a moment from input, that is local/utc/zone equivalent to model.
+ function cloneWithOffset(input, model) {
+ var res, diff;
+ if (model._isUTC) {
+ res = model.clone();
+ diff = (isMoment(input) || isDate(input) ? +input : +local__createLocal(input)) - (+res);
+ // Use low-level api, because this fn is low-level api.
+ res._d.setTime(+res._d + diff);
+ utils_hooks__hooks.updateOffset(res, false);
+ return res;
+ } else {
+ return local__createLocal(input).local();
+ }
+ return model._isUTC ? local__createLocal(input).zone(model._offset || 0) : local__createLocal(input).local();
+ }
+
+ function getDateOffset (m) {
+ // On Firefox.24 Date#getTimezoneOffset returns a floating point.
+ // https://github.com/moment/moment/pull/1871
+ return -Math.round(m._d.getTimezoneOffset() / 15) * 15;
+ }
+
+ // HOOKS
+
+ // This function will be called whenever a moment is mutated.
+ // It is intended to keep the offset in sync with the timezone.
+ utils_hooks__hooks.updateOffset = function () {};
+
+ // MOMENTS
+
+ // keepLocalTime = true means only change the timezone, without
+ // affecting the local hour. So 5:31:26 +0300 --[utcOffset(2, true)]-->
+ // 5:31:26 +0200 It is possible that 5:31:26 doesn't exist with offset
+ // +0200, so we adjust the time as needed, to be valid.
+ //
+ // Keeping the time actually adds/subtracts (one hour)
+ // from the actual represented time. That is why we call updateOffset
+ // a second time. In case it wants us to change the offset again
+ // _changeInProgress == true case, then we have to adjust, because
+ // there is no such time in the given timezone.
+ function getSetOffset (input, keepLocalTime) {
+ var offset = this._offset || 0,
+ localAdjust;
+ if (input != null) {
+ if (typeof input === 'string') {
+ input = offsetFromString(input);
+ }
+ if (Math.abs(input) < 16) {
+ input = input * 60;
+ }
+ if (!this._isUTC && keepLocalTime) {
+ localAdjust = getDateOffset(this);
+ }
+ this._offset = input;
+ this._isUTC = true;
+ if (localAdjust != null) {
+ this.add(localAdjust, 'm');
+ }
+ if (offset !== input) {
+ if (!keepLocalTime || this._changeInProgress) {
+ add_subtract__addSubtract(this, create__createDuration(input - offset, 'm'), 1, false);
+ } else if (!this._changeInProgress) {
+ this._changeInProgress = true;
+ utils_hooks__hooks.updateOffset(this, true);
+ this._changeInProgress = null;
+ }
+ }
+ return this;
+ } else {
+ return this._isUTC ? offset : getDateOffset(this);
+ }
+ }
+
+ function getSetZone (input, keepLocalTime) {
+ if (input != null) {
+ if (typeof input !== 'string') {
+ input = -input;
+ }
+
+ this.utcOffset(input, keepLocalTime);
+
+ return this;
+ } else {
+ return -this.utcOffset();
+ }
+ }
+
+ function setOffsetToUTC (keepLocalTime) {
+ return this.utcOffset(0, keepLocalTime);
+ }
+
+ function setOffsetToLocal (keepLocalTime) {
+ if (this._isUTC) {
+ this.utcOffset(0, keepLocalTime);
+ this._isUTC = false;
+
+ if (keepLocalTime) {
+ this.subtract(getDateOffset(this), 'm');
+ }
+ }
+ return this;
+ }
+
+ function setOffsetToParsedOffset () {
+ if (this._tzm) {
+ this.utcOffset(this._tzm);
+ } else if (typeof this._i === 'string') {
+ this.utcOffset(offsetFromString(this._i));
+ }
+ return this;
+ }
+
+ function hasAlignedHourOffset (input) {
+ if (!input) {
+ input = 0;
+ }
+ else {
+ input = local__createLocal(input).utcOffset();
+ }
+
+ return (this.utcOffset() - input) % 60 === 0;
+ }
+
+ function isDaylightSavingTime () {
+ return (
+ this.utcOffset() > this.clone().month(0).utcOffset() ||
+ this.utcOffset() > this.clone().month(5).utcOffset()
+ );
+ }
+
+ function isDaylightSavingTimeShifted () {
+ if (this._a) {
+ var other = this._isUTC ? create_utc__createUTC(this._a) : local__createLocal(this._a);
+ return this.isValid() && compareArrays(this._a, other.toArray()) > 0;
+ }
+
+ return false;
+ }
+
+ function isLocal () {
+ return !this._isUTC;
+ }
+
+ function isUtcOffset () {
+ return this._isUTC;
+ }
+
+ function isUtc () {
+ return this._isUTC && this._offset === 0;
+ }
+
+ var aspNetRegex = /(\-)?(?:(\d*)\.)?(\d+)\:(\d+)(?:\:(\d+)\.?(\d{3})?)?/;
+
+ // from http://docs.closure-library.googlecode.com/git/closure_goog_date_date.js.source.html
+ // somewhat more in line with 4.4.3.2 2004 spec, but allows decimal anywhere
+ var create__isoRegex = /^(-)?P(?:(?:([0-9,.]*)Y)?(?:([0-9,.]*)M)?(?:([0-9,.]*)D)?(?:T(?:([0-9,.]*)H)?(?:([0-9,.]*)M)?(?:([0-9,.]*)S)?)?|([0-9,.]*)W)$/;
+
+ function create__createDuration (input, key) {
+ var duration = input,
+ // matching against regexp is expensive, do it on demand
+ match = null,
+ sign,
+ ret,
+ diffRes;
+
+ if (isDuration(input)) {
+ duration = {
+ ms : input._milliseconds,
+ d : input._days,
+ M : input._months
+ };
+ } else if (typeof input === 'number') {
+ duration = {};
+ if (key) {
+ duration[key] = input;
+ } else {
+ duration.milliseconds = input;
+ }
+ } else if (!!(match = aspNetRegex.exec(input))) {
+ sign = (match[1] === '-') ? -1 : 1;
+ duration = {
+ y : 0,
+ d : toInt(match[DATE]) * sign,
+ h : toInt(match[HOUR]) * sign,
+ m : toInt(match[MINUTE]) * sign,
+ s : toInt(match[SECOND]) * sign,
+ ms : toInt(match[MILLISECOND]) * sign
+ };
+ } else if (!!(match = create__isoRegex.exec(input))) {
+ sign = (match[1] === '-') ? -1 : 1;
+ duration = {
+ y : parseIso(match[2], sign),
+ M : parseIso(match[3], sign),
+ d : parseIso(match[4], sign),
+ h : parseIso(match[5], sign),
+ m : parseIso(match[6], sign),
+ s : parseIso(match[7], sign),
+ w : parseIso(match[8], sign)
+ };
+ } else if (duration == null) {// checks for null or undefined
+ duration = {};
+ } else if (typeof duration === 'object' && ('from' in duration || 'to' in duration)) {
+ diffRes = momentsDifference(local__createLocal(duration.from), local__createLocal(duration.to));
+
+ duration = {};
+ duration.ms = diffRes.milliseconds;
+ duration.M = diffRes.months;
+ }
+
+ ret = new Duration(duration);
+
+ if (isDuration(input) && hasOwnProp(input, '_locale')) {
+ ret._locale = input._locale;
+ }
+
+ return ret;
+ }
+
+ create__createDuration.fn = Duration.prototype;
+
+ function parseIso (inp, sign) {
+ // We'd normally use ~~inp for this, but unfortunately it also
+ // converts floats to ints.
+ // inp may be undefined, so careful calling replace on it.
+ var res = inp && parseFloat(inp.replace(',', '.'));
+ // apply sign while we're at it
+ return (isNaN(res) ? 0 : res) * sign;
+ }
+
+ function positiveMomentsDifference(base, other) {
+ var res = {milliseconds: 0, months: 0};
+
+ res.months = other.month() - base.month() +
+ (other.year() - base.year()) * 12;
+ if (base.clone().add(res.months, 'M').isAfter(other)) {
+ --res.months;
+ }
+
+ res.milliseconds = +other - +(base.clone().add(res.months, 'M'));
+
+ return res;
+ }
+
+ function momentsDifference(base, other) {
+ var res;
+ other = cloneWithOffset(other, base);
+ if (base.isBefore(other)) {
+ res = positiveMomentsDifference(base, other);
+ } else {
+ res = positiveMomentsDifference(other, base);
+ res.milliseconds = -res.milliseconds;
+ res.months = -res.months;
+ }
+
+ return res;
+ }
+
+ function createAdder(direction, name) {
+ return function (val, period) {
+ var dur, tmp;
+ //invert the arguments, but complain about it
+ if (period !== null && !isNaN(+period)) {
+ deprecateSimple(name, 'moment().' + name + '(period, number) is deprecated. Please use moment().' + name + '(number, period).');
+ tmp = val; val = period; period = tmp;
+ }
+
+ val = typeof val === 'string' ? +val : val;
+ dur = create__createDuration(val, period);
+ add_subtract__addSubtract(this, dur, direction);
+ return this;
+ };
+ }
+
+ function add_subtract__addSubtract (mom, duration, isAdding, updateOffset) {
+ var milliseconds = duration._milliseconds,
+ days = duration._days,
+ months = duration._months;
+ updateOffset = updateOffset == null ? true : updateOffset;
+
+ if (milliseconds) {
+ mom._d.setTime(+mom._d + milliseconds * isAdding);
+ }
+ if (days) {
+ get_set__set(mom, 'Date', get_set__get(mom, 'Date') + days * isAdding);
+ }
+ if (months) {
+ setMonth(mom, get_set__get(mom, 'Month') + months * isAdding);
+ }
+ if (updateOffset) {
+ utils_hooks__hooks.updateOffset(mom, days || months);
+ }
+ }
+
+ var add_subtract__add = createAdder(1, 'add');
+ var add_subtract__subtract = createAdder(-1, 'subtract');
+
+ function moment_calendar__calendar (time) {
+ // We want to compare the start of today, vs this.
+ // Getting start-of-today depends on whether we're local/utc/offset or not.
+ var now = time || local__createLocal(),
+ sod = cloneWithOffset(now, this).startOf('day'),
+ diff = this.diff(sod, 'days', true),
+ format = diff < -6 ? 'sameElse' :
+ diff < -1 ? 'lastWeek' :
+ diff < 0 ? 'lastDay' :
+ diff < 1 ? 'sameDay' :
+ diff < 2 ? 'nextDay' :
+ diff < 7 ? 'nextWeek' : 'sameElse';
+ return this.format(this.localeData().calendar(format, this, local__createLocal(now)));
+ }
+
+ function clone () {
+ return new Moment(this);
+ }
+
+ function isAfter (input, units) {
+ var inputMs;
+ units = normalizeUnits(typeof units !== 'undefined' ? units : 'millisecond');
+ if (units === 'millisecond') {
+ input = isMoment(input) ? input : local__createLocal(input);
+ return +this > +input;
+ } else {
+ inputMs = isMoment(input) ? +input : +local__createLocal(input);
+ return inputMs < +this.clone().startOf(units);
+ }
+ }
+
+ function isBefore (input, units) {
+ var inputMs;
+ units = normalizeUnits(typeof units !== 'undefined' ? units : 'millisecond');
+ if (units === 'millisecond') {
+ input = isMoment(input) ? input : local__createLocal(input);
+ return +this < +input;
+ } else {
+ inputMs = isMoment(input) ? +input : +local__createLocal(input);
+ return +this.clone().endOf(units) < inputMs;
+ }
+ }
+
+ function isBetween (from, to, units) {
+ return this.isAfter(from, units) && this.isBefore(to, units);
+ }
+
+ function isSame (input, units) {
+ var inputMs;
+ units = normalizeUnits(units || 'millisecond');
+ if (units === 'millisecond') {
+ input = isMoment(input) ? input : local__createLocal(input);
+ return +this === +input;
+ } else {
+ inputMs = +local__createLocal(input);
+ return +(this.clone().startOf(units)) <= inputMs && inputMs <= +(this.clone().endOf(units));
+ }
+ }
+
+ function absFloor (number) {
+ if (number < 0) {
+ return Math.ceil(number);
+ } else {
+ return Math.floor(number);
+ }
+ }
+
+ function diff (input, units, asFloat) {
+ var that = cloneWithOffset(input, this),
+ zoneDelta = (that.utcOffset() - this.utcOffset()) * 6e4,
+ delta, output;
+
+ units = normalizeUnits(units);
+
+ if (units === 'year' || units === 'month' || units === 'quarter') {
+ output = monthDiff(this, that);
+ if (units === 'quarter') {
+ output = output / 3;
+ } else if (units === 'year') {
+ output = output / 12;
+ }
+ } else {
+ delta = this - that;
+ output = units === 'second' ? delta / 1e3 : // 1000
+ units === 'minute' ? delta / 6e4 : // 1000 * 60
+ units === 'hour' ? delta / 36e5 : // 1000 * 60 * 60
+ units === 'day' ? (delta - zoneDelta) / 864e5 : // 1000 * 60 * 60 * 24, negate dst
+ units === 'week' ? (delta - zoneDelta) / 6048e5 : // 1000 * 60 * 60 * 24 * 7, negate dst
+ delta;
+ }
+ return asFloat ? output : absFloor(output);
+ }
+
+ function monthDiff (a, b) {
+ // difference in months
+ var wholeMonthDiff = ((b.year() - a.year()) * 12) + (b.month() - a.month()),
+ // b is in (anchor - 1 month, anchor + 1 month)
+ anchor = a.clone().add(wholeMonthDiff, 'months'),
+ anchor2, adjust;
+
+ if (b - anchor < 0) {
+ anchor2 = a.clone().add(wholeMonthDiff - 1, 'months');
+ // linear across the month
+ adjust = (b - anchor) / (anchor - anchor2);
+ } else {
+ anchor2 = a.clone().add(wholeMonthDiff + 1, 'months');
+ // linear across the month
+ adjust = (b - anchor) / (anchor2 - anchor);
+ }
+
+ return -(wholeMonthDiff + adjust);
+ }
+
+ utils_hooks__hooks.defaultFormat = 'YYYY-MM-DDTHH:mm:ssZ';
+
+ function toString () {
+ return this.clone().locale('en').format('ddd MMM DD YYYY HH:mm:ss [GMT]ZZ');
+ }
+
+ function moment_format__toISOString () {
+ var m = this.clone().utc();
+ if (0 < m.year() && m.year() <= 9999) {
+ if ('function' === typeof Date.prototype.toISOString) {
+ // native implementation is ~50x faster, use it when we can
+ return this.toDate().toISOString();
+ } else {
+ return formatMoment(m, 'YYYY-MM-DD[T]HH:mm:ss.SSS[Z]');
+ }
+ } else {
+ return formatMoment(m, 'YYYYYY-MM-DD[T]HH:mm:ss.SSS[Z]');
+ }
+ }
+
+ function format (inputString) {
+ var output = formatMoment(this, inputString || utils_hooks__hooks.defaultFormat);
+ return this.localeData().postformat(output);
+ }
+
+ function from (time, withoutSuffix) {
+ if (!this.isValid()) {
+ return this.localeData().invalidDate();
+ }
+ return create__createDuration({to: this, from: time}).locale(this.locale()).humanize(!withoutSuffix);
+ }
+
+ function fromNow (withoutSuffix) {
+ return this.from(local__createLocal(), withoutSuffix);
+ }
+
+ function to (time, withoutSuffix) {
+ if (!this.isValid()) {
+ return this.localeData().invalidDate();
+ }
+ return create__createDuration({from: this, to: time}).locale(this.locale()).humanize(!withoutSuffix);
+ }
+
+ function toNow (withoutSuffix) {
+ return this.to(local__createLocal(), withoutSuffix);
+ }
+
+ function locale (key) {
+ var newLocaleData;
+
+ if (key === undefined) {
+ return this._locale._abbr;
+ } else {
+ newLocaleData = locale_locales__getLocale(key);
+ if (newLocaleData != null) {
+ this._locale = newLocaleData;
+ }
+ return this;
+ }
+ }
+
+ var lang = deprecate(
+ 'moment().lang() is deprecated. Instead, use moment().localeData() to get the language configuration. Use moment().locale() to change languages.',
+ function (key) {
+ if (key === undefined) {
+ return this.localeData();
+ } else {
+ return this.locale(key);
+ }
+ }
+ );
+
+ function localeData () {
+ return this._locale;
+ }
+
+ function startOf (units) {
+ units = normalizeUnits(units);
+ // the following switch intentionally omits break keywords
+ // to utilize falling through the cases.
+ switch (units) {
+ case 'year':
+ this.month(0);
+ /* falls through */
+ case 'quarter':
+ case 'month':
+ this.date(1);
+ /* falls through */
+ case 'week':
+ case 'isoWeek':
+ case 'day':
+ this.hours(0);
+ /* falls through */
+ case 'hour':
+ this.minutes(0);
+ /* falls through */
+ case 'minute':
+ this.seconds(0);
+ /* falls through */
+ case 'second':
+ this.milliseconds(0);
+ }
+
+ // weeks are a special case
+ if (units === 'week') {
+ this.weekday(0);
+ }
+ if (units === 'isoWeek') {
+ this.isoWeekday(1);
+ }
+
+ // quarters are also special
+ if (units === 'quarter') {
+ this.month(Math.floor(this.month() / 3) * 3);
+ }
+
+ return this;
+ }
+
+ function endOf (units) {
+ units = normalizeUnits(units);
+ if (units === undefined || units === 'millisecond') {
+ return this;
+ }
+ return this.startOf(units).add(1, (units === 'isoWeek' ? 'week' : units)).subtract(1, 'ms');
+ }
+
+ function to_type__valueOf () {
+ return +this._d - ((this._offset || 0) * 60000);
+ }
+
+ function unix () {
+ return Math.floor(+this / 1000);
+ }
+
+ function toDate () {
+ return this._offset ? new Date(+this) : this._d;
+ }
+
+ function toArray () {
+ var m = this;
+ return [m.year(), m.month(), m.date(), m.hour(), m.minute(), m.second(), m.millisecond()];
+ }
+
+ function moment_valid__isValid () {
+ return valid__isValid(this);
+ }
+
+ function parsingFlags () {
+ return extend({}, getParsingFlags(this));
+ }
+
+ function invalidAt () {
+ return getParsingFlags(this).overflow;
+ }
+
+ addFormatToken(0, ['gg', 2], 0, function () {
+ return this.weekYear() % 100;
+ });
+
+ addFormatToken(0, ['GG', 2], 0, function () {
+ return this.isoWeekYear() % 100;
+ });
+
+ function addWeekYearFormatToken (token, getter) {
+ addFormatToken(0, [token, token.length], 0, getter);
+ }
+
+ addWeekYearFormatToken('gggg', 'weekYear');
+ addWeekYearFormatToken('ggggg', 'weekYear');
+ addWeekYearFormatToken('GGGG', 'isoWeekYear');
+ addWeekYearFormatToken('GGGGG', 'isoWeekYear');
+
+ // ALIASES
+
+ addUnitAlias('weekYear', 'gg');
+ addUnitAlias('isoWeekYear', 'GG');
+
+ // PARSING
+
+ addRegexToken('G', matchSigned);
+ addRegexToken('g', matchSigned);
+ addRegexToken('GG', match1to2, match2);
+ addRegexToken('gg', match1to2, match2);
+ addRegexToken('GGGG', match1to4, match4);
+ addRegexToken('gggg', match1to4, match4);
+ addRegexToken('GGGGG', match1to6, match6);
+ addRegexToken('ggggg', match1to6, match6);
+
+ addWeekParseToken(['gggg', 'ggggg', 'GGGG', 'GGGGG'], function (input, week, config, token) {
+ week[token.substr(0, 2)] = toInt(input);
+ });
+
+ addWeekParseToken(['gg', 'GG'], function (input, week, config, token) {
+ week[token] = utils_hooks__hooks.parseTwoDigitYear(input);
+ });
+
+ // HELPERS
+
+ function weeksInYear(year, dow, doy) {
+ return weekOfYear(local__createLocal([year, 11, 31 + dow - doy]), dow, doy).week;
+ }
+
+ // MOMENTS
+
+ function getSetWeekYear (input) {
+ var year = weekOfYear(this, this.localeData()._week.dow, this.localeData()._week.doy).year;
+ return input == null ? year : this.add((input - year), 'y');
+ }
+
+ function getSetISOWeekYear (input) {
+ var year = weekOfYear(this, 1, 4).year;
+ return input == null ? year : this.add((input - year), 'y');
+ }
+
+ function getISOWeeksInYear () {
+ return weeksInYear(this.year(), 1, 4);
+ }
+
+ function getWeeksInYear () {
+ var weekInfo = this.localeData()._week;
+ return weeksInYear(this.year(), weekInfo.dow, weekInfo.doy);
+ }
+
+ addFormatToken('Q', 0, 0, 'quarter');
+
+ // ALIASES
+
+ addUnitAlias('quarter', 'Q');
+
+ // PARSING
+
+ addRegexToken('Q', match1);
+ addParseToken('Q', function (input, array) {
+ array[MONTH] = (toInt(input) - 1) * 3;
+ });
+
+ // MOMENTS
+
+ function getSetQuarter (input) {
+ return input == null ? Math.ceil((this.month() + 1) / 3) : this.month((input - 1) * 3 + this.month() % 3);
+ }
+
+ addFormatToken('D', ['DD', 2], 'Do', 'date');
+
+ // ALIASES
+
+ addUnitAlias('date', 'D');
+
+ // PARSING
+
+ addRegexToken('D', match1to2);
+ addRegexToken('DD', match1to2, match2);
+ addRegexToken('Do', function (isStrict, locale) {
+ return isStrict ? locale._ordinalParse : locale._ordinalParseLenient;
+ });
+
+ addParseToken(['D', 'DD'], DATE);
+ addParseToken('Do', function (input, array) {
+ array[DATE] = toInt(input.match(match1to2)[0], 10);
+ });
+
+ // MOMENTS
+
+ var getSetDayOfMonth = makeGetSet('Date', true);
+
+ addFormatToken('d', 0, 'do', 'day');
+
+ addFormatToken('dd', 0, 0, function (format) {
+ return this.localeData().weekdaysMin(this, format);
+ });
+
+ addFormatToken('ddd', 0, 0, function (format) {
+ return this.localeData().weekdaysShort(this, format);
+ });
+
+ addFormatToken('dddd', 0, 0, function (format) {
+ return this.localeData().weekdays(this, format);
+ });
+
+ addFormatToken('e', 0, 0, 'weekday');
+ addFormatToken('E', 0, 0, 'isoWeekday');
+
+ // ALIASES
+
+ addUnitAlias('day', 'd');
+ addUnitAlias('weekday', 'e');
+ addUnitAlias('isoWeekday', 'E');
+
+ // PARSING
+
+ addRegexToken('d', match1to2);
+ addRegexToken('e', match1to2);
+ addRegexToken('E', match1to2);
+ addRegexToken('dd', matchWord);
+ addRegexToken('ddd', matchWord);
+ addRegexToken('dddd', matchWord);
+
+ addWeekParseToken(['dd', 'ddd', 'dddd'], function (input, week, config) {
+ var weekday = config._locale.weekdaysParse(input);
+ // if we didn't get a weekday name, mark the date as invalid
+ if (weekday != null) {
+ week.d = weekday;
+ } else {
+ getParsingFlags(config).invalidWeekday = input;
+ }
+ });
+
+ addWeekParseToken(['d', 'e', 'E'], function (input, week, config, token) {
+ week[token] = toInt(input);
+ });
+
+ // HELPERS
+
+ function parseWeekday(input, locale) {
+ if (typeof input === 'string') {
+ if (!isNaN(input)) {
+ input = parseInt(input, 10);
+ }
+ else {
+ input = locale.weekdaysParse(input);
+ if (typeof input !== 'number') {
+ return null;
+ }
+ }
+ }
+ return input;
+ }
+
+ // LOCALES
+
+ var defaultLocaleWeekdays = 'Sunday_Monday_Tuesday_Wednesday_Thursday_Friday_Saturday'.split('_');
+ function localeWeekdays (m) {
+ return this._weekdays[m.day()];
+ }
+
+ var defaultLocaleWeekdaysShort = 'Sun_Mon_Tue_Wed_Thu_Fri_Sat'.split('_');
+ function localeWeekdaysShort (m) {
+ return this._weekdaysShort[m.day()];
+ }
- moment.suppressDeprecationWarnings = false;
+ var defaultLocaleWeekdaysMin = 'Su_Mo_Tu_We_Th_Fr_Sa'.split('_');
+ function localeWeekdaysMin (m) {
+ return this._weekdaysMin[m.day()];
+ }
- moment.createFromInputFallback = deprecate(
- "moment construction falls back to js Date. This is " +
- "discouraged and will be removed in upcoming major " +
- "release. Please refer to " +
- "https://github.com/moment/moment/issues/1407 for more info.",
- function (config) {
- config._d = new Date(config._i);
- });
+ function localeWeekdaysParse (weekdayName) {
+ var i, mom, regex;
- // Pick a moment m from moments so that m[fn](other) is true for all
- // other. This relies on the function fn to be transitive.
- //
- // moments should either be an array of moment objects or an array, whose
- // first element is an array of moment objects.
- function pickBy(fn, moments) {
- var res, i;
- if (moments.length === 1 && isArray(moments[0])) {
- moments = moments[0];
- }
- if (!moments.length) {
- return moment();
+ if (!this._weekdaysParse) {
+ this._weekdaysParse = [];
}
- res = moments[0];
- for (i = 1; i < moments.length; ++i) {
- if (moments[i][fn](res)) {
- res = moments[i];
+
+ for (i = 0; i < 7; i++) {
+ // make the regex if we don't have it already
+ if (!this._weekdaysParse[i]) {
+ mom = local__createLocal([2000, 1]).day(i);
+ regex = '^' + this.weekdays(mom, '') + '|^' + this.weekdaysShort(mom, '') + '|^' + this.weekdaysMin(mom, '');
+ this._weekdaysParse[i] = new RegExp(regex.replace('.', ''), 'i');
+ }
+ // test the regex
+ if (this._weekdaysParse[i].test(weekdayName)) {
+ return i;
}
}
- return res;
}
- moment.min = function () {
- var args = [].slice.call(arguments, 0);
+ // MOMENTS
- return pickBy('isBefore', args);
- };
+ function getSetDayOfWeek (input) {
+ var day = this._isUTC ? this._d.getUTCDay() : this._d.getDay();
+ if (input != null) {
+ input = parseWeekday(input, this.localeData());
+ return this.add(input - day, 'd');
+ } else {
+ return day;
+ }
+ }
- moment.max = function () {
- var args = [].slice.call(arguments, 0);
+ function getSetLocaleDayOfWeek (input) {
+ var weekday = (this.day() + 7 - this.localeData()._week.dow) % 7;
+ return input == null ? weekday : this.add(input - weekday, 'd');
+ }
- return pickBy('isAfter', args);
- };
+ function getSetISODayOfWeek (input) {
+ // behaves the same as moment#day except
+ // as a getter, returns 7 instead of 0 (1-7 range instead of 0-6)
+ // as a setter, sunday should belong to the previous week.
+ return input == null ? this.day() || 7 : this.day(this.day() % 7 ? input : input - 7);
+ }
- // creating with utc
- moment.utc = function (input, format, lang, strict) {
- var c;
+ addFormatToken('H', ['HH', 2], 0, 'hour');
+ addFormatToken('h', ['hh', 2], 0, function () {
+ return this.hours() % 12 || 12;
+ });
- if (typeof(lang) === "boolean") {
- strict = lang;
- lang = undefined;
- }
- // object construction must be done this way.
- // https://github.com/moment/moment/issues/1423
- c = {};
- c._isAMomentObject = true;
- c._useUTC = true;
- c._isUTC = true;
- c._l = lang;
- c._i = input;
- c._f = format;
- c._strict = strict;
- c._pf = defaultParsingFlags();
+ function meridiem (token, lowercase) {
+ addFormatToken(token, 0, 0, function () {
+ return this.localeData().meridiem(this.hours(), this.minutes(), lowercase);
+ });
+ }
- return makeMoment(c).utc();
- };
+ meridiem('a', true);
+ meridiem('A', false);
- // creating with unix timestamp (in seconds)
- moment.unix = function (input) {
- return moment(input * 1000);
- };
+ // ALIASES
- // duration
- moment.duration = function (input, key) {
- var duration = input,
- // matching against regexp is expensive, do it on demand
- match = null,
- sign,
- ret,
- parseIso;
+ addUnitAlias('hour', 'h');
- if (moment.isDuration(input)) {
- duration = {
- ms: input._milliseconds,
- d: input._days,
- M: input._months
- };
- } else if (typeof input === 'number') {
- duration = {};
- if (key) {
- duration[key] = input;
- } else {
- duration.milliseconds = input;
- }
- } else if (!!(match = aspNetTimeSpanJsonRegex.exec(input))) {
- sign = (match[1] === "-") ? -1 : 1;
- duration = {
- y: 0,
- d: toInt(match[DATE]) * sign,
- h: toInt(match[HOUR]) * sign,
- m: toInt(match[MINUTE]) * sign,
- s: toInt(match[SECOND]) * sign,
- ms: toInt(match[MILLISECOND]) * sign
- };
- } else if (!!(match = isoDurationRegex.exec(input))) {
- sign = (match[1] === "-") ? -1 : 1;
- parseIso = function (inp) {
- // We'd normally use ~~inp for this, but unfortunately it also
- // converts floats to ints.
- // inp may be undefined, so careful calling replace on it.
- var res = inp && parseFloat(inp.replace(',', '.'));
- // apply sign while we're at it
- return (isNaN(res) ? 0 : res) * sign;
- };
- duration = {
- y: parseIso(match[2]),
- M: parseIso(match[3]),
- d: parseIso(match[4]),
- h: parseIso(match[5]),
- m: parseIso(match[6]),
- s: parseIso(match[7]),
- w: parseIso(match[8])
- };
- }
+ // PARSING
- ret = new Duration(duration);
+ function matchMeridiem (isStrict, locale) {
+ return locale._meridiemParse;
+ }
- if (moment.isDuration(input) && input.hasOwnProperty('_lang')) {
- ret._lang = input._lang;
- }
+ addRegexToken('a', matchMeridiem);
+ addRegexToken('A', matchMeridiem);
+ addRegexToken('H', match1to2);
+ addRegexToken('h', match1to2);
+ addRegexToken('HH', match1to2, match2);
+ addRegexToken('hh', match1to2, match2);
- return ret;
- };
+ addParseToken(['H', 'HH'], HOUR);
+ addParseToken(['a', 'A'], function (input, array, config) {
+ config._isPm = config._locale.isPM(input);
+ config._meridiem = input;
+ });
+ addParseToken(['h', 'hh'], function (input, array, config) {
+ array[HOUR] = toInt(input);
+ getParsingFlags(config).bigHour = true;
+ });
- // version number
- moment.version = VERSION;
+ // LOCALES
- // default format
- moment.defaultFormat = isoFormat;
+ function localeIsPM (input) {
+ // IE8 Quirks Mode & IE7 Standards Mode do not allow accessing strings like arrays
+ // Using charAt should be more compatible.
+ return ((input + '').toLowerCase().charAt(0) === 'p');
+ }
- // constant that refers to the ISO standard
- moment.ISO_8601 = function () {};
+ var defaultLocaleMeridiemParse = /[ap]\.?m?\.?/i;
+ function localeMeridiem (hours, minutes, isLower) {
+ if (hours > 11) {
+ return isLower ? 'pm' : 'PM';
+ } else {
+ return isLower ? 'am' : 'AM';
+ }
+ }
- // Plugins that add properties should also add the key here (null value),
- // so we can properly clone ourselves.
- moment.momentProperties = momentProperties;
- // This function will be called whenever a moment is mutated.
- // It is intended to keep the offset in sync with the timezone.
- moment.updateOffset = function () {};
+ // MOMENTS
- // This function allows you to set a threshold for relative time strings
- moment.relativeTimeThreshold = function(threshold, limit) {
- if (relativeTimeThresholds[threshold] === undefined) {
- return false;
- }
- relativeTimeThresholds[threshold] = limit;
- return true;
- };
+ // Setting the hour should keep the time, because the user explicitly
+ // specified which hour he wants. So trying to maintain the same hour (in
+ // a new timezone) makes sense. Adding/subtracting hours does not follow
+ // this rule.
+ var getSetHour = makeGetSet('Hours', true);
- // This function will load languages and then set the global language. If
- // no arguments are passed in, it will simply return the current global
- // language key.
- moment.lang = function (key, values) {
- var r;
- if (!key) {
- return moment.fn._lang._abbr;
- }
- if (values) {
- loadLang(normalizeLanguage(key), values);
- } else if (values === null) {
- unloadLang(key);
- key = 'en';
- } else if (!languages[key]) {
- getLangDefinition(key);
- }
- r = moment.duration.fn._lang = moment.fn._lang = getLangDefinition(key);
- return r._abbr;
- };
+ addFormatToken('m', ['mm', 2], 0, 'minute');
- // returns language data
- moment.langData = function (key) {
- if (key && key._lang && key._lang._abbr) {
- key = key._lang._abbr;
- }
- return getLangDefinition(key);
- };
+ // ALIASES
- // compare moment object
- moment.isMoment = function (obj) {
- return obj instanceof Moment ||
- (obj != null && obj.hasOwnProperty('_isAMomentObject'));
- };
+ addUnitAlias('minute', 'm');
- // for typechecking Duration objects
- moment.isDuration = function (obj) {
- return obj instanceof Duration;
- };
+ // PARSING
- for (i = lists.length - 1; i >= 0; --i) {
- makeList(lists[i]);
- }
+ addRegexToken('m', match1to2);
+ addRegexToken('mm', match1to2, match2);
+ addParseToken(['m', 'mm'], MINUTE);
- moment.normalizeUnits = function (units) {
- return normalizeUnits(units);
- };
+ // MOMENTS
- moment.invalid = function (flags) {
- var m = moment.utc(NaN);
- if (flags != null) {
- extend(m._pf, flags);
- }
- else {
- m._pf.userInvalidated = true;
- }
+ var getSetMinute = makeGetSet('Minutes', false);
- return m;
- };
+ addFormatToken('s', ['ss', 2], 0, 'second');
- moment.parseZone = function () {
- return moment.apply(null, arguments).parseZone();
- };
+ // ALIASES
- moment.parseTwoDigitYear = function (input) {
- return toInt(input) + (toInt(input) > 68 ? 1900 : 2000);
- };
+ addUnitAlias('second', 's');
- /************************************
- Moment Prototype
- ************************************/
+ // PARSING
+ addRegexToken('s', match1to2);
+ addRegexToken('ss', match1to2, match2);
+ addParseToken(['s', 'ss'], SECOND);
- extend(moment.fn = Moment.prototype, {
+ // MOMENTS
- clone : function () {
- return moment(this);
- },
+ var getSetSecond = makeGetSet('Seconds', false);
- valueOf : function () {
- return +this._d + ((this._offset || 0) * 60000);
- },
+ addFormatToken('S', 0, 0, function () {
+ return ~~(this.millisecond() / 100);
+ });
- unix : function () {
- return Math.floor(+this / 1000);
- },
+ addFormatToken(0, ['SS', 2], 0, function () {
+ return ~~(this.millisecond() / 10);
+ });
- toString : function () {
- return this.clone().lang('en').format("ddd MMM DD YYYY HH:mm:ss [GMT]ZZ");
- },
+ function millisecond__milliseconds (token) {
+ addFormatToken(0, [token, 3], 0, 'millisecond');
+ }
- toDate : function () {
- return this._offset ? new Date(+this) : this._d;
- },
+ millisecond__milliseconds('SSS');
+ millisecond__milliseconds('SSSS');
- toISOString : function () {
- var m = moment(this).utc();
- if (0 < m.year() && m.year() <= 9999) {
- return formatMoment(m, 'YYYY-MM-DD[T]HH:mm:ss.SSS[Z]');
- } else {
- return formatMoment(m, 'YYYYYY-MM-DD[T]HH:mm:ss.SSS[Z]');
- }
- },
-
- toArray : function () {
- var m = this;
- return [
- m.year(),
- m.month(),
- m.date(),
- m.hours(),
- m.minutes(),
- m.seconds(),
- m.milliseconds()
- ];
- },
-
- isValid : function () {
- return isValid(this);
- },
-
- isDSTShifted : function () {
-
- if (this._a) {
- return this.isValid() && compareArrays(this._a, (this._isUTC ? moment.utc(this._a) : moment(this._a)).toArray()) > 0;
- }
+ // ALIASES
- return false;
- },
+ addUnitAlias('millisecond', 'ms');
- parsingFlags : function () {
- return extend({}, this._pf);
- },
+ // PARSING
- invalidAt: function () {
- return this._pf.overflow;
- },
+ addRegexToken('S', match1to3, match1);
+ addRegexToken('SS', match1to3, match2);
+ addRegexToken('SSS', match1to3, match3);
+ addRegexToken('SSSS', matchUnsigned);
+ addParseToken(['S', 'SS', 'SSS', 'SSSS'], function (input, array) {
+ array[MILLISECOND] = toInt(('0.' + input) * 1000);
+ });
- utc : function () {
- return this.zone(0);
- },
+ // MOMENTS
+
+ var getSetMillisecond = makeGetSet('Milliseconds', false);
+
+ addFormatToken('z', 0, 0, 'zoneAbbr');
+ addFormatToken('zz', 0, 0, 'zoneName');
+
+ // MOMENTS
+
+ function getZoneAbbr () {
+ return this._isUTC ? 'UTC' : '';
+ }
+
+ function getZoneName () {
+ return this._isUTC ? 'Coordinated Universal Time' : '';
+ }
+
+ var momentPrototype__proto = Moment.prototype;
+
+ momentPrototype__proto.add = add_subtract__add;
+ momentPrototype__proto.calendar = moment_calendar__calendar;
+ momentPrototype__proto.clone = clone;
+ momentPrototype__proto.diff = diff;
+ momentPrototype__proto.endOf = endOf;
+ momentPrototype__proto.format = format;
+ momentPrototype__proto.from = from;
+ momentPrototype__proto.fromNow = fromNow;
+ momentPrototype__proto.to = to;
+ momentPrototype__proto.toNow = toNow;
+ momentPrototype__proto.get = getSet;
+ momentPrototype__proto.invalidAt = invalidAt;
+ momentPrototype__proto.isAfter = isAfter;
+ momentPrototype__proto.isBefore = isBefore;
+ momentPrototype__proto.isBetween = isBetween;
+ momentPrototype__proto.isSame = isSame;
+ momentPrototype__proto.isValid = moment_valid__isValid;
+ momentPrototype__proto.lang = lang;
+ momentPrototype__proto.locale = locale;
+ momentPrototype__proto.localeData = localeData;
+ momentPrototype__proto.max = prototypeMax;
+ momentPrototype__proto.min = prototypeMin;
+ momentPrototype__proto.parsingFlags = parsingFlags;
+ momentPrototype__proto.set = getSet;
+ momentPrototype__proto.startOf = startOf;
+ momentPrototype__proto.subtract = add_subtract__subtract;
+ momentPrototype__proto.toArray = toArray;
+ momentPrototype__proto.toDate = toDate;
+ momentPrototype__proto.toISOString = moment_format__toISOString;
+ momentPrototype__proto.toJSON = moment_format__toISOString;
+ momentPrototype__proto.toString = toString;
+ momentPrototype__proto.unix = unix;
+ momentPrototype__proto.valueOf = to_type__valueOf;
+
+ // Year
+ momentPrototype__proto.year = getSetYear;
+ momentPrototype__proto.isLeapYear = getIsLeapYear;
+
+ // Week Year
+ momentPrototype__proto.weekYear = getSetWeekYear;
+ momentPrototype__proto.isoWeekYear = getSetISOWeekYear;
+
+ // Quarter
+ momentPrototype__proto.quarter = momentPrototype__proto.quarters = getSetQuarter;
+
+ // Month
+ momentPrototype__proto.month = getSetMonth;
+ momentPrototype__proto.daysInMonth = getDaysInMonth;
+
+ // Week
+ momentPrototype__proto.week = momentPrototype__proto.weeks = getSetWeek;
+ momentPrototype__proto.isoWeek = momentPrototype__proto.isoWeeks = getSetISOWeek;
+ momentPrototype__proto.weeksInYear = getWeeksInYear;
+ momentPrototype__proto.isoWeeksInYear = getISOWeeksInYear;
+
+ // Day
+ momentPrototype__proto.date = getSetDayOfMonth;
+ momentPrototype__proto.day = momentPrototype__proto.days = getSetDayOfWeek;
+ momentPrototype__proto.weekday = getSetLocaleDayOfWeek;
+ momentPrototype__proto.isoWeekday = getSetISODayOfWeek;
+ momentPrototype__proto.dayOfYear = getSetDayOfYear;
+
+ // Hour
+ momentPrototype__proto.hour = momentPrototype__proto.hours = getSetHour;
+
+ // Minute
+ momentPrototype__proto.minute = momentPrototype__proto.minutes = getSetMinute;
+
+ // Second
+ momentPrototype__proto.second = momentPrototype__proto.seconds = getSetSecond;
+
+ // Millisecond
+ momentPrototype__proto.millisecond = momentPrototype__proto.milliseconds = getSetMillisecond;
+
+ // Offset
+ momentPrototype__proto.utcOffset = getSetOffset;
+ momentPrototype__proto.utc = setOffsetToUTC;
+ momentPrototype__proto.local = setOffsetToLocal;
+ momentPrototype__proto.parseZone = setOffsetToParsedOffset;
+ momentPrototype__proto.hasAlignedHourOffset = hasAlignedHourOffset;
+ momentPrototype__proto.isDST = isDaylightSavingTime;
+ momentPrototype__proto.isDSTShifted = isDaylightSavingTimeShifted;
+ momentPrototype__proto.isLocal = isLocal;
+ momentPrototype__proto.isUtcOffset = isUtcOffset;
+ momentPrototype__proto.isUtc = isUtc;
+ momentPrototype__proto.isUTC = isUtc;
+
+ // Timezone
+ momentPrototype__proto.zoneAbbr = getZoneAbbr;
+ momentPrototype__proto.zoneName = getZoneName;
+
+ // Deprecations
+ momentPrototype__proto.dates = deprecate('dates accessor is deprecated. Use date instead.', getSetDayOfMonth);
+ momentPrototype__proto.months = deprecate('months accessor is deprecated. Use month instead', getSetMonth);
+ momentPrototype__proto.years = deprecate('years accessor is deprecated. Use year instead', getSetYear);
+ momentPrototype__proto.zone = deprecate('moment().zone is deprecated, use moment().utcOffset instead. https://github.com/moment/moment/issues/1779', getSetZone);
+
+ var momentPrototype = momentPrototype__proto;
+
+ function moment__createUnix (input) {
+ return local__createLocal(input * 1000);
+ }
+
+ function moment__createInZone () {
+ return local__createLocal.apply(null, arguments).parseZone();
+ }
+
+ var defaultCalendar = {
+ sameDay : '[Today at] LT',
+ nextDay : '[Tomorrow at] LT',
+ nextWeek : 'dddd [at] LT',
+ lastDay : '[Yesterday at] LT',
+ lastWeek : '[Last] dddd [at] LT',
+ sameElse : 'L'
+ };
- local : function () {
- this.zone(0);
- this._isUTC = false;
- return this;
- },
-
- format : function (inputString) {
- var output = formatMoment(this, inputString || moment.defaultFormat);
- return this.lang().postformat(output);
- },
-
- add : function (input, val) {
- var dur;
- // switch args to support add('s', 1) and add(1, 's')
- if (typeof input === 'string' && typeof val === 'string') {
- dur = moment.duration(isNaN(+val) ? +input : +val, isNaN(+val) ? val : input);
- } else if (typeof input === 'string') {
- dur = moment.duration(+val, input);
- } else {
- dur = moment.duration(input, val);
- }
- addOrSubtractDurationFromMoment(this, dur, 1);
- return this;
- },
-
- subtract : function (input, val) {
- var dur;
- // switch args to support subtract('s', 1) and subtract(1, 's')
- if (typeof input === 'string' && typeof val === 'string') {
- dur = moment.duration(isNaN(+val) ? +input : +val, isNaN(+val) ? val : input);
- } else if (typeof input === 'string') {
- dur = moment.duration(+val, input);
- } else {
- dur = moment.duration(input, val);
- }
- addOrSubtractDurationFromMoment(this, dur, -1);
- return this;
- },
+ function locale_calendar__calendar (key, mom, now) {
+ var output = this._calendar[key];
+ return typeof output === 'function' ? output.call(mom, now) : output;
+ }
- diff : function (input, units, asFloat) {
- var that = makeAs(input, this),
- zoneDiff = (this.zone() - that.zone()) * 6e4,
- diff, output;
+ var defaultLongDateFormat = {
+ LTS : 'h:mm:ss A',
+ LT : 'h:mm A',
+ L : 'MM/DD/YYYY',
+ LL : 'MMMM D, YYYY',
+ LLL : 'MMMM D, YYYY LT',
+ LLLL : 'dddd, MMMM D, YYYY LT'
+ };
- units = normalizeUnits(units);
+ function longDateFormat (key) {
+ var output = this._longDateFormat[key];
+ if (!output && this._longDateFormat[key.toUpperCase()]) {
+ output = this._longDateFormat[key.toUpperCase()].replace(/MMMM|MM|DD|dddd/g, function (val) {
+ return val.slice(1);
+ });
+ this._longDateFormat[key] = output;
+ }
+ return output;
+ }
- if (units === 'year' || units === 'month') {
- // average number of days in the months in the given dates
- diff = (this.daysInMonth() + that.daysInMonth()) * 432e5; // 24 * 60 * 60 * 1000 / 2
- // difference in months
- output = ((this.year() - that.year()) * 12) + (this.month() - that.month());
- // adjust by taking difference in days, average number of days
- // and dst in the given months.
- output += ((this - moment(this).startOf('month')) -
- (that - moment(that).startOf('month'))) / diff;
- // same as above but with zones, to negate all dst
- output -= ((this.zone() - moment(this).startOf('month').zone()) -
- (that.zone() - moment(that).startOf('month').zone())) * 6e4 / diff;
- if (units === 'year') {
- output = output / 12;
- }
- } else {
- diff = (this - that);
- output = units === 'second' ? diff / 1e3 : // 1000
- units === 'minute' ? diff / 6e4 : // 1000 * 60
- units === 'hour' ? diff / 36e5 : // 1000 * 60 * 60
- units === 'day' ? (diff - zoneDiff) / 864e5 : // 1000 * 60 * 60 * 24, negate dst
- units === 'week' ? (diff - zoneDiff) / 6048e5 : // 1000 * 60 * 60 * 24 * 7, negate dst
- diff;
- }
- return asFloat ? output : absRound(output);
- },
-
- from : function (time, withoutSuffix) {
- return moment.duration(this.diff(time)).lang(this.lang()._abbr).humanize(!withoutSuffix);
- },
-
- fromNow : function (withoutSuffix) {
- return this.from(moment(), withoutSuffix);
- },
-
- calendar : function (time) {
- // We want to compare the start of today, vs this.
- // Getting start-of-today depends on whether we're zone'd or not.
- var now = time || moment(),
- sod = makeAs(now, this).startOf('day'),
- diff = this.diff(sod, 'days', true),
- format = diff < -6 ? 'sameElse' :
- diff < -1 ? 'lastWeek' :
- diff < 0 ? 'lastDay' :
- diff < 1 ? 'sameDay' :
- diff < 2 ? 'nextDay' :
- diff < 7 ? 'nextWeek' : 'sameElse';
- return this.format(this.lang().calendar(format, this));
- },
-
- isLeapYear : function () {
- return isLeapYear(this.year());
- },
-
- isDST : function () {
- return (this.zone() < this.clone().month(0).zone() ||
- this.zone() < this.clone().month(5).zone());
- },
-
- day : function (input) {
- var day = this._isUTC ? this._d.getUTCDay() : this._d.getDay();
- if (input != null) {
- input = parseWeekday(input, this.lang());
- return this.add({ d : input - day });
- } else {
- return day;
- }
- },
+ var defaultInvalidDate = 'Invalid date';
- month : makeAccessor('Month', true),
+ function invalidDate () {
+ return this._invalidDate;
+ }
- startOf: function (units) {
- units = normalizeUnits(units);
- // the following switch intentionally omits break keywords
- // to utilize falling through the cases.
- switch (units) {
- case 'year':
- this.month(0);
- /* falls through */
- case 'quarter':
- case 'month':
- this.date(1);
- /* falls through */
- case 'week':
- case 'isoWeek':
- case 'day':
- this.hours(0);
- /* falls through */
- case 'hour':
- this.minutes(0);
- /* falls through */
- case 'minute':
- this.seconds(0);
- /* falls through */
- case 'second':
- this.milliseconds(0);
- /* falls through */
- }
+ var defaultOrdinal = '%d';
+ var defaultOrdinalParse = /\d{1,2}/;
- // weeks are a special case
- if (units === 'week') {
- this.weekday(0);
- } else if (units === 'isoWeek') {
- this.isoWeekday(1);
- }
+ function ordinal (number) {
+ return this._ordinal.replace('%d', number);
+ }
- // quarters are also special
- if (units === 'quarter') {
- this.month(Math.floor(this.month() / 3) * 3);
- }
+ function preParsePostFormat (string) {
+ return string;
+ }
- return this;
- },
+ var defaultRelativeTime = {
+ future : 'in %s',
+ past : '%s ago',
+ s : 'a few seconds',
+ m : 'a minute',
+ mm : '%d minutes',
+ h : 'an hour',
+ hh : '%d hours',
+ d : 'a day',
+ dd : '%d days',
+ M : 'a month',
+ MM : '%d months',
+ y : 'a year',
+ yy : '%d years'
+ };
- endOf: function (units) {
- units = normalizeUnits(units);
- return this.startOf(units).add((units === 'isoWeek' ? 'week' : units), 1).subtract('ms', 1);
- },
-
- isAfter: function (input, units) {
- units = typeof units !== 'undefined' ? units : 'millisecond';
- return +this.clone().startOf(units) > +moment(input).startOf(units);
- },
-
- isBefore: function (input, units) {
- units = typeof units !== 'undefined' ? units : 'millisecond';
- return +this.clone().startOf(units) < +moment(input).startOf(units);
- },
-
- isSame: function (input, units) {
- units = units || 'ms';
- return +this.clone().startOf(units) === +makeAs(input, this).startOf(units);
- },
-
- min: deprecate(
- "moment().min is deprecated, use moment.min instead. https://github.com/moment/moment/issues/1548",
- function (other) {
- other = moment.apply(null, arguments);
- return other < this ? this : other;
- }
- ),
-
- max: deprecate(
- "moment().max is deprecated, use moment.max instead. https://github.com/moment/moment/issues/1548",
- function (other) {
- other = moment.apply(null, arguments);
- return other > this ? this : other;
- }
- ),
-
- // keepTime = true means only change the timezone, without affecting
- // the local hour. So 5:31:26 +0300 --[zone(2, true)]--> 5:31:26 +0200
- // It is possible that 5:31:26 doesn't exist int zone +0200, so we
- // adjust the time as needed, to be valid.
- //
- // Keeping the time actually adds/subtracts (one hour)
- // from the actual represented time. That is why we call updateOffset
- // a second time. In case it wants us to change the offset again
- // _changeInProgress == true case, then we have to adjust, because
- // there is no such time in the given timezone.
- zone : function (input, keepTime) {
- var offset = this._offset || 0;
- if (input != null) {
- if (typeof input === "string") {
- input = timezoneMinutesFromString(input);
- }
- if (Math.abs(input) < 16) {
- input = input * 60;
- }
- this._offset = input;
- this._isUTC = true;
- if (offset !== input) {
- if (!keepTime || this._changeInProgress) {
- addOrSubtractDurationFromMoment(this,
- moment.duration(offset - input, 'm'), 1, false);
- } else if (!this._changeInProgress) {
- this._changeInProgress = true;
- moment.updateOffset(this, true);
- this._changeInProgress = null;
- }
- }
+ function relative__relativeTime (number, withoutSuffix, string, isFuture) {
+ var output = this._relativeTime[string];
+ return (typeof output === 'function') ?
+ output(number, withoutSuffix, string, isFuture) :
+ output.replace(/%d/i, number);
+ }
+
+ function pastFuture (diff, output) {
+ var format = this._relativeTime[diff > 0 ? 'future' : 'past'];
+ return typeof format === 'function' ? format(output) : format.replace(/%s/i, output);
+ }
+
+ function locale_set__set (config) {
+ var prop, i;
+ for (i in config) {
+ prop = config[i];
+ if (typeof prop === 'function') {
+ this[i] = prop;
} else {
- return this._isUTC ? offset : this._d.getTimezoneOffset();
+ this['_' + i] = prop;
}
- return this;
- },
+ }
+ // Lenient ordinal parsing accepts just a number in addition to
+ // number + (possibly) stuff coming from _ordinalParseLenient.
+ this._ordinalParseLenient = new RegExp(this._ordinalParse.source + '|' + (/\d{1,2}/).source);
+ }
- zoneAbbr : function () {
- return this._isUTC ? "UTC" : "";
- },
+ var prototype__proto = Locale.prototype;
- zoneName : function () {
- return this._isUTC ? "Coordinated Universal Time" : "";
- },
+ prototype__proto._calendar = defaultCalendar;
+ prototype__proto.calendar = locale_calendar__calendar;
+ prototype__proto._longDateFormat = defaultLongDateFormat;
+ prototype__proto.longDateFormat = longDateFormat;
+ prototype__proto._invalidDate = defaultInvalidDate;
+ prototype__proto.invalidDate = invalidDate;
+ prototype__proto._ordinal = defaultOrdinal;
+ prototype__proto.ordinal = ordinal;
+ prototype__proto._ordinalParse = defaultOrdinalParse;
+ prototype__proto.preparse = preParsePostFormat;
+ prototype__proto.postformat = preParsePostFormat;
+ prototype__proto._relativeTime = defaultRelativeTime;
+ prototype__proto.relativeTime = relative__relativeTime;
+ prototype__proto.pastFuture = pastFuture;
+ prototype__proto.set = locale_set__set;
- parseZone : function () {
- if (this._tzm) {
- this.zone(this._tzm);
- } else if (typeof this._i === 'string') {
- this.zone(this._i);
- }
- return this;
- },
+ // Month
+ prototype__proto.months = localeMonths;
+ prototype__proto._months = defaultLocaleMonths;
+ prototype__proto.monthsShort = localeMonthsShort;
+ prototype__proto._monthsShort = defaultLocaleMonthsShort;
+ prototype__proto.monthsParse = localeMonthsParse;
- hasAlignedHourOffset : function (input) {
- if (!input) {
- input = 0;
- }
- else {
- input = moment(input).zone();
- }
+ // Week
+ prototype__proto.week = localeWeek;
+ prototype__proto._week = defaultLocaleWeek;
+ prototype__proto.firstDayOfYear = localeFirstDayOfYear;
+ prototype__proto.firstDayOfWeek = localeFirstDayOfWeek;
- return (this.zone() - input) % 60 === 0;
- },
-
- daysInMonth : function () {
- return daysInMonth(this.year(), this.month());
- },
-
- dayOfYear : function (input) {
- var dayOfYear = round((moment(this).startOf('day') - moment(this).startOf('year')) / 864e5) + 1;
- return input == null ? dayOfYear : this.add("d", (input - dayOfYear));
- },
-
- quarter : function (input) {
- return input == null ? Math.ceil((this.month() + 1) / 3) : this.month((input - 1) * 3 + this.month() % 3);
- },
-
- weekYear : function (input) {
- var year = weekOfYear(this, this.lang()._week.dow, this.lang()._week.doy).year;
- return input == null ? year : this.add("y", (input - year));
- },
-
- isoWeekYear : function (input) {
- var year = weekOfYear(this, 1, 4).year;
- return input == null ? year : this.add("y", (input - year));
- },
-
- week : function (input) {
- var week = this.lang().week(this);
- return input == null ? week : this.add("d", (input - week) * 7);
- },
-
- isoWeek : function (input) {
- var week = weekOfYear(this, 1, 4).week;
- return input == null ? week : this.add("d", (input - week) * 7);
- },
-
- weekday : function (input) {
- var weekday = (this.day() + 7 - this.lang()._week.dow) % 7;
- return input == null ? weekday : this.add("d", input - weekday);
- },
-
- isoWeekday : function (input) {
- // behaves the same as moment#day except
- // as a getter, returns 7 instead of 0 (1-7 range instead of 0-6)
- // as a setter, sunday should belong to the previous week.
- return input == null ? this.day() || 7 : this.day(this.day() % 7 ? input : input - 7);
- },
-
- isoWeeksInYear : function () {
- return weeksInYear(this.year(), 1, 4);
- },
-
- weeksInYear : function () {
- var weekInfo = this._lang._week;
- return weeksInYear(this.year(), weekInfo.dow, weekInfo.doy);
- },
-
- get : function (units) {
- units = normalizeUnits(units);
- return this[units]();
- },
+ // Day of Week
+ prototype__proto.weekdays = localeWeekdays;
+ prototype__proto._weekdays = defaultLocaleWeekdays;
+ prototype__proto.weekdaysMin = localeWeekdaysMin;
+ prototype__proto._weekdaysMin = defaultLocaleWeekdaysMin;
+ prototype__proto.weekdaysShort = localeWeekdaysShort;
+ prototype__proto._weekdaysShort = defaultLocaleWeekdaysShort;
+ prototype__proto.weekdaysParse = localeWeekdaysParse;
- set : function (units, value) {
- units = normalizeUnits(units);
- if (typeof this[units] === 'function') {
- this[units](value);
- }
- return this;
- },
+ // Hours
+ prototype__proto.isPM = localeIsPM;
+ prototype__proto._meridiemParse = defaultLocaleMeridiemParse;
+ prototype__proto.meridiem = localeMeridiem;
- // If passed a language key, it will set the language for this
- // instance. Otherwise, it will return the language configuration
- // variables for this instance.
- lang : function (key) {
- if (key === undefined) {
- return this._lang;
- } else {
- this._lang = getLangDefinition(key);
- return this;
- }
+ function lists__get (format, index, field, setter) {
+ var locale = locale_locales__getLocale();
+ var utc = create_utc__createUTC().set(setter, index);
+ return locale[field](utc, format);
+ }
+
+ function list (format, index, field, count, setter) {
+ if (typeof format === 'number') {
+ index = format;
+ format = undefined;
}
- });
- function rawMonthSetter(mom, value) {
- var dayOfMonth;
+ format = format || '';
- // TODO: Move this out of here!
- if (typeof value === 'string') {
- value = mom.lang().monthsParse(value);
- // TODO: Another silent failure?
- if (typeof value !== 'number') {
- return mom;
- }
+ if (index != null) {
+ return lists__get(format, index, field, setter);
}
- dayOfMonth = Math.min(mom.date(),
- daysInMonth(mom.year(), value));
- mom._d['set' + (mom._isUTC ? 'UTC' : '') + 'Month'](value, dayOfMonth);
- return mom;
+ var i;
+ var out = [];
+ for (i = 0; i < count; i++) {
+ out[i] = lists__get(format, i, field, setter);
+ }
+ return out;
}
- function rawGetter(mom, unit) {
- return mom._d['get' + (mom._isUTC ? 'UTC' : '') + unit]();
+ function lists__listMonths (format, index) {
+ return list(format, index, 'months', 12, 'month');
}
- function rawSetter(mom, unit, value) {
- if (unit === 'Month') {
- return rawMonthSetter(mom, value);
- } else {
- return mom._d['set' + (mom._isUTC ? 'UTC' : '') + unit](value);
- }
+ function lists__listMonthsShort (format, index) {
+ return list(format, index, 'monthsShort', 12, 'month');
}
- function makeAccessor(unit, keepTime) {
- return function (value) {
- if (value != null) {
- rawSetter(this, unit, value);
- moment.updateOffset(this, keepTime);
- return this;
- } else {
- return rawGetter(this, unit);
- }
- };
+ function lists__listWeekdays (format, index) {
+ return list(format, index, 'weekdays', 7, 'day');
}
- moment.fn.millisecond = moment.fn.milliseconds = makeAccessor('Milliseconds', false);
- moment.fn.second = moment.fn.seconds = makeAccessor('Seconds', false);
- moment.fn.minute = moment.fn.minutes = makeAccessor('Minutes', false);
- // Setting the hour should keep the time, because the user explicitly
- // specified which hour he wants. So trying to maintain the same hour (in
- // a new timezone) makes sense. Adding/subtracting hours does not follow
- // this rule.
- moment.fn.hour = moment.fn.hours = makeAccessor('Hours', true);
- // moment.fn.month is defined separately
- moment.fn.date = makeAccessor('Date', true);
- moment.fn.dates = deprecate("dates accessor is deprecated. Use date instead.", makeAccessor('Date', true));
- moment.fn.year = makeAccessor('FullYear', true);
- moment.fn.years = deprecate("years accessor is deprecated. Use year instead.", makeAccessor('FullYear', true));
+ function lists__listWeekdaysShort (format, index) {
+ return list(format, index, 'weekdaysShort', 7, 'day');
+ }
+
+ function lists__listWeekdaysMin (format, index) {
+ return list(format, index, 'weekdaysMin', 7, 'day');
+ }
- // add plural methods
- moment.fn.days = moment.fn.day;
- moment.fn.months = moment.fn.month;
- moment.fn.weeks = moment.fn.week;
- moment.fn.isoWeeks = moment.fn.isoWeek;
- moment.fn.quarters = moment.fn.quarter;
+ locale_locales__getSetGlobalLocale('en', {
+ ordinalParse: /\d{1,2}(th|st|nd|rd)/,
+ ordinal : function (number) {
+ var b = number % 10,
+ output = (toInt(number % 100 / 10) === 1) ? 'th' :
+ (b === 1) ? 'st' :
+ (b === 2) ? 'nd' :
+ (b === 3) ? 'rd' : 'th';
+ return number + output;
+ }
+ });
- // add aliased format methods
- moment.fn.toJSON = moment.fn.toISOString;
+ // Side effect imports
+ utils_hooks__hooks.lang = deprecate('moment.lang is deprecated. Use moment.locale instead.', locale_locales__getSetGlobalLocale);
+ utils_hooks__hooks.langData = deprecate('moment.langData is deprecated. Use moment.localeData instead.', locale_locales__getLocale);
- /************************************
- Duration Prototype
- ************************************/
+ var mathAbs = Math.abs;
+ function duration_abs__abs () {
+ var data = this._data;
- extend(moment.duration.fn = Duration.prototype, {
+ this._milliseconds = mathAbs(this._milliseconds);
+ this._days = mathAbs(this._days);
+ this._months = mathAbs(this._months);
- _bubble : function () {
- var milliseconds = this._milliseconds,
- days = this._days,
- months = this._months,
- data = this._data,
- seconds, minutes, hours, years;
+ data.milliseconds = mathAbs(data.milliseconds);
+ data.seconds = mathAbs(data.seconds);
+ data.minutes = mathAbs(data.minutes);
+ data.hours = mathAbs(data.hours);
+ data.months = mathAbs(data.months);
+ data.years = mathAbs(data.years);
- // The following code bubbles up values, see the tests for
- // examples of what that means.
- data.milliseconds = milliseconds % 1000;
+ return this;
+ }
- seconds = absRound(milliseconds / 1000);
- data.seconds = seconds % 60;
+ function duration_add_subtract__addSubtract (duration, input, value, direction) {
+ var other = create__createDuration(input, value);
- minutes = absRound(seconds / 60);
- data.minutes = minutes % 60;
+ duration._milliseconds += direction * other._milliseconds;
+ duration._days += direction * other._days;
+ duration._months += direction * other._months;
- hours = absRound(minutes / 60);
- data.hours = hours % 24;
+ return duration._bubble();
+ }
- days += absRound(hours / 24);
- data.days = days % 30;
+ // supports only 2.0-style add(1, 's') or add(duration)
+ function duration_add_subtract__add (input, value) {
+ return duration_add_subtract__addSubtract(this, input, value, 1);
+ }
- months += absRound(days / 30);
- data.months = months % 12;
+ // supports only 2.0-style subtract(1, 's') or subtract(duration)
+ function duration_add_subtract__subtract (input, value) {
+ return duration_add_subtract__addSubtract(this, input, value, -1);
+ }
- years = absRound(months / 12);
- data.years = years;
- },
+ function bubble () {
+ var milliseconds = this._milliseconds;
+ var days = this._days;
+ var months = this._months;
+ var data = this._data;
+ var seconds, minutes, hours, years = 0;
- weeks : function () {
- return absRound(this.days() / 7);
- },
+ // The following code bubbles up values, see the tests for
+ // examples of what that means.
+ data.milliseconds = milliseconds % 1000;
- valueOf : function () {
- return this._milliseconds +
- this._days * 864e5 +
- (this._months % 12) * 2592e6 +
- toInt(this._months / 12) * 31536e6;
- },
+ seconds = absFloor(milliseconds / 1000);
+ data.seconds = seconds % 60;
- humanize : function (withSuffix) {
- var difference = +this,
- output = relativeTime(difference, !withSuffix, this.lang());
+ minutes = absFloor(seconds / 60);
+ data.minutes = minutes % 60;
- if (withSuffix) {
- output = this.lang().pastFuture(difference, output);
- }
+ hours = absFloor(minutes / 60);
+ data.hours = hours % 24;
- return this.lang().postformat(output);
- },
+ days += absFloor(hours / 24);
- add : function (input, val) {
- // supports only 2.0-style add(1, 's') or add(moment)
- var dur = moment.duration(input, val);
+ // Accurately convert days to years, assume start from year 0.
+ years = absFloor(daysToYears(days));
+ days -= absFloor(yearsToDays(years));
- this._milliseconds += dur._milliseconds;
- this._days += dur._days;
- this._months += dur._months;
+ // 30 days to a month
+ // TODO (iskren): Use anchor date (like 1st Jan) to compute this.
+ months += absFloor(days / 30);
+ days %= 30;
- this._bubble();
+ // 12 months -> 1 year
+ years += absFloor(months / 12);
+ months %= 12;
- return this;
- },
+ data.days = days;
+ data.months = months;
+ data.years = years;
- subtract : function (input, val) {
- var dur = moment.duration(input, val);
+ return this;
+ }
- this._milliseconds -= dur._milliseconds;
- this._days -= dur._days;
- this._months -= dur._months;
+ function daysToYears (days) {
+ // 400 years have 146097 days (taking into account leap year rules)
+ return days * 400 / 146097;
+ }
- this._bubble();
+ function yearsToDays (years) {
+ // years * 365 + absFloor(years / 4) -
+ // absFloor(years / 100) + absFloor(years / 400);
+ return years * 146097 / 400;
+ }
- return this;
- },
+ function as (units) {
+ var days;
+ var months;
+ var milliseconds = this._milliseconds;
- get : function (units) {
- units = normalizeUnits(units);
- return this[units.toLowerCase() + 's']();
- },
+ units = normalizeUnits(units);
- as : function (units) {
- units = normalizeUnits(units);
- return this['as' + units.charAt(0).toUpperCase() + units.slice(1) + 's']();
- },
-
- lang : moment.fn.lang,
-
- toIsoString : function () {
- // inspired by https://github.com/dordille/moment-isoduration/blob/master/moment.isoduration.js
- var years = Math.abs(this.years()),
- months = Math.abs(this.months()),
- days = Math.abs(this.days()),
- hours = Math.abs(this.hours()),
- minutes = Math.abs(this.minutes()),
- seconds = Math.abs(this.seconds() + this.milliseconds() / 1000);
-
- if (!this.asSeconds()) {
- // this is the same as C#'s (Noda) and python (isodate)...
- // but not other JS (goog.date)
- return 'P0D';
+ if (units === 'month' || units === 'year') {
+ days = this._days + milliseconds / 864e5;
+ months = this._months + daysToYears(days) * 12;
+ return units === 'month' ? months : months / 12;
+ } else {
+ // handle milliseconds separately because of floating point math errors (issue #1867)
+ days = this._days + Math.round(yearsToDays(this._months / 12));
+ switch (units) {
+ case 'week' : return days / 7 + milliseconds / 6048e5;
+ case 'day' : return days + milliseconds / 864e5;
+ case 'hour' : return days * 24 + milliseconds / 36e5;
+ case 'minute' : return days * 1440 + milliseconds / 6e4;
+ case 'second' : return days * 86400 + milliseconds / 1000;
+ // Math.floor prevents floating point math errors here
+ case 'millisecond': return Math.floor(days * 864e5) + milliseconds;
+ default: throw new Error('Unknown unit ' + units);
}
-
- return (this.asSeconds() < 0 ? '-' : '') +
- 'P' +
- (years ? years + 'Y' : '') +
- (months ? months + 'M' : '') +
- (days ? days + 'D' : '') +
- ((hours || minutes || seconds) ? 'T' : '') +
- (hours ? hours + 'H' : '') +
- (minutes ? minutes + 'M' : '') +
- (seconds ? seconds + 'S' : '');
}
- });
+ }
- function makeDurationGetter(name) {
- moment.duration.fn[name] = function () {
- return this._data[name];
- };
+ // TODO: Use this.as('ms')?
+ function duration_as__valueOf () {
+ return (
+ this._milliseconds +
+ this._days * 864e5 +
+ (this._months % 12) * 2592e6 +
+ toInt(this._months / 12) * 31536e6
+ );
}
- function makeDurationAsGetter(name, factor) {
- moment.duration.fn['as' + name] = function () {
- return +this / factor;
+ function makeAs (alias) {
+ return function () {
+ return this.as(alias);
};
}
- for (i in unitMillisecondFactors) {
- if (unitMillisecondFactors.hasOwnProperty(i)) {
- makeDurationAsGetter(i, unitMillisecondFactors[i]);
- makeDurationGetter(i.toLowerCase());
- }
+ var asMilliseconds = makeAs('ms');
+ var asSeconds = makeAs('s');
+ var asMinutes = makeAs('m');
+ var asHours = makeAs('h');
+ var asDays = makeAs('d');
+ var asWeeks = makeAs('w');
+ var asMonths = makeAs('M');
+ var asYears = makeAs('y');
+
+ function duration_get__get (units) {
+ units = normalizeUnits(units);
+ return this[units + 's']();
}
- makeDurationAsGetter('Weeks', 6048e5);
- moment.duration.fn.asMonths = function () {
- return (+this - this.years() * 31536e6) / 2592e6 + this.years() * 12;
- };
+ function makeGetter(name) {
+ return function () {
+ return this._data[name];
+ };
+ }
+ var duration_get__milliseconds = makeGetter('milliseconds');
+ var seconds = makeGetter('seconds');
+ var minutes = makeGetter('minutes');
+ var hours = makeGetter('hours');
+ var days = makeGetter('days');
+ var months = makeGetter('months');
+ var years = makeGetter('years');
- /************************************
- Default Lang
- ************************************/
+ function weeks () {
+ return absFloor(this.days() / 7);
+ }
+ var round = Math.round;
+ var thresholds = {
+ s: 45, // seconds to minute
+ m: 45, // minutes to hour
+ h: 22, // hours to day
+ d: 26, // days to month
+ M: 11 // months to year
+ };
- // Set default language, other languages will inherit from English.
- moment.lang('en', {
- ordinal : function (number) {
- var b = number % 10,
- output = (toInt(number % 100 / 10) === 1) ? 'th' :
- (b === 1) ? 'st' :
- (b === 2) ? 'nd' :
- (b === 3) ? 'rd' : 'th';
- return number + output;
+ // helper function for moment.fn.from, moment.fn.fromNow, and moment.duration.fn.humanize
+ function substituteTimeAgo(string, number, withoutSuffix, isFuture, locale) {
+ return locale.relativeTime(number || 1, !!withoutSuffix, string, isFuture);
+ }
+
+ function duration_humanize__relativeTime (posNegDuration, withoutSuffix, locale) {
+ var duration = create__createDuration(posNegDuration).abs();
+ var seconds = round(duration.as('s'));
+ var minutes = round(duration.as('m'));
+ var hours = round(duration.as('h'));
+ var days = round(duration.as('d'));
+ var months = round(duration.as('M'));
+ var years = round(duration.as('y'));
+
+ var a = seconds < thresholds.s && ['s', seconds] ||
+ minutes === 1 && ['m'] ||
+ minutes < thresholds.m && ['mm', minutes] ||
+ hours === 1 && ['h'] ||
+ hours < thresholds.h && ['hh', hours] ||
+ days === 1 && ['d'] ||
+ days < thresholds.d && ['dd', days] ||
+ months === 1 && ['M'] ||
+ months < thresholds.M && ['MM', months] ||
+ years === 1 && ['y'] || ['yy', years];
+
+ a[2] = withoutSuffix;
+ a[3] = +posNegDuration > 0;
+ a[4] = locale;
+ return substituteTimeAgo.apply(null, a);
+ }
+
+ // This function allows you to set a threshold for relative time strings
+ function duration_humanize__getSetRelativeTimeThreshold (threshold, limit) {
+ if (thresholds[threshold] === undefined) {
+ return false;
}
+ if (limit === undefined) {
+ return thresholds[threshold];
+ }
+ thresholds[threshold] = limit;
+ return true;
+ }
+
+ function humanize (withSuffix) {
+ var locale = this.localeData();
+ var output = duration_humanize__relativeTime(this, !withSuffix, locale);
+
+ if (withSuffix) {
+ output = locale.pastFuture(+this, output);
+ }
+
+ return locale.postformat(output);
+ }
+
+ var iso_string__abs = Math.abs;
+
+ function iso_string__toISOString() {
+ // inspired by https://github.com/dordille/moment-isoduration/blob/master/moment.isoduration.js
+ var Y = iso_string__abs(this.years());
+ var M = iso_string__abs(this.months());
+ var D = iso_string__abs(this.days());
+ var h = iso_string__abs(this.hours());
+ var m = iso_string__abs(this.minutes());
+ var s = iso_string__abs(this.seconds() + this.milliseconds() / 1000);
+ var total = this.asSeconds();
+
+ if (!total) {
+ // this is the same as C#'s (Noda) and python (isodate)...
+ // but not other JS (goog.date)
+ return 'P0D';
+ }
+
+ return (total < 0 ? '-' : '') +
+ 'P' +
+ (Y ? Y + 'Y' : '') +
+ (M ? M + 'M' : '') +
+ (D ? D + 'D' : '') +
+ ((h || m || s) ? 'T' : '') +
+ (h ? h + 'H' : '') +
+ (m ? m + 'M' : '') +
+ (s ? s + 'S' : '');
+ }
+
+ var duration_prototype__proto = Duration.prototype;
+
+ duration_prototype__proto.abs = duration_abs__abs;
+ duration_prototype__proto.add = duration_add_subtract__add;
+ duration_prototype__proto.subtract = duration_add_subtract__subtract;
+ duration_prototype__proto.as = as;
+ duration_prototype__proto.asMilliseconds = asMilliseconds;
+ duration_prototype__proto.asSeconds = asSeconds;
+ duration_prototype__proto.asMinutes = asMinutes;
+ duration_prototype__proto.asHours = asHours;
+ duration_prototype__proto.asDays = asDays;
+ duration_prototype__proto.asWeeks = asWeeks;
+ duration_prototype__proto.asMonths = asMonths;
+ duration_prototype__proto.asYears = asYears;
+ duration_prototype__proto.valueOf = duration_as__valueOf;
+ duration_prototype__proto._bubble = bubble;
+ duration_prototype__proto.get = duration_get__get;
+ duration_prototype__proto.milliseconds = duration_get__milliseconds;
+ duration_prototype__proto.seconds = seconds;
+ duration_prototype__proto.minutes = minutes;
+ duration_prototype__proto.hours = hours;
+ duration_prototype__proto.days = days;
+ duration_prototype__proto.weeks = weeks;
+ duration_prototype__proto.months = months;
+ duration_prototype__proto.years = years;
+ duration_prototype__proto.humanize = humanize;
+ duration_prototype__proto.toISOString = iso_string__toISOString;
+ duration_prototype__proto.toString = iso_string__toISOString;
+ duration_prototype__proto.toJSON = iso_string__toISOString;
+ duration_prototype__proto.locale = locale;
+ duration_prototype__proto.localeData = localeData;
+
+ // Deprecations
+ duration_prototype__proto.toIsoString = deprecate('toIsoString() is deprecated. Please use toISOString() instead (notice the capitals)', iso_string__toISOString);
+ duration_prototype__proto.lang = lang;
+
+ // Side effect imports
+
+ addFormatToken('X', 0, 0, 'unix');
+ addFormatToken('x', 0, 0, 'valueOf');
+
+ // PARSING
+
+ addRegexToken('x', matchSigned);
+ addRegexToken('X', matchTimestamp);
+ addParseToken('X', function (input, array, config) {
+ config._d = new Date(parseFloat(input, 10) * 1000);
+ });
+ addParseToken('x', function (input, array, config) {
+ config._d = new Date(toInt(input));
});
- /* EMBED_LANGUAGES */
+ // Side effect imports
- /************************************
- Exposing Moment
- ************************************/
- function makeGlobal(shouldDeprecate) {
- /*global ender:false */
- if (typeof ender !== 'undefined') {
- return;
- }
- oldGlobalMoment = globalScope.moment;
- if (shouldDeprecate) {
- globalScope.moment = deprecate(
- "Accessing Moment through the global scope is " +
- "deprecated, and will be removed in an upcoming " +
- "release.",
- moment);
- } else {
- globalScope.moment = moment;
- }
- }
+ utils_hooks__hooks.version = '2.10.3';
- // CommonJS module is defined
- if (hasModule) {
- module.exports = moment;
- } else if (typeof define === "function" && define.amd) {
- define("moment", function (require, exports, module) {
- if (module.config && module.config() && module.config().noGlobal === true) {
- // release the global variable
- globalScope.moment = oldGlobalMoment;
- }
+ setHookCallback(local__createLocal);
- return moment;
- });
- makeGlobal(true);
- } else {
- makeGlobal();
- }
-}).call(this);
+ utils_hooks__hooks.fn = momentPrototype;
+ utils_hooks__hooks.min = min;
+ utils_hooks__hooks.max = max;
+ utils_hooks__hooks.utc = create_utc__createUTC;
+ utils_hooks__hooks.unix = moment__createUnix;
+ utils_hooks__hooks.months = lists__listMonths;
+ utils_hooks__hooks.isDate = isDate;
+ utils_hooks__hooks.locale = locale_locales__getSetGlobalLocale;
+ utils_hooks__hooks.invalid = valid__createInvalid;
+ utils_hooks__hooks.duration = create__createDuration;
+ utils_hooks__hooks.isMoment = isMoment;
+ utils_hooks__hooks.weekdays = lists__listWeekdays;
+ utils_hooks__hooks.parseZone = moment__createInZone;
+ utils_hooks__hooks.localeData = locale_locales__getLocale;
+ utils_hooks__hooks.isDuration = isDuration;
+ utils_hooks__hooks.monthsShort = lists__listMonthsShort;
+ utils_hooks__hooks.weekdaysMin = lists__listWeekdaysMin;
+ utils_hooks__hooks.defineLocale = defineLocale;
+ utils_hooks__hooks.weekdaysShort = lists__listWeekdaysShort;
+ utils_hooks__hooks.normalizeUnits = normalizeUnits;
+ utils_hooks__hooks.relativeTimeThreshold = duration_humanize__getSetRelativeTimeThreshold;
+
+ var _moment = utils_hooks__hooks;
+
+ return _moment;
+
+}));
\ No newline at end of file
diff --git a/src/UI/Shims/moment.js b/src/UI/Shims/moment.js
deleted file mode 100644
index 359f88a73..000000000
--- a/src/UI/Shims/moment.js
+++ /dev/null
@@ -1,4 +0,0 @@
-require('backbone');
-var backgrid = require('../JsLibraries/moment');
-
-module.exports = backgrid;
\ No newline at end of file