Calculate total seconds from string in format "HH:mm:ss,fff" - powershell

In PowerShell V2, I want to calculate the total seconds and milliseconds of a given string.
My string is 00:03:56,908 and the desired output would be 236.908
My working, but awkward code is
$a = "00:03:56,908"
$a = [datetime]::ParseExact($a,"HH:mm:ss,fff",$null)
[string]($a.Hour*3600 + $a.Minute*60 + $a.Second) +"."+ [string]$a.Millisecond
Is there a smarter / shorter way to achieve this?
All I found was .totalseconds from a TimeSpan object. But this code was even longer in my attempt

The problem with the TimeSpan class in .NET versions earlier than 4.0 is that it's not handling different cultures or formatting strings very well. Given that your string has a comma instead of a period, we'll have to change that if we want to parse it to a timespan, but I think that's still the best way to go at it.
$timeString = "00:03:56,908"
$timeStringWithPeriod = $timeString.Replace(",",".")
$timespan = [TimeSpan]::Parse($timestringWithPeriod)
$totalSeconds = $timespan.TotalSeconds

I wouldn't shy away from TotalSeconds, it can be useful in this circumstance... and reasonably short, if you just extract that property:
PS C:\> $a = "00:03:56,908"
PS C:\> $a = [datetime]::ParseExact($a,"HH:mm:ss,fff",$null)
PS C:\> (New-TimeSpan -Start (Get-Date).Date -End $a).TotalSeconds
236.908
Putting New-TimeSpan in parenthesis allows us to evaluate that statement first, and then to extract TotalSeconds afterwards. Using (Get-Date).Date is important because it defines midnight, and since you converted $a to a DateTime where the date is today, we need to use midnight today as our Start time.

Related

How to calculate difference in minutes between START and END times in a file using Powershell or cmd

The file has the following content.
START Sun 04/17/2022 13:01:44.13
END Sun 04/17/2022 14:41:50.60
I'm trying to find a way to automate how many minutes it took from END to START time, I don't care about the seconds, just need to know how many minutes took to run.
In this example it took 100 minutes but I had to calculate manually.
I'd appreciate any feed back.
Assuming that your input file is named file.txt:
$dates = [datetime[]] ((Get-Content file.txt) -replace '^\w+ +')
[int] ($dates[1] - $dates[0]).TotalMinutes # -> 100
Your date-time strings are in a format that can be cast to [datetime] directly.
To extract the date-time string from each line, the first whitespace-separated token must be removed, which the regex-based -replace operator can do:
-replace '^\w+ +' matches one or more (+) word characters (\w, letters, digits or _) at the start (^) of the string, followed by one more spaces, matching something like END . Since no replacement string is specified, the matched string is effectively removed.
-replace can operate on an array as its LHS, in which case the operation is performed on each element; therefore, all lines of the input file can serve as the input at once, as returned by Get-Content.
Subtracting two [datetime] instances yields a [timespan] instance representing the span of time between the two points of time:
Its .TotalMinutes property reports the span in fractional minutes (as a [double] instance)
Casting that value to [int] yields an integral number of minutes, using half-to-even midpoint-rounding.
New-TimeSpan and the ParseExact() or TryParse() method of from an object instance of DateTime will help you here.
$Start = [datetime]::ParseExact('04/17/2022 13:01:44.13', 'MM/dd/yyyy HH:mm:ss.ff', $null)
$End = [datetime]::ParseExact('04/17/2022 14:41:50.60', 'MM/dd/yyyy HH:mm:ss.ff', $null)
New-TimeSpan -Start $Start -End $End
This will provide an output like the below:
Days : 0
Hours : 1
Minutes : 40
Seconds : 6
Milliseconds : 470
Ticks : 60064700000
TotalDays : 0.0695193287037037
TotalHours : 1.66846388888889
TotalMinutes : 100.107833333333
TotalSeconds : 6006.47
TotalMilliseconds : 6006470
Access specifically minutes by doing something similar to the below:
> $result = New-TimeSpan -Start $Start -End $End
> $result.TotalMinutes
100.107833333333
# Round up to 2 decimal places
> [Math]::Round($result.TotalMinutes, 2)
100.11
# Round up to 0 decimal places
> [Math]::Round($result.TotalMinutes, 0)
100

