Cách bỏ qua múi giờ của người dùng và buộc Ngày () sử dụng múi giờ cụ thể


104

Trong một ứng dụng JS, tôi nhận được dấu thời gian (eq. 1270544790922) Từ máy chủ (Ajax).

Dựa trên dấu thời gian đó, tôi tạo Dateđối tượng bằng cách sử dụng:

var _date = new Date();
_date.setTime(1270544790922);

Bây giờ, _datedấu thời gian được giải mã theo múi giờ địa phương của người dùng hiện tại. Tôi không muốn điều đó.

Tôi muốn _ datechuyển đổi dấu thời gian này thành thời gian hiện tại ở thành phố Helsinki ở Châu Âu (không tính đến múi giờ hiện tại của người dùng).

Làm thế nào tôi có thể làm điều đó?


Tôi biết rằng chênh lệch múi giờ ở Helsinki là +2 vào mùa đông và +3 vào DST. Nhưng ai biết DST là khi nào? Chỉ một số cơ chế ngôn ngữ không có trong JS
warpech

Có thể, nhưng không sử dụng các phương pháp gốc của Javascript, vì javascript không có phương pháp nào để xác định lịch sử chuyển đổi múi giờ của múi giờ khác với múi giờ hiện tại của hệ thống của người dùng (và nó phụ thuộc vào trình duyệt ít nhất là khi chúng ta đi đến ngày của năm 80). Nhưng theo cách này thì hoàn toàn có thể: stackoverflow.com/a/12814213/1691517 và tôi nghĩ rằng câu trả lời của tôi mang lại cho bạn kết quả chính xác.
Timo Kähkönen

Câu trả lời:


64

Giá trị cơ bản của đối tượng Ngày thực sự là UTC. Để chứng minh điều này, hãy chú ý rằng nếu bạn gõ new Date(0)bạn sẽ thấy một cái gì đó như: Wed Dec 31 1969 16:00:00 GMT-0800 (PST). 0 được coi là 0 trong GMT, nhưng .toString()phương thức hiển thị giờ địa phương.

Lưu ý lớn, UTC là viết tắt của mã thời gian quốc tế . Giờ hiện tại ngay bây giờ ở 2 nơi khác nhau có cùng UTC, nhưng đầu ra có thể được định dạng khác nhau.

Những gì chúng ta cần ở đây là một số định dạng

var _date = new Date(1270544790922); 
// outputs > "Tue Apr 06 2010 02:06:30 GMT-0700 (PDT)", for me
_date.toLocaleString('fi-FI', { timeZone: 'Europe/Helsinki' });
// outputs > "6.4.2010 klo 12.06.30"
_date.toLocaleString('en-US', { timeZone: 'Europe/Helsinki' });
// outputs > "4/6/2010, 12:06:30 PM"

Điều này hoạt động nhưng .... bạn thực sự không thể sử dụng bất kỳ phương pháp ngày nào khác cho mục đích của mình vì chúng mô tả múi giờ của người dùng. Những gì bạn muốn là một đối tượng ngày tháng liên quan đến múi giờ Helsinki. Các tùy chọn của bạn tại thời điểm này là sử dụng một số thư viện của bên thứ 3 (tôi khuyên bạn nên sử dụng điều này) hoặc hack đối tượng ngày tháng để bạn có thể sử dụng hầu hết các phương pháp của nó.

Tùy chọn 1 - bên thứ 3 như múi giờ thời điểm

moment(1270544790922).tz('Europe/Helsinki').format('YYYY-MM-DD HH:mm:ss')
// outputs > 2010-04-06 12:06:30
moment(1270544790922).tz('Europe/Helsinki').hour()
// outputs > 12

Điều này trông thanh lịch hơn rất nhiều so với những gì chúng tôi sắp làm tiếp theo.

Tùy chọn 2 - Hack đối tượng ngày tháng

var currentHelsinkiHoursOffset = 2; // sometimes it is 3
var date = new Date(1270544790922);
var helsenkiOffset = currentHelsinkiHoursOffset*60*60000;
var userOffset = _date.getTimezoneOffset()*60000; // [min*60000 = ms]
var helsenkiTime = new Date(date.getTime()+ helsenkiOffset + userOffset);
// Outputs > Tue Apr 06 2010 12:06:30 GMT-0700 (PDT)

Nó vẫn nghĩ đó là GMT-0700 (PDT), nhưng nếu bạn không nhìn chăm chú quá, bạn có thể nhầm nó với một đối tượng ngày tháng hữu ích cho mục đích của bạn.

