LoginSignup
1
0

More than 3 years have passed since last update.

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

Last updated at Posted at 2020-02-13

すでに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を指定するように、という修正です。

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

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0