modules/Helper/DateTimeFormula.psm1

class DateTimeFormula{
    hidden [string[]] $UNITS = 'Y', 'Q', 'M', 'W', 'D', 'h', 'm', 's'

    hidden [string] $referentUnit
    hidden [bool] $isReferentNegative
    hidden [int] $referentValue
    hidden [string] $timespanUnit

    [bool] $isDateTimeFormula
    [bool] $isTimeSpanFormula

    [string] $formula
    [datetime] $datetime

    DateTimeFormula(){

    }

    hidden [datetime] applyTimeSpanFormula([datetime] $datetime, [string]$formula){
        $this.datetime = $datetime
        $ts = $this.getTimeSpanValue($formula)

        return ($datetime + $ts)
    }


    [datetime] getDateTimeValue([string] $formula){
        $this.formula = $formula
        $this.identifyUnits()

        if ($this.isDateTimeFormula){
            try {
                $this.datetime = $this.parseDateTime()
            }
            catch {
                $this.isDateTimeFormula = $false
            }
        }

        return $this.datetime
    }

    [timespan] getTimeSpanValue([string]$formula){
        $ref = Get-Date
        $retValue = $ref

        try {
            $exp = $this.splitTimespanFormula($formula)

            foreach ($node in $exp.nodes) {
                switch -CaseSensitive ($node.unit) {
                    'Y' { $retValue = $retValue.AddYears($node.number) }
                    'Q' { $retValue = $retValue.AddMonths(3 * $node.number) }
                    'M' { $retValue = $retValue.AddMonths($node.number) }
                    'W' { $retValue = $retValue.AddDays(7 * $node.number) }
                    'D' { $retValue = $retValue.AddDays($node.number) }
                    'h' { $retValue = $retValue.AddHours($node.number) }
                    'm' { $retValue = $retValue.AddMinutes($node.number) }
                    's' { $retValue = $retValue.AddSeconds($node.number) }
                }
            }
            $this.isTimeSpanFormula = ($exp.nodes.Count -gt 0)
        }
        catch {
            $this.isTimeSpanFormula = $false
        }

        return ($retValue - $ref)
    }

    hidden [void] identifyUnits() {
        if ($this.formula.SubString(0,1) -eq '-') {
            $this.isReferentNegative = $true
            $this.formula = $this.formula.SubString(1)
        }
        elseif ($this.formula.SubString(0,1) -eq '+') {
            $this.isReferentNegative = $false
            $this.formula = $this.formula.SubString(1)
        }
        else {
            $this.isReferentNegative = $false
        }

        if ($this.formula.SubString(0, 2) -ceq 'WD') {
            $this.referentUnit = $this.formula.SubString(0, 2)

            if ($this.formula.Length -gt 2){
                $this.referentValue = $this.formula.SubString(2, 1)
                $this.timespanUnit = $this.formula.SubString(3)
            }
            else {
                $this.referentValue = '0'
                $this.timespanUnit = ''
            }

            $this.isDateTimeFormula = $true
        }
        elseif ($this.formula.SubString(0, 1) -cin $this.UNITS) { #('Y', 'M', 'D', 'h', 'm')
            $this.referentUnit = $this.formula.SubString(0, 1)

            $haveValue = $false
            for($i=1; $i -lt $this.formula.Length; $i++){
                if ($this.formula[$i] -cin ($this.UNITS + @('+', '-'))) {
                    $this.referentValue = $this.formula.SubString(1, $i-1)
                    $this.timespanUnit = $this.formula.SubString($i)
                    $haveValue = $true
                    break
                }
            }
            if (-not $haveValue){
                $this.referentValue = $this.formula.SubString(1)
                $this.timespanUnit = ''
            }

            $this.isDateTimeFormula = $true
        }
        elseif ($this.formula.SubString(0, 1) -ceq 'C') {
            $this.referentUnit = $this.formula.SubString(0, 2)
            $this.timespanUnit = $this.formula.SubString(2)

            $this.isDateTimeFormula = $true
        }
        else {
            $this.isDateTimeFormula = $false
        }
    }

    hidden [datetime] parseDateTime(){
        $retValue = [datetime]::MinValue

        $retValue = $this.getReferentDate($this.referentUnit)

        if ($this.timespanUnit) {
            $ts = $this.getTimeSpanValue($this.timespanUnit)
            $retValue = $retValue + $ts
        }

        return $retValue
    }

