Finding the last Sunday or another day of a month

I’ve had to find out programmatically when was the last Sunday of a month.
So, I remembered that the Powershell MVP Richard Siddaway has already shown a function to get the second Tuesday of a month, aka the Black Tuesday in the patchmanagement world.
I also did a little search on google and found the following nice blog post.
Howerver, I used another approach because:

  • I don’t want to use the while statement…
  • I wanted to be able to submit input from the pipeline,
  • I wanted the function to leverage new V3 features
  • I wanted to return different type of outputs: a datetime or integer
  • I wanted to add a “strong” parameters validation
  • I wanted to the function to work with any regional settings

The last goal was the most challenging. I found a very smart way to validate the Day parameter submitted as a string.
I first casted it into the .Net system.dayOfWeek class and used the new -in statement to see if the returned integer was in the array of all valid days of week stored as integer, i.e from 0 to 6.
To understand how I came up with this code, let’s try the following:

[System.DayOfWeek]1            
            
[System.DayOfWeek]::Monday -eq 1            
            
[System.Enum]::GetValues([System.DayOfWeek]) | ForEach-Object -Process {                        
    '{0} -> {1}' -f ([System.DayOfWeek]::$_.Value__),$_                        
}

Day of Week

Here’s the function:

Function Get-LastxOfMonth {            
[CmdletBinding()]            
param(            
    [parameter(Mandatory)]            
    [String]$Day,            
            
    [parameter(ParameterSetName='ByDate',Mandatory,ValueFromPipeline)]            
    [System.DateTime]$Date,            
            
    [parameter(ParameterSetName='ByString',Mandatory,ValueFromPipelineByPropertyName)]            
    [ValidateRange(1,12)]            
    [int]$Month,             
    [parameter(ParameterSetName='ByString',Mandatory,ValueFromPipelineByPropertyName)]            
    [ValidatePattern('^\d{4}$')]            
    [int]$Year,            
            
    [switch]$asDate=$false            
)            
Begin {            
    $alldays = @()            
}            
Process {            
    # Validate the Day string passed as parameter by casting it into            
    if (-not([System.DayOfWeek]::$Day -in 0..6)) {            
        Write-Warning -Message 'Invalid string submitted as Day parameter'            
        return            
    }            
            
    Switch ($PSCmdlet.ParameterSetName)            
    {            
        ByString {            
            # Do nothing, variables are already defined and validated            
        }            
        ByDate   {            
            $Month = $Date.Month            
            $Year = $Date.Year            
        }            
    }            
    # There aren't 32 days in any month so we make sure we iterate through all days in a month            
    0..31 | ForEach-Object -Process {            
        $evaldate = (Get-Date -Year $Year -Month $Month -Day 1).AddDays($_)            
        if ($evaldate.Month -eq $Month)            
        {            
            if ($evaldate.DayOfWeek -eq $Day) {            
                $alldays += $evaldate.Day            
            }            
        }            
    }            
    # Output            
    if ($asDate) {            
        Get-Date -Year $Year -Month $Month -Day $alldays[-1]            
    } else {            
        $alldays[-1]            
    }            
}            
End {}            
}

Let’s see what you can do with it:

# Find the last  Wednesday of each month of 2013            
1..12 | ForEach-Object -Process {            
    Get-LastxOfMonth -Day Wednesday -Month $_ -Year 2013 -asDate            
}            
            
# Make a mistake in the spelling of the day name            
Get-LastxOfMonth -Day FridayZ -Month 10 -Year 2010            
            
# Get the last Sunday of this month            
Get-LastxOfMonth -Day Sunday -Month 10 -Year 2012            
            
# Find the last Friday of the current month but next year            
Get-LastxOfMonth -Day Friday -Date (Get-Date).AddYears(1) -asDate            
            
# Submit a Datetime object through the pipeline            
(Get-Date).AddMonths(-4).AddYears(1) | Get-LastxOfMonth -Day Friday -asDate            
            
# Use the other parameter set that uses only a value from the pipeline by property name            
New-Object -TypeName psobject -Property @{            
    Month = 10;            
    Year = 2013;            
} | Get-LastxOfMonth -Day Friday -asDate
Advertisements

9 thoughts on “Finding the last Sunday or another day of a month

    • Hi,
      Yes, that’s possible:

      First, I’d rename the function to Get-FirstxOfMonth

      Then, you just need to modify the output at the end of the function like this:

          # Output            
          if ($asDate) {            
              Get-Date -Year $Year -Month $Month -Day $alldays[0]            
          } else {            
              $alldays[0]            
          } 
      
  1. Using your code as is for param section…
    param(
    [parameter(Mandatory)]
    [String]$Day,
    [parameter(ParameterSetName=’ByDate’,Mandatory,ValueFromPipeline)]
    [System.DateTime]$Date,
    [parameter(ParameterSetName=’ByString’,Mandatory,ValueFromPipelineByPropertyName)]
    [ValidateRange(1,12)]
    [int]$Month,
    [parameter(ParameterSetName=’ByString’,Mandatory,ValueFromPipelineByPropertyName)]
    [ValidatePattern(‘^\d{4}$’)]
    [int]$Year,
    [switch]$asDate=$false
    )

    On Powershell 2.0I get:

    The “=” operator is missing after a named argument.
    At C:\Temp\cr-msg2-apac.ps1:4 char:24
    + [parameter(Mandatory) <<<< ]
    + CategoryInfo : ParserError: (:) [], ParseException
    + FullyQualifiedErrorId : MissingEqualsInNamedArgument

    Any ideas?

    • Hi,
      Yes, it’s because of the new feature in Powershell 3.0 that allows you to specify just Mandatory instead of Mandatory=$true in the parameter definition.

  2. It seems that you are not clarifying that this will only work on a certain release of PS. PS2.0 fails miserably in your param section and the -In operator…
    if (-not([System.DayOfWeek]::$Day -In 0..6)) {
    also fails because the -In does not work in PS2.0

  3. Hi..I’m completely new to Powershell…I’m doing migration from unix environment to windows.I need to know the commands to get last friday in a month and also first friday in every monthy.Kindly help me in this by providing detailed explaination for this.

    • Hi,
      I’ve seen your question and started to revisit the code and tried to improve it. I didn’t finish yet and plan to write another blog post to highlight some advanced features used in the revised code.
      I’ll keep you posted when it’s published.
      To quickly answer your question, you can use the function in the above blog post to get the last friday in a month using the following

      # open a powershell console
      # copy/paste the function presented in the above blog post
      # to get the last friday of the current month, you can just do:
      Get-Date |Get-LastxOfMonth -Day Friday -asDate
      

      To get the first friday in every month this year, you should duplicate the function and change its name to Get-FirstxOfMonth and then apply the fix I detailed in a comment above

      1..12 | ForEach-Object {
       Get-FistxOfMonth -Day Friday -Year 2017 -Month $_ -asDate
      }
      

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s