How to find next business day with powershell ?
Well, my phone allows me to set which days are business days, but Windows/.NET won't, so I assume Monday through Friday.
Note: As the question includes "golf" I am golfing this one, that is trying to use as few bytes for the script as possible. The code is not necessarily readable as a result.
The easiest and most straightforward way to do is would be to start with today, add a day and look whether it is in the wanted range:
PS> $d = [DateTime]::Now.AddDays(1); while ($d.DayOfWeek -eq "Saturday" -or $d.DayOfWeek -eq "Sunday") { $d = $d.AddDays(1) }; $d
Montag, 22. Juni 2009 19:50:27
We can shorten that a little, though:
PS> $d=(Get-Date)+"1";for(;6,0-contains$d.DayOfWeek){$d+="1"}$d
Montag, 22. Juni 2009 19:52:31
But we can also try it differently, using the pipeline. The next business day is at least one and at most three days away, so we can generate a list of possible dates and filter them accordingly and at last, select the first one:
PS> #(1..3|%{(Get-Date).AddDays($_)}|?{$_.DayOfWeek -ge "Monday" -and $_.DayOfWeek -le "Friday"})[0]
Montag, 22. Juni 2009 22:11:19
or shorter:
PS> #(1..3|%{(Get-Date)+"$_"}|?{1..5-contains$_.DayOfWeek})[0]
Montag, 22. Juni 2009 19:55:53
By letting the range go to 4 we can guarantee that it always returns at least two workdays and save the # operator to force an array:
PS> (1..4|%{(Get-Date)+"$_"}|?{1..5-contains$_.DayOfWeek})[0]
Montag, 22. Juni 2009 20:24:06
This is pretty short too (but uses aliases):
,(1,2-eq7-(date).dayofweek)|%{(date)+"$(1+$_[0])"}
In one single statement:
(date)+"$(1+$(#(1,2-eq7-(date).dayofweek)))"
A few notes about this approach:
In Powershell (v1 at least), comparisons with collections return items where the condition is true, for example:
PS> 1,2 -eq 1
PS> 1
I'm taking advantage of the fact that the actual exceptions to the rule today + 1 to calculate the next business day are only Friday (+3 days) and Saturday (+2 days).
Here is another pipline way:
(#(1..4) | foreach { if (([datetime]::Now.AddDays($_)).DayOfWeek -ne "Sunday" -and ([datetime]::Now.AddDays($_)).DayOfWeek -ne "Saturday") {[datetime]::Now.AddDays($_); }})[0]
Not sure why I have to use (1..4) instead of (1..3) however.
I found the sample code in the first answer to be really difficult to follow so I rewrote it to be a bit easier to see what was happening. I'm still using the -eq behavior where the -eq test will return the matching value.
$date = get-date "2013 Apr 24"
write-host "Based on your date"
$date
write-host "next business day (skipping saturday and sunday)"
$Date.AddDays(1 + $(1,2 -eq 7 - [int]$date.dayofweek) )
write-host "Next week monday"
$Date.AddDays(1 + $(0,1,2,3,4,5,6,7 -eq 7 - [int]$date.dayofweek) )
Related
I have to extrapolate student year of graduation based on their grade number. Their student ID number has the 2-digit year of graduation in it, however this has proven to be inaccurate sometimes due to students who have been held back and other ID number based anomalies.
I wrote a powershell script to do this for me as part of a larger script to take a CSV exported from powerschool and check their location in active directory based on their graduation year. The portion of the script I'm having trouble with is as follows:
function GradeToYog($grade){ # converts grade number to year of grad because IDNUM is not a reliable indicator of grad year
if($grade -ne $null){
# get current date
$date = Get-Date
# account for time of year, add 1yr offset to grad year if september or later
$offset = 0
if($date.month -ge 9){
$offset = 1
}
# Account for placement of extended-stay students such as SPED kids
if($grade -gt 12){
$grade = 12
}
return ($date.year + 12 + $offset - $grade)
} else {
return "Invalid entry."
}
}
This works for grades -1 (pre-K) through 12, however it breaks when I test it against -2:
PS C:\> GradeToYog 12
2022
PS C:\> GradeToYog 9
2025
PS C:\> GradeToYog 6
2028
PS C:\> GradeToYog 3
2031
PS C:\> GradeToYog 1
2033
PS C:\> GradeToYog 0
2034
PS C:\> GradeToYog -1
2035
PS C:\> GradeToYog -2
2022
The weird thing is that if I test the expected values against the same logic in powershell it works:
PS C:\> 2022 + 12 + 0 - -2
2036
I searched for similar questions but either I'm not searching for it correctly or there doesn't seem to be an instance of powershell incorrectly subtracting '-2'
Note: I can't reproduce your specific symptom, but the following advice is still worth heeding.
Type-constrain your $grade parameter to ensure that it is a number:
# Make sure that $grade is an [int] (System.Int32) value
function GradeToYog([int] $grade) { # ...
# Alternative syntax, with a param(...) block,
# which makes it easier to use validation attributes.
function GradeToYog {
param(
[ValidateRange(-2, 15)] # Constrain the range of allowable numbers.
[int] $grade
)
# ...
}
An unconstrained parameter is effectively the same as [object] $grade, i.e. it can receive an object of any type.
PowerShell infers certain types from unquoted arguments such as 2 and -2 and, perhaps surprisingly, 2 becomes an [int], whereas -2 becomes a [string] (to force the latter to be interpreted as an [int] ad hoc, pass it as (-2)).
Many PowerShell operators, such as -eq / ne and -lt / -le / -gt / ge can operate on both strings and numbers, and it is usually the type of the LHS operand that determines the data type of the operation overall.
Here's an example of where this would make a difference:
2 -lt 10 # $true
'2' -lt 10 # !! $false, due to *lexical* comparison
I've tried searching for this, but I'm at a loss... I can find answers for other languages, but not for PowerShell.
Basically, I want to test if the time now is between 21:15 and 5:45.
I'm pretty sure I need to use New-TimeSpan - but, for the life of me, I just can't work it out.
I'd share my test code, but I think I'm so far away from the answer that it wouldn't be of any help.
Can anyone help me?
Use Get-Date to create DateTime objects describing those thresholds as points in time on todays date, then test if the time right now is before the early one or after the late one:
$now = Get-Date
$morning = Get-Date -Date $now -Hour 5 -Minute 45
$evening = Get-Date -Date $now -Hour 21 -Minute 15
$isWithinRange = $now -le $morning -or $now -ge $evening
If this is purely about the time of day and you don't need any date calculations, you can do the following, relying on the fact that for padded number strings lexical sorting equals numeric sorting:
# Get the current point in time's time of day in 24-hour ('HH') format.
# Same as: [datetime]::Now.ToString('HH\:mm')
$timeNow = Get-Date -Format 'HH\:mm'
$timeNow -ge '21:15' -or $timeNow -le '05:45'
If you'd have to check if you are in the range 23:00-04:00, crossing the midnight, you could:
$now=(Get-Date).TimeofDay
if ($now.TotalHours -ge 23 -or $now.TotalHours -lt 04)
{"In Range"}
else
{"Out of range"}
I'm trying to just get the groundwork for running some code with a time conditional. But I can't seem to grasp how to add time into the equation. Any Powershell people out there?
The $EndDate doesnt matter anymore. I just tried to use it as a way to understand how powershell uses conditional
$StartTime=(Get-Date)
$EndDate=[datetime]”00:00”
if ($StartDate -gt "00:00" -and $StartTime -lt "11:59")
{
Write-Host "It is time to work"
}
else
{
Write-Host "it is time to go"
}
$StartTime
My code right now should say its time to go but should say its time to work because as of right now its only 11:56 AM ET.
If you want to compare against the time of day, use the TimeOfDay TimeSpan exposed by [datetime] - PowerShell will automatically convert the right-hand "HH:mm" string into a meaningful TimeSpan that can be compared against:
$StartTime = Get-Date
if($StartTime.TimeOfDay -gt "00:00" -and $StartTime.TimeOfDay -le "12:00"){
# AM
}
else{
# PM
}
These two things are usually true.
(get-date) -gt '00:00'
True
(get-date) -lt '23:59'
True
Check for unset variables:
set-strictmode -v 1
I have the following test code. Basically I am checking when a new file is created in folder. I need to know that if the file was created after 4pm display the next business day. Currently my code displays the next day, but I need to display the next business day. Any help would be appreciated.
$formatteddate = "{0:h:mm:ss tt}" -f (get-date)
if ($formatteddate -gt "4:00:00 PM"){
$(Get-Date).AddDays(1).ToString('MMM d yyyy')
}
Adding to what jisaak said: "business day" is organization specific. Some organizations don't work on holidays that other organizations do. If you want to handle holidays correctly you'll need an explicit list of holidays for your business
Omitting the formatting details (which OP seems to understand) this should do it:
# $date is input date
$nextBizDay = $date.adddays(1)
# You would probably want to generate the follow list programmatically,
# instead of manually as done here
$holidays = 1, <# New Years #>
18, <# MLK day 2016 #>
<# other holidays encoded as Day Of Year #>
360 <# Christmas in a Leap Year #>
# An alternative to a list of holidays like this is to find a web service
# you can query to get the holidays for a given year
while ($nextBizDay.DayOfWeek -eq 'Saturday' -or
$nextBizDay.DayOfWeek -eq 'Sunday' -or
$nextBizDay.DayOfYear -in $holidays) {
if ($nextBizDay.DayOfYear -gt 366) {
throw "No next business day this year. Need to add additional logic"
}
$nextBizDay = $nextBizDay.adddays(1)
}
I have a log file where dates and corresponding information are listed in strings with a format of:
Tuesday, July 14, 2015 4:44:03 PM
Order Number 001006
Credit Card Type: AX
MessageType: 0100
Bit 2 [Primary_Account_Number..................] 3797*******1000
Bit 3 [Processing_Code.........................] 200000
Bit 4 [Amount_of_Transaction...................] 2.40
Bit 11 [Debit_Reg_E_Receipt_Number..............] 000083
Bit 14 [Expiration_Date.........................] 1704
Bit 18 [Merchant_Category_Code..................] 5812
Bit 22 [POS_Entry_Mode_PIN_Capability...........] 011
Bit 24 [Network_International_ID_NII............] 001
Bit 25 [POS_Condition_Code......................] 00
Bit 31 [Acquirer_Reference_Data.................] 2
Bit 37 [Retrieval_Reference_Number..............] 000000000283
Bit 41 [Terminal_ID.............................] 04765035
Bit 42 [Merchant_ID.............................] 000425249820993
Bit 59 [Merchant_ZIP_Postal_Code................] 19004
Tuesday, July 14, 2015 4:44:07 PM : Response:
Order Number
Credit Card Type:
MessageType: 0110
Bit 3 [Processing_Code.........................] 200000
Bit 4 [Amount_of_Transaction...................] 000000000240
Bit 7 [Transmission_Date_Time..................] 0714234410
Bit 11 [Debit_Reg_E_Receipt_Number..............] 000083
Bit 24 [Network_International_ID_NII............] 0001
Bit 25 [POS_Condition_Code......................] 00
Bit 37 [Retrieval_Reference_Number..............] 000000000283
Bit 39 [Response_Code...........................] 00
Bit 41 [Terminal_ID.............................] 04765035
Table 14
N 000000000000000000000240
Table 22
APPROVAL
This log is constantly updated, but I would like to get the current date/time and display only the information in the log from 3 hours ago, to present. I need all the information in the log, just not anything prior to Tuesday, July 14, 2015 1:44:03 (as an example). Is there some way I can get the current date/time and parse the log file for 3 hours prior to get the information I need? Maybe using Powershell or some sort of windows port of unix commands? The goal is to create a script that essentially truncates a log to display the past 3 hours of activity, and output that into a new log. I hope I stated this clearly enough. Any help is greatly appreciated.
I updated my answer as you updated your post :
$path = 'C:\list.txt'
$file = Get-Content $path
$i = 0
$before = (Get-Date).AddHours(-3)
foreach ($line in $file)
{
$i++
if ($line -match ', 2015 ')
{
$date = [regex]::match($line,'(?<=day, )(.*\n?)(?<=M)').value | Get-Date
if ($date -ge $before)
{
(Get-Content $path) | Select -Skip $($i-1)
Break
}
}
}
Ok, so this will set a boolean variable $NewLogs to $false. Then it reads your log file and for each line it checks to see if that $NewLogs has been set to $true, or if a date string is found, and if that date string is less than 3 hours old. If either the $NewLogs condition or the date string condition resolve to true, then it sets $NewLogs to $true (to make everything go faster for all lines past the current), and it passes that line through the pipeline. Then since $NewLogs is now $true all lines past that one will be passed through the pipeline.
$NewLogs = $false
Get-Content C:\Path\To\LogFile.txt -ReadCount 1000 |
ForEach{
If($NewLogs -or ($_ -match "(\S+day, \w+ \d{1,2}, \d{4} \d{1,2}:\d{1,2}:\d{2} \w{2})" -and ([datetime]::ParseExact($Matches[1],"dddd, MMMM dd, yyyy h:mm:ss tt",$null)) -gt [datetime]::Now.AddHours(-3))){$NewLogs=$true;$_}
}
Then you can just pipe that to a Set-Content command to output to a new file, or leave it as is to display it on the screen. If it were me I'd probably change that very last line to:
} | Set-Content (join-path 'C:\Path\To\' ([datetime]::now.tostring('MMddyyhhmmtt.\tx\t')))
So if I did that right now it would save the last 3 hours of logs to the file:
C:\Path\To\0715150359PM.txt
I get the same error about null array on Powershell v4 with the solution of TheMadTechnician, but doing the following works :
Remember to change the dates to
Thursday, July 16, 2015 8:44:03 PM
Thursday, July 16, 2015 8:44:04 PM
The updated script :
$NewLogs = $false
$file = Get-Content C:\list.txt -ReadCount 1000
foreach ($line in $file)
{
If($NewLogs -or ($line -match '(\S+day, \w+ \d{1,2}, \d{4} \d{1,2}:\d{1,2}:\d{2} \w{2})' -and ([datetime]::ParseExact($Matches[1],'dddd, MMMM dd, yyyy h:mm:ss tt',$null)) -gt [datetime]::Now.AddHours(-3)))
{
$NewLogs = $true
$line
}
}