Date conversion going wrong with different computer language

你说的曾经没有我的故事 提交于 2020-12-06 15:50:36

问题


I wrote a Powershell script for billing of customers. I get the list of bills, detect the month and get that specific bill. The timestamps are in Unix format and somehow I mess something up in the conversion depending on system language.

For example: $FirstDayPrevMonth = (get-date -Day 1 -Hour 0 -Minute 0 -second 0 -Millisecond 0).Addmonths(-1)

The conversion fails for systems that return: zondag 1 november 2020 00:00:00. The conversion succeeds for systems that return: Sunday, November 1, 2020 12:00:00 AM

The timestamp I get from the bills list is for example: 1604188800000

The I run the following function passing 1604188800000, which should return number of the month:

    Function Convert-FromUnixDateToMonth ($UnixDate) {
        (get-date( [timezone]::CurrentTimeZone.ToLocalTime(([datetime]'1/1/1970').Addmilliseconds($UnixDate)).ToString("MM/dd/yyyy") ) ) -as [int]  ### -UFormat %m  
        }

The result for the english system is 11 (correct), for the Dutch system is 1 (incorrect). I know it is somewhere in the language setting but I just can't figure out how to change this so it would work independent of the language.


回答1:


There is no reason to involve string representations of timestamps; apart from slowing things down, there's the pitfall of culture-specific interpretation, as you've experienced (see next section):

The simplest solution is to use type [datetimeoffset] (System.DateTimeOffset):

# Returns 11 - the month index - in all time zones *at or ahead of* UTC.
[datetimeoffset]::FromUnixTimeMilliseconds(1604188800000).LocalDateTime.Month

A corrected version of your solution attempt:

[timezone]::CurrentTimeZone.ToLocalTime(
  ([datetime] '1/1/1970').AddMilliSeconds(1604188800000)
).Month
  • Note that the from-string conversion from '1/1/1970' is unproblematic in this case, due to using a cast.

  • You didn't need a call to Get-Date at all, which is where the problem arose due to passing a string representation of a date - see next section.

  • Incidentally, Get-Date -Date directly accepts a [datetime] instance, so there's no need for to pass a string.

    • When you do pass a string, the resulting [datetime] (System.DateTime) has a .Kind property value of Unspecified rather than Local

Specifically, you've run into a problematic inconsistency in from-string type conversions in different contexts:

  • Casts and script/function parameters use culture-invariant from-string conversion; that is, irrespective of what the current culture is (as reflected in $PSCulture), the rules of the so-called invariant culture are used, whose date formats are based on the US-English culture; therefore, for instance, [datetime] '11/10/2000' is always interpreted as "10 November 2000", i.e., the first token, 11, is interpreted as the month.

    • However, more international-friendly formats are supported too; e.g.,
      [datetime] '2000-11-10' is the equivalent of the above.
  • Unexpectedly, cmdlet parameters use culture-sensitive from-string conversion.

    • This discrepancy is a known problem, but it was decided not to fix it, so as not to break backward compatibility - see GitHub issue #3348.



回答2:


Try below function to convert a Unix timestamp to either Local or UTC DateTime object

function ConvertFrom-UnixTimeStamp([Int64]$UnixTimeStamp, [switch]$AsUTC) {
    while ($UnixTimeStamp -lt -62135596800 -or $UnixTimeStamp -gt 253402300799) {
        # Assume $UnixTimeStamp to include milli- or nano seconds
        $UnixTimeStamp = [int64][math]::Truncate([double]$UnixTimeStamp / 1000)
    }
    if ($UnixTimeStamp -gt [Int32]::MaxValue) {
        # see: https://en.wikipedia.org/wiki/Year_2038_problem
        Write-Warning "The given value exceeds the [Int32]::MaxValue of 2147483647 and therefore enters the Year2038 Unix bug.."
    }
    # the Unix Epoch is January 1, 1970 midnight in UTC
    # older PowerShell versions use:
    # [DateTime]$epoch = New-Object System.DateTime 1970, 1, 1, 0, 0, 0, 0, Utc
    [DateTime]$epoch = [DateTime]::new(1970, 1, 1, 0, 0, 0, 0, 'Utc')

    $date = $epoch.AddSeconds($UnixTimeStamp)
    if ($AsUTC) { $date } else { $date.ToLocalTime() }

    # or use:
    # if ($AsUTC) { [DateTimeOffset]::FromUnixTimeSeconds($UnixTimeStamp).UtcDateTime }
    # else { [DateTimeOffset]::FromUnixTimeSeconds($UnixTimeStamp).LocalDateTime }
}

For your purposes, get the Month by using it like

(ConvertFrom-UnixTimeStamp 1604188800000).Month

Result on my Dutch system:

11


来源:https://stackoverflow.com/questions/65093931/date-conversion-going-wrong-with-different-computer-language

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!