Add sexagesimal times in powershell - powershell

I'm using ffprobe to get the sexagesimal time and after trimming the last three (unnecessary) digits I get the following format:
#examples
0:05:51.15
11:03:15.24
Is there a way to add these 2 so that the result is 11:09:06.39 ?

You can cast the strings to type [timespan], which allows you to perform arithmetic on the resulting objects, and to apply custom formatting on output:
PS> ([timespan] ' 0:05:51.15' + [timespan] '11:03:15.24').ToString('h\:mm\:ss\.ff')
11:09:06.39
Note: If there's a chance that the resulting time span exceeds 24 hours, more work is needed.[1]
Note how \-escaping must be used to specify the output separators as literals.
In this case, the simpler .ToString('g') would have yielded the same output, but only in cultures that use . as the decimal separator, because the standard g format specifier is culture-sensitive.
See the [timespan]::ToString() documentation as well as the documentation on standard and custom time-span format specifiers.
By contrast, PowerShell uses the invariant culture when interpreting the input format cast to [timespan], where . is the decimal separator; similarly, using a [timespan] instance in expandable strings yields a culture-invariant representation; e.g.:
[timespan] '11:03:15.24' always works, irrespective of the current culture, because the invariant culture expects . as the decimal separator.
"$([timespan] '1' - 1)" always yields 23:59:59.9999999, irrespective of the current culture[2].
As No Refunds No Returns notes, if you're dealing with differently formatted input, possibly from a different culture, you can use [timespan]::Parse() / [timespan]::ParseExact() / [timespan]::TryParseExact()
Parsing the standard formats of a given culture:
[timespan]::Parse(' 0:05:51,15', 'fr-FR') # OK
Note the , as the decimal separator.
If you omit the culture argument (or pass $null), the current culture is applied. Note how that differs from using a [timespan] cast, which is always culture-invariant (and assumes . as the decimal separator).
Parsing with a custom format:
[timespan]::ParseExact(' 0:05:51,15'.Trim(), 'h\:mm\:ss\,ff', $null) # OK
Note that using such a literal custom format is never culture-sensitive, because all the separators must be specified as - escaped - literals (e.g., \:), so $null is passed as the culture argument (IFormatProvider).
Conversely, passing a specific culture only makes sense with the culture-sensitive standard format specifiers, g and G.
Parsing with a culture-aware custom format:
If you don't know what culture will be in effect at runtime, but you want to respect that culture's decimal separator in combination with a custom format, you need to dynamically embed the current culture's decimal separator in your custom format string:
$tsText = ' 0:05:51.15'
[timespan]::ParseExact($tsText.Trim(),
('h\:mm\:ss\{0}ff' -f [cultureinfo]::CurrentCulture.NumberFormat.NumberDecimalSeparator),
$null
) # OK in cultures that use "." as the decimal separator
[1] h and hh only ever reflect the hours not included in full days in the input time span. To reflect the number of days too, prepend something like d\. - there is no format specifier that allows you express the total number of hours across multiple days, but you can use general-purpose string formatting to achieve that - do note, however, that you'll also need custom parsing code in order to convert the resulting string back to a [timespan] instance:
$ts = [timespan] ' 1:05:51.15' + [timespan] '23:03:15.24'
'{0}:{1:mm\:ss\.ff}' -f [Math]::Floor($ts.TotalHours), $ts
[2] At the .NET level, calling .ToString() on objects typically yields a culture-sensitive representation (if the type supports it), but with [timespan] the output happens to be culture-invariant. By contrast, PowerShell explicitly uses the invariant culture behind the scenes in casts and string interpolation, so that "$var" and $var.ToString() may not yield the same representation - see this answer for details.

Related

convert varying timestamps to a uniform format

