3

While searching how to get the ISO 8601 Week of Year in PowerShell, I've stumbled upon this question for C#.

Trying not to crowd that question with PowerShell code, here's my Powershell port below. (based on the answer by user6887101)

I'll leave this 'not accepted' for a while in case anyone comes up with a better solution.

Petru Zaharia
  • 1,031
  • 13
  • 20

6 Answers6

11

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.

Use this easier way:

Get-Date -UFormat %V

Or this if you want ISO8601:

"{0:d1}" -f ($(Get-Culture).Calendar.GetWeekOfYear(
    (Get-Date),
    [System.Globalization.CalendarWeekRule]::FirstFourDayWeek,
    [DayOfWeek]::Monday))
KyleMit
  • 30,350
  • 66
  • 462
  • 664
Insomniac
  • 136
  • 1
  • 5
  • Excelent. Yes, the first one isn't ISO8601 but the second one is. `'2004-12-31', '2005-01-01' | Get-Date -UFormat '%V'` yields 53, 1. `'2004-12-31', '2005-01-01' | % {[System.Globalization.GregorianCalendar]::new().GetWeekOfYear(([datetime] $_),[System.Globalization.CalendarWeekRule]::FirstFourDayWeek, [DayOfWeek]::Monday)}` yields 53, 53. The correct answer for ISO8601 is 53, 53. – Petru Zaharia May 24 '20 at 13:45
  • The second example here fails for dates like 2007-12-31 where the last week of the year starts on a Monday and so should actually be week 1 of the next year. My answer below accounts for that. – SS64 Jul 09 '20 at 07:59
9

As mentioned by user6887101 and explained in detail here, the pseudo-algorithm is:

An ISO 8601 Week starts with Monday and ends with Sunday.

  1. For any given date, find the Thursday from the same week as the given date. E.g.:
    • If original date is Sunday, January 1st XXXX find the Thursday, December 29th XXXX-1
    • If original date is Monday, December 31st XXXX find the Thursday, January 3rd XXXX+1
  2. The Year of the ISO 8601 Week is the one containing the Thursday found in step 1 (e.g.: XXXX-1 or XXXX+1)
  3. The ISO 8601 Week number is the number of Thursdays in the year 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-ISO8601Week

Year 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
    ...........
#>
Petru Zaharia
  • 1,031
  • 13
  • 20
  • 1
    My up-vote for the nice description and workout. Considerations: Call it just `Get-ISO8601` (Or `Get-ISO8601Week`) as you actually returning an *object* where `WeekOfYear` is a property (`(Get-Date | Get-ISO8601WeekOfYear).WeekOfYear` looks redundant to me) add a `WeekNumber` property that returns an `[Int]`, this makes it easier (and faster) to compare: `Where {$_.WeekOfYear -match '^[0-9]+-W0[0-2]$'}` --> `Where {$_.WeekNumber -le 2}` and to operate. – iRon Oct 11 '17 at 17:34
  • 1
    @iRon Thanks for the suggestions. Implemented some of the changes. ISO8601 is bigger than Weeks so I kept `Get-ISO8601Week` – Petru Zaharia Oct 11 '17 at 18:28
2
$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)

From https://ss64.com/ps/get-date.html

SS64
  • 334
  • 5
  • 12
  • 26
1

For Pure Powershell: There is a problem with Culture and UICulture To print the first day of the week in the language of the Culture:

(Get-Culture).DateTimeFormat.DayNames[(Get-Culture).DateTimeFormat.FirstDayOfWeek.value__]
maandag (which is My language AND My starting weekday
(Get-uiCulture).DateTimeFormat.DayNames[(Get-Culture).DateTimeFormat.FirstDayOfWeek.value__]
Monday (which is NOT My language BUT is My starting weekday
(Get-uiCulture).DateTimeFormat.DayNames[(Get-uiCulture).DateTimeFormat.FirstDayOfWeek.value__]
Sunday (which is NOT My language AND NOT My starting weekday
(Get-Culture).DateTimeFormat.DayNames[(Get-uiCulture).DateTimeFormat.FirstDayOfWeek.value__]
zondag (which is My language AND NOT My starting weekday

Then there is the problem of weeknumbers not working to ISO8601 standard. Date 2012-12-31 should be week 1 but it gives 53. So you would have to implement some sort of solution to that.

(Get-Culture).Calendar.GetWeekOfYear((Get-Date).AddDays(3*([int](Get-Date).DayOfWeek -in (1,2,3))), ((Get-Culture).DateTimeFormat.CalendarWeekRule), ((Get-Culture).DateTimeFormat.FirstDayOfWeek))

For testing:

(Get-Culture).Calendar.GetWeekOfYear((Get-Date('2013-01-01')).AddDays(3*([int](Get-Date('2013-01-01')).DayOfWeek -in (1,2,3))), ((Get-Culture).DateTimeFormat.CalendarWeekRule), ((Get-Culture).DateTimeFormat.FirstDayOfWeek))
(Get-Culture).Calendar.GetWeekOfYear((Get-Date('2012-12-31')).AddDays(3*([int](Get-Date('2012-12-31')).DayOfWeek -in (1,2,3))), ((Get-Culture).DateTimeFormat.CalendarWeekRule), ((Get-Culture).DateTimeFormat.FirstDayOfWeek))
(Get-Culture).Calendar.GetWeekOfYear((Get-Date('2012-12-30')).AddDays(3*([int](Get-Date('2012-12-30')).DayOfWeek -in (1,2,3))), ((Get-Culture).DateTimeFormat.CalendarWeekRule), ((Get-Culture).DateTimeFormat.FirstDayOfWeek))

These should give 1,1,52(correct) instead of 1,53,52(incorrect). Or (UICulture related version)

(Get-UICulture).Calendar.GetWeekOfYear((Get-Date).AddDays(3*([int](Get-Date).DayOfWeek -in (0,1,2))), ((Get-UICulture).DateTimeFormat.CalendarWeekRule), ((Get-UICulture).DateTimeFormat.FirstDayOfWeek))

For testing:

(Get-UICulture).Calendar.GetWeekOfYear((Get-Date('2013-01-01')).AddDays(3*([int](Get-Date('2013-01-01')).DayOfWeek -in (0,1,2))), ((Get-UICulture).DateTimeFormat.CalendarWeekRule), ((Get-UICulture).DateTimeFormat.FirstDayOfWeek))
(Get-UICulture).Calendar.GetWeekOfYear((Get-Date('2012-12-31')).AddDays(3*([int](Get-Date('2012-12-31')).DayOfWeek -in (0,1,2))), ((Get-UICulture).DateTimeFormat.CalendarWeekRule), ((Get-UICulture).DateTimeFormat.FirstDayOfWeek))
(Get-UICulture).Calendar.GetWeekOfYear((Get-Date('2012-12-30')).AddDays(3*([int](Get-Date('2012-12-30')).DayOfWeek -in (0,1,2))), ((Get-UICulture).DateTimeFormat.CalendarWeekRule), ((Get-UICulture).DateTimeFormat.FirstDayOfWeek))
(Get-UICulture).Calendar.GetWeekOfYear((Get-Date('2012-12-29')).AddDays(3*([int](Get-Date('2012-12-29')).DayOfWeek -in (0,1,2))), ((Get-UICulture).DateTimeFormat.CalendarWeekRule), ((Get-UICulture).DateTimeFormat.FirstDayOfWeek))

These should give 1,1,1,52(correct) instead of 1,53,53,52(incorrect). Because starting weekday is Sunday.

You can test the incorrect result by replacing 3* with 0*.

Made it into a function you can give it no parameter a date or a string:

function Get-ISO8601Week {
    Param(
        $getISO8601Week = $(Get-Date)
    )
    If ($getISO8601Week.GetType() -eq [string]){
        $getISO8601Week = (Get-Date($getISO8601Week))
    }
    $getCulture = Get-Culture
    ($getCulture).Calendar.GetWeekOfYear(
        $getISO8601Week.AddDays(
            3*([int]$getISO8601Week.DayOfWeek -in (0,1,2))
        ), ($getCulture.DateTimeFormat.CalendarWeekRule
        ), ($getCulture.DateTimeFormat.FirstDayOfWeek
        )
    )
}
Get-ISO8601Week $(Get-Date('2012-12-31'))
1
Get-ISO8601Week (Get-Date('2012-12-31'))
1
Get-ISO8601Week '2012-12-31'
1

When I wrote this:

Get-ISO8601Week
4
B-Art
  • 69
  • 1
  • 5
  • _**It still had failures**_ So I made another one. You can find it in this post. Even **making failures is not a problem for me**. But I keep it in mind and **hope you will learn from my mistakes**. ;-) – B-Art Feb 04 '21 at 16:04
0

Ok the final draw for (Dutch) Culture AND UICulture.

I solved it by creating WeekRuleDay:

function Get-ISO8601Week {
    Param(
    [datetime]$DT = (Get-Date)
    )
    <#
        First create an integer(0/1) from the boolean,
        "Is the integer DayOfWeek value greater than zero?".
        Then Multiply it with 4 or 6 (weekrule = 0 or 2) minus the integer DayOfWeek value.
        This turns every day (except Sunday) into Thursday.
        Then return the ISO8601 WeekNumber.
    #>
    $Cult = Get-Culture; $DT = Get-Date($DT)
    $WeekRule = $Cult.DateTimeFormat.CalendarWeekRule.value__
    $FirstDayOfWeek = $Cult.DateTimeFormat.FirstDayOfWeek.value__
    $WeekRuleDay = [int]($DT.DayOfWeek.Value__ -ge $FirstDayOfWeek ) * ( (6 - $WeekRule) - $DT.DayOfWeek.Value__ )
    $Cult.Calendar.GetWeekOfYear(($DT).AddDays($WeekRuleDay), $WeekRule, $FirstDayOfWeek)
}

function Get-UiISO8601Week {
    Param(
    [datetime]$DT = (Get-Date)
    )
    <#
        First create an integer(0/1) from the boolean,
        "Is the integer DayOfWeek value greater than zero?".
        Then Multiply it with 4 or 6 (weekrule = 0 or 2) minus the integer DayOfWeek value.
        This turns every day (except Sunday) into Thursday.
        Then return the ISO8601 WeekNumber.
    #>
    $Cult = Get-UICulture; $DT = Get-Date($DT)
    $WeekRule = $Cult.DateTimeFormat.CalendarWeekRule.value__
    $FirstDayOfWeek = $Cult.DateTimeFormat.FirstDayOfWeek.value__
    $ThursSunDay = [int]($DT.DayOfWeek.Value__ -ge $FirstDayOfWeek ) * ( (6 - $WeekRule) - $DT.DayOfWeek.Value__ )
    $Cult.Calendar.GetWeekOfYear(($DT).AddDays($ThursSunDay), $WeekRule, $FirstDayOfWeek)
}

Write-Host "UICulture: " -NoNewline
(20..31).ForEach( { Get-UiISO8601Week("2012-12-$($_)") }) + ((1..7).ForEach( { Get-UiISO8601Week("2013-1-$($_)") })) -join ', ' 
Write-Host "  Culture: " -NoNewline
(20..31).ForEach( { Get-ISO8601Week("2012-12-$($_)") }) + ((1..7).ForEach( { Get-ISO8601Week("2013-1-$($_)") })) -join ', ' 

This works as a bugfix!

B-Art
  • 53
  • 4
B-Art
  • 69
  • 1
  • 5
  • (26..31).ForEach({Get-ISO8601Week("2009-12-$($_)")});(1..7).ForEach({Get-ISO8601Week("2010-1-$($_)")}) 52 52 53 53 53 53 53 53 53 1 1 1 1 – B-Art Feb 04 '21 at 15:57
0

I guess I found a quite robust implementation.

 #ISO week
$date = [DateTime] '2014-12-29'
(Get-Culture).Calendar.GetWeekOfYear(($date).AddDays(-[Int] (($date).AddDays(-1)).DayOfWeek+3), 2, 1)

#ISO year
(Get-Culture).Calendar.GetYear(($date).AddDays(-[Int] (($date).AddDays(-1)).DayOfWeek+3))

This calculates the week of the Thursday of the week. Other solutions would wrongly calculate week 53 for the given date.

Dodo
  • 1