@@ -10,25 +10,11 @@ import {
10
10
UpdateUserQuietHoursScheduleRequest ,
11
11
UserQuietHoursScheduleResponse ,
12
12
} from "api/typesGenerated" ;
13
- import cronParser from "cron-parser" ;
14
13
import MenuItem from "@mui/material/MenuItem" ;
15
14
import { Stack } from "components/Stack/Stack" ;
16
- import dayjs from "dayjs" ;
17
- import relativeTime from "dayjs/plugin/relativeTime" ;
18
- import timezone from "dayjs/plugin/timezone" ;
19
- import utc from "dayjs/plugin/utc" ;
20
- import { timeZones } from "utils/timeZones" ;
15
+ import { timeZones , getPreferredTimezone } from "utils/timeZones" ;
21
16
import { Alert } from "components/Alert/Alert" ;
22
- import { useQueryClient } from "@tanstack/react-query" ;
23
-
24
- dayjs . extend ( utc ) ;
25
- import advancedFormat from "dayjs/plugin/advancedFormat" ;
26
- import duration from "dayjs/plugin/duration" ;
27
- import { userQuietHoursScheduleKey } from "api/queries/settings" ;
28
- dayjs . extend ( advancedFormat ) ;
29
- dayjs . extend ( duration ) ;
30
- dayjs . extend ( timezone ) ;
31
- dayjs . extend ( relativeTime ) ;
17
+ import { timeToCron , quietHoursDisplay } from "utils/schedule" ;
32
18
33
19
export interface ScheduleFormValues {
34
20
startTime : string ;
@@ -41,14 +27,14 @@ const validationSchema = Yup.object({
41
27
. test ( "is-time-string" , "Time must be in HH:mm format." , ( value ) => {
42
28
if ( value === "" ) {
43
29
return true ;
44
- } else if ( ! / ^ [ 0 - 9 ] [ 0 - 9 ] : [ 0 - 9 ] [ 0 - 9 ] $ / . test ( value ) ) {
30
+ }
31
+ if ( ! / ^ [ 0 - 9 ] [ 0 - 9 ] : [ 0 - 9 ] [ 0 - 9 ] $ / . test ( value ) ) {
45
32
return false ;
46
- } else {
47
- const parts = value . split ( ":" ) ;
48
- const HH = Number ( parts [ 0 ] ) ;
49
- const mm = Number ( parts [ 1 ] ) ;
50
- return HH >= 0 && HH <= 23 && mm >= 0 && mm <= 59 ;
51
33
}
34
+ const parts = value . split ( ":" ) ;
35
+ const HH = Number ( parts [ 0 ] ) ;
36
+ const mm = Number ( parts [ 1 ] ) ;
37
+ return HH >= 0 && HH <= 23 && mm >= 0 && mm <= 59 ;
52
38
} ) ,
53
39
timezone : Yup . string ( ) . required ( ) ,
54
40
} ) ;
@@ -71,10 +57,8 @@ export const ScheduleForm: FC<React.PropsWithChildren<ScheduleFormProps>> = ({
71
57
onSubmit,
72
58
now,
73
59
} ) => {
74
- // Force a re-render every 15 seconds to update the "Next occurrence" field.
75
- // The app re-renders by itself occasionally but this is just to be sure it
76
- // doesn't get stale.
77
- const [ _ , setTime ] = useState < number > ( Date . now ( ) ) ;
60
+ // Update every 15 seconds to update the "Next occurrence" field.
61
+ const [ , setTime ] = useState < number > ( Date . now ( ) ) ;
78
62
useEffect ( ( ) => {
79
63
const interval = setInterval ( ( ) => setTime ( Date . now ( ) ) , 15000 ) ;
80
64
return ( ) => {
@@ -159,9 +143,8 @@ export const ScheduleForm: FC<React.PropsWithChildren<ScheduleFormProps>> = ({
159
143
disabled
160
144
fullWidth
161
145
label = "Next occurrence"
162
- value = { formatNextRun (
146
+ value = { quietHoursDisplay (
163
147
form . values . startTime ,
164
-
165
148
form . values . timezone ,
166
149
now ,
167
150
) }
@@ -181,59 +164,3 @@ export const ScheduleForm: FC<React.PropsWithChildren<ScheduleFormProps>> = ({
181
164
</ Form >
182
165
) ;
183
166
} ;
184
-
185
- const getPreferredTimezone = ( ) => {
186
- return Intl . DateTimeFormat ( ) . resolvedOptions ( ) . timeZone ;
187
- } ;
188
-
189
- const timeToCron = ( time : string , tz ?: string ) => {
190
- const [ HH , mm ] = time . split ( ":" ) ;
191
- let prefix = "" ;
192
- if ( tz ) {
193
- prefix = `CRON_TZ=${ tz } ` ;
194
- }
195
- return `${ prefix } ${ mm } ${ HH } * * *` ;
196
- } ;
197
-
198
- // evaluateNextRun returns a Date object of the next cron run time.
199
- const evaluateNextRun = (
200
- time : string ,
201
- tz : string ,
202
- now : Date | undefined ,
203
- ) : Date => {
204
- // The cron-parser package doesn't accept a timezone in the cron string, but
205
- // accepts it as an option.
206
- const cron = timeToCron ( time ) ;
207
- const parsed = cronParser . parseExpression ( cron , {
208
- currentDate : now ,
209
- iterator : false ,
210
- utc : false ,
211
- tz,
212
- } ) ;
213
-
214
- return parsed . next ( ) . toDate ( ) ;
215
- } ;
216
-
217
- const formatNextRun = (
218
- time : string ,
219
- tz : string ,
220
- now : Date | undefined ,
221
- ) : string => {
222
- const nowDjs = dayjs ( now ) . tz ( tz ) ;
223
- const djs = dayjs ( evaluateNextRun ( time , tz , now ) ) . tz ( tz ) ;
224
- let str = djs . format ( "h:mm A" ) ;
225
- if ( djs . isSame ( nowDjs , "day" ) ) {
226
- str += " today" ;
227
- } else if ( djs . isSame ( nowDjs . add ( 1 , "day" ) , "day" ) ) {
228
- str += " tomorrow" ;
229
- } else {
230
- // This case will rarely ever be hit, as we're dealing with only times and
231
- // not dates, but it can be hit due to mismatched browser timezone to cron
232
- // timezone or due to daylight savings changes.
233
- str += ` on ${ djs . format ( "dddd, MMMM D" ) } ` ;
234
- }
235
-
236
- str += ` (${ djs . from ( now ) } )` ;
237
-
238
- return str ;
239
- } ;
0 commit comments