• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

statuscompliance / status-backend / 14592205578

22 Apr 2025 10:08AM UTC coverage: 45.53% (+9.6%) from 35.962%
14592205578

Pull #90

github

web-flow
Merge ac2816879 into ef0c8590e
Pull Request #90: test(utils): added dates

315 of 808 branches covered (38.99%)

Branch coverage included in aggregate %.

96 of 115 new or added lines in 1 file covered. (83.48%)

130 existing lines in 3 files now uncovered.

831 of 1709 relevant lines covered (48.62%)

5.31 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

77.78
/src/utils/dates.js
1
import { addYears, addMonths, addWeeks, addDays, parseISO, isWithinInterval, add, setSeconds, isSameDay, getDay, getDaysInMonth, getYear, getMonth, isValid } from 'date-fns';
2

3

4
export const periodTypes = {
17✔
5
  yearly: addYears,
6
  monthly: addMonths,
7
  weekly: addWeeks,
8
  daily: addDays,
9
  hourly: (date) => new Date(date.setHours(date.getHours() + 1)),
3✔
10
};
11

12
export function getDates(from, to, period, customConfig) {
13

14
  if (!isValid(from)) {
16✔
15
    console.error("Invalid 'from' date provided.");
1✔
16
    return [];
1✔
17
  }
18

19
  if (!isValid(to)) {
15✔
20
    console.error("Invalid 'to' date provided.");
1✔
21
    return [];
1✔
22
  }
23
  if (period in periodTypes) {
14✔
24
    const periodFunction = periodTypes[period];
11✔
25
    let current = new Date(from);
11✔
26
    const dates = [];
11✔
27
    try {
11✔
28
      while (current <= to) {
11✔
29
        dates.push(new Date(current));
20✔
30
        current = periodFunction(current, 1);
20✔
31
      }
32
      return dates;
11✔
33
    } catch (error) {
NEW
34
      console.error(`Error generating dates for period ${period}:`, error);
×
NEW
35
      return [];
×
36
    }
37
  }
38

39
  if (period === 'customRules') {
3✔
40
    if (!customConfig?.rules || !customConfig?.Wto) {
2!
41
      console.error("Incomplete custom rules configuration: 'rules' and 'Wto' are required.");
2✔
42
      return [];
2✔
43
    }
NEW
44
    try {
×
NEW
45
      let rulesArr = customConfig.rules.split('---');
×
NEW
46
      let untilDate = `;UNTIL=${customConfig.Wto.toISOString().replace(/[.\-:]/g, '').substring(0, 15)}Z`;
×
NEW
47
      rulesArr = rulesArr.map(rule => rule + untilDate);
×
NEW
48
      return generateDatesFromRules(rulesArr, from, to);
×
49
    } catch (error) {
NEW
50
      console.error('Error generating dates with custom rules:', error);
×
NEW
51
      return [];
×
52
    }
53
  }
54

55
  console.error(`Invalid period type: ${period}`);
1✔
56
  return [];
1✔
57
}
58

59
export function generateDatesFromRules(rulesArr, from, to) {
60

61
  const generatedDates = [];
3✔
62

63
  rulesArr.forEach(rule => {
3✔
64
    const ruleData = parseRule(rule);
3✔
65
    if (ruleData) {
3✔
66
      generatedDates.push(...generateDatesForFrequency(ruleData, from, to));
2✔
67
    }
68
  });
69

70
  return generatedDates;
3✔
71
}
72