How to add durations in Powershell?

I have two durations in my code. And I want to add them to get the total duration.
Many code examples add some durations to a date, but I have here two durations.
$startOfAnalysis = $(Get-Date)
#Analysis code here
$analysisDuration = $(Get-Date) - $startOfAnalysis
$totalAnalysisTime = "{0:HH:mm:ss}" -f ([datetime] $analysisDuration.Ticks)
$totalAnalysisTime #Outputs similar to 00:16:21
$startOfExecution = $(Get-Date)
#Execution code here
$executionDuration = $(Get-Date) - $startOfExecution
$totalExecutionTime = "{0:HH:mm:ss}" -f ([datetime]$executionDuration.Ticks)
$totalExecutionTime #Outputs similar to 00:13:39
#($totalAnalysisTime+$totalExecutionTime) How to add the two to get 00:30:00?
How do I sum it up to make it to 30 minutes, i. e. 00:30:00?
You can add two TimeSpan instances together directly, no need for conversion to DateTime and back:
$total = $executionDuration + $analysisDuration
Just like with a DateTime instance, you can now use the -f string format operator, but beware that you need to escape special characters:
'{0:hh\:mm\:ss}' -f $total
I think there are two options.
First one:
Convert your time back to seconds and then sum them up. Like that:
$Time1 = "01:56:40"
$textReformat1 = $Time1 -replace ",","."
$Time2 = "01:56:40"
$textReformat2 = $Time2 -replace ",","."
$seconds = ([TimeSpan]::Parse($textReformat1)).TotalSeconds + ([TimeSpan]::Parse($textReformat2)).TotalSeconds
$TimeSpan = [timespan]::fromseconds($seconds)
("{0:HH\:mm\:ss}" -f [DateTime]$TimeSpan.Ticks)
And option two (the simple one):
Just calculate everything together before you convert them to a string. Something like that:
("{0:HH\:mm\:ss}" -f ([datetime]$analysisDuration.Ticks + $executionDuration.Ticks))
Credits to:
Convert seconds to hh:mm:ss,fff format in PowerShell
For the example how to convert the timespan back to seconds

Convert a string to datetime in PowerShell