    hidden [datetime] getReferentDate([string]$value){
        $date = Get-Date

        switch -CaseSensitive ($value){
            'CT' { $date = (Get-Date) }
            'CY' { $date = (Get-Date -Year $date.Year -Month 1 -Day 1 -Hour 0 -Minute 0 -Second 0 -Millisecond 0) }
            'CQ' { $date = (Get-Date -Year $date.Year -Month (3 * [Math]::Floor($date.Month / 3)) -Day 1 -Hour 0 -Minute 0 -Second 0 -Millisecond 0) }
            'CM' { $date = (Get-Date -Year $date.Year -Month $date.Month -Day 1 -Hour 0 -Minute 0 -Second 0 -Millisecond 0) }
            'CW' { $date = (Get-Date -Year $date.Year -Month $date.Month -Day $date.Day -Hour 0 -Minute 0 -Second 0 -Millisecond 0).AddDays(-$date.DayOfWeek) }
            'CD' { $date = (Get-Date -Year $date.Year -Month $date.Month -Day $date.Day -Hour 0 -Minute 0 -Second 0 -Millisecond 0) }
            'Ch' { $date = (Get-Date -Year $date.Year -Month $date.Month -Day $date.Day -Hour $date.Hour -Minute 0 -Second 0 -Millisecond 0) }
            'Cm' { $date = (Get-Date -Year $date.Year -Month $date.Month -Day $date.Day -Hour $date.Hour -Minute $date.Minute -Second 0 -Millisecond 0) }
            'Y' {
                if ($this.isReferentNegative) {
                    $date = (Get-Date -Year $date.Year -Month 1 -Day 1 -Hour 0 -Minute 0 -Second 0 -Millisecond 0).AddYears(-$this.referentValue)
                }
                else {
                    $date = (Get-Date -Year $this.referentValue -Month 1 -Day 1 -Hour 0 -Minute 0 -Second 0 -Millisecond 0)
                }
            }
            'Q' {
                if ($this.isReferentNegative) {
                    $date = (Get-Date -Year $date.Year -Month $date.Month -Day 1 -Hour 0 -Minute 0 -Second 0 -Millisecond 0).AddMonths(-3 * $this.referentValue)
                }
                else {
                    $date = (Get-Date -Year $date.Year -Month 1 -Day 1 -Hour 0 -Minute 0 -Second 0 -Millisecond 0).AddMonths(3 * $this.referentValue)
                }
            }
            'M' {
                if ($this.isReferentNegative) {
                    $date = (Get-Date -Year $date.Year -Month $date.Month -Day 1 -Hour 0 -Minute 0 -Second 0 -Millisecond 0).AddMonths(-$this.referentValue)
                }
                else {
                    $date = (Get-Date -Year $date.Year -Month $this.referentValue -Day 1 -Hour 0 -Minute 0 -Second 0 -Millisecond 0)
                }
            }
            'W' {
                if ($this.isReferentNegative) {
                    $date = (Get-Date -Year $date.Year -Month $date.Month -Day $date.Day -Hour 0 -Minute 0 -Second 0 -Millisecond 0).AddDays(-7 * $this.referentValue)
                }
                else {
                    $date = (Get-Date -Year $date.Year -Month 1 -Day 1 -Hour 0 -Minute 0 -Second 0 -Millisecond 0).AddDays(7 * $date.DayOfWeek)
                }
            }
            'D' {
                if ($this.isReferentNegative) {
                    $date = (Get-Date -Year $date.Year -Month $date.Month -Day $date.Day -Hour 0 -Minute 0 -Second 0 -Millisecond 0).AddDays(-$this.referentValue)
                }
                else {
                    $date = (Get-Date -Year $date.Year -Month $date.Month -Day $this.referentValue -Hour 0 -Minute 0 -Second 0 -Millisecond 0)
                }
            }
            'h' {
                if ($this.isReferentNegative) {
                    $date = (Get-Date -Year $date.Year -Month $date.Month -Day $date.Day -Hour $date.Hour -Minute 0 -Second 0 -Millisecond 0).AddHours(-$this.referentValue)
                }
                else {
                    $date = (Get-Date -Year $date.Year -Month $date.Month -Day $date.Day -Hour $this.referentValue -Minute 0 -Second 0 -Millisecond 0)
                }
             }
            'm' {
                if ($this.isReferentNegative) {
                    $date = (Get-Date -Year $date.Year -Month $date.Month -Day $date.Day -Hour $date.Hour -Minute $date.Minute -Second 0 -Millisecond 0).AddMinutes(-$this.referentValue)
                }
                else {
                    $date = (Get-Date -Year $date.Year -Month $date.Month -Day $date.Day -Hour $date.Hour -Minute $this.referentValue -Second 0 -Millisecond 0)
                }
             }
            's' {
                if ($this.isReferentNegative) {
                    $date = (Get-Date -Year $date.Year -Month $date.Month -Day $date.Day -Hour $date.Hour -Minute $date.Minute -Second $date.Second -Millisecond 0).AddSeconds(-$this.referentValue)
                }
                else {
                    $date = (Get-Date -Year $date.Year -Month $date.Month -Day $date.Day -Hour $date.Hour -Minute $date.Minute -Second $this.referentValue -Millisecond 0)
                }
             }
            'WD' {
                if ($this.isReferentNegative) {
                    $date = (Get-Date -Year $date.Year -Month $date.Month -Day $date.Day -Hour 0 -Minute 0 -Second 0 -Millisecond 0).AddDays(-7).AddDays(-($date.DayOfWeek-$this.referentValue))
                }
                else {
                    $date = (Get-Date -Year $date.Year -Month $date.Month -Day $date.Day -Hour 0 -Minute 0 -Second 0 -Millisecond 0).AddDays(-($date.DayOfWeek-$this.referentValue))
                }
            }
            default { $date = $null }
        }

        return $date
    }

    hidden [PSCustomObject] splitTimespanFormula([string] $value){
        $retValue = [PSCustomObject]@{expression=$value; nodes=@()}

        $prev=0
        for($i=0; $i -lt $value.Length; $i++){
            if ($value[$i] -cin $this.UNITS){
                $number = $value.Substring($prev, $i-$prev)
                $unit = $value.Substring($i, 1)
                $prev = $i + 1
                $retValue.nodes += [PSCustomObject]@{number=[int]$number; unit=$unit}
            }
        }

        return $retValue
    }
}