| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655 |
- /**
- * DevExtreme (ui/scheduler/utils.recurrence.js)
- * Version: 19.1.16
- * Build date: Tue Oct 18 2022
- *
- * Copyright (c) 2012 - 2022 Developer Express Inc. ALL RIGHTS RESERVED
- * Read about DevExtreme licensing here: https://js.devexpress.com/Licensing/
- */
- "use strict";
- var errors = require("../../core/errors");
- var extend = require("../../core/utils/extend").extend;
- var each = require("../../core/utils/iterator").each;
- var inArray = require("../../core/utils/array").inArray;
- var isDefined = require("../../core/utils/type").isDefined;
- var 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(date, value) {
- date.setSeconds(value)
- },
- byminute: function(date, value) {
- date.setMinutes(value)
- },
- byhour: function(date, value) {
- date.setHours(value)
- },
- bymonth: function(date, value) {
- date.setMonth(value)
- },
- bymonthday: function(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(date, byDay, appointmentWeekStart, frequency, firstDayOfWeek) {
- var appointmentDayOfWeek = date.getDay();
- var weekStart = days[appointmentWeekStart];
- byDay += byDay >= weekStart === weekStart > appointmentDayOfWeek ? 7 : 0;
- date.setDate(date.getDate() - appointmentDayOfWeek + byDay)
- },
- byweekno: function(date, weekNumber, weekStart) {
- var initialDate = new Date(date);
- var firstYearDate = new Date(initialDate.setMonth(0, 1));
- var dayShift = firstYearDate.getDay() - days[weekStart];
- var firstDayOfYear = firstYearDate.getTime() - dayShift * toMs("day");
- var newFirstYearDate = dayShift + 1;
- if (newFirstYearDate > leastDaysInWeek) {
- date.setTime(firstDayOfYear + 7 * weekNumber * toMs("day"))
- } else {
- date.setTime(firstDayOfYear + 7 * (weekNumber - 1) * toMs("day"))
- }
- var timezoneDiff = (date.getTimezoneOffset() - firstYearDate.getTimezoneOffset()) * toMs("minute");
- timezoneDiff && date.setTime(date.getTime() + timezoneDiff)
- },
- byyearday: function(date, dayOfYear) {
- date.setMonth(0, 1);
- date.setDate(dayOfYear)
- }
- };
- var setDateByNegativeValue = function(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(date) {
- return date.getSeconds()
- },
- byminute: function(date) {
- return date.getMinutes()
- },
- byhour: function(date) {
- return date.getHours()
- },
- bymonth: function(date) {
- return date.getMonth()
- },
- bymonthday: function(date) {
- return date.getDate()
- },
- byday: function(date) {
- return date.getDay()
- },
- byweekno: function(date, weekStart) {
- var current = new Date(date);
- var diff = leastDaysInWeek - current.getDay() + days[weekStart] - 1;
- var dayInMilliseconds = toMs("day");
- if (date.getDay() < days[weekStart]) {
- diff -= 7
- }
- current.setHours(0, 0, 0);
- current.setDate(current.getDate() + diff);
- var yearStart = new Date(current.getFullYear(), 0, 1);
- var timezoneDiff = (yearStart.getTimezoneOffset() - current.getTimezoneOffset()) * toMs("minute");
- var daysFromYearStart = 1 + (current - yearStart + timezoneDiff) / dayInMilliseconds;
- return Math.ceil(daysFromYearStart / 7)
- },
- byyearday: function(date) {
- var yearStart = new Date(date.getFullYear(), 0, 0);
- var timezoneDiff = date.getTimezoneOffset() - yearStart.getTimezoneOffset();
- var diff = date - yearStart - timezoneDiff * toMs("minute");
- var dayLength = toMs("day");
- return Math.floor(diff / dayLength)
- }
- };
- var ruleNames = ["freq", "interval", "byday", "byweekno", "byyearday", "bymonth", "bymonthday", "count", "until", "byhour", "byminute", "bysecond", "bysetpos", "wkst"];
- var freqNames = ["DAILY", "WEEKLY", "MONTHLY", "YEARLY", "SECONDLY", "MINUTELY", "HOURLY"];
- var days = {
- SU: 0,
- MO: 1,
- TU: 2,
- WE: 3,
- TH: 4,
- FR: 5,
- SA: 6
- };
- var daysNames = {
- 0: "SU",
- 1: "MO",
- 2: "TU",
- 3: "WE",
- 4: "TH",
- 5: "FR",
- 6: "SA"
- };
- var getTimeZoneOffset = function() {
- return (new Date).getTimezoneOffset()
- };
- var dateInRecurrenceRange = function(options) {
- var result = [];
- if (options.rule) {
- result = getDatesByRecurrence(options)
- }
- return !!result.length
- };
- var normalizeInterval = function(rule) {
- var interval = rule.interval;
- var freq = rule.freq;
- var intervalObject = {};
- var intervalField = intervalMap[freq.toLowerCase()];
- if ("MONTHLY" === freq && rule.byday) {
- intervalField = intervalMap.daily
- }
- intervalObject[intervalField] = interval;
- return intervalObject
- };
- var getDatesByRecurrenceException = function(ruleValues, date) {
- var result = [];
- for (var i = 0, len = ruleValues.length; i < len; i++) {
- result[i] = getDateByAsciiString(ruleValues[i], date)
- }
- return result
- };
- var dateIsRecurrenceException = function(date, recurrenceException) {
- var result = false;
- if (!recurrenceException) {
- return result
- }
- var splitDates = recurrenceException.split(",");
- var exceptDates = getDatesByRecurrenceException(splitDates, date);
- var 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 (0 === diffs.years && 0 === diffs.months && 0 === diffs.days) {
- result = true
- }
- } else {
- if (date.getTime() === exceptDates[i].getTime()) {
- result = true
- }
- }
- }
- return result
- };
- var doNextIteration = function(date, startIntervalDate, endIntervalDate, recurrenceRule, iterationCount) {
- var matchCountIsCorrect = true;
- endIntervalDate = endIntervalDate.getTime();
- if (recurrenceRule.until) {
- if (recurrenceRule.until.getTime() < endIntervalDate) {
- endIntervalDate = recurrenceRule.until.getTime()
- }
- }
- if (recurrenceRule.count) {
- if (iterationCount === recurrenceRule.count) {
- matchCountIsCorrect = false
- }
- }
- var dateInInterval = date.getTime() <= endIntervalDate;
- return dateInInterval && matchCountIsCorrect
- };
- var getDatesByRecurrence = function(options) {
- var result = [];
- var recurrenceRule = getRecurrenceRule(options.rule);
- var iterationResult = {};
- var rule = recurrenceRule.rule;
- var recurrenceStartDate = options.start;
- if (!recurrenceRule.isValid || !rule.freq) {
- return result
- }
- rule.interval = normalizeInterval(rule);
- var dateRules = splitDateRules(rule, options.firstDayOfWeek);
- 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(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(currentDate, i, config, verifiedField) {
- if (!dateIsRecurrenceException(currentDate, config.exception)) {
- var duration = dateUtils.sameDate(currentDate, config.recurrenceEndDate) && config.recurrenceEndDate.getTime() > currentDate.getTime() ? 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(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(originalDate, date) {
- if (originalDate.getDate() !== date) {
- originalDate.setDate(date)
- }
- };
- var incrementDate = function(date, originalStartDate, rule, iterationStep) {
- var initialDate = new Date(date);
- var needCorrect = true;
- date = dateUtils.addInterval(date, rule.interval);
- if ("MONTHLY" === rule.freq && !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 ("YEARLY" === rule.freq) {
- 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(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(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(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(rule) {
- var wrongUntil = false;
- var until = rule.until;
- if (void 0 !== until && !(until instanceof Date)) {
- wrongUntil = true
- }
- return wrongUntil
- };
- var wrongCountRule = function(rule) {
- var wrongCount = false;
- var count = rule.count;
- if (count && "string" === typeof count) {
- wrongCount = true
- }
- return wrongCount
- };
- var wrongByMonthDayRule = function(rule) {
- var wrongByMonthDay = false;
- var byMonthDay = rule.bymonthday;
- if (byMonthDay && isNaN(parseInt(byMonthDay))) {
- wrongByMonthDay = true
- }
- return wrongByMonthDay
- };
- var wrongByMonth = function wrongByMonth(rule) {
- var wrongByMonth = false;
- var byMonth = rule.bymonth;
- if (byMonth && isNaN(parseInt(byMonth))) {
- wrongByMonth = true
- }
- return wrongByMonth
- };
- var wrongIntervalRule = function(rule) {
- var wrongInterval = false;
- var interval = rule.interval;
- if (interval && "string" === typeof interval) {
- wrongInterval = true
- }
- return wrongInterval
- };
- var wrongDayOfWeek = function(rule) {
- var daysByRule = daysFromByDayRule(rule);
- var brokenDaysExist = false;
- each(daysByRule, function(_, day) {
- if (!Object.prototype.hasOwnProperty.call(days, day)) {
- brokenDaysExist = true;
- return false
- }
- });
- return brokenDaysExist
- };
- var brokenRuleNameExists = function(rule) {
- var brokenRuleExists = false;
- each(rule, function(ruleName) {
- if (inArray(ruleName, ruleNames) === -1) {
- brokenRuleExists = true;
- return false
- }
- });
- return brokenRuleExists
- };
- var logBrokenRule = function(recurrence) {
- if (inArray(recurrence, loggedWarnings) === -1) {
- errors.log("W0006", recurrence);
- loggedWarnings.push(recurrence)
- }
- };
- var parseRecurrenceRule = function(recurrence) {
- var ruleObject = {};
- var ruleParts = recurrence.split(";");
- for (var i = 0, len = ruleParts.length; i < len; i++) {
- var rule = ruleParts[i].split("=");
- var ruleName = rule[0].toLowerCase();
- var 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(string, initialDate) {
- if ("string" !== typeof 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 = void 0 !== arrayDate[8];
- var currentOffset = initialDate ? initialDate.getTimezoneOffset() : resultUtils.getTimeZoneOffset();
- var date = new(Function.prototype.bind.apply(Date, prepareDateArrayToParse(arrayDate)));
- currentOffset = 6e4 * currentOffset;
- if (isUTC) {
- date = new Date(date.getTime() - currentOffset)
- }
- return date
- };
- var prepareDateArrayToParse = function(arrayDate) {
- arrayDate.shift();
- if (void 0 === arrayDate[3]) {
- arrayDate.splice(3)
- } else {
- arrayDate.splice(3, 1);
- arrayDate.splice(6)
- }
- arrayDate[1]--;
- arrayDate.unshift(null);
- return arrayDate
- };
- var daysFromByDayRule = function(rule) {
- var result = [];
- if (rule.byday) {
- if (Array.isArray(rule.byday)) {
- result = rule.byday
- } else {
- result = rule.byday.split(",")
- }
- }
- return result
- };
- var getAsciiStringByDate = function(date) {
- var currentOffset = 6e4 * resultUtils.getTimeZoneOffset();
- 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(rule) {
- var firstDayOfWeek = arguments.length > 1 && void 0 !== arguments[1] ? arguments[1] : null;
- var result = [];
- if (isDefined(firstDayOfWeek)) {
- rule.fdow = firstDayOfWeek
- }
- if (!rule.wkst) {
- rule.wkst = isDefined(firstDayOfWeek) ? daysNames[firstDayOfWeek] : "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(",");
- var ruleArray = getDateRuleArray(field, ruleFieldValues);
- result = result.length ? extendObjectArray(ruleArray, result) : ruleArray
- }
- return result
- };
- var getDateRuleArray = function(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(field, value) {
- var result = parseInt(value);
- if ("bymonth" === field) {
- result -= 1
- }
- if ("byday" === field) {
- result = days[value]
- }
- return result
- };
- var extendObjectArray = function(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(dateRules, startDate, rule) {
- var result = [];
- for (var i = 0, len = dateRules.length; i < len; i++) {
- var current = dateRules[i];
- var updatedDate = prepareDate(startDate, dateRules, rule.wkst);
- for (var field in current) {
- dateSetterMap[field] && dateSetterMap[field](updatedDate, current[field], rule.wkst, rule.freq, rule.fdow)
- }
- 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(dateRules, startDate, recurrenceStartDate, rule) {
- var result = [];
- var count = rule.count;
- var counter = 0;
- var date = prepareDate(startDate, dateRules, rule.wkst);
- 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 += length;
- var delCount = counter - count;
- if (counter > count) {
- checkedDates.splice(length - delCount, delCount)
- }
- for (i = 0; i < checkedDates.length; i++) {
- result.push(checkedDates[i])
- }
- var interval = rule.interval;
- if ("days" === Object.keys(interval)[0]) {
- interval = {
- weeks: 1
- }
- }
- date = dateUtils.addInterval(date, interval)
- }
- return result
- };
- var prepareDate = function(startDate, dateRules, weekStartRule) {
- var date = new Date(startDate);
- var day = date.getDay();
- if (dateRules.length && isDefined(dateRules[0].byday)) {
- date.setDate(date.getDate() - day + days[weekStartRule] - (day < days[weekStartRule] ? 7 : 0))
- } else {
- date.setDate(1)
- }
- return date
- };
- var checkDateByRule = function(date, rules, weekStart) {
- var result = false;
- for (var i = 0; i < rules.length; i++) {
- var current = rules[i];
- var currentRuleResult = true;
- for (var field in current) {
- var processNegative = "bymonthday" === field && 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(object) {
- if (!object || !object.freq) {
- return
- }
- var result = "";
- for (var field in object) {
- var value = object[field];
- if ("interval" === field && value < 2) {
- continue
- }
- if ("until" === field) {
- 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;
|