2015-01-22 33 views
7

Chúng tôi có một ứng dụng trong ASP.NET lưu trữ tất cả dữ liệu múi giờ của người dùng ở định dạng Windows (theo TimeZoneInfo.Id).Chuyển múi giờ Windows sang múi giờ moment.js?

Chúng tôi cũng sử dụng thư viện TimeZone và moment.js TimeZone để chuyển đổi dữ liệu UTC thành dữ liệu người dùng ở phía máy khách. Nó là một ứng dụng AngularJs phức tạp cần phải chuyển đổi múi giờ ở phía máy khách.

Cho đến nay, chúng tôi đã sử dụng thư viện NodaTime .NET để chuyển đổi ID múi giờ của Windows thành ID múi giờ Moment.js. Nó hoạt động tốt cho hầu hết các múi giờ chung. Nhưng chúng tôi cần thực hiện chuyển đổi này tương thích 100%.

Hiện tại, dường như không có cách nào đáng tin cậy để ánh xạ ID múi giờ Windows cho dữ liệu Múi giờ IANA. Có rất nhiều sự khác biệt.

Tôi tin rằng các ứng dụng JS hiện đại thường xử lý các múi giờ. Và đôi khi cần phải chuyển đổi chính xác TZ trên phía máy chủ (C#) và phía máy khách (JS).

Có cách nào để nghiêm chỉnh ánh xạ/chuyển đổi .NET TimeZoneInfo thành đối tượng múi giờ Moment.js không?

+1

múi giờ sử dụng múi giờ chuẩn IANA. Nếu bạn tìm thấy một múi giờ cụ thể không ánh xạ chính xác bằng cách sử dụng hàm 'WindowsToIana' trong câu trả lời được liên kết, hãy cho tôi biết cái nào. Tất cả các 'TimeZoneInfo' id nên được mappable. –

+0

Matt, điều này không hoàn toàn đúng. Trong khi moment.js sử dụng định dạng IANA, múi giờ Windows không thể được ánh xạ 100% theo múi giờ IANA. Sẽ có nhiều sự khác biệt. Để giải quyết vấn đề hoàn toàn, chúng ta có thể chuyển đổi các quy tắc TimeZoneInfo thành đối tượng dữ liệu vùng moment.js. Vì vậy, sẽ có bản đồ Windows-> Moment.js 100%. – Evgenyt

+0

@Evgenyt - Nó phụ thuộc vào mức độ chi tiết và lịch sử mong muốn. Từ quan điểm của người dùng chọn múi giờ hiện tại của họ và làm việc với dữ liệu trong thời gian hiện đại, sau đó [ánh xạ CLDR] (http://unicode.org/repos/cldr/trunk/common/supplemental/windowsZones.xml) Unicode duy trì là chính xác hợp lý, và tất cả các vùng đều có thể được biến đổi theo hướng cửa sổ-> iana. Bạn thậm chí có thể có độ chính xác tốt hơn nếu bạn sử dụng mã quốc gia, thay vì chọn lãnh thổ "chính" (001). Chỉ của nó theo hướng iana-> cửa sổ có những vùng không thể điều chỉnh được. –

Trả lời

6

TL; DR:

  • tiếp tục sử dụng Noda Thời gian trên phía máy chủ
  • Chọn xem sử dụng dữ liệu hoặc dữ liệu BCL IANA; Cá nhân tôi sẽ giới thiệu IANA, nhưng đó là cuộc gọi của bạn. (Ngoài bất cứ điều gì khác, dữ liệu IANA được phiên bản rõ ràng hơn.)
  • Sử dụng Noda Thời gian để tạo dữ liệu moment.js để bạn biết chính xác những gì khách hàng sẽ sử dụng và nó sẽ phù hợp với những gì bạn làm server
  • làm việc ra một chiến lược cho những gì sẽ xảy ra khi dữ liệu thay đổi

chi tiết:

Và đôi khi cần phải chuyển đổi TZ chính xác vào serv er-side (C#) và phía máy khách (JS).

Bạn cần phải nhận chính xác cùng một dữ liệu múi giờ trên cả hai mặt triển khai tương đương trên cả hai mặt. Này có vấn đề bởi vì:

  • IANA dữ liệu múi giờ được cập nhật thường xuyên (vì vậy bạn cần để có thể nói "sử dụng dữ liệu 2015a" chẳng hạn)
  • của Windows dữ liệu múi giờ được cập nhật thường xuyên
  • tôi không muốn đặt cược rằng mọi việc thực hiện các quy tắc IANA là giống hệt nhau, mặc dù họ nên
  • tôi biết rằng việc thực hiện TimeZoneInfo đã thay đổi theo thời gian, một phần để loại bỏ một số odd bugs và một phần để include more data. (.NET 4.6 hiểu khái niệm về múi giờ thay đổi độ lệch chuẩn của nó so với lịch sử; các phiên bản trước đó không)

Với thời gian Noda, bạn có thể dễ dàng chuyển đổi dữ liệu múi giờ BCL hoặc IANA sang định dạng moment.js - và làm như vậy đáng tin cậy hơn mã của Evgenyt, bởi vì TimeZoneInfo không cho phép bạn yêu cầu chuyển tiếp. (Do lỗi trong số TimeZoneInfo chính nó, có những túi nhỏ, nơi bù trừ có thể thay đổi chỉ trong vài giờ - chúng không nên, nhưng nếu bạn muốn khớp chính xác hành vi TimeZoneInfo, bạn cần có khả năng tìm tất cả những điều đó - Mã của Evgenyt sẽ không luôn luôn phát hiện ra chúng.) Ngay cả khi Noda Time không phản chiếu chính xác TimeZoneInfo, nó phải phù hợp với chính nó.

Định dạng moment.js trông khá đơn giản, miễn là bạn không nhớ vận chuyển dữ liệu cho khách hàng, đó chắc chắn là một tùy chọn. Bạn cần phải suy nghĩ về những việc cần làm khi dữ liệu thay đổi mặc dù:

  • Làm thế nào để bạn chọn nó trên máy chủ?
  • Làm thế nào để bạn đối phó với khách hàng tạm thời sử dụng dữ liệu cũ?

Nếu tính nhất quán chính xác thực sự quan trọng đối với bạn, bạn cũng có thể gửi dữ liệu múi giờ cho khách hàng bằng phiên bản dữ liệu múi giờ ... mà khách hàng có thể quay lại máy chủ khi nó đăng dữ liệu.(Tôi giả định rằng nó đang làm như vậy, tất nhiên.) Các máy chủ sau đó có thể sử dụng phiên bản đó, hoặc từ chối yêu cầu của khách hàng và nói rằng có dữ liệu gần đây hơn.

Dưới đây là một số mã mẫu để chuyển đổi dữ liệu múi giờ Noda thành moment.js - có vẻ ổn với tôi, nhưng tôi đã không làm nhiều với nó. Nó khớp với tài liệu trong momentjs.com ... lưu ý rằng bù đắp phải được đảo ngược vì moment.js quyết định sử dụng số dư dương cho các múi giờ là sau UTC, vì một số lý do.

using System; 
using System.Linq; 

using NodaTime; 
using Newtonsoft.Json; 

class Test 
{ 
    static void Main(string[] args) 
    { 
     Console.WriteLine(GenerateMomentJsZoneData("Europe/London", 2010, 2020)); 
    } 

    static string GenerateMomentJsZoneData(string tzdbId, int fromYear, int toYear) 
    { 
     var intervals = DateTimeZoneProviders 
      .Tzdb[tzdbId] 
      .GetZoneIntervals(Instant.FromUtc(fromYear, 1, 1, 0, 0), 
           Instant.FromUtc(toYear + 1, 1, 1, 0, 0)) 
      .ToList(); 

     var abbrs = intervals.Select(interval => interval.Name); 
     var untils = intervals.Select(interval => interval.End.Ticks/NodaConstants.TicksPerMillisecond); 
     var offsets = intervals.Select(interval => -interval.WallOffset.Ticks/NodaConstants.TicksPerMinute); 
     var result = new { name = tzdbId, abbrs, untils, offsets }; 
     return JsonConvert.SerializeObject(result); 
    } 
} 
+0

Cảm ơn Jon, tôi hiện đang sử dụng NodaTime và mã của bạn để liệt kê các khoảng cách đến/bù. Tuy nhiên mã của bạn không thành công cho đến nay, khi chạy thử nghiệm .NET/JavaScript. https://github.com/ScreenshotMonitor/WindowsTimeZoneToMomentJs/blob/noda/src/Pranas.WindowsTimeZoneToMomentJs/TimeZoneToMomentConverter.cs – Evgenyt

+0

@Evgenyt: Thử nghiệm kiểm tra, chính xác là gì và thất bại là gì? (Và bạn đang sử dụng phiên bản nào của Noda Time và phiên bản .NET nào đã thay đổi ...) –

+0

NodaTime 1.3.1 và .NET 4.5, kiểm tra đơn giản lặp qua các ngày và so sánh kết quả của sự hội tụ bằng momentjs và TimeZoneInfo. https://github.com/ScreenshotMonitor/WindowsTimeZoneToMomentJs/blob/noda/src/Pranas.WindowsTimeZoneToMomentJs.Test/TimeZoneToMomentConverterTest.cs – Evgenyt

4

CẬP NHẬT

Jon gợi ý rằng bạn phải sử dụng dữ liệu NodaTime BCL hoặc IANA trong cả momentjs và .NET. Nếu không, bạn sẽ nhận được sự khác biệt. Tôi nên đồng ý với điều này.

Bạn không thể 100% đáng tin cậy chuyển đổi thời gian trong .NET 4.5 bằng cách sử dụng TimeZoneInfo. Ngay cả khi bạn chuyển đổi nó bằng cách sử dụng NodaTime như được đề xuất hoặc TimeZoneToMomentConverter như bên dưới.


ORIGINAL ĐÁP

IANA và Windows múi giờ cập nhật dữ liệu theo thời gian và có granularity khác nhau.

Vì vậy, nếu bạn muốn chính xác cùng một chuyển đổi trong moment.js NET và - bạn có một trong hai để

  • sử dụng IANA ở khắp mọi nơi (với NodaTime như đề nghị Matt),
  • sử dụng Windows múi giờ ở khắp mọi nơi (chuyển đổi TimeZoneInfo quy tắc định dạng moment.js).

Chúng tôi đã đi theo cách thứ hai và triển khai trình chuyển đổi.

Nó bổ sung bộ nhớ cache an toàn cho chủ đề hiệu quả hơn vì nó cơ bản lặp qua các ngày (thay vì cố gắng tự chuyển đổi các quy tắc TimeZoneInfo). Trong các thử nghiệm của chúng tôi, nó chuyển đổi múi giờ Windows hiện tại với độ chính xác 100% (xem các bài kiểm tra trên GitHub).

Đây là mã của công cụ này:

using System; 
using System.Collections.Concurrent; 
using System.Collections.Generic; 
using System.Linq; 
using System.Text; 
using System.Threading.Tasks; 
using System.Web.Script.Serialization; 

namespace Pranas.WindowsTimeZoneToMomentJs 
{ 
    /// <summary> 
    /// Tool to generates JavaScript that adds MomentJs timezone into moment.tz store. 
    /// As per http://momentjs.com/timezone/docs/ 
    /// </summary> 
    public static class TimeZoneToMomentConverter 
    { 
     private static readonly DateTimeOffset UnixEpoch = new DateTimeOffset(1970, 1, 1, 0, 0, 0, 0, TimeSpan.Zero); 
     private static readonly JavaScriptSerializer Serializer = new JavaScriptSerializer(); 
     private static readonly ConcurrentDictionary<Tuple<string, int, int, string>, string> Cache = new ConcurrentDictionary<Tuple<string, int, int, string>, string>(); 

     /// <summary> 
     /// Generates JavaScript that adds MomentJs timezone into moment.tz store. 
     /// It caches the result by TimeZoneInfo.Id 
     /// </summary> 
     /// <param name="tz">TimeZone</param> 
     /// <param name="yearFrom">Minimum year</param> 
     /// <param name="yearTo">Maximum year (inclusive)</param> 
     /// <param name="overrideName">Name of the generated MomentJs Zone; TimeZoneInfo.Id by default</param> 
     /// <returns>JavaScript</returns> 
     public static string GenerateAddMomentZoneScript(TimeZoneInfo tz, int yearFrom, int yearTo, string overrideName = null) 
     { 
      var key = new Tuple<string, int, int, string>(tz.Id, yearFrom, yearTo, overrideName); 

      return Cache.GetOrAdd(key, x => 
      { 
       var untils = EnumerateUntils(tz, yearFrom, yearTo).ToArray(); 

       return string.Format(
@"(function(){{ 
    var z = new moment.tz.Zone(); 
    z.name = {0}; 
    z.abbrs = {1}; 
    z.untils = {2}; 
    z.offsets = {3}; 
    moment.tz._zones[z.name.toLowerCase().replace(/\//g, '_')] = z; 
}})();", 
        Serializer.Serialize(overrideName ?? tz.Id), 
        Serializer.Serialize(untils.Select(u => "-")), 
        Serializer.Serialize(untils.Select(u => u.Item1)), 
        Serializer.Serialize(untils.Select(u => u.Item2))); 
      }); 
     } 

     private static IEnumerable<Tuple<long, int>> EnumerateUntils(TimeZoneInfo timeZone, int yearFrom, int yearTo) 
     { 
      // return until-offset pairs 
      int maxStep = (int)TimeSpan.FromDays(7).TotalMinutes; 
      Func<DateTimeOffset, int> offset = t => (int)TimeZoneInfo.ConvertTime(t, timeZone).Offset.TotalMinutes; 

      var t1 = new DateTimeOffset(yearFrom, 1, 1, 0, 0, 0, TimeSpan.Zero); 

      while (t1.Year <= yearTo) 
      { 
       int step = maxStep; 

       var t2 = t1.AddMinutes(step); 
       while (offset(t1) != offset(t2) && step > 1) 
       { 
        step = step/2; 
        t2 = t1.AddMinutes(step); 
       } 

       if (step == 1 && offset(t1) != offset(t2)) 
       { 
        yield return new Tuple<long, int>((long)(t2 - UnixEpoch).TotalMilliseconds, -offset(t1)); 
       } 
       t1 = t2; 
      } 

      yield return new Tuple<long, int>((long)(t1 - UnixEpoch).TotalMilliseconds, -offset(t1)); 
     } 
    } 
} 

Bạn cũng có thể nhận được nó qua NuGet:

PM> Install-Package Pranas.WindowsTimeZoneToMomentJs 

Và trình duyệt nguồn cho mã và thử nghiệm trên GitHub.

+0

Tôi tin rằng điều này sẽ thất bại trong một số trường hợp góc nhỏ mà 'TimeZoneInfo' quyết định chuyển đổi offsets chỉ trong vài giờ - nó có thể dễ dàng bỏ lỡ những chuyển đổi nhanh chóng qua lại. Xem http://codeblog.jonskeet.uk/2014/09/30/the-mysteries-of-bcl-time-zone-data/ để biết thêm chi tiết và một công cụ nhỏ giúp bạn tìm thấy những trường hợp đó. –

Các vấn đề liên quan