Subtract months from date in velocity - date

I am trying to get the date a certain number of months ago from a given date
I've tried converting to Calendar to then use the add method, but that didn't work:
#set( $myCalendar = $date.toCalendar($endDate))
#set( $startdate = $calendarstart.add("MONTH", -$minusMonths))
I've tried to do this in a few different ways:
#set( $temp = 0 - $numberOfMissedPremiums)
#evaluate($calendarstart.add( 2 , $temp ))
#set( $a = $calendarstart.add( 2 , $temp ))
I've even tried defining blocks, but that didn't work either

The Calendar.add() method takes an int, not a string, for the field indicator. To change the months, you need the Calendar.MONTH method, which is 2.
So you would write:
#set( $startdate = $calendarstart.clone() )
$startdate.add(2, -$minusMonths)
Also, if you're still using Velocity 1.7, you may need to write:
#set( $startdate = $calendarstart.clone() )
#set( $temp = 0 - $minusMonths )
$startdate.add(2, $temp)
To nicify a bit this code, you can use the FieldTool, which you can configure like this:
<tools>
<toolbox scope="application">
<tool key = "cal"
class="org.apache.velocity.tools.generic.FieldTool"
include="java.util.Date"/>
</toolbox>
</tools>
So you can now write:
#set( $startdate = $calendarstart.clone() )
$startdate = $calendarstart.add($cal.MONTH, -$minusMonths)
(and I let you adapt the 1.7 version).
As a final note, please take a moment to consider moving this code to a Java tool. VTL is a template language, and what you are trying to do seems more like a business logic task.
(Edited) As noted in #luis-rico first comment, Calendar.add() returns void. Calendar is a mutable object, so if you want to keep the original Calendar instance, you will have to clone it first. Then you can directly call $startdate.add(2, -$minusMonth) in the template, since void results aren't printed.

OK, so I finally managed to get it working:
#set( $minusmonths = $math.mul($numberOfMonthsToSubtract,-1))
#set( $Mycalendar = $date.getCalendar())
#set( $Dateformat = "yyyy-MM-dd" )
$Mycalendar.setTime($convert.parseDate(
$endDate,
$Dateformat
))
#set( $calConst = $field.in($Mycalendar) )
$Mycalendar.add($calConst.MONTH, $minusmonths.intValue())
The problem seemed to be on the Calendar.add() not parsing the value correctly, it only accepts Int and was receiving a Number.
The way I defined the calendar variable was giving some problems as well.

Related

combining objects and arrays with condition in powershell

My data($messages) looks like this
Source: sourceA
Message : {#{messagetext = abc;publishedtime = 10/5/2020}, #{messagetext = def;publishedtime = 10/12/2020}
I am trying to filter the message by displaying only messages that happened from last week until now then combining it with the source property. It is possible that a source may have multiple lines if several publishedtime happened last week.
$filterDate = (Get-Date).AddDays(-7)
$messages | select source, #{n='messagetext' ; e={???}} , #{n='publishedtime' ; e={???}}
I am lost on how to add the calculated property with the condition. I was also thinking if this could be done with a loop.
I also tried the following but having some errors
$Messages | Where-Object {([DateTime]($_.messages).publishedtime).ToUniversalTime() -ge $filterDate} but having some error
There is no need to use a calculated property. First filter the $messages collection by date. Like so,
$filterDate = (Get-Date).AddDays(-7)
$lastWeek = $messages | ? { $_.PublishedTime -ge $filterDate }
Now the $lastWeek collection contains only messages that were published within a week. Depending on the exact format of PublishedTime and your locale settings, it might need to be parsed as a date. If that's the case, please edit the question and show a few examples of actual data. Sanitize the messages if needed.
I think you should always compare dates to dates, and not compare a datetime object to a string, especially since in your question it is unclear what exact format the date is in.
Probably the format used is 'M/d/yyyy', but it could just aswell be 'd/M/yyyy' or even 'MM/d/yyyy'
$dateFormat = 'M/d/yyyy' # <-- this is unclear in the question, could also be 'd/M/yyyy' or 'MM/d/yyyy'
# you can set the Time part to all zero, as the dates in the $Messages objects also do not have that
$filterDate = (Get-Date).AddDays(-7).Date
$Messages | Where-Object {[DateTime]::ParseExact($_.Message.publishedtime, $dateFormat, $null) -ge $filterDate}

Perform Math per Column in CSV using Column Values

I'm attempting to use PowerShell to pickup a CSV file, subtract value of one column from another and put it in a third, then print the CSV to default printer.
I've got everything working except the math. It imports, sets up my headers, and prints. However it doesn't seem to execute my foreach to do the math. It runs without errors though.
$hhsignscsv = Import-Csv -Header ("PLU","Description","Quantity","Price","FreqShopType","FreqShopValue","FreqShopPrice","LabelFormat","LabelQTY","SizeMeasurement","Limit") -Path hhsignsmod.csv
foreach ($hhsigns in $hhsignscsv) {
$PLU = $hhsigns.PLU
$Description = $hhsigns.Description
$Quantity = $hhsigns.Quantity
$Price = $hhsigns.Price
$FreqShopType = $hhsigns.FreqShopType
$FreqShopValue = $hhsigns.FreqShopValue
$FreqShopPrice = $hhsigns.FreqShopPrice
$LabelFormat = $hhsigns.LabelFormat
$LabelQTY = $hhsigns.LabelQTY
$SizeMeasurement = $hhsigns.SizeMeasurement
$Limit = $hhsigns.Limit
}
foreach ($hhsigns in $hhsignscsv) {
$FreqShopPrice = $Price - $FreqShopValue
}
Out-Printer -InputObject $hhsignscsv
Can anyone tell me why the Math part ($FreqShopPrice = $Price - $FreqShopValue) won't put the values into the $FreqShopPrice column? I don't get any syntax errors when debugging or running but on the print out the $FreqShopPrice is blank instead of containing the value of the subtraction.
The statement
$FreqShopPrice = $hhsigns.FreqShopPrice
copies the value of the CSV field FreqShopPrice into the variable $FreqShopPrice.
The statement
$FreqShopPrice = $Price - $FreqShopValue
updates the variable $FreqShopPrice with the difference between the variables $Price and $FreqShopValue. However, since you filled those variables in a separate loop before the current loop they contain the values from the last record from the CSV.
To actually update the field FreqShopPrice in the CSV you need to do it like this:
foreach($hhsigns in $hhsignscsv) {
$hhsigns.FreqShopPrice = $hhsigns.Price - $hhsigns.FreqShopValue
}
Remove the other loop. It serves no purpose except burning CPU cycles.

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)

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

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.