2

我需要制造在用户选择的时区工作的错觉。服务器和客户端代码坚持使用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;
}

我正在添加/减去 browserUtcOffset,因为当日期在服务器和客户端之间移动时,它会由浏览器自动添加/减去。

它运行良好,但此解决方案缺少对 DST 的处理。我想检查该日期的 DST 是否有效,然后在需要时添加 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);

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

下一步尝试像我在 C# 中所做的那样使用 moment-timezone 来做某事。不幸的是,我不明白如何实现这一点。作为起点,我遇到的第一个问题是:UTC time, Base 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)此代码返回 68 个时区。 在此处输入图像描述

为什么他们每个人都有很多缩写?直到是什么意思?我只想检查所选时区的UTC时间,即“中央标准时间”,夏令时是否有效。再看一次 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);

该应用程序已在 angularjs 1.6.3 上编写。

4

2 回答 2

4

一些东西:

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

  • 通过传递this.clientTimeZoneOffset,您似乎将时区与时区偏移量混为一谈。这些是独立的概念,因为一个时区可能会经历多个不同的偏移量,这既是由于 DST 也是由于标准时间的变化,它们在历史上发生过。另请参阅时区标签 wiki 中的“时区!= 偏移量” 。仅传递客户端的偏移量是没有用的,因为该偏移量仅适用于单个时间点。您不能将其用于时区转换,因为它不会告诉您哪些偏移量用于其他时间点。

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

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

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

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

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

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

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

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

  • 你最后说的有点不同:

    我只想检查所选时区的UTC时间,即“中央标准时间”,夏令时是否有效。

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

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

    当然,您可以在我传递数组的地方传递各种其他受支持的输入。Moment 文档可以为您提供可用选项。

于 2019-01-04T16:38:48.723 回答
0

请检查马特约翰逊对这个问题的回答。这对我来说非常有用。

词汇

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

问题

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

回答

在服务器上的 .net 项目中,因此在数据库中存储了窗口的时区 ID。我采用了下一种方法:依靠服务器 DST 获取服务器上最小日期范围内的日期范围,并将其保存在客户端的本地存储中。我喜欢在这种情况下,服务器是事实来源之一,因此可以在任何客户端上执行计算,而无需操纵时区。并且无需在 IANA、Windows 和 rails 时区之间进行转换。但也有一个问题:需要预先计算范围 from DateTime.MinValueto的 DST DateTime.MaxValue,目前为了加快我正在计算范围 from 01.01.2000to 的DST DateTime.Now- 将值从数据库转换为 UI 就足够了,因为在数据库中,最小日期在2008 年,但不足以用于 html 输入,因为用户可以选择大于的值DateTime.Now然后再降低01.01.2000。为了解决这个问题,我计划使用TimeZoneConverter发送到客户端IANATimezoneId,并且对于提供的日期(UI 或服务器)不在[01.01.2000, DateTime.Now]属于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;
}
}

附言

我想摆脱偏移量并在客户端上使用时刻时区并在服务器上使用时区转换器,但如果客户问的话会稍后,因为我以前从未尝试过,我不确定它是否会运作良好,并且当前的解决方案正在运行。无论如何,偏移量也将用于 dateinputs 组件(angularjs),因为它们使用的是 kendo-dateinput,其中 ng-model 是浏览器本地时间的 JS Date,但我提供了另一个时区,因此需要在组件内部进行转换。

从我的角度来看,PPS 最佳解决方案,如果时区转换器时刻时区将按预期工作

无论如何,整个应用程序都在运行 JS 日期,所以当用户在莫斯科和美国的个人资料中选择 kendo-input 中的日期时间,或者查看 setup kendo-scheduler,或者在 kendo-table 中显示日期时,我需要使用偏移量进行操作. 但是,我不是直接传递客户端偏移量,而是计划在时区转换器的帮助下从服务器传递 IANA 时区 ID,并直接从moment-timezone获取我需要的偏移量。

于 2019-01-08T11:27:57.990 回答