すでにIssueとして挙がっている
ので修正待つだけだけどSpring'20で修正されたので修正点を一番下に追記しました。
ローカルに置いとくと邪魔なので調査時のメモを供養。
日時のハンドラっぽい関数にブレークポイントを設置しまくってハンドラを見つける
components/ui/inputDateTime.js > setDateTimeValue
this.dateTimeLib.dateTimeService.getISOValue
の生成がおかしい
libraries/ui/dateTimeLib/dateTimeService.js
getISOValue:function(date, config, callback) {
var hours = config.hours;
var minutes = config.minutes;
if(hours) {
date = Date.UTC(date.getUTCFullYear(), date.getUTCMonth(), date.getUTCDate(), hours, minutes)
}
var isoValue = function isoValue(convertedDate) {
var translatedDate = $A.localizationService.translateFromOtherCalendar(convertedDate);
var isoString = translatedDate.toISOString();
callback(isoString)
};
convertFromTimezone(date, config.timezone, $A.getCallback(isoValue))
}, formatYear:function(year) {
var jpYear = $A.get("$Locale.showJapaneseImperialYear") && getImperialYear(year);
return year + (jpYear ? " (" + jpYear + ")" : "")
}}
convertFromTimezone が変換をかけているのに、 isoValueの中でもう一度 translatedDate.toISOString で変換がかかっている
convertedDate が、Firefoxだともとの数値、Chrome80だと前日になっている。
isoValue 内にブレークポイントを設置してコールスタックを見ると、(以下はスタックコピー用に作ったError)
at eval (eval at isoValue (/libraries/ui/dateTimeLib/dateTimeService.js), <anonymous>:1:7)
at isoValue (/libraries/ui/dateTimeLib/dateTimeService.js:91:7)
at callbackWrapper (/auraFW/javascript/5fuxCiO1mNHGdvJphU5ELQ/aura_proddebug.js:46084:23)
at AuraLocalizationService.$WallTimeToUTC$ (/auraFW/javascript/5fuxCiO1mNHGdvJphU5ELQ/aura_proddebug.js:43984:3)
at convertFromTimezone (/libraries/ui/dateTimeLib/dateTimeService.js:11:28)
at Object.getISOValue (/libraries/ui/dateTimeLib/dateTimeService.js:93:5)
at Object.setDateTimeValue (/components/ui/inputDateTime.js:434:46)
at Object.setTimeValue (/components/ui/inputDateTime.js:412:18)
at Object.handleDateTimeSelection (/components/ui/inputDateTime.js:370:22)
at setDateTime (/components/ui/inputDateTime.js:51:16)
以下の関数の data.offset
が違う。Firefox:540, Chrome:1980
AuraLocalizationService.prototype.$WallTimeToUTC$ = function(date, timezone, callback) {
$A.assert(date instanceof Date, "AuraLocalizationService.WallTimeToUTC(): 'date' must be a Date object.");
$A.assert(typeof callback === "function", "AuraLocalizationService.WallTimeToUTC(): callback must be a function.");
timezone = this.$normalizeTimeZone$(timezone);
if(timezone === "UTC" || !this.$isValidDateObject$(date)) {
callback(date);
return
}
var data = this.$createDateTimeData$(date, timezone);
var convertedData = this.$setDataToZone$(data, "UTC");
var dateTime = convertedData["config"];
var ts = Date.UTC(dateTime["year"], dateTime["month"] - 1, dateTime["day"], dateTime["hour"], dateTime["minute"]);
var utcDate = new Date(ts);
utcDate.setUTCSeconds(date.getSeconds(), date.getMilliseconds());
callback(utcDate)
};
1980 - 540 = 1440 = 1日あたりの分。
data を生成している this.$createDateTimeData$ を参照。
$A.localizationService.$createDateTimeData$
AuraLocalizationService.prototype.$createDateTimeData$ = function(date, timeZone) {
var config = {"year":date.getUTCFullYear(), "month":date.getUTCMonth() + 1, "day":date.getUTCDate(), "hour":date.getUTCHours(), "minute":date.getUTCMinutes()};
var zoneInfo = this.$getZoneInfo$(config, timeZone);
return{"config":config, "offset":zoneInfo[1], "timestamp":zoneInfo[0], "timeZone":timeZone}
};
this.$getZoneInfo$(config, timeZone);
zoneInfo[1] が1980。
AuraLocalizationService.prototype.$getZoneInfo$ を参照
AuraLocalizationService.prototype.$getZoneInfo$ = function(config, timeZone) {
var nowOffset = this.$zoneOffset$(Date.now(), timeZone);
var localTs = Date.UTC(config["year"], config["month"] - 1, config["day"], config["hour"], config["minute"]);
var utcGuess = localTs - nowOffset * 6E4;
var guessOffset = this.$zoneOffset$(utcGuess, timeZone);
if(nowOffset === guessOffset) {
return[utcGuess, guessOffset]
}
utcGuess -= (guessOffset - nowOffset) * 6E4;
var guessOffset2 = this.$zoneOffset$(utcGuess, timeZone);
if(guessOffset === guessOffset2) {
return[utcGuess, guessOffset]
}
return[localTs - Math.max(guessOffset, guessOffset2) * 6E4, Math.max(guessOffset, guessOffset2)]
};
localTs はChromeとFirefoxで同じ値。
utcGuess も。
guessOffset からずれている。
// timestamp = 1581433200000 で調査
AuraLocalizationService.prototype.$zoneOffset$ = function(timestamp, timeZone) {
if(timeZone === "UTC") {
return 0
}
var date = new Date(timestamp);
date.setSeconds(0, 0);
var dateTimeString = this.$formatDateToEnUSString$(date, timeZone);
var zoneTs = this.$parseEnUSDateTimeString$(dateTimeString);
return(zoneTs - date.getTime()) / 6E4
};
dateTimeString が Chrome:"02/12/2020, 24:00" Firefox:"02/12/2020, 00:00"
Chromeに24時が存在する。
AuraLocalizationService.prototype.$formatDateToEnUSString$ = function(date, timeZone) {
var timeZoneFormat = this.$createEnUSDateTimeFormat$(timeZone);
var dateString = timeZoneFormat ? this.$format$(timeZoneFormat, date) : null;
return dateString !== null ? dateString : this.$formatDateTime$(date, "MM/dd/yyyy, hh:mm")
};
timeZoneFormat.format(new Date(2020, 2, 1, 0, 0))
//=> "03/01/2020, 24:00"
timeZoneFormat.format
//=> ƒ () { [native code] }
AuraLocalizationService.prototype.$createEnUSDateTimeFormat$ = function(timeZone) {
var timeZoneFormat = this.$timeZoneFormatCache$[timeZone];
if(timeZoneFormat !== undefined) {
return timeZoneFormat
}
try {
timeZoneFormat = Intl["DateTimeFormat"]("en-US", {"timeZone":timeZone, "hour12":false, "year":"numeric", "month":"2-digit", "day":"2-digit", "hour":"2-digit", "minute":"2-digit"})
}catch(e) {
timeZoneFormat = null
}
this.$timeZoneFormatCache$[timeZone] = timeZoneFormat;
return timeZoneFormat
};
f = Intl.DateTimeFormat("en-US", {timeZone:"Asia/Tokyo", hour12: false, year:"numeric", month: "2-digit", day: "2-digit", hour: "2-digit", minute: "2-digit"});
f.format(new Date(2020, 3, 1, 0, 0));
//=> Chrome: "04/01/2020, 24:00"
//=> Firefox: "04/01/2020, 00:00"
f = Intl.DateTimeFormat("en-US", {timeZone:"Asia/Tokyo", hour12: false, year:"numeric", month: "2-digit", day: "2-digit", hour: "2-digit", minute: "2-digit"});
f.format(new Date(2020, 3, 1, 0, 59));
//=> "04/01/2020, 24:59"
f = Intl.DateTimeFormat("en-US", {timeZone:"Asia/Tokyo", hour12: false, year:"numeric", month: "2-digit", day: "2-digit", hour: "2-digit", minute: "2-digit"});
f.format(new Date(2020, 3, 1, 0, 60));
//=> "04/01/2020, 01:00"
後続のパース処理 parseEnUSDateTimeString の調査
AuraLocalizationService.prototype.$parseEnUSDateTimeString$ = function(dateTimeString) {
var match = this.$enUsDateTimePatterns$.$primaryPattern$.$REG_EXP$.exec(dateTimeString);
if(match === null) {
match = this.$enUsDateTimePatterns$.$secondaryPattern$.$REG_EXP$.exec(dateTimeString);
if(match === null) {
return null
}
this.$enUsDateTimePatterns$.$swap$()
}
return Date.UTC(parseInt(match[this.$enUsDateTimePatterns$.$primaryPattern$.$YEAR$], 10), parseInt(match[this.$enUsDateTimePatterns$.$primaryPattern$.$MONTH$], 10) - 1, parseInt(match[this.$enUsDateTimePatterns$.$primaryPattern$.$DAY$], 10), parseInt(match[this.$enUsDateTimePatterns$.$primaryPattern$.$HOUR$], 10), parseInt(match[this.$enUsDateTimePatterns$.$primaryPattern$.$MINUTE$], 10))
};
2020/02/13 00:30 の時、 dateTimeStringは 「02/13/2020, 24:30」 となり、
match
//=> ["02/13/2020, 24:30", "02", "13", "2020", "24", "30", index: 0, input: "02/13/2020, 24:30", groups: undefined]
なのでここで最後の Date.UTC 引数の時間指定も 24 になり、次の日のインスタンスが生成される。
AuraLocalizationService.prototype.$zoneOffset$
の最後が
return(zoneTs - date.getTime()) / 6E4
で、この次の日で生成された時間と、指定されたタイムスタンプの差分をオフセットとして返している。
Spring'20とともに修正されました。
AuraLocalizationService.prototype.$createEnUSDateTimeFormat$ = function(timeZone) {
var timeZoneFormat = this.$timeZoneFormatCache$[timeZone];
if(timeZoneFormat !== undefined) {
return timeZoneFormat
}
try {
var testDateTime = Intl["DateTimeFormat"]("en-US", {"hour12":false, "timeZone":"America/New_York", "year":"numeric", "month":"2-digit", "day":"2-digit", "hour":"2-digit", "minute":"2-digit", "second":"2-digit"});
var testDateTimeFormatted = testDateTime["format"](new Date("2014-06-25T04:00:00.123Z"));
if(testDateTimeFormatted === "06/25/2014, 00:00:00" || testDateTimeFormatted === "\u200e06\u200e/\u200e25\u200e/\u200e2014\u200e \u200e00\u200e:\u200e00\u200e:\u200e00") {
timeZoneFormat = Intl["DateTimeFormat"]("en-US", {"hour12":false, "timeZone":timeZone, "year":"numeric", "month":"2-digit", "day":"2-digit", "hour":"2-digit", "minute":"2-digit", "second":"2-digit"})
}else {
timeZoneFormat = Intl["DateTimeFormat"]("en-US", {"hourCycle":"h23", "timeZone":timeZone, "year":"numeric", "month":"2-digit", "day":"2-digit", "hour":"2-digit", "minute":"2-digit", "second":"2-digit"})
}
}catch(e) {
timeZoneFormat = null
}
this.$timeZoneFormatCache$[timeZone] = timeZoneFormat;
return timeZoneFormat
};
固定の日時でフォーマットした結果が0時表記と一致しなければhourCycleにh23を指定するように、という修正です。
他にもやり方はあるでしょうが、影響を最小限に留めるためにこの修正を選択したのでしょう。