devextreme
Version:
HTML5 JavaScript Component Suite for Responsive Web Development
782 lines (607 loc) • 22.3 kB
JavaScript
"use strict";
var errors = require("../../core/errors"),
extend = require("../../core/utils/extend").extend,
each = require("../../core/utils/iterator").each,
inArray = require("../../core/utils/array").inArray,
dateUtils = require("../../core/utils/date");
var toMs = dateUtils.dateToMilliseconds;
var leastDaysInWeek = 4;
var intervalMap = {
secondly: "seconds",
minutely: "minutes",
hourly: "hours",
daily: "days",
weekly: "weeks",
monthly: "months",
yearly: "years"
};
var dateSetterMap = {
"bysecond": function bysecond(date, value) {
date.setSeconds(value);
},
"byminute": function byminute(date, value) {
date.setMinutes(value);
},
"byhour": function byhour(date, value) {
date.setHours(value);
},
"bymonth": function bymonth(date, value) {
date.setMonth(value);
},
"bymonthday": function bymonthday(date, value) {
if (value < 0) {
var initialDate = new Date(date);
setDateByNegativeValue(initialDate, 1, -1);
var daysInMonth = initialDate.getDate();
if (daysInMonth >= Math.abs(value)) {
setDateByNegativeValue(date, 1, value);
} else {
setDateByNegativeValue(date, 2, value);
}
} else {
date.setDate(value);
correctDate(date, value);
}
},
"byday": function byday(date, byDay, weekStart, frequency) {
var dayOfWeek = byDay;
if (frequency === "DAILY" && byDay === 0) {
dayOfWeek = 7;
}
byDay += days[weekStart] > dayOfWeek ? 7 : 0;
date.setDate(date.getDate() - date.getDay() + byDay);
},
"byweekno": function byweekno(date, weekNumber, weekStart) {
var initialDate = new Date(date),
firstYearDate = new Date(initialDate.setMonth(0, 1)),
dayShift = firstYearDate.getDay() - days[weekStart],
firstDayOfYear = firstYearDate.getTime() - dayShift * toMs("day"),
newFirstYearDate = dayShift + 1;
if (newFirstYearDate > leastDaysInWeek) {
date.setTime(firstDayOfYear + weekNumber * 7 * toMs("day"));
} else {
date.setTime(firstDayOfYear + (weekNumber - 1) * 7 * toMs("day"));
}
var timezoneDiff = (date.getTimezoneOffset() - firstYearDate.getTimezoneOffset()) * toMs("minute");
timezoneDiff && date.setTime(date.getTime() + timezoneDiff);
},
"byyearday": function byyearday(date, dayOfYear) {
date.setMonth(0, 1);
date.setDate(dayOfYear);
}
};
var setDateByNegativeValue = function setDateByNegativeValue(date, month, value) {
var initialDate = new Date(date);
date.setMonth(date.getMonth() + month);
if (date.getMonth() - initialDate.getMonth() > month) {
date.setDate(value + 1);
}
date.setDate(value + 1);
};
var dateGetterMap = {
"bysecond": function bysecond(date) {
return date.getSeconds();
},
"byminute": function byminute(date) {
return date.getMinutes();
},
"byhour": function byhour(date) {
return date.getHours();
},
"bymonth": function bymonth(date) {
return date.getMonth();
},
"bymonthday": function bymonthday(date) {
return date.getDate();
},
"byday": function byday(date) {
return date.getDay();
},
"byweekno": function byweekno(date, weekStart) {
var daysFromYearStart,
current = new Date(date),
diff = leastDaysInWeek - current.getDay() + days[weekStart] - 1,
dayInMilliseconds = toMs("day");
if (date.getDay() < days[weekStart]) {
diff -= 7;
}
current.setHours(0, 0, 0);
current.setDate(current.getDate() + diff);
daysFromYearStart = 1 + (current - new Date(current.getFullYear(), 0, 1)) / dayInMilliseconds;
return Math.ceil(daysFromYearStart / 7);
},
"byyearday": function byyearday(date) {
var yearStart = new Date(date.getFullYear(), 0, 0),
timezoneDiff = date.getTimezoneOffset() - yearStart.getTimezoneOffset(),
diff = date - yearStart - timezoneDiff * toMs("minute"),
dayLength = toMs("day");
return Math.floor(diff / dayLength);
}
};
var ruleNames = ["freq", "interval", "byday", "byweekno", "byyearday", "bymonth", "bymonthday", "count", "until", "byhour", "byminute", "bysecond", "bysetpos", "wkst"],
freqNames = ["DAILY", "WEEKLY", "MONTHLY", "YEARLY", "SECONDLY", "MINUTELY", "HOURLY"],
days = { SU: 0, MO: 1, TU: 2, WE: 3, TH: 4, FR: 5, SA: 6 };
var getTimeZoneOffset = function getTimeZoneOffset() {
return new Date().getTimezoneOffset();
};
var dateInRecurrenceRange = function dateInRecurrenceRange(options) {
var result = [];
if (options.rule) {
result = getDatesByRecurrence(options);
}
return !!result.length;
};
var normalizeInterval = function normalizeInterval(rule) {
var interval = rule.interval,
freq = rule.freq,
intervalObject = {},
intervalField = intervalMap[freq.toLowerCase()];
if (freq === "MONTHLY" && rule["byday"]) {
intervalField = intervalMap["daily"];
}
intervalObject[intervalField] = interval;
return intervalObject;
};
var getDatesByRecurrenceException = function getDatesByRecurrenceException(ruleValues) {
var result = [];
for (var i = 0, len = ruleValues.length; i < len; i++) {
result[i] = getDateByAsciiString(ruleValues[i]);
}
return result;
};
var dateIsRecurrenceException = function dateIsRecurrenceException(date, recurrenceException) {
var result = false;
if (!recurrenceException) {
return result;
}
var splitDates = recurrenceException.split(","),
exceptDates = getDatesByRecurrenceException(splitDates),
shortFormat = /\d{8}$/;
for (var i = 0, len = exceptDates.length; i < len; i++) {
if (splitDates[i].match(shortFormat)) {
var diffs = getDatePartDiffs(date, exceptDates[i]);
if (diffs.years === 0 && diffs.months === 0 && diffs.days === 0) {
result = true;
}
} else {
if (date.getTime() === exceptDates[i].getTime()) {
result = true;
}
}
}
return result;
};
var doNextIteration = function doNextIteration(date, startIntervalDate, endIntervalDate, recurrenceRule, iterationCount) {
var matchCountIsCorrect = true,
dateInInterval;
endIntervalDate = endIntervalDate.getTime();
if (recurrenceRule.until) {
if (recurrenceRule.until.getTime() < endIntervalDate) {
endIntervalDate = recurrenceRule.until.getTime();
}
}
if (recurrenceRule.count) {
if (iterationCount === recurrenceRule.count) {
matchCountIsCorrect = false;
}
}
dateInInterval = date.getTime() <= endIntervalDate;
return dateInInterval && matchCountIsCorrect;
};
var getDatesByRecurrence = function getDatesByRecurrence(options) {
var result = [],
recurrenceRule = getRecurrenceRule(options.rule),
iterationResult = {},
rule = recurrenceRule.rule,
recurrenceStartDate = options.start,
dateRules;
if (!recurrenceRule.isValid || !rule.freq) {
return result;
}
rule.interval = normalizeInterval(rule);
dateRules = splitDateRules(rule);
var duration = options.end ? options.end.getTime() - options.start.getTime() : toMs("day");
var config = {
exception: options.exception,
min: options.min,
dateRules: dateRules,
rule: rule,
recurrenceStartDate: recurrenceStartDate,
recurrenceEndDate: options.end,
duration: duration
};
if (dateRules.length && rule.count) {
var iteration = 0;
getDatesByCount(dateRules, new Date(recurrenceStartDate), new Date(recurrenceStartDate), rule).forEach(function (currentDate, i) {
if (currentDate < options.max) {
iteration++;
iterationResult = pushToResult(iteration, iterationResult, currentDate, i, config, true);
}
});
} else {
getDatesByRules(dateRules, new Date(recurrenceStartDate), rule).forEach(function (currentDate, i) {
var iteration = 0;
while (doNextIteration(currentDate, recurrenceStartDate, options.max, rule, iteration)) {
iteration++;
iterationResult = pushToResult(iteration, iterationResult, currentDate, i, config);
currentDate = incrementDate(currentDate, recurrenceStartDate, rule, i);
}
});
}
if (rule["bysetpos"]) {
each(iterationResult, function (iterationIndex, iterationDates) {
iterationResult[iterationIndex] = filterDatesBySetPos(iterationDates, rule["bysetpos"]);
});
}
each(iterationResult, function (_, iterationDates) {
result = result.concat(iterationDates);
});
result.sort(function (a, b) {
return a - b;
});
return result;
};
var pushToResult = function pushToResult(iteration, iterationResult, currentDate, i, config, verifiedField) {
if (!iterationResult[iteration]) {
iterationResult[iteration] = [];
}
if (checkDate(currentDate, i, config, verifiedField)) {
iterationResult[iteration].push(currentDate);
}
return iterationResult;
};
var checkDate = function checkDate(currentDate, i, config, verifiedField) {
if (!dateIsRecurrenceException(currentDate, config.exception)) {
var duration = dateUtils.sameDate(currentDate, config.recurrenceEndDate) ? config.recurrenceEndDate.getTime() - currentDate.getTime() : config.duration;
if (currentDate.getTime() >= config.recurrenceStartDate.getTime() && currentDate.getTime() + duration > config.min.getTime()) {
return verifiedField || checkDateByRule(currentDate, [config.dateRules[i]], config.rule["wkst"]);
}
}
return false;
};
var filterDatesBySetPos = function filterDatesBySetPos(dates, bySetPos) {
var resultArray = [];
bySetPos.split(",").forEach(function (index) {
index = Number(index);
var dateIndex = index > 0 ? index - 1 : dates.length + index;
if (dates[dateIndex]) {
resultArray.push(dates[dateIndex]);
}
});
return resultArray;
};
var correctDate = function correctDate(originalDate, date) {
if (originalDate.getDate() !== date) {
originalDate.setDate(date);
}
};
var incrementDate = function incrementDate(date, originalStartDate, rule, iterationStep) {
var initialDate = new Date(date),
needCorrect = true;
date = dateUtils.addInterval(date, rule.interval);
if (rule.freq === "MONTHLY" && !rule["byday"]) {
var expectedDate = originalStartDate.getDate();
if (rule["bymonthday"]) {
expectedDate = Number(rule["bymonthday"].split(",")[iterationStep]);
if (expectedDate < 0) {
initialDate.setMonth(initialDate.getMonth() + 1, 1);
dateSetterMap["bymonthday"](initialDate, expectedDate);
date = initialDate;
needCorrect = false;
}
}
needCorrect && correctDate(date, expectedDate);
}
if (rule.freq === "YEARLY") {
if (rule["byyearday"]) {
var dayNumber = Number(rule["byyearday"].split(",")[iterationStep]);
dateSetterMap["byyearday"](date, dayNumber);
}
var dateRules = splitDateRules(rule);
for (var field in dateRules[iterationStep]) {
dateSetterMap[field] && dateSetterMap[field](date, dateRules[iterationStep][field], rule["wkst"]);
}
}
return date;
};
var getDatePartDiffs = function getDatePartDiffs(date1, date2) {
return {
years: date1.getFullYear() - date2.getFullYear(),
months: date1.getMonth() - date2.getMonth(),
days: date1.getDate() - date2.getDate(),
hours: date1.getHours() - date2.getHours(),
minutes: date1.getMinutes() - date2.getMinutes(),
seconds: date1.getSeconds() - date2.getSeconds()
};
};
var getRecurrenceRule = function getRecurrenceRule(recurrence) {
var result = {
rule: {},
isValid: false
};
if (recurrence) {
result.rule = parseRecurrenceRule(recurrence);
result.isValid = validateRRule(result.rule, recurrence);
}
return result;
};
var loggedWarnings = [];
var validateRRule = function validateRRule(rule, recurrence) {
if (brokenRuleNameExists(rule) || inArray(rule.freq, freqNames) === -1 || wrongCountRule(rule) || wrongIntervalRule(rule) || wrongDayOfWeek(rule) || wrongByMonthDayRule(rule) || wrongByMonth(rule) || wrongUntilRule(rule)) {
logBrokenRule(recurrence);
return false;
}
return true;
};
var wrongUntilRule = function wrongUntilRule(rule) {
var wrongUntil = false,
until = rule.until;
if (until !== undefined && !(until instanceof Date)) {
wrongUntil = true;
}
return wrongUntil;
};
var wrongCountRule = function wrongCountRule(rule) {
var wrongCount = false,
count = rule.count;
if (count && typeof count === "string") {
wrongCount = true;
}
return wrongCount;
};
var wrongByMonthDayRule = function wrongByMonthDayRule(rule) {
var wrongByMonthDay = false,
byMonthDay = rule["bymonthday"];
if (byMonthDay && isNaN(parseInt(byMonthDay))) {
wrongByMonthDay = true;
}
return wrongByMonthDay;
};
var wrongByMonth = function wrongByMonth(rule) {
var wrongByMonth = false,
byMonth = rule["bymonth"];
if (byMonth && isNaN(parseInt(byMonth))) {
wrongByMonth = true;
}
return wrongByMonth;
};
var wrongIntervalRule = function wrongIntervalRule(rule) {
var wrongInterval = false,
interval = rule.interval;
if (interval && typeof interval === "string") {
wrongInterval = true;
}
return wrongInterval;
};
var wrongDayOfWeek = function wrongDayOfWeek(rule) {
var daysByRule = daysFromByDayRule(rule),
brokenDaysExist = false;
each(daysByRule, function (_, day) {
if (!days.hasOwnProperty(day)) {
brokenDaysExist = true;
return false;
}
});
return brokenDaysExist;
};
var brokenRuleNameExists = function brokenRuleNameExists(rule) {
var brokenRuleExists = false;
each(rule, function (ruleName) {
if (inArray(ruleName, ruleNames) === -1) {
brokenRuleExists = true;
return false;
}
});
return brokenRuleExists;
};
var logBrokenRule = function logBrokenRule(recurrence) {
if (inArray(recurrence, loggedWarnings) === -1) {
errors.log("W0006", recurrence);
loggedWarnings.push(recurrence);
}
};
var parseRecurrenceRule = function parseRecurrenceRule(recurrence) {
var ruleObject = {},
ruleParts = recurrence.split(";");
for (var i = 0, len = ruleParts.length; i < len; i++) {
var rule = ruleParts[i].split("="),
ruleName = rule[0].toLowerCase(),
ruleValue = rule[1];
ruleObject[ruleName] = ruleValue;
}
var count = parseInt(ruleObject.count);
if (!isNaN(count)) {
ruleObject.count = count;
}
if (ruleObject.interval) {
var interval = parseInt(ruleObject.interval);
if (!isNaN(interval)) {
ruleObject.interval = interval;
}
} else {
ruleObject.interval = 1;
}
if (ruleObject.freq && ruleObject.until) {
ruleObject.until = getDateByAsciiString(ruleObject.until);
}
return ruleObject;
};
var getDateByAsciiString = function getDateByAsciiString(string) {
if (typeof string !== "string") {
return string;
}
var arrayDate = string.match(/(\d{4})(\d{2})(\d{2})(T(\d{2})(\d{2})(\d{2}))?(Z)?/);
if (!arrayDate) {
return null;
}
var isUTC = arrayDate[8] !== undefined,
currentOffset = resultUtils.getTimeZoneOffset() * 60000,
date = new (Function.prototype.bind.apply(Date, prepareDateArrayToParse(arrayDate)))();
if (isUTC) {
date = new Date(date.getTime() - currentOffset);
}
return date;
};
var prepareDateArrayToParse = function prepareDateArrayToParse(arrayDate) {
arrayDate.shift();
if (arrayDate[3] === undefined) {
arrayDate.splice(3);
} else {
arrayDate.splice(3, 1);
arrayDate.splice(6);
}
arrayDate[1]--;
arrayDate.unshift(null);
return arrayDate;
};
var daysFromByDayRule = function daysFromByDayRule(rule) {
var result = [];
if (rule["byday"]) {
result = rule["byday"].split(",");
}
return result;
};
var getAsciiStringByDate = function getAsciiStringByDate(date) {
var currentOffset = resultUtils.getTimeZoneOffset() * 60000;
date = new Date(date.getTime() + currentOffset);
return date.getFullYear() + ('0' + (date.getMonth() + 1)).slice(-2) + ('0' + date.getDate()).slice(-2) + 'T' + ('0' + date.getHours()).slice(-2) + ('0' + date.getMinutes()).slice(-2) + ('0' + date.getSeconds()).slice(-2) + 'Z';
};
var splitDateRules = function splitDateRules(rule) {
var result = [];
if (!rule["wkst"]) {
rule["wkst"] = "MO";
}
if (rule["byweekno"] && !rule["byday"]) {
var dayNames = Object.keys(days);
for (var i = 0; i < days[rule["wkst"]]; i++) {
dayNames.push(dayNames.shift());
}
rule["byday"] = dayNames.join(",");
}
for (var field in dateSetterMap) {
if (!rule[field]) {
continue;
}
var ruleFieldValues = rule[field].split(","),
ruleArray = getDateRuleArray(field, ruleFieldValues);
result = result.length ? extendObjectArray(ruleArray, result) : ruleArray;
}
return result;
};
var getDateRuleArray = function getDateRuleArray(field, values) {
var result = [];
for (var i = 0, length = values.length; i < length; i++) {
var dateRule = {};
dateRule[field] = handleRuleFieldValue(field, values[i]);
result.push(dateRule);
}
return result;
};
var handleRuleFieldValue = function handleRuleFieldValue(field, value) {
var result = parseInt(value);
if (field === "bymonth") {
result -= 1;
}
if (field === "byday") {
result = days[value];
}
return result;
};
var extendObjectArray = function extendObjectArray(firstArray, secondArray) {
var result = [];
for (var i = 0, firstArrayLength = firstArray.length; i < firstArrayLength; i++) {
for (var j = 0, secondArrayLength = secondArray.length; j < secondArrayLength; j++) {
result.push(extend({}, firstArray[i], secondArray[j]));
}
}
return result;
};
var getDatesByRules = function getDatesByRules(dateRules, startDate, rule) {
var result = [];
for (var i = 0, len = dateRules.length; i < len; i++) {
var current = dateRules[i],
updatedDate = new Date(startDate);
for (var field in current) {
dateSetterMap[field] && dateSetterMap[field](updatedDate, current[field], rule["wkst"], rule.freq);
}
if (Array.isArray(updatedDate)) {
result = result.concat(updatedDate);
} else {
result.push(new Date(updatedDate));
}
}
if (!result.length) {
result.push(startDate);
}
return result;
};
var getDatesByCount = function getDatesByCount(dateRules, startDate, recurrenceStartDate, rule) {
var result = [],
count = rule.count,
counter = 0,
date = new Date(startDate.setDate(1));
while (counter < count) {
var dates = getDatesByRules(dateRules, date, rule);
var checkedDates = [];
for (var i = 0; i < dates.length; i++) {
if (dates[i].getTime() >= recurrenceStartDate.getTime()) {
checkedDates.push(dates[i]);
}
}
var length = checkedDates.length;
counter = counter + length;
var delCount = counter - count;
if (counter > count) {
checkedDates.splice(length - delCount, delCount);
}
for (i = 0; i < checkedDates.length; i++) {
result.push(checkedDates[i]);
}
date = dateUtils.addInterval(date, rule.interval);
}
return result;
};
var checkDateByRule = function checkDateByRule(date, rules, weekStart) {
var result = false;
for (var i = 0; i < rules.length; i++) {
var current = rules[i],
currentRuleResult = true;
for (var field in current) {
var processNegative = field === "bymonthday" && current[field] < 0;
if (dateGetterMap[field] && !processNegative && current[field] !== dateGetterMap[field](date, weekStart)) {
currentRuleResult = false;
}
}
result = result || currentRuleResult;
}
return result || !rules.length;
};
var getRecurrenceString = function getRecurrenceString(object) {
if (!object || !object.freq) {
return;
}
var result = "";
for (var field in object) {
var value = object[field];
if (field === "interval" && value < 2) {
continue;
}
if (field === "until") {
value = getAsciiStringByDate(value);
}
result += field + "=" + value + ";";
}
result = result.substring(0, result.length - 1);
return result.toUpperCase();
};
var resultUtils = {
getRecurrenceString: getRecurrenceString,
getRecurrenceRule: getRecurrenceRule,
getAsciiStringByDate: getAsciiStringByDate,
getDatesByRecurrence: getDatesByRecurrence,
dateInRecurrenceRange: dateInRecurrenceRange,
getDateByAsciiString: getDateByAsciiString,
daysFromByDayRule: daysFromByDayRule,
getTimeZoneOffset: getTimeZoneOffset
};
module.exports = resultUtils;