Tôi tiện lợi bỏ qua một phần. Bạn cần phải có khả năng xác định currentHelsinkiOffset. Nếu bạn có thể sử dụng date.getTimezoneOffset()ở phía máy chủ hoặc chỉ sử dụng một số câu lệnh if để mô tả thời điểm xảy ra thay đổi múi giờ, điều đó sẽ giải quyết được vấn đề của bạn.

Kết luận - Tôi nghĩ đặc biệt cho mục đích này, bạn nên sử dụng thư viện ngày tháng như múi giờ thời điểm .


cũng ... một nhiệm vụ tương tự có thể được thực hiện, chỉ bằng cách sử dụng gmt offset từ một vị trí. Bạn không cần javascript trong trường hợp đó.
Parris

xin lỗi nhưng không, ý tôi là hoàn toàn ngược lại :) Tôi đã chỉnh sửa câu hỏi, có lẽ bây giờ nó rõ ràng hơn
warpech

Ok, tôi đã thay đổi giải pháp của mình. Tôi nghĩ rằng đây là những gì bạn đang tìm kiếm.
Parris

Thật không may, đó là điều duy nhất mà tôi nghĩ ra. Tôi nghĩ rằng có thể trình duyệt có thể tạo "_helsinkiOffset" cho tôi.
warpech

2
Tôi tin rằng *60*60thay vì nên *60000, như getTime là bằng mili giây và getTimezoneOffset là trong vài phút, trong đó có 60000 mili giây trong một phút, không 60 * 60 == 3600
AaronLS

20

Để tính theo mili giây và múi giờ của người dùng, hãy sử dụng như sau:

var _userOffset = _date.getTimezoneOffset()*60*1000; // user's offset time
var _centralOffset = 6*60*60*1000; // 6 for central time - use whatever you need
_date = new Date(_date.getTime() - _userOffset + _centralOffset); // redefine variable

Để thay thế bằng cách sử dụng độ lệch cố định cho trung tâm, tôi đã sử dụng khái niệm tạo ngày bằng CST với thời gian cố định là 00:00, sau đó getUTCHHours của ngày đó.
Grantwparks,

+1 Điều này đã làm việc cho tôi. Không chắc chắn câu trả lời 'câu trả lời' có thể hoạt động như thế nào nếu không tính bằng mili giây.
Chris Wallis,

2
@Ehren bạn không nên thêm timezoneOffset để đến gmt và sau đó trừ đi phần bù trung tâm?
coder

15

Chỉ là một cách tiếp cận khác

function parseTimestamp(timestampStr) {
  return new Date(new Date(timestampStr).getTime() + (new Date(timestampStr).getTimezoneOffset() * 60 * 1000));
};

//Sun Jan 01 2017 12:00:00
var timestamp = 1483272000000;
date = parseTimestamp(timestamp);
document.write(date);

Chúc mừng!


2
Điều này có thể có kết quả không mong muốn do Giờ tiết kiệm ánh sáng ban ngày. Nếu khách hàng ở trong múi giờ sử dụng DST, kết quả phân tích cú pháp có thể bị tắt đi một giờ (một số khu vực sử dụng phân số của một giờ). Ví dụ: Người dùng đang ở NY và hôm nay là ngày 4 tháng 7. Điều đó có nghĩa là người dùng đang ở GMT -0400 (Múi giờ ban ngày miền Đông kể từ khi DST bắt đầu hoạt động); dấu thời gian được thông qua là cho ngày 30 tháng 1, là GMT-0500 (Múi giờ chuẩn miền Đông - không có DST thời điểm đó trong năm). Kết quả sẽ là một giờ nghỉ, vì getTimezoneOffset () cung cấp cho bạn mức bù ngay bây giờ, không phải là giá trị vào tháng Giêng.
Dimitar Darazhanski

2
Để khắc phục vấn đề này, bạn cần phải dành thời gian bù đắp kể từ ngày bạn đang đi qua trong (không phải là thay đổi thời gian hiện tại) : new Date().getTimezoneOffset()nên được thay đổinew Date(timestampStr).getTimezoneOffset()
Dimitar Darazhanski

13

Tôi có một nghi ngờ, rằng Câu trả lời không cho kết quả chính xác. Trong câu hỏi, người hỏi muốn chuyển đổi dấu thời gian từ máy chủ sang thời gian hiện tại ở Hellsinki bất kể múi giờ hiện tại của người dùng.

