Get the ISO 8601 Week of Year of a given date in Powershell
As mentioned by user6887101 and explained in detail here, the pseudo-algorithm is:
An ISO 8601 Week starts with Monday and ends with Sunday.
- For any given date, find the Thursday from the same week as the givendate. E.g.:
- If original date is
Sunday, January 1st XXXX
find theThursday, December 29th XXXX-1
- If original date is
Monday, December 31st XXXX
find theThursday, January 3rd XXXX+1
- If original date is
- The
Year of the ISO 8601 Week
is the one containing theThursday found in step 1
(e.g.:XXXX-1
orXXXX+1
) - The
ISO 8601 Week number
is thenumber of Thursdays
in theyear from step 2
(up to and including the found Thursday itself)
function Get-ISO8601Week (){# Adapted from https://stackoverflow.com/a/43736741/444172 [CmdletBinding()] param( [Parameter( ValueFromPipeline = $true, ValueFromPipelinebyPropertyName = $true )] [datetime] $DateTime ) process { foreach ($_DateTime in $DateTime) { $_ResultObject = [pscustomobject] @{ Year = $null WeekNumber = $null WeekString = $null DateString = $_DateTime.ToString('yyyy-MM-dd dddd') } $_DayOfWeek = $_DateTime.DayOfWeek.value__ # In the underlying object, Sunday is always 0 (Monday = 1, ..., Saturday = 6) irrespective of the FirstDayOfWeek settings (Sunday/Monday) # Since ISO 8601 week date (https://en.wikipedia.org/wiki/ISO_week_date) is Monday-based, flipping Sunday to 7 and switching to one-based numbering. if ($_DayOfWeek -eq 0) { $_DayOfWeek = 7 } # Find the Thursday from this week: # E.g.: If original date is a Sunday, January 1st , will find Thursday, December 29th from the previous year. # E.g.: If original date is a Monday, December 31st , will find Thursday, January 3rd from the next year. $_DateTime = $_DateTime.AddDays((4 - $_DayOfWeek)) # The above Thursday it's the Nth Thursday from it's own year, wich is also the ISO 8601 Week Number $_ResultObject.WeekNumber = [math]::Ceiling($_DateTime.DayOfYear / 7) $_ResultObject.Year = $_DateTime.Year # The format requires the ISO week-numbering year and numbers are zero-left-padded (https://en.wikipedia.org/wiki/ISO_8601#General_principles) # It's also easier to debug this way :) $_ResultObject.WeekString = "$($_DateTime.Year)-W$("$($_ResultObject.WeekNumber)".PadLeft(2, '0'))" Write-Output $_ResultObject } }}
Quick test:
PS C:\> Get-Date | Get-ISO8601WeekYear WeekNumber WeekString DateString---- ---------- ---------- ----------2017 41 2017-W41 2017-10-11 Wednesday
Test correct results accross a wide range of inputs:
#<# Test Get-ISO8601Week (You can manually check accuracy @ https://planetcalc.com/1252/)# Tested on $PSVersionTable.PSVersion :# 5.1.15063.502 "Week starts on: $([System.Globalization.DateTimeFormatInfo]::CurrentInfo.FirstDayOfWeek)"# Test dates from 2000-01-01 (730119) to 2020-12-31 (737789)# To get the 'serial day number' for a given date, use:# (Get-Date -Date '2020-12-31').Ticks / [timespan]::TicksPerDay $WeekOfYearObjectGroupList = 730119..737789 | ForEach-Object -Process {[datetime]::new(($_ * [timespan]::TicksPerDay))} | Get-ISO8601Week | Group-Object -Property 'Year' '=============================================================' foreach ($WeekOfYearObjectGroup in $WeekOfYearObjectGroupList) { $WeekOfYearObjectGroup.Group | Where-Object {$_.WeekNumber -lt 1 } | Format-Table -AutoSize $WeekOfYearObjectGroup.Group | Where-Object {$_.WeekNumber -in 1..2 } | Format-Table -AutoSize '...........' $WeekOfYearObjectGroup.Group | Where-Object {$_.WeekNumber -in 52..53 } | Format-Table -AutoSize $WeekOfYearObjectGroup.Group | Where-Object {$_.WeekNumber -gt 53 } | Format-Table -AutoSize '=============================================================' }#>
Sample of 'tricky' dates referenced @ MSDN
You can manually check accuracy @ https://planetcalc.com/1252/
<# Sample of 'tricky' dates referenced @ https://blogs.msdn.microsoft.com/shawnste/2006/01/24/iso-8601-week-of-year-format-in-microsoft-net/ ........... 2004 52 2004-W52 2004-12-26 Sunday 2004 53 2004-W53 2004-12-27 Monday 2004 53 2004-W53 2004-12-28 Tuesday 2004 53 2004-W53 2004-12-29 Wednesday 2004 53 2004-W53 2004-12-30 Thursday 2004 53 2004-W53 2004-12-31 Friday 2004 53 2004-W53 2005-01-01 Saturday 2004 53 2004-W53 2005-01-02 Sunday ============================================================= 2005 1 2005-W01 2005-01-03 Monday 2005 1 2005-W01 2005-01-04 Tuesday 2005 1 2005-W01 2005-01-05 Wednesday 2005 1 2005-W01 2005-01-06 Thursday 2005 1 2005-W01 2005-01-07 Friday 2005 1 2005-W01 2005-01-08 Saturday 2005 1 2005-W01 2005-01-09 Sunday 2005 2 2005-W02 2005-01-10 Monday ...........#>
A little late to the party, but... just going to leave this answer here since this question was the first thing i stumbled upon when looking for a solution to this. There's a much easier way:
get-date -UFormat %V
OR
"{0:d1}" -f ($(Get-Culture).Calendar.GetWeekOfYear((Get-Date),[System.Globalization.CalendarWeekRule]::FirstFourDayWeek, [DayOfWeek]::Monday))
Depending on if you want ISO8601 or not.
$checkdate = Get-Date -date "2007-12-31"$dow=[int]($checkdate).dayofweek# if the day of week is before Thurs (Mon-Wed) add 3 since Thursday is the critical# day for determining when the ISO week starts.if ($dow -match "[1-3]") {$checkdate.addDays(3)}# Return the ISO week number$(Get-Culture).Calendar.GetWeekOfYear(($checkdate),[System.Globalization.CalendarWeekRule]::FirstFourDayWeek, [DayOfWeek]::Monday)