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

pglejzer / timepicker-ui / 21549519772

31 Jan 2026 07:14PM UTC coverage: 80.798%. First build
21549519772

Pull #110

github

web-flow
Merge f50571e65 into fcb4cf379
Pull Request #110: Upgrade/new options

2137 of 2953 branches covered (72.37%)

Branch coverage included in aggregate %.

753 of 823 new or added lines in 33 files covered. (91.49%)

2807 of 3166 relevant lines covered (88.66%)

32.08 hits per line

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

90.86
/app/src/managers/clock/engine/HourEngine.ts
1
import type { ClockType, AmPmType, DisabledTimeConfig } from '../types';
2

10✔
3
export class HourEngine {
10✔
4
  static angleToIndex(angle: number, clockType: ClockType, isInnerCircle: boolean): number {
5
    const baseIndex = Math.round(angle / 30) % 12;
6

17✔
7
    if (clockType === '24h') {
17✔
8
      if (isInnerCircle) {
8✔
9
        const hour = baseIndex + 12;
5✔
10
        return hour === 12 ? 0 : hour;
5✔
11
      } else {
12
        return baseIndex === 0 ? 12 : baseIndex;
13
      }
3✔
14
    }
15

16
    return baseIndex === 0 ? 12 : baseIndex;
9✔
17
  }
18

19
  static indexToValue(index: number, clockType: ClockType, amPm: AmPmType): string {
27✔
20
    if (clockType === '24h') {
9✔
21
      return index.toString().padStart(2, '0');
22
    }
18✔
23

18✔
24
    let hour = index;
1✔
25
    if (hour === 0) hour = 12;
18!
26
    if (hour > 12) hour = hour - 12;
×
27

18✔
28
    return hour.toString().padStart(2, '0');
29
  }
30

25✔
31
  static indexToAngle(index: number, clockType: ClockType): number {
7✔
32
    if (clockType === '24h') {
4✔
33
      if (index >= 12) {
34
        return ((index - 12) % 12) * 30;
35
      } else {
3✔
36
        return (index % 12) * 30;
37
      }
38
    }
18✔
39

18✔
40
    const hour = index === 0 ? 12 : index;
41
    return (hour % 12) * 30;
42
  }
72✔
43

8✔
44
  static isDisabled(value: string, amPm: AmPmType, disabledTime: DisabledTimeConfig | null): boolean {
64✔
45
    if (!disabledTime) return false;
4✔
46

47
    if (disabledTime.isInterval && disabledTime.intervals && disabledTime.clockType) {
60✔
48
      return this.isDisabledByInterval(value, amPm, disabledTime);
10✔
49
    }
50

50✔
51
    if (disabledTime.rangeFromType !== undefined && disabledTime.rangeFromHour !== undefined) {
72✔
52
      return this.isDisabledForRange12h(value, amPm, disabledTime);
53
    }
12✔
54

55
    if (disabledTime.hours) {
56
      return disabledTime.hours.some(
10✔
57
        (h) => String(h) === value || Number(h) === Number(value) || h === value,
10✔
58
      );
10✔
59
    }
10✔
60

1✔
61
    return false;
9✔
62
  }
1✔
63

64
  private static isDisabledForRange12h(
8✔
65
    value: string,
4✔
66
    currentAmPm: AmPmType,
1✔
67
    disabledTime: DisabledTimeConfig,
68
  ): boolean {
3✔
69
    const fromType = disabledTime.rangeFromType;
1✔
70
    const fromHour = disabledTime.rangeFromHour;
71
    const toHour = parseInt(value, 10);
2✔
72

73
    if (fromType === null || fromHour === undefined) return false;
4✔
74

1✔
75
    if (currentAmPm === 'AM' && fromType === 'PM') {
76
      return true;
3!
77
    }
3✔
78

1✔
79
    if (currentAmPm === 'AM' && fromType === 'AM') {
80
      if (fromHour === 12) {
2✔
81
        return false;
1✔
82
      }
83
      if (toHour === 12) {
1✔
84
        return true;
NEW
85
      }
×
86
      return toHour < fromHour;
87
    }
88

4!
NEW
89
    if (currentAmPm === 'PM' && fromType === 'AM') {
×
90
      return false;
4✔
91
    }
153✔
92

153✔
93
    if (currentAmPm === 'PM' && fromType === 'PM') {
2✔
94
      if (fromHour === 12) {
95
        return false;
96
      }
2✔
97
      if (toHour === 12) {
98
        return true;
99
      }
153✔
100
      return toHour < fromHour;
153✔
101
    }
306✔
102

153✔
103
    return false;
151✔
104
  }
105

106
  private static isDisabledByInterval(
2✔
107
    hour: string,
108
    amPm: AmPmType,
109
    disabledTime: DisabledTimeConfig,
153✔
110
  ): boolean {
153✔
111
    if (!disabledTime.intervals) return false;
153✔
112

153✔
113
    for (let minute = 0; minute < 60; minute++) {
114
      const minuteStr = minute.toString().padStart(2, '0');
115
      if (!this.isTimeInIntervals(hour, minuteStr, amPm, disabledTime.intervals, disabledTime.clockType!)) {
459✔
116
        return false;
279✔
117
      }
279!
118
    }
×
119

279✔
120
    return true;
279✔
121
  }
279✔
122

279!
123
  private static isTimeInIntervals(
×
124
    hour: string,
279!
125
    minute: string,
×
126
    amPm: AmPmType,
279✔
127
    intervals: string[],
128
    clockType: ClockType,
129
  ): boolean {
180✔
130
    const timeStr = clockType === '12h' ? `${hour}:${minute} ${amPm}` : `${hour}:${minute}`;
180✔
131

132
    for (const interval of intervals) {
133
      const [start, end] = interval.split('-').map((s) => s.trim());
134
      if (this.isTimeBetween(timeStr, start, end, clockType)) {
4✔
135
        return true;
4✔
136
      }
7✔
137
    }
7✔
138

7✔
139
    return false;
7!
140
  }
×
141

7!
142
  private static isTimeBetween(time: string, start: string, end: string, clockType: ClockType): boolean {
×
143
    const timeValue = this.timeToMinutes(time, clockType);
7✔
144
    const startValue = this.timeToMinutes(start, clockType);
7✔
145
    const endValue = this.timeToMinutes(end, clockType);
4✔
146

147
    return timeValue >= startValue && timeValue <= endValue;
148
  }
149

×
150
  private static timeToMinutes(time: string, clockType: ClockType): number {
151
    if (clockType === '12h') {
152
      const match = time.match(/(\d{1,2}):(\d{2})\s*(AM|PM)/i);
10✔
153
      if (!match) return 0;
154

155
      let hours = parseInt(match[1]);
156
      const minutes = parseInt(match[2]);
157
      const period = match[3].toUpperCase();
158

159
      if (period === 'PM' && hours !== 12) hours += 12;
160
      if (period === 'AM' && hours === 12) hours = 0;
161

162
      return hours * 60 + minutes;
163
    } else {
164
      const [hours, minutes] = time.split(':').map(Number);
165
      return hours * 60 + minutes;
166
    }
167
  }
168

169
  static findNearestValid(
170
    index: number,
171
    clockType: ClockType,
172
    amPm: AmPmType,
173
    disabledTime: DisabledTimeConfig | null,
174
    isInnerCircle: boolean,
175
  ): number {
176
    const maxHour = clockType === '24h' ? 23 : 12;
177

178
    for (let offset = 0; offset <= maxHour; offset++) {
179
      const candidates = offset === 0 ? [index] : [index + offset, index - offset];
180

181
      for (const candidate of candidates) {
182
        let testIndex = candidate;
183
        if (testIndex < 0) testIndex += maxHour + 1;
184
        if (testIndex > maxHour) testIndex = testIndex % (maxHour + 1);
185

186
        const value = this.indexToValue(testIndex, clockType, amPm);
187
        if (!this.isDisabled(value, amPm, disabledTime)) {
188
          return testIndex;
189
        }
190
      }
191
    }
192

193
    return index;
194
  }
195
}
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