Thực tế là múi giờ của người dùng có thể là múi giờ nên chúng tôi không thể tin tưởng vào nó.

Nếu vd. dấu thời gian là 1270544790922 và chúng ta có một hàm:

var _date = new Date();
_date.setTime(1270544790922);
var _helsenkiOffset = 2*60*60;//maybe 3
var _userOffset = _date.getTimezoneOffset()*60*60; 
var _helsenkiTime = new Date(_date.getTime()+_helsenkiOffset+_userOffset);

Khi một người New York truy cập trang, cảnh báo (_helsenkiTime) sẽ in:

Tue Apr 06 2010 05:21:02 GMT-0400 (EDT)

Và khi một người Phần Lan truy cập trang, cảnh báo (_helsenkiTime) sẽ in:

Tue Apr 06 2010 11:55:50 GMT+0300 (EEST)

Vì vậy, chức năng chỉ đúng nếu khách truy cập trang có múi giờ mục tiêu (Châu Âu / Helsinki) trong máy tính của anh ta, nhưng không thành công ở hầu hết các nơi khác trên thế giới. Và bởi vì dấu thời gian của máy chủ thường là dấu thời gian UNIX, theo định nghĩa là UTC, số giây kể từ Kỷ nguyên Unix (ngày 1 tháng 1 năm 1970 00:00:00 GMT), chúng tôi không thể xác định DST hoặc không phải DST từ dấu thời gian.

Vì vậy, giải pháp là XÓA múi giờ hiện tại của người dùng và thực hiện một số cách để tính toán bù UTC cho dù ngày đó có ở DST hay không. Javascript không có phương pháp gốc để xác định lịch sử chuyển đổi DST của múi giờ khác với múi giờ hiện tại của người dùng. Chúng tôi có thể đạt được điều này đơn giản nhất bằng cách sử dụng tập lệnh phía máy chủ, vì chúng tôi có thể dễ dàng truy cập vào cơ sở dữ liệu múi giờ của máy chủ với toàn bộ lịch sử chuyển đổi của tất cả các múi giờ.

Nhưng nếu bạn không có quyền truy cập vào cơ sở dữ liệu múi giờ của máy chủ (hoặc bất kỳ máy chủ nào khác) VÀ dấu thời gian ở UTC, bạn có thể có được chức năng tương tự bằng cách mã hóa cứng các quy tắc DST trong Javascript.

Để hiển thị các ngày trong năm 1998 - 2099 ở Châu Âu / Helsinki, bạn có thể sử dụng hàm sau ( jsfiddled ):

function timestampToHellsinki(server_timestamp) {
    function pad(num) {
        num = num.toString();
        if (num.length == 1) return "0" + num;
        return num;
    }

    var _date = new Date();
    _date.setTime(server_timestamp);

    var _year = _date.getUTCFullYear();

    // Return false, if DST rules have been different than nowadays:
    if (_year<=1998 && _year>2099) return false;

    // Calculate DST start day, it is the last sunday of March
    var start_day = (31 - ((((5 * _year) / 4) + 4) % 7));
    var SUMMER_start = new Date(Date.UTC(_year, 2, start_day, 1, 0, 0));

    // Calculate DST end day, it is the last sunday of October
    var end_day = (31 - ((((5 * _year) / 4) + 1) % 7))
    var SUMMER_end = new Date(Date.UTC(_year, 9, end_day, 1, 0, 0));

    // Check if the time is between SUMMER_start and SUMMER_end
    // If the time is in summer, the offset is 2 hours
    // else offset is 3 hours
    var hellsinkiOffset = 2 * 60 * 60 * 1000;
    if (_date > SUMMER_start && _date < SUMMER_end) hellsinkiOffset = 
    3 * 60 * 60 * 1000;

    // Add server timestamp to midnight January 1, 1970
    // Add Hellsinki offset to that
    _date.setTime(server_timestamp + hellsinkiOffset);
    var hellsinkiTime = pad(_date.getUTCDate()) + "." + 
    pad(_date.getUTCMonth()) + "." + _date.getUTCFullYear() + 
    " " + pad(_date.getUTCHours()) + ":" +
    pad(_date.getUTCMinutes()) + ":" + pad(_date.getUTCSeconds());

    return hellsinkiTime;
}

Ví dụ về cách sử dụng:

var server_timestamp = 1270544790922;
document.getElementById("time").innerHTML = "The timestamp " + 
server_timestamp + " is in Hellsinki " + 
timestampToHellsinki(server_timestamp);

