Reputation: 7802
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
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
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
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