Ext.namespace('Zarafa.core'); /** * @class Zarafa.core.DateRange * @extends Ext.util.Observable * * Represents a date range defined by a start and due date. The start date is inclusive, while the due date is exclusive. For example, * to denote an appointment that lasts all day on July 1st, 2010 one would write this as (00:00 July 1st 2010, 00:00 July 2nd 2010). * In sort, the range is defined as [startDate, dueDate>. * <p> * This class encapsulates such ranges because they are used often in especially the calendering components. */ Zarafa.core.DateRange = Ext.extend(Ext.util.Observable, { /** * @cfg {Date} startDate start date for this {@link Zarafa.core.DateRange DateRange}. * use {@link #getStartDate} and {@link #setStartDate} to modify this value. */ startDate : null, /** * @cfg {Date} dueDate due date for this {@link Zarafa.core.DateRange DateRange}. * use {@link #getDueDate} and {@link #setDueDate} to modify this value. */ dueDate : null, /** * @cfg {Boolean} allowBlank Specifies empty dates are accepted by this {@link Zarafa.core.DateRange DateRange}, * if {@link #allowBlank} is true then only we can use empty start/due dates in {@link Zarafa.core.DateRange DateRange}. * otherwise {@link #startDate} and {@link #dueDate} will be initialized with current dates. */ allowBlank : false, /** * @constructor * @param {Date} startDate (optional) start date * @param {Date} dueDate (optional) due date */ constructor : function(config) { config = config || {}; Ext.applyIf(config, { allowBlank : this.allowBlank }); if(config.allowBlank) { // if allowBlank is true then we should initialize start / due date with undefined // but this should only be done when start / due dates are not provided in the configs hence used Ext.applyIf // there is a restriction that start date can not exist without due date, so we will be initializing due date // same as start date if only start date is provided Ext.applyIf(config, { startDate : null, dueDate : config.startDate ? config.startDate.clone() : null }); } else { // if allowBlank is false then we should initialize start / due date with current dates Ext.applyIf(config, { startDate : new Date(), dueDate : config.startDate ? config.startDate.clone() : new Date() }); } Ext.apply(this, config); // Precondition if (Ext.isDate(this.startDate) && Ext.isDate(this.dueDate) && (this.getStartTime() > this.getDueTime())) { throw 'Invalid date range, start date is after due date'; } this.addEvents( /** * @event update * Fires when the daterange is modified. * @param {Zarafa.core.DateRange} newRange The changed daterange object * @param {Zarafa.core.DateRange} oldRange The orignal daterange values (clone of the daterange object, * prior of the change). */ 'update' ); Zarafa.core.DateRange.superclass.constructor.call(this); }, /** * @return {Date} the range's start date. */ getStartDate : function() { return this.startDate; }, /** * @return {Date} the range's due date. */ getDueDate : function() { return this.dueDate; }, /** * Sets the range start date. * @param {Date} startDate the range's start date. * @param {Boolean} silence When set to true it will not throw an start date after due date error. * @param {Boolean} ignoreUpdate When set to true it will not fire the update event. * @return {Zarafa.core.DateRange} this date range */ setStartDate : function(startDate, silence, ignoreUpdate) { var original = null; // make sure that you are saving null when no valid date is passed startDate = Ext.isDate(startDate) ? startDate : null; // Preconditions if (!this.allowBlank && !Ext.isDate(startDate)) { throw 'Cannot set DateRange start to undefined'; } else if (Ext.isDate(this.dueDate) && Ext.isDate(startDate) && startDate.getTime() > this.getDueTime() && !silence) { throw 'Cannot set DateRange start date to after its due date'; } // No update event when new value equals old value ignoreUpdate = ignoreUpdate || (Ext.isDate(this.startDate) && Ext.isDate(startDate) && this.getStartTime() == startDate.getTime()); if (!ignoreUpdate) { original = this.clone(); } this.startDate = startDate; if(!ignoreUpdate) { this.fireEvent("update", this, original); } return this; }, /** * Sets the range due date. * @param {Date} dueDate the range's due date. * @param {Boolean} silence When set to true it will not throw an start date after due date error. * @param {Boolean} ignoreUpdate When set to true it will not fire the update event. * @return {Zarafa.core.DateRange} this date range */ setDueDate : function(dueDate, silence, ignoreUpdate) { var original = null; // make sure that you are saving null when no valid date is passed dueDate = Ext.isDate(dueDate) ? dueDate : null; // Precondition if (!this.allowBlank && !Ext.isDate(dueDate)) { throw 'Cannot set DateRange due date to undefined'; } else if (Ext.isDate(this.startDate) && Ext.isDate(dueDate) && dueDate.getTime() < this.getStartTime() && !silence) { throw 'Cannot set DateRange due date to before its start date'; } // No update event when new value equals old value ignoreUpdate = ignoreUpdate || (Ext.isDate(this.dueDate) && Ext.isDate(dueDate) && this.getDueTime() == dueDate.getTime()); if (!ignoreUpdate) { original = this.clone(); } this.dueDate = dueDate; if(!ignoreUpdate) { this.fireEvent("update", this, original); } return this; }, /** * Sets the start and due dates. * @param {Date} startDate the range's start date. * @param {Date} dueDate the range's due date. * @param {Boolean} silence When set to true it will not throw an start date after due date error. * @param {Boolean} ignoreUpdate When set to true it will not fire the update event. * @return {Zarafa.core.DateRange} this date range */ set : function(startDate, dueDate, silence, ignoreUpdate) { var original = null; // make sure that you are saving null when no valid date is passed startDate = Ext.isDate(startDate) ? startDate : null; dueDate = Ext.isDate(dueDate) ? dueDate : null; // Precondition if (!this.allowBlank && !Ext.isDate(startDate)) { throw 'Cannot set DateRange start to undefined'; } if (!this.allowBlank && !Ext.isDate(dueDate)) { throw 'Cannot set DateRange due date to undefined'; } if (Ext.isDate(startDate) && Ext.isDate(dueDate) && startDate.getTime() > dueDate.getTime() && !silence) { throw 'Invalid date range, start date is after due date'; } ignoreUpdate = ignoreUpdate || (Ext.isDate(startDate) && Ext.isDate(dueDate) && (startDate.getTime() == this.getStartTime()) && (dueDate.getTime() == this.getDueTime())); if (!ignoreUpdate) { original = this.clone(); } // don't fire update event from these functions this.setStartDate(startDate, true, true); this.setDueDate(dueDate, true, true); if (!ignoreUpdate) { this.fireEvent("update", this, original); } return this; }, /** * @return {Number} the range's start time in milliseconds since epoch, GMT. */ getStartTime : function() { return (Ext.isDate(this.startDate) && this.startDate.getTime()) || this.startDate; }, /** * @return {Number} the range's due time in milliseconds since epoch, GMT. */ getDueTime : function() { return (Ext.isDate(this.dueDate) && this.dueDate.getTime()) || this.dueDate; }, /** * Sets the range start time. * @param {Number} startTime the range's start time. * @param {Boolean} silence When set to true it will not throw an start date after due date error. * @param {Boolean} ignoreUpdate When set to true it will not fire the update event. * @return {Zarafa.core.DateRange} this date range */ setStartTime : function(startTime, silence, ignoreUpdate) { var original = null; // Preconditions if (!this.allowBlank && !startTime) { throw 'Cannot set DateRange start to undefined'; } else if (Ext.isDate(this.dueDate) && startTime > this.dueDate.getTime() && !silence) { throw 'Cannot set DateRange start date to after its due date'; } // No update event when new value equals old value ignoreUpdate = ignoreUpdate || (this.getStartTime() == startTime); if (!ignoreUpdate) { original = this.clone(); } if (!Ext.isEmpty(startTime)) { this.startDate = new Date(startTime); } else { this.startDate = null; } if(!ignoreUpdate) { this.fireEvent("update", this, original); } return this; }, /** * Sets the range due time. * @param {Number} dueTime the range's due time. * @param {Boolean} silence When set to true it will not throw an start date after due date error. * @param {Boolean} ignoreUpdate When set to true it will not fire the update event. * @return {Zarafa.core.DateRange} this date range */ setDueTime : function(dueTime, silence, ignoreUpdate) { var original = null; // Precondition if (!this.allowBlank && !dueTime) { throw 'Cannot set DateRange due date to undefined'; } else if (Ext.isDate(this.startDate) && dueTime < this.startDate.getTime() && !silence) { throw 'Cannot set DateRange due date to before its start date'; } // No update event when new value equals old value ignoreUpdate = ignoreUpdate || (this.getDueTime() == dueTime); if (!ignoreUpdate) { original = this.clone(); } if (!Ext.isEmpty(dueTime)) { this.dueDate = new Date(dueTime); } else { this.dueDate = null; } if(!ignoreUpdate) { this.fireEvent("update", this, original); } return this; }, /** * Sets the start and due times. * @param {Number} startDate the range's start time. * @param {Number} dueDate the range's due time. * @param {Boolean} silence When set to true it will not throw an start date after due date error. * @param {Boolean} ignoreUpdate When set to true it will not fire the update event. * @return {Zarafa.core.DateRange} this date range */ setTime : function(startTime, dueTime, silence, ignoreUpdate) { var original = null; // Precondition if(!startTime || !dueTime) { if (!this.allowBlank && !startTime) { throw 'Cannot set DateRange start to undefined'; } if (!this.allowBlank && !dueTime) { throw 'Cannot set DateRange due date to undefined'; } } else if (startTime > dueTime && !silence) { throw 'Invalid date range, start date is after due date'; } ignoreUpdate = ignoreUpdate || ((startTime == this.getStartTime()) && (dueTime == this.getDueTime())); if (!ignoreUpdate) { original = this.clone(); } this.setStartTime(startTime, true, true); this.setDueTime(dueTime, true, true); if (!ignoreUpdate) { this.fireEvent("update", this, original); } return this; }, /** * @param {String} interval (optional) A valid date interval enum value. * @return {Number} the range's duration in milliseconds */ getDuration : function(interval) { if (Ext.isDate(this.dueDate) && Ext.isDate(this.startDate)) { return Date.diff(interval || Date.MILLI, this.dueDate, this.startDate); } return 0; }, /** * @param {Number} duration the new duration of the range in milliseconds. * @param {Boolean} ignoreUpdate When set to true it will not fire the update event. * @return {Zarafa.core.DateRange} this date range */ setDuration : function(duration, ignoreUpdate) { var original = null; if(!Ext.isDate(this.startDate)) { // if no start date is provided then we can't set duration throw 'Cannot set duration when start date is not specified'; } // No update event when new value equals old value ignoreUpdate = ignoreUpdate || (this.getDuration() == duration); if (!ignoreUpdate) { original = this.clone(); } this.dueDate = new Date(this.getStartTime() + duration); if (!ignoreUpdate) { this.fireEvent("update", this, original); } return this; }, /** * Calculates the number of days spanned by this appointment, rounded to whole days. The function * assumes that the range is an all day range. Even so, the rounding is still required to deal with * date ranges that start and end in different time zones. * * @return {Number} the number of days spanned by this appointment, rounded to whole days. */ getNumDays : function() { var duration = this.getDuration(Date.DAY); if (Ext.isDefined(duration)) { return Math.round(duration); } else { return 0; } }, /** * Expands the range so that both the start and due times are multiples of the 'timeSlice' parameter. The start * date is moved back, the due date is moved forward. Since this method does not care about time zones the value * of 'timeSlice' is assumed to be <= 60 minutes. * * @param {Number} timeSlice the time slice to 'snap' to. * @return {Zarafa.core.DateRange} this date range */ expand : function(timeSlice) { var original = this.clone(); if(Ext.isDate(this.startDate)) { this.startDate = new Date(this.getStartTime() - this.getStartTime() % timeSlice); } if(Ext.isDate(this.dueDate)) { this.dueDate = new Date(this.getDueTime() + timeSlice - this.getDueTime() % timeSlice); } // only fire event if anything has changed if(Ext.isDate(this.startDate) || Ext.isDate(this.dueDate)) { this.fireEvent("update", this, original); } return this; }, /** * Deep-clones the date range. * @return {Zarafa.core.DateRange} a clone of this date range. */ clone : function() { return new Zarafa.core.DateRange({ startDate : Ext.isDate(this.startDate) ? new Date(this.getStartTime()) : undefined, dueDate : Ext.isDate(this.dueDate) ? new Date(this.getDueTime()) : undefined, allowBlank : this.allowBlank }); }, /** * Test this date range for equality against another date range. * @param {Zarafa.core.DateRange} otherRange a date range to compare with. * @return {Boolean} true if this range equals the given other range. */ equals : function(otherRange) { if (!otherRange) { return false; } if(this.getStartTime() !== otherRange.getStartTime()) { // start dates don't match return false; } if(this.getDueTime() !== otherRange.getDueTime()) { // due dates don't match return false; } // both start/due dates matches return true; }, /** * Compares two date ranges for order. If range A comes before B, the function returns -1. If B comes before * A, the function returns 1. If both are equal, this function returns 0. This functionality is equivalent * to Java's Comparable interface. * * Comparison is based on the range start. If the start dates are equal, further distinction is made by * due date, with earlier due dates coming first. * * Undefined dates will be considered as zero and compared. * * @param {Zarafa.core.DateRange} otherRange Date range to compare with. * @return {Number} If this range 'comes before' otherRange, the funtion returns -1. If this range 'comes * after' otherRange, return 1. Otherwise return 0. * */ compare : function(otherRange) { // Compare start times. var aStartTime = this.getStartTime() || 0; var bStartTime = otherRange.getStartTime() || 0; if (aStartTime !== bStartTime) { return aStartTime > bStartTime ? 1 : -1; } // If start times are equal, compare due times. var aDueTime = this.getDueTime() || 0; var bDueTime = otherRange.getDueTime() || 0; if (aDueTime !== bDueTime) { return aDueTime > bDueTime ? 1 : -1; } // If ranges are equal, return 0. return 0; }, /** * Tests whether the date range is a so-called 'all day' range, * meaning that start and due date duration time is more then one day(24 hours). * @return {Boolean} true if the range is an all day range. */ isAllDay : function() { if(Ext.isDate(this.startDate) && Ext.isDate(this.dueDate) && !this.isZeroMinuteRange()) { return (this.startDate.clearTime(true).getTime() === this.getStartTime()) && (this.dueDate.clearTime(true).getTime() === this.getDueTime()); } return false; }, /** * Tests whether the date range is a 0 minute. * @return {Boolean} true if the range duration is 0 minute. */ isZeroMinuteRange : function() { return (this.dueDate.getTime() === this.startDate.getTime()); }, /** * @return {Boolean} true if this date range overlaps with the other date range. */ overlaps : function(other) { var start1 = this.getStartTime(); var due1 = this.getDueTime(); var start2 = other.getStartTime(); var due2 = other.getDueTime(); return (start1 >= start2 && start1 < due2) || (start2 >= start1 && start2 < due1); }, /** * @param {Zarafa.core.DateRange} range date range to check against. * @return {Boolean} true if this date range is inside the given date range. */ inside : function(range) { if(this.getStartTime() && range.getStartTime() && this.getDueTime() && range.getDueTime()) { return this.getStartTime() >= range.getStartTime() && this.getDueTime() <= range.getDueTime(); } return false; }, /** * @param {Date} date the date to test. * @return {Boolean} true if the give date is inside this date range. */ containsDate : function(date) { return this.getStartTime() <= date.getTime() && this.getDueTime() > date.getTime(); }, /** * Formats the current visible date range as human-readable text. The formatter looks at which components the * start and due dates have in common. * For instance, the first and last days of a week range might lie in the same month (i.e. '13 - 19 July 2009'), * or they might not (i.e. '28 September - 2 November 2009'). Finally a range may have the start and end dates * in different years, i.e. '28 December 2009 - 1 January 2010'. * * @return {String} the current date range as text. */ format : function() { var startDate = this.startDate; var dueDate = this.dueDate; if(!Ext.isDate(startDate) || !Ext.isDate(dueDate)) { return ''; } // If the due date is _exactly_ midnight, we must assume the last day of the period // is the previous day. So decrease the duedate with a day (making sure the duedate // does not move before the startDate). if (dueDate.getTime() === dueDate.clearTime(true).getTime()) { // Move to the previous day, use 12:00 as starting hour, // to prevent problems when the DST swithes at 00:00 (e.g. in Brasil). // We don't need to restore to the original time, as the string // which we are going to ignore doesn't contain a time representation. dueDate = dueDate.clone(); dueDate.setHours(12); dueDate = dueDate.add(Date.DAY, -1); if (dueDate.getTime() < startDate.getTime()) { dueDate = startDate; } } // The startDate and duedate are in completely different years. // Format the full date strings for both dates. if (startDate.getYear() != dueDate.getYear()) { // # TRANSLATORS: See http://docs.sencha.com/ext-js/3-4/#!/api/Date for the meaning of these formatting instructions return String.format('{0} - {1}', startDate.format(_('jS F Y')), dueDate.format(_('jS F Y'))); } // The startDate and dueDate are in different months. // Format the date strings with the year in common. if (startDate.getMonth() != dueDate.getMonth()) { // # TRANSLATORS: See http://docs.sencha.com/ext-js/3-4/#!/api/Date for the meaning of these formatting instructions return String.format('{0} - {1} {2}', startDate.format(_('jS F')), dueDate.format(_('jS F')), startDate.format(_('Y'))); } // The startDate and dueDate are on different days. // Format the date strings with the month and year in common. if (startDate.getDate() != dueDate.getDate()) { // # TRANSLATORS: See http://docs.sencha.com/ext-js/3-4/#!/api/Date for the meaning of these formatting instructions return String.format('{0} - {1} {2}', startDate.format(_('jS')), dueDate.format(_('jS')), startDate.format(_('F Y'))); } // The startDate and dueDate are on the same day. // Format the date string with everything in common. // # TRANSLATORS: See http://docs.sencha.com/ext-js/3-4/#!/api/Date for the meaning of these formatting instructions return startDate.format(_('jS F Y')); } });