ncal.psm1
#Requires -Version 7.2 function Get-NcalRow { <# .NOTES Helper function for Get-NCalendar. Prints one row of calendar output, given an Index corresponding to the first day of the month. #> param ( [Parameter(Mandatory, Position = 0)] [System.Globalization.Calendar]$Calendar, [Parameter(Mandatory, Position = 1)] [Int]$Index, [Parameter(Position = 2)] [ValidateRange(28, 31)] [Int]$DayPerMonth, [Parameter(Position = 3)] [ValidateRange(1, 13)] [Int]$Month, [Parameter(Position = 4)] [ValidateRange(1, 9999)] [Int]$Year, [Parameter(Position = 5)] [Object]$Highlight, [Parameter(Position = 6)] [Bool]$JulianSpecified ) begin { $WeekDay = $Index $Row = '' $JulianDay = 0 if ($JulianSpecified) { $PadRow = 24 $PadDay = 3 } else { $PadRow = 19 $PadDay = 2 } } process { do { if ($WeekDay -lt 1) { $Row += "{0,$PadDay} " -f $null } else { if ($JulianSpecified) { try { $UseDate = $Calendar.ToDateTime($Year, $Month, $WeekDay, 0, 0, 0, 0, $Calendar.Eras[0] ) $JulianDay = $Calendar.GetDayOfYear($UseDate) $Row += "{0,$PadDay} " -f $JulianDay } catch { Write-Error "day beyond day/month - $($_.Message)" } } else { $Row += "{0,$PadDay} " -f $WeekDay } } $WeekDay += 7 } until ($WeekDay -gt $DayPerMonth) $OutString = "$($Row.TrimEnd())".PadRight($PadRow, ' ') <# The secret to not screwing up formatted strings with non-printable characters is to mess with the string after the string is padded to the right length. #> if ( ($Highlight.Today -gt 0) -and $JulianSpecified) { $UseDate = $Calendar.ToDateTime($Year, $Month, $Highlight.Today, 0, 0, 0, 0, $Calendar.Eras[0] ) $JulianDay = $Calendar.GetDayOfYear($UseDate) if ($OutString -match "\b$JulianDay\b") { $OutString = $OutString -replace "$JulianDay\b", "$($Highlight.DayStyle)$JulianDay$($Highlight.DayReset)" } } elseif ( ($Highlight.Today -gt 0) -and ($OutString -match "\b$($Highlight.Today)\b")) { if ($Highlight.Today -lt 10) { $OutString = $OutString -replace "\s$($Highlight.Today)\b", "$($Highlight.DayStyle) $($Highlight.Today)$($Highlight.DayReset)" } else { $OutString = $OutString -replace "$($Highlight.Today)\b", "$($Highlight.DayStyle)$($Highlight.Today)$($Highlight.DayReset)" } } Write-Output $OutString } } function Get-CalRow { <# .NOTES Helper function for Get-Calendar - Prints one row of calendar output, given an Index corresponding to the first day of the month. #> param ( [Parameter(Position = 0)] [System.Globalization.Calendar]$Calendar, [Parameter(Position = 1)] [Int]$Index, [Parameter(Position = 2)] [ValidateRange(28, 31)] [Int]$DayPerMonth, [Parameter(Position = 3)] [ValidateRange(1, 13)] [Int]$Month, [Parameter(Position = 4)] [ValidateRange(1, 9999)] [Int]$Year, [Parameter(Position = 5)] [Object]$Highlight, [Parameter(Position = 6)] [Bool]$JulianSpecified ) begin { $WeekDay = $Index $Row = '' $JulianDay = 0 if ($JulianSpecified) { $PadRow = 30 $PadDay = 3 } else { $PadRow = 23 $PadDay = 2 } } process { # Repeat 7 times for each week day in the row 0..6 | ForEach-Object { if ($WeekDay -lt 1) { $Row += "{0,$PadDay} " -f $null } elseif ($WeekDay -gt $DayPerMonth) { #do nothing } else { if ($JulianSpecified) { try { $UseDate = $Calendar.ToDateTime($Year, $Month, $WeekDay, 0, 0, 0, 0, $Calendar.Eras[0] ) $JulianDay = $Calendar.GetDayOfYear($UseDate) $Row += "{0,$PadDay} " -f $JulianDay } catch { Write-Error "day beyond day/month - $($_.Message)" } } else { $Row += "{0,$PadDay} " -f $WeekDay } } $WeekDay++ } $OutString = "$($Row.TrimEnd())".PadRight($PadRow, ' ') <# The secret to not screwing up formatted strings with non-printable characters is to mess with the string after the string is padded to the right length. #> if ( ($Highlight.Today -gt 0) -and $JulianSpecified) { $UseDate = $Calendar.ToDateTime($Year, $Month, $Highlight.Today, 0, 0, 0, 0, $Calendar.Eras[0] ) $JulianDay = $Calendar.GetDayOfYear($UseDate) if ($OutString -match "\b$JulianDay\b") { $OutString = $OutString -replace "$JulianDay\b", "$($Highlight.DayStyle)$JulianDay$($Highlight.DayReset)" } } elseif ( ($Highlight.Today -gt 0) -and ($OutString -match "\b$($Highlight.Today)\b")) { if ($Highlight.Today -lt 10) { $OutString = $OutString -replace "\s$($Highlight.Today)\b", "$($Highlight.DayStyle) $($Highlight.Today)$($Highlight.DayReset)" } else { $OutString = $OutString -replace "$($Highlight.Today)\b", "$($Highlight.DayStyle)$($Highlight.Today)$($Highlight.DayReset)" } } Write-Output $OutString } } function Get-Today { [CmdletBinding()] param ( [System.Globalization.Calendar]$Calendar ) process { $Now = Get-Date [PSCustomObject]@{ 'DateTime' = $Now #Always shown in local calendar 'Year' = $Calendar.GetYear($Now) 'Month' = $Calendar.GetMonth($Now) 'Day' = $Calendar.GetDayOfMonth($Now) } } } function Get-MonthHeading { <# .NOTES Helper function for Get-NCalendar and Get-Calendar. Returns a string. #> param ( [Parameter(Position = 0)] [System.Globalization.CultureInfo]$Culture, [Parameter(Position = 1)] [String]$MonthName, [Parameter(Position = 2)] [Bool]$JulianSpecified ) begin { $CallStack = Get-PSCallStack if ($CallStack.Count -gt 1) { $CallingFunction = $CallStack[1].Command } } process { if ($CallingFunction -eq 'Get-Calendar') { if ($true -eq $JulianSpecified) { $HeadingLength = 30 } else { $HeadingLength = 23 } # Special cases - resorted to hard coding for double width character sets like Kanji. # Japanese, traditional Chinese and Korean have 1 double width character in month name, simplified Chinese # and Yi have two. This is a rough hack, but it works. if ($Culture.Name -match '^(ja|zh-hant|ko$|ko\-)') { $HeadingLength -= 1 } if ($Culture.Name -match '^(zh$|zh-hans|ii)') { $HeadingLength -= 2 } # cal month headings are centred. $Pad = $MonthName.Length + (($HeadingLength - 2 - $MonthName.Length) / 2) $MonthHeading = ($MonthName.PadLeft($Pad, ' ')).PadRight($HeadingLength, ' ') } # Get-NCalendar is the (default) calling function else { if ($true -eq $JulianSpecified) { $HeadingLength = 24 } else { $HeadingLength = 19 } if ($Culture.Name -match '^(ja|zh-hant|ko$|ko\-)') { $HeadingLength -= 1 } if ($Culture.Name -match '^(zh$|zh-hans|ii)') { $HeadingLength -= 2 } $MonthHeading = "$MonthName".PadRight($HeadingLength, ' ') } Write-Output $MonthHeading } } function Get-FirstDayOfMonth { <# .NOTES Helper function for Get-NCalendar and Get-Calendar that returns the date and day position of the first day of each required month, using the specified calendar. This function performs the parameter validation for both ncal and cal. #> [CmdletBinding()] param ( [Parameter(Mandatory, Position = 0)] [System.Globalization.Calendar]$Calendar, # Could be an integer between 1 and 13 or the same with an 'f' or 'p' suffix [Parameter(Position = 1)] [String]$Month, [Parameter(Position = 2)] [ValidateRange(1, 9999)] [Int]$Year, [Parameter(Position = 3)] [Int]$Before, [Parameter(Position = 4)] [Int]$After, [Parameter(Position = 5)] [Switch]$Three ) process { $Now = Get-Today -Calendar $Calendar Write-Verbose "today year = $($Now.Year), today month = $($Now.Month), today day = $($Now.Day)" if ($PSBoundParameters.ContainsKey('Month')) { [Int]$AfterOffset = 0 if ($PSBoundParameters.ContainsKey('Year')) { $YearSpecified = $true } else { $Year = $Now.Year $YearSpecified = $false } # allowing 13 to be specified for Asian Lunar calendars. Obviously you get an error for all other # calendars. if ($Month -in 1..13) { [Int]$MonthNumber = $Month } # trailing 'f' means month specified, but next year required elseif ($Month -match '^(0?[1-9]|1[0-2])[Ff]$') { [Int]$MonthNumber = ($Month | Select-String -Pattern '^\d+').Matches.Value $Year += 1 } # trailing 'p' means month specified, but last year required elseif ($Month -match '^(0?[1-9]|1[0-2])[Pp]$') { [Int]$MonthNumber = ($Month | Select-String -Pattern '^\d+').Matches.Value $Year -= 1 } else { Write-Error "'$Month' is not a valid month number" return } <# add additional month before and after required month. This is better than Linux ncal. It allows a month in any year to be specified with -three. Linux ncal just ignores -3 and displays the specified year. #> if ($PSBoundParameters.ContainsKey('Three')) { [Int]$BeforeOffset = 1 [Int]$AfterOffset = 1 } } else { # No month if ($PSBoundParameters.ContainsKey('Year')) { # Year specified with no month; showing whole year. [Int]$MonthNumber = 1 [Int]$BeforeOffset = 0 [Int]$AfterOffset = ($Calendar.GetMonthsInYear($Year) - 1) $YearSpecified = $true if ($PSBoundParameters.ContainsKey('Three')) { <# If we allow -year and -three (with no -month) then we get the first month of specified year with the month before and after. This feels like a bug, so ignore -three in this case. #> Write-Warning 'The Three parameter is ignored when Year is specified with no Month.' } } else { # Default is this month only $MonthNumber = $Now.Month $Year = $Now.Year $YearSpecified = $false # add additional month before and after this month. if ($PSBoundParameters.ContainsKey('Three')) { [Int]$BeforeOffset = 1 [Int]$AfterOffset = 1 } } } # add specified number of months before the month(s) or year already identified if ($PSBoundParameters.ContainsKey('Before')) { $BeforeOffset += $Before } # add specified number of months following the month(s) or year already identified if ($PSBoundParameters.ContainsKey('After')) { $AfterOffset += $After } <# Parameter validation complete. We have the required months to show, and a target month (typically this month or possibly first month of required year). Get the date object of the first day of the specified month and then use this to determine the date object of the first day of the first required month. #> $MonthCount = 1 + $BeforeOffset + $AfterOffset try { $TargetDate = $Calendar.ToDateTime($Year, $MonthNumber, 1, 0, 0, 0, 0, $Calendar.Eras[0]) $FirstDay = $Calendar.AddMonths($TargetDate, - $BeforeOffset) } catch { Write-Error "Date conversion error - $($PSItem.Exception.Message)" return } for ($i = 1; $i -le $MonthCount; $i++) { $ThisYear = $Calendar.GetYear($FirstDay) $ThisMonth = $Calendar.GetMonth($FirstDay) $DayPerMonth = $Calendar.GetDaysInMonth($ThisYear, $ThisMonth) $MonthPerYear = $Calendar.GetMonthsInYear($ThisYear) [pscustomobject] @{ 'Date' = $FirstDay # illustrates funky looking date when shown in local culture 'Year' = $ThisYear 'Month' = $ThisMonth 'Day' = $Calendar.GetDayOfMonth($FirstDay) # for clarity, not used 'FirstDay' = $Calendar.GetDayOfWeek($FirstDay) # for clarity, not used 'FirstDayIndex' = $Calendar.GetDayOfWeek($FirstDay).value__ # Monday=1, Sunday=7 'DayPerMonth' = $DayPerMonth 'MonthPerYear' = $MonthPerYear 'YearSpecified' = $YearSpecified # Used to determine year and month headings } $FirstDay = $Calendar.AddMonths($FirstDay, 1) } } #end process } function Get-StartWeekIndex { <# .NOTES The first day index is always 1 through 7, Monday to Sunday. This function returns an index, based on the real/desired first day of the week and the actual start day. This will be a number between -5 and 1. When we reach an index=1, we start printing, otherwise we print a space. #> [CmdletBinding()] param ( [Parameter(Mandatory, Position = 1)] [ValidateSet('Friday', 'Saturday', 'Sunday', 'Monday')] [string]$StartWeekDay, [Int]$FirstDayIndex ) process { # Monday = -2 thru Sunday = -8, which means Friday-Sunday start on next column so force 1, 0, -1 resp. if ('Friday' -eq $StartWeekDay) { $ThisIndex = -1 - $FirstDayIndex if ($ThisIndex -eq -6) { $ThisIndex = 1 } elseif ($ThisIndex -eq -7) { $ThisIndex = 0 } elseif ($ThisIndex -eq -8) { $ThisIndex = -1 } } # Monday = -1 thru Sunday = -7 which mean both Saturday and Sunday start on next column, so force 1 and 0 resp. elseif ('Saturday' -eq $StartWeekDay) { $ThisIndex = 0 - $FirstDayIndex if ($ThisIndex -eq -6) { $ThisIndex = 1 } elseif ($ThisIndex -eq -7) { $ThisIndex = 0 } } # Monday = 0 thru Sunday = -6, which would mean start on next column, so force Sunday to be 1 elseif ('Sunday' -eq $StartWeekDay) { $ThisIndex = 1 - $FirstDayIndex if ($ThisIndex -eq -6) { $ThisIndex = 1 } } # Week starts Monday. Here we need Monday = 1 thru Sunday = -5 else { $ThisIndex = 2 - $FirstDayIndex if ($ThisIndex -eq 2) { $ThisIndex = -5 } } Write-Output $ThisIndex } } function Get-WeekDayName { <# .NOTES Determine the localized week day names. There are globalisation issues with attempting to truncate day names in some cultures, so only truncate cultures with standard character sets. For ncal only, MonthOffset is an attempt to fix column formats with many cultures that have mixed length short and long day names. It seems to work ok providing an appropriate font is installed supporting unicode characters. According .Net, just one language (Dhivehi - cultures dv and dv-MV), spoken in Maldives, has a week day starting on Friday. Most Islamic countries (some Arabic, Persian, Pashto and one or two others) use Saturday. All Western and Eastern European countries, Russia, Indian, most of Africa, Asian-Pacific countries, except China and Japan follow ISO 1806 standard with Monday as the first day of the week. All North and South American countries, some African, China and Japan use Sunday. With ncal, we want full day names or abbreviated day names (modified for some cultures). With cal, we want abbreviated names (shortened for some cultures) or the shortest day names. #> [CmdletBinding()] param ( [Parameter(Mandatory, Position = 0)] [System.Globalization.CultureInfo]$Culture, [Parameter(Mandatory, Position = 1)] [ValidateSet('Friday', 'Saturday', 'Sunday', 'Monday')] [string]$FirstDayOfWeek, [Parameter(Position = 2)] [Switch]$LongDayName, [Parameter(Position = 3)] [Switch]$JulianSpecified ) begin { [Int]$MonthOffset = 0 $CallStack = Get-PSCallStack if ($CallStack.Count -gt 1) { $CallingFunction = $CallStack[1].Command } } process { if ($CallingFunction -eq 'Get-Calendar') { # Some cultures use double width character sets. Attempt to capture these and do not pad day names if ($Culture.Name -match '^(ja|zh|ko$|ko\-|ii)') { $WeekDay = $Culture.DateTimeFormat.ShortestDayNames if ($true -eq $JulianSpecified) { # For day of the year, each day is 2 characters wide with double-width character sets. $WeekDay = $WeekDay | ForEach-Object { "$_".PadLeft(2, ' ') } } } else { # Truncate some Abbreviated day names to two characters (e.g. Mo, Tu), rather than Shortest day # names (e.g. M, T) for some Western and other languages. if ($Culture.Name -match '^(da|de|es|eo|en|fr|it|pt|wo|fil)') { $WeekDay = $Culture.DateTimeFormat.AbbreviatedDayNames | ForEach-Object { "$_".Substring(0, 2) } } else { # Most cultures use a single character, so ensure the names are 2 characters. $WeekDay = $Culture.DateTimeFormat.ShortestDayNames | ForEach-Object { "$_".PadLeft(2, ' ') } } if ($true -eq $JulianSpecified) { # For day of the year, each day is 3 characters wide. $WeekDay = $WeekDay | ForEach-Object { "$_".PadLeft(3, ' ') } } } Write-Verbose "Short week day - $WeekDay" } else { # Get-NCalendar is the (default) calling function if ($true -eq $LongDayName) { $WeekDayLong = $Culture.DateTimeFormat.DayNames Write-Verbose "Long week day - $WeekDayLong" if ($Culture.Name -match '^(ja|zh|ko$|ko\-|ii)') { # Full day name for cultures that use double width character sets, double the size of the month # offset but not the weekday $MonthOffset = 2 + ((($WeekDayLong | ForEach-Object { "$_".Length } | Measure-Object -Maximum).Maximum) * 2) $WeekDayLength = ($WeekDayLong | ForEach-Object { "$_".Length } | Measure-Object -Maximum).Maximum $WeekDay = $WeekDayLong | ForEach-Object { "$_".PadRight($WeekDayLength + 1, ' ') } } else { # Full day name for cultures using latin and Persian character sets. $MonthOffset = 2 + (($WeekDayLong | ForEach-Object { "$_".Length } | Measure-Object -Maximum).Maximum) $WeekDay = $WeekDayLong | ForEach-Object { "$_".PadRight($MonthOffset - 1, ' ') } } } else { $WeekDayShort = $Culture.DateTimeFormat.AbbreviatedDayNames Write-Verbose "Abbreviated week day - $WeekDayShort" if ($Culture.Name -match '^(ja|zh|ko$|ko\-|ii)') { # Short day names for cultures that use double width character sets $MonthOffset = 2 + ((($WeekDayShort | ForEach-Object { "$_".Length } | Measure-Object -Maximum).Maximum) * 2) $WeekDayLength = ($WeekDayShort | ForEach-Object { "$_".Length } | Measure-Object -Maximum).Maximum $WeekDay = $WeekDayShort | ForEach-Object { "$_".PadRight($WeekDayLength + 1, ' ') } } elseif ($ThisCulture.Name -match '^(en|fr|de|it|es|pt|eo)') { # Simulate the Linux ncal command with two character day names on some Western cultures. $MonthOffset = 4 $WeekDay = $WeekDayShort | ForEach-Object { "$_".Substring(0, 2).PadRight(3, ' ') } } else { # Short day name for all other cultures. $MonthOffset = 2 + (($WeekDayShort | ForEach-Object { "$_".Length } | Measure-Object -Maximum).Maximum) $WeekDay = $WeekDayShort | ForEach-Object { "$_".PadRight($MonthOffset - 1, ' ') } } } } Write-Verbose "Specified/assumed first day of week is $FirstDayOfWeek" Write-Verbose "Cultural first day of the week is $($Culture.DateTimeFormat.FirstDayOfWeek)" # DayNames and AbbreviatedDayNames properties in .Net are always Sunday based, regardless of culture. if ('Friday' -eq $FirstDayOfWeek) { $WeekDay = $WeekDay[5, 6, 0, 1, 2, 3, 4] } if ('Saturday' -eq $FirstDayOfWeek) { $WeekDay = $WeekDay[6, 0, 1, 2, 3, 4, 5] } elseif ('Monday' -eq $FirstDayOfWeek) { $WeekDay = $WeekDay[1, 2, 3, 4, 5, 6, 0] } [PSCustomObject]@{ Name = $WeekDay Offset = $MonthOffset } } } function Get-WeekRow { <# .NOTES Helper function for Get-NCalendar. Displays the week number beneath each column. Uses a .Net call to obtain the week number for the first week of the month for the required culture. We use the specified first day of the week to ensure the week numbers align (although this is not culturally correct). Otherwise, we are using the default first day of the week for the required culture. The other thing we're doing is ensuring the correct number of week numbers per month. Most of the time, this is five columns. However, if the first day of the month appears in the last day of the week position and there are 30 or 31 days in the month, then there are 6 columns. If it appears in the next to last position, and there 31 days, then are 6 columns. This equates to Index = -5 or -4 respectively. The only other combination is when there are 28 days in February, and the first day is position 1 (Index = 1). In this case, there are only 4 columns. All cultures are fine, but calendars (typically the Asian Lunar calendars) don't so correct week numbers. This is because non-default calendars are inheriting DateTimeFormat from some Culture (which typically uses Gregorian by default). DateTimeFormat is used to calculate the week number of the first week in the month. I these situations, don't allow week row being displayed. #> [CmdletBinding()] param ( [Parameter(Mandatory, Position = 0)] [System.Globalization.CultureInfo]$Culture, [Parameter(Mandatory, Position = 1)] [System.Globalization.Calendar]$Calendar, [Parameter(Mandatory, Position = 2)] [ValidateRange(1, 13)] [Int]$Month, [Parameter(Mandatory, Position = 3)] [ValidateRange(1, 9999)] [Int]$Year, [Parameter(Mandatory, Position = 4)] [ValidateSet('Friday', 'Saturday', 'Sunday', 'Monday')] [String]$FirstDayOfWeek, [Parameter(Mandatory, Position = 5)] [ValidateRange(-5, 1)] [Int]$Index, [Parameter(Position = 6)] [Bool]$JulianSpecified ) process { $FirstDate = $Calendar.ToDateTime($Year, $Month, 1, 0, 0, 0, 0, $Calendar.Eras[0] ) $DayPerMonth = $Calendar.GetDaysInMonth($Year, $Month) $CultureWeekRule = $Culture.DateTimeFormat.CalendarWeekRule # Adjust the starting week number, based on the first day of the week. if ('Friday' -eq $FirstDayOfWeek) { $StartWeekDay = 5 } if ('Saturday' -eq $FirstDayOfWeek) { $StartWeekDay = 6 } elseif ('Sunday' -eq $FirstDayOfWeek) { $StartWeekDay = 0 } elseif ('Monday' -eq $FirstDayOfWeek) { $StartWeekDay = 1 } else { #can't be necessary, but use default for the requested culture, for illustration. $StartWeekDay = $Culture.DateTimeFormat.FirstDayOfWeek } # This is the starting week number of this month, taking into account the starting week day. $FirstWeek = $Culture.Calendar.GetWeekOfYear($FirstDate, $CultureWeekRule, $StartWeekDay) [String]$WeekRow = '' [Int]$WeekCount = 5 # month starts last day of week and has at least 30 days is 6 columns wide if (-5 -eq $Index -and $DayPerMonth -ge 30) { $WeekCount = 6 } # month starts next to last day of week and has over 30 day is 6 columns wide elseif (-4 -eq $Index -and $DayPerMonth -gt 30) { $WeekCount = 6 } # February in some calendars that starts on the first day of the week is 4 columns wide elseif (1 -eq $Index -and $DayPerMonth -eq 28) { $WeekCount = 4 } if ($true -eq $JulianSpecified) { $PadRow = 24 $PadDay = 3 } else { $PadRow = 19 $PadDay = 2 } $LastWeek = $FirstWeek + ($WeekCount - 1) $FirstWeek..$LastWeek | ForEach-Object { if ($_ -gt 52) { $WeekRow += "{0,$PadDay} " -f ($_ - 52) } else { $WeekRow += "{0,$PadDay} " -f $_ } } $OutString = "$WeekRow".PadRight($PadRow, ' ') Write-Output $OutString } # end process } function Get-Highlight { <# .NOTES Helper function for Get-NCalendar and Get-Calendar. Returns a list of formatting strings and the day for today. This is used to highlighting year/month headings, week row and todays day. #> [CmdletBinding()] param ( [Parameter(Mandatory, Position = 0)] [System.Globalization.Calendar]$Calendar, [Parameter(Mandatory, Position = 1)] [ValidateRange(1, 13)] [Int]$Month, [Parameter(Mandatory, Position = 2)] [ValidateRange(1, 9999)] [Int]$Year, [Parameter(Position = 3)] [ValidateSet('None', 'Red', 'Green', 'Blue', 'Yellow', 'Cyan', 'Magenta', 'White', 'Orange', 'Pink', $null)] [String]$Highlight ) process { $Now = Get-Today -Calendar $Calendar if ( ($Month -eq $Now.Month) -and ($Year -eq $Now.Year) ) { $Today = $Now.Day } else { $Today = 0 } if (-not $Highlight) { # This is the default; reverse highlight today but no highlighted month name Write-Output @{ Today = $Today MonStyle = $null MonReset = $null DayStyle = $PSStyle.Reverse DayReset = $PSStyle.ReverseOff } } elseif ('None' -eq $Highlight) { # Turn off highlighting today Write-Output @{ Today = 0 } } # Just for giggles, demonstrate some non-PSStyle supplied colours elseif ('Orange' -eq $Highlight) { Write-Output @{ Today = $Today MonStyle = "$($PSStyle.Foreground.FromRgb(255,131,0))$($PSStyle.Bold)" MonReset = $PSStyle.Reset DayStyle = "$($PSStyle.Background.FromRgb(255,131,0))$($PSStyle.Foreground.FromRgb(0,0,0))" DayReset = $PSStyle.Reset } } elseif ('Pink' -eq $Highlight) { Write-Output @{ Today = $Today MonStyle = "$($PSStyle.Foreground.FromRgb(255,0,255))$($PSStyle.Bold)" MonReset = $PSStyle.Reset DayStyle = "$($PSStyle.Background.FromRgb(255,0,255))$($PSStyle.Foreground.FromRgb(0,0,0))" DayReset = $PSStyle.Reset } } else { # Force the bright version of the specified colour $Colour = "Bright$Highlight" Write-Output @{ Today = $Today MonStyle = "$($PSStyle.Foreground.$Colour)$($PSStyle.Bold)" MonReset = $PSStyle.Reset DayStyle = "$($PSStyle.Background.$Colour)$($PSStyle.Foreground.FromRgb(0,0,0))" DayReset = $PSStyle.Reset } } } } function Get-NCalendar { <# .SYNOPSIS Get-NCalendar .DESCRIPTION This command displays calendar information similar to the Linux ncal command. It implements the same functionality, including the ability to display multiple months, years, week number per year, day of the year and month forward and month previous by one year. But in addition, the command can do a whole lot more: 1. Display a calendar in any supported culture. Month and day names are displayed in the appropriate language for the specified culture and the appropriate calendar is used (e.g. Gregorian, Persian), along with appropriate DateTimeFormat information (e.g. default first day of the week). 2. As well as display the primary calendar (used by each culture), also display optional calendars. These are Hijri, Hebrew, Japanese (Solar), Korean (Solar) and Taiwanese (Solar) calendars. In addition, the non-optional calendars (i.e. calendars not used by any culture, but still observed for religious, scientific or traditional purposes). These are the Julian and Chinese, Japanese, Korean and Taiwanese Lunar calendars. (Note: Only the current era is supported). 3. Specify the first day of the week (Friday through Monday). The specified or current culture setting is used by default. Friday through Monday are supported because all cultures use one of these days. 4. Display abbreviated (default) or full day names, specific to the culture. 5. Display one to six months in a row, when multiple months are displayed (the default is 4). 6. When displaying week numbers, they will align correctly with respect to the default or specified first day of the week. 7. Highlight the year and month headings, todays date and week numbers using a specified colour. It is highly recommended that Windows Terminal is used with an appropriate font to ensure that ISO unicode character sets are both available and are displayed correctly. With other consoles, like Visual Studio Code, the ISE and the default PowerShell console, some fonts might not display correctly and with extended unicode character sets, calendars may appear misaligned. Note: From version 1.22.10352.0 (Feb 2025) of Windows Terminal, grapheme clusters are now supported and are turned on by default. A grapheme cluster is a single user-perceived character made up of multiple code points from the Unicode Standard, introduced in .NET 5. Whilst this is considered the correct method for handling and displaying Unicode character sets, PowerShell doesn't support grapheme clusters and thus, calandars in ncal appear misaligned. This can be remedied, in the short term, by disabling grapheme cluster support in Settings > Compatibility > Text measurement mode, selecting "Windows Console" and then restarting the Windows Terminal. .PARAMETER Month Specifies the required month. This must be specified as a number 0..13. An 'f' (forward by one year) or a 'p' (previous by one year) suffix can also be appended to the month number. .PARAMETER Year Specifies the required year. If no month is specified, the whole year is shown. .PARAMETER Culture Specifies the required culture. The system default culture is used by default. .PARAMETER Calendar Instead of a culture, specify a calendar. This allows displaying optional and other calendars not used by any culture. They include Julian, Hijri, Hebrew and Chinese Lunar calendars. .PARAMETER FirstDayOfWeek Display the specified first day of the week. By default, the required culture is used to determine this. .PARAMETER MonthPerRow Display the specified number of months in each row. By default it is 4 months. .PARAMETER Highlight By default, today's date is highlighted. Specify a colour to highlight today, the year/month headings and week numbers or disable the default highlight with 'none'. .PARAMETER Before The specified number of months are added before the specified month(s). See -After for examples. .PARAMETER After The specified number of months are added after the specified month(s), i.e. in addition to any date range selected by the -Year or -Three options. Negative numbers are allowed, in which case the specified number of months is subtracted. For example ncal -after 11 simply shows the next 12 months in any culture. .PARAMETER Three Display the current month together with the previous and following month. This is ignored if -Year is also specified without a month. .PARAMETER DayOfYear Display the day of the year (days one-based, numbered from 1st January). .PARAMETER Week Display the number of the week below each week column. .PARAMETER LongDayName Display full day names for the required culture or calendar, instead of abbreviated day names (default). For some cultures, there is no difference. .PARAMETER Name Display the name of the specified culture and/or calendar name as a banner above the calendar. .EXAMPLE PS C:\> ncal Displays this month using the current culture .EXAMPLE PS C:\> ncal -month 1 -after 11 -culture fa Displays the first month and the following 11 months (this year) for any specified culture. For example, -Year 2025 with cultures that do not use the Gregorian calendar by default will not work or produce unintended results. Some cultures use the Persian (Iranian), ThaiBuddist and UmAlQura (Umm al-Qura, Saudi Arabian) calendars by default. .EXAMPLE PS C:\> ncal -m 1f Displays January next year. -m 4p shows April from the previous year .EXAMPLE PS C:\> ncal -m 4 -y 2025 -b 2 -a 1 Displays April 2025 with the two months before and one month after it. .EXAMPLE PS C:\> ncal -y 2025 -a 24 Shows 2025 through 2027 .EXAMPLE PS C:\> ncal -DayOfYear -three Show the day number, starting from 1st January, for this month as well as last month and next month. .EXAMPLE PS C:\> ncal 2 2026 -three Show February 2026 with the month prior and month after. .EXAMPLE PS C:> ncal -Year 2025 -Week -H Cyan Shows the specified year with a highlighted colour. Supports red, blue, green, yellow, orange, pink, cyan, magenta and white. Disable all highlighting with - Highlight 'none'. Week numbers are shown below each week column and are also highlighted. .EXAMPLE PS C:> ncal -culture ja-JP -Year 2025 -Highlight Orange Display a calender using the Japanese (Japan) culture for the specified year. .EXAMPLE PS C:> 'Persian','Hijri','UmAlQura' | % { ncal -calendar $_ -name } Display three calendars (the current month) using the specified calendars with a banner to identify each culture/calendar. .EXAMPLE PC C:> 'en-au','en-us','dv','mzn' | % { ncal -c $_ -Name -Week -Highlight Yellow } Display calendars for the specified cultures. This example illustrates the different DateTimeFormat information for each culture (different start days for the week). .EXAMPLE PS C:> ncal -calendar Julian -m 1 -a 11 Shows this year in the Julian calendar. Note: This actually works correctly, unlike the Linux ncal command (as at Feb 2025), which sometimes shows the wrong month (shows this Julian month but in terms of month number on the Gregorian calendar), depending on the day of the month. .EXAMPLE PS C:> ncal -cal Hijri -m 1 -a 11 Shows this year in the Hijri (Muslim) calendar. Note: This is not supported with Linux ncal command. .LINK https://github.com/atkinsroy/ncal/docs .INPUTS [System.String] [System.Int] .OUTPUTS [System.String] .NOTES Author: Roy Atkins #> [Alias('ncal')] [CmdletBinding(DefaultParameterSetName = 'UseCulture')] param( # Could be integer between 1 and 13 or the same with an 'f' or 'p' suffix. [Parameter(Position = 0)] [Alias('m')] [String]$Month, [Parameter(Position = 1)] [ValidateRange(1, 9999)] [Int]$Year, [Parameter(Position = 2, ParameterSetName = 'UseCulture')] [Alias('c', 'cul')] [String]$Culture, [Parameter(Position = 2, ParameterSetName = 'UseCalendar')] [Alias('cal')] [ValidateSet( 'Gregorian', 'Persian', 'Hijri', 'Hebrew', 'Japanese', 'Korean', 'Taiwan', 'UmAlQura', 'ThaiBuddhist', 'Julian', 'ChineseLunisolar', 'JapaneseLunisolar', 'KoreanLunisolar', 'TaiwanLunisolar')] [String]$Calendar, [Parameter(Position = 3)] [ValidateSet('Friday', 'Saturday', 'Sunday', 'Monday')] [String]$FirstDayOfWeek, [Parameter(Position = 4)] [Alias('r', 'row')] [ValidateRange(1, 6)] [Int]$MonthPerRow = 4, [Parameter(Position = 5)] [ValidateSet('None', 'Red', 'Green', 'Blue', 'Yellow', 'Cyan', 'Magenta', 'White', 'Orange', 'Pink')] [String]$Highlight, [Int]$Before, [Int]$After, [Switch]$Three, [Switch]$DayOfYear, [Switch]$Week, [Switch]$LongDayName, [switch]$Name ) begin { $Abort = $false if ($PSBoundParameters.ContainsKey('Culture')) { try { $ThisCulture = New-Object System.Globalization.CultureInfo($Culture) -ErrorAction Stop # The above doesn't alway capture a dodgy culture so test further $AllCulture = (Get-Culture -ListAvailable).Name if ($Culture -notin $AllCulture) { Write-Warning "Invalid culture: '$Culture'. Using the system default culture ($((Get-Culture).Name)). Use 'Get-Culture -ListAvailable'." $ThisCulture = [System.Globalization.CultureInfo]::CurrentCulture } } catch { Write-Warning "Invalid culture specified:'$Culture'. Using the system default culture ($((Get-Culture).Name)). Use 'Get-Culture -ListAvailable'." $ThisCulture = [System.Globalization.CultureInfo]::CurrentCulture } $ThisCalendar = $ThisCulture.Calendar } elseif ($PSBoundParameters.ContainsKey('Calendar')) { $CultureLookup = @{ 'Gregorian' = 'en-AU' 'Persian' = 'fa-IR' 'Hijri' = 'ar-SA' 'Hebrew' = 'he-IL' 'Japanese' = 'ja-JP' 'Korean' = 'ko-KR' 'Taiwan' = 'zh-Hant-TW' 'UmAlQura' = 'ar-SA' 'ThaiBuddhist' = 'th-th' 'Julian' = 'en-AU' 'ChineseLunisolar' = 'zh' 'JapaneseLunisolar' = 'ja' 'KoreanLunisolar' = 'ko' 'TaiwanLunisolar' = 'zh-Hant-TW' } <# In order to support Julian and Asian Lunar calendars ('non-optional'), treat culture and calendar separately. With optional calenders you can set the culture to use them, but this doesn't work for the above. #> $ThisCulture = New-Object System.Globalization.CultureInfo($($CultureLookup[$Calendar])) $ThisCalendar = New-Object "System.Globalization.$($Calendar)Calendar" } else { $ThisCulture = [System.Globalization.CultureInfo]::CurrentCulture $ThisCalendar = $ThisCulture.Calendar } # Display the Culture/Calendar name as a heading if ($PSBoundParameters.ContainsKey('Name') -and $PSBoundParameters.ContainsKey('Calendar')) { $CalendarString = $($ThisCalendar.ToString().Replace('System.Globalization.', '').Replace('Calendar', ' Calendar')) Write-Output "`n$($PSStyle.Reverse)--|$CalendarString|--$($PSStyle.ReverseOff)`n" } elseif ($PSBoundParameters.ContainsKey('Name')) { $CalendarString = $($ThisCulture.Calendar.ToString().Replace('System.Globalization.', '').Replace('Calendar', ' Calendar')) Write-Output "`n$($PSStyle.Reverse)--|$($ThisCulture.Name)|-|$($ThisCulture.DisplayName)|-|$CalendarString|--$($PSStyle.ReverseOff)`n" } # Some optional calendars don't display week numbers accurately. In these case don't display week numbers $IgnoreWeekRow = $false $IgnoreCalendar = @( 'HijriCalendar', 'HebrewCalendar', 'JulianCalendar', 'ChineseLunisolarCalendar', 'JapaneseLunisolarCalendar', 'KoreanLunisolarCalendar', 'TaiwanLunisolarCalendar' ) $CompareCalender = $($ThisCalendar.ToString().Replace('System.Globalization.', '')) if ($PSBoundParameters.ContainsKey('Week') -and $CompareCalender -in $IgnoreCalendar) { $IgnoreWeekRow = $true Write-Warning "Displaying week numbers is not supported with the $($CompareCalender.Replace('Calendar',' calendar'))" } # Full month names in current culture $MonthNameArray = $ThisCulture.DateTimeFormat.MonthGenitiveNames # Calling DayOfYear 'Julian' is incorrect. JulianDay is something else altogether. However keep 'Julian' as # its more eye-catching. if ($PSBoundParameters.ContainsKey('DayOfYear')) { [Bool]$JulianSpecified = $true } else { [Bool]$JulianSpecified = $false } # List of cultural specific day names in the required order. if ($PSBoundParameters.ContainsKey('FirstDayOfWeek')) { $FirstDay = $FirstDayOfWeek } else { $FirstDay = $ThisCulture.DateTimeFormat.FirstDayOfWeek } $Param = @{ 'Culture' = $ThisCulture 'FirstDayOfWeek' = $FirstDay 'JulianSpecified' = $JulianSpecified 'LongDayName' = $LongDayName } $WeekDay = Get-WeekDayName @Param # Get the date of the first day of each required month, based on the culture (common to ncal & cal) $DateParam = New-Object -TypeName System.Collections.Hashtable $DateParam.Add('Calendar', $ThisCalendar) if ($PSBoundParameters.ContainsKey('Month')) { $DateParam.Add('Month', $Month) } if ($PSBoundParameters.ContainsKey('Year')) { $DateParam.Add('Year', $Year) } if ($PSBoundParameters.ContainsKey('Before')) { $DateParam.Add('Before', $Before) } if ($PSBoundParameters.ContainsKey('After')) { $DateParam.Add('After', $After) } if ($PSBoundParameters.ContainsKey('Three')) { $DateParam.Add('Three', $Three) } # this is where most parameter validation occurs, and most of the date conversion stuff. try { $MonthList = Get-FirstDayOfMonth @DateParam -ErrorAction Stop } catch { Write-Error $PSItem.Exception.Message $Abort = $true } # To hold each row of 1 to 6 months, initialized with culture specific day abbreviation [System.Collections.Generic.List[String]]$MonthRow = $WeekDay.Name $MonthCount = 0 $MonthHeading = ' ' * $WeekDay.Offset $WeekRow = ' ' * ($WeekDay.Offset - 1) } process { foreach ($RequiredMonth in $MonthList) { if ($true -eq $Abort) { return } $ThisYear = $RequiredMonth.Year $ThisMonth = $RequiredMonth.Month $DayPerMonth = $RequiredMonth.DayPerMonth $FirstDayIndex = $RequiredMonth.FirstDayIndex $YearSpecified = $RequiredMonth.YearSpecified $MonthName = $MonthNameArray[$ThisMonth - 1] # MonthNameArray is zero based if (13 -eq $ThisMonth) { $MonthName = '13' } if ($PSBoundParameters.ContainsKey('Three') -or $PSBoundParameters.ContainsKey('Month') -or $false -eq $YearSpecified) { $MonthName = "$MonthName $ThisYear" } # for highlighting today $Pretty = Get-Highlight $ThisCalendar $ThisMonth $ThisYear $Highlight if ($PSBoundParameters.ContainsKey('Calendar')) { Write-Verbose "monthname = $MonthName, thismonth = $ThisMonth, thisyear = $ThisYear, dayspermonth = $DayPerMonth, monthcount = $MonthCount, calendar = $($ThisCalendar.ToString().Replace('System.Globalization.', '')), era = $($ThisCalendar.Eras[0])" } else { Write-Verbose "monthname = $MonthName, thismonth = $ThisMonth, thisyear = $ThisYear, dayspermonth = $DayPerMonth, monthcount = $MonthCount, culture = $($ThisCulture.Name)" } # User specified First day of the week, or use the default for the culture being used. if ($PSBoundParameters.ContainsKey('FirstDayOfWeek')) { if ('Friday' -eq $FirstDayOfWeek) { $StartWeekDay = 'Friday' } elseif ('Saturday' -eq $FirstDayOfWeek) { $StartWeekDay = 'Saturday' } elseif ('Sunday' -eq $FirstDayOfWeek) { $StartWeekDay = 'Sunday' } else { $StartWeekDay = 'Monday' } } else { $StartWeekDay = $ThisCulture.DateTimeFormat.FirstDayOfWeek } # Get the starting index for the month, to offset when to start printing dates in the row. $Param = @{ 'StartWeekDay' = $StartWeekDay 'FirstDayIndex' = $FirstDayIndex } $ThisIndex = Get-StartWeekIndex @Param # User can choose number of months to print per row, default is 4. In this case, just append the month. if ($MonthCount -lt $MonthPerRow) { $Param = @{ 'Culture' = $ThisCulture 'MonthName' = $MonthName 'JulianSpecified' = $JulianSpecified } $MonthHeading += "$(Get-MonthHeading @Param)" if ($PSBoundParameters.ContainsKey('Week') -and -not $IgnoreWeekRow) { $Param = @{ 'Culture' = $ThisCulture 'Calendar' = $ThisCalendar 'Month' = $ThisMonth 'Year' = $ThisYear 'FirstDayOfWeek' = $StartWeekDay 'Index' = $ThisIndex 'JulianSpecified' = $JulianSpecified } $WeekRow += Get-WeekRow @Param } } else { # Print a year heading before January when year is specified when the year is not already in month name if ($MonthHeading -match "\b$($MonthNameArray[0])\b" -and $MonthName -notmatch $ThisYear) { $YearPad = (((18 * $MonthPerRow) + 3 - 2 ) / 2) + 2 $YearHeading = "$ThisYear".PadLeft($YearPad, ' ') Write-Output "$($Pretty.MonStyle)$YearHeading$($Pretty.MonReset)" } Write-Output "$($Pretty.MonStyle)$MonthHeading$($Pretty.MonReset)" Write-Output $MonthRow if ($PSBoundParameters.ContainsKey('Week') -and -not $IgnoreWeekRow) { Write-Output "$($Pretty.MonStyle)$WeekRow$($Pretty.MonReset)" } Write-Output '' # Reset for next row of months [System.Collections.Generic.List[String]]$MonthRow = $WeekDay.Name $MonthCount = 0 $Param = @{ 'Culture' = $ThisCulture 'MonthName' = $MonthName 'JulianSpecified' = $JulianSpecified } $MonthHeading = (' ' * $WeekDay.Offset) + "$(Get-MonthHeading @Param)" if ($PSBoundParameters.ContainsKey('Week') -and -not $IgnoreWeekRow) { $Param = @{ 'Culture' = $ThisCulture 'Calendar' = $ThisCalendar 'Month' = $ThisMonth 'Year' = $ThisYear 'FirstDayOfWeek' = $StartWeekDay 'Index' = $ThisIndex 'JulianSpecified' = $JulianSpecified } $WeekRow = Get-WeekRow @Param # starting padding for the week row is dependent on first week number if (1 -eq "$WeekRow".Split(' ')[0].Length) { $WeekRow = (' ' * $WeekDay.Offset) + $WeekRow } else { $WeekRow = (' ' * ($WeekDay.Offset - 1)) + $WeekRow } } } 0..6 | ForEach-Object { $Param = @{ 'Calendar' = $ThisCalendar 'Index' = $ThisIndex 'DayPerMonth' = $DayPerMonth 'Month' = $ThisMonth 'Year' = $ThisYear 'Highlight' = $Pretty 'JulianSpecified' = $JulianSpecified } $MonthRow[$_] += "$(Get-NcalRow @Param)" $ThisIndex++ } $MonthCount++ } } end { # Write the last month or row of months # Print a year heading before January when there is no year already in the month name. if (-Not $Abort) { if ($MonthHeading -match "\b$($MonthNameArray[0])\b" -and $MonthName -notmatch $ThisYear) { $YearPad = (((18 * $MonthPerRow) + 3 - 2 ) / 2) + 2 $YearHeading = "$ThisYear".PadLeft($YearPad, ' ') Write-Output "$($Pretty.MonStyle)$YearHeading$($Pretty.MonReset)" } Write-Output "$($Pretty.MonStyle)$MonthHeading$($Pretty.MonReset)" Write-Output $MonthRow if ($PSBoundParameters.ContainsKey('Week') -and -not $IgnoreWeekRow) { Write-Output "$($Pretty.MonStyle)$WeekRow$($Pretty.MonReset)" } } } } function Get-Calendar { <# .SYNOPSIS Get-Calendar .DESCRIPTION This command displays calendar information similar to the Linux cal command. It implements the same functionality, including the ability to display multiple months, years, day of the year and month forward and month previous by one year. But in addition, the command can do a whole lot more: 1. Display a calendar in any supported culture. Month and day names are displayed in the appropriate language for the specified culture and the appropriate calendar is used (e.g. Gregorian, Persian), along with appropriate DateTimeFormat information (e.g. default first day of the week). 2. As well as display the primary calendar (used by each culture), also display optional calendars. These are Hijri, Hebrew, Japanese (Solar), Korean (Solar) and Taiwanese (Solar) calendars. In addition, the non-optional calendars (i.e. calendars not used by any culture, but still observed for religious, scientific or traditional purposes). These are the Julian and Chinese, Japanese, Korean and Taiwanese Lunar calendars. (Note: Only the current era is supported). 3. Specify the first day of the week (Friday through Monday). The specified or current culture setting is used by default. Friday through Monday are supported because all cultures use one of these days. 4. Display one to six months in a row, when multiple months are displayed (the default is 3). 5. Highlight the year and month headings and todays date using a specified colour. It is highly recommended that Windows Terminal is used with an appropriate font to ensure that ISO unicode character sets are both available and are displayed correctly. With other consoles, like Visual Studio Code, the ISE and the default PowerShell console, some fonts might not display correctly and with extended unicode character sets, calendars may appear misaligned. Note: From version 1.22.10352.0 (Feb 2025) of Windows Terminal, grapheme clusters are now supported and are turned on by default. A grapheme cluster is a single user-perceived character made up of multiple code points from the Unicode Standard, introduced in .NET 5. Whilst this is considered the correct method for handling and displaying Unicode character sets, PowerShell doesn't support grapheme clusters and thus, calandars in ncal appear misaligned. This can be remedied, in the short term, by disabling grapheme cluster support in Settings > Compatibility > Text measurement mode, selecting "Windows Console" and then restarting the Windows Terminal. .PARAMETER Month Specifies the required month. This must be specified as a number 0..13. An 'f' (forward by one year) or a 'p' (previous year) suffix can also be appended to the month number. .PARAMETER Year Specifies the required year. If no month is specified, the whole year is shown. .PARAMETER Culture Specifies the required culture. The system default culture is used by default. .PARAMETER Calendar Instead of a culture, specify a calendar. This allows displaying optional and other calendars not used by any culture. They include Julian, Hijri, Hebrew and Chinese Lunar calendars. .PARAMETER FirstDayOfWeek Display the specified first day of the week. By default, the required culture is used to determine this. .PARAMETER MonthPerRow Display the specified number of months in each row. By default it is 3 months. .PARAMETER Highlight By default, today's date is highlighted. Specify a colour to highlight today, the year/month headings and week numbers or disable the default highlight with 'none'. .PARAMETER Before The specified number of months are added before the specified month(s). See -After for examples. .PARAMETER After The specified number of months are added after the specified month(s). This is in addition to any date range selected by the -Year or -Three options. Negative numbers are allowed, in which case the specified number of months is subtracted. For example ncal -after 11 simply shows the next 12 months in any culture. .PARAMETER Three Display the current month together with the previous and following month. This is ignored if -Year is also specified without a month. .PARAMETER DayOfYear Display the day of the year (days one-based, numbered from 1st January). .PARAMETER Name Display the name of the specified culture and/or calendar name as a banner above the calendar. .EXAMPLE PS C:\> cal Displays this month using the current culture .EXAMPLE PS C:\> cal -m 1 -a 11 -culture fa Displays the first month and the following 11 months (this year) for any specified culture. For example, -Year 2025 with cultures that do not use the Gregorian calendar by default will not work or produce unintended results. Some cultures use the Persian (Iranian), ThaiBuddist and UmAlQura (Umm al-Qura, Saudi Arabian) calendars by default. .EXAMPLE PS C:\> cal -m 1f Displays January next year. -m 4p shows April from the previous year .EXAMPLE PS C:\> cal -m 4 -y 2025 -b 2 -a 1 Displays April 2025 with the two months before and one month after it. .EXAMPLE PS C:\> cal -y 2025 -a 24 Shows 2025 through 2027 .EXAMPLE PS C:\> cal -DayOfYear -Three Show the day number, starting from 1st January, for this month as well as last month and next month. .EXAMPLE PS C:\> cal 2 2026 -three Show February 2026 with the month prior and month after. .EXAMPLE PS C:> cal -Year 2025 -Highlight Cyan Shows the specified year with a highlighted colour. Supports red, blue, green, yellow, orange, pink, cyan, magenta and white. Disable all highlighting with - Highlight 'none'. .EXAMPLE PS C:> cal -culture ja-JP -Year 2025 -Highlight Orange Display a calender using the Japanese (Japan) culture for the specified year. .EXAMPLE PS C:> 'Persian','Hijri','UmAlQura' | % { cal -calendar $_ -name } Display three calendars (the current month) using the specified calendars with a banner to identify each culture/calendar. .EXAMPLE PC C:> 'en-au','en-us','dv','mzn' | % { ncal -c $_ -Name -Week -Highlight Yellow } Display calendars for the specified cultures. This example illustrates the different DateTimeFormat information for each culture (different start days for the week). .EXAMPLE PS C:> ncal -calendar Julian -m 1 -a 11 Shows this year in the Julian calendar. Note: This actually works correctly, unlike the Linux ncal command (as at Feb 2025), which sometimes shows the wrong month (shows this Julian month but in terms of month number on the Gregorian calendar), depending on the day of the month. .EXAMPLE PS C:> ncal -cal Hijri -m 1 -a 11 Shows this year in the Hijri (Muslim) calendar. Note: This is not supported with Linux ncal command. .LINK https://github.com/atkinsroy/ncal/docs .INPUTS [System.String] [System.Int] .OUTPUTS [System.String] .NOTES Author: Roy Atkins #> [Alias('cal')] [CmdletBinding(DefaultParameterSetName = 'UseCulture')] param( # Could be integer between 1 and 13 or the same with an 'f' or 'p' suffix. [Parameter(Position = 0)] [Alias('m')] [String]$Month, [Parameter(Position = 1)] [ValidateRange(1, 9999)] [Int]$Year, [parameter(Position = 2, ParameterSetName = 'UseCulture')] [Alias('c', 'cul')] [String]$Culture, [Parameter(Position = 2, ParameterSetName = 'UseCalendar')] [Alias('cal')] [ValidateSet( 'Gregorian', 'Persian', 'Hijri', 'Hebrew', 'Japanese', 'Korean', 'Taiwan', 'UmAlQura', 'ThaiBuddhist', 'Julian', 'ChineseLunisolar', 'JapaneseLunisolar', 'KoreanLunisolar', 'TaiwanLunisolar')] [String]$Calendar, [parameter(Position = 3)] [ValidateSet('Friday', 'Saturday', 'Sunday', 'Monday')] [String]$FirstDayOfWeek, [parameter(Position = 4)] [Alias('r', 'row')] [ValidateRange(1, 6)] [Int]$MonthPerRow = 3, [parameter(Position = 5)] [ValidateSet('None', 'Red', 'Green', 'Blue', 'Yellow', 'Cyan', 'Magenta', 'White', 'Orange', 'Pink')] [String]$Highlight, [Int]$Before, [Int]$After, [Switch]$Three, [Switch]$DayOfYear, [Switch]$Name ) begin { $Abort = $false if ($PSBoundParameters.ContainsKey('Culture')) { try { $ThisCulture = New-Object System.Globalization.CultureInfo($Culture) -ErrorAction Stop # The above doesn't alway capture a dodgy culture so test further $AllCulture = (Get-Culture -ListAvailable).Name if ($Culture -notin $AllCulture) { Write-Warning "Invalid culture: '$Culture'. Using the system default culture ($((Get-Culture).Name)). Use 'Get-Culture -ListAvailable'." $ThisCulture = [System.Globalization.CultureInfo]::CurrentCulture } } catch { Write-Warning "Invalid culture specified:'$Culture'. Using the system default culture ($((Get-Culture).Name)). Use 'Get-Culture -ListAvailable'." $ThisCulture = [System.Globalization.CultureInfo]::CurrentCulture } $ThisCalendar = $ThisCulture.Calendar } elseif ($PSBoundParameters.ContainsKey('Calendar')) { $CultureLookup = @{ 'Gregorian' = 'en-AU' 'Persian' = 'fa-IR' 'Hijri' = 'ar-SA' 'Hebrew' = 'he-IL' 'Japanese' = 'ja-JP' 'Korean' = 'ko-KR' 'Taiwan' = 'zh-Hant-TW' 'UmAlQura' = 'ar-SA' 'ThaiBuddhist' = 'th-th' 'Julian' = 'en-AU' 'ChineseLunisolar' = 'zh' 'JapaneseLunisolar' = 'ja' 'KoreanLunisolar' = 'ko' 'TaiwanLunisolar' = 'zh-Hant-TW' } <# In order to support Julian and Asian Lunar calendars ('non-optional'), treat culture and calendar separately. With optional calenders you can set the culture to use them, but this doesn't work for the above. #> $ThisCulture = New-Object System.Globalization.CultureInfo($($CultureLookup[$Calendar])) $ThisCalendar = New-Object "System.Globalization.$($Calendar)Calendar" } else { $ThisCulture = [System.Globalization.CultureInfo]::CurrentCulture $ThisCalendar = $ThisCulture.Calendar } # Display the Culture/Calendar name as a heading if ($PSBoundParameters.ContainsKey('Name') -and $PSBoundParameters.ContainsKey('Calendar')) { $CalendarString = $($ThisCalendar.ToString().Replace('System.Globalization.', '').Replace('Calendar', ' Calendar')) Write-Output "`n$($PSStyle.Reverse)--|$CalendarString|--$($PSStyle.ReverseOff)`n" } elseif ($PSBoundParameters.ContainsKey('Name')) { $CalendarString = $($ThisCulture.Calendar.ToString().Replace('System.Globalization.', '').Replace('Calendar', ' Calendar')) Write-Output "`n$($PSStyle.Reverse)--|$($ThisCulture.Name)|-|$($ThisCulture.DisplayName)|-|$CalendarString|--$($PSStyle.ReverseOff)`n" } # Full month names in current culture $MonthNameArray = $ThisCulture.DateTimeFormat.MonthGenitiveNames # Calling DayOfYear 'Julian' is incorrect. JulianDay is something else altogether. However keep 'Julian' as # its more eye-catching. if ($PSBoundParameters.ContainsKey('DayOfYear')) { [Bool]$JulianSpecified = $true } else { [Bool]$JulianSpecified = $false } # List of cultural specific day names in the required order. if ($PSBoundParameters.ContainsKey('FirstDayOfWeek')) { $FirstDay = $FirstDayOfWeek } else { $FirstDay = $ThisCulture.DateTimeFormat.FirstDayOfWeek } $Param = @{ 'Culture' = $ThisCulture 'FirstDayOfWeek' = $FirstDay 'JulianSpecified' = $JulianSpecified } $WeekDay = Get-WeekDayName @Param # Get the date of the first day of each required month, based on the culture (common to ncal & cal) $DateParam = New-Object -TypeName System.Collections.Hashtable $DateParam.Add('Calendar', $ThisCalendar) if ($PSBoundParameters.ContainsKey('Month')) { $DateParam.Add('Month', $Month) } if ($PSBoundParameters.ContainsKey('Year')) { $DateParam.Add('Year', $Year) } if ($PSBoundParameters.ContainsKey('Three')) { $DateParam.Add('Three', $Three) } if ($PSBoundParameters.ContainsKey('Before')) { $DateParam.Add('Before', $Before) } if ($PSBoundParameters.ContainsKey('After')) { $DateParam.Add('After', $After) } # this is where most parameter validation occurs, and most of the date conversion stuff. try { $MonthList = Get-FirstDayOfMonth @DateParam -ErrorAction Stop } catch { Write-Error $PSItem.Exception.Message $Abort = $true } # initialize a strongly typed, fixed length array with no values. $MonthRow = New-Object -TypeName System.String[] -ArgumentList 7 $MonthCount = 0 $MonthHeading = '' } process { foreach ($RequiredMonth in $MonthList) { if ($true -eq $Abort) { return } $ThisYear = $RequiredMonth.Year $ThisMonth = $RequiredMonth.Month $DayPerMonth = $RequiredMonth.DayPerMonth $FirstDayIndex = $RequiredMonth.FirstDayIndex $YearSpecified = $RequiredMonth.YearSpecified $MonthName = $MonthNameArray[$ThisMonth - 1] # MonthNameArray is zero based if (13 -eq $ThisMonth) { $MonthName = '13' } if ($PSBoundParameters.ContainsKey('Three') -or $PSBoundParameters.ContainsKey('Month') -or $false -eq $YearSpecified) { $MonthName = "$MonthName $ThisYear" } # for highlighting today $Pretty = Get-Highlight $ThisCalendar $ThisMonth $ThisYear $Highlight if ($PSBoundParameters.ContainsKey('Calendar')) { Write-Verbose "monthname = $MonthName, thismonth = $ThisMonth, thisyear = $ThisYear, dayspermonth = $DayPerMonth, monthcount = $MonthCount, calendar = $($ThisCalendar.ToString().Replace('System.Globalization.', '')), era = $($ThisCalendar.Eras[0])" } else { Write-Verbose "monthname = $MonthName, thismonth = $ThisMonth, thisyear = $ThisYear, dayspermonth = $DayPerMonth, monthcount = $MonthCount, culture = $($ThisCulture.Name)" } # User specified First day of the week, or use the default for the culture being used. if ($PSBoundParameters.ContainsKey('FirstDayOfWeek')) { if ('Friday' -eq $FirstDayOfWeek) { $StartWeekDay = 'Friday' } elseif ('Saturday' -eq $FirstDayOfWeek) { $StartWeekDay = 'Saturday' } elseif ('Sunday' -eq $FirstDayOfWeek) { $StartWeekDay = 'Sunday' } else { $StartWeekDay = 'Monday' } } else { $StartWeekDay = $ThisCulture.DateTimeFormat.FirstDayOfWeek } # Get the starting index for the month, to offset when to start printing dates in the row. $Param = @{ 'StartWeekDay' = $StartWeekDay 'FirstDayIndex' = $FirstDayIndex } $ThisIndex = Get-StartWeekIndex @Param # User can choose number of months to print per row, default is 3. In this case, just append the month. if ($MonthCount -lt $MonthPerRow) { $Param = @{ 'Culture' = $ThisCulture 'MonthName' = $MonthName 'JulianSpecified' = $JulianSpecified } $MonthHeading += "$(Get-MonthHeading @Param)" } else { # Print a year heading before January when year is specified when the year is not already in month name if ($MonthHeading -match "\b$($MonthNameArray[0])\b" -and $MonthName -notmatch $ThisYear) { $YearPad = (((22 * $MonthPerRow) - 2 ) / 2) + 2 $YearHeading = "$ThisYear".PadLeft($YearPad, ' ') Write-Output "$($Pretty.MonStyle)$YearHeading$($Pretty.MonReset)" } Write-Output "$($Pretty.MonStyle)$MonthHeading$($Pretty.MonReset)" Write-Output $MonthRow Write-Output '' # Reset for next row of months $MonthRow = New-Object -TypeName System.String[] -ArgumentList 7 $MonthCount = 0 $Param = @{ 'Culture' = $ThisCulture 'MonthName' = $MonthName 'JulianSpecified' = $JulianSpecified } $MonthHeading = "$(Get-MonthHeading @Param)" } $MonthRow[0] += "$($WeekDay.Name)" + ' ' # PadRight doesn't work here because of double width chars 1..6 | ForEach-Object { $Param = @{ 'Calendar' = $ThisCalendar 'Index' = $ThisIndex 'DayPerMonth' = $DayPerMonth 'Month' = $ThisMonth 'Year' = $ThisYear 'Highlight' = $Pretty 'JulianSpecified' = $JulianSpecified } $MonthRow[$_] += "$(Get-CalRow @Param)" $ThisIndex += 7 } $MonthCount++ } } end { # Write the last month or row of months # Print a year heading before Month 1 when year is specified if (-Not $Abort) { if ($MonthHeading -match "\b$($MonthNameArray[0])\b" -and $MonthName -notmatch $ThisYear) { $YearPad = (((22 * $MonthPerRow) - 2 ) / 2) + 2 $YearHeading = "$ThisYear".PadLeft($YearPad, ' ') Write-Output "$($Pretty.MonStyle)$YearHeading$($Pretty.MonReset)" } Write-Output "$($Pretty.MonStyle)$MonthHeading$($Pretty.MonReset)" Write-Output $MonthRow } } } function Get-Now { <# .SYNOPSIS Get today's date in any of the calendars supported by .NET .DESCRIPTION Displays today's date in any of the calendars supported by .NET Framework. By default, today's date for every supported calendar is shown. The Gregorian calendar is always shown, to compare with the specified calendar. .LINK https://github.com/atkinsroy/ncal/docs .EXAMPLE Get-Now Displays today's date in every supported calendar .EXAMPLE Get-Now -Calendar Persian,Hijri,UmAlQura Displays today's date for each specified calendar, along with the Gregorian calendar. #> [CmdletBinding()] param ( [parameter(Position = 0)] [ValidateSet( 'Julian', 'Hijri', 'Persian', 'UmAlQura', 'ChineseLunisolar', 'Hebrew', 'Japanese', 'JapaneseLunisolar', 'Korean', 'KoreanLunisolar', 'Taiwan', 'TaiwanLunisolar', 'ThaiBuddhist' )] [String[]]$Calendar = @( 'Julian', 'Hijri', 'Persian', 'UmAlQura', 'ChineseLunisolar', 'Hebrew', 'Japanese', 'JapaneseLunisolar', 'Korean', 'KoreanLunisolar', 'Taiwan', 'TaiwanLunisolar', 'ThaiBuddhist') ) begin { $Now = Get-Date $Cal = New-Object -TypeName 'System.Globalization.GregorianCalendar' $CalMonth = $Cal.GetMonth($Now) $CalYear = $Cal.GetYear($Now) [PSCustomObject] @{ 'PSTypeName' = 'Ncal.Date' 'Calendar' = 'GregorianCalendar' 'Day' = $Cal.GetDayOfMonth($Now) 'Month' = $CalMonth 'Year' = $CalYear 'DaysInMonth' = $Cal.GetDaysInMonth($CalYear, $CalMonth) 'MonthsInYear' = $Cal.GetMonthsInYear($CalYear) 'Era' = $Cal.Eras[0] } } process { foreach ($ThisCalendar in $Calendar) { $ThisCalendarString = "$($ThisCalendar)Calendar" $Cal = New-Object -TypeName "System.Globalization.$ThisCalendarString" $CalMonth = $Cal.GetMonth($Now) $CalYear = $Cal.GetYear($Now) [PSCustomObject] @{ 'PSTypeName' = 'Ncal.Date' 'Calendar' = $ThisCalendarString 'Day' = $Cal.GetDayOfMonth($Now) 'Month' = $CalMonth 'Year' = $CalYear 'DaysInMonth' = $Cal.GetDaysInMonth($CalYear, $CalMonth) 'MonthsInYear' = $Cal.GetMonthsInYear($CalYear) 'Era' = $Cal.Eras[0] } } } } # SIG # Begin signature block # MIItkwYJKoZIhvcNAQcCoIIthDCCLYACAQExDzANBglghkgBZQMEAgEFADB5Bgor # BgEEAYI3AgEEoGswaTA0BgorBgEEAYI3AgEeMCYCAwEAAAQQH8w7YFlLCE63JNLG # KX7zUQIBAAIBAAIBAAIBAAIBADAxMA0GCWCGSAFlAwQCAQUABCBple+6qZrveQBj # 2ctij2oOU6K47yYGFXj71Def5ZmBpKCCEXUwggVvMIIEV6ADAgECAhBI/JO0YFWU # jTanyYqJ1pQWMA0GCSqGSIb3DQEBDAUAMHsxCzAJBgNVBAYTAkdCMRswGQYDVQQI # DBJHcmVhdGVyIE1hbmNoZXN0ZXIxEDAOBgNVBAcMB1NhbGZvcmQxGjAYBgNVBAoM # EUNvbW9kbyBDQSBMaW1pdGVkMSEwHwYDVQQDDBhBQUEgQ2VydGlmaWNhdGUgU2Vy # dmljZXMwHhcNMjEwNTI1MDAwMDAwWhcNMjgxMjMxMjM1OTU5WjBWMQswCQYDVQQG # EwJHQjEYMBYGA1UEChMPU2VjdGlnbyBMaW1pdGVkMS0wKwYDVQQDEyRTZWN0aWdv # IFB1YmxpYyBDb2RlIFNpZ25pbmcgUm9vdCBSNDYwggIiMA0GCSqGSIb3DQEBAQUA # A4ICDwAwggIKAoICAQCN55QSIgQkdC7/FiMCkoq2rjaFrEfUI5ErPtx94jGgUW+s # hJHjUoq14pbe0IdjJImK/+8Skzt9u7aKvb0Ffyeba2XTpQxpsbxJOZrxbW6q5KCD # J9qaDStQ6Utbs7hkNqR+Sj2pcaths3OzPAsM79szV+W+NDfjlxtd/R8SPYIDdub7 # P2bSlDFp+m2zNKzBenjcklDyZMeqLQSrw2rq4C+np9xu1+j/2iGrQL+57g2extme # me/G3h+pDHazJyCh1rr9gOcB0u/rgimVcI3/uxXP/tEPNqIuTzKQdEZrRzUTdwUz # T2MuuC3hv2WnBGsY2HH6zAjybYmZELGt2z4s5KoYsMYHAXVn3m3pY2MeNn9pib6q # RT5uWl+PoVvLnTCGMOgDs0DGDQ84zWeoU4j6uDBl+m/H5x2xg3RpPqzEaDux5mcz # mrYI4IAFSEDu9oJkRqj1c7AGlfJsZZ+/VVscnFcax3hGfHCqlBuCF6yH6bbJDoEc # QNYWFyn8XJwYK+pF9e+91WdPKF4F7pBMeufG9ND8+s0+MkYTIDaKBOq3qgdGnA2T # OglmmVhcKaO5DKYwODzQRjY1fJy67sPV+Qp2+n4FG0DKkjXp1XrRtX8ArqmQqsV/ # AZwQsRb8zG4Y3G9i/qZQp7h7uJ0VP/4gDHXIIloTlRmQAOka1cKG8eOO7F/05QID # AQABo4IBEjCCAQ4wHwYDVR0jBBgwFoAUoBEKIz6W8Qfs4q8p74Klf9AwpLQwHQYD # VR0OBBYEFDLrkpr/NZZILyhAQnAgNpFcF4XmMA4GA1UdDwEB/wQEAwIBhjAPBgNV # HRMBAf8EBTADAQH/MBMGA1UdJQQMMAoGCCsGAQUFBwMDMBsGA1UdIAQUMBIwBgYE # VR0gADAIBgZngQwBBAEwQwYDVR0fBDwwOjA4oDagNIYyaHR0cDovL2NybC5jb21v # ZG9jYS5jb20vQUFBQ2VydGlmaWNhdGVTZXJ2aWNlcy5jcmwwNAYIKwYBBQUHAQEE # KDAmMCQGCCsGAQUFBzABhhhodHRwOi8vb2NzcC5jb21vZG9jYS5jb20wDQYJKoZI # hvcNAQEMBQADggEBABK/oe+LdJqYRLhpRrWrJAoMpIpnuDqBv0WKfVIHqI0fTiGF # OaNrXi0ghr8QuK55O1PNtPvYRL4G2VxjZ9RAFodEhnIq1jIV9RKDwvnhXRFAZ/ZC # J3LFI+ICOBpMIOLbAffNRk8monxmwFE2tokCVMf8WPtsAO7+mKYulaEMUykfb9gZ # pk+e96wJ6l2CxouvgKe9gUhShDHaMuwV5KZMPWw5c9QLhTkg4IUaaOGnSDip0TYl # d8GNGRbFiExmfS9jzpjoad+sPKhdnckcW67Y8y90z7h+9teDnRGWYpquRRPaf9xH # +9/DUp/mBlXpnYzyOmJRvOwkDynUWICE5EV7WtgwggXgMIIESKADAgECAhBe0Nem # O8DFLKPUj+l9xr3tMA0GCSqGSIb3DQEBDAUAMFQxCzAJBgNVBAYTAkdCMRgwFgYD # VQQKEw9TZWN0aWdvIExpbWl0ZWQxKzApBgNVBAMTIlNlY3RpZ28gUHVibGljIENv # ZGUgU2lnbmluZyBDQSBSMzYwHhcNMjIwNjA3MDAwMDAwWhcNMjUwNjA2MjM1OTU5 # WjB3MQswCQYDVQQGEwJVUzEOMAwGA1UECAwFVGV4YXMxKzApBgNVBAoMIkhld2xl # dHQgUGFja2FyZCBFbnRlcnByaXNlIENvbXBhbnkxKzApBgNVBAMMIkhld2xldHQg # UGFja2FyZCBFbnRlcnByaXNlIENvbXBhbnkwggGiMA0GCSqGSIb3DQEBAQUAA4IB # jwAwggGKAoIBgQC6ng2ELduVFspn7zNxeY/AS4Tij830RQECD/dUgsnqHOpnTX60 # CjMefSLxVsqkcIlD4V+deOiSs5JQDnupfshA6pV+tBiN1yWa3gTEX05RO2rYe7bc # xD1vaitATa1FpZ6n0s1KWP4M1f+UaKMcE6CE4dlCu1KATOXCMD0scA8qj4ScBMZR # mvZ4S8sNbwrQC5KpzOIHhQmiLRWEMGsDkyDXu6cCwPnsnHvEiNdX9mIBfMnARY5C # 0oD2ul2lKnjyMgZCWStI7zWXf8P8hU8Ji73+Pep1jeyn3jd/Gveza0fow+wd1fdx # 1+TS01bf054mbaipE5KKwqUD9XukKhsAEC+WLvTXMCGRpJZasSX2bKnR6ew0aEoX # c9zQ0BKa4ZFbWbEPceyBOlQVB+2f53Wplrvh+8z1B4yifG/91clQLsP+UZJqzvhX # E0cTcdb4aHaW/iHZQRAFdID0CBIHMNDTcYuaB+K961mvXYp+CxVDJF5kAT6W5KaM # QNuooNWjk/rO9FkCAwEAAaOCAYkwggGFMB8GA1UdIwQYMBaAFA8qyyCHKLjsb0iu # K1SmKaoXpM0MMB0GA1UdDgQWBBRsoQDhdo3sYBJ+aurMJLkY4RrLUDAOBgNVHQ8B # Af8EBAMCB4AwDAYDVR0TAQH/BAIwADATBgNVHSUEDDAKBggrBgEFBQcDAzBKBgNV # HSAEQzBBMDUGDCsGAQQBsjEBAgEDAjAlMCMGCCsGAQUFBwIBFhdodHRwczovL3Nl # Y3RpZ28uY29tL0NQUzAIBgZngQwBBAEwSQYDVR0fBEIwQDA+oDygOoY4aHR0cDov # L2NybC5zZWN0aWdvLmNvbS9TZWN0aWdvUHVibGljQ29kZVNpZ25pbmdDQVIzNi5j # cmwweQYIKwYBBQUHAQEEbTBrMEQGCCsGAQUFBzAChjhodHRwOi8vY3J0LnNlY3Rp # Z28uY29tL1NlY3RpZ29QdWJsaWNDb2RlU2lnbmluZ0NBUjM2LmNydDAjBggrBgEF # BQcwAYYXaHR0cDovL29jc3Auc2VjdGlnby5jb20wDQYJKoZIhvcNAQEMBQADggGB # AGSOCQjjqdaLpLxf8u4P14ItH+lKoeDGsIgBPxOMwM4/BAElYOCW5XWzUTI+joRN # zVi527GkhIrguRQrvS1q0xi9Sg/fTo/XyIMlW1/pssrwOuwUGw/LRSxpu50/Z0I8 # ZbHc55CbBSWC+LIskrxVvy+5nn1WMmyPaf1Hr/Fz2aUx/ILsGnR+vyPPRCaVV+0l # dOpBuTkuN+fPwcf8lCIrADPyyujLf34MJHbBXhOjiTM9WSoROKGQUJBRufA+vMKr # 45vPybW36vwMaMqPXLCfVQ/cXazuFZILmpmsn+6snKDkaLcBokZk7TPoKhSYrKLw # vkPMChrd8vVxyaPAoEkQEOOGDkINNMmkjk2Y2I0XcrB4GiiLC2Vple0tOXH4MIku # aWeIRxv+yELyNHLHj148LeN22FfucA2BPTbEH4YKOjw2Zbn1BP46QS0B3OlTGvwH # 6Jsgh71kTGR695wFzcZLvtmInxzhnfZb5fmiSw1mG4rA86/r6x1EpXIN/s1c1q7D # szCCBhowggQCoAMCAQICEGIdbQxSAZ47kHkVIIkhHAowDQYJKoZIhvcNAQEMBQAw # VjELMAkGA1UEBhMCR0IxGDAWBgNVBAoTD1NlY3RpZ28gTGltaXRlZDEtMCsGA1UE # AxMkU2VjdGlnbyBQdWJsaWMgQ29kZSBTaWduaW5nIFJvb3QgUjQ2MB4XDTIxMDMy # MjAwMDAwMFoXDTM2MDMyMTIzNTk1OVowVDELMAkGA1UEBhMCR0IxGDAWBgNVBAoT # D1NlY3RpZ28gTGltaXRlZDErMCkGA1UEAxMiU2VjdGlnbyBQdWJsaWMgQ29kZSBT # aWduaW5nIENBIFIzNjCCAaIwDQYJKoZIhvcNAQEBBQADggGPADCCAYoCggGBAJsr # nVP6NT+OYAZDasDP9X/2yFNTGMjO02x+/FgHlRd5ZTMLER4ARkZsQ3hAyAKwktlQ # qFZOGP/I+rLSJJmFeRno+DYDY1UOAWKA4xjMHY4qF2p9YZWhhbeFpPb09JNqFiTC # Yy/Rv/zedt4QJuIxeFI61tqb7/foXT1/LW2wHyN79FXSYiTxcv+18Irpw+5gcTbX # nDOsrSHVJYdPE9s+5iRF2Q/TlnCZGZOcA7n9qudjzeN43OE/TpKF2dGq1mVXn37z # K/4oiETkgsyqA5lgAQ0c1f1IkOb6rGnhWqkHcxX+HnfKXjVodTmmV52L2UIFsf0l # 4iQ0UgKJUc2RGarhOnG3B++OxR53LPys3J9AnL9o6zlviz5pzsgfrQH4lrtNUz4Q # q/Va5MbBwuahTcWk4UxuY+PynPjgw9nV/35gRAhC3L81B3/bIaBb659+Vxn9kT2j # Uztrkmep/aLb+4xJbKZHyvahAEx2XKHafkeKtjiMqcUf/2BG935A591GsllvWwID # AQABo4IBZDCCAWAwHwYDVR0jBBgwFoAUMuuSmv81lkgvKEBCcCA2kVwXheYwHQYD # VR0OBBYEFA8qyyCHKLjsb0iuK1SmKaoXpM0MMA4GA1UdDwEB/wQEAwIBhjASBgNV # HRMBAf8ECDAGAQH/AgEAMBMGA1UdJQQMMAoGCCsGAQUFBwMDMBsGA1UdIAQUMBIw # BgYEVR0gADAIBgZngQwBBAEwSwYDVR0fBEQwQjBAoD6gPIY6aHR0cDovL2NybC5z # ZWN0aWdvLmNvbS9TZWN0aWdvUHVibGljQ29kZVNpZ25pbmdSb290UjQ2LmNybDB7 # BggrBgEFBQcBAQRvMG0wRgYIKwYBBQUHMAKGOmh0dHA6Ly9jcnQuc2VjdGlnby5j # b20vU2VjdGlnb1B1YmxpY0NvZGVTaWduaW5nUm9vdFI0Ni5wN2MwIwYIKwYBBQUH # MAGGF2h0dHA6Ly9vY3NwLnNlY3RpZ28uY29tMA0GCSqGSIb3DQEBDAUAA4ICAQAG # /4Lhd2M2bnuhFSCbE/8E/ph1RGHDVpVx0ZE/haHrQECxyNbgcv2FymQ5PPmNS6Da # h66dtgCjBsULYAor5wxxcgEPRl05pZOzI3IEGwwsepp+8iGsLKaVpL3z5CmgELIq # mk/Q5zFgR1TSGmxqoEEhk60FqONzDn7D8p4W89h8sX+V1imaUb693TGqWp3T32IK # GfIgy9jkd7GM7YCa2xulWfQ6E1xZtYNEX/ewGnp9ZeHPsNwwviJMBZL4xVd40uPW # UnOJUoSiugaz0yWLODRtQxs5qU6E58KKmfHwJotl5WZ7nIQuDT0mWjwEx7zSM7fs # 9Tx6N+Q/3+49qTtUvAQsrEAxwmzOTJ6Jp6uWmHCgrHW4dHM3ITpvG5Ipy62KyqYo # vk5O6cC+040Si15KJpuQ9VJnbPvqYqfMB9nEKX/d2rd1Q3DiuDexMKCCQdJGpOqU # sxLuCOuFOoGbO7Uv3RjUpY39jkkp0a+yls6tN85fJe+Y8voTnbPU1knpy24wUFBk # fenBa+pRFHwCBB1QtS+vGNRhsceP3kSPNrrfN2sRzFYsNfrFaWz8YOdU254qNZQf # d9O/VjxZ2Gjr3xgANHtM3HxfzPYF6/pKK8EE4dj66qKKtm2DTL1KFCg/OYJyfrdL # Jq1q2/HXntgr2GVw+ZWhrWgMTn8v1SjZsLlrgIfZHDGCG3QwghtwAgEBMGgwVDEL # MAkGA1UEBhMCR0IxGDAWBgNVBAoTD1NlY3RpZ28gTGltaXRlZDErMCkGA1UEAxMi # U2VjdGlnbyBQdWJsaWMgQ29kZSBTaWduaW5nIENBIFIzNgIQXtDXpjvAxSyj1I/p # fca97TANBglghkgBZQMEAgEFAKB8MBAGCisGAQQBgjcCAQwxAjAAMBkGCSqGSIb3 # DQEJAzEMBgorBgEEAYI3AgEEMBwGCisGAQQBgjcCAQsxDjAMBgorBgEEAYI3AgEV # MC8GCSqGSIb3DQEJBDEiBCAZRtoSN6B5mLfRQT89reJGC0z1NhJjcl8WQPZ1FsnA # sTANBgkqhkiG9w0BAQEFAASCAYCXI+pEVle+JgCEqs98IASdtf1gOYTfVMM5zeFP # SmfwXISFWlrXMMeOeGQGX6d9dcvsanFCq6Nbh4GcAscrv3h9yvHV6E+r7hksfrwb # kuNgY8RhsBkv+uogZwoj29KmDTIQkZ3zViGo+8Q7PoxxTQm3K6Y9iqVB2vk39Qh8 # fiugEwAfVfwiKmYYB28BIKyKy7vIqY8nAyEQWC/PZpAGbafLMqTr2OZ4Dk1CMpRu # f7svIkyTwk2l1a9SZftuPFCWtbcxMj6blQWwfsYO2OzrW+RjHjLrZ3+/+39gwga4 # GMIc5lzT82td3452oFUKmns3h9nUbbZu6vHU5l5iSSuJ+JubLvIrKc5HPNJwU1FP # lpkCgaVzvB3oU11yawYxjHxRIovegieu0fbkjpmubXCElpEQgAxhoj7Xlg3XHwoE # rsBLsTHmFg7GizeoUkSldmgJnLZr+NnU3hz7+oqqeqVBB6Gj7cWSfHzZUQCA9TXP # 8nGFGKdMzLiPJEkZ2U0rFGIoPsWhghjfMIIY2wYKKwYBBAGCNwMDATGCGMswghjH # BgkqhkiG9w0BBwKgghi4MIIYtAIBAzEPMA0GCWCGSAFlAwQCAgUAMIIBBAYLKoZI # hvcNAQkQAQSggfQEgfEwge4CAQEGCisGAQQBsjECAQEwQTANBglghkgBZQMEAgIF # AAQwVXaZ7crxv2kS+aSxOsY1WlTyVReit1kV58ODJJee7UjwnyJMzU1cojCBwU6r # Kb2MAhUArdorHomUjy04bcAMEerFxE9LjIcYDzIwMjUwMjI1MDIwNjU2WqBypHAw # bjELMAkGA1UEBhMCR0IxEzARBgNVBAgTCk1hbmNoZXN0ZXIxGDAWBgNVBAoTD1Nl # Y3RpZ28gTGltaXRlZDEwMC4GA1UEAxMnU2VjdGlnbyBQdWJsaWMgVGltZSBTdGFt # cGluZyBTaWduZXIgUjM1oIIS/zCCBl0wggTFoAMCAQICEDpSaiyEzlXmHWX8zBLY # 6YkwDQYJKoZIhvcNAQEMBQAwVTELMAkGA1UEBhMCR0IxGDAWBgNVBAoTD1NlY3Rp # Z28gTGltaXRlZDEsMCoGA1UEAxMjU2VjdGlnbyBQdWJsaWMgVGltZSBTdGFtcGlu # ZyBDQSBSMzYwHhcNMjQwMTE1MDAwMDAwWhcNMzUwNDE0MjM1OTU5WjBuMQswCQYD # VQQGEwJHQjETMBEGA1UECBMKTWFuY2hlc3RlcjEYMBYGA1UEChMPU2VjdGlnbyBM # aW1pdGVkMTAwLgYDVQQDEydTZWN0aWdvIFB1YmxpYyBUaW1lIFN0YW1waW5nIFNp # Z25lciBSMzUwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCN0Wf0wUib # vf04STpNYYGbw9jcRaVhBDaNBp7jmJaA9dQZW5ighrXGNMYjK7Dey5RIHMqLIbT9 # z9if753mYbojJrKWO4ZP0N5dBT2TwZZaPb8E+hqaDZ8Vy2c+x1NiEwbEzTrPX4W3 # QFq/zJvDDbWKL99qLL42GJQzX3n5wWo60KklfFn+Wb22mOZWYSqkCVGl8aYuE12S # qIS4MVO4PUaxXeO+4+48YpQlNqbc/ndTgszRQLF4MjxDPjRDD1M9qvpLTZcTGVzx # fViyIToRNxPP6DUiZDU6oXARrGwyP9aglPXwYbkqI2dLuf9fiIzBugCDciOly8TP # DgBkJmjAfILNiGcVEzg+40xUdhxNcaC+6r0juPiR7bzXHh7v/3RnlZuT3ZGstxLf # mE7fRMAFwbHdDz5gtHLqjSTXDiNF58IxPtvmZPG2rlc+Yq+2B8+5pY+QZn+1vEif # I0MDtiA6BxxQuOnj4PnqDaK7NEKwtD1pzoA3jJFuoJiwbatwhDkg1PIjYnMDbDW+ # wAc9FtRN6pUsO405jaBgigoFZCw9hWjLNqgFVTo7lMb5rVjJ9aSBVVL2dcqzyFW2 # LdWk5Xdp65oeeOALod7YIIMv1pbqC15R7QCYLxcK1bCl4/HpBbdE5mjy9JR70BHu # Yx27n4XNOZbwrXcG3wZf9gEUk7stbPAoBQIDAQABo4IBjjCCAYowHwYDVR0jBBgw # FoAUX1jtTDF6omFCjVKAurNhlxmiMpswHQYDVR0OBBYEFGjvpDJJabZSOB3qQzks # 9BRqngyFMA4GA1UdDwEB/wQEAwIGwDAMBgNVHRMBAf8EAjAAMBYGA1UdJQEB/wQM # MAoGCCsGAQUFBwMIMEoGA1UdIARDMEEwNQYMKwYBBAGyMQECAQMIMCUwIwYIKwYB # BQUHAgEWF2h0dHBzOi8vc2VjdGlnby5jb20vQ1BTMAgGBmeBDAEEAjBKBgNVHR8E # QzBBMD+gPaA7hjlodHRwOi8vY3JsLnNlY3RpZ28uY29tL1NlY3RpZ29QdWJsaWNU # aW1lU3RhbXBpbmdDQVIzNi5jcmwwegYIKwYBBQUHAQEEbjBsMEUGCCsGAQUFBzAC # hjlodHRwOi8vY3J0LnNlY3RpZ28uY29tL1NlY3RpZ29QdWJsaWNUaW1lU3RhbXBp # bmdDQVIzNi5jcnQwIwYIKwYBBQUHMAGGF2h0dHA6Ly9vY3NwLnNlY3RpZ28uY29t # MA0GCSqGSIb3DQEBDAUAA4IBgQCw3C7J+k82TIov9slP1e8YTx+fDsa//hJ62Y6S # Mr2E89rv82y/n8we5W6z5pfBEWozlW7nWp+sdPCdUTFw/YQcqvshH6b9Rvs9qZp5 # Z+V7nHwPTH8yzKwgKzTTG1I1XEXLAK9fHnmXpaDeVeI8K6Lw3iznWZdLQe3zl+Re # jdq5l2jU7iUfMkthfhFmi+VVYPkR/BXpV7Ub1QyyWebqkjSHJHRmv3lBYbQyk08/ # S7TlIeOr9iQ+UN57fJg4QI0yqdn6PyiehS1nSgLwKRs46T8A6hXiSn/pCXaASnds # 0LsM5OVoKYfbgOOlWCvKfwUySWoSgrhncihSBXxH2pAuDV2vr8GOCEaePZc0Dy6O # 1rYnKjGmqm/IRNkJghSMizr1iIOPN+23futBXAhmx8Ji/4NTmyH9K0UvXHiuA2Pa # 3wZxxR9r9XeIUVb2V8glZay+2ULlc445CzCvVSZV01ZB6bgvCuUuBx079gCcepjn # ZDCcEuIC5Se4F6yFaZ8RvmiJ4hgwggYUMIID/KADAgECAhB6I67aU2mWD5HIPlz0 # x+M/MA0GCSqGSIb3DQEBDAUAMFcxCzAJBgNVBAYTAkdCMRgwFgYDVQQKEw9TZWN0 # aWdvIExpbWl0ZWQxLjAsBgNVBAMTJVNlY3RpZ28gUHVibGljIFRpbWUgU3RhbXBp # bmcgUm9vdCBSNDYwHhcNMjEwMzIyMDAwMDAwWhcNMzYwMzIxMjM1OTU5WjBVMQsw # CQYDVQQGEwJHQjEYMBYGA1UEChMPU2VjdGlnbyBMaW1pdGVkMSwwKgYDVQQDEyNT # ZWN0aWdvIFB1YmxpYyBUaW1lIFN0YW1waW5nIENBIFIzNjCCAaIwDQYJKoZIhvcN # AQEBBQADggGPADCCAYoCggGBAM2Y2ENBq26CK+z2M34mNOSJjNPvIhKAVD7vJq+M # DoGD46IiM+b83+3ecLvBhStSVjeYXIjfa3ajoW3cS3ElcJzkyZlBnwDEJuHlzpbN # 4kMH2qRBVrjrGJgSlzzUqcGQBaCxpectRGhhnOSwcjPMI3G0hedv2eNmGiUbD12O # eORN0ADzdpsQ4dDi6M4YhoGE9cbY11XxM2AVZn0GiOUC9+XE0wI7CQKfOUfigLDn # 7i/WeyxZ43XLj5GVo7LDBExSLnh+va8WxTlA+uBvq1KO8RSHUQLgzb1gbL9Ihgzx # mkdp2ZWNuLc+XyEmJNbD2OIIq/fWlwBp6KNL19zpHsODLIsgZ+WZ1AzCs1HEK6VW # rxmnKyJJg2Lv23DlEdZlQSGdF+z+Gyn9/CRezKe7WNyxRf4e4bwUtrYE2F5Q+05y # DD68clwnweckKtxRaF0VzN/w76kOLIaFVhf5sMM/caEZLtOYqYadtn034ykSFaZu # IBU9uCSrKRKTPJhWvXk4CllgrwIDAQABo4IBXDCCAVgwHwYDVR0jBBgwFoAU9ndq # 3T/9ARP/FqFsggIv0Ao9FCUwHQYDVR0OBBYEFF9Y7UwxeqJhQo1SgLqzYZcZojKb # MA4GA1UdDwEB/wQEAwIBhjASBgNVHRMBAf8ECDAGAQH/AgEAMBMGA1UdJQQMMAoG # CCsGAQUFBwMIMBEGA1UdIAQKMAgwBgYEVR0gADBMBgNVHR8ERTBDMEGgP6A9hjto # dHRwOi8vY3JsLnNlY3RpZ28uY29tL1NlY3RpZ29QdWJsaWNUaW1lU3RhbXBpbmdS # b290UjQ2LmNybDB8BggrBgEFBQcBAQRwMG4wRwYIKwYBBQUHMAKGO2h0dHA6Ly9j # cnQuc2VjdGlnby5jb20vU2VjdGlnb1B1YmxpY1RpbWVTdGFtcGluZ1Jvb3RSNDYu # cDdjMCMGCCsGAQUFBzABhhdodHRwOi8vb2NzcC5zZWN0aWdvLmNvbTANBgkqhkiG # 9w0BAQwFAAOCAgEAEtd7IK0ONVgMnoEdJVj9TC1ndK/HYiYh9lVUacahRoZ2W2hf # iEOyQExnHk1jkvpIJzAMxmEc6ZvIyHI5UkPCbXKspioYMdbOnBWQUn733qMooBfI # ghpR/klUqNxx6/fDXqY0hSU1OSkkSivt51UlmJElUICZYBodzD3M/SFjeCP59anw # xs6hwj1mfvzG+b1coYGnqsSz2wSKr+nDO+Db8qNcTbJZRAiSazr7KyUJGo1c+MSc # GfG5QHV+bps8BX5Oyv9Ct36Y4Il6ajTqV2ifikkVtB3RNBUgwu/mSiSUice/Jp/q # 8BMk/gN8+0rNIE+QqU63JoVMCMPY2752LmESsRVVoypJVt8/N3qQ1c6FibbcRabo # 3azZkcIdWGVSAdoLgAIxEKBeNh9AQO1gQrnh1TA8ldXuJzPSuALOz1Ujb0PCyNVk # Wk7hkhVHfcvBfI8NtgWQupiaAeNHe0pWSGH2opXZYKYG4Lbukg7HpNi/KqJhue2K # eak6qH9A8CeEOB7Eob0Zf+fU+CCQaL0cJqlmnx9HCDxF+3BLbUufrV64EbTI40zq # egPZdA+sXCmbcZy6okx/SjwsusWRItFA3DE8MORZeFb6BmzBtqKJ7l939bbKBy2j # vxcJI98Va95Q5JnlKor3m0E7xpMeYRriWklUPsetMSf2NvUQa/E5vVyefQIwggaC # MIIEaqADAgECAhA2wrC9fBs656Oz3TbLyXVoMA0GCSqGSIb3DQEBDAUAMIGIMQsw # CQYDVQQGEwJVUzETMBEGA1UECBMKTmV3IEplcnNleTEUMBIGA1UEBxMLSmVyc2V5 # IENpdHkxHjAcBgNVBAoTFVRoZSBVU0VSVFJVU1QgTmV0d29yazEuMCwGA1UEAxMl # VVNFUlRydXN0IFJTQSBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTAeFw0yMTAzMjIw # MDAwMDBaFw0zODAxMTgyMzU5NTlaMFcxCzAJBgNVBAYTAkdCMRgwFgYDVQQKEw9T # ZWN0aWdvIExpbWl0ZWQxLjAsBgNVBAMTJVNlY3RpZ28gUHVibGljIFRpbWUgU3Rh # bXBpbmcgUm9vdCBSNDYwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCI # ndi5RWedHd3ouSaBmlRUwHxJBZvMWhUP2ZQQRLRBQIF3FJmp1OR2LMgIU14g0JIl # L6VXWKmdbmKGRDILRxEtZdQnOh2qmcxGzjqemIk8et8sE6J+N+Gl1cnZocew8eCA # awKLu4TRrCoqCAT8uRjDeypoGJrruH/drCio28aqIVEn45NZiZQI7YYBex48eL78 # lQ0BrHeSmqy1uXe9xN04aG0pKG9ki+PC6VEfzutu6Q3IcZZfm00r9YAEp/4aeiLh # yaKxLuhKKaAdQjRaf/h6U13jQEV1JnUTCm511n5avv4N+jSVwd+Wb8UMOs4netap # q5Q/yGyiQOgjsP/JRUj0MAT9YrcmXcLgsrAimfWY3MzKm1HCxcquinTqbs1Q0d2V # MMQyi9cAgMYC9jKc+3mW62/yVl4jnDcw6ULJsBkOkrcPLUwqj7poS0T2+2JMzPP+ # jZ1h90/QpZnBkhdtixMiWDVgh60KmLmzXiqJc6lGwqoUqpq/1HVHm+Pc2B6+wCy/ # GwCcjw5rmzajLbmqGygEgaj/OLoanEWP6Y52Hflef3XLvYnhEY4kSirMQhtberRv # aI+5YsD3XVxHGBjlIli5u+NrLedIxsE88WzKXqZjj9Zi5ybJL2WjeXuOTbswB7Xj # kZbErg7ebeAQUQiS/uRGZ58NHs57ZPUfECcgJC+v2wIDAQABo4IBFjCCARIwHwYD # VR0jBBgwFoAUU3m/WqorSs9UgOHYm8Cd8rIDZsswHQYDVR0OBBYEFPZ3at0//QET # /xahbIICL9AKPRQlMA4GA1UdDwEB/wQEAwIBhjAPBgNVHRMBAf8EBTADAQH/MBMG # A1UdJQQMMAoGCCsGAQUFBwMIMBEGA1UdIAQKMAgwBgYEVR0gADBQBgNVHR8ESTBH # MEWgQ6BBhj9odHRwOi8vY3JsLnVzZXJ0cnVzdC5jb20vVVNFUlRydXN0UlNBQ2Vy # dGlmaWNhdGlvbkF1dGhvcml0eS5jcmwwNQYIKwYBBQUHAQEEKTAnMCUGCCsGAQUF # BzABhhlodHRwOi8vb2NzcC51c2VydHJ1c3QuY29tMA0GCSqGSIb3DQEBDAUAA4IC # AQAOvmVB7WhEuOWhxdQRh+S3OyWM637ayBeR7djxQ8SihTnLf2sABFoB0DFR6JfW # S0snf6WDG2gtCGflwVvcYXZJJlFfym1Doi+4PfDP8s0cqlDmdfyGOwMtGGzJ4iIm # yaz3IBae91g50QyrVbrUoT0mUGQHbRcF57olpfHhQEStz5i6hJvVLFV/ueQ21SM9 # 9zG4W2tB1ExGL98idX8ChsTwbD/zIExAopoe3l6JrzJtPxj8V9rocAnLP2C8Q5wX # VVZcbw4x4ztXLsGzqZIiRh5i111TW7HV1AtsQa6vXy633vCAbAOIaKcLAo/IU7sC # lyZUk62XD0VUnHD+YvVNvIGezjM6CRpcWed/ODiptK+evDKPU2K6synimYBaNH49 # v9Ih24+eYXNtI38byt5kIvh+8aW88WThRpv8lUJKaPn37+YHYafob9Rg7LyTrSYp # yZoBmwRWSE4W6iPjB7wJjJpH29308ZkpKKdpkiS9WNsf/eeUtvRrtIEiSJHN899L # 1P4l6zKVsdrUu1FX1T/ubSrsxrYJD+3f3aKg6yxdbugot06YwGXXiy5UUGZvOu3l # XlxA+fC13dQ5OlL2gIb5lmF6Ii8+CQOYDwXM+yd9dbmocQsHjcRPsccUd5E9Fisw # EqORvz8g3s+jR3SFCgXhN4wz7NgAnOgpCdUo4uDyllU9PzGCBJEwggSNAgEBMGkw # VTELMAkGA1UEBhMCR0IxGDAWBgNVBAoTD1NlY3RpZ28gTGltaXRlZDEsMCoGA1UE # AxMjU2VjdGlnbyBQdWJsaWMgVGltZSBTdGFtcGluZyBDQSBSMzYCEDpSaiyEzlXm # HWX8zBLY6YkwDQYJYIZIAWUDBAICBQCgggH5MBoGCSqGSIb3DQEJAzENBgsqhkiG # 9w0BCRABBDAcBgkqhkiG9w0BCQUxDxcNMjUwMjI1MDIwNjU2WjA/BgkqhkiG9w0B # CQQxMgQwOSsOWBw92WTeaip53xtNvFK93j4caSm67B7L2W1yYkoxnOEZUbT1GBUm # 5LB9bHA6MIIBegYLKoZIhvcNAQkQAgwxggFpMIIBZTCCAWEwFgQU+GCYGab7iCz3 # 6FKX8qEZUhoWd18wgYcEFMauVOR4hvF8PVUSSIxpw0p6+cLdMG8wW6RZMFcxCzAJ # BgNVBAYTAkdCMRgwFgYDVQQKEw9TZWN0aWdvIExpbWl0ZWQxLjAsBgNVBAMTJVNl # Y3RpZ28gUHVibGljIFRpbWUgU3RhbXBpbmcgUm9vdCBSNDYCEHojrtpTaZYPkcg+ # XPTH4z8wgbwEFIU9Yy2TgoJhfNCQNcSR3pLBQtrHMIGjMIGOpIGLMIGIMQswCQYD # VQQGEwJVUzETMBEGA1UECBMKTmV3IEplcnNleTEUMBIGA1UEBxMLSmVyc2V5IENp # dHkxHjAcBgNVBAoTFVRoZSBVU0VSVFJVU1QgTmV0d29yazEuMCwGA1UEAxMlVVNF # UlRydXN0IFJTQSBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eQIQNsKwvXwbOuejs902 # y8l1aDANBgkqhkiG9w0BAQEFAASCAgA56lxPJXc4yicbH//CMAJcrNpdNYQok2oc # w6YdAFiZjFa8ECkHq9+cprS2wY5LkZr7+JATMGmAIdxcqu/1iHqByceodVhPnCBt # q6E3wUDz3pk3ZwNWekc8L911CheTHQm83dwAkvwDM1O19jZawxsO2bgL5ZzLBUKl # Hoa7iiYx8rW6+dS4+nJW0jHk+7/XZ+auJFquzdZuhZQGZy1gM7Hj5No/f4Ziw17r # camKnXhDS1A91+oXURSf2YLrg7zQ/bJ885lB6PfmYoNOFlaPQb45mO2sRXXwHh9c # uLHsRhM896XZdlsbvbGKQt+TRI1w2U1ECVF8YnSqZKUKZ41q1+hsSL8vWfN5KRjF # qZfIk/6v6U885n5hIfoAQKlB9ztAvzzqNEurArkn9FoIc/kDMZyovn/EebMWbtmR # hPB44J5It7B+N2pmvnC4k4auTITaEO2AJJ5YTBDsQCQbscmYLa/3s5m5etBRQjbD # CHJN/0Ipa+XdCW4FPuk1fKvyVDS1UxsdMlP9nJCMPlrw2TkwKGWqwYtcg696PBBd # eWUKAb8L38viWLK/bLtuNiCIjMWXAfv217Pg4FiPQoW+PR+YzCkrcT1Z84bxqyiY # U5FFviAanfuorqWrjTuL8z+yE/QZYAqXfn/TCrI9E5su0ayS0vjnSCicS3Ra8UuL # p6qvHe8eVg== # SIG # End signature block |