73
export function parseRule(rule) {
74
  const dtstartMatch = rule.match(/DTSTART:(\d+T\d+)/);
10✔
75
  const rruleMatch = rule.match(/RRULE:(.+)/);
10✔
76

77
  if (!dtstartMatch || !rruleMatch) {
10✔
78
    return null;
3✔
79
  }
80

81
  const startDate = parseISO(dtstartMatch[1]);
7✔
82
  const rruleParts = rruleMatch[1].split(';');
7✔
83
  const frequency = rruleParts.find(part => part.startsWith('FREQ='))?.split('=')[1];
7✔
84
  const interval = parseInt(rruleParts.find(part => part.startsWith('INTERVAL='))?.split('=')[1] || '1');
14✔
85
  const byHour = rruleParts.find(part => part.startsWith('BYHOUR='))?.split('=')[1]?.split(',').map(Number) || [0];
15✔
86
  const until = rruleParts.find(part => part.startsWith('UNTIL='))?.split('=')[1];
16✔
87
  const byDay = rruleParts.find(part => part.startsWith('BYDAY='))?.split('=')[1]?.split(',');
16✔
88
  const byMonthDay = rruleParts.find(part => part.startsWith('BYMONTHDAY='))?.split('=')[1];
16✔
89
  const byMonth = rruleParts.find(part => part.startsWith('BYMONTH='))?.split('=')[1];
15✔
90

91
  return {
7✔
92
    startDate,
93
    frequency,
94
    interval,
95
    byHour,
96
    until,
97
    byDay,
98
    byMonthDay: byMonthDay ? parseInt(byMonthDay, 10) : undefined,
7✔
99
    byMonth: byMonth ? parseInt(byMonth, 10) - 1 : undefined, // Month in date-fns is 0-indexed
7✔
100
  };
101
}
102

103
//
104
export function generateDatesForFrequency(ruleData, from, to) {
105
  const generatedDates = [];
7✔
106
  let currentDate = new Date(ruleData.startDate);
7✔
107
  const untilDate = ruleData.until ? parseISO(ruleData.until) : to;
7✔
108
  const byHour = Array.isArray(ruleData.byHour) ? ruleData.byHour : [0];
7✔
109

110
  if (ruleData.frequency === 'WEEKLY' && ruleData.byDay) {
7!
NEW
111
    currentDate = adjustToFirstWeeklyOccurrence(currentDate, ruleData.byDay);
×
112
  }
113

114
  while (isWithinInterval(currentDate, { start: from, end: untilDate }) || isSameDay(currentDate, untilDate)) {
7✔
115
    generateDatesForDay(currentDate, byHour, from, to, generatedDates);
14✔
116
    currentDate = advanceToNextDate(currentDate, ruleData);
14✔
117
    if (!currentDate) break;
14✔
118
  }
119

120
  return generatedDates.sort((a, b) => a.getTime() - b.getTime());
7✔
121

122
}
123

124
function adjustToFirstWeeklyOccurrence(currentDate, byDay) {
NEW
125
  const firstOccurrence = findNextWeeklyDate(new Date(add(currentDate, { days: -7 })), byDay, 1);
×
NEW
126
  if (firstOccurrence && firstOccurrence >= currentDate) {
×
NEW
127
    return firstOccurrence;
×
NEW
128
  } else if (firstOccurrence && firstOccurrence < currentDate) {
×
NEW
129
    return findNextWeeklyDate(new Date(currentDate), byDay, 1);
×
130
  }
NEW
131
  return currentDate;
×
132
}
133

134
function generateDatesForDay(currentDate, byHour, from, to, generatedDates) {
135
  byHour.forEach(hour => {
14✔
136
    const dateWithHour = adjustDateToHour(new Date(currentDate), hour);
14✔
137
    if (isDateWithinRange(dateWithHour, from, to)) {
14✔
138
      generatedDates.push(dateWithHour);
13✔
139
    }
140
  });
141
}
142

