วิธีละเว้นเขตเวลาของผู้ใช้และบังคับให้ Date () ใช้เขตเวลาเฉพาะ


104

ในแอป JS ฉันได้รับการประทับเวลา (eq. 1270544790922) จากเซิร์ฟเวอร์ (Ajax)

จากการประทับเวลานั้นฉันสร้างDateวัตถุโดยใช้:

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

ตอนนี้การ_dateประทับเวลาถอดรหัสในเขตเวลาของตำแหน่งที่ตั้งของผู้ใช้ปัจจุบัน ฉันไม่ต้องการสิ่งนั้น

ฉันต้องการ _ dateเพื่อแปลงการประทับเวลานี้เป็นเวลาปัจจุบันในเมืองเฮลซิงกิในยุโรป (โดยไม่คำนึงถึงเขตเวลาปัจจุบันของผู้ใช้)

ฉันจะทำเช่นนั้นได้อย่างไร?


ฉันรู้ว่าเขตเวลาชดเชยในเฮลซิงกิคือ +2 ในฤดูหนาวและ +3 ใน DST แต่ใครจะรู้ว่า DST คือเมื่อไหร่? มีเพียงกลไกบางภาษาที่ไม่มีใน JS
warpech

เป็นไปได้ แต่จะไม่ใช้วิธีดั้งเดิมของ Javascript เนื่องจาก javascript ไม่มีวิธีการในการกำหนดประวัติการเปลี่ยนเขตเวลาของเขตเวลาอื่นนอกเหนือจากเขตเวลาปัจจุบันของระบบของผู้ใช้ (และโดยเบราว์เซอร์จะขึ้นอยู่กับอย่างน้อยเมื่อเราไปที่วันที่ 80) แต่วิธีนี้เป็นไปได้: stackoverflow.com/a/12814213/1691517และฉันคิดว่าคำตอบของฉันให้ผลลัพธ์ที่ถูกต้อง
Timo Kähkönen

คำตอบ:


64

ค่าพื้นฐานของอ็อบเจ็กต์ Date อยู่ใน UTC เพื่อพิสูจน์สิ่งนี้โปรดสังเกตว่าหากคุณพิมพ์new Date(0)คุณจะเห็นสิ่งWed Dec 31 1969 16:00:00 GMT-0800 (PST)ต่อไปนี้: 0 จะถือว่าเป็น 0 ใน GMT แต่.toString()วิธีการแสดงเวลาท้องถิ่น

หมายเหตุใหญ่ UTC ย่อมาจากรหัสเวลาสากล เวลาปัจจุบันใน 2 ที่ที่ต่างกันคือ UTC เดียวกัน แต่ผลลัพธ์สามารถจัดรูปแบบต่างกันได้

สิ่งที่เราต้องการนี่คือการจัดรูปแบบบางอย่าง

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"

วิธีนี้ใช้งานได้ แต่ .... คุณไม่สามารถใช้วิธีการวันที่อื่น ๆ ตามวัตถุประสงค์ของคุณได้เนื่องจากอธิบายถึงเขตเวลาของผู้ใช้ สิ่งที่คุณต้องการคือวัตถุวันที่ที่เกี่ยวข้องกับเขตเวลาของเฮลซิงกิ ตัวเลือกของคุณ ณ จุดนี้คือการใช้ไลบรารีของบุคคลที่สาม (ฉันแนะนำสิ่งนี้) หรือแฮ็กวัตถุวันที่เพื่อให้คุณสามารถใช้วิธีการส่วนใหญ่ได้

ตัวเลือกที่ 1 - บุคคลที่สามเช่นเขตเวลาช่วงเวลา

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

สิ่งนี้ดูหรูหรากว่าสิ่งที่เรากำลังจะทำต่อไป

ตัวเลือกที่ 2 - แฮ็กวัตถุวันที่

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)

ยังคงคิดว่าเป็น GMT-0700 (PDT) แต่ถ้าคุณไม่จ้องมองหนักเกินไปคุณอาจเข้าใจผิดว่าเป็นวัตถุวันที่ที่มีประโยชน์สำหรับวัตถุประสงค์ของคุณ

