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

pglejzer / timepicker-ui / 27286160512

10 Jun 2026 03:12PM UTC coverage: 86.868% (+1.5%) from 85.325%
27286160512

push

github

web-flow
Merge pull request #167 from pglejzer/feature/4.4.0

Feature/4.4.0

2277 of 2878 branches covered (79.12%)

Branch coverage included in aggregate %.

406 of 427 new or added lines in 29 files covered. (95.08%)

162 existing lines in 27 files now uncovered.

4523 of 4950 relevant lines covered (91.37%)

115.32 hits per line

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

93.57
/app/src/managers/clock/engine/HourEngine.ts
1
import type { ClockType, AmPmType, DisabledTimeConfig } from '../types';
2
import { parseIntervalEdge } from '../../../utils/time/parse';
18✔
3

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

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

17
    return baseIndex === 0 ? 12 : baseIndex;
15✔
18
  }
19

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

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

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

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

24✔
41
    const hour = index === 0 ? 12 : index;
42
    return (hour % 12) * 30;
43
  }
238✔
44

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

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

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

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

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

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

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

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

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

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

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

306✔
104
    return false;
153✔
105
  }
153✔
106

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

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

×
121
    return true;
7✔
122
  }
7✔
123

4✔
124
  private static isTimeInIntervals(
125
    hour: string,
126
    minute: string,
UNCOV
127
    amPm: AmPmType,
×
128
    intervals: string[],
129
    clockType: ClockType,
130
  ): boolean {
18✔
131
    const timeStr = clockType === '12h' ? `${hour}:${minute} ${amPm}` : `${hour}:${minute}`;
132
    const target = parseIntervalEdge(timeStr, clockType);
133

134
    for (const interval of intervals) {
135
      const [start, end] = interval.split('-').map((s) => s.trim());
136
      const startValue = parseIntervalEdge(start, clockType);
137
      const endValue = parseIntervalEdge(end, clockType);
138
      if (target >= startValue && target <= endValue) return true;
139
    }
140

141
    return false;
142
  }
143

144
  static findNearestValid(
145
    index: number,
146
    clockType: ClockType,
147
    amPm: AmPmType,
148
    disabledTime: DisabledTimeConfig | null,
149
  ): number {
150
    const maxHour = clockType === '24h' ? 23 : 12;
151

152
    for (let offset = 0; offset <= maxHour; offset++) {
153
      const candidates = offset === 0 ? [index] : [index + offset, index - offset];
154

155
      for (const candidate of candidates) {
156
        let testIndex = candidate;
157
        if (testIndex < 0) testIndex += maxHour + 1;
158
        if (testIndex > maxHour) testIndex = testIndex % (maxHour + 1);
159

160
        const value = this.indexToValue(testIndex, clockType);
161
        if (!this.isDisabled(value, amPm, disabledTime)) {
162
          return testIndex;
163
        }
164
      }
165
    }
166

167
    return index;
168
  }
169
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE TRIAL · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc