2021年8月8日

日付 と 時刻

新しい組み込みオブジェクトを見ていきましょう: Date。日付や時刻を保存し、管理するためのメソッドを提供します。

例えば、作成/修正時刻を保存したり、時間を測定したり、単に現在の時刻を表示するために使うことができます。

作成

新しい Date オブジェクトを作るには、 次のいずれかの引数で new Date() を呼びます:

new Date()

引数なし – 現在の日付と時刻で Date オブジェクトを作ります:

let now = new Date();
alert( now ); // 現在の日時を表示します
new Date(milliseconds)

Jan 1st of 1970 UTC+0 (1970年 1月1日 UTC+0) からの経過したミリ秒(秒の1/1000)に等しい時間をもつ Date オブジェクトを作ります。

// 0 は 01.01.1970 UTC+0 を意味します
let Jan01_1970 = new Date(0);
alert( Jan01_1970 );

// 今 24 時間を追加しました, 02.01.1970 UTC+0 になります
let Jan02_1970 = new Date(24 * 3600 * 1000);
alert( Jan02_1970 );

1970年初めから経過したミリ秒の数値は タイムスタンプ と呼ばれます。

これは日付の軽量な数値表現です。常に new Date(timestamp) を使ってタイムスタンプから日付を作成し、存在する Date オブジェクトを date.getTime() メソッド(後述) を使ってタイムスタンプに変換します。

1970.01.01 以前の日付は負のタイムスタンプになります。例:

// 31 Dec 1969
let Dec31_1969 = new Date(-24 * 3600 * 1000);
alert( Dec31_1969 );
new Date(datestring)

1つの引数でそれが文字列の場合、自動でパースされます。Date.parse アルゴリズム(後述)でパースされます。

let date = new Date("2017-01-26");
alert(date);
// 時刻が設定されていないので、GMT の深夜0時とみなされ、
// コーザルが実行されるタイムゾーンに応じて調整されます。
// したがって、結果は以下になります
// Thu Jan 26 2017 11:00:00 GMT+1100 (Australian Eastern Daylight Time)
// または
// Wed Jan 25 2017 16:00:00 GMT-0800 (Pacific Standard Time)
new Date(year, month, date, hours, minutes, seconds, ms)

ローカルタイムゾーンで、与えられた要素で日付を作成します。最初の2つの引数は必須です。

  • year は4桁でなければいけません。2013 はOKですが、98 はダメです。
  • month 0 (1月) から数え、11 (12月)までです。
  • date パラメータは実際の月の日です。もし指定がなければ 1 になります。
  • もし hours/minutes/seconds/ms がなければ、これらは 0 とみなされます。

例:

new Date(2011, 0, 1, 0, 0, 0, 0); // // 1 Jan 2011, 00:00:00
new Date(2011, 0, 1); // 同じです。時などはデフォルトで 0 です

最小の精度は 1ms (1/1000秒)です:

let date = new Date(2011, 0, 1, 2, 3, 4, 567);
alert( date ); // 1.01.2011, 02:03:04.567

date コンポーネントへのアクセス

Date オブジェクトから 年、月などへアクセスする多くのメソッドがあります。しかし、カテゴライズすることで簡単に覚えることができます。

getFullYear()
年を取得します(4桁)
getMonth()
月を取得します, 0から11
getDate()
月の日を取得し、値は 1 から 31 です。メソッドの名前には少し違和感がありますが。
getHours(), getMinutes(), getSeconds(), getMilliseconds()
対応する時刻の構成要素を取得します。
getYear() ではなく getFullYear() です

多くのJavaScriptエンジンは 標準ではないメソッド getYear() を実装しています。このメソッドは非推奨です。これは2桁の年を返す時があるので、決して使わないでください。年の取得には getFullYear() があります。

加えて、週の曜日を取得することもできます:

getDay()
週の曜日を取得し、値は 0 (日曜) から 6 (土曜) です。最初の日は常に日曜です。いくつかの国ではそうではありませんが、変えることはできません。

上のすべてのメソッドはローカルタイムゾーンを基準に構成要素を返します。

タイムゾーン UTC+0 の日、月、年などを返す、UTCカウンターパートもあります:getUTCFullYear(), getUTCMonth(), getUTCDay(). 単に "get" の直後に "UTC" を挿入するだけです。.

あなたのタイムゾーンが UTC から相対的にシフトしている場合、下のコードの結果は異なる時間を表示します:

// 現在の date
let date = new Date();

// あなたの現在のタイムゾーンでの時間
alert( date.getHours() );

// UTC+0 のタイムゾーンでの時間 (サマータイムのないロンドン時間)
alert( date.getUTCHours() );

なお、UTCのパターンを持たない、2つの特別なメソッドがあります:

getTime()

日付のタイムスタンプを返します – それは、1970年 UTC+0 の 1月1日からの経過ミリ秒です。

getTimezoneOffset()

ローカルタイムゾーンとUTCの差を、分で返します:

// タイムゾーン UTC-1 にいる場合、60 を出力
// タイムゾーン UTC+3 にいる場合、-180 を出力
alert( new Date().getTimezoneOffset() );

日付の構成要素を設定する

次のメソッドで、日付/時刻の構成要素をセットすることができます:

setTime() を除くすべてのものは UTCのパターンもあります。例えば setUTCHours() です。

これまで見たように、いくつかのメソッドは一度に複数の構成要素をセットすることができます。例えば、setHours です。設定するときに言及されていない構成要素は変更されません。

例:

let today = new Date();

today.setHours(0);
alert(today); // 今日のままですが、時は 0 に変更されます

today.setHours(0, 0, 0, 0);
alert(today); // 今日のままで, 今は 00:00:00 です

自動補正

自動補正Date オブジェクトのとても便利な機能です。私たちが範囲外の値を指定した場合、それは自動的に調節されます。

例:

let date = new Date(2013, 0, 32); // 32 Jan 2013 ?!?
alert(date); // ...iは 2013/2/1 です!

範囲外の数値が指定された構成要素は自動的に補正されます。

“2016年2月28日” の日付を2日増やす必要があるとしましょう。“3月2日”、うるう年の場合には “3月1日” になりますが、考える必要はありません。 2日を追加するだけです。 Date オブジェクトが残りを行います:

let date = new Date(2016, 1, 28);
date.setDate(date.getDate() + 2);

alert( date ); // 1 Mar 2016

この機能は、指定した期間後の日付を取得したいときによく利用されます。 例えば、“70秒後” の日付を取得しましょう。:

let date = new Date();
date.setSeconds(date.getSeconds() + 70);

alert( date ); // 正しい日時を表示します

また、ゼロや負値をセットすることもできます。例えば:

let date = new Date(2016, 0, 2); // 2 Jan 2016

date.setDate(1); // 月の1日を設定します
alert( date );

date.setDate(0); // 最小日は1なので、先月の最後の日になります
alert( date ); // 31 Dec 2015

日付から数値へ、日付の差分

Date オブジェクトが数値へ変換されるとき、date.getTime() と同じようにタイムスタンプになります:

let date = new Date();
alert(+date); // ミリ秒の数値です, date.getTime() と同じです

重要な副作用は、日付は減算することができますが、結果はミリ秒単位での差分になることです。

これは時間の計測で使うことができます:

let start = new Date(); // 計測開始

// なにかする
for (let i = 0; i < 100000; i++) {
  let doSomething = i * i * i;
}

let end = new Date(); // 終了

alert( `The loop took ${end - start} ms` );

Date.now()

もし差分だけ測定したい場合、Date オブジェクトを使う必要はありません。

現在のタイムスタンプを返す特別なメソッド Date.now() があります。

これは、意味的には new Date().getTime() と同じですが、中間の Date オブジェクトを作らないため、より速く、ガベージコレクションに負荷をかけません。

これは主に利便性であったり、JavaScriptでのゲームやその他特別なアプリケーションなどパフォーマンスが重要な場合に使われます。

なので、これはおそらくベターです:

let start = Date.now(); // 1 Jan 1970 からのミリ秒

// なにかする
for (let i = 0; i < 100000; i++) {
  let doSomething = i * i * i;
}

let end = Date.now(); // 終了

alert( `The loop took ${end - start} ms` ); // 日付ではなく、数値を減算する

ベンチマーク

CPUを必要とする機能について、信頼できるベンチマークが必要な場合は注意が必要です。

例えば、2つの日付の差を計算する2つの関数を測定してみましょう。どちらがより速いでしょうか?