ฉันข้ามส่วนหนึ่งไปโดยสะดวก คุณต้องสามารถกำหนดcurrentHelsinkiOffsetได้ หากคุณสามารถใช้date.getTimezoneOffset()ในฝั่งเซิร์ฟเวอร์หรือใช้คำสั่ง if เพื่ออธิบายว่าจะเกิดการเปลี่ยนแปลงโซนเวลาเมื่อใดนั่นจะช่วยแก้ปัญหาของคุณได้

สรุป - ผมคิดว่าโดยเฉพาะอย่างยิ่งเพื่อการนี้คุณควรใช้ห้องสมุดวันเช่นขณะ-เขตเวลา


นอกจากนี้ ... งานที่คล้ายกันสามารถทำได้โดยใช้ gmt offset จากที่เดียว คุณไม่จำเป็นต้องใช้จาวาสคริปต์เลยในกรณีนั้น
Parris

ขอโทษ แต่ไม่ฉันหมายถึงสิ่งที่ตรงกันข้าม :) ฉันแก้ไขคำถามแล้วอาจจะชัดเจนกว่านี้
warpech

ตกลงฉันเปลี่ยนวิธีแก้ปัญหาแล้ว ฉันคิดว่านี่คือสิ่งที่คุณกำลังมองหา
Parris

น่าเสียดายที่นั่นเป็นสิ่งเดียวที่ฉันคิดขึ้นมา ฉันคิดว่าเบราว์เซอร์อาจสร้าง "_helsinkiOffset" ให้ฉัน
warpech

2
ฉันเชื่อว่า*60*60ควรจะ*60000เป็นเช่นนั้นเนื่องจาก getTime อยู่ในหน่วยมิลลิวินาทีและ getTimezoneOffset เป็นนาทีซึ่งมี 60000 มิลลิวินาทีต่อนาทีไม่ใช่ 60 * 60 == 3600
AaronLS

21

หากต้องการคำนวณมิลลิวินาทีและเขตเวลาของผู้ใช้ให้ใช้สิ่งต่อไปนี้:

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

เพื่อแทนที่โดยใช้ค่าชดเชยคงที่สำหรับส่วนกลางฉันใช้แนวคิดในการสร้างวันที่โดยใช้ CST ด้วยเวลาคงที่คือ 00:00 น. จากนั้นรับ UTCHHours ของวันที่นั้น
Grantwparks

+1 สิ่งนี้ได้ผลสำหรับฉัน ไม่แน่ใจว่าคำตอบของ 'คำตอบ' สามารถทำงานได้อย่างไรโดยไม่ต้องจัดการเป็นมิลลิวินาที
Chris Wallis

2
@ คุณไม่ควรเพิ่ม timezoneOffset เพื่อไปที่ gmt แล้วลบค่าชดเชยกลาง?
coder

16

อีกวิธีหนึ่ง

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);

ไชโย!


2
ซึ่งอาจมีผลลัพธ์ที่ไม่ได้ตั้งใจเนื่องจากเวลาออมแสง หากไคลเอ็นต์อยู่ในเขตเวลาที่ใช้ DST ผลลัพธ์ของการแยกวิเคราะห์อาจปิดลงหนึ่งชั่วโมง (บางโซนใช้เศษส่วนของชั่วโมง) เช่น: ผู้ใช้อยู่ในนิวยอร์กและวันนี้คือวันที่ 4 กรกฎาคม นั่นหมายความว่าผู้ใช้อยู่ใน GMT -0400 (เขตเวลาออมแสงตะวันออกนับตั้งแต่ DST เริ่ม) การประทับเวลาที่ผ่านคือวันที่ 30 มกราคมซึ่งก็คือ GMT-0500 (เขตเวลามาตรฐานตะวันออก - ไม่มี DST ในช่วงเวลานั้นของปี) ผลลัพธ์จะปิดหนึ่งชั่วโมงเนื่องจาก getTimezoneOffset () ให้ค่าชดเชยแก่คุณในตอนนี้ไม่ใช่สิ่งที่เป็นในเดือนมกราคม
Dimitar Darazhanski

2
ในการแก้ไขปัญหานี้คุณต้องใช้เวลาชดเชยของวันที่ที่คุณกำลังจะผ่านไป (ไม่ใช่กะเวลาปัจจุบัน) : new Date().getTimezoneOffset()ควรเปลี่ยนเป็นnew Date(timestampStr).getTimezoneOffset()
Dimitar Darazhanski

13

ฉันมีข้อสงสัยว่าคำตอบไม่ได้ให้ผลลัพธ์ที่ถูกต้อง ในคำถามผู้ถามต้องการแปลงการประทับเวลาจากเซิร์ฟเวอร์เป็นเวลาปัจจุบันใน Hellsinki โดยไม่คำนึงถึงเขตเวลาปัจจุบันของผู้ใช้

เป็นความจริงที่ว่าเขตเวลาของผู้ใช้อาจเป็นสิ่งที่เราไม่สามารถเชื่อถือได้

ถ้าเช่น. การประทับเวลาคือ 1270544790922 และเรามีฟังก์ชัน:

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);

เมื่อชาวนิวยอร์กเข้าชมหน้าการแจ้งเตือน (_helsenkiTime) จะพิมพ์:

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

และเมื่อชาวฟินแลนด์เข้าชมหน้าการแจ้งเตือน (_helsenkiTime) จะพิมพ์:

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

ดังนั้นฟังก์ชันจะถูกต้องก็ต่อเมื่อผู้เยี่ยมชมเพจมีเขตเวลาเป้าหมาย (ยุโรป / เฮลซิงกิ) ในคอมพิวเตอร์ของเขา แต่ล้มเหลวในเกือบทุกส่วนของโลก และเนื่องจากการประทับเวลาของเซิร์ฟเวอร์มักจะเป็นการประทับเวลา UNIX ซึ่งตามคำจำกัดความใน UTC จำนวนวินาทีนับตั้งแต่ Unix Epoch (1 มกราคม 1970 00:00:00 GMT) เราจึงไม่สามารถระบุ DST หรือไม่ใช่ DST จากการประทับเวลาได้

ดังนั้นวิธีแก้ปัญหาคือการยกเลิกเขตเวลาปัจจุบันของผู้ใช้และใช้วิธีคำนวณค่าชดเชย UTC ว่าวันที่อยู่ใน DST หรือไม่ Javascript ไม่มีวิธีดั้งเดิมในการกำหนดประวัติการเปลี่ยน DST ของเขตเวลาอื่นที่ไม่ใช่เขตเวลาปัจจุบันของผู้ใช้ เราสามารถบรรลุสิ่งนี้ได้อย่างง่ายดายที่สุดโดยใช้สคริปต์ฝั่งเซิร์ฟเวอร์เพราะเราสามารถเข้าถึงฐานข้อมูลเขตเวลาของเซิร์ฟเวอร์ได้อย่างง่ายดายพร้อมด้วยประวัติการเปลี่ยนแปลงทั้งหมดของเขตเวลาทั้งหมด

แต่ถ้าคุณไม่สามารถเข้าถึงฐานข้อมูลเขตเวลาของเซิร์ฟเวอร์ (หรือเซิร์ฟเวอร์อื่น ๆ ) ได้และการประทับเวลาอยู่ใน UTC คุณสามารถรับฟังก์ชันที่คล้ายกันได้โดยการเข้ารหัสกฎ DST ใน Javascript

เพื่อให้ครอบคลุมวันที่ในปี 1998-2099 ในยุโรป / เฮลซิงกิคุณสามารถใช้ฟังก์ชันต่อไปนี้ ( 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;
}

ตัวอย่างการใช้งาน:

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);​

และสิ่งนี้จะพิมพ์สิ่งต่อไปนี้โดยไม่คำนึงถึงเขตเวลาของผู้ใช้:

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

แน่นอนว่าหากคุณสามารถส่งคืนการประทับเวลาในรูปแบบที่มีการเพิ่มออฟเซ็ต (DST หรือไม่ใช่ DST หนึ่ง) ลงในการประทับเวลาบนเซิร์ฟเวอร์แล้วคุณไม่จำเป็นต้องคำนวณไคลเอนต์ไซด์และคุณสามารถลดความซับซ้อนของฟังก์ชันได้มาก แต่อย่าลืมอย่าใช้ timezoneOffset () เพราะคุณต้องจัดการกับเขตเวลาของผู้ใช้และนี่ไม่ใช่พฤติกรรมที่ต้องการ


2
nb. เฮลซิงกิมี 'l' เพียงตัวเดียว ความผิดพลาดนี้ทำให้เกิดผลเสียจากคำตอบนี้จริงๆ
Ben McIntyre

@BenMcIntyre มันเป็นเรื่องตลกเล็กน้อย หรือควรจะเป็นเช่นนั้น :)
Timo Kähkönen

อาไม่เคยเขียนโค้ดการใช้งานเขตเวลา / เขตเวลาของคุณเอง ... แต่ฉันขี้เกียจเกินไปที่จะ -1
mb21

3

สมมติว่าคุณได้รับการประทับเวลาในเวลาเฮลซิงกิฉันจะสร้างวัตถุวันที่ตั้งค่าเป็นเที่ยงคืน 1 มกราคม 1970 UTC (สำหรับการไม่คำนึงถึงการตั้งค่าเขตเวลาท้องถิ่นของเบราว์เซอร์) จากนั้นเพิ่มจำนวนมิลลิวินาทีที่ต้องการลงไป

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

ระวังในภายหลังเพื่อถามค่า UTC จากวัตถุวันที่เสมอ วิธีนี้ผู้ใช้จะเห็นค่าวันที่เดียวกันโดยไม่คำนึงถึงการตั้งค่าท้องถิ่น มิฉะนั้นค่าวันที่จะเปลี่ยนไปตามการตั้งค่าเวลาท้องถิ่น


0

คุณสามารถใช้ setUTCMilliseconds()

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

คำตอบนี้ผิด setUTCMilliseconds เพิ่มจำนวนมิลลิวินาทีที่ระบุเป็นวันที่
Tibor

@Tibor: จาก MozDev: setUTCMilliseconds()วิธีการตั้งค่ามิลลิวินาทีสำหรับวันที่ที่ระบุตามเวลาสากล [... ] หากพารามิเตอร์ที่คุณระบุอยู่นอกช่วงที่คาดไว้ให้setUTCMilliseconds()พยายามอัปเดตข้อมูลวันที่ในออบเจ็กต์ Date ให้สอดคล้องกัน กล่าวอีกนัยหนึ่งคือคุณสร้างDateวัตถุที่มีการประทับเวลา Unix ที่กำหนดในเวลา UTC
jimasun

จาก w3schools: วิธี setUTCMilliseconds () ตั้งค่ามิลลิวินาที (จาก 0 ถึง 999) ตามเวลาสากล โปรดลองทำตามคำตอบด้านบนและดูด้วยตัวคุณเอง
Tibor

จาก MDN: พารามิเตอร์มิลลิวินาทีค่า: ตัวเลขระหว่าง 0 ถึง 999 คิดเป็นมิลลิวินาที ...
Tibor

ตัวอย่างของคุณไม่ทำงานตามที่คาดไว้เนื่องจาก Date ใหม่ () สร้างออบเจ็กต์วันที่ที่มีวันที่ในเครื่องปัจจุบัน และหลังจากนั้นเพิ่มจำนวนมิลลิวินาทีที่ระบุลงไป ดังนั้นในตัวอย่างของคุณหากวันที่ในท้องถิ่นปัจจุบันคือ 2017-05-04 วันที่ผลลัพธ์จะอยู่ที่ใดที่หนึ่งหลังจากปี 4027 ...
Tibor
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.