I am using PowerShell to try and convert a string to a datetime. It should be easy, right?
I am getting the string from a CSV import, and it comes in the format of Jul-16. I have tried multiple ways of getting it into the format I want which is yyyy-MM-dd and I am currently at the following.
$invoice = $object.'Invoice Month'
$invoice = "01-" + $invoice
$invoice = [datetime]::parseexact($invoice, 'yyyy-MM-dd', $null)
But I get the error:
String was not recognized as a valid DateTime.
Am I missing something?
ParseExact is told the format of the date it is expected to parse, not the format you wish to get out.
$invoice = '01-Jul-16'
[datetime]::parseexact($invoice, 'dd-MMM-yy', $null)
If you then wish to output a date string:
[datetime]::parseexact($invoice, 'dd-MMM-yy', $null).ToString('yyyy-MM-dd')
You can simply cast strings to DateTime:
[DateTime]"2020-7-16"
or
[DateTime]"Jul-16"
or
$myDate = [DateTime]"Jul-16";
And you can format the resulting DateTime variable by doing something like this:
'{0:yyyy-MM-dd}' -f [DateTime]'Jul-16'
or
([DateTime]"Jul-16").ToString('yyyy-MM-dd')
or
$myDate = [DateTime]"Jul-16";
'{0:yyyy-MM-dd}' -f $myDate
You need to specify the format it already has, in order to parse it:
$InvoiceDate = [datetime]::ParseExact($invoice, "dd-MMM-yy", $null)
Now you can output it in the format you need:
$InvoiceDate.ToString('yyyy-MM-dd')
or
'{0:yyyy-MM-dd}' -f $InvoiceDate
Chris Dents' answer has already covered the OPs' question but seeing as this was the top search on google for PowerShell format string as date I thought I'd give a different string example.
If like me, you get the time string like this 20190720170000.000000+000
An important thing to note is you need to use ToUniversalTime() when using [System.Management.ManagementDateTimeConverter] otherwise you get offset times against your input.
PS Code
cls
Write-Host "This example is for the 24hr clock with HH"
Write-Host "ToUniversalTime() must be used when using [System.Management.ManagementDateTimeConverter]"
$my_date_24hr_time = "20190720170000.000000+000"
$date_format = "yyyy-MM-dd HH:mm"
[System.Management.ManagementDateTimeConverter]::ToDateTime($my_date_24hr_time).ToUniversalTime();
[System.Management.ManagementDateTimeConverter]::ToDateTime($my_date_24hr_time).ToUniversalTime().ToSTring($date_format)
[datetime]::ParseExact($my_date_24hr_time,"yyyyMMddHHmmss.000000+000",$null).ToSTring($date_format)
Write-Host
Write-Host "-----------------------------"
Write-Host
Write-Host "This example is for the am pm clock with hh"
Write-Host "Again, ToUniversalTime() must be used when using [System.Management.ManagementDateTimeConverter]"
Write-Host
$my_date_ampm_time = "20190720110000.000000+000"
[System.Management.ManagementDateTimeConverter]::ToDateTime($my_date_ampm_time).ToUniversalTime();
[System.Management.ManagementDateTimeConverter]::ToDateTime($my_date_ampm_time).ToUniversalTime().ToSTring($date_format)
[datetime]::ParseExact($my_date_ampm_time,"yyyyMMddhhmmss.000000+000",$null).ToSTring($date_format)
Output
This example is for the 24hr clock with HH
ToUniversalTime() must be used when using [System.Management.ManagementDateTimeConverter]
20 July 2019 17:00:00
2019-07-20 17:00
2019-07-20 17:00
-----------------------------
This example is for the am pm clock with hh
Again, ToUniversalTime() must be used when using [System.Management.ManagementDateTimeConverter]
20 July 2019 11:00:00
2019-07-20 11:00
2019-07-20 11:00
MS doc on [Management.ManagementDateTimeConverter]:
https://learn.microsoft.com/en-us/dotnet/api/system.management.managementdatetimeconverter?view=dotnet-plat-ext-3.1
$invoice = "Jul-16"
[datetime]$newInvoice = "01-" + $invoice
$newInvoice.ToString("yyyy-MM-dd")
There you go, use a type accelerator, but also into a new var, if you want to use it elsewhere, use it like so: $newInvoice.ToString("yyyy-MM-dd")as $newInvoice will always be in the datetime format, unless you cast it as a string afterwards, but will lose the ability to perform datetime functions - adding days etc...
Hope below helps!
PS C:\Users\aameer>$invoice = $object.'Invoice Month'
$invoice = "01-" + $invoice
[datetime]$Format_date =$invoice
Now type is converted. You can use method or can access any property.
Example :$Format_date.AddDays(5)
It's very easy; in my case it works with;
Input:
$Date = '29-07-2022'
DateFormat Convertion:
[datetime]::parseexact($date, 'dd-MM-yyyy', $null).ToString('dd-MMMM-yyyy')
Output:
I had a different but related need to convert a number (seconds) into days/hours/seconds etc.
$seconds = 41414141
New-Timespan -seconds $seconds

Change date format from "yyyymmdd" to "mm/dd/yyyy"

