TimeSpan.psm1
[CmdletBinding()] param() $baseName = [System.IO.Path]::GetFileNameWithoutExtension($PSCommandPath) $script:PSModuleInfo = Test-ModuleManifest -Path "$PSScriptRoot\$baseName.psd1" $script:PSModuleInfo | Format-List | Out-String -Stream | ForEach-Object { Write-Debug $_ } $scriptName = $script:PSModuleInfo.Name Write-Debug "[$scriptName] - Importing module" #region [functions] - [private] Write-Debug "[$scriptName] - [functions] - [private] - Processing folder" #region [functions] - [private] - [Format-UnitValue] Write-Debug "[$scriptName] - [functions] - [private] - [Format-UnitValue] - Importing" function Format-UnitValue { <# .SYNOPSIS Formats a numerical value with its corresponding unit. .DESCRIPTION This function takes an integer value and a unit and returns a formatted string. If the -FullNames switch is specified, the function uses the singular or plural full unit name. Otherwise, it returns the value with the unit abbreviation. .EXAMPLE Format-UnitValue -Value 5 -Unit 'meter' Output: ```powershell 5m ``` Returns the formatted value with its abbreviation. .EXAMPLE Format-UnitValue -Value 1 -Unit 'meter' -FullNames Output: ```powershell 1 meter ``` Returns the formatted value with the full singular unit name. .EXAMPLE Format-UnitValue -Value 2 -Unit 'meter' -FullNames Output: ```powershell 2 meters ``` Returns the formatted value with the full plural unit name. .OUTPUTS string. A formatted string combining the value and its corresponding unit abbreviation or full name. .LINK https://psmodule.io/Format/Functions/Format-UnitValue/ #> [OutputType([string])] [CmdletBinding()] param( # The numerical value to be formatted with a unit. [Parameter(Mandatory)] [System.Int128] $Value, # The unit type to append to the value. [Parameter(Mandatory)] [string] $Unit, # Switch to use full unit names instead of abbreviations. [Parameter()] [switch] $FullNames ) if ($FullNames) { # Choose singular or plural form based on the value. $unitName = if ($Value -eq 1) { $script:UnitMap[$Unit].Singular } else { $script:UnitMap[$Unit].Plural } return "$Value $unitName" } "$Value$($script:UnitMap[$Unit].Abbreviation)" } Write-Debug "[$scriptName] - [functions] - [private] - [Format-UnitValue] - Done" #endregion [functions] - [private] - [Format-UnitValue] Write-Debug "[$scriptName] - [functions] - [private] - Done" #endregion [functions] - [private] #region [functions] - [public] Write-Debug "[$scriptName] - [functions] - [public] - Processing folder" #region [functions] - [public] - [completer] Write-Debug "[$scriptName] - [functions] - [public] - [completer] - Importing" Register-ArgumentCompleter -CommandName Format-TimeSpan -ParameterName BaseUnit -ScriptBlock { param($commandName, $parameterName, $wordToComplete, $commandAst, $fakeBoundParameter) $null = $commandName, $parameterName, $wordToComplete, $commandAst, $fakeBoundParameter $($script:UnitMap.Keys) | Where-Object { $_ -like "$wordToComplete*" } } Write-Debug "[$scriptName] - [functions] - [public] - [completer] - Done" #endregion [functions] - [public] - [completer] #region [functions] - [public] - [Format-TimeSpan] Write-Debug "[$scriptName] - [functions] - [public] - [Format-TimeSpan] - Importing" function Format-TimeSpan { <# .SYNOPSIS Formats a TimeSpan object into a human-readable string. .DESCRIPTION This function converts a TimeSpan object into a formatted string based on a chosen unit or precision. It allows specifying a base unit, the number of precision levels, and whether to use full unit names. If the TimeSpan is negative, it is prefixed with a minus sign. .EXAMPLE New-TimeSpan -Minutes 90 | Format-TimeSpan Output: ```powershell 2h ``` Formats the given TimeSpan into a human-readable format using the most significant unit. .EXAMPLE [TimeSpan]::FromSeconds(3661) | Format-TimeSpan -Precision 2 -FullNames Output: ```powershell 1 hour 1 minute ``` Returns the TimeSpan formatted into multiple components based on the specified precision. .EXAMPLE [TimeSpan]::FromMilliseconds(500) | Format-TimeSpan -Precision 2 -FullNames Output: ```powershell 500 milliseconds 0 microseconds ``` Forces the output to be formatted in milliseconds, regardless of precision. .OUTPUTS System.String .NOTES The formatted string representation of the TimeSpan. .LINK https://psmodule.io/TimeSpan/Functions/Format-TimeSpan/ #> [CmdletBinding()] param( # The TimeSpan object to format. [Parameter(Mandatory, ValueFromPipeline, ValueFromPipelineByPropertyName)] [TimeSpan] $TimeSpan, # Specifies the number of precision levels to include in the output. [Parameter()] [int] $Precision = 1, # Specifies the base unit to use for formatting the TimeSpan. [Parameter()] [string] $BaseUnit, # If specified, outputs full unit names instead of abbreviations. [Parameter()] [switch] $FullNames ) process { $isNegative = $TimeSpan.Ticks -lt 0 if ($isNegative) { $TimeSpan = [System.TimeSpan]::FromTicks(-1 * $TimeSpan.Ticks) } $originalTicks = $TimeSpan.Ticks # Ordered list of units from most to least significant. $orderedUnits = @($script:UnitMap.Keys) if ($Precision -eq 1) { # For precision=1, use the "fractional" approach. if ($BaseUnit) { $chosenUnit = $BaseUnit } else { # Pick the most significant unit that fits (unless all are zero). $chosenUnit = $null foreach ($unit in $orderedUnits) { if (($script:UnitMap.Keys -contains $unit) -and $originalTicks -ge $script:UnitMap[$unit].Ticks) { $chosenUnit = $unit; break } } if (-not $chosenUnit) { $chosenUnit = 'Microseconds' } } $fractionalValue = $originalTicks / $script:UnitMap[$chosenUnit].Ticks $roundedValue = [math]::Round($fractionalValue, 0, [System.MidpointRounding]::AwayFromZero) $formatted = Format-UnitValue -value $roundedValue -unit $chosenUnit -FullNames:$FullNames if ($isNegative) { $formatted = "-$formatted" } return $formatted } else { # For multi-component output, perform a sequential breakdown. if ($BaseUnit) { $startingIndex = $orderedUnits.IndexOf($BaseUnit) if ($startingIndex -lt 0) { throw "Invalid BaseUnit value: $BaseUnit" } } else { $startingIndex = 0 foreach ($unit in $orderedUnits) { if (($script:UnitMap.Keys -contains $unit) -and $originalTicks -ge $script:UnitMap[$unit].Ticks) { break } $startingIndex++ } if ($startingIndex -ge $orderedUnits.Count) { $startingIndex = $orderedUnits.Count - 1 } } $resultSegments = @() $remainder = $originalTicks $endIndex = [Math]::Min($startingIndex + $Precision - 1, $orderedUnits.Count - 1) for ($i = $startingIndex; $i -le $endIndex; $i++) { $unit = $orderedUnits[$i] $unitTicks = $script:UnitMap[$unit].Ticks if ($i -eq $endIndex) { $value = [math]::Round($remainder / $unitTicks, 0, [System.MidpointRounding]::AwayFromZero) } else { $value = [math]::Floor($remainder / $unitTicks) } $remainder = $remainder - ($value * $unitTicks) $resultSegments += Format-UnitValue -value $value -unit $unit -FullNames:$FullNames } $formatted = $resultSegments -join ' ' if ($isNegative) { $formatted = "-$formatted" } return $formatted } } } Write-Debug "[$scriptName] - [functions] - [public] - [Format-TimeSpan] - Done" #endregion [functions] - [public] - [Format-TimeSpan] Write-Debug "[$scriptName] - [functions] - [public] - Done" #endregion [functions] - [public] #region [variables] - [private] Write-Debug "[$scriptName] - [variables] - [private] - Processing folder" #region [variables] - [private] - [UnitMap] Write-Debug "[$scriptName] - [variables] - [private] - [UnitMap] - Importing" $script:AverageDaysInMonth = 30.436875 $script:AverageDaysInYear = 365.2425 $script:DaysInWeek = 7 $script:HoursInDay = 24 $script:UnitMap = [ordered]@{ 'Millennia' = @{ Singular = 'millennium' Plural = 'millennia' Abbreviation = 'mil' Ticks = [System.TimeSpan]::TicksPerDay * $script:AverageDaysInYear * 1000 } 'Centuries' = @{ Singular = 'century' Plural = 'centuries' Abbreviation = 'cent' Ticks = [System.TimeSpan]::TicksPerDay * $script:AverageDaysInYear * 100 } 'Decades' = @{ Singular = 'decade' Plural = 'decades' Abbreviation = 'dec' Ticks = [System.TimeSpan]::TicksPerDay * $script:AverageDaysInYear * 10 } 'Years' = @{ Singular = 'year' Plural = 'years' Abbreviation = 'y' Ticks = [System.TimeSpan]::TicksPerDay * $script:AverageDaysInYear } 'Months' = @{ Singular = 'month' Plural = 'months' Abbreviation = 'mo' Ticks = [System.TimeSpan]::TicksPerDay * $script:AverageDaysInMonth } 'Weeks' = @{ Singular = 'week' Plural = 'weeks' Abbreviation = 'w' Ticks = [System.TimeSpan]::TicksPerDay * $script:DaysInWeek } 'Days' = @{ Singular = 'day' Plural = 'days' Abbreviation = 'd' Ticks = [System.TimeSpan]::TicksPerDay } 'Hours' = @{ Singular = 'hour' Plural = 'hours' Abbreviation = 'h' Ticks = [System.TimeSpan]::TicksPerHour } 'Minutes' = @{ Singular = 'minute' Plural = 'minutes' Abbreviation = 'min' Ticks = [System.TimeSpan]::TicksPerMinute } 'Seconds' = @{ Singular = 'second' Plural = 'seconds' Abbreviation = 's' Ticks = [System.TimeSpan]::TicksPerSecond } 'Milliseconds' = @{ Singular = 'millisecond' Plural = 'milliseconds' Abbreviation = 'ms' Ticks = [System.TimeSpan]::TicksPerMillisecond } 'Microseconds' = @{ Singular = 'microsecond' Plural = 'microseconds' Abbreviation = "$([char]0x00B5)s" Ticks = 10 } } Write-Debug "[$scriptName] - [variables] - [private] - [UnitMap] - Done" #endregion [variables] - [private] - [UnitMap] Write-Debug "[$scriptName] - [variables] - [private] - Done" #endregion [variables] - [private] #region Member exporter $exports = @{ Alias = '*' Cmdlet = '' Function = 'Format-TimeSpan' Variable = '' } Export-ModuleMember @exports #endregion Member exporter |