143
function advanceToNextDate(currentDate, ruleData) {
144
  if (ruleData.frequency === 'DAILY') {
14✔
145
    return add(currentDate, { days: ruleData.interval || 1 });
13!
146
  } else if (ruleData.frequency === 'WEEKLY' && ruleData.byDay) {
1!
NEW
147
    return findNextWeeklyDate(new Date(currentDate), ruleData.byDay, ruleData.interval || 1);
×
148
  } else if (ruleData.frequency === 'MONTHLY' && ruleData.byMonthDay !== undefined) {
1!
NEW
149
    return advanceToMonthlyDate(new Date(currentDate), ruleData.byMonthDay, ruleData.interval || 1);
×
150
  } else if (ruleData.frequency === 'YEARLY' && ruleData.byMonth !== undefined && ruleData.byMonthDay !== undefined) {
1!
NEW
151
    return advanceToYearlyDate(new Date(currentDate), ruleData.byMonth, ruleData.byMonthDay, ruleData.interval || 1);
×
152
  } else {
153
    console.warn(`Unsupported frequency or missing parameters: ${ruleData.frequency}`);
1✔
154
    return null;
1✔
155
  }
156
}
157

158
//
159
export function adjustDateToHour(date, hour) {
160
  const dateWithHour = setSeconds(new Date(date), 0);
17✔
161
  dateWithHour.setHours(hour);
17✔
162
  return dateWithHour;
17✔
163
}
164

165
export function isDateWithinRange(date, from, to) {
166
  return isWithinInterval(date, { start: from, end: to }) || isSameDay(date, to);
22✔
167
}
168

169
export function findNextWeeklyDate(currentDate, byDay, interval) {
170
  let foundDay = false;
6✔
171
  let nextDate = new Date(currentDate);
6✔
172
  for (let i = 1; i <= 7; i++) {
6✔
173
    const potentialDate = add(currentDate, { days: i });
29✔
174
    const dayOfWeek = ['SU', 'MO', 'TU', 'WE', 'TH', 'FR', 'SA'][getDay(potentialDate)];
29✔
175
    if (byDay.includes(dayOfWeek)) {
29✔
176
      nextDate = potentialDate;
5✔
177
      foundDay = true;
5✔
178
      break;
5✔
179
    }
180
  }
181

182
  if (foundDay) {
6✔
183
    const currentDayIndex = getDay(currentDate);
5✔
184
    const nextDayIndex = getDay(nextDate);
5✔
185
    // Calculate the number of days to add for the interval
186
    let daysToAdd = (interval - 1) * 7;
5✔
187
    if (nextDayIndex <= currentDayIndex) {
5✔
188
      daysToAdd += (7 - currentDayIndex + nextDayIndex);
3✔
189
    } else {
190
      daysToAdd += (nextDayIndex - currentDayIndex);
2✔
191
    }
192
    const finalNextDate = add(currentDate, { days: daysToAdd });
5✔
193
    return finalNextDate;
5✔
194
  }
195
  return null;
1✔
196
}
197

198
export function advanceToMonthlyDate(currentDate, byMonthDay, interval) {
199
  const currentYear = getYear(currentDate);
5✔
200
  const currentMonth = getMonth(currentDate);
5✔
201
  const nextMonth = currentMonth + interval;
5✔
202
  const nextYearBasedOnMonth = currentYear + Math.floor(nextMonth / 12);
5✔
203
  const finalMonth = nextMonth % 12;
5✔
204
  const daysInNextMonth = getDaysInMonth(new Date(nextYearBasedOnMonth, finalMonth));
5✔
205
  const dayOfMonth = Math.min(byMonthDay, daysInNextMonth);
5✔
206
  return new Date(nextYearBasedOnMonth, finalMonth, dayOfMonth, currentDate.getHours(), currentDate.getMinutes(), currentDate.getSeconds());
5✔
207
}
208

209
export function advanceToYearlyDate(currentDate, byMonth, byMonthDay, interval) {
210
  const nextYear = getYear(currentDate) + interval;
4✔
211
  const monthIndex = byMonth - 1;
4✔
212
  const daysInMonth = getDaysInMonth(new Date(nextYear, monthIndex));
4✔
213
  const dayOfMonth = Math.min(byMonthDay, daysInMonth);
4✔
214
  return new Date(nextYear, monthIndex, dayOfMonth, currentDate.getHours(), currentDate.getMinutes(), currentDate.getSeconds());
4✔
215
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc