rrule.js 67 KB

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