このようなパフォーマンス測定はよく “ベンチマーク” と呼ばれます。

// date1 と date2 を持っており、これらの差をmsで返すのはどちらの関数が速いでしょう?
function diffSubtract(date1, date2) {
  return date2 - date1;
}

// or
function diffGetTime(date1, date2) {
  return date2.getTime() - date1.getTime();
}

これら2つは正確に同じことをしますが、片方は日付のミリ秒を取得するために明示的な date.getTime() を使います。また、もう一方は日付の数値変換を当てにしています。これらの結果は常に同じです。

さて、どちらがより速いでしょうか?

最初に思いつくアイデアは、それらを何度も連続で実行し、その時間の差を測ることです。我々のケースでは、関数はとてもシンプルなので、約10万回程度行う必要があります。

測定してみましょう。:

function diffSubtract(date1, date2) {
  return date2 - date1;
}

function diffGetTime(date1, date2) {
  return date2.getTime() - date1.getTime();
}

function bench(f) {
  let date1 = new Date(0);
  let date2 = new Date();

  let start = Date.now();
  for (let i = 0; i < 100000; i++) f(date1, date2);
  return Date.now() - start;
}

alert( 'Time of diffSubtract: ' + bench(diffSubtract) + 'ms' );
alert( 'Time of diffGetTime: ' + bench(diffGetTime) + 'ms' );

なんということでしょう! getTime() を使う方が遥かに速いです! なぜかと言うと、この場合は型変換がなく、エンジンが最適化をするのがとても簡単なためです。

さて、私たちは測定結果を得ましたが、これはまだ良いベンチマークではありません。

bench(diffSubtract) を実行しているときにCPUは並列で何かをしていてリソースを消費しており、bench(diffGetTime) の実行時までにはその作業が完了していたと想像してください。

これは、現代のマルチプロセスOSでのよくある実際のシナリオです。

上の場合、結果として最初のベンチマークは2回目よりも利用できるCPUリソースが少なくなります。これは誤った結果を導きます。

より信頼性の高いベンチマークを行うには、ベンチマーク全体を複数回再実行する必要があります。

ここではそのコードのサンプルです:

function diffSubtract(date1, date2) {
  return date2 - date1;
}

function diffGetTime(date1, date2) {
  return date2.getTime() - date1.getTime();
}

function bench(f) {
  let date1 = new Date(0);
  let date2 = new Date();

  let start = Date.now();
  for (let i = 0; i < 100000; i++) f(date1, date2);
  return Date.now() - start;
}

let time1 = 0;
let time2 = 0;

// bench(upperSlice) と bench(upperLoop) を交互に10回実行する
for (let i = 0; i < 10; i++) {
  time1 += bench(diffSubtract);
  time2 += bench(diffGetTime);
}

alert( 'Total time for diffSubtract: ' + time1 );
alert( 'Total time for diffGetTime: ' + time2 );

現代のJavaScriptエンジンは、何度も実行される「ホットコード」に対してのみ高度な最適化を適用し始めます(ほとんど実行されないものを最適化する必要はないためです)。したがって、上記の例では、最初の実行は最適化されていません。 ヒートアップ(メインの実行の前の助走)を追加することもできます:

// メインループの前の "ヒートアップ" のために追加
bench(diffSubtract);
bench(diffGetTime);

// now benchmark
for (let i = 0; i < 10; i++) {
  time1 += bench(diffSubtract);
  time2 += bench(diffGetTime);
}
マイクロベンチマークをするのは気をつけてください

現代のJavaScriptエンジンは多くの最適化を行います。それらは “人工的なテスト” の結果を “通常の使用” と比較して調整するかもしれません。 非常に小さいものをベンチマークするときは特にそうです。従って、真面目にパフォーマンスを理解したいのであれば、JavaScriptエンジンの仕組みを学んでください。そして、マイクロベンチマークは全く必要ないでしょう。

V8 についての素晴らしい記事は http://mrale.ph にあります。

文字列からの Date.parse

メソッド Date.parse(str) は文字列から日付を読むことができます。

文字列のフォーマットは YYYY-MM-DDTHH:mm:ss.sssZ でなければなりません。:

  • YYYY-MM-DD は日付です。年-月-日
  • "T" の文字はデリミタとして使用されます。
  • HH:mm:ss.sss は時間です(時、分、秒とミリ秒)。
  • オプションの 'Z' の部分は、フォーマット +-hh:mm のタイムゾーンを示します。UTC+0 を意味する単一の文字 Z です。

