rrule.js 59 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910
  1. /*!
  2. * rrule.js - Library for working with recurrence rules for calendar dates.
  3. * https://github.com/jakubroztocil/rrule
  4. *
  5. * Copyright 2010, Jakub Roztocil and Lars Schoning
  6. * Licenced under the BSD licence.
  7. * https://github.com/jakubroztocil/rrule/blob/master/LICENCE
  8. *
  9. * Based on:
  10. * python-dateutil - Extensions to the standard Python datetime module.
  11. * Copyright (c) 2003-2011 - Gustavo Niemeyer <gustavo@niemeyer.net>
  12. * Copyright (c) 2012 - Tomi Pieviläinen <tomi.pievilainen@iki.fi>
  13. * https://github.com/jakubroztocil/rrule/blob/master/LICENCE
  14. *
  15. */
  16. (function(root){
  17. var serverSide = typeof module !== 'undefined' && module.exports;
  18. var getnlp = function() {
  19. if (!getnlp._nlp) {
  20. if (serverSide) {
  21. // Lazy, runtime import to avoid circular refs.
  22. getnlp._nlp = require('./nlp')
  23. } else if (!(getnlp._nlp = root._RRuleNLP)) {
  24. throw new Error(
  25. 'You need to include rrule/nlp.js for fromText/toText to work.'
  26. )
  27. }
  28. }
  29. return getnlp._nlp;
  30. };
  31. //=============================================================================
  32. // Date utilities
  33. //=============================================================================
  34. /**
  35. * General date-related utilities.
  36. * Also handles several incompatibilities between JavaScript and Python
  37. *
  38. */
  39. var dateutil = {
  40. MONTH_DAYS: [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31],
  41. /**
  42. * Number of milliseconds of one day
  43. */
  44. ONE_DAY: 1000 * 60 * 60 * 24,
  45. /**
  46. * @see: <http://docs.python.org/library/datetime.html#datetime.MAXYEAR>
  47. */
  48. MAXYEAR: 9999,
  49. /**
  50. * Python uses 1-Jan-1 as the base for calculating ordinals but we don't
  51. * want to confuse the JS engine with milliseconds > Number.MAX_NUMBER,
  52. * therefore we use 1-Jan-1970 instead
  53. */
  54. ORDINAL_BASE: new Date(1970, 0, 1),
  55. /**
  56. * Python: MO-SU: 0 - 6
  57. * JS: SU-SAT 0 - 6
  58. */
  59. PY_WEEKDAYS: [6, 0, 1, 2, 3, 4, 5],
  60. /**
  61. * py_date.timetuple()[7]
  62. */
  63. getYearDay: function(date) {
  64. var dateNoTime = new Date(
  65. date.getFullYear(), date.getMonth(), date.getDate());
  66. return Math.ceil(
  67. (dateNoTime - new Date(date.getFullYear(), 0, 1))
  68. / dateutil.ONE_DAY) + 1;
  69. },
  70. isLeapYear: function(year) {
  71. if (year instanceof Date) {
  72. year = year.getFullYear();
  73. }
  74. return ((year % 4 === 0) && (year % 100 !== 0)) || (year % 400 === 0);
  75. },
  76. /**
  77. * @return {Number} the date's timezone offset in ms
  78. */
  79. tzOffset: function(date) {
  80. return date.getTimezoneOffset() * 60 * 1000
  81. },
  82. /**
  83. * @see: <http://www.mcfedries.com/JavaScript/DaysBetween.asp>
  84. */
  85. daysBetween: function(date1, date2) {
  86. // The number of milliseconds in one day
  87. // Convert both dates to milliseconds
  88. var date1_ms = date1.getTime() - dateutil.tzOffset(date1);
  89. var date2_ms = date2.getTime() - dateutil.tzOffset(date2);
  90. // Calculate the difference in milliseconds
  91. var difference_ms = Math.abs(date1_ms - date2_ms);
  92. // Convert back to days and return
  93. return Math.round(difference_ms / dateutil.ONE_DAY);
  94. },
  95. /**
  96. * @see: <http://docs.python.org/library/datetime.html#datetime.date.toordinal>
  97. */
  98. toOrdinal: function(date) {
  99. return dateutil.daysBetween(date, dateutil.ORDINAL_BASE);
  100. },
  101. /**
  102. * @see - <http://docs.python.org/library/datetime.html#datetime.date.fromordinal>
  103. */
  104. fromOrdinal: function(ordinal) {
  105. var millisecsFromBase = ordinal * dateutil.ONE_DAY;
  106. return new Date(dateutil.ORDINAL_BASE.getTime()
  107. - dateutil.tzOffset(dateutil.ORDINAL_BASE)
  108. + millisecsFromBase
  109. + dateutil.tzOffset(new Date(millisecsFromBase)));
  110. },
  111. /**
  112. * @see: <http://docs.python.org/library/calendar.html#calendar.monthrange>
  113. */
  114. monthRange: function(year, month) {
  115. var date = new Date(year, month, 1);
  116. return [dateutil.getWeekday(date), dateutil.getMonthDays(date)];
  117. },
  118. getMonthDays: function(date) {
  119. var month = date.getMonth();
  120. return month == 1 && dateutil.isLeapYear(date)
  121. ? 29
  122. : dateutil.MONTH_DAYS[month];
  123. },
  124. /**
  125. * @return {Number} python-like weekday
  126. */
  127. getWeekday: function(date) {
  128. return dateutil.PY_WEEKDAYS[date.getDay()];
  129. },
  130. /**
  131. * @see: <http://docs.python.org/library/datetime.html#datetime.datetime.combine>
  132. */
  133. combine: function(date, time) {
  134. time = time || date;
  135. return new Date(
  136. date.getFullYear(), date.getMonth(), date.getDate(),
  137. time.getHours(), time.getMinutes(), time.getSeconds()
  138. );
  139. },
  140. clone: function(date) {
  141. var dolly = new Date(date.getTime());
  142. dolly.setMilliseconds(0);
  143. return dolly;
  144. },
  145. cloneDates: function(dates) {
  146. var clones = [];
  147. for (var i = 0; i < dates.length; i++) {
  148. clones.push(dateutil.clone(dates[i]));
  149. }
  150. return clones;
  151. },
  152. /**
  153. * Sorts an array of Date or dateutil.Time objects
  154. */
  155. sort: function(dates) {
  156. dates.sort(function(a, b){
  157. return a.getTime() - b.getTime();
  158. });
  159. },
  160. timeToUntilString: function(time) {
  161. var date = new Date(time);
  162. var comp, comps = [
  163. date.getUTCFullYear(),
  164. date.getUTCMonth() + 1,
  165. date.getUTCDate(),
  166. 'T',
  167. date.getUTCHours(),
  168. date.getUTCMinutes(),
  169. date.getUTCSeconds(),
  170. 'Z'
  171. ];
  172. for (var i = 0; i < comps.length; i++) {
  173. comp = comps[i];
  174. if (!/[TZ]/.test(comp) && comp < 10) {
  175. comps[i] = '0' + String(comp);
  176. }
  177. }
  178. return comps.join('');
  179. },
  180. untilStringToDate: function(until) {
  181. var re = /^(\d{4})(\d{2})(\d{2})(T(\d{2})(\d{2})(\d{2})Z)?$/;
  182. var bits = re.exec(until);
  183. if (!bits) {
  184. throw new Error('Invalid UNTIL value: ' + until)
  185. }
  186. return new Date(
  187. Date.UTC(bits[1],
  188. bits[2] - 1,
  189. bits[3],
  190. bits[5] || 0,
  191. bits[6] || 0,
  192. bits[7] || 0
  193. ));
  194. }
  195. };
  196. dateutil.Time = function(hour, minute, second) {
  197. this.hour = hour;
  198. this.minute = minute;
  199. this.second = second;
  200. };
  201. dateutil.Time.prototype = {
  202. getHours: function() {
  203. return this.hour;
  204. },
  205. getMinutes: function() {
  206. return this.minute;
  207. },
  208. getSeconds: function() {
  209. return this.second;
  210. },
  211. getTime: function() {
  212. return ((this.hour * 60 * 60)
  213. + (this.minute * 60)
  214. + this.second)
  215. * 1000;
  216. }
  217. };
  218. //=============================================================================
  219. // Helper functions
  220. //=============================================================================
  221. /**
  222. * Simplified version of python's range()
  223. */
  224. var range = function(start, end) {
  225. if (arguments.length === 1) {
  226. end = start;
  227. start = 0;
  228. }
  229. var rang = [];
  230. for (var i = start; i < end; i++) {
  231. rang.push(i);
  232. }
  233. return rang;
  234. };
  235. var repeat = function(value, times) {
  236. var i = 0, array = [];
  237. if (value instanceof Array) {
  238. for (; i < times; i++) {
  239. array[i] = [].concat(value);
  240. }
  241. } else {
  242. for (; i < times; i++) {
  243. array[i] = value;
  244. }
  245. }
  246. return array;
  247. };
  248. /**
  249. * closure/goog/math/math.js:modulo
  250. * Copyright 2006 The Closure Library Authors.
  251. * The % operator in JavaScript returns the remainder of a / b, but differs from
  252. * some other languages in that the result will have the same sign as the
  253. * dividend. For example, -1 % 8 == -1, whereas in some other languages
  254. * (such as Python) the result would be 7. This function emulates the more
  255. * correct modulo behavior, which is useful for certain applications such as
  256. * calculating an offset index in a circular list.
  257. *
  258. * @param {number} a The dividend.
  259. * @param {number} b The divisor.
  260. * @return {number} a % b where the result is between 0 and b (either 0 <= x < b
  261. * or b < x <= 0, depending on the sign of b).
  262. */
  263. var pymod = function(a, b) {
  264. var r = a % b;
  265. // If r and b differ in sign, add b to wrap the result to the correct sign.
  266. return (r * b < 0) ? r + b : r;
  267. };
  268. /**
  269. * @see: <http://docs.python.org/library/functions.html#divmod>
  270. */
  271. var divmod = function(a, b) {
  272. return {div: Math.floor(a / b), mod: pymod(a, b)};
  273. };
  274. /**
  275. * Python-like boolean
  276. * @return {Boolean} value of an object/primitive, taking into account
  277. * the fact that in Python an empty list's/tuple's
  278. * boolean value is False, whereas in JS it's true
  279. */
  280. var plb = function(obj) {
  281. return (obj instanceof Array && obj.length == 0)
  282. ? false
  283. : Boolean(obj);
  284. };
  285. /**
  286. * Return true if a value is in an array
  287. */
  288. var contains = function(arr, val) {
  289. return arr.indexOf(val) != -1;
  290. };
  291. //=============================================================================
  292. // Date masks
  293. //=============================================================================
  294. // Every mask is 7 days longer to handle cross-year weekly periods.
  295. var M365MASK = [].concat(
  296. repeat(1, 31), repeat(2, 28), repeat(3, 31),
  297. repeat(4, 30), repeat(5, 31), repeat(6, 30),
  298. repeat(7, 31), repeat(8, 31), repeat(9, 30),
  299. repeat(10, 31), repeat(11, 30), repeat(12, 31),
  300. repeat(1, 7)
  301. );
  302. var M366MASK = [].concat(
  303. repeat(1, 31), repeat(2, 29), repeat(3, 31),
  304. repeat(4, 30), repeat(5, 31), repeat(6, 30),
  305. repeat(7, 31), repeat(8, 31), repeat(9, 30),
  306. repeat(10, 31), repeat(11, 30), repeat(12, 31),
  307. repeat(1, 7)
  308. );
  309. var
  310. M28 = range(1, 29),
  311. M29 = range(1, 30),
  312. M30 = range(1, 31),
  313. M31 = range(1, 32);
  314. var MDAY366MASK = [].concat(
  315. M31, M29, M31,
  316. M30, M31, M30,
  317. M31, M31, M30,
  318. M31, M30, M31,
  319. M31.slice(0, 7)
  320. );
  321. var MDAY365MASK = [].concat(
  322. M31, M28, M31,
  323. M30, M31, M30,
  324. M31, M31, M30,
  325. M31, M30, M31,
  326. M31.slice(0, 7)
  327. );
  328. M28 = range(-28, 0);
  329. M29 = range(-29, 0);
  330. M30 = range(-30, 0);
  331. M31 = range(-31, 0);
  332. var NMDAY366MASK = [].concat(
  333. M31, M29, M31,
  334. M30, M31, M30,
  335. M31, M31, M30,
  336. M31, M30, M31,
  337. M31.slice(0, 7)
  338. );
  339. var NMDAY365MASK = [].concat(
  340. M31, M28, M31,
  341. M30, M31, M30,
  342. M31, M31, M30,
  343. M31, M30, M31,
  344. M31.slice(0, 7)
  345. );
  346. var M366RANGE = [0, 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335, 366];
  347. var M365RANGE = [0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334, 365];
  348. var WDAYMASK = (function() {
  349. for (var wdaymask = [], i = 0; i < 55; i++) {
  350. wdaymask = wdaymask.concat(range(7));
  351. }
  352. return wdaymask;
  353. }());
  354. //=============================================================================
  355. // Weekday
  356. //=============================================================================
  357. var Weekday = function(weekday, n) {
  358. if (n === 0) {
  359. throw new Error('Can\'t create weekday with n == 0');
  360. }
  361. this.weekday = weekday;
  362. this.n = n;
  363. };
  364. Weekday.prototype = {
  365. // __call__ - Cannot call the object directly, do it through
  366. // e.g. RRule.TH.nth(-1) instead,
  367. nth: function(n) {
  368. return this.n == n ? this : new Weekday(this.weekday, n);
  369. },
  370. // __eq__
  371. equals: function(other) {
  372. return this.weekday == other.weekday && this.n == other.n;
  373. },
  374. // __repr__
  375. toString: function() {
  376. var s = ['MO', 'TU', 'WE', 'TH', 'FR', 'SA', 'SU'][this.weekday];
  377. if (this.n) {
  378. s = (this.n > 0 ? '+' : '') + String(this.n) + s;
  379. }
  380. return s;
  381. },
  382. getJsWeekday: function() {
  383. return this.weekday == 6 ? 0 : this.weekday + 1;
  384. }
  385. };
  386. //=============================================================================
  387. // RRule
  388. //=============================================================================
  389. /**
  390. *
  391. * @param {Object?} options - see <http://labix.org/python-dateutil/#head-cf004ee9a75592797e076752b2a889c10f445418>
  392. * The only required option is `freq`, one of RRule.YEARLY, RRule.MONTHLY, ...
  393. * @constructor
  394. */
  395. var RRule = function(options, noCache) {
  396. // RFC string
  397. this._string = null;
  398. options = options || {};
  399. this._cache = noCache ? null : {
  400. all: false,
  401. before: [],
  402. after: [],
  403. between: []
  404. };
  405. // used by toString()
  406. this.origOptions = {};
  407. var invalid = [],
  408. keys = Object.keys(options),
  409. defaultKeys = Object.keys(RRule.DEFAULT_OPTIONS);
  410. // Shallow copy for origOptions and check for invalid
  411. keys.forEach(function(key) {
  412. this.origOptions[key] = options[key];
  413. if (!contains(defaultKeys, key)) invalid.push(key);
  414. }, this);
  415. if (invalid.length) {
  416. throw new Error('Invalid options: ' + invalid.join(', '))
  417. }
  418. if (!RRule.FREQUENCIES[options.freq] && options.byeaster === null) {
  419. throw new Error('Invalid frequency: ' + String(options.freq))
  420. }
  421. // Merge in default options
  422. defaultKeys.forEach(function(key) {
  423. if (!contains(keys, key)) options[key] = RRule.DEFAULT_OPTIONS[key];
  424. });
  425. var opts = this.options = options;
  426. if (opts.byeaster !== null) {
  427. opts.freq = RRule.YEARLY;
  428. }
  429. if (!opts.dtstart) {
  430. opts.dtstart = new Date();
  431. opts.dtstart.setMilliseconds(0);
  432. }
  433. if (opts.wkst === null) {
  434. opts.wkst = RRule.MO.weekday;
  435. } else if (typeof opts.wkst == 'number') {
  436. // cool, just keep it like that
  437. } else {
  438. opts.wkst = opts.wkst.weekday;
  439. }
  440. if (opts.bysetpos !== null) {
  441. if (typeof opts.bysetpos == 'number') {
  442. opts.bysetpos = [opts.bysetpos];
  443. }
  444. for (var i = 0; i < opts.bysetpos.length; i++) {
  445. var v = opts.bysetpos[i];
  446. if (v == 0 || !(-366 <= v && v <= 366)) {
  447. throw new Error(
  448. 'bysetpos must be between 1 and 366,' +
  449. ' or between -366 and -1'
  450. );
  451. }
  452. }
  453. }
  454. if (!(plb(opts.byweekno) || plb(opts.byyearday)
  455. || plb(opts.bymonthday) || opts.byweekday !== null
  456. || opts.byeaster !== null))
  457. {
  458. switch (opts.freq) {
  459. case RRule.YEARLY:
  460. if (!opts.bymonth) {
  461. opts.bymonth = opts.dtstart.getMonth() + 1;
  462. }
  463. opts.bymonthday = opts.dtstart.getDate();
  464. break;
  465. case RRule.MONTHLY:
  466. opts.bymonthday = opts.dtstart.getDate();
  467. break;
  468. case RRule.WEEKLY:
  469. opts.byweekday = dateutil.getWeekday(
  470. opts.dtstart);
  471. break;
  472. }
  473. }
  474. // bymonth
  475. if (opts.bymonth !== null
  476. && !(opts.bymonth instanceof Array)) {
  477. opts.bymonth = [opts.bymonth];
  478. }
  479. // byyearday
  480. if (opts.byyearday !== null
  481. && !(opts.byyearday instanceof Array)) {
  482. opts.byyearday = [opts.byyearday];
  483. }
  484. // bymonthday
  485. if (opts.bymonthday === null) {
  486. opts.bymonthday = [];
  487. opts.bynmonthday = [];
  488. } else if (opts.bymonthday instanceof Array) {
  489. var bymonthday = [], bynmonthday = [];
  490. for (i = 0; i < opts.bymonthday.length; i++) {
  491. var v = opts.bymonthday[i];
  492. if (v > 0) {
  493. bymonthday.push(v);
  494. } else if (v < 0) {
  495. bynmonthday.push(v);
  496. }
  497. }
  498. opts.bymonthday = bymonthday;
  499. opts.bynmonthday = bynmonthday;
  500. } else {
  501. if (opts.bymonthday < 0) {
  502. opts.bynmonthday = [opts.bymonthday];
  503. opts.bymonthday = [];
  504. } else {
  505. opts.bynmonthday = [];
  506. opts.bymonthday = [opts.bymonthday];
  507. }
  508. }
  509. // byweekno
  510. if (opts.byweekno !== null
  511. && !(opts.byweekno instanceof Array)) {
  512. opts.byweekno = [opts.byweekno];
  513. }
  514. // byweekday / bynweekday
  515. if (opts.byweekday === null) {
  516. opts.bynweekday = null;
  517. } else if (typeof opts.byweekday == 'number') {
  518. opts.byweekday = [opts.byweekday];
  519. opts.bynweekday = null;
  520. } else if (opts.byweekday instanceof Weekday) {
  521. if (!opts.byweekday.n || opts.freq > RRule.MONTHLY) {
  522. opts.byweekday = [opts.byweekday.weekday];
  523. opts.bynweekday = null;
  524. } else {
  525. opts.bynweekday = [
  526. [opts.byweekday.weekday,
  527. opts.byweekday.n]
  528. ];
  529. opts.byweekday = null;
  530. }
  531. } else {
  532. var byweekday = [], bynweekday = [];
  533. for (i = 0; i < opts.byweekday.length; i++) {
  534. var wday = opts.byweekday[i];
  535. if (typeof wday == 'number') {
  536. byweekday.push(wday);
  537. } else if (!wday.n || opts.freq > RRule.MONTHLY) {
  538. byweekday.push(wday.weekday);
  539. } else {
  540. bynweekday.push([wday.weekday, wday.n]);
  541. }
  542. }
  543. opts.byweekday = plb(byweekday) ? byweekday : null;
  544. opts.bynweekday = plb(bynweekday) ? bynweekday : null;
  545. }
  546. // byhour
  547. if (opts.byhour === null) {
  548. opts.byhour = (opts.freq < RRule.HOURLY)
  549. ? [opts.dtstart.getHours()]
  550. : null;
  551. } else if (typeof opts.byhour == 'number') {
  552. opts.byhour = [opts.byhour];
  553. }
  554. // byminute
  555. if (opts.byminute === null) {
  556. opts.byminute = (opts.freq < RRule.MINUTELY)
  557. ? [opts.dtstart.getMinutes()]
  558. : null;
  559. } else if (typeof opts.byminute == 'number') {
  560. opts.byminute = [opts.byminute];
  561. }
  562. // bysecond
  563. if (opts.bysecond === null) {
  564. opts.bysecond = (opts.freq < RRule.SECONDLY)
  565. ? [opts.dtstart.getSeconds()]
  566. : null;
  567. } else if (typeof opts.bysecond == 'number') {
  568. opts.bysecond = [opts.bysecond];
  569. }
  570. if (opts.freq >= RRule.HOURLY) {
  571. this.timeset = null;
  572. } else {
  573. this.timeset = [];
  574. for (i = 0; i < opts.byhour.length; i++) {
  575. var hour = opts.byhour[i];
  576. for (var j = 0; j < opts.byminute.length; j++) {
  577. var minute = opts.byminute[j];
  578. for (var k = 0; k < opts.bysecond.length; k++) {
  579. var second = opts.bysecond[k];
  580. // python:
  581. // datetime.time(hour, minute, second,
  582. // tzinfo=self._tzinfo))
  583. this.timeset.push(new dateutil.Time(hour, minute, second));
  584. }
  585. }
  586. }
  587. dateutil.sort(this.timeset);
  588. }
  589. };
  590. //}}}
  591. // RRule class 'constants'
  592. RRule.FREQUENCIES = [
  593. 'YEARLY', 'MONTHLY', 'WEEKLY', 'DAILY',
  594. 'HOURLY', 'MINUTELY', 'SECONDLY'
  595. ];
  596. RRule.YEARLY = 0;
  597. RRule.MONTHLY = 1;
  598. RRule.WEEKLY = 2;
  599. RRule.DAILY = 3;
  600. RRule.HOURLY = 4;
  601. RRule.MINUTELY = 5;
  602. RRule.SECONDLY = 6;
  603. RRule.MO = new Weekday(0);
  604. RRule.TU = new Weekday(1);
  605. RRule.WE = new Weekday(2);
  606. RRule.TH = new Weekday(3);
  607. RRule.FR = new Weekday(4);
  608. RRule.SA = new Weekday(5);
  609. RRule.SU = new Weekday(6);
  610. RRule.DEFAULT_OPTIONS = {
  611. freq: null,
  612. dtstart: null,
  613. interval: 1,
  614. wkst: RRule.MO,
  615. count: null,
  616. until: null,
  617. bysetpos: null,
  618. bymonth: null,
  619. bymonthday: null,
  620. byyearday: null,
  621. byweekno: null,
  622. byweekday: null,
  623. byhour: null,
  624. byminute: null,
  625. bysecond: null,
  626. byeaster: null
  627. };
  628. RRule.parseText = function(text, language) {
  629. return getnlp().parseText(text, language)
  630. };
  631. RRule.fromText = function(text, language) {
  632. return getnlp().fromText(text, language)
  633. };
  634. RRule.optionsToString = function(options) {
  635. var key, keys, defaultKeys, value, strValues, pairs = [];
  636. keys = Object.keys(options);
  637. defaultKeys = Object.keys(RRule.DEFAULT_OPTIONS);
  638. for (var i = 0; i < keys.length; i++) {
  639. if (!contains(defaultKeys, keys[i])) continue;
  640. key = keys[i].toUpperCase();
  641. value = options[keys[i]];
  642. strValues = [];
  643. if (value === null || value instanceof Array && !value.length) {
  644. continue;
  645. }
  646. switch (key) {
  647. case 'FREQ':
  648. value = RRule.FREQUENCIES[options.freq];
  649. break;
  650. case 'WKST':
  651. value = value.toString();
  652. break;
  653. case 'BYWEEKDAY':
  654. /*
  655. NOTE: BYWEEKDAY is a special case.
  656. RRule() deconstructs the rule.options.byweekday array
  657. into an array of Weekday arguments.
  658. On the other hand, rule.origOptions is an array of Weekdays.
  659. We need to handle both cases here.
  660. It might be worth change RRule to keep the Weekdays.
  661. Also, BYWEEKDAY (used by RRule) vs. BYDAY (RFC)
  662. */
  663. key = 'BYDAY';
  664. if (!(value instanceof Array)) {
  665. value = [value];
  666. }
  667. for (var wday, j = 0; j < value.length; j++) {
  668. wday = value[j];
  669. if (wday instanceof Weekday) {
  670. // good
  671. } else if (wday instanceof Array) {
  672. wday = new Weekday(wday[0], wday[1]);
  673. } else {
  674. wday = new Weekday(wday);
  675. }
  676. strValues[j] = wday.toString();
  677. }
  678. value = strValues;
  679. break;
  680. case'DTSTART':
  681. case'UNTIL':
  682. value = dateutil.timeToUntilString(value);
  683. break;
  684. default:
  685. if (value instanceof Array) {
  686. for (var j = 0; j < value.length; j++) {
  687. strValues[j] = String(value[j]);
  688. }
  689. value = strValues;
  690. } else {
  691. value = String(value);
  692. }
  693. }
  694. pairs.push([key, value]);
  695. }
  696. var strings = [];
  697. for (var i = 0; i < pairs.length; i++) {
  698. var attr = pairs[i];
  699. strings.push(attr[0] + '=' + attr[1].toString());
  700. }
  701. return strings.join(';');
  702. };
  703. RRule.prototype = {
  704. /**
  705. * @param {Function} iterator - optional function that will be called
  706. * on each date that is added. It can return false
  707. * to stop the iteration.
  708. * @return Array containing all recurrences.
  709. */
  710. all: function(iterator) {
  711. if (iterator) {
  712. return this._iter(new CallbackIterResult('all', {}, iterator));
  713. } else {
  714. var result = this._cacheGet('all');
  715. if (result === false) {
  716. result = this._iter(new IterResult('all', {}));
  717. this._cacheAdd('all', result);
  718. }
  719. return result;
  720. }
  721. },
  722. /**
  723. * Returns all the occurrences of the rrule between after and before.
  724. * The inc keyword defines what happens if after and/or before are
  725. * themselves occurrences. With inc == True, they will be included in the
  726. * list, if they are found in the recurrence set.
  727. * @return Array
  728. */
  729. between: function(after, before, inc, iterator) {
  730. var args = {
  731. before: before,
  732. after: after,
  733. inc: inc
  734. }
  735. if (iterator) {
  736. return this._iter(
  737. new CallbackIterResult('between', args, iterator));
  738. } else {
  739. var result = this._cacheGet('between', args);
  740. if (result === false) {
  741. result = this._iter(new IterResult('between', args));
  742. this._cacheAdd('between', result, args);
  743. }
  744. return result;
  745. }
  746. },
  747. /**
  748. * Returns the last recurrence before the given datetime instance.
  749. * The inc keyword defines what happens if dt is an occurrence.
  750. * With inc == True, if dt itself is an occurrence, it will be returned.
  751. * @return Date or null
  752. */
  753. before: function(dt, inc) {
  754. var args = {
  755. dt: dt,
  756. inc: inc
  757. },
  758. result = this._cacheGet('before', args);
  759. if (result === false) {
  760. result = this._iter(new IterResult('before', args));
  761. this._cacheAdd('before', result, args);
  762. }
  763. return result;
  764. },
  765. /**
  766. * Returns the first recurrence after the given datetime instance.
  767. * The inc keyword defines what happens if dt is an occurrence.
  768. * With inc == True, if dt itself is an occurrence, it will be returned.
  769. * @return Date or null
  770. */
  771. after: function(dt, inc) {
  772. var args = {
  773. dt: dt,
  774. inc: inc
  775. },
  776. result = this._cacheGet('after', args);
  777. if (result === false) {
  778. result = this._iter(new IterResult('after', args));
  779. this._cacheAdd('after', result, args);
  780. }
  781. return result;
  782. },
  783. /**
  784. * Returns the number of recurrences in this set. It will have go trough
  785. * the whole recurrence, if this hasn't been done before.
  786. */
  787. count: function() {
  788. return this.all().length;
  789. },
  790. /**
  791. * Converts the rrule into its string representation
  792. * @see <http://www.ietf.org/rfc/rfc2445.txt>
  793. * @return String
  794. */
  795. toString: function() {
  796. return RRule.optionsToString(this.origOptions);
  797. },
  798. /**
  799. * Will convert all rules described in nlp:ToText
  800. * to text.
  801. */
  802. toText: function(gettext, language) {
  803. return getnlp().toText(this, gettext, language);
  804. },
  805. isFullyConvertibleToText: function() {
  806. return getnlp().isFullyConvertible(this)
  807. },
  808. /**
  809. * @param {String} what - all/before/after/between
  810. * @param {Array,Date} value - an array of dates, one date, or null
  811. * @param {Object?} args - _iter arguments
  812. */
  813. _cacheAdd: function(what, value, args) {
  814. if (!this._cache) return;
  815. if (value) {
  816. value = (value instanceof Date)
  817. ? dateutil.clone(value)
  818. : dateutil.cloneDates(value);
  819. }
  820. if (what == 'all') {
  821. this._cache.all = value;
  822. } else {
  823. args._value = value;
  824. this._cache[what].push(args);
  825. }
  826. },
  827. /**
  828. * @return false - not in the cache
  829. * null - cached, but zero occurrences (before/after)
  830. * Date - cached (before/after)
  831. * [] - cached, but zero occurrences (all/between)
  832. * [Date1, DateN] - cached (all/between)
  833. */
  834. _cacheGet: function(what, args) {
  835. if (!this._cache) {
  836. return false;
  837. }
  838. var cached = false;
  839. if (what == 'all') {
  840. cached = this._cache.all;
  841. } else {
  842. // Let's see whether we've already called the
  843. // 'what' method with the same 'args'
  844. loopItems:
  845. for (var item, i = 0; i < this._cache[what].length; i++) {
  846. item = this._cache[what][i];
  847. for (var k in args) {
  848. if (args.hasOwnProperty(k)
  849. && String(args[k]) != String(item[k])) {
  850. continue loopItems;
  851. }
  852. }
  853. cached = item._value;
  854. break;
  855. }
  856. }
  857. if (!cached && this._cache.all) {
  858. // Not in the cache, but we already know all the occurrences,
  859. // so we can find the correct dates from the cached ones.
  860. var iterResult = new IterResult(what, args);
  861. for (var i = 0; i < this._cache.all.length; i++) {
  862. if (!iterResult.accept(this._cache.all[i])) {
  863. break;
  864. }
  865. }
  866. cached = iterResult.getValue();
  867. this._cacheAdd(what, cached, args);
  868. }
  869. return cached instanceof Array
  870. ? dateutil.cloneDates(cached)
  871. : (cached instanceof Date
  872. ? dateutil.clone(cached)
  873. : cached);
  874. },
  875. /**
  876. * @return a RRule instance with the same freq and options
  877. * as this one (cache is not cloned)
  878. */
  879. clone: function() {
  880. return new RRule(this.origOptions);
  881. },
  882. _iter: function(iterResult) {
  883. /* Since JavaScript doesn't have the python's yield operator (<1.7),
  884. we use the IterResult object that tells us when to stop iterating.
  885. */
  886. var dtstart = this.options.dtstart;
  887. var
  888. year = dtstart.getFullYear(),
  889. month = dtstart.getMonth() + 1,
  890. day = dtstart.getDate(),
  891. hour = dtstart.getHours(),
  892. minute = dtstart.getMinutes(),
  893. second = dtstart.getSeconds(),
  894. weekday = dateutil.getWeekday(dtstart),
  895. yearday = dateutil.getYearDay(dtstart);
  896. // Some local variables to speed things up a bit
  897. var
  898. freq = this.options.freq,
  899. interval = this.options.interval,
  900. wkst = this.options.wkst,
  901. until = this.options.until,
  902. bymonth = this.options.bymonth,
  903. byweekno = this.options.byweekno,
  904. byyearday = this.options.byyearday,
  905. byweekday = this.options.byweekday,
  906. byeaster = this.options.byeaster,
  907. bymonthday = this.options.bymonthday,
  908. bynmonthday = this.options.bynmonthday,
  909. bysetpos = this.options.bysetpos,
  910. byhour = this.options.byhour,
  911. byminute = this.options.byminute,
  912. bysecond = this.options.bysecond;
  913. var ii = new Iterinfo(this);
  914. ii.rebuild(year, month);
  915. var getdayset = {};
  916. getdayset[RRule.YEARLY] = ii.ydayset;
  917. getdayset[RRule.MONTHLY] = ii.mdayset;
  918. getdayset[RRule.WEEKLY] = ii.wdayset;
  919. getdayset[RRule.DAILY] = ii.ddayset;
  920. getdayset[RRule.HOURLY] = ii.ddayset;
  921. getdayset[RRule.MINUTELY] = ii.ddayset;
  922. getdayset[RRule.SECONDLY] = ii.ddayset;
  923. getdayset = getdayset[freq];
  924. var timeset;
  925. if (freq < RRule.HOURLY) {
  926. timeset = this.timeset;
  927. } else {
  928. var gettimeset = {};
  929. gettimeset[RRule.HOURLY] = ii.htimeset;
  930. gettimeset[RRule.MINUTELY] = ii.mtimeset;
  931. gettimeset[RRule.SECONDLY] = ii.stimeset;
  932. gettimeset = gettimeset[freq];
  933. if ((freq >= RRule.HOURLY && plb(byhour) && !contains(byhour, hour)) ||
  934. (freq >= RRule.MINUTELY && plb(byminute) && !contains(byminute, minute)) ||
  935. (freq >= RRule.SECONDLY && plb(bysecond) && !contains(bysecond, minute)))
  936. {
  937. timeset = [];
  938. } else {
  939. timeset = gettimeset.call(ii, hour, minute, second);
  940. }
  941. }
  942. var filtered, total = 0, count = this.options.count;
  943. var iterNo = 0;
  944. var i, j, k, dm, div, mod, tmp, pos, dayset, start, end, fixday;
  945. while (true) {
  946. // Get dayset with the right frequency
  947. tmp = getdayset.call(ii, year, month, day);
  948. dayset = tmp[0]; start = tmp[1]; end = tmp[2];
  949. // Do the "hard" work ;-)
  950. filtered = false;
  951. for (j = start; j < end; j++) {
  952. i = dayset[j];
  953. if ((plb(bymonth) && !contains(bymonth, ii.mmask[i])) ||
  954. (plb(byweekno) && !ii.wnomask[i]) ||
  955. (plb(byweekday) && !contains(byweekday, ii.wdaymask[i])) ||
  956. (plb(ii.nwdaymask) && !ii.nwdaymask[i]) ||
  957. (byeaster !== null && !contains(ii.eastermask, i)) ||
  958. (
  959. (plb(bymonthday) || plb(bynmonthday)) &&
  960. !contains(bymonthday, ii.mdaymask[i]) &&
  961. !contains(bynmonthday, ii.nmdaymask[i])
  962. )
  963. ||
  964. (
  965. plb(byyearday)
  966. &&
  967. (
  968. (
  969. i < ii.yearlen &&
  970. !contains(byyearday, i + 1) &&
  971. !contains(byyearday, -ii.yearlen + i)
  972. )
  973. ||
  974. (
  975. i >= ii.yearlen &&
  976. !contains(byyearday, i + 1 - ii.yearlen) &&
  977. !contains(byyearday, -ii.nextyearlen + i - ii.yearlen)
  978. )
  979. )
  980. )
  981. )
  982. {
  983. dayset[i] = null;
  984. filtered = true;
  985. }
  986. }
  987. // Output results
  988. if (plb(bysetpos) && plb(timeset)) {
  989. var daypos, timepos, poslist = [];
  990. for (i, j = 0; j < bysetpos.length; j++) {
  991. var pos = bysetpos[j];
  992. if (pos < 0) {
  993. daypos = Math.floor(pos / timeset.length);
  994. timepos = pymod(pos, timeset.length);
  995. } else {
  996. daypos = Math.floor((pos - 1) / timeset.length);
  997. timepos = pymod((pos - 1), timeset.length);
  998. }
  999. try {
  1000. tmp = [];
  1001. for (k = start; k < end; k++) {
  1002. var val = dayset[k];
  1003. if (val === null) {
  1004. continue;
  1005. }
  1006. tmp.push(val);
  1007. }
  1008. if (daypos < 0) {
  1009. // we're trying to emulate python's aList[-n]
  1010. i = tmp.slice(daypos)[0];
  1011. } else {
  1012. i = tmp[daypos];
  1013. }
  1014. var time = timeset[timepos];
  1015. var date = dateutil.fromOrdinal(ii.yearordinal + i);
  1016. var res = dateutil.combine(date, time);
  1017. // XXX: can this ever be in the array?
  1018. // - compare the actual date instead?
  1019. if (!contains(poslist, res)) {
  1020. poslist.push(res);
  1021. }
  1022. } catch (e) {}
  1023. }
  1024. dateutil.sort(poslist);
  1025. for (j = 0; j < poslist.length; j++) {
  1026. var res = poslist[j];
  1027. if (until && res > until) {
  1028. this._len = total;
  1029. return iterResult.getValue();
  1030. } else if (res >= dtstart) {
  1031. ++total;
  1032. if (!iterResult.accept(res)) {
  1033. return iterResult.getValue();
  1034. }
  1035. if (count) {
  1036. --count;
  1037. if (!count) {
  1038. this._len = total;
  1039. return iterResult.getValue();
  1040. }
  1041. }
  1042. }
  1043. }
  1044. } else {
  1045. for (j = start; j < end; j++) {
  1046. i = dayset[j];
  1047. if (i !== null) {
  1048. var date = dateutil.fromOrdinal(ii.yearordinal + i);
  1049. for (k = 0; k < timeset.length; k++) {
  1050. var time = timeset[k];
  1051. var res = dateutil.combine(date, time);
  1052. if (until && res > until) {
  1053. this._len = total;
  1054. return iterResult.getValue();
  1055. } else if (res >= dtstart) {
  1056. ++total;
  1057. if (!iterResult.accept(res)) {
  1058. return iterResult.getValue();
  1059. }
  1060. if (count) {
  1061. --count;
  1062. if (!count) {
  1063. this._len = total;
  1064. return iterResult.getValue();
  1065. }
  1066. }
  1067. }
  1068. }
  1069. }
  1070. }
  1071. }
  1072. // Handle frequency and interval
  1073. fixday = false;
  1074. if (freq == RRule.YEARLY) {
  1075. year += interval;
  1076. if (year > dateutil.MAXYEAR) {
  1077. this._len = total;
  1078. return iterResult.getValue();
  1079. }
  1080. ii.rebuild(year, month);
  1081. } else if (freq == RRule.MONTHLY) {
  1082. month += interval;
  1083. if (month > 12) {
  1084. div = Math.floor(month / 12);
  1085. mod = pymod(month, 12);
  1086. month = mod;
  1087. year += div;
  1088. if (month == 0) {
  1089. month = 12;
  1090. --year;
  1091. }
  1092. if (year > dateutil.MAXYEAR) {
  1093. this._len = total;
  1094. return iterResult.getValue();
  1095. }
  1096. }
  1097. ii.rebuild(year, month);
  1098. } else if (freq == RRule.WEEKLY) {
  1099. if (wkst > weekday) {
  1100. day += -(weekday + 1 + (6 - wkst)) + interval * 7;
  1101. } else {
  1102. day += -(weekday - wkst) + interval * 7;
  1103. }
  1104. weekday = wkst;
  1105. fixday = true;
  1106. } else if (freq == RRule.DAILY) {
  1107. day += interval;
  1108. fixday = true;
  1109. } else if (freq == RRule.HOURLY) {
  1110. if (filtered) {
  1111. // Jump to one iteration before next day
  1112. hour += Math.floor((23 - hour) / interval) * interval;
  1113. }
  1114. while (true) {
  1115. hour += interval;
  1116. dm = divmod(hour, 24);
  1117. div = dm.div;
  1118. mod = dm.mod;
  1119. if (div) {
  1120. hour = mod;
  1121. day += div;
  1122. fixday = true;
  1123. }
  1124. if (!plb(byhour) || contains(byhour, hour)) {
  1125. break;
  1126. }
  1127. }
  1128. timeset = gettimeset.call(ii, hour, minute, second);
  1129. } else if (freq == RRule.MINUTELY) {
  1130. if (filtered) {
  1131. // Jump to one iteration before next day
  1132. minute += Math.floor(
  1133. (1439 - (hour * 60 + minute)) / interval) * interval;
  1134. }
  1135. while(true) {
  1136. minute += interval;
  1137. dm = divmod(minute, 60);
  1138. div = dm.div;
  1139. mod = dm.mod;
  1140. if (div) {
  1141. minute = mod;
  1142. hour += div;
  1143. dm = divmod(hour, 24);
  1144. div = dm.div;
  1145. mod = dm.mod;
  1146. if (div) {
  1147. hour = mod;
  1148. day += div;
  1149. fixday = true;
  1150. filtered = false;
  1151. }
  1152. }
  1153. if ((!plb(byhour) || contains(byhour, hour)) &&
  1154. (!plb(byminute) || contains(byminute, minute))) {
  1155. break;
  1156. }
  1157. }
  1158. timeset = gettimeset.call(ii, hour, minute, second);
  1159. } else if (freq == RRule.SECONDLY) {
  1160. if (filtered) {
  1161. // Jump to one iteration before next day
  1162. second += Math.floor(
  1163. (86399 - (hour * 3600 + minute * 60 + second))
  1164. / interval) * interval;
  1165. }
  1166. while (true) {
  1167. second += interval;
  1168. dm = divmod(second, 60);
  1169. div = dm.div;
  1170. mod = dm.mod;
  1171. if (div) {
  1172. second = mod;
  1173. minute += div;
  1174. dm = divmod(minute, 60);
  1175. div = dm.div;
  1176. mod = dm.mod;
  1177. if (div) {
  1178. minute = mod;
  1179. hour += div;
  1180. dm = divmod(hour, 24);
  1181. div = dm.div;
  1182. mod = dm.mod;
  1183. if (div) {
  1184. hour = mod;
  1185. day += div;
  1186. fixday = true;
  1187. }
  1188. }
  1189. }
  1190. if ((!plb(byhour) || contains(byhour, hour)) &&
  1191. (!plb(byminute) || contains(byminute, minute)) &&
  1192. (!plb(bysecond) || contains(bysecond, second)))
  1193. {
  1194. break;
  1195. }
  1196. }
  1197. timeset = gettimeset.call(ii, hour, minute, second);
  1198. }
  1199. if (fixday && day > 28) {
  1200. var daysinmonth = dateutil.monthRange(year, month - 1)[1];
  1201. if (day > daysinmonth) {
  1202. while (day > daysinmonth) {
  1203. day -= daysinmonth;
  1204. ++month;
  1205. if (month == 13) {
  1206. month = 1;
  1207. ++year;
  1208. if (year > dateutil.MAXYEAR) {
  1209. this._len = total;
  1210. return iterResult.getValue();
  1211. }
  1212. }
  1213. daysinmonth = dateutil.monthRange(year, month - 1)[1];
  1214. }
  1215. ii.rebuild(year, month);
  1216. }
  1217. }
  1218. }
  1219. }
  1220. };
  1221. RRule.parseString = function(rfcString) {
  1222. rfcString = rfcString.replace(/^\s+|\s+$/, '');
  1223. if (!rfcString.length) {
  1224. return null;
  1225. }
  1226. var i, j, key, value, attr,
  1227. attrs = rfcString.split(';'),
  1228. options = {};
  1229. for (i = 0; i < attrs.length; i++) {
  1230. attr = attrs[i].split('=');
  1231. key = attr[0];
  1232. value = attr[1];
  1233. switch (key) {
  1234. case 'FREQ':
  1235. options.freq = RRule[value];
  1236. break;
  1237. case 'WKST':
  1238. options.wkst = RRule[value];
  1239. break;
  1240. case 'COUNT':
  1241. case 'INTERVAL':
  1242. case 'BYSETPOS':
  1243. case 'BYMONTH':
  1244. case 'BYMONTHDAY':
  1245. case 'BYYEARDAY':
  1246. case 'BYWEEKNO':
  1247. case 'BYHOUR':
  1248. case 'BYMINUTE':
  1249. case 'BYSECOND':
  1250. if (value.indexOf(',') != -1) {
  1251. value = value.split(',');
  1252. for (j = 0; j < value.length; j++) {
  1253. if (/^[+-]?\d+$/.test(value[j])) {
  1254. value[j] = Number(value[j]);
  1255. }
  1256. }
  1257. } else if (/^[+-]?\d+$/.test(value)) {
  1258. value = Number(value);
  1259. }
  1260. key = key.toLowerCase();
  1261. options[key] = value;
  1262. break;
  1263. case 'BYDAY': // => byweekday
  1264. var n, wday, day, days = value.split(',');
  1265. options.byweekday = [];
  1266. for (j = 0; j < days.length; j++) {
  1267. day = days[j];
  1268. if (day.length == 2) { // MO, TU, ...
  1269. wday = RRule[day]; // wday instanceof Weekday
  1270. options.byweekday.push(wday);
  1271. } else { // -1MO, +3FR, 1SO, ...
  1272. day = day.match(/^([+-]?\d)([A-Z]{2})$/);
  1273. n = Number(day[1]);
  1274. wday = day[2];
  1275. wday = RRule[wday].weekday;
  1276. options.byweekday.push(new Weekday(wday, n));
  1277. }
  1278. }
  1279. break;
  1280. case 'DTSTART':
  1281. options.dtstart = dateutil.untilStringToDate(value);
  1282. break;
  1283. case 'UNTIL':
  1284. options.until = dateutil.untilStringToDate(value);
  1285. break;
  1286. case 'BYEASTER':
  1287. options.byeaster = Number(value);
  1288. break;
  1289. default:
  1290. throw new Error("Unknown RRULE property '" + key + "'");
  1291. }
  1292. }
  1293. return options;
  1294. };
  1295. RRule.fromString = function(string) {
  1296. return new RRule(RRule.parseString(string));
  1297. };
  1298. //=============================================================================
  1299. // Iterinfo
  1300. //=============================================================================
  1301. var Iterinfo = function(rrule) {
  1302. this.rrule = rrule;
  1303. this.lastyear = null;
  1304. this.lastmonth = null;
  1305. this.yearlen = null;
  1306. this.nextyearlen = null;
  1307. this.yearordinal = null;
  1308. this.yearweekday = null;
  1309. this.mmask = null;
  1310. this.mrange = null;
  1311. this.mdaymask = null;
  1312. this.nmdaymask = null;
  1313. this.wdaymask = null;
  1314. this.wnomask = null;
  1315. this.nwdaymask = null;
  1316. this.eastermask = null;
  1317. };
  1318. Iterinfo.prototype.easter = function(y, offset) {
  1319. offset = offset || 0;
  1320. var a = y % 19,
  1321. b = Math.floor(y / 100),
  1322. c = y % 100,
  1323. d = Math.floor(b / 4),
  1324. e = b % 4,
  1325. f = Math.floor((b + 8) / 25),
  1326. g = Math.floor((b - f + 1) / 3),
  1327. h = Math.floor(19 * a + b - d - g + 15) % 30,
  1328. i = Math.floor(c / 4),
  1329. k = c % 4,
  1330. l = Math.floor(32 + 2 * e + 2 * i - h - k) % 7,
  1331. m = Math.floor((a + 11 * h + 22 * l) / 451),
  1332. month = Math.floor((h + l - 7 * m + 114) / 31),
  1333. day = (h + l - 7 * m + 114) % 31 + 1,
  1334. date = Date.UTC(y, month - 1, day + offset),
  1335. yearStart = Date.UTC(y, 0, 1);
  1336. return [ Math.ceil((date - yearStart) / (1000 * 60 * 60 * 24)) ];
  1337. }
  1338. Iterinfo.prototype.rebuild = function(year, month) {
  1339. var rr = this.rrule;
  1340. if (year != this.lastyear) {
  1341. this.yearlen = dateutil.isLeapYear(year) ? 366 : 365;
  1342. this.nextyearlen = dateutil.isLeapYear(year + 1) ? 366 : 365;
  1343. var firstyday = new Date(year, 0, 1);
  1344. this.yearordinal = dateutil.toOrdinal(firstyday);
  1345. this.yearweekday = dateutil.getWeekday(firstyday);
  1346. var wday = dateutil.getWeekday(new Date(year, 0, 1));
  1347. if (this.yearlen == 365) {
  1348. this.mmask = [].concat(M365MASK);
  1349. this.mdaymask = [].concat(MDAY365MASK);
  1350. this.nmdaymask = [].concat(NMDAY365MASK);
  1351. this.wdaymask = WDAYMASK.slice(wday);
  1352. this.mrange = [].concat(M365RANGE);
  1353. } else {
  1354. this.mmask = [].concat(M366MASK);
  1355. this.mdaymask = [].concat(MDAY366MASK);
  1356. this.nmdaymask = [].concat(NMDAY366MASK);
  1357. this.wdaymask = WDAYMASK.slice(wday);
  1358. this.mrange = [].concat(M366RANGE);
  1359. }
  1360. if (!plb(rr.options.byweekno)) {
  1361. this.wnomask = null;
  1362. } else {
  1363. this.wnomask = repeat(0, this.yearlen + 7);
  1364. var no1wkst, firstwkst, wyearlen;
  1365. no1wkst = firstwkst = pymod(
  1366. 7 - this.yearweekday + rr.options.wkst, 7);
  1367. if (no1wkst >= 4) {
  1368. no1wkst = 0;
  1369. // Number of days in the year, plus the days we got
  1370. // from last year.
  1371. wyearlen = this.yearlen + pymod(
  1372. this.yearweekday - rr.options.wkst, 7);
  1373. } else {
  1374. // Number of days in the year, minus the days we
  1375. // left in last year.
  1376. wyearlen = this.yearlen - no1wkst;
  1377. }
  1378. var div = Math.floor(wyearlen / 7);
  1379. var mod = pymod(wyearlen, 7);
  1380. var numweeks = Math.floor(div + (mod / 4));
  1381. for (var n, i, j = 0; j < rr.options.byweekno.length; j++) {
  1382. n = rr.options.byweekno[j];
  1383. if (n < 0) {
  1384. n += numweeks + 1;
  1385. } if (!(0 < n && n <= numweeks)) {
  1386. continue;
  1387. } if (n > 1) {
  1388. i = no1wkst + (n - 1) * 7;
  1389. if (no1wkst != firstwkst) {
  1390. i -= 7-firstwkst;
  1391. }
  1392. } else {
  1393. i = no1wkst;
  1394. }
  1395. for (var k = 0; k < 7; k++) {
  1396. this.wnomask[i] = 1;
  1397. i++;
  1398. if (this.wdaymask[i] == rr.options.wkst) {
  1399. break;
  1400. }
  1401. }
  1402. }
  1403. if (contains(rr.options.byweekno, 1)) {
  1404. // Check week number 1 of next year as well
  1405. // orig-TODO : Check -numweeks for next year.
  1406. var i = no1wkst + numweeks * 7;
  1407. if (no1wkst != firstwkst) {
  1408. i -= 7 - firstwkst;
  1409. }
  1410. if (i < this.yearlen) {
  1411. // If week starts in next year, we
  1412. // don't care about it.
  1413. for (var j = 0; j < 7; j++) {
  1414. this.wnomask[i] = 1;
  1415. i += 1;
  1416. if (this.wdaymask[i] == rr.options.wkst) {
  1417. break;
  1418. }
  1419. }
  1420. }
  1421. }
  1422. if (no1wkst) {
  1423. // Check last week number of last year as
  1424. // well. If no1wkst is 0, either the year
  1425. // started on week start, or week number 1
  1426. // got days from last year, so there are no
  1427. // days from last year's last week number in
  1428. // this year.
  1429. var lnumweeks;
  1430. if (!contains(rr.options.byweekno, -1)) {
  1431. var lyearweekday = dateutil.getWeekday(
  1432. new Date(year - 1, 0, 1));
  1433. var lno1wkst = pymod(
  1434. 7 - lyearweekday + rr.options.wkst, 7);
  1435. var lyearlen = dateutil.isLeapYear(year - 1) ? 366 : 365;
  1436. if (lno1wkst >= 4) {
  1437. lno1wkst = 0;
  1438. lnumweeks = Math.floor(
  1439. 52
  1440. + pymod(
  1441. lyearlen + pymod(
  1442. lyearweekday - rr.options.wkst, 7), 7)
  1443. / 4);
  1444. } else {
  1445. lnumweeks = Math.floor(
  1446. 52 + pymod(this.yearlen - no1wkst, 7) / 4);
  1447. }
  1448. } else {
  1449. lnumweeks = -1;
  1450. }
  1451. if (contains(rr.options.byweekno, lnumweeks)) {
  1452. for (var i = 0; i < no1wkst; i++) {
  1453. this.wnomask[i] = 1;
  1454. }
  1455. }
  1456. }
  1457. }
  1458. }
  1459. if (plb(rr.options.bynweekday)
  1460. && (month != this.lastmonth || year != this.lastyear)) {
  1461. var ranges = [];
  1462. if (rr.options.freq == RRule.YEARLY) {
  1463. if (plb(rr.options.bymonth)) {
  1464. for (j = 0; j < rr.options.bymonth.length; j++) {
  1465. month = rr.options.bymonth[j];
  1466. ranges.push(this.mrange.slice(month - 1, month + 1));
  1467. }
  1468. } else {
  1469. ranges = [[0, this.yearlen]];
  1470. }
  1471. } else if (rr.options.freq == RRule.MONTHLY) {
  1472. ranges = [this.mrange.slice(month - 1, month + 1)];
  1473. }
  1474. if (plb(ranges)) {
  1475. // Weekly frequency won't get here, so we may not
  1476. // care about cross-year weekly periods.
  1477. this.nwdaymask = repeat(0, this.yearlen);
  1478. for (var j = 0; j < ranges.length; j++) {
  1479. var rang = ranges[j];
  1480. var first = rang[0], last = rang[1];
  1481. last -= 1;
  1482. for (var k = 0; k < rr.options.bynweekday.length; k++) {
  1483. var wday = rr.options.bynweekday[k][0],
  1484. n = rr.options.bynweekday[k][1];
  1485. if (n < 0) {
  1486. i = last + (n + 1) * 7;
  1487. i -= pymod(this.wdaymask[i] - wday, 7);
  1488. } else {
  1489. i = first + (n - 1) * 7;
  1490. i += pymod(7 - this.wdaymask[i] + wday, 7);
  1491. }
  1492. if (first <= i && i <= last) {
  1493. this.nwdaymask[i] = 1;
  1494. }
  1495. }
  1496. }
  1497. }
  1498. this.lastyear = year;
  1499. this.lastmonth = month;
  1500. }
  1501. if (rr.options.byeaster !== null) {
  1502. this.eastermask = this.easter(year, rr.options.byeaster);
  1503. }
  1504. };
  1505. Iterinfo.prototype.ydayset = function(year, month, day) {
  1506. return [range(this.yearlen), 0, this.yearlen];
  1507. };
  1508. Iterinfo.prototype.mdayset = function(year, month, day) {
  1509. var set = repeat(null, this.yearlen);
  1510. var start = this.mrange[month-1];
  1511. var end = this.mrange[month];
  1512. for (var i = start; i < end; i++) {
  1513. set[i] = i;
  1514. }
  1515. return [set, start, end];
  1516. };
  1517. Iterinfo.prototype.wdayset = function(year, month, day) {
  1518. // We need to handle cross-year weeks here.
  1519. var set = repeat(null, this.yearlen + 7);
  1520. var i = dateutil.toOrdinal(
  1521. new Date(year, month - 1, day)) - this.yearordinal;
  1522. var start = i;
  1523. for (var j = 0; j < 7; j++) {
  1524. set[i] = i;
  1525. ++i;
  1526. if (this.wdaymask[i] == this.rrule.options.wkst) {
  1527. break;
  1528. }
  1529. }
  1530. return [set, start, i];
  1531. };
  1532. Iterinfo.prototype.ddayset = function(year, month, day) {
  1533. var set = repeat(null, this.yearlen);
  1534. var i = dateutil.toOrdinal(
  1535. new Date(year, month - 1, day)) - this.yearordinal;
  1536. set[i] = i;
  1537. return [set, i, i + 1];
  1538. };
  1539. Iterinfo.prototype.htimeset = function(hour, minute, second) {
  1540. var set = [], rr = this.rrule;
  1541. for (var i = 0; i < rr.options.byminute.length; i++) {
  1542. minute = rr.options.byminute[i];
  1543. for (var j = 0; j < rr.options.bysecond.length; j++) {
  1544. second = rr.options.bysecond[j];
  1545. set.push(new dateutil.Time(hour, minute, second));
  1546. }
  1547. }
  1548. dateutil.sort(set);
  1549. return set;
  1550. };
  1551. Iterinfo.prototype.mtimeset = function(hour, minute, second) {
  1552. var set = [], rr = this.rrule;
  1553. for (var j = 0; j < rr.options.bysecond.length; j++) {
  1554. second = rr.options.bysecond[j];
  1555. set.push(new dateutil.Time(hour, minute, second));
  1556. }
  1557. dateutil.sort(set);
  1558. return set;
  1559. };
  1560. Iterinfo.prototype.stimeset = function(hour, minute, second) {
  1561. return [new dateutil.Time(hour, minute, second)];
  1562. };
  1563. //=============================================================================
  1564. // Results
  1565. //=============================================================================
  1566. /**
  1567. * This class helps us to emulate python's generators, sorta.
  1568. */
  1569. var IterResult = function(method, args) {
  1570. this.init(method, args)
  1571. };
  1572. IterResult.prototype = {
  1573. init: function(method, args) {
  1574. this.method = method;
  1575. this.args = args;
  1576. this._result = [];
  1577. this.minDate = null;
  1578. this.maxDate = null;
  1579. if (method == 'between') {
  1580. this.maxDate = args.inc
  1581. ? args.before
  1582. : new Date(args.before.getTime() - 1);
  1583. this.minDate = args.inc
  1584. ? args.after
  1585. : new Date(args.after.getTime() + 1);
  1586. } else if (method == 'before') {
  1587. this.maxDate = args.inc ? args.dt : new Date(args.dt.getTime() - 1);
  1588. } else if (method == 'after') {
  1589. this.minDate = args.inc ? args.dt : new Date(args.dt.getTime() + 1);
  1590. }
  1591. },
  1592. /**
  1593. * Possibly adds a date into the result.
  1594. *
  1595. * @param {Date} date - the date isn't necessarly added to the result
  1596. * list (if it is too late/too early)
  1597. * @return {Boolean} true if it makes sense to continue the iteration;
  1598. * false if we're done.
  1599. */
  1600. accept: function(date) {
  1601. var tooEarly = this.minDate && date < this.minDate,
  1602. tooLate = this.maxDate && date > this.maxDate;
  1603. if (this.method == 'between') {
  1604. if (tooEarly)
  1605. return true;
  1606. if (tooLate)
  1607. return false;
  1608. } else if (this.method == 'before') {
  1609. if (tooLate)
  1610. return false;
  1611. } else if (this.method == 'after') {
  1612. if (tooEarly)
  1613. return true;
  1614. this.add(date);
  1615. return false;
  1616. }
  1617. return this.add(date);
  1618. },
  1619. /**
  1620. *
  1621. * @param {Date} date that is part of the result.
  1622. * @return {Boolean} whether we are interested in more values.
  1623. */
  1624. add: function(date) {
  1625. this._result.push(date);
  1626. return true;
  1627. },
  1628. /**
  1629. * 'before' and 'after' return only one date, whereas 'all'
  1630. * and 'between' an array.
  1631. * @return {Date,Array?}
  1632. */
  1633. getValue: function() {
  1634. switch (this.method) {
  1635. case 'all':
  1636. case 'between':
  1637. return this._result;
  1638. case 'before':
  1639. case 'after':
  1640. return this._result.length
  1641. ? this._result[this._result.length - 1]
  1642. : null;
  1643. }
  1644. }
  1645. };
  1646. /**
  1647. * IterResult subclass that calls a callback function on each add,
  1648. * and stops iterating when the callback returns false.
  1649. */
  1650. var CallbackIterResult = function(method, args, iterator) {
  1651. var allowedMethods = ['all', 'between'];
  1652. if (!contains(allowedMethods, method)) {
  1653. throw new Error('Invalid method "' + method
  1654. + '". Only all and between works with iterator.');
  1655. }
  1656. this.add = function(date) {
  1657. if (iterator(date, this._result.length)) {
  1658. this._result.push(date);
  1659. return true;
  1660. }
  1661. return false;
  1662. };
  1663. this.init(method, args);
  1664. };
  1665. CallbackIterResult.prototype = IterResult.prototype;
  1666. //=============================================================================
  1667. // Export
  1668. //=============================================================================
  1669. if (serverSide) {
  1670. module.exports = {
  1671. RRule: RRule
  1672. // rruleset: rruleset
  1673. }
  1674. }
  1675. if (typeof ender === 'undefined') {
  1676. root['RRule'] = RRule;
  1677. // root['rruleset'] = rruleset;
  1678. }
  1679. if (typeof define === "function" && define.amd) {
  1680. /*global define:false */
  1681. define("rrule", [], function () {
  1682. return RRule;
  1683. });
  1684. }
  1685. }(this));