如何使用时刻时区与 C# TimeZoneInfo 进行映射?

How to use moment-timezone for mapping with C# TimeZoneInfo?

我需要产生在用户选择的时区工作的错觉。服务器和客户端代码坚持javascript日期的问题。所以为了达到要求,我已经从 utc 手动映射到客户端的日期:

dateToServer(date) {
    const momentDate = moment(date);
    let serverDate = null;
    if (momentDate.isValid() && date) {
        const browserUtcOffset = momentDate.utcOffset();
        serverDate = momentDate
            .utc()
            .subtract(this.clientTimeZoneOffset, 'minutes')
            .add(browserUtcOffset, 'minutes')
            .toDate();
    }
    return serverDate;
}


dateToClient(date) {
    const momentDate = moment(date);
    let uiDate = null;
    if (momentDate.isValid() && date) {
        const browserUtcOffset = momentDate.utcOffset();
        uiDate = momentDate
            .utc()
            .subtract(browserUtcOffset, 'minutes')
            .add(this.clientTimeZoneOffset, 'minutes')
            .toDate();
    }
    return uiDate;
}

我是 adding/subtracting browserUtcOffset,因为它是 adding/subtracting 当日期在服务器和客户端之间移动时,浏览器会自动 adding/subtracting。

它运行良好,但此解决方案缺少对 DST 的处理。我想检查该日期的夏令时是否有效,然后根据需要添加夏令时偏移量。

这里是 C# 代码,可以做到这一点:

        string timeZone = "Central Standard Time";
        TimeZoneInfo timeZoneInfo = TimeZoneInfo.FindSystemTimeZoneById(timeZone);
        DateTime date = new DateTime(2011, 3, 14);
        Console.WriteLine(timeZoneInfo.BaseUtcOffset.TotalMinutes); // -360
        Console.WriteLine(timeZoneInfo.GetUtcOffset(date).TotalMinutes); // -300
        Console.WriteLine(timeZoneInfo.IsDaylightSavingTime(date)); // true
        Console.WriteLine(timeZoneInfo.DaylightName);
        Console.WriteLine(timeZoneInfo.SupportsDaylightSavingTime);

我在 momentjs 中找到了 isDST,当我将 windows 本地时区设置为 CST 并在浏览器控制台中检查 moment([2011, 2, 14]).isDST(); 时,我看到了真实情况。如何查看 isDST 取决于浏览器本地时间。

下一步尝试使用 moment-timezone 来做 smth,就像我在 C# 中所做的那样。不幸的是,我不明白如何实现这一目标。作为起点的第一个问题:UTC timeBase offset(-360 in C# sample)timezone name: Central Standard Time,但时刻时区的时区不同。

moment.tz.names().map(name => moment.tz.zone(name)).filter(zone => zone.abbrs.find(abbr => abbr === 'CST') != null) 此代码 returns 68 个时区。

为什么他们每个人都有很多缩写?直到什么意思?我只想检查所选时区中的 UTC 时间,即 "Central Standard Time",夏令时是否有效。再看一次 C# 示例 :)

        string timeZone = "Central Standard Time";
        TimeZoneInfo timeZoneInfo = TimeZoneInfo.FindSystemTimeZoneById(timeZone);
        DateTime date = new DateTime(2011, 3, 14);
        Console.WriteLine(timeZoneInfo.BaseUtcOffset.TotalMinutes); //-360
        Console.WriteLine(timeZoneInfo.GetUtcOffset(date).TotalMinutes); //-300
        Console.WriteLine(timeZoneInfo.IsDaylightSavingTime(date)); //true, I have checked is daylightsavingtime for the date
        Console.WriteLine(timeZoneInfo.DaylightName);
        Console.WriteLine(timeZoneInfo.SupportsDaylightSavingTime);

申请已写在angularjs1.6.3.