より短い表記も可能です。YYYY-MM-DDYYYY-MM 、または YYYY です。

Date.parse(str) の呼び出しでは、文字列を与えられたフォーマットにパースし、タイムスタンプを返します(1970年 1月1日 UTC+0からのミリ秒)。もしフォーマットが正しくない場合には NaN を返します。

例:

let ms = Date.parse('2012-01-26T13:51:50.417-07:00');

alert(ms); // 1327611110417  (timestamp)

タイムスタンプから、即座に new Date オブジェクトを作ることができます。

let date = new Date( Date.parse('2012-01-26T13:51:50.417-07:00') );

alert(date);

サマリ

  • JavaScript での日付と時刻はDate オブジェクトで表現されます。“日付だけ”、“時刻だけ” を作ることはできません。Date オブジェクトは常に両方を持ちます。
  • 月はゼロからカウントされます(なので、1月は ゼロです)。
  • getDay() の週の曜日もゼロからカウントされます(ゼロは日曜です)
  • 範囲外の構成要素がセットされたとき、Date は自身を自動補正します。日/月/時の加減算の場合には便利です。
  • 日付はミリ秒で与えられた差分で引き算することができます。これは、数値に変換されるとき、Date はタイムスタンプになるためです。
  • 素早く現在のタイムスタンプを取得するには Date.now() を使いましょう。

多くの他のシステムとは異なり、JavaScriptでのタイムスタンプは秒ではなく、ミリ秒です。

また、私たちはより高精度の時間計測が必要な場合があります。JavaScript自身はマイクロ秒(100万分の1秒)での時間を計測する方法を持っていませんが、ほとんどの環境はそれを提供しています。例えば、ブラウザはマイクロ秒の精度(少数第3桁)で、ページ読み込み開始からのミリ秒を返す performance.now() を持っています。:

alert(`Loading started ${performance.now()}ms ago`);
// このようになります: "Loading started 34731.26000000001ms ago"
// .26 はマイクロ秒 (260 マイクロ秒)
// 少数点3桁以上は精度エラーで、最初の3桁だけが正しいです

Node.js は microtime モジュールや他の方法を持っています。技術的には、どのデバイスや環境でも精度をあげることができます。単に Date にはないだけです。

タスク

重要性: 5

日付 “Feb 20, 2012, 3:12am” の Date オブジェクトを作成してください。タイムゾーンはローカルです。

alert を使って表示してください。

new Date コンストラクタはデフォルトでローカルのタイムゾーンを使います。なので、覚えておくべき重要なことは 月 はゼロからスタートすることだけです。

2月は数値で 1 になります。

let d = new Date(2012, 1, 20, 3, 12);
alert( d );
重要性: 5

ショートフォーマット: ‘MO’, ‘TU’, ‘WE’, ‘TH’, ‘FR’, ‘SA’, ‘SU’ で平日を表示する関数 getWeekDay(date) を書いてください。

例:

let date = new Date(2012, 0, 3);  // 3 Jan 2012
alert( getWeekDay(date) );        // "TU" と表示される

テストと一緒にサンドボックスを開く

メソッド date.getDay() は日曜開始で平日の数値を返します。

その数値から適切な日の名前が取得できるよう、平日の配列を作りましょう。

function getWeekDay(date) {
  let days = ['SU', 'MO', 'TU', 'WE', 'TH', 'FR', 'SA'];

  return days[date.getDay()];
}

let date = new Date(2014, 0, 3); // 3 Jan 2014
alert( getWeekDay(date) ); // FR
function getWeekDay(date) {
  let days = ['SU', 'MO', 'TU', 'WE', 'TH', 'FR', 'SA'];

  return days[date.getDay()];
}

サンドボックスでテストと一緒に解答を開く

重要性: 5

ヨーロッパ諸国では、月曜日(1) から始まり、次に火曜日(2)、日曜日(7)までの曜日があります。 date について "ヨーロッパ " の曜日を返す関数getLocalDay(date) を書いてください。

let date = new Date(2012, 0, 3);  // 3 Jan 2012
alert( getLocalDay(date) );       // tuesday, should show 2

