Graham
Graham

Reputation: 7802

Splitting numbers and letters from a string misses zeroes

I am processing a time value that arrives in a string that starts with a T then is followed by a series of numbers immediately followed by the unit for each number. For example, 8 hours and 22 minutes will come in as T8H22M and 3 hours, 2 minutes, and 1 second will be T3H2M1S.

I need to split this encoded value into separate H/M/S columns, but I am having issues getting zeroes to work properly. I am using this code from another Question here with similar (but not identical) requirements.

This script:

Write-Host $('T8H22M' -split { [bool]($_ -as [double]) })
Write-Host $('T8H22M' -split { ! [bool]($_ -as [double]) })
Write-Host $('T8H0M' -split { [bool]($_ -as [double]) })
Write-Host $('T8H0M' -split { ! [bool]($_ -as [double]) })
Write-Host $('T0H' -split { $_ -eq '0' })

Produces this output:

T H  M
 8 22 
T H0M
 8   
T H

As you can see, any time the numerical value is zero the string just doesn't split. This is a real problem as zero-time comes in as T0H which just won't parse using the method above.

How can I modify the code above to also split out zero values?

Upvotes: 0

Views: 120

Answers (3)

iRon
iRon

Reputation: 23693

To compliment the answers from @boxdog and @Theo on the actual question why the zero's are missing and "How can I modify the code above to also split out zero values?"

Quote on the <ScriptBlock> parameter

<ScriptBlock>
An expression that specifies rules for applying the delimiter. The expression must evaluate to $true or $false. Enclose the script block in braces.

The point here is that the 0 evaluates to $False ("falsy"):

if (0) { 'True' } else { 'False' }
False

if (1) { 'True' } else { 'False' }
True

[Bool]0
False

[Bool]1
True

In other words you will need to compare your split expression against $Null to get what you are looking for:

Write-Host $('T8H22M' -split { $Null -ne ($_ -as [double]) })
T H  M
Write-Host $('T8H22M' -split { $Null -eq ($_ -as [double]) })
 8 22
Write-Host $('T8H0M' -split { $Null -ne ($_ -as [double]) })
T H M
Write-Host $('T8H0M' -split { $Null -eq ($_ -as [double]) })
 8 0

Anyways, I recommend you to go for the solution from @boxdog or @Theo.

Upvotes: 1

Theo
Theo

Reputation: 61093

The time values you have resemble the ISO 8601 duration format except they all lack the leading P.

You can use the ToTimeSpan() method of .net System.Xml.XmlConvert to convert these to TimeSpans using:

'T3H2M1S','T8H22M','T8H0M','T0H' | ForEach-Object { 
    [System.Xml.XmlConvert]::ToTimeSpan("P$_")  # prepend a 'P'
}

To return an array of TimeSpan objects

Days              : 0
Hours             : 8
Minutes           : 22
Seconds           : 0
Milliseconds      : 0
Ticks             : 301200000000
TotalDays         : 0,348611111111111
TotalHours        : 8,36666666666667
TotalMinutes      : 502
TotalSeconds      : 30120
TotalMilliseconds : 30120000

Days              : 0
Hours             : 8
Minutes           : 0
Seconds           : 0
Milliseconds      : 0
Ticks             : 288000000000
TotalDays         : 0,333333333333333
TotalHours        : 8
TotalMinutes      : 480
TotalSeconds      : 28800
TotalMilliseconds : 28800000

Days              : 0
Hours             : 0
Minutes           : 0
Seconds           : 0
Milliseconds      : 0
Ticks             : 0
TotalDays         : 0
TotalHours        : 0
TotalMinutes      : 0
TotalSeconds      : 0
TotalMilliseconds : 0

Now you can use any of the properties to format as you like.

Upvotes: 3

boxdog
boxdog

Reputation: 8442

You could use a regex to extract the values. For example:

'T3H2M1S','T8H22M','T8H0M','T0H' |
    ForEach-Object {
        if($_ -match '^T((?<hours>\d+)H)*((?<minutes>\d+)M)*((?<seconds>\d+)S)*$') {
            [PsCustomObject]@{
                TimeString = $matches.0
                Hours      = [Int]$matches.hours
                Minutes    = [Int]$matches.minutes
                Seconds    = [Int]$matches.seconds
            }
        }
    }

This produces an object for each time string, with Hours, Minutes and Seconds properties corresponding to the related part of that string:

TimeString Hours Minutes Seconds
---------- ----- ------- -------
T3H2M1S        3       2       1
T8H22M         8      22       0
T8H0M          8       0       0
T0H            0       0       0

Of course, you can change the contents of the if to manipulate the values however you like, not just creating an object - the key is that the RegEx should* split it correctly for you.

* - I say 'should' because I only tested with the example strings you give, so be sure to test more thoroughly yourself.

Upvotes: 3

Related Questions