几件事:

  • JavaScript 中的 Date 对象跟踪特定的基于 UTC 的时间点。您可以通过调用 .valueOf().getTime() 查看该时间戳。只有某些函数和构造函数参数适用于本地时区(例如 .toString())。在调用函数时应用该本地时区。不能替换不同的时区(传递给 toLocaleString 的选项对象除外)。因此,Date对象不能从一个时区转换到另一个时区。因为你的两个函数都接受一个 Date 对象和 return 一个 Date 对象,所以你在中间所做的只是 选择不同的时间点 .这也可以在使用 Moment 的 addsubtract 方法的函数中看到。他们操纵表示的时间点 - 他们不更改时区。

  • 通过 this.clientTimeZoneOffset,您似乎将时区与时区偏移量混为一谈。这些是不同的概念,因为一个时区可能会经历多个不同的偏移量,由于 DST,但也由于标准时间的变化,它们在历史上发生过。另见 the timezone tag wiki 中的 "Time Zone != Offset"。仅传递客户端的偏移量是没有用的,因为该偏移量仅适用于单个时间点。您不能将它用于时区转换,因为它不会告诉您关于其他时间点使用哪些偏移量的任何信息。

  • 而是传递一个时区标识符。在 .NET 中的 Windows 上,这些看起来像 "Central Standard Time"(代表标准时间和夏令时),在 JavaScript 和大多数其他操作系统中,使用 IANA 时区名称。他们看起来像 "America/Chicago"。这也包含在 timezone tag wiki.

  • 如果您的 .NET 代码使用 Windows 标识符,您可以使用我的 TimeZoneConverter 库将 Windows 转换为 IANA,然后将该字符串发送到浏览器作为时区。

  • 使用 Moment-Timezone,您可以像这样简单地检查 DST:

    moment.tz([2011, 2, 14], 'America/Chicago').isDST()
    

    同样,您应该使用 IANA time zone IDs,如果您在服务器端使用 Windows 时区,TimeZoneConverter 可以提供它们。

  • 可以 考虑在服务器端使用 Noda Time 及其 TZDB 时区提供程序。这将允许您在双方都使用 IANA 时区。 TimeZoneInfo when 运行 on .NET Core on Linux or Mac OSX.

  • 也是这种情况
  • 当您使用 CST 搜索缩写时看到这么多条目的原因是时区缩写不明确。您可能指的是美国中部标准时间,但您也可能指的是古巴标准时间或中国标准时间,或使用该缩写的其他各种地方。

  • 关于untils数组,一般不需要关心。它是 Moment-Timezone 用于选择正确时间点以选择时区偏移量和缩写的内部数据的一部分。

  • 你最后说的有点不一样:

    All I want to check if UTC time in the selected timezone, which is "Central Standard Time", the daylight saving day active.

    我之前给出的示例假定您从该时区的本地时间开始。如果您从 UTC 时间开始,那么它是这样的:

    moment.utc([2011, 2, 14]).tz('America/Chicago').isDST()
    

    当然,你可以在我传递数组的地方传递各种其他支持的输入。 Moment docs 可以帮助您选择可用的选项。

请勾选。对我很有用

词汇

  • UI 日期 - 用户在 html 日期输入时看到的日期 select输入日期。 UIdate为原生js日期
  • 服务器日期 - 存储在 数据库。
  • timezone id - 标识时区的字符串。它们对于 windows(.net)、IANA(javascript) 和 rails 约定是不同的。

问题

主要问题f.e。当一个人登录到位于时区 +3 的 PC 上时,但他的帐户设置为 -6。 angularjs 和 kendo 正在项目中使用,kendo 时间仅适用于本机 js 日期。本机 js 日期始终在浏览器时区中,对于此示例 +3。但我应该在 -6 中设置类似的东西。 F.e。示例用户有 selected 时间 07:00,UTC 将是 04:00 (07:00 - 3),但对于他的帐户,时区是 -6,UTC 应该是13:00 (07:00 + 6)。当我们将本机 js 日期(UI 日期)转换为 UTC 和向后时,会自动应用此对话。因此决定计算服务器上的偏移量并摆脱浏览器时区偏移量:utcTime = UItime + browserOffset - clientTimezoneBaseOffset - daylightSavingTimeOffset。 然而,当需要返回 UI 时间时会出现问题,f.e。今天 06.06.2014 并且 DST 在这个日期是正确的,但是当我们从服务器获取 03.03.2014 日期时,我们不知道 DST 在 03.03.2014 是否有效。

回答

在服务器上的 .net 项目中,因此在数据库中存储了 window 的时区 ID。我用下一种方式完成了:依靠服务器 DST 的日期范围从服务器上的最小日期开始,并保存在客户端的本地存储中。我喜欢这种情况 服务器是真实来源之一,因此可以在任何客户端上执行计算而无需操纵时区。无需在 IANA、Windows 和 rails 时区之间进行转换。但也有一个问题:需要预先计算 DateTime.MinValueDateTime.MaxValue 范围内的夏令时,目前为了加快我正在计算 01.01.2000DateTime.Now 范围内的夏令时 -将数据库中的值转换为 UI 就足够了,因为在数据库中,最小日期是 2008 年,但对于 html 输入是不够的,因为用户可以 select 值大于DateTime.Now 低于 01.01.2000。为了修复它,我计划使用 TimeZoneConverter 向客户端发送 IANATimezoneId,并且对于提供的日期(UI 或服务器)不在 [=20= 范围内的情况] 属于 moment.utc(date).tz(IANATimezoneId).isDST().