テストと一緒にサンドボックスを開く

function getLocalDay(date) {

  let day = date.getDay();

  if (day == 0) { // 0 becomes 7
    day = 7;
  }

  return day;
}

alert( getLocalDay(new Date(2012, 0, 3)) ); // 2
function getLocalDay(date) {

  let day = date.getDay();

  if (day == 0) { // weekday 0 (sunday) is 7 in european
    day = 7;
  }

  return day;
}

サンドボックスでテストと一緒に解答を開く

重要性: 4

date からdays 前の日付を返す関数 getDateAgo(date, days) を作成してください。

例えば、今日が 20日の場合、getDateAgo(new Date(), 1) は 19で、getDateAgo(new Date(), 2) は 18になります。

数ヶ月/年に対しても、信頼性をもって動作しなければなりません:

let date = new Date(2015, 0, 2);

alert( getDateAgo(date, 1) ); // 1, (1 Jan 2015)
alert( getDateAgo(date, 2) ); // 31, (31 Dec 2014)
alert( getDateAgo(date, 365) ); // 2, (2 Jan 2014)

P.S. 関数は与えられた date を変更すべきではありません。

テストと一緒にサンドボックスを開く

考え方はシンプルです: date から与えられた日数を減算します:

function getDateAgo(date, days) {
  date.setDate(date.getDate() - days);
  return date.getDate();
}

…しかし、関数は date を変えてはいけません。私たちに日付を与えた外部コードはそれが変更することは期待していないからです。

それを実装するために、次のように日付をクローンしましょう:

function getDateAgo(date, days) {
  let dateCopy = new Date(date);

  dateCopy.setDate(date.getDate() - days);
  return dateCopy.getDate();
}

let date = new Date(2015, 0, 2);

alert( getDateAgo(date, 1) ); // 1, (1 Jan 2015)
alert( getDateAgo(date, 2) ); // 31, (31 Dec 2014)
alert( getDateAgo(date, 365) ); // 2, (2 Jan 2014)
function getDateAgo(date, days) {
  let dateCopy = new Date(date);

  dateCopy.setDate(date.getDate() - days);
  return dateCopy.getDate();
}

サンドボックスでテストと一緒に解答を開く

重要性: 5

月の最後の日を返す関数 getLastDayOfMonth(year, month) を書いてください。それは 30, 31, または2月であれば 28/29 です。

パラメータ:

  • year – 4桁の年、例えば 2012
  • month – 月、0 から 11

例えば、getLastDayOfMonth(2012, 1) = 29 (うるう年、2月)

テストと一緒にサンドボックスを開く

次の月を使って日付を作りますが、その日としてゼロを渡します:

function getLastDayOfMonth(year, month) {
  let date = new Date(year, month + 1, 0);
  return date.getDate();
}

alert( getLastDayOfMonth(2012, 0) ); // 31
alert( getLastDayOfMonth(2012, 1) ); // 29
alert( getLastDayOfMonth(2013, 1) ); // 28

通常、日は 1から始まりますが、技術的には任意の数値を渡すことが可能で、日付は自身を自動調整します。なので、 0 を渡したとき、それは “その月の1日より、1日前” を意味します。: つまり、“前の月の最終日” になります。

function getLastDayOfMonth(year, month) {
  let date = new Date(year, month + 1, 0);
  return date.getDate();
}

サンドボックスでテストと一緒に解答を開く

重要性: 5

今日の開始から秒数を返す関数 getSecondsToday() を書いてください。

例えば、今 10:00 am で夏時間のシフトがない場合:

getSecondsToday() == 36000 // (3600 * 10)

関数は任意の日で動作する必要があります。つまり、“今日” のハードコードを持つべきではありません。

秒数を取得するために、現在の日で、時間が 00:00:00 の日付を生成し、“今” からそれを減算します。

差異はその日の開始からのミリ秒であり、秒を取得するために1000で割る必要があります。:

function getSecondsToday() {
  let now = new Date();

  // 現在の 日/月/年を使ってオブジェクトを作成
  let today = new Date(now.getFullYear(), now.getMonth(), now.getDate());

  let diff = now - today; // ms difference
  return Math.round(diff / 1000); // 秒を作る
}

alert( getSecondsToday() );

