2013-11-01 14 views
15

Khách hàng của tôi, là người trong lĩnh vực bảo hiểm, yêu cầu ngày sinh của người được bảo hiểm tiềm năng. Nó được nhập vào một mẫu web bằng cách sử dụng một datepicker jQuery, kết nối với một thuộc tính mô hình bằng cách sử dụng Knockout và được gửi thông qua ajax trong JSON tới một bộ điều khiển MVC 4.Thực hiện DST trong JavaScript gây ra sự cố khi gửi đến bộ điều khiển MVC

Một số chủ hợp đồng đã nhận được tài liệu bảo hiểm của họ với ngày sinh sai. Trong quá trình tiếp theo điều tra, chúng tôi thấy rằng ngoài các lỗi nhập dữ liệu, đó là cực kỳ biên, ngày tháng sai tập trung ở hai giai đoạn sau:

  • ba tuần cuối cùng của tháng Ba đến đầu tháng Tư
  • cuối cùng tuần từ tháng 10 đến tuần đầu tiên của tháng 11.

Khi khách hàng của chúng tôi ở múi giờ Mỹ/Montreal, tôi ngay lập tức nghĩ về một vấn đề của DST. The DST rules changed in 2007 in this timezone.

Đọc một số bài viết và các câu hỏi về Stack Overflow khác, tôi đã học được rằng tiêu chuẩn ECMAScript 5 xác định rằng việc triển khai phải tính đến các quy tắc DST hiện tại và không cần phải tôn trọng lịch sử DST của các thay đổi. ES5 15.9.1.8 Trình duyệt duy nhất không tuân thủ đặc điểm kỹ thuật này tại thời điểm này là IE10 (chi tiết về điều này sau).

Với đặc điểm kỹ thuật này, trình duyệt sẽ báo cáo ngày như sau (thử nghiệm trong Chrome):

(new Date(2006, 9, 31, 0, 0, 0)).toISOString() 
// 2006-10-31T04:00:00.000Z 

(new Date(2008, 9, 31, 0, 0, 0)).toISOString() 
// 2008-10-31T04:00:00.000Z 

Trong khi các báo cáo nền tảng .NET ngày một cách chính xác như sau:

DateTime dt = new DateTime(2006, 10, 31, 0, 0, 0); 
Console.WriteLine("{0:MM/dd/yy H:mm:ss zzz}", dt); 
// 10-31-06 0:00:00 -05:00 
dt = new DateTime(2008, 10, 31, 0, 0, 0); 
Console.WriteLine("{0:MM/dd/yy H:mm:ss zzz}", dt); 
// 10-31-08 0:00:00 -04:00 

Một nguyên nhân của việc này vấn đề là nếu một người được bảo hiểm được sinh ra trong tuần cuối cùng của tháng 10 trước năm 2007, thời gian UTC sẽ được báo cáo cho bộ điều khiển MVC của tôi không chính xác, ví dụ:

ngày javascript đối tượng:

new DateTime(2006, 9, 31, 0, 0, 0); 

Net phân tích dữ liệu JSON và nhận:

10-30-06 23:00:00 GMT 

Trong các bài kiểm tra, tôi phát hiện ra rằng các đại diện bên trong của một đối tượng Date trong Javascript là không tương thích với của một .Net DateTime và tôi không thể cuộn trình phân tích cú pháp của riêng tôi bằng cách sử dụng biểu diễn JavaScript:

Javascript:

(new Date(2006, 9, 31, 0, 0, 0)).getTime() 
// 1162267200000 

Net:

DateTime dt = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc).AddMilliseconds(1162267200000); 
Console.WriteLine("{0}", dt); 
// 2006-10-31 04:00:00 

(Điều này là sai, bởi vì nó được biểu diễn như ngày 30 tháng 10 năm 2006 lúc 23:00 theo giờ địa phương).

Đối với trường hợp sử dụng khách hàng của tôi , vì đó là phần ngày mà tôi quan tâm, tôi có thể chỉ đặt phần thời gian của đối tượng Date thành trưa, điều này sẽ bảo vệ tôi khỏi mọi trường hợp hiểu sai ngày về phần JavaScript.Thật không may, đây là bản vá Ugly và không giải quyết các tình huống tiềm ẩn khác, chẳng hạn như nếu khách hàng của tôi muốn chúng tôi triển khai chức năng yêu cầu chúng tôi có ngày và giờ chính xác của sự kiện đã xảy ra trong quá khứ.

