import type { Moment } from 'moment' ; import { RRule } from 'rrule' ; export class Recurrence { private readonly rrule : RRule ; private readonly startDate : Moment | null ; private readonly scheduledDate : Moment | null ; private readonly dueDate : Moment | null ; /**
     * The reference date is used to calculate future occurences.
     *
     * Future occurences will recur based on the reference date.
     * The reference date is the due date, if it is given.
     * Otherwise the scheduled date, if it is given. And so on.
     *
     * Recurrence of all dates will be kept relative to the reference date.
     * For example: if the due date and the start date are given, the due date
     * is the reference date. Future occurrences will have a start date with the
     * same relative distance to the due date as the original task. For example
     * "starts one week before it is due".
     */ private readonly referenceDate : Moment | null ; constructor ( { rrule , referenceDate , startDate , scheduledDate , dueDate , } ) { this . rrule = rrule ; this . referenceDate = referenceDate ; this . startDate = startDate ; this . scheduledDate = scheduledDate ; this . dueDate = dueDate ; } public static fromText ( { recurrenceRuleText , startDate , scheduledDate , dueDate , } ) { try { const options = RRule . parseText ( recurrenceRuleText ) ; if ( options !== null ) { let referenceDate = null ; if ( dueDate ) { referenceDate = window . moment ( dueDate ) ; } else if ( scheduledDate ) { referenceDate = window . moment ( scheduledDate ) ; } else if ( startDate ) { referenceDate = window . moment ( startDate ) ; } if ( referenceDate !== null ) { options . dtstart = window . moment ( referenceDate ) . startOf ( 'day' ) . utc ( true ) . toDate ( ) ; } const rrule = new RRule ( options ) ; return new Recurrence ( { rrule , referenceDate , startDate , scheduledDate , dueDate , } ) ; } } catch ( error ) { } return null ; } public toText ( ) { return this . rrule . toText ( ) ; } /**
     * Returns the dates of the next occurrence or null if there is no next occurrence.
     */ public next ( ) { let after ; if ( this . referenceDate !== null ) { after = window . moment ( this . referenceDate ) ; } else { after = window . moment ( ) ; } after . endOf ( 'day' ) ; after . utc ( true ) ; const next = this . rrule . after ( after . toDate ( ) ) ; if ( next !== null ) { const localTimeZone = window . moment . utc ( next ) . local ( true ) ; const nextOccurrence = localTimeZone . startOf ( 'day' ) ; let startDate = null ; let scheduledDate = null ; let dueDate = null ; if ( this . referenceDate ) { if ( this . startDate ) { const originalDifference = window . moment . duration ( this . startDate . diff ( this . referenceDate ) ) ; startDate = window . moment ( nextOccurrence ) ; startDate . add ( Math . round ( originalDifference . asDays ( ) ) , 'days' ) ; } if ( this . scheduledDate ) { const originalDifference = window . moment . duration ( this . scheduledDate . diff ( this . referenceDate ) ) ; scheduledDate = window . moment ( nextOccurrence ) ; scheduledDate . add ( Math . round ( originalDifference . asDays ( ) ) , 'days' ) ; } if ( this . dueDate ) { const originalDifference = window . moment . duration ( this . dueDate . diff ( this . referenceDate ) ) ; dueDate = window . moment ( nextOccurrence ) ; dueDate . add ( Math . round ( originalDifference . asDays ( ) ) , 'days' ) ; } } return { startDate , scheduledDate , dueDate , } ; } return null ; } }