I've tried a lot of different ways and I can't seem to get it right.
Here is the code of what I have tried so far...
[String]$dateValue = '20161212'
[String]$dateStamp = $dateValue -f (Get-Date)
[String]$dateStamp2 = ([datetime]::parseexact($dateValue, "yyyyMMdd", [System.Globalization.CultureInfo]::InvariantCulture)).Date
[String]$dateStamp3 = ([datetime]::FromFileTime($dateValue)).ToString('g')
Write-Host '$dateStamp = ' $dateStamp
Write-Host '$dateStamp2 = ' $dateStamp2
Write-Host '$dateStamp3 = ' $dateStamp3
Current Code Output
$dateStamp = 20161212
$dateStamp2 = 12/12/2016 00:00:00
$dateStamp3 = 12/31/1600 5:00 PM
Desired Code Output
$dateStamp = 12/12/2016
Any Ideas?
Once you have a datetime object it's easy to convert it to whatever string format you need. You are so close with your second attempt. Adding ToString allows you to specify a string format.
([datetime]::parseexact($dateValue, "yyyyMMdd", [System.Globalization.CultureInfo]::InvariantCulture)).ToString("dd/MM/yyyy")
Given that you have a culture-invariant string as your input and that you want a fixed output format, you may as well perform string parsing, without the need to convert to an intermediate [datetime] instance:
> '20161213' -replace '\d{2}(\d{2})(\d{2})(\d{2})', '$2/$3/$1'
12/13/16
Note that I've changed the day to be different from the month to better highlight the reformatting that takes place.
Generally, though, the [datetime]-based method demonstrated in Nick's helpful answer gives you the most flexibility.

extract filename and data from file