Một cách tiếp cận khác là viết một thuật toán để phát hiện các tuần DST có khả năng có vấn đề trong bộ điều khiển của tôi và đặt thời gian chính xác nếu tôi nhận được ngày trong các tuần được đề cập. Thật không may, cho rằng tiêu chuẩn ECMAScript 6 chỉ rõ rằng lịch sử thay đổi DST phải được tôn trọng (vì vậy tất cả các trình duyệt trong tương lai phải xử lý tình huống đó một cách chính xác) và cho rằng IE10 đã hoạt động đúng cách, tôi sợ tạo ra một tình huống có vấn đề trong tương lai . Cách tiếp cận này cũng có vẻ sai, về mặt kiến ​​trúc.

Một khía cạnh khác cần xem xét là nếu tôi phải chuyển mô hình từ máy khách đến máy chủ và sau đó quay lại máy khách, tôi sẽ cần tạo cách tạo lại đối tượng Ngày JavaScript "xấu" để chắc chắn không ảnh hưởng màn hình hiển thị ở phía máy khách của ứng dụng.

Không có giải pháp nào có vẻ chính xác và có thể mở rộng. Tôi không thể tin rằng không ai từng phải đối phó với vấn đề này trước đây.

Làm cách nào để khắc phục sự cố này vĩnh viễn và theo cách có thể áp dụng cho tất cả các dự án JavaScript/MVC khác?

EDIT # 1 - thông tin thêm vào một số không liên quan trực tiếp đến vấn đề:

Như @ mờ-johnson chỉ ra, có ít trường hợp các thông tin múi giờ nên được sử dụng. Tuy nhiên, trong trường hợp đó, ngày sinh được bảo hiểm tương ứng với múi giờ mà khách hàng của tôi đang ở. Hãy lấy ví dụ một người 17 tuổi sống ở Victoria-BC với sinh nhật của anh ấy là ngày 2 tháng 12. Nếu anh ta gửi báo giá bảo hiểm vào ngày 1 tháng 12 lúc 22:00, mặc dù anh ta vẫn 17 tuổi, báo giá bảo hiểm sẽ được chấp nhận vì anh ấy đã 18 trong múi giờ của khách hàng của tôi. Quy tắc tương tự cũng áp dụng cho việc định giá bảo hiểm. Đó là một yêu cầu pháp lý.

Chỉ cần rõ ràng, tôi đang tìm giải pháp toàn diện, có thể áp dụng cho bất kỳ dự án nào khác. Vấn đề cụ thể là: Javascript, chỉ trong vài tuần cụ thể trong nhiều năm trước năm 2007, báo cáo thời gian có chênh lệch 1 giờ. Sự bù đắp này luôn luôn ở đó bất kể tôi đại diện cho thời gian (múi giờ địa phương hay UTC) như thế nào, bởi vì đó là dữ liệu cơ bản (không phải là biểu diễn dữ liệu) sai.

Tôi đã sử dụng giải pháp "đặt thời gian vào buổi trưa" để tránh lỗi, nhưng vấn đề cơ bản vẫn còn. Điều gì sẽ xảy ra nếu khách hàng của tôi yêu cầu chúng tôi phát triển một ứng dụng web yêu cầu chúng tôi nhận được ngày VÀ thời gian của một sự kiện cụ thể trong quá khứ? Ví dụ: "Vui lòng nêu rõ ngày và giờ chính xác xảy ra tai nạn: ngày 31 tháng 10 năm 2005 lúc 19:32". Bộ điều khiển MVC sẽ đặt thời gian lúc 18:32.

+0

Có thể loại bỏ hoàn toàn múi giờ khỏi phương trình không? Chỉ cần sử dụng UTC cho ngày/giờ? Nếu bạn phải theo dõi thời gian trong tương lai, hãy dịch chúng sang UTC để lưu trữ trong cơ sở dữ liệu và khi bạn cần xử lý tính toán thời gian, sau đó xử lý hiển thị giao diện người dùng trong múi giờ chính xác. –