I am trying to convert a timestamp to milliseconds in a script using an external program. The external program outputs the time in the shortest possible format
Example the external program can output any of the following formats:
"1:33.823" #1min:33 seconds.823 - shortest
"11:33.823" #11min:33 seconds.823 - short
"1:11:33.823" #1 hour 11min:33 seconds.823 - long
I need this value converted in milliseconds. so I tried [timestamp] $mystring but it complains about the format in the first case. I thought about parsing the format manually with something like
$format='h\:mm\:ss\.ff'
[timespan]::ParseExact($mystring,$format , $null)
But the problem with this approach is that I have to predict every possible output format of the external program and implement all the cases manually (if shortest then $format=m\:ss\.fff else if short then $format=...
Or I can possibly split and define the string, loop from the back and define the attributes of the TimeSpan object manually.
My question is: are there any standard (good practice) conversion methods for my case provided in the language or are my "quick and dirty" solutions common?
Thanks
If you ensure that your time-span strings have at least 3 :-separated components, you can cast them directly to [timespan] (which, behind the scenes, delegates to [timespan]::Parse($inputString, [cultureinfo]::InvariantCulture))[1]
#(
"1:33.823" #1min:33 seconds.823 - shortest
"11:33.823" #11min:33 seconds.823 - short
"1:11:33.823" #1 hour 11min:33 seconds.823 - long
) | ForEach-Object {
$colonCount = ($_ -replace '[^:]').Length
[timespan] ('0:' * [math]::Max(0, (3 - $colonCount - 1)) + $_)
}
The above transforms the input strings to 0:1:33.823, 0:11:33.823, and 1:11:33.823 before casting, i.e. prepends 0: components as needed.
[1] PowerShell by design uses the invariant culture when possible - see this answer for more information.

Powershell Cleanest way to convert a string to mmddyyyy format

Thank you in advance...
I have String output:
2021-12-23
the string Base.Type is System.Object
I would like to convert it to:
12-23-2021
or
12/23/2021
any ideas?
Note: This question is a near-duplicate of Change date format from "yyyymmdd" to "mm/dd/yyyy"; while the two are closely related, this question is more overtly focused on both parsing from a string to a date and then back to a string, using a different format; by contrast, the linked question has the from aspect covered as an incidental part of the question.
Bali C, in a comment on the question, came up with the most concise solution, which only requires a small tweak:
# Note the "\"-escaped separators - see below.
PS> Get-Date -Date 2021-12-23 -Format dd\-MM\-yyyy # or dd\/MM\/yyyy
23-12-2021
This will work with most cultures in effect, because format yyyy-MM-dd (as exemplified by your sample input string, '2021-12-23') is a recognized format in all but a few cultures.
PowerShell generally tries to use the invariant culture in order for code to work the same across cultures, but in the case of cmdlet arguments actually is culture-sensitive, which is a historical accident that cannot be fixed without breaking backward compatibility.
See below for a truly culture-invariant solution, which also explains why separator chars. - and / should be escaped for maximum robustness.
Since PowerShell's casts use the invariant culture and format yyyy-MM-dd (as exemplified by your sample input string, '2021-12-23') happens to be a recognized format in that culture, you can simply:
cast your string to [datetime] to create a System.DateTime instance
and then call .ToString() with the desired format string to produce the desired string format, as shown in the many answers to this question.
The following should work irrespective of the specific culture in effect:
PS> ([datetime] '2021-12-23').ToString('MM\-dd\-yyyy')
12-23-2021
PS> ([datetime] '2021-12-23').ToString('MM"/"dd"/"yyyy')
12/23/2021
Note that the separator chars. - and / are escaped / quoted so as to indicate that they're to be used verbatim (you're free to choose between \-escaping and embedded quoting).
This is necessary, because characters such as / are by default interpreted as placeholders for the culture-appropriate date separator, for instance, so - depending on the culture in effect (as reflected in $PSCulture) - they may be translated to a different character, such as ., for instance.
If your original string representation were not recognized in the invariant culture, you can use System.DateTime.ParseExact to parse your input string into a [datetime] (System.DateTime) instance before calling .ToString() as described above:
[datetime]::ParseExact(
'2021-12-23',
'yyyy\-MM\-dd',
$null # current culture
).ToString('MM"-"dd"-"yyyy')
The above again yields '12-23-2021'.
Note that, due to use of $null, the current culture is used for parsing, which has no effect in this case, however, given that the separator chars. use embedded quoting, and given that only digits are involved, not (culture-dependent) names, such as month names.
If you do need the context of a specific culture, pass a [cultureinfo] instance (System.Globalization.CultureInfo) instead of $null; e.g., to parse in the context of the French (France) culture, pass [cultureinfo] 'fr-FR'.