I have hourly log files from the past couple of months and i would like to export the data to MS Charts.
I've managed to get the data out, but having problems with getting the date on the x axis of the Chart.
The file name of the log file contains the date and i've tried using the creatation date or last write time. The closest i've got is to count the number of logs then divide by 24, but the dates it generates does not match up with the data.
Any ideas please?
Everything works as expected, but i cant get the X Axis to display the correct dates. in theory, it should calculate the 11 June as the start date, and the end date is yesterday's date.
the format of the file it reads from is
HealthCheck Wed 10 Sep 2014 - 05.00 AM.log
Ideally, I would like to get the date from the file name. I dont want to rely on calculations on when the file was written as this is prone to errors.
[void][Reflection.Assembly]::LoadWithPartialName("System.Windows.Forms")
[void][System.Reflection.Assembly]::LoadWithPartialName("System.Windows.Forms.DataVisualization")
$Farm = "XAccess"
#gets files from yesterday
$Date = Get-Date #-Uformat %x
$Date = ($Date).adddays(-1)
$Date = $Date.ToString("M/d/yyyy")
$files = get-childitem "c:\$Farm*.log" | Where{$_.LastWriteTime -lt $date}
$ActiveSessions = Select-String -path $files '(?<=^"*Total Active Sessions: )\d+(?=)'|
ForEach-Object {$_.Matches[0].Value}
#Calculates numbers of days
$datapoints = $ActiveSessions.count/24
#== Creates Chart ==#
# create chart object
$Chart = New-object System.Windows.Forms.DataVisualization.Charting.Chart
$Chart.Width = 1600
$Chart.Height = 800
# create a chartarea to draw on and add to chart
$ChartArea = New-Object System.Windows.Forms.DataVisualization.Charting.ChartArea
$Chart.ChartAreas.Add($ChartArea)
[void]$Chart.Series.Add("Data")
$Chart.ChartAreas["ChartArea1"].AxisX.Interval = 24
$Chart.ChartAreas["ChartArea1"].AxisX.LabelStyle.Angle = -40
$Chart.ChartAreas["ChartArea1"].AxisY.Interval = 5
$Chart.ChartAreas["ChartArea1"].AxisY.title = "Active Sessions"
$chart.Series.Add('ChartArea1')
# add a data point for each server
foreach ($session in $ActiveSessions)
{
$dp1 = new-object System.Windows.Forms.DataVisualization.Charting.DataPoint(0, $session)
$dp1.AxisLabel = (get-date).adddays(-"$datapoints").tostring("ddd dd MMM")
$datapoints = $datapoints - 0.0416666666666667
$Chart.Series["Data"].Points.Add($dp1)
}
# set the title to the date and time
$title = new-object System.Windows.Forms.DataVisualization.Charting.Title
$Chart.Titles.Add( $title )
$Chart.Titles[0].Font = "Arial,13pt"
$Chart.Titles[0].Text = "Year to Date sessions for $Farm"
# save the chart to a file
$Chart.SaveImage("C:\$Farm Farm.png","png")
Ok, so you want the date from the file. No problem, we can do that. Let's start with how best to do it... We have options (as usual) to choose from. Wait, I'm getting ahead of myself. Where do we want to define it? Well, simplest place I can think of is anyplace that you are looping through the files in question and getting other data, so we can keep everything properly associated. So, it looks like the lines that you assign $ActiveSessions is going to be our best bet.
Now, back to how to get the date. We could use the SubString method, but that just seems messy to me when we have a defined format for the text. Personally I'd rather do a regex match. So what I see in the file name is a three letter day abbreviation, then a 1-2 digit day, a three letter month, and a four digit year. After that there's a hyphen, and two digit hour, period, two digit minute, and the AM/PM designator.
So, as far as [datetime] formats go that's ddd d MMM yyyy - hh.mm tt. We'll get back to that. How to extract the date? Oh right, a regex. Here's how I'd get that:
([regex]"\w{3} \d{1,2} \w{3} \d{4} - \d{2}\.\d{2} (?:AM|PM)").matches($_.Filename).value
That declares the pattern as a regex object, and then uses it's Matches() method, and get the value of the match. So we have the date, now to actually make it usable. Here's where we get back to that DateTime format. We can use the [DateTime]::ParseExact() method to get the date time, even with the strange formatting. Now we are going to be getting it in the ForEach loop, and are extracting the date from the FileName property of the object that Select-String is feeding the loop. Here's what it will look like:
[datetime]::ParseExact(([regex]"\w{3} \d{1,2} \w{3} \d{4} - \d{2}\.\d{2} (?:AM|PM)").matches($_.Filename).value,"ddd d MMM yyyy - hh.mm tt",$null)
Ok, that actually gives us a nice usable datetime object. So that ForEach loop is already spitting back the active sessions, and $ActiveSessions is an array of strings. Let's change that a little and make it an array of objects, and each object will have two properties now, Sessions and Date. So the inside of the ForEach loop has to make an object with those properties. Easiest way (with PowerShell v3 or higher, I'll show you the hard way if you are running an old version of PowerShell and need me to) is:
[PSCustomObject][Ordered]#{
'Sessions'=$_.Matches[0].Value
'Date'=[datetime]::ParseExact(([regex]"\w{3} \d{1,2} \w{3} \d{4} - \d{2}\.\d{2} (?:AM|PM)").matches($_.Filename).value,"ddd d MMM yyyy - hh.mm tt",$null)
}
That makes the whole $ActiveSessions = line look like:
$ActiveSessions = Select-String -path $files '(?<=^"*Total Active Sessions: )\d+(?=)'|
ForEach-Object {[PSCustomObject][Ordered]#{
'Sessions'=$_.Matches[0].Value
'Date'=[datetime]::ParseExact(([regex]"\w{3} \d{1,2} \w{3} \d{4} - \d{2}\.\d{2} (?:AM|PM)").matches($_.Filename).value,"ddd d MMM yyyy - hh.mm tt",$null)
}
}
Only thing that leaves is to change where that variable is referenced:
foreach ($session in $ActiveSessions)
{
$dp1 = new-object System.Windows.Forms.DataVisualization.Charting.DataPoint(0, $session.session)
$dp1.AxisLabel = $Session.Date.tostring("ddd dd MMM")
$Chart.Series["Data"].Points.Add($dp1)
}
That should do it for you.
I tried using regex but could not get pass the error i mentioned.
so after a bit more research, i decided exporting the creation date of the file and the data i needed into a CSV file, then creating the chart from that. So this is the code i used to get the data in to csv:
$files = get-childitem "c:\$farm*.log" | sort CreationTime
Foreach ($file in $files)
{
$FileCreation = $file.CreationTime.Date.ToString('ddd dd MMM yyyy')
$ActiveSessions = Select-String -path $file '(?<=^"*Total Active Sessions: )\d+(?=)' | ForEach-Object {$_.Matches[0].Value}
Add-Content d:\licInUse.csv "$FileCreation,$ActiveSessions"
}
# processing the Data
$Processes = Import-Csv -path d:\licInUse.csv -Delimiter ',' -Header "Date","Count"
$DateNames = #(foreach($Date in $Processes){$Date.Date})
$SessionCount = #(foreach($Date in $Processes){$Date.Count})
then used the following to plot the data
$Chart.Series["Data"].Points.DataBindXY($DateNames, $SessionCount)