datetime.ts 5.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268
  1. import { ParsedOptions, Frequency } from './types'
  2. import { pymod, divmod, empty, includes } from './helpers'
  3. import { getWeekday, MAXYEAR, monthRange } from './dateutil'
  4. export class Time {
  5. public hour: number
  6. public minute: number
  7. public second: number
  8. public millisecond: number
  9. constructor(
  10. hour: number,
  11. minute: number,
  12. second: number,
  13. millisecond: number
  14. ) {
  15. this.hour = hour
  16. this.minute = minute
  17. this.second = second
  18. this.millisecond = millisecond || 0
  19. }
  20. getHours() {
  21. return this.hour
  22. }
  23. getMinutes() {
  24. return this.minute
  25. }
  26. getSeconds() {
  27. return this.second
  28. }
  29. getMilliseconds() {
  30. return this.millisecond
  31. }
  32. getTime() {
  33. return (
  34. (this.hour * 60 * 60 + this.minute * 60 + this.second) * 1000 +
  35. this.millisecond
  36. )
  37. }
  38. }
  39. export class DateTime extends Time {
  40. public day: number
  41. public month: number
  42. public year: number
  43. static fromDate(date: Date) {
  44. return new this(
  45. date.getUTCFullYear(),
  46. date.getUTCMonth() + 1,
  47. date.getUTCDate(),
  48. date.getUTCHours(),
  49. date.getUTCMinutes(),
  50. date.getUTCSeconds(),
  51. date.valueOf() % 1000
  52. )
  53. }
  54. constructor(
  55. year: number,
  56. month: number,
  57. day: number,
  58. hour: number,
  59. minute: number,
  60. second: number,
  61. millisecond: number
  62. ) {
  63. super(hour, minute, second, millisecond)
  64. this.year = year
  65. this.month = month
  66. this.day = day
  67. }
  68. getWeekday() {
  69. return getWeekday(new Date(this.getTime()))
  70. }
  71. getTime() {
  72. return new Date(
  73. Date.UTC(
  74. this.year,
  75. this.month - 1,
  76. this.day,
  77. this.hour,
  78. this.minute,
  79. this.second,
  80. this.millisecond
  81. )
  82. ).getTime()
  83. }
  84. getDay() {
  85. return this.day
  86. }
  87. getMonth() {
  88. return this.month
  89. }
  90. getYear() {
  91. return this.year
  92. }
  93. public addYears(years: number) {
  94. this.year += years
  95. }
  96. public addMonths(months: number) {
  97. this.month += months
  98. if (this.month > 12) {
  99. const yearDiv = Math.floor(this.month / 12)
  100. const monthMod = pymod(this.month, 12)
  101. this.month = monthMod
  102. this.year += yearDiv
  103. if (this.month === 0) {
  104. this.month = 12
  105. --this.year
  106. }
  107. }
  108. }
  109. public addWeekly(days: number, wkst: number) {
  110. if (wkst > this.getWeekday()) {
  111. this.day += -(this.getWeekday() + 1 + (6 - wkst)) + days * 7
  112. } else {
  113. this.day += -(this.getWeekday() - wkst) + days * 7
  114. }
  115. this.fixDay()
  116. }
  117. public addDaily(days: number) {
  118. this.day += days
  119. this.fixDay()
  120. }
  121. public addHours(hours: number, filtered: boolean, byhour: number[]) {
  122. if (filtered) {
  123. // Jump to one iteration before next day
  124. this.hour += Math.floor((23 - this.hour) / hours) * hours
  125. }
  126. for (;;) {
  127. this.hour += hours
  128. const { div: dayDiv, mod: hourMod } = divmod(this.hour, 24)
  129. if (dayDiv) {
  130. this.hour = hourMod
  131. this.addDaily(dayDiv)
  132. }
  133. if (empty(byhour) || includes(byhour, this.hour)) break
  134. }
  135. }
  136. public addMinutes(
  137. minutes: number,
  138. filtered: boolean,
  139. byhour: number[],
  140. byminute: number[]
  141. ) {
  142. if (filtered) {
  143. // Jump to one iteration before next day
  144. this.minute +=
  145. Math.floor((1439 - (this.hour * 60 + this.minute)) / minutes) * minutes
  146. }
  147. for (;;) {
  148. this.minute += minutes
  149. const { div: hourDiv, mod: minuteMod } = divmod(this.minute, 60)
  150. if (hourDiv) {
  151. this.minute = minuteMod
  152. this.addHours(hourDiv, false, byhour)
  153. }
  154. if (
  155. (empty(byhour) || includes(byhour, this.hour)) &&
  156. (empty(byminute) || includes(byminute, this.minute))
  157. ) {
  158. break
  159. }
  160. }
  161. }
  162. public addSeconds(
  163. seconds: number,
  164. filtered: boolean,
  165. byhour: number[],
  166. byminute: number[],
  167. bysecond: number[]
  168. ) {
  169. if (filtered) {
  170. // Jump to one iteration before next day
  171. this.second +=
  172. Math.floor(
  173. (86399 - (this.hour * 3600 + this.minute * 60 + this.second)) /
  174. seconds
  175. ) * seconds
  176. }
  177. for (;;) {
  178. this.second += seconds
  179. const { div: minuteDiv, mod: secondMod } = divmod(this.second, 60)
  180. if (minuteDiv) {
  181. this.second = secondMod
  182. this.addMinutes(minuteDiv, false, byhour, byminute)
  183. }
  184. if (
  185. (empty(byhour) || includes(byhour, this.hour)) &&
  186. (empty(byminute) || includes(byminute, this.minute)) &&
  187. (empty(bysecond) || includes(bysecond, this.second))
  188. ) {
  189. break
  190. }
  191. }
  192. }
  193. public fixDay() {
  194. if (this.day <= 28) {
  195. return
  196. }
  197. let daysinmonth = monthRange(this.year, this.month - 1)[1]
  198. if (this.day <= daysinmonth) {
  199. return
  200. }
  201. while (this.day > daysinmonth) {
  202. this.day -= daysinmonth
  203. ++this.month
  204. if (this.month === 13) {
  205. this.month = 1
  206. ++this.year
  207. if (this.year > MAXYEAR) {
  208. return
  209. }
  210. }
  211. daysinmonth = monthRange(this.year, this.month - 1)[1]
  212. }
  213. }
  214. public add(options: ParsedOptions, filtered: boolean) {
  215. const { freq, interval, wkst, byhour, byminute, bysecond } = options
  216. switch (freq) {
  217. case Frequency.YEARLY:
  218. return this.addYears(interval)
  219. case Frequency.MONTHLY:
  220. return this.addMonths(interval)
  221. case Frequency.WEEKLY:
  222. return this.addWeekly(interval, wkst)
  223. case Frequency.DAILY:
  224. return this.addDaily(interval)
  225. case Frequency.HOURLY:
  226. return this.addHours(interval, filtered, byhour)
  227. case Frequency.MINUTELY:
  228. return this.addMinutes(interval, filtered, byhour, byminute)
  229. case Frequency.SECONDLY:
  230. return this.addSeconds(interval, filtered, byhour, byminute, bysecond)
  231. }
  232. }
  233. }