11import { AxiosError } from 'axios' ;
2+ import isAfter from 'date-fns/isAfter' ;
23import isPast from 'date-fns/isPast' ;
4+ import { formatInTimeZone , zonedTimeToUtc } from 'date-fns-tz' ;
35import { DateArray , DateTime , EventAttributes , createEvents } from 'ics' ;
46import { TFunction } from 'next-i18next' ;
57
@@ -13,6 +15,26 @@ import { getPlaceFields } from '../place/utils';
1315import { EventStatus , PublicationStatus , SuperEventType } from './constants' ;
1416import { Event , EventFields , EventQueryVariables } from './types' ;
1517
18+ export const PAID_EVENT_CANCELLATION_DEADLINE_DAYS = 7 ;
19+ const HELSINKI_TIME_ZONE = 'Europe/Helsinki' ;
20+
21+ const getHelsinkiStartOfDayFromDateString = ( dateString : string ) : Date =>
22+ zonedTimeToUtc ( `${ dateString } T00:00:00` , HELSINKI_TIME_ZONE ) ;
23+
24+ const getHelsinkiStartOfDay = ( date : Date ) : Date =>
25+ getHelsinkiStartOfDayFromDateString (
26+ formatInTimeZone ( date , HELSINKI_TIME_ZONE , 'yyyy-MM-dd' )
27+ ) ;
28+
29+ // Use UTC date arithmetic on yyyy-mm-dd to avoid runtime TZ and DST drift.
30+ const subtractDaysFromDateString = ( dateString : string , days : number ) : string => {
31+ const [ year , month , day ] = dateString . split ( '-' ) . map ( Number ) ;
32+ const date = new Date ( Date . UTC ( year , month - 1 , day ) ) ;
33+ date . setUTCDate ( date . getUTCDate ( ) - days ) ;
34+
35+ return date . toISOString ( ) . slice ( 0 , 10 ) ;
36+ } ;
37+
1638export const getEventFields = ( event : Event , locale : Language ) : EventFields => {
1739 return {
1840 audienceMaxAge : event . audience_max_age || null ,
@@ -193,3 +215,34 @@ export const isEventStarted = (event: Event): boolean => {
193215 const startTime = event . start_time ? new Date ( event . start_time ) : null ;
194216 return startTime ? isPast ( startTime ) : false ;
195217} ;
218+
219+ export const isPaidEvent = ( event : Event ) : boolean =>
220+ event . offers . some ( ( offer ) => ! ! offer && offer . is_free === false ) ;
221+
222+ export const isPaidEventCancellationDeadlinePassed = ( {
223+ event,
224+ now = new Date ( ) ,
225+ } : {
226+ event : Event ;
227+ now ?: Date ;
228+ } ) : boolean => {
229+ if ( ! event . start_time || ! isPaidEvent ( event ) ) {
230+ return false ;
231+ }
232+
233+ const eventStartDateInHelsinki = formatInTimeZone (
234+ new Date ( event . start_time ) ,
235+ HELSINKI_TIME_ZONE ,
236+ 'yyyy-MM-dd'
237+ ) ;
238+ const latestCancellationDateInHelsinki = subtractDaysFromDateString (
239+ eventStartDateInHelsinki ,
240+ PAID_EVENT_CANCELLATION_DEADLINE_DAYS
241+ ) ;
242+ const latestCancellationDay = getHelsinkiStartOfDayFromDateString (
243+ latestCancellationDateInHelsinki
244+ ) ;
245+
246+ // Cancellation is allowed for the full cutoff day and disabled starting next day.
247+ return isAfter ( getHelsinkiStartOfDay ( now ) , latestCancellationDay ) ;
248+ } ;
0 commit comments