server_timestamp = 1349841923 * 1000;
document.getElementById("time").innerHTML += "<br><br>The timestamp " + 
server_timestamp + " is in Hellsinki " + timestampToHellsinki(server_timestamp);

var now = new Date();
server_timestamp = now.getTime();
document.getElementById("time").innerHTML += "<br><br>The timestamp is now " +
server_timestamp + " and the current local time in Hellsinki is " +
timestampToHellsinki(server_timestamp);​

Và điều này sẽ in như sau bất kể múi giờ của người dùng:

The timestamp 1270544790922 is in Hellsinki 06.03.2010 12:06:30

The timestamp 1349841923000 is in Hellsinki 10.09.2012 07:05:23

The timestamp is now 1349853751034 and the current local time in Hellsinki is 10.09.2012 10:22:31

Tất nhiên nếu bạn có thể trả về dấu thời gian ở dạng mà phần bù (DST hoặc không phải DST) đã được thêm vào dấu thời gian trên máy chủ, bạn không phải tính toán nó ở phía máy khách và bạn có thể đơn giản hóa hàm rất nhiều. NHƯNG nhớ KHÔNG sử dụng timezoneOffset (), vì khi đó bạn phải xử lý múi giờ của người dùng và đây không phải là hành vi mong muốn.


2
nb. Helsinki chỉ có một chữ 'l'. Sai lầm này thực sự làm mất đi câu trả lời này.
Ben McIntyre

@BenMcIntyre Đó là một trò đùa nhỏ. Hoặc được cho là một trong những như vậy. :)
Timo Kähkönen

ah, không bao giờ viết mã riêng thực hiện thời gian / múi giờ của bạn ... nhưng tôi quá lười biếng để -1
mb21

3

Giả sử bạn nhận được dấu thời gian theo giờ Helsinki, tôi sẽ tạo một đối tượng ngày được đặt thành nửa đêm ngày 1 tháng 1 năm 1970 UTC (vì bỏ qua cài đặt múi giờ địa phương của trình duyệt). Sau đó, chỉ cần thêm số mili giây cần thiết vào đó.

var _date	= new Date( Date.UTC(1970, 0, 1, 0, 0, 0, 0) );
_date.setUTCMilliseconds(1270544790922);

alert(_date); //date shown shifted corresponding to local time settings
alert(_date.getUTCFullYear());    //the UTC year value
alert(_date.getUTCMonth());       //the UTC month value
alert(_date.getUTCDate());        //the UTC day of month value
alert(_date.getUTCHours());       //the UTC hour value
alert(_date.getUTCMinutes());     //the UTC minutes value

Để ý sau, để luôn hỏi các giá trị UTC từ đối tượng ngày tháng. Bằng cách này, người dùng sẽ thấy các giá trị ngày giống nhau bất kể cài đặt cục bộ. Nếu không, giá trị ngày sẽ được thay đổi tương ứng với cài đặt giờ địa phương.


0

Bạn đã có thể sử dụng setUTCMilliseconds()

var _date = new Date();
_date.setUTCMilliseconds(1270544790922);

Câu trả lời này là sai. setUTCMilliseconds thêm số mili giây được chỉ định vào Ngày.
Tibor

@Tibor: Từ MozDev: setUTCMilliseconds()Phương thức đặt mili giây cho một ngày cụ thể theo giờ quốc tế. [...] Nếu một tham số bạn chỉ định nằm ngoài phạm vi mong đợi, hãy setUTCMilliseconds()cố gắng cập nhật thông tin ngày trong đối tượng Ngày cho phù hợp. Nói cách khác, bạn tạo một Dateđối tượng có dấu thời gian Unix đã cho, tại UTC.
jimasun

Từ w3schools: Phương thức setUTCMilliseconds () đặt mili giây (từ 0 đến 999), theo thời gian quốc tế. Hãy thử câu trả lời ở trên và xem cho chính mình.
Tibor

Từ MDN: Tham số mili giây Giá trị: Một số từ 0 đến 999, đại diện cho mili giây ...
Tibor

Ví dụ của bạn không hoạt động như mong đợi, vì Date () mới tạo một đối tượng ngày với ngày cục bộ hiện tại. Và sau đó thêm số mili giây được chỉ định cho nó. Vì vậy, trong ví dụ của bạn nếu ngày địa phương hiện tại là 2017-05-04, ngày kết quả sẽ ở đâu đó sau năm 4027 ...
Tibor
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.