chuvsu-js 2.8.2 → 3.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (43) hide show
  1. package/README.md +3 -2
  2. package/dist/browser.d.ts +1 -5
  3. package/dist/browser.js +1 -2
  4. package/dist/index.d.ts +1 -8
  5. package/dist/index.js +2 -3
  6. package/dist/shared.d.ts +9 -0
  7. package/dist/shared.js +7 -0
  8. package/dist/tt/client.js +1 -1
  9. package/dist/tt/parse/audience.d.ts +3 -0
  10. package/dist/tt/parse/audience.js +150 -0
  11. package/dist/tt/parse/full-schedule.d.ts +4 -0
  12. package/dist/tt/parse/full-schedule.js +190 -0
  13. package/dist/tt/parse/groups.d.ts +15 -0
  14. package/dist/tt/parse/groups.js +28 -0
  15. package/dist/tt/parse/index.d.ts +5 -0
  16. package/dist/tt/parse/index.js +5 -0
  17. package/dist/tt/parse/lists.d.ts +11 -0
  18. package/dist/tt/parse/lists.js +80 -0
  19. package/dist/tt/parse/overlays.d.ts +10 -0
  20. package/dist/tt/parse/overlays.js +104 -0
  21. package/dist/tt/parse/teacher.d.ts +4 -0
  22. package/dist/tt/parse/teacher.js +166 -0
  23. package/dist/tt/schedule.d.ts +1 -2
  24. package/dist/tt/schedule.js +2 -8
  25. package/dist/tt/types.d.ts +9 -4
  26. package/dist/tt/utils/date.d.ts +5 -0
  27. package/dist/tt/utils/date.js +27 -0
  28. package/dist/tt/{utils.d.ts → utils/holidays.d.ts} +5 -55
  29. package/dist/tt/utils/holidays.js +197 -0
  30. package/dist/tt/utils/index.d.ts +7 -0
  31. package/dist/tt/utils/index.js +6 -0
  32. package/dist/tt/utils/lessons.d.ts +14 -0
  33. package/dist/tt/utils/lessons.js +123 -0
  34. package/dist/tt/utils/period.d.ts +6 -0
  35. package/dist/tt/utils/period.js +24 -0
  36. package/dist/tt/utils/semester.d.ts +25 -0
  37. package/dist/tt/utils/semester.js +47 -0
  38. package/dist/tt/utils/time-slots.d.ts +5 -0
  39. package/dist/tt/utils/time-slots.js +41 -0
  40. package/package.json +1 -1
  41. package/dist/tt/parse.d.ts +0 -16
  42. package/dist/tt/parse.js +0 -671
  43. package/dist/tt/utils.js +0 -521