How to reference an index in a string array [duplicate]

I found some strange behavior in PowerShell surrounding arrays and double quotes. If I create and print the first element in an array, such as:
$test = #('testing')
echo $test[0]
Output:
testing
Everything works fine. But if I put double quotes around it:
echo "$test[0]"
Output:
testing[0]
Only the $test variable was evaluated and the array marker [0] was treated literally as a string. The easy fix is to just avoid interpolating array variables in double quotes, or assign them to another variable first. But is this behavior by design?
So when you are using interpolation, by default it interpolates just the next variable in toto. So when you do this:
"$test[0]"
It sees the $test as the next variable, it realizes that this is an array and that it has no good way to display an array, so it decides it can't interpolate and just displays the string as a string. The solution is to explicitly tell PowerShell where the bit to interpolate starts and where it stops:
"$($test[0])"
Note that this behavior is one of my main reasons for using formatted strings instead of relying on interpolation:
"{0}" -f $test[0]
EBGreen's helpful answer contains effective solutions, but only a cursory explanation of PowerShell's string expansion (string interpolation):
Only variables by themselves can be embedded directly inside double-quoted strings ("...") (by contrast, single-quoted strings ('...'), as in many other languages, are for literal contents).
This applies to both regular variables and variables referencing a specific namespace; e.g.:
"var contains: $var", "Path: $env:PATH"
If the first character after the variable name can be mistaken for part of the name - which notably includes : - use {...} around the variable name to disambiguate; e.g.:
"${var}", "${env:PATH}"
To use a $ as a literal, you must escape it with `, PowerShell's escape character; e.g.:
"Variable `$var"
Any character after the variable name - including [ and . is treated as a literal part of the string, so in order to index into embedded variables ($var[0]) or to access a property ($var.Count), you need $(...), the subexpression operator (in fact, $(...) allows you to embed entire statements); e.g.:
"1st element: $($var[0])"
"Element count: $($var.Count)"
"Today's date: $((Get-Date -DisplayHint Date | Out-String).Trim())"
Stringification (to-string conversion) is applied to any variable value / evaluation result that isn't already a string:
Caveat: Where culture-specific formatting can be applied, PowerShell chooses the invariant culture, which largely coincides with the US-English date and number formatting; that is, dates and numbers will be represented in US-like format (e.g., month-first date format and . as the decimal mark).
In essence, the .ToString() method is called on any resulting non-string object or collection (strictly speaking, it is .psobject.ToString(), which overrides .ToString() in some cases, notably for arrays / collections and PS custom objects)
Note that this is not the same representation you get when you output a variable or expression directly, and many types have no meaningful default string representations - they just return their full type name.
However, you can embed $(... | Out-String) in order to explicitly apply PowerShell's default output formatting.
For a more comprehensive discussion of stringification, see this answer.
As stated, using -f, the string-formatting operator (<format-string> -f <arg>[, ...]) is an alternative to string interpolation that separates the literal parts of a string from the variable parts:
'1st element: {0}; count: {1:x}' -f $var[0], $var.Count
Note the use of '...' on the LHS, because the format string (the template) is itself a literal. Using '...' in this case is a good habit to form, both to signal the intent of using literal contents and for the ability to embed $ characters without escaping.
In addition to simple positional placeholders ({0} for the 1st argument. {1} for the 2nd, ...), you may optionally exercise more formatting control over the to-string conversion; in the example above, x requests a hex representation of the number.
For available formats, see the documentation of the .NET framework's String.Format method, which the -f operator is based on.
Pitfall: -f has high precedence, so be sure to enclose RHS expressions other than simple index or property access in (...); e.g., '{0:N2}' -f 1/3 won't work as intended, only '{0:N2}' -f (1/3) will.
Caveats: There are important differences between string interpolation and -f:
Unlike expansion inside "...", the -f operator is culture-sensitive:
Therefore, the following two seemingly equivalent statements do not
yield the same result:
PS> [cultureinfo]::CurrentCulture='fr'; $n=1.2; "expanded: $n"; '-f: {0}' -f $n
expanded: 1.2
-f: 1,2
Note how only the -f-formatted command respected the French (fr) decimal mark (,).
Again, see the previously linked answer for a comprehensive look at when PowerShell is and isn't culture-sensitive.
Unlike expansion inside "...", -f stringifies arrays as <type-name>[]:
PS> $arr = 1, 2, 3; "`$arr: $arr"; '$arr: {0}' -f (, $arr)
$arr: 1 2 3
$arr: System.Object[]
Note how "..." interpolation created a space-separated list of the stringification of all array elements, whereas -f-formatting only printed the array's type name.
(As discussed, $arr inside "..." is equivalent to:
(1, 2, 3).psobject.ToString() and it is the generally invisible helper type [psobject] that provides the friendly representation.)
Also note how (, ...) was used to wrap array $arr in a helper array that ensures that -f sees the expression as a single operand; by default, the array's elements would be treated as individual operands.
In such cases you have to do:
echo "$($test[0])"
Another alternative is to use string formatting
echo "this is {0}" -f $test[0]
Note that this will be the case when you are accessing properties in strings as well. Like "$a.Foo" - should be written as "$($a.Foo)"

How to convert a long to a string without having the scientific notation

I have a Powershell script that get the current time and convert it in Unix Timestamp. I would like to use this value as string parameter to make an HTTP request:
$currentTime = [math]::Round([double](get-date -uformat %s ((get-date).AddMinutes(-5).ToUniversalTime()))*1000)
Invoke-RestMethod -Method Delete -Uri "http://something?unmodifiedSince=$currentTime"
On some Windows machine it works fine but on some other (different Region settings?), I get the current time converted with the scientific notation. For example
http://something?unmodifiedSince=1.53835531189786E+17
How to avoid this conversion?
Update:
In Windows PowerShell 5.1 and PowerShell (Core) 7+ (.NET Framework 4.6+) a much simpler solution is now available, using System.DateTimeOffset.ToUnixTimeSeconds:
[datetimeoffset]::UtcNow.AddMinutes(-5).ToUnixTimeSeconds()
tl;dr:
To work around the problem with culture-specific number formatting, avoid a [double] cast and use [double]::Parse() instead, which by default parses in a culture-sensitive manner:
[Math]::Round(
[double]::Parse(
(Get-Date -Uformat %s ((Get-Date).ToUniversalTime().AddMinutes(-5)))
) * 1000,
0,
'AwayFromZero'
)
The AwayFromZero midpoint-rounding strategy ensures that .5 second values are always rounded up, whereas the default ToEven strategy would situationally round down, namely whenever the integer part of the number happens to be even.
Indeed, your problem stems from the fact that:
Get-Date -UFormat % outputs a string representation of the Unix timestamp
and uses culture-sensitive formatting for the underlying floating-point number[1], which means that in some cultures you'll get a string such as '1538651788,87456' (, as the decimal mark) rather than '1538651788.87456' as the output.
By contrast, PowerShell's casts always use the invariant culture, which only recognizes . as the decimal mark - and ignores ,, which is considered a thousands-grouping character.
PS> [double] '1538651788,87456'
153865178887456 # !! , was IGNORED
Because the decimal mark was ignored and there are 5 decimal places, the resulting number is too large by a factor of 10,000 in this case (though note that the number of decimal places can vary, because trailing zeros aren't displayed).
If you then multiply that result with 1000, you get a number so large that PowerShell defaults its string representation to the scientific format that you've experienced:
PS> [double] '1538651788,87456' * 1000
1.53865178887456E+17 # !! scientific notation.
[1] Optional reading: Get-Date -UFormat %s problems in Windows PowerShell vs. PowerShell Core (v6+):
Unix timestamps are integers, so Get-Date -UFormat %s shouldn't return a floating-point number to begin with. This problem has been corrected in PowerShell Core.
Unix timestamps are expressed in UTC, but Windows PowerShell only returns the correct value if you explicitly pass a UTC [datetime] instance. This problem has been corrected in PowerShell Core (v6+).
E.g., to get the current time's Unix timestamp in Windows PowerShell, using
Get-Date -UFormat %s is not enough; use Get-Date -UFormat %s ([datetime]::UtcNow) instead.
In short: This question's problem wouldn't have arisen in PowerShell Core, because string representations of integers aren't culture-sensitive; additionally, the need for rounding goes away, as does the need to convert the input date to UTC, so that a PowerShell Core solution simplifies to:
# PowerShell *Core* only
1000 * (Get-Date -UFormat %s ((Get-Date).AddMinutes(-5)))
Note: This technically returns a [double] instance, but one without decimal places; use [long] (...) if an integer type is expressly needed.
I use this small function to get the current date and time as Unix Timestamp.
It returns an int64 so you should not have any trouble adding it in the url:
function Get-CurrentUnixTimeStamp {
[DateTime]$epoch = New-Object System.DateTime 1970, 1, 1, 0, 0, 0, 0, Utc
[TimeSpan]$diff = (Get-Date).ToUniversalTime() - $epoch
return [int64][Math]::Floor($diff.TotalSeconds)
}
$currentTime = Get-CurrentUnixTimeStamp
Invoke-RestMethod -Method Delete -Uri "http://something?unmodifiedSince=$currentTime"
You could indicate explicitly $currentTime variable data type as [Decimal] instead of auto assigned [Double]. Like so:
[Decimal]$currentTime = [math]::Round([double](get-date -uformat %s ((get-date).AddMinutes(-5).ToUniversalTime()))*1000)

PowerShell outputting array items when interpolating within double quotes

I found some strange behavior in PowerShell surrounding arrays and double quotes. If I create and print the first element in an array, such as:
$test = #('testing')
echo $test[0]
Output:
testing
Everything works fine. But if I put double quotes around it:
echo "$test[0]"
Output:
testing[0]
Only the $test variable was evaluated and the array marker [0] was treated literally as a string. The easy fix is to just avoid interpolating array variables in double quotes, or assign them to another variable first. But is this behavior by design?
So when you are using interpolation, by default it interpolates just the next variable in toto. So when you do this:
"$test[0]"
It sees the $test as the next variable, it realizes that this is an array and that it has no good way to display an array, so it decides it can't interpolate and just displays the string as a string. The solution is to explicitly tell PowerShell where the bit to interpolate starts and where it stops:
"$($test[0])"
Note that this behavior is one of my main reasons for using formatted strings instead of relying on interpolation:
"{0}" -f $test[0]
EBGreen's helpful answer contains effective solutions, but only a cursory explanation of PowerShell's string expansion (string interpolation):
Only variables by themselves can be embedded directly inside double-quoted strings ("...") (by contrast, single-quoted strings ('...'), as in many other languages, are for literal contents).
This applies to both regular variables and variables referencing a specific namespace; e.g.:
"var contains: $var", "Path: $env:PATH"
If the first character after the variable name can be mistaken for part of the name - which notably includes : - use {...} around the variable name to disambiguate; e.g.:
"${var}", "${env:PATH}"
To use a $ as a literal, you must escape it with `, PowerShell's escape character; e.g.:
"Variable `$var"
Any character after the variable name - including [ and . is treated as a literal part of the string, so in order to index into embedded variables ($var[0]) or to access a property ($var.Count), you need $(...), the subexpression operator (in fact, $(...) allows you to embed entire statements); e.g.:
"1st element: $($var[0])"
"Element count: $($var.Count)"
"Today's date: $((Get-Date -DisplayHint Date | Out-String).Trim())"
Stringification (to-string conversion) is applied to any variable value / evaluation result that isn't already a string:
Caveat: Where culture-specific formatting can be applied, PowerShell chooses the invariant culture, which largely coincides with the US-English date and number formatting; that is, dates and numbers will be represented in US-like format (e.g., month-first date format and . as the decimal mark).
In essence, the .ToString() method is called on any resulting non-string object or collection (strictly speaking, it is .psobject.ToString(), which overrides .ToString() in some cases, notably for arrays / collections and PS custom objects)
Note that this is not the same representation you get when you output a variable or expression directly, and many types have no meaningful default string representations - they just return their full type name.
However, you can embed $(... | Out-String) in order to explicitly apply PowerShell's default output formatting.
For a more comprehensive discussion of stringification, see this answer.
As stated, using -f, the string-formatting operator (<format-string> -f <arg>[, ...]) is an alternative to string interpolation that separates the literal parts of a string from the variable parts:
'1st element: {0}; count: {1:x}' -f $var[0], $var.Count
Note the use of '...' on the LHS, because the format string (the template) is itself a literal. Using '...' in this case is a good habit to form, both to signal the intent of using literal contents and for the ability to embed $ characters without escaping.
In addition to simple positional placeholders ({0} for the 1st argument. {1} for the 2nd, ...), you may optionally exercise more formatting control over the to-string conversion; in the example above, x requests a hex representation of the number.
For available formats, see the documentation of the .NET framework's String.Format method, which the -f operator is based on.
Pitfall: -f has high precedence, so be sure to enclose RHS expressions other than simple index or property access in (...); e.g., '{0:N2}' -f 1/3 won't work as intended, only '{0:N2}' -f (1/3) will.
Caveats: There are important differences between string interpolation and -f:
Unlike expansion inside "...", the -f operator is culture-sensitive:
Therefore, the following two seemingly equivalent statements do not
yield the same result:
PS> [cultureinfo]::CurrentCulture='fr'; $n=1.2; "expanded: $n"; '-f: {0}' -f $n
expanded: 1.2
-f: 1,2
Note how only the -f-formatted command respected the French (fr) decimal mark (,).
Again, see the previously linked answer for a comprehensive look at when PowerShell is and isn't culture-sensitive.
Unlike expansion inside "...", -f stringifies arrays as <type-name>[]:
PS> $arr = 1, 2, 3; "`$arr: $arr"; '$arr: {0}' -f (, $arr)
$arr: 1 2 3
$arr: System.Object[]
Note how "..." interpolation created a space-separated list of the stringification of all array elements, whereas -f-formatting only printed the array's type name.
(As discussed, $arr inside "..." is equivalent to:
(1, 2, 3).psobject.ToString() and it is the generally invisible helper type [psobject] that provides the friendly representation.)
Also note how (, ...) was used to wrap array $arr in a helper array that ensures that -f sees the expression as a single operand; by default, the array's elements would be treated as individual operands.
In such cases you have to do:
echo "$($test[0])"
Another alternative is to use string formatting
echo "this is {0}" -f $test[0]
Note that this will be the case when you are accessing properties in strings as well. Like "$a.Foo" - should be written as "$($a.Foo)"