在 Powershell 中获取给定日期的 ISO 8601 年度周数

Get the ISO 8601 Week of Year of a given date in Powershell

正在搜索如何获得 ISO 8601 Week of Year in PowerShell, I've stumbled upon this question for C#

尽量不要用 PowerShell 代码来解决这个问题,下面是我的 Powershell 移植。 (基于answer by user6887101

我会暂时搁置这个 'not accepted' 以防有人想出更好的解决方案。

user6887101 and explained in detail here所述,伪算法为:

ISO 8601 周从 星期一 开始,到 星期日.

结束
  1. 对于任何给定日期,找到与给定日期同一周的星期四 日期。例如。:
    • 如果原始日期是 Sunday, January 1st XXXX 找到 Thursday, December 29th XXXX-1
    • 如果原始日期是 Monday, December 31st XXXX 找到 Thursday, January 3rd XXXX+1
  2. Year of the ISO 8601 Week 是包含 Thursday found in step 1 的那个(例如:XXXX-1XXXX+1
  3. ISO 8601 Week numberyear from step 2 中的 number of Thursdays(直到并包括找到的 Thursday 本身)
function Get-ISO8601Week (){
# Adapted from 
  [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
    }
  }
}

快速测试:

PS C:\>  Get-Date  |  Get-ISO8601Week

Year WeekNumber WeekString DateString
---- ---------- ---------- ----------
2017         41 2017-W41   2017-10-11   Wednesday

在广泛的输入范围内测试正确的结果:

#<# 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
    '============================================================='
    }
#>

'tricky' 日期样本@MSDN
您可以手动检查准确性@ 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
    ...........
#>

聚会有点晚了,但是...只是想在这里留下这个答案,因为这个问题是我在寻找解决方案时偶然发现的第一个问题。有一个更简单的方法:

get-date -UFormat %V

"{0:d1}" -f ($(Get-Culture).Calendar.GetWeekOfYear((Get-Date),[System.Globalization.CalendarWeekRule]::FirstFourDayWeek, [DayOfWeek]::Monday))

取决于您是否需要 ISO8601。

$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)

来自https://ss64.com/ps/get-date.html

对于纯 Powershell: Culture 和 UICulture 有问题 要以文化语言打印一周的第一天:

(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

还有周数不符合 ISO8601 标准的问题。 日期 2012-12-31 应该是第 1 周,但它给出了 53。 所以你必须实施某种解决方案。

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

用于测试:

(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))

这些应该给出 1,1,52(正确)而不是 1,53,52(不正确)。 或者(UICulture相关版本)

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

用于测试:

(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))

这些应该给出 1,1,1,52(正确)而不是 1,53,53,52(不正确)。 因为开始工作日是星期日。

您可以通过将 3* 替换为 0* 来测试不正确的结果。

把它变成一个函数,你可以不给它参数一个日期或一个字符串:

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

当我写这篇文章时:

Get-ISO8601Week
4

确定(荷兰)文化和 UICulture 的最终抽签。

我通过创建 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 ', ' 

这可以作为错误修复!

我想我找到了一个非常可靠的实现。

 #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))

这会计算一周中的星期四。 其他解决方案会错误地计算给定日期的第 53 周。