package/dist/tt/utils.js DELETED
@@ -1,521 +0,0 @@
1
- export function getCurrentPeriod(opts) {
2
- const date = opts?.date ?? new Date();
3
- const month = date.getMonth();
4
- const day = date.getDate();
5
- // Dec 25+ and Jan → Winter session (зимняя сессия)
6
- if (month === 0 || (month === 11 && day >= 25))
7
- return 2 /* Period.WinterSession */;
8
- // Feb–May → Spring semester (весенний семестр)
9
- if (month >= 1 && month <= 4)
10
- return 3 /* Period.SpringSemester */;
11
- // Jun–Aug → Summer session (летняя сессия)
12
- if (month >= 5 && month <= 7)
13
- return 4 /* Period.SummerSession */;
14
- // Sep – Dec 24 → Fall semester (осенний семестр)
15
- return 1 /* Period.FallSemester */;
16
- }
17
- export function isSessionPeriod(period) {
18
- return period === 2 /* Period.WinterSession */ || period === 4 /* Period.SummerSession */;
19
- }
20
- export function sortLessons(a, b) {
21
- return a.start.date.getTime() - b.start.date.getTime();
22
- }
23
- const WEEKDAY_NAMES = [
24
- "Воскресенье",
25
- "Понедельник",
26
- "Вторник",
27
- "Среда",
28
- "Четверг",
29
- "Пятница",
30
- "Суббота",
31
- ];
32
- export function getWeekdayName(weekday) {
33
- return WEEKDAY_NAMES[weekday] ?? "";
34
- }
35
- export function getMonday(date) {
36
- const d = new Date(date);
37
- const day = d.getDay();
38
- const diff = day === 0 ? -6 : 1 - day;
39
- d.setDate(d.getDate() + diff);
40
- d.setHours(0, 0, 0, 0);
41
- return d;
42
- }
43
- /**
44
- * Get the start date of a semester.
45
- * Fall: September 1 of the given year.
46
- * Spring: first Monday of February of the given year.
47
- */
48
- export function getSemesterStart(opts) {
49
- const year = opts.year ?? new Date().getFullYear();
50
- if (opts.period === 1 /* Period.FallSemester */) {
51
- return new Date(year, 8, 1); // September 1
52
- }
53
- // Spring: first Monday of February
54
- const feb1 = new Date(year, 1, 1);
55
- const day = feb1.getDay();
56
- const daysToAdd = day === 1 ? 0 : day === 0 ? 1 : 8 - day;
57
- const firstMonday = new Date(year, 1, 1 + daysToAdd);
58
- firstMonday.setHours(0, 0, 0, 0);
59
- return firstMonday;
60
- }
61
- /**
62
- * Get all weeks in a semester with their start/end dates.
63
- * Week 0 starts from the semester start date.
64
- */
65
- export function getSemesterWeeks(opts) {
66
- const weekCount = opts.weekCount ?? 17;
67
- const semesterStart = getSemesterStart(opts);
68
- const startMonday = getMonday(semesterStart);
69
- const weeks = [];
70
- for (let i = 0; i <= weekCount; i++) {
71
- const start = new Date(startMonday);
72
- start.setDate(startMonday.getDate() + i * 7);
73
- const end = new Date(start);
74
- end.setDate(start.getDate() + 6);
75
- end.setHours(23, 59, 59, 999);
76
- weeks.push({ week: i, start, end });
77
- }
78
- return weeks;
79
- }
80
- /**
81
- * Get the current week number within a semester.
82
- */
83
- export function getWeekNumber(opts) {
84
- const date = opts.date ?? new Date();
85
- const semesterStart = getSemesterStart(opts);
86
- const startMonday = getMonday(semesterStart);
87
- const targetMonday = getMonday(date);
88
- const diff = targetMonday.getTime() - startMonday.getTime();
89
- return Math.floor(diff / (7 * 24 * 60 * 60 * 1000));
90
- }
91
- function isSameDay(a, b) {
92
- return (a.getFullYear() === b.getFullYear() &&
93
- a.getMonth() === b.getMonth() &&
94
- a.getDate() === b.getDate());
95
- }
96
- function filterEntries(entries, opts) {
97
- return entries.filter((e) => {
98
- // Subgroup filter applies to all entry types
99
- if (opts?.subgroup && e.subgroup && e.subgroup !== opts.subgroup) {
100
- return false;
101
- }
102
- // Transfer entries: only include when the query date matches the target date
103
- if (e.transfer) {
104
- if (!opts?.date)
105
- return false;
106
- return isSameDay(e.transfer.targetDate, opts.date);
107
- }
108
- // Substitute-for entries: only include when the query date matches
109
- if (e.substituteFor) {
110
- if (!opts?.date)
111
- return false;
112
- return isSameDay(e.substituteFor.date, opts.date);
113
- }
114
- if (opts?.week != null) {
115
- if (e.weeks.from > 0 &&
116
- (opts.week < e.weeks.from || opts.week > e.weeks.to)) {
117
- return false;
118
- }
119
- if (e.weekParity) {
120
- const isEven = opts.week % 2 === 0;
121
- if (e.weekParity === "even" && !isEven)
122
- return false;
123
- if (e.weekParity === "odd" && isEven)
124
- return false;
125
- }
126
- }
127
- return true;
128
- });
129
- }
130
- export function filterSlots(slots, opts) {
131
- if (opts?.subgroup == null && opts?.week == null && opts?.date == null)
132
- return slots;
133
- return slots
134
- .map((slot) => ({
135
- ...slot,
136
- entries: filterEntries(slot.entries, opts),
137
- }))
138
- .filter((slot) => slot.entries.length > 0);
139
- }
140
- function makeLessonTime(date, time) {
141
- const d = new Date(date);
142
- d.setHours(time.hours, time.minutes, 0, 0);
143
- return { date: d, hours: time.hours, minutes: time.minutes };
144
- }
145
- export function slotsToLessons(slots, date, opts) {
146
- const lessons = [];
147
- for (const slot of slots) {
148
- for (const entry of slot.entries) {
149
- let room = entry.room;
150
- let teacher = entry.teacher;
151
- let originalRoom;
152
- let originalTeacher;
153
- // Apply date-specific substitutions
154
- if (entry.substitutions) {
155
- const sub = entry.substitutions.find((s) => isSameDay(s.date, date));
156
- if (sub) {
157
- if (sub.room) {
158
- originalRoom = room;
159
- room = sub.room;
160
- }
161
- if (sub.teacher) {
162
- // On teacher schedules, a teacher substitution means another teacher
163
- // is taking over — exclude the lesson entirely.
164
- if (opts?.isTeacherSchedule)
165
- continue;
166
- originalTeacher = teacher;
167
- teacher = sub.teacher;
168
- }
169
- }
170
- }
171
- lessons.push({
172
- number: slot.number,
173
- start: makeLessonTime(date, slot.timeStart),
174
- end: makeLessonTime(date, slot.timeEnd),
175
- subject: entry.subject,
176
- type: entry.type,
177
- room,
178
- teacher,
179
- groups: entry.groups,
180
- weeks: entry.weeks,
181
- subgroup: entry.subgroup,
182
- weekParity: entry.weekParity,
183
- originalRoom,
184
- originalTeacher,
185
- transfer: entry.transfer,
186
- substituteFor: entry.substituteFor,
187
- possibleChanges: entry.possibleChanges,
188
- });
189
- }
190
- }
191
- return lessons;
192
- }
193
- /** Collect all transfer entries from schedule days. */
194
- export function collectTransfers(days) {
195
- const transfers = [];
196
- for (const day of days) {
197
- for (const slot of day.slots) {
198
- for (const entry of slot.entries) {
199
- if (entry.transfer)
200
- transfers.push(entry);
201
- }
202
- }
203
- }
204
- return transfers;
205
- }
206
- /** Remove lessons whose source date/slot match a transfer. */
207
- export function suppressTransferredLessons(lessons, transfers, date) {
208
- return lessons.filter((lesson) => {
209
- return !transfers.some((t) => t.transfer &&
210
- isSameDay(t.transfer.fromDate, date) &&
211
- t.transfer.fromSlot === lesson.number &&
212
- t.transfer.subject === lesson.subject);
213
- });
214
- }
215
- // --- Lesson time slots ---
216
- const VO_TIME_SLOTS = [
217
- {
218
- number: 1,
219
- start: { hours: 8, minutes: 20 },
220
- end: { hours: 9, minutes: 40 },
221
- },
222
- {
223
- number: 2,
224
- start: { hours: 9, minutes: 50 },
225
- end: { hours: 11, minutes: 10 },
226
- },
227
- {
228
- number: 3,
229
- start: { hours: 11, minutes: 40 },
230
- end: { hours: 13, minutes: 0 },
231
- },
232
- {
233
- number: 4,
234
- start: { hours: 13, minutes: 30 },
235
- end: { hours: 14, minutes: 50 },
236
- },
237
- {
238
- number: 5,
239
- start: { hours: 15, minutes: 0 },
240
- end: { hours: 16, minutes: 20 },
241
- },
242
- {
243
- number: 6,
244
- start: { hours: 16, minutes: 40 },
245
- end: { hours: 18, minutes: 0 },
246
- },
247
- {
248
- number: 7,
249
- start: { hours: 18, minutes: 10 },
250
- end: { hours: 19, minutes: 30 },
251
- },
252
- {
253
- number: 8,
254
- start: { hours: 19, minutes: 40 },
255
- end: { hours: 21, minutes: 0 },
256
- },
257
- ];
258
- const SPO_TIME_SLOTS = [
259
- {
260
- number: 1,
261
- start: { hours: 8, minutes: 10 },
262
- end: { hours: 9, minutes: 40 },
263
- },
264
- {
265
- number: 2,
266
- start: { hours: 9, minutes: 55 },
267
- end: { hours: 11, minutes: 25 },
268
- },
269
- {
270
- number: 3,
271
- start: { hours: 11, minutes: 55 },
272
- end: { hours: 13, minutes: 25 },
273
- },
274
- {
275
- number: 4,
276
- start: { hours: 13, minutes: 40 },
277
- end: { hours: 15, minutes: 10 },
278
- },
279
- {
280
- number: 5,
281
- start: { hours: 15, minutes: 25 },
282
- end: { hours: 16, minutes: 55 },
283
- },
284
- {
285
- number: 6,
286
- start: { hours: 17, minutes: 10 },
287
- end: { hours: 18, minutes: 40 },
288
- },
289
- {
290
- number: 7,
291
- start: { hours: 18, minutes: 55 },
292
- end: { hours: 20, minutes: 25 },
293
- },
294
- ];
295
- export function getTimeSlots(educationType) {
296
- return educationType === 2 /* EducationType.VocationalEducation */
297
- ? SPO_TIME_SLOTS
298
- : VO_TIME_SLOTS;
299
- }
300
- function timeToMinutes(t) {
301
- return t.hours * 60 + t.minutes;
302
- }
303
- export function getLessonNumber(time, educationType) {
304
- const slots = getTimeSlots(educationType);
305
- const target = timeToMinutes(time);
306
- let closest = slots[0];
307
- let minDiff = Math.abs(timeToMinutes(closest.start) - target);
308
- for (let i = 1; i < slots.length; i++) {
309
- const diff = Math.abs(timeToMinutes(slots[i].start) - target);
310
- if (diff < minDiff) {
311
- minDiff = diff;
312
- closest = slots[i];
313
- }
314
- }
315
- return closest.number;
316
- }
317
- export function getAdjacentSemester(session) {
318
- return session === 2 /* Period.WinterSession */
319
- ? 1 /* Period.FallSemester */
320
- : 3 /* Period.SpringSemester */;
321
- }
322
- /** Russian non-working public holidays (Статья 112 ТК РФ). */
323
- export const RUSSIAN_HOLIDAYS = [
324
- { month: 1, day: 1, name: "Новый год" },
325
- { month: 1, day: 2, name: "Новогодние каникулы" },
326
- { month: 1, day: 3, name: "Новогодние каникулы" },
327
- { month: 1, day: 4, name: "Новогодние каникулы" },
328
- { month: 1, day: 5, name: "Новогодние каникулы" },
329
- { month: 1, day: 6, name: "Новогодние каникулы" },
330
- { month: 1, day: 7, name: "Рождество Христово" },
331
- { month: 1, day: 8, name: "Новогодние каникулы" },
332
- { month: 2, day: 23, name: "День защитника Отечества" },
333
- { month: 3, day: 8, name: "Международный женский день" },
334
- { month: 5, day: 1, name: "Праздник Весны и Труда" },
335
- { month: 5, day: 9, name: "День Победы" },
336
- { month: 6, day: 12, name: "День России" },
337
- { month: 11, day: 4, name: "День народного единства" },
338
- ];
339
- /**
340
- * January holiday dates (1–8) are excluded from automatic weekend transfer
341
- * per Art. 112 ТК РФ. Their transfers are decided by government decree.
342
- */
343
- function isJanuaryHoliday(h) {
344
- return h.month === 1 && h.day >= 1 && h.day <= 8;
345
- }
346
- /**
347
- * Compute bridge-day transfers for non-January holidays.
348
- *
349
- * Pattern (consistent across government decrees):
350
- * - Holiday on Tuesday → Monday becomes day off, preceding Saturday is work day
351
- * - Holiday on Thursday → Friday becomes day off, following Saturday is work day
352
- * (only for 5-day week; 6-day week has Saturday classes, so no gap to bridge)
353
- *
354
- * January holidays are excluded (their 2 transfers are unpredictable).
355
- */
356
- function computeBridgeDays(year, holidays, effectiveDays, sixDayWeek) {
357
- const bridges = [];
358
- for (const h of holidays) {
359
- if (isJanuaryHoliday(h))
360
- continue;
361
- const date = new Date(year, h.month - 1, h.day);
362
- const dow = date.getDay();
363
- if (dow === 2) {
364
- // Tuesday → Monday off, preceding Saturday works
365
- const monday = new Date(date);
366
- monday.setDate(date.getDate() - 1);
367
- const saturday = new Date(date);
368
- saturday.setDate(date.getDate() - 3);
369
- if (!effectiveDays.some((d) => isSameDay(d, monday))) {
370
- bridges.push({
371
- dayOff: monday,
372
- workDay: sixDayWeek ? null : saturday,
373
- });
374
- }
375
- }
376
- else if (dow === 4 && !sixDayWeek) {
377
- // Thursday → Friday off, following Saturday works
378
- // Only for 5-day week: 6-day week has no gap (Saturday is a work day)
379
- const friday = new Date(date);
380
- friday.setDate(date.getDate() + 1);
381
- const saturday = new Date(date);
382
- saturday.setDate(date.getDate() + 2);
383
- if (!effectiveDays.some((d) => isSameDay(d, friday))) {
384
- bridges.push({ dayOff: friday, workDay: saturday });
385
- }
386
- }
387
- }
388
- return bridges;
389
- }
390
- /**
391
- * Compute effective non-working holiday dates for a given year.
392
- *
393
- * Rules (Art. 112 ТК РФ):
394
- * 1. All holidays in the list are non-working days.
395
- * 2. For non-January holidays: if a holiday falls on Sat/Sun, the day off
396
- * automatically transfers to the next working day.
397
- * 3. For January holidays (1–8): weekend transfers are NOT automatic —
398
- * they are decided by annual government decree. Pass them via `transfers`.
399
- * 4. Bridge days: if a non-January holiday falls on Tue, Mon is day off
400
- * (preceding Sat works); if on Thu, Fri is day off (following Sat works).
401
- * Computed automatically, can be overridden via `transfers`.
402
- * 5. Government decree transfers (`transfers`) add extra days off and
403
- * override auto-computed bridge days.
404
- * 6. For 6-day week (`sixDayWeek`): Saturday is a work day, so
405
- * Thursday bridges don't apply and Saturday holidays don't auto-transfer.
406
- */
407
- export function getEffectiveHolidays(year, holidays = RUSSIAN_HOLIDAYS, transfers = [], sixDayWeek = true) {
408
- const originalDates = holidays.map((h) => new Date(year, h.month - 1, h.day));
409
- const isOriginalHoliday = (d) => originalDates.some((od) => isSameDay(od, d));
410
- const effectiveDays = [...originalDates];
411
- // Auto-transfer: only non-January holidays that fall on weekends
412
- const nonJanuaryOnWeekend = holidays
413
- .filter((h) => !isJanuaryHoliday(h))
414
- .map((h) => new Date(year, h.month - 1, h.day))
415
- .filter((d) => d.getDay() === 0 || d.getDay() === 6)
416
- .sort((a, b) => a.getTime() - b.getTime());
417
- for (const holiday of nonJanuaryOnWeekend) {
418
- let candidate = new Date(holiday);
419
- if (candidate.getDay() === 6) {
420
- candidate.setDate(candidate.getDate() + 2);
421
- }
422
- else {
423
- candidate.setDate(candidate.getDate() + 1);
424
- }
425
- while (candidate.getDay() === 0 ||
426
- candidate.getDay() === 6 ||
427
- isOriginalHoliday(candidate) ||
428
- effectiveDays.some((ed) => isSameDay(ed, candidate))) {
429
- candidate.setDate(candidate.getDate() + 1);
430
- }
431
- effectiveDays.push(new Date(candidate));
432
- }
433
- // Bridge days (auto-computed, can be overridden)
434
- const autoBridges = computeBridgeDays(year, holidays, effectiveDays, sixDayWeek);
435
- // Merge: explicit transfers override auto-computed bridges
436
- const allTransfers = [...autoBridges];
437
- for (const t of transfers) {
438
- const idx = allTransfers.findIndex((b) => isSameDay(b.dayOff, t.dayOff));
439
- if (idx !== -1) {
440
- allTransfers[idx] = t;
441
- }
442
- else {
443
- allTransfers.push(t);
444
- }
445
- }
446
- const compensatingWorkDays = [];
447
- for (const t of allTransfers) {
448
- if (!effectiveDays.some((d) => isSameDay(d, t.dayOff))) {
449
- effectiveDays.push(t.dayOff);
450
- }
451
- if (t.workDay) {
452
- compensatingWorkDays.push(t.workDay);
453
- }
454
- }
455
- return effectiveDays.sort((a, b) => a.getTime() - b.getTime());
456
- }
457
- /**
458
- * Compute all transfers (auto bridges + explicit) for a given year.
459
- * Useful for getting compensating work days (Saturdays that become working).
460
- */
461
- export function getHolidayTransfers(year, holidays = RUSSIAN_HOLIDAYS, transfers = [], sixDayWeek) {
462
- const originalDates = holidays.map((h) => new Date(year, h.month - 1, h.day));
463
- const effectiveDays = [...originalDates];
464
- // Replay weekend transfers to build effectiveDays for bridge computation
465
- const nonJanuaryOnWeekend = holidays
466
- .filter((h) => !isJanuaryHoliday(h))
467
- .map((h) => new Date(year, h.month - 1, h.day))
468
- .filter((d) => d.getDay() === 0 || d.getDay() === 6)
469
- .sort((a, b) => a.getTime() - b.getTime());
470
- const isOriginalHoliday = (d) => originalDates.some((od) => isSameDay(od, d));
471
- for (const holiday of nonJanuaryOnWeekend) {
472
- let candidate = new Date(holiday);
473
- if (candidate.getDay() === 6) {
474
- candidate.setDate(candidate.getDate() + 2);
475
- }
476
- else {
477
- candidate.setDate(candidate.getDate() + 1);
478
- }
479
- while (candidate.getDay() === 0 ||
480
- candidate.getDay() === 6 ||
481
- isOriginalHoliday(candidate) ||
482
- effectiveDays.some((ed) => isSameDay(ed, candidate))) {
483
- candidate.setDate(candidate.getDate() + 1);
484
- }
485
- effectiveDays.push(new Date(candidate));
486
- }
487
- const autoBridges = computeBridgeDays(year, holidays, effectiveDays, sixDayWeek);
488
- const allTransfers = [...autoBridges];
489
- for (const t of transfers) {
490
- const idx = allTransfers.findIndex((b) => isSameDay(b.dayOff, t.dayOff));
491
- if (idx !== -1) {
492
- allTransfers[idx] = t;
493
- }
494
- else {
495
- allTransfers.push(t);
496
- }
497
- }
498
- return allTransfers;
499
- }
500
- /**
501
- * Returns the list of compensating work days (e.g. Saturdays that become working).
502
- * Includes both auto-computed bridge compensations and explicit transfers.
503
- */
504
- export function getCompensatingWorkDays(year, holidays = RUSSIAN_HOLIDAYS, transfers = [], sixDayWeek) {
505
- return getHolidayTransfers(year, holidays, transfers, sixDayWeek)
506
- .filter((t) => t.workDay != null)
507
- .map((t) => t.workDay);
508
- }
509
- /**
510
- * Returns true if the given date is a non-working holiday,
511
- * including transferred holidays when they fall on weekends (Art. 112 ТК РФ)
512
- * and auto-computed bridge days.
513
- * Pass an empty array for `holidays` to disable holiday checking.
514
- */
515
- export function isHoliday(date, holidays = RUSSIAN_HOLIDAYS, transfers = [], sixDayWeek = true) {
516
- if (holidays.length === 0 && transfers.length === 0)
517
- return false;
518
- const year = date.getFullYear();
519
- const effectiveDays = getEffectiveHolidays(year, holidays, transfers, sixDayWeek);
520
- return effectiveDays.some((d) => isSameDay(d, date));
521
- }