Help us understand the problem. What is going on with this article?

Chrome80+LEXの日時型入力で日付がずれる件の調査メモ

すでにIssueとして挙がっている

https://success.salesforce.com/issues_view?id=a1p3A000001Go5aQAC

ので修正待つだけだけど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を指定するように、という修正です。

他にもやり方はあるでしょうが、影響を最小限に留めるためにこの修正を選択したのでしょう。

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした