RussianCalendar.psm1
#Const $datasetUrl = 'https://data.gov.ru/api/json/dataset/7708660670-proizvcalendar' $ApiKeyPath = "$Env:APPDATA\PowerShell\RussianCalendar\ApiKey" $YearFirstWorkDays = &{ $Path = "$Env:APPDATA\PowerShell\RussianCalendar\YearFirstWorkDays" if (!(Test-Path -Path $Path)) {New-Item -Path $Path -ItemType File -Force} $Content = Get-Content -Raw -Path $Path $NewHashTable = @{} if ($Content) { $HashTable = ConvertFrom-StringData -StringData $Content $HashTable.Keys | % { $NewHashTable[$_] = [DateTime]::FromFileTime($HashTable[$_]) } } $NewHashTable } #Var $Param = @{ WorkSchedule = @(5,2) FirstWorkDay = $null YearFirstWorkDays = $YearFirstWorkDays CultureInfo = $(Get-Culture) FirstDayOfWeek = $((Get-Culture).DateTimeFormat.FirstDayOfWeek) ColorScheme = $(Invoke-Expression -Command (Get-Content -Raw -Path "$PSScriptRoot\ColorScheme")) Padding = @{ Char = $([char]32) Length = 5 Padding = 4 Align = 'Right' } } $HoliDays = @{ Array = @() Cache = @{} Hash = @{} } $LocalStorage = @{ MeasureCalendarYearCounter = @{ Weekday = 1 Weekend = 1 Switch = 0 } } #------------------------------------------------------------------------------------------------------- Function Set-CalendarApiKey { [CmdletBinding()] param( [Parameter(Mandatory=$true)][string]$ApiKey ) New-Item -Path $ApiKeyPath -ItemType File -Force | Out-Null Set-Content -Path $ApiKeyPath -Value $ApiKey -Force } Function Set-CalendarFirstWorkDays { [CmdletBinding()] param( [Parameter(Mandatory=$true)][int]$Year = (Get-Date).Year, [Parameter(Mandatory=$true)][DateTime]$Date ) $YearFirstWorkDays = $Param['YearFirstWorkDays'] } Function Get-CalendarApiKey { [CmdletBinding()] $ApiKey = Get-Content -Path $ApiKeyPath -ErrorAction SilentlyContinue if (!$ApiKey) { Write-Verbose -Message "visit https://data.gov.ru/get-api-key and get the key for the api and run Set-CalendarApiKey" -Verbose Write-Verbose -Message "key not found in $ApiKeyPath" Set-CalendarApiKey $ApiKey = Get-Content -Path $ApiKeyPath -ErrorAction SilentlyContinue } if ($ApiKey) { Write-Verbose -Message "key was found in $ApiKeyPath" $ApiKey } else { Write-Verbose -Message "key not found in $ApiKeyPath" } } Function Find-CalendarApiVersion { [CmdletBinding()] param( [switch]$All ) $ApiKey = Get-CalendarApiKey $Responce = Invoke-RestMethod -Uri "$datasetUrl/version/" -Body @{access_token = $ApiKey} $Versions = $Responce | Sort-Object -Property @{Expression = {[DateTime]::ParseExact($_.created,'yyyyMMddTHHmmss',$null)}} -Descending if ($Versions) { if ($All) {$Versions.created} else {$Versions[0].created} } } Function Update-CalendarFile { [CmdletBinding()] param( [string]$Version = $(Find-CalendarApiVersion) ) $ScopeIsAllUsers = $Env:PSModulePath.Split(';') -match 'Program.Files|Windows.System32' | ? {$PSScriptRoot -like "$_*"} if ($ScopeIsAllUsers) { Write-Verbose -Message "Scope is AllUsers" $WindowsPrincipal = New-Object -TypeName System.Security.Principal.WindowsPrincipal -ArgumentList @([System.Security.Principal.WindowsIdentity]::GetCurrent()) if (!($WindowsPrincipal.IsInRole([System.Security.Principal.WindowsBuiltInRole]::Administrator))) { throw {'Requires: RunAsAdministrator'} } } $ApiKey = Get-CalendarApiKey $FilePath = "$PSScriptRoot\CalendarFiles\$Version.json" try { $Responce = Invoke-RestMethod -Uri "$datasetUrl/version/$Version/content/" -Body @{access_token = $ApiKey} if ($?) { Write-Verbose -Message "Responced: $([bool]$Responce)" Write-Verbose -Message "Saving path: $FilePath" $Responce | ConvertTo-Json | Out-File -FilePath $FilePath -Force Import-CalendarFile | Out-Null } } catch { Write-Verbose -Message "Responce error" } } Function Import-CalendarFile { [CmdletBinding()] param() if ($HoliDays['Hash'].Count -and $HoliDays['Array'].Count) { Write-Verbose -Message "Using: Saved HoliDays Variable" $HoliDays } else { $CalendarFile = Get-ChildItem -Path "$PSScriptRoot\CalendarFiles\*.json" | ? {$_.BaseName -match '^\d{8}T\d{6}$'} | Sort-Object -Property @{Expression = {[DateTime]::ParseExact($_.BaseName,'yyyyMMddTHHmmss',$null)}} -Descending | Select-Object -First 1 Write-Verbose -Message "Using: $CalendarFile" (ConvertFrom-Json -InputObject (Get-Content -Path $CalendarFile -Raw)) | ConvertTo-Csv | ConvertFrom-Csv -Header Year,1,2,3,4,5,6,7,8,9,10,11,12 | Select-Object -Skip 1 -PipelineVariable Object | % { Write-Debug -Message $(('=')*80) [int]$Year = $Object.Year $HoliDays['Hash'][$Year] = @{} 1..12 | Select-Object -PipelineVariable Month | % { $HoliDays['Hash'][$Year][$Month] = @{} $Days = $Object.psobject.Properties[$Month].Value -split ',' Write-Debug -Message "Year: $Year, Month: $Month, $('Days: ' + ($Days -join ','))" $Days | Select-Object -PipelineVariable Day | % { [int]$DayInt = $Day -replace '\D' [string]$DayView = $Day $HoliDays['Hash'][$Year][$Month][$DayInt] = $DayView $HoliDays['Array'] += [DateTime]::New($Year,$Month,$DayInt) } } } $HoliDays } } #------------------------------------------------------------------------------------------------------- Import-CalendarFile #------------------------------------------------------------------------------------------------------- Function Get-DateList { param( [Parameter(Mandatory=$true)][DateTime]$Start, [Parameter(Mandatory=$true)][DateTime]$End, [TimeSpan]$Step = [TimeSpan]::FromDays(1) ) while ($Start -le $End) {$Start; $Start = $Start + $Step} } Function Get-WeekOfYear { param( [Parameter(ValueFromPipeline=$true)][System.DateTime]$DateTime = (Get-Date), [System.Globalization.CultureInfo]$CultureInfo = $Param['CultureInfo'], [System.DayOfWeek]$FirstDayOfWeek = $Param['FirstDayOfWeek'] ) $PSBoundParameters.Keys.Where({$Param.ContainsKey($_)}).ForEach({$Param[$_] = $PSBoundParameters[$_]}) $CalendarWeekRule = $CultureInfo.DateTimeFormat.CalendarWeekRule $CultureInfo.Calendar.GetWeekOfYear($DateTime, $CalendarWeekRule, $FirstDayOfWeek) } Function Get-DaysOfWeek { param( [System.DayOfWeek]$FirstDayOfWeek = $Param['FirstDayOfWeek'] ) $PSBoundParameters.Keys.Where({$Param.ContainsKey($_)}).ForEach({$Param[$_] = $PSBoundParameters[$_]}) [System.Collections.ArrayList]$DaysOfWeekDefault = [System.DayOfWeek[]](0..6) [System.Collections.ArrayList]$DaysOfWeek = @() $DaysOfWeek.AddRange($DaysOfWeekDefault.GetRange($DaysOfWeekDefault.IndexOf($FirstDayOfWeek),(7-$DaysOfWeekDefault.IndexOf($FirstDayOfWeek)))) $DaysOfWeek.AddRange($DaysOfWeekDefault.GetRange(0,$DaysOfWeekDefault.IndexOf($FirstDayOfWeek))) $LocalStorage['DaysOfWeek'] = $DaysOfWeek $DaysOfWeek } Function Format-StringPadding { param( [Parameter(Mandatory=$true,ValueFromPipeline=$true)][String[]]$String, [char]$Char = $([char]32), [int]$Length, [int]$Padding, [ValidateSet('Left','Right','Center')][String]$Align = 'Right' ) Begin {} Process { [int]$HalfStringLength = [System.Math]::Floor($String.Length / 2) [int]$HalfLength = [System.Math]::Floor($Length / 2) if (!$Padding) {$Padding = [int]$HalfLength - $HalfStringLength} if ($Align -eq 'Right') {$String.PadLeft($Padding,$Char).PadRight($Length,$Char)} if ($Align -eq 'Left') {$String.PadRight($Padding,$Char).PadLeft($Length,$Char)} if ($Align -eq 'Center') { $Padding = [System.Math]::Floor(($Length - $String.Length) / 2) + ($String.Length * 2) $String.PadRight($Padding,$Char).PadLeft($Length,$Char) } } End {} } Function ConvertTo-StringData { param([HashTable]$HashTable) $HashTable.Keys.ForEach({"$_ = $($HashTable[$_])"}) } #------------------------------------------------------------------------------------------------------- Function Measure-CalendarYear { [CmdLetBinding()] param( [int]$Year = (Get-Date).Year, [System.DayOfWeek]$FirstDayOfWeek = $Param['FirstDayOfWeek'], [ValidateCount(2,2)][int[]]$WorkSchedule = $Param['WorkSchedule'], [DateTime]$FirstWorkDay ) $PSBoundParameters.Keys.Where({$Param.ContainsKey($_)}).ForEach({$Param[$_] = $PSBoundParameters[$_]}) if ($WorkSchedule -and !$FirstWorkDay) { if ($WorkSchedule[0] -eq 5 -and $WorkSchedule[1] -eq 2) { $FirstWorkDay = Get-DateList -Start ([DateTime]::new($Year,1,1)) -End ([DateTime]::new($Year,1,7)) | ? {$_.DayOfWeek -eq $FirstDayOfWeek} } else { Write-Verbose -Verbose -Message "Parameter FirstWorkDay is not set and WorkSchedule is not 5/2. Specify FirstWorkDay or read more about `$PSDefaultParameterValues" if (!$FirstWorkDay) { $FirstWorkDay = [DateTime]::new($Year,1,1) } } } $DateNow = Get-Date $Counter = $LocalStorage['MeasureCalendarYearCounter'].Clone() Get-DateList -Start $([DateTime]::new($Year,1,1)) -End $([DateTime]::new($Year,12,31)) | Select-Object -Property @( ,@{Name = 'DateTime'; Expression = {$_}} ,'Year' ,'Month' ,'Day' ,@{Name = 'HoliDay'; Expression = {$HoliDays['Array'].Contains($_)}} ,@{Name = 'ToDay'; Expression = {$_.Date -eq $DateNow.Date}} ,'DayOfWeek' ,'DayOfYear' ,@{Name = 'WeekOfYear'; Expression = {Get-WeekOfYear -DateTime $_.Date -FirstDayOfWeek $FirstDayOfWeek}} ,@{Name = 'ScheduledWeekend'; Expression = { if ($_.Date -ge $FirstWorkDay.Date) { if ($Counter['Switch'] -eq 0) { if ($Counter['Weekday'] -le $WorkSchedule[0]) {$false; $Counter['Weekday'] += 1} if ($Counter['Weekday'] -gt $WorkSchedule[0]) {$Counter['Weekday'] = 1; $Counter['Switch'] = 1} } elseif ($Counter['Switch'] -eq 1) { if ($Counter['Weekend'] -le $WorkSchedule[1]) {$true; $Counter['Weekend'] += 1} if ($Counter['Weekend'] -gt $WorkSchedule[1]) {$Counter['Weekend'] = 1; $Counter['Switch'] = 0} } else {$null} } }} ) | Select-Object -Property @( ,'*' ,@{Name = 'DayView'; Expression = { if ($_.HoliDay) { $HoliDays['Hash'][$_.Year][$_.Month][$_.Day].ToString() } else { $_.Day.ToString() } }} ) | Select-Object -Property @( ,'*' ,@{Name = 'Color'; Expression = { $ColorParam = $Param['ColorScheme']['Default'].Clone() if ($_.ScheduledWeekend) { if ($Param['ColorScheme']['ScheduledWeekend'].ContainsKey('Foreground')) {$ColorParam['Foreground'] = $Param['ColorScheme']['ScheduledWeekend']['Foreground']} if ($Param['ColorScheme']['ScheduledWeekend'].ContainsKey('Background')) {$ColorParam['Background'] = $Param['ColorScheme']['ScheduledWeekend']['Background']} if (!$_.HoliDay) { if ($Param['ColorScheme']['ScheduledWeekendIsNotHoliDay'].ContainsKey('Foreground')) {$ColorParam['Foreground'] = $Param['ColorScheme']['ScheduledWeekendIsNotHoliDay']['Foreground']} if ($Param['ColorScheme']['ScheduledWeekendIsNotHoliDay'].ContainsKey('Background')) {$ColorParam['Background'] = $Param['ColorScheme']['ScheduledWeekendIsNotHoliDay']['Background']} } } if ($_.HoliDay) { if ($Param['ColorScheme']['HoliDay'].ContainsKey('Foreground')) {$ColorParam['Foreground'] = $Param['ColorScheme']['HoliDay']['Foreground']} if ($Param['ColorScheme']['HoliDay'].ContainsKey('Background')) {$ColorParam['Background'] = $Param['ColorScheme']['HoliDay']['Background']} } if ($_.ToDay) { if ($Param['ColorScheme']['ToDay'].ContainsKey('Foreground')) {$ColorParam['Foreground'] = $Param['ColorScheme']['ToDay']['Foreground']} if ($Param['ColorScheme']['ToDay'].ContainsKey('Background')) {$ColorParam['Background'] = $Param['ColorScheme']['ToDay']['Background']} } $ColorParam }} ) | Tee-Object -Variable Cache $HoliDays['Cache'][$Year] = $Cache } Function Show-Calendar { [CmdLetBinding()] param( [int]$Year = (Get-Date).Year, [int]$Month, [System.DayOfWeek]$FirstDayOfWeek = $Param['FirstDayOfWeek'], [ValidateCount(2,2)][int[]]$WorkSchedule = $Param['WorkSchedule'], [DateTime]$FirstWorkDay, [Switch]$ShowWeeks ) $PSBoundParameters.Keys.Where({$Param.ContainsKey($_)}).ForEach({$Param[$_] = $PSBoundParameters[$_]}) $CalendarYearParam = @{Year = $Year} $PSBoundParameters.Keys.Where({@('FirstDayOfWeek','WorkSchedule','FirstWorkDay').Contains($_)}).ForEach({$CalendarYearParam[$_] = $PSBoundParameters[$_]}) if ($CalendarYearParam.Count -eq 1) { if ($HoliDays['Cache'].ContainsKey($Year)) { Write-Verbose -Message "Using Cache" $CalendarYear = $HoliDays['Cache'][$Year] } else { Write-Verbose -Message "Cache is empty. Need measuring" $CalendarYear = Measure-CalendarYear @CalendarYearParam } } else { Write-Verbose -Message "PSBoundParameters is declared. Need measuring" $CalendarYear = Measure-CalendarYear @CalendarYearParam } Write-Verbose -Message "PSBoundParameters: $($PSBoundParameters | ConvertTo-Json)" Write-Verbose -Message "CalendarYearParam: $($CalendarYearParam | ConvertTo-Json)" $TitleColor = $Param['ColorScheme']['Title'] $HeaderColor = $Param['ColorScheme']['Header'] $SpecialCharsColor = $Param['ColorScheme']['SpecialChars'] Function Optimize-Month ($CalendarYear,$Month) { $MonthGroups = $CalendarYear | Group-Object -Property Month -AsHashTable $MonthGroup = $MonthGroups[$Month] $WeekGroups = $MonthGroup | Group-Object -Property WeekOfYear -AsHashTable $WeekGroupKeys = $WeekGroups.Keys | Sort-Object Write-Verbose -Message "Weeks: $($WeekGroupKeys -join ',')" $FirstWeekNumber = $WeekGroupKeys[0] $LastWeekNumber = $WeekGroupKeys[-1] if (($WeekGroups[$FirstWeekNumber]).Count -ne 7) { $WeekGroup = @([pscustomobject][ordered]@{DayView = [string][char]183*2; Color = $SpecialCharsColor}) * (7 - $WeekGroups[$FirstWeekNumber].Count) $WeekGroups[$FirstWeekNumber] | % {$WeekGroup += $_} $WeekGroups[$FirstWeekNumber] = $WeekGroup } if (($WeekGroups[$LastWeekNumber]).Count -ne 7) { $WeekGroup = $WeekGroups[$LastWeekNumber] @([pscustomobject][ordered]@{DayView = [string][char]183*2; Color = $SpecialCharsColor}) * (7 - $WeekGroups[$LastWeekNumber].Count) | % {$WeekGroup += $_} $WeekGroups[$LastWeekNumber] = $WeekGroup } $WeekGroups } Write-Host ' ' -BackgroundColor Black -ForegroundColor Black $Padding = $Param['Padding'] $TitlePadding = $Padding.Clone() if (!$ShowWeeks) {$ColNum = 7} else {$ColNum = 8} if ($Month) { $TitlePadding['Length'] *= $ColNum $OptimizedMonth = Optimize-Month -CalendarYear $CalendarYear -Month $Month Write-Host @TitleColor (Format-StringPadding @TitlePadding -String ([DateTime]::New($Year,$Month,1)).ToString('MMMM yyyy')) Write-Host @HeaderColor (-join ((Get-DaysOfWeek) -replace @('(.{3}).*','$1') | Format-StringPadding -Length 5 -Padding 4)) Write-Host @SpecialCharsColor ((' --- ') * $ColNum) $OptimizedMonth.Keys | Sort-Object | % { $OptimizedMonth[$_] | % { $Color = $_.Color Write-Host @Color (Format-StringPadding @Padding -String $_.DayView) -NoNewline } if ($ShowWeeks) {Write-Host (Format-StringPadding @Padding -String $_) -NoNewline -ForegroundColor DarkGray} Write-Host '' -BackgroundColor Black -ForegroundColor Black } } else { $TitlePadding['Length'] *= 7 $EmptyObject = @([pscustomobject][ordered]@{DayView = [string][char]32; Color = $SpecialCharsColor}) @(@(1..3),@(4..6),@(7..9),@(10..12)) | % { $Quarter = $_ | % {Optimize-Month -CalendarYear $CalendarYear -Month $_} $MaxWeekCount = ($Quarter | Measure-Object -Property Count -Maximum).Maximum $QuarterWeeks = @{ 0 = $Quarter[0].Keys | Sort-Object 1 = $Quarter[1].Keys | Sort-Object 2 = $Quarter[2].Keys | Sort-Object } Write-Host @TitleColor (-join (0..2 | % {$Quarter[$_][$QuarterWeeks[$_]][0][6].DateTime.ToString('MMMM')} | Format-StringPadding @TitlePadding)) Write-Host @HeaderColor (-join (((Get-DaysOfWeek) -replace @('(.{3}).*','$1') | Format-StringPadding -Length 5 -Padding 4))*3) Write-Host @SpecialCharsColor ((' --- ') * 21) 0..($MaxWeekCount) | % { $Row = @() $Row += try {$Quarter[0][$QuarterWeeks[0][$_]]} catch {$EmptyObject * 7} $Row += try {$Quarter[1][$QuarterWeeks[1][$_]]} catch {$EmptyObject * 7} $Row += try {$Quarter[2][$QuarterWeeks[2][$_]]} catch {$EmptyObject * 7} $Row | % { $_ | % { $Color = $_.Color Write-Host @Color (Format-StringPadding @Padding -String $_.DayView) -NoNewline } Write-Host @SpecialCharsColor '' -NoNewline } Write-Host '' } } } Write-Host ' ' -BackgroundColor Black -ForegroundColor Black } Set-Alias -Name cal -Value Show-Calendar -Option ReadOnly -Scope Global |