这是服务器端的新代码

    private class DaylightSavingTimeDescriptor
    {
        public DateTime StartTime { get; set; }
        public DateTime EndTime { get; set; }
        public bool IsDaylightSavingTime { get; set; }
    }

    private string GetDaylightSavingTimeDescriptorsJson(string timeZone)
    {
        string daylightSaveingTimeDescriptorsJson = String.Empty;
        if(timeZone != null)
        {
            List<DaylightSavingTimeDescriptor> dstList = new List<DaylightSavingTimeDescriptor>();
            TimeZoneInfo timeZoneInfo = TimeZoneInfo.FindSystemTimeZoneById(timeZone);
            DateTime startDate = new DateTime(2000, 1, 1);
            DateTime dateIterator = startDate;
            bool isDST = timeZoneInfo.IsDaylightSavingTime(startDate);
            while (dateIterator < DateTime.Now)
            {
                bool currDST = timeZoneInfo.IsDaylightSavingTime(dateIterator);
                if (isDST != currDST)
                {
                    dstList.Add(new DaylightSavingTimeDescriptor()
                    {
                        EndTime = dateIterator.AddDays(-1),
                        IsDaylightSavingTime = isDST,
                        StartTime = startDate
                    });
                    startDate = dateIterator;
                    isDST = currDST;
                }
                dateIterator = dateIterator.AddDays(1);
            }

            daylightSaveingTimeDescriptorsJson = Newtonsoft.Json.JsonConvert.SerializeObject(dstList);
        }
        return daylightSaveingTimeDescriptorsJson;
    }

这是客户端修改的dateToServer,dateToClient

export default class DateService{
constructor (authService) {
    const authData = authService.getAuthData();
    this.clientTimeZoneOffset = Number(authData.timeZoneOffset);
    this.daylightSavingTimeRanges = authData.daylightSavingTimeRanges ? JSON.parse(authData.daylightSavingTimeRanges) : [];
}

getDaylightSavingTimeMinutesOffset(utcDate) {
    const dstRange = this.daylightSavingTimeRanges.find(range => {
        const momentStart = moment(range.startTime).utc();
        const momentEnd = moment(range.endTime).utc();
        const momentDate = moment(utcDate).utc();
        return momentStart.isBefore(momentDate) && momentEnd.isAfter(momentDate);
    });
    const isDaylightSavingTime = dstRange ? dstRange.isDaylightSavingTime : false;
    return isDaylightSavingTime ? '60' : 0;
}

dateToClient(date) {
    const momentDate = moment(date);
    let uiDate = null;
    if (momentDate.isValid() && date) {
        const browserUtcOffset = momentDate.utcOffset();
        uiDate = momentDate
            .utc()
            .subtract(browserUtcOffset, 'minutes')
            .add(this.clientTimeZoneOffset, 'minutes')
            .add(this.getDaylightSavingTimeMinutesOffset(momentDate.utc()), 'minutes')
            .toDate();
    }
    return uiDate;
}

dateToServer(date) {
    const momentDate = moment(date);
    let serverDate = null;
    if (momentDate.isValid() && date) {
        const browserUtcOffset = momentDate.utcOffset();
        serverDate = momentDate
            .utc()
            .subtract(this.clientTimeZoneOffset, 'minutes')
            .add(browserUtcOffset, 'minutes')
            .subtract(this.getDaylightSavingTimeMinutesOffset(momentDate.utc()), 'minutes')
            .toDate();
    }
    return serverDate;
}
}

PS

我想摆脱偏移量并在服务器上使用 moment-timezone on the client and timezone converter,但如果客户要求,我会稍后再说,因为我以前从未尝试过,我不确定它是否有效好吧,当前的解决方案正在运行。此外,偏移量无论如何都将用于 dateinputs 组件(angularjs),因为它们正在使用 kendo-dateinput,其中 ng-model 是浏览器本地时间的 JS Date,但我提供了另一个时区,所以需要到组件内部的转换。

PPS 从我的角度来看最好的解决方案,如果 timezone converter and moment-timezone 将按预期工作

无论如何,整个应用程序都在运行 JS 日期,所以当用户在莫斯科和美国时,在配置文件 selects 中输入 kendo 中的日期时间,或查看设置 kendo-调度程序,或在 kendo-table 中显示日期,我需要使用偏移量进行操作。但是我没有直接传递客户端偏移量,而是计划在 timezone converter and get the offset I need directly from moment-timezone.

的帮助下从服务器传递 IANA 时区 ID