+0

Nhận xét hay, @StevenV. Đó là suy nghĩ đầu tiên của tôi, nhưng ngay cả ngày UTC cũng không được báo cáo chính xác trong JavaScript cho những ngày đó.Vấn đề bắt nguồn từ một sai lệch múi giờ sai trong đối tượng Date Date, vì vậy nó vẫn có mặt khi áp dụng cho biểu diễn UTC. Ngoài ra, chúng tôi phải duy trì múi giờ của khách hàng, bởi vì công ty là quốc gia và có các quy tắc kinh doanh yêu cầu chúng tôi so sánh múi giờ của người được bảo hiểm với khách hàng của tôi. – MartinVeronneau

+0

"chúng tôi phải duy trì múi giờ của khách hàng", bạn có nghĩa là khách hàng nào? Nếu bạn đang nói về cá nhân được bảo hiểm, bạn nhận được dữ liệu đó về cá nhân như thế nào? Nếu không, bạn chỉ nhận được múi giờ của trình duyệt/hệ thống, có thể dễ dàng được sửa đổi/thay đổi. Ngoài ra, sự kỳ quặc trong (các) thử nghiệm của bạn, tham số 'month' trong hàm tạo' Date() 'là 0. Tháng 9 là ngày 8 và tháng 10 là 9. –

Trả lời

4

Bạn đang bỏ qua một phần cấu trúc DateTime của .Net. Cụ thể, bạn không xem xét rằng thuộc tính .Kind của bất kỳ DateTime là một trong ba giá trị DateTimeKind có thể có. More on MSDN.

Nếu bạn đang làm việc với các giá trị UTC, thì bạn cần đặt DateTimeKind.Utc. Kể từ khi các giá trị số của JavaScript được tính theo giờ UTC dựa, sau đó bạn nên sử dụng này cho việc chuyển đổi:

DateTime dt = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc) 
         .AddMilliseconds(1162267200000); 

Ngoài ra, bạn đang làm gì đó một chút lạ mà đang sử dụng zzz formatter trên DateTimeDateTimeKind.Unspecified. Trong trường hợp này, thực sự không có bất kỳ múi giờ nào được liên kết với giá trị, nhưng.Net sẽ cho rằng bạn muốn nó hoạt động như thể nó là DateTimeKind.Local, vì định dạng zzz không có ý nghĩa khác.

Bạn thực sự cần phải cẩn thận để không sử dụng DateTimeKind.Local giá trị (như DateTime.Now) hoặc bất cứ điều gì nơi các loại không xác định có thể bị hiểu sai như địa phương (như zzz hoặc .ToUniversalTime()). "Địa phương" trong ngữ cảnh này sẽ là cục bộ cho máy chủ của bạn, hiếm khi có liên quan trong ứng dụng web. Read more here.

Về triển khai DST của JavaScript, tôi đã thực hiện các quan sát tương tự về thông số ECMAScript. You can read more on that here.

Bây giờ với tất cả điều đó đã nói, bạn nhấn vào một điểm rất cụ thể, đó không phải là JavaScript hoặc .Net cung cấp một loại đại diện cho một ngày không có thời gian. Cả hai đều có cách tiếp cận của việc thiết lập thời gian đến nửa đêm. Nếu bạn không cẩn thận, bạn có thể gặp sự cố với các ngày địa phương không có nửa đêm, chẳng hạn như October 20th, 2013 in Brazil. Nếu bạn bị giới hạn ở Hoa Kỳ, thì bạn có thể không có vấn đề cụ thể đó, nhưng nó vẫn có liên quan từ quan điểm thiết kế.

Cách tiếp cận tốt nhất là sử dụng loại "ngày không có thời gian" thực sự. Trong .Net, bạn có thể tìm thấy số này là LocalDate trong thư viện Noda Time. Là một chuỗi ISO8601, nó sẽ trông giống như "2013-10-31", không có bất kỳ thời gian hoặc múi giờ nào.