代わりの解答として、時間/分/秒 を取得してそれらを秒に変換する、という方法もあります。:

function getSecondsToday() {
  let d = new Date();
  return d.getHours() * 3600 + d.getMinutes() * 60 + d.getSeconds();
};
重要性: 5

明日までの秒数を返す関数 getSecondsToTomorrow() を作成してください。

例えば、今が 23:00 とすると:

getSecondsToTomorrow() == 3600

P.S. 関数は任意の日付で動作する必要があり、“今日” はハードコードではありません。

“明日の 00:00:00” から現在の日付を減算することで、明日までのミリ秒を取得することができます。

最初に、“明日” を生成してそれをします:

function getSecondsToTomorrow() {
  let now = new Date();

  // 明日の日付
  let tomorrow = new Date(now.getFullYear(), now.getMonth(), now.getDate()+1);

  let diff = tomorrow - now; // difference in ms
  return Math.round(diff / 1000); // convert to seconds
}
重要性: 4

次のように date をフォーマットする関数 formatDate(date) を書いてください。:

  • もし date が1秒未満で渡された場合、"right now" です。
  • そうではなく、date が1分未満で渡された場合、"n sec. ago" です。
  • そうではなく、1時間未満の場合は "m min. ago" です。
  • そ例外の場合はフォーマット "DD.MM.YY HH:mm" の完全な日付です。つまり: "day.month.year hours:minutes", すべて2桁の形式です。e.g. 31.12.16 10:00

例:

alert( formatDate(new Date(new Date - 1)) ); // "right now"

alert( formatDate(new Date(new Date - 30 * 1000)) ); // "30 sec. ago"

alert( formatDate(new Date(new Date - 5 * 60 * 1000)) ); // "5 min. ago"

// yesterday's date like 31.12.2016, 20:00
alert( formatDate(new Date(new Date - 86400 * 1000)) );

テストと一緒にサンドボックスを開く

date から “今” までの 時間を取得するために – 日付を減算しましょう。

function formatDate(date) {
  let diff = new Date() - date; // ミリ秒での差

  if (diff < 1000) { // 1秒未満
    return 'right now';
  }

  let sec = Math.floor(diff / 1000); // 差分を秒に変換

  if (sec < 60) {
    return sec + ' sec. ago';
  }

  let min = Math.floor(diff / 60000); // 差分を分に変換
  if (min < 60) {
    return min + ' min. ago';
  }

  // 日付のフォーマット
  // 1桁の 日/月/時/分 に先頭のゼロを追加
  let d = date;
  d = [
    '0' + d.getDate(),
    '0' + (d.getMonth() + 1),
    '' + d.getFullYear(),
    '0' + d.getHours(),
    '0' + d.getMinutes()
  ].map(component => component.slice(-2)); // すべてのコンポーネントの最後の2桁を取る

  // コンポーネントを日付に連結
  return d.slice(0, 3).join('.') + ' ' + d.slice(3).join(':');
}

alert( formatDate(new Date(new Date - 1)) ); // "right now"

alert( formatDate(new Date(new Date - 30 * 1000)) ); // "30 sec. ago"

alert( formatDate(new Date(new Date - 5 * 60 * 1000)) ); // "5 min. ago"

// yesterday's date like 31.12.2016, 20:00
alert( formatDate(new Date(new Date - 86400 * 1000)) );
function formatDate(date) {
  let diff = new Date() - date; // the difference in milliseconds

  if (diff < 1000) { // less than 1 second
    return 'right now';
  }

  let sec = Math.floor(diff / 1000); // convert diff to seconds

  if (sec < 60) {
    return sec + ' sec. ago';
  }

  let min = Math.floor(diff / 60000); // convert diff to minutes
  if (min < 60) {
    return min + ' min. ago';
  }

  // format the date
  // add leading zeroes to single-digit day/month/hours/minutes
  let d = date;
  d = [
    '0' + d.getDate(),
    '0' + (d.getMonth() + 1),
    '' + d.getFullYear(),
    '0' + d.getHours(),
    '0' + d.getMinutes()
  ].map(component => component.slice(-2)); // take last 2 digits of every component

  // join the components into date
  return d.slice(0, 3).join('.') + ' ' + d.slice(3).join(':');
}

サンドボックスでテストと一緒に解答を開く

チュートリアルマップ