Я думаю, вы пытаетесь создать временную метку UT C на основе временной метки в другом часовом поясе и с учетом репрезентативного местоположения IANA.
Один из способов сделать это - сгенерировать Дата для местоположения, используя метку времени как UT C, и посмотрите, какова разница в часах и минутах. Это должно быть смещение. Затем примените смещение к исходной отметке времени. Затем следует сгенерировать временную метку с требуемой датой и временем для целевого местоположения, когда применяется смещение часового пояса.
Следующая функция реализует этот алгоритм с использованием Intl.DateTimeFormat и репрезентативного местоположения IANA. Это только слегка протестировано, поэтому, пожалуйста, тестируйте дальше. Для корректировок выполняется oop, чтобы проверить, что применение смещения не перемещает дату за границу летнего времени и, следовательно, необходимо перенастроить смещение.
По умолчанию возвращается ISO 8601 UT C отметка времени для эквивалентного времени, если для необязательного параметра returnOffset установлено значение true
, вместо этого возвращается смещение.
Я предоставил примеры стандартных и летних меток времени как для восточной, так и для восточной к западу от GMT (но не так много). Пожалуйста, тщательно проверьте и измените при необходимости.
/* @param {string} isoString - ISO 8601 timestamp without timezone
** e.g. YYYY-MM-DDTHH:mm:ss or YYYY-MM-DD HH:mm:ss
** @param {string} loc - IANA representateive location
** e.g. Europe/Berlin
** @param {boolean} returnOffset - if true, return the offset instead of timestamp
** @returns {string} if returnOffset is true, offset is ±HH:mm[:ss] (seconds only if not zero)
** if returnOffset is false, equivalent ISO 8601 UTC timestamp
*/
let getUTCTime = (function() {
let n = 'numeric';
let formatterOpts = {year:n, month:n, day:n, hour:n, minute:n, second:n, hour12: false};
// Parse YYYY-MM-DDTHH:mm:ss as UTC (T can be space)
function parse (isoString) {
let [Y,M,D,H,m,s] = isoString.split(/[\DT\s]/);
return new Date(Date.UTC(Y,M-1,D,H,m,s));
}
// Get date parts, use supplied formatter
function toParts(date, formatter) {
return formatter.formatToParts(date).reduce((acc, part) => {
acc[part.type] = part.value;
return acc;
}, Object.create(null));
}
return function (isoString, loc, returnOffset = false) {
// Update formatter options with loc so get target loc values
formatterOpts.timeZone = loc;
// Formatter
let formatter = new Intl.DateTimeFormat('en', formatterOpts);
// Parse input string as UTC
let oDate = parse(isoString);
// Date to adjust to wanted values
let utcDate = new Date(oDate);
// maxLoops limits do..while in dilemma zone, ensures sensible value
let maxLoops = 3,
p, diff;
// Adjust utcDate so it generates required local date values
// Adjustment may shift over DST boundary so may need 2nd adjustment
// Limit number of loops (shouldn't be required but just in case...)
do {
// Get date parts in target timezone
p = toParts(utcDate, formatter);
// Get difference between local and adjusted values
diff = new Date(Date.UTC(p.year, p.month-1, p.day, p.hour, p.minute, p.second)) - oDate;
// If necessary, adjust utcDate so it generates required values when shifted
if (diff) {
utcDate.setTime(utcDate.getTime() - diff);
}
// Loop until generated values match original or maxLoops
} while (diff && maxLoops--)
// If maxLoops is -1, hit DST dilemma zone: time doesn't exist on that date
// E.g. going into daylight saving at 02:00, then 02:30 doesn't exist
// and loop will flip in/out of DST until stopped by maxLoops
// So generate valid date and offset in DST period
let dDiff = null;
if (maxLoops < 0) {
p = toParts(utcDate, formatter);
dDiff = Date.UTC(p.year, p.month - 1, p.day, p.hour, p.minute, p.second) - utcDate;
let msg = isoString + ' does not exist at ' + loc + ' due to ' +
'daylight saving change-over, shifting into DST';
// console.log(msg);
// throw new RangeError(msg);
}
// Convert diff between local and adjusted to get ±HH:mm offset
// Use dilemma diff (dDiff) if has been set
let oDiff = dDiff || oDate - utcDate;
let sign = oDiff > 0? '+' : '-';
oDiff = Math.abs(oDiff);
// console.log(sign + new Date(oDiff).toISOString().substring(11,19).replace(/:00$/,''));
let offH = oDiff / 3.6e6 | 0;
let offM = (oDiff % 3.6e6) / 6e4 | 0;
let offS = (oDiff % 6e4) / 1e3 | 0;
let z = n=>(n<10?'0':'')+n;
// Return offset (with offset seconds if not zero) or ISO 8601 UTC string
return returnOffset? `${sign}${z(offH)}:${z(offM)}${offS? ':' + z(offS) : ''}` :
utcDate.toISOString();
}
})();
// Given a local timestmap in format YYYY-MM-DDTHH:mm:ss and
// loc as IANA representative location
// Return equivalent ISO 8061 UTC timestmap
function getUTCString(timestamp, loc) {
return getUTCTime(timestamp, loc);
}
// Given a local timestmap in format YYYY-MM-DDTHH:mm:ss and
// loc as IANA representative location
// Return offset at loc as ±HH:mm[:ss]
// - seconds only included if not zero (typically pre-1900)
function getUTCOffset(timestamp, loc) {
return getUTCTime(timestamp, loc, true);
}
// Examples
window.onload = function() {
let t = document.getElementById('t0');
let params = ['Local time', 'UTC time', false];
let showData = (localTime, loc, offset, timeUTC) => {
let row = t.insertRow();
[localTime, loc, timeUTC, null, null].forEach((s, i) => {
cell = row.insertCell();
cell.textContent = i == 0? localTime.replace('T',' '):
i == 1? loc:
i == 2? offset :
i == 3? timeUTC :
new Date(timeUTC).toLocaleString('en-CA', {timeZone: loc, hour12: false}).replace(',','') ;
});
return new Date(timeUTC).toLocaleString('en-CA', {timeZone: loc, hour12: false}).replace(',','');
};
// Local time required Location
[['2020-04-22T09:00:00','Europe/Berlin'], // DST offset +2
['2020-01-22T09:00:00','Europe/Berlin'], // Std offset +1
['2020-04-22T09:00:00','America/Denver'], // DST offset -6
['2020-01-22T09:00:00','America/Denver'], // Std offset -7
['2020-04-22T09:00:00','US/Aleutian'], // DST offset -9
['2020-01-22T09:00:00','US/Aleutian'], // Std offset -10
['2020-01-22T09:00:00','Pacific/Honolulu'],// Std offset -11
['2020-01-22T19:00:00','Pacific/Honolulu'],// Std offset -11
['2020-04-22T09:00:00','Asia/Singapore'], // Std offset +8
['2020-04-22T09:00:00','Pacific/Apia'], // Std offset +13
['2020-01-22T09:00:00','Pacific/Apia'], // DST offset +14
['2020-01-22T09:00:00','Asia/Yangon'], // Std offset +6:30
['2020-04-22T09:00:00','Pacific/Chatham'], // Std offset +12:45
['2020-01-22T09:00:00','Pacific/Chatham'], // DST offset +13:45
// Historic offsets pre 1900
['1857-01-01T00:00:00','Europe/Berlin'], // Std offset +00:53:28
['1857-01-01T00:00:00','Australia/Sydney'],// Std offset +10:04:52
['1857-01-01T00:00:00','America/New_York'],// Std offset -04:56:02
['1857-01-01T00:00:00','America/Sao_Paulo'],//Std offset -03:06:28
// DST boundary check out of DST (2 to 3 am counted as "out")
['2020-04-05T01:45:00','Australia/Sydney'],// DST offset +11:00
['2020-04-05T01:59:59','Australia/Sydney'],// DST offset +11:00
['2020-04-05T02:00:00','Australia/Sydney'],// Std offset +10:00
['2020-04-05T02:30:00','Australia/Sydney'],// Std offset +10:00
['2020-04-05T03:00:00','Australia/Sydney'],// Std offset +10:00
['2020-04-05T03:15:00','Australia/Sydney'],// Std offset +10:00
// DST boundary check into DST (2 to 3 am counted as "in")
['2020-10-04T01:45:00','Australia/Sydney'],// Std offset +10:00
['2020-10-04T02:00:00','Australia/Sydney'],// DST offset +11:00
['2020-10-04T02:30:00','Australia/Sydney'],// DST offset +11:00
['2020-10-04T02:59:59','Australia/Sydney'],// DST offset +11:00
['2020-10-04T03:00:00','Australia/Sydney'],// DST offset +11:00
['2020-10-04T03:15:00','Australia/Sydney'] // DST offset +11:00
].forEach(([localTime,loc]) => {
// Show results
let timeUTC = getUTCString(localTime, loc);
let offset = getUTCOffset(localTime, loc);
showData(localTime, loc, offset, timeUTC);
});
};
// Example use
let timestamp = '2020-06-30 08:30:00';
let locBer = 'Europe/Berlin';
let locSng = 'Asia/Singapore';
// Get UTC timestamp and offset for Berlin
let utc = getUTCString(timestamp, locBer);
let off = getUTCOffset(timestamp, locBer);
// Show times and offset - offset is just for display, not used to
// generate Singapore timestamp
console.log('Berlin : ' + timestamp + ' ' + off); //
console.log('Singapore: ' + new Date(utc).toLocaleString(
'en-CA',{hour12:false, timeZone:locSng, timeZoneName:'short'}
).replace(',',''));
table {
border-collapse: collapse;
}
td {
font-family: geneva, arial;
font-size: 80%;
padding: 5px;
border: 1px solid #bbbbbb;
}
td:nth-child(2) {
font-family: monospace;
font-size: 100%;
}
<table id="t0">
<tr><th>Local time<th>Place<th>Offset<th>UTC<th>Check
</table>