Nếu bạn không muốn sử dụng Thời gian Noda (mặc dù tôi khuyên bạn nên sử dụng nó), bạn vẫn có thể sử dụng loại DateTime .Net, hãy cẩn thận để không bao giờ sử dụng phần thời gian cho bất kỳ thứ gì. Nó phải có DateTimeKind.Unspecified.

Thật không may, hiện không có loại gốc trong JavaScript cho một ngày không có thời gian. (Lớp JavaScript Date có lẽ đã được đặt tên là DateTime). Vì vậy, bạn sẽ cần phải tự xây dựng chuỗi chỉ ngày. Bạn có thể lắp ráp nó từ một Date bằng tay, chẳng hạn như:

var s = dt.getFullYear() + "-" + 
    (dt.getMonth() < 10 ? "0" : "") + dt.getMonth() + "-" + 
    (dt.getDate() < 10 ? "0" : "") + dt.getDate(); 

Nhưng cách dễ dàng hơn là sử dụng moment.js thư viện:

var s = moment(dt).format("YYYY-MM-DD"); 

Tôi chỉ cung cấp quan điểm của tôi là đến "cách tiếp cận tốt nhất ". Nhưng nếu bạn chỉ muốn biết làm thế nào để ngăn chặn sự thất bại với những gì bạn đang làm, sau đó bạn chỉ nên cẩn thận không chuyển đổi sang UTC trong phản ứng JavaScript của bạn. Bạn đang làm điều đó với .toISOString(). Một lần nữa, bạn có thể lắp ráp toàn bộ ngày giờ theo cách thủ công hoặc bạn có thể sử dụng thời điểm để định dạng phản hồi theo thời gian địa phương.

Nếu bạn thực sự nghĩ về điều đó, ngày sinh không thực sự có múi giờ hoặc múi giờ liên kết với nó. Hầu hết mọi người không theo dõi giờ/phút/giây ngày sinh của họ (hoặc múi giờ của vị trí sinh của họ) khi họ đang tìm ra tuổi của họ. Bạn có thể làm điều này cho chiêm tinh học, nhưng đối với việc sử dụng kinh doanh hàng ngày, bạn sẽ chỉ sử dụng ngày bắt đầu tại múi giờ địa phương nơi bạn đang đánh giá tuổi. Vì vậy, ngày thực sự là phần dữ liệu quan trọng duy nhất.

+0

+1 bởi vì điều này chứa rất nhiều thông tin hữu ích về cấu trúc DateTime, tất cả ở một nơi thuận tiện. - Thật vậy, các ví dụ của tôi chỉ là các đoạn cho thấy có một vấn đề thực tế. Đối với ứng dụng web đó, tôi cần thông tin múi giờ vì đây là một phần thông tin quan trọng cần thiết cho logic nghiệp vụ. Mã thực sự quản lý DateTimeKind và tôi chuyển đổi tất cả DateTime thành UTC nội bộ. Dù sao, phần múi giờ của một DateTime chỉ là một bù đắp cho thời gian UTC. Trong trường hợp này, bù đắp là sai, vì vậy không có vấn đề làm thế nào tôi lưu trữ nó, thông tin là không chính xác. Tôi sẽ cập nhật câu hỏi của mình. – MartinVeronneau

+0

Về câu hỏi được cập nhật của bạn, câu hỏi đó có được xác định ở đâu đó trong các yêu cầu pháp lý hoặc doanh nghiệp của bạn mà tuổi của khách hàng được đánh giá ở vị trí của họ không? Thời gian thực sự nhạy cảm phải không? Tôi nghĩ rằng toàn bộ ứng dụng sẽ được giữ cho đến khi bắt đầu ngày làm việc tiếp theo. (Chỉ cần đoán). –

+1

Ngoài ra, bạn đã có một điểm về thời gian của một sự cố, mà chắc chắn * nên * có múi giờ chính xác gắn liền với nó. Bối cảnh là rất quan trọng khi giao dịch với ngày tháng và thời gian. Ví dụ, nếu bạn quyết định sử dụng Noda Time, bạn sẽ lưu trữ ngày sinh như một giá trị 'LocalDate', trong khi một sự cố có thể là một' ZonedDateTime'. –

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