JavaScript | 第8章「日付と時刻の表現」

javascrpit JavaScript
スポンサーリンク

日付の作成・加算を体験する練習問題 + 自動採点の HTML

<!doctype html>
<html lang="ja">
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width,initial-scale=1">
  <title>JavaScript 日付演習 (自動採点)</title>
  <style>
    :root{font-family:system-ui,-apple-system,'Segoe UI',Roboto,'Hiragino Kaku Gothic ProN','メイリオ',Meiryo, sans-serif;color:#222}
    body{max-width:900px;margin:28px auto;padding:18px;background:#fbfcfd;border:1px solid #eef2f7;border-radius:12px}
    h1{font-size:1.4rem;margin:0 0 8px}
    p.lead{margin:0 0 18px;color:#4b5563}
    .card{background:white;border:1px solid #e6eef6;padding:14px;border-radius:10px;margin:12px 0}
    label{display:block;font-weight:600;margin-bottom:6px}
    input[type=text], select{width:100%;padding:8px;border:1px solid #d1e2f0;border-radius:6px}
    .row{display:flex;gap:12px}
    .col{flex:1}
    button{background:#0f62fe;color:white;border:none;padding:8px 12px;border-radius:8px;cursor:pointer}
    button.secondary{background:#6b7280}
    .result{margin-top:10px;padding:10px;border-radius:8px}
    .ok{background:#ecfdf5;color:#065f46;border:1px solid #bbf7d0}
    .ng{background:#fff1f2;color:#9f1239;border:1px solid #fecaca}
    pre{background:#0b1220;color:#dbeafe;padding:10px;border-radius:6px;overflow:auto}
    .hint{color:#6b7280;font-size:0.95rem}
    .score{font-size:1.1rem;font-weight:700}
    .small{font-size:0.9rem;color:#374151}
    .flex{display:flex;gap:8px;align-items:center}
    .solution{white-space:pre-wrap;background:#f8fafc;padding:10px;border-radius:6px;border:1px dashed #cbd5e1}
  </style>
</head>
<body>
  <h1>JavaScript 日付演習(自動採点)</h1>
  <p class="lead">以下の問題に答えてください。ボタンを押すと自動で採点します。ヒントや模範解答も表示できます。</p>

  <div class="card" id="instructions">
    <strong>使い方のポイント</strong>
    <ul>
      <li>`new Date(year, monthIndex, day, hour, minute, second)` の月は <code>0</code> が1月、<code>11</code> が12月です。</li>
      <li>日付の加算はタイムスタンプ(ミリ秒)を使うか、Date の set/get 系で慎重に行います。</li>
    </ul>
  </div>

  <!-- 問題リスト -->
  <form id="exerciseForm">

    <div class="card">
      <h2>問題 1 — 年月日で Date を作る</h2>
      <label>次の日時を <code>new Date(...)</code> で作り、その ISO 文字列を入力してください。</label>
      <p class="small">対象日時:<strong>2025年12月25日 15:30:00(ローカル)</strong></p>
      <input type="text" id="q1" placeholder="例: 2025-12-25T06:30:00.000Z または new Date(2025,11,25,15,30,0) を使って作った値のISO">
      <div class="hint">※ 解答欄には <code>toISOString()</code> の結果を入れてください(UTCベースの ISO 形式)。</div>
    </div>

    <div class="card">
      <h2>問題 2 — ISO 文字列からパース</h2>
      <label>次の ISO 文字列をパースして、ローカルの年月日時分を <code>YYYY-MM-DD HH:mm</code> 形式で入力してください。</label>
      <p class="small">対象 ISO:<code>2025-03-01T09:00:00Z</code></p>
      <input type="text" id="q2" placeholder="例: 2025-03-01 18:00 (日本ではUTC+9の場合)" />
    </div>

    <div class="card">
      <h2>問題 3 — 日付の加算(2日後)</h2>
      <label>今日(解答時の現在日時)から <strong>48時間後</strong> の日付を ISO 形式で入力してください。</label>
      <input type="text" id="q3" placeholder="例: 2025-10-15T12:34:56.000Z" />
      <div class="hint">解答は UTC の ISO 文字列(<code>toISOString()</code>)でお願いします。</div>
    </div>

    <div class="card">
      <h2>問題 4 — 月をまたぐ加算</h2>
      <label>次の日時に <strong>1か月を加算</strong> した結果を <code>YYYY-MM-DD</code> 形式で入力してください。</label>
      <p class="small">対象日時:<code>2025-01-31</code>(時刻は 00:00 として扱う)</p>
      <input type="text" id="q4" placeholder="期待される結果: 2025-02-28 または 2025-03-02 など(説明を読んで判断)" />
      <div class="hint">1か月の加算は単純に月インデックスを +1 にする方法と、日数で加算する違いを考えてください。</div>
    </div>

    <div class="card">
      <h2>問題 5 — タイムゾーン差を取得</h2>
      <label>現在の環境の UTC との差を分で入力してください。(<code>getTimezoneOffset()</code> の値)</label>
      <input type="text" id="q5" placeholder="例: -540 (日本は -540 分)" />
      <div class="hint">注意:<code>getTimezoneOffset()</code> は "UTC - ローカル"(分)を返します。日本(UTC+9)は -540。</div>
    </div>

    <div style="display:flex;gap:10px;margin-top:12px;align-items:center">
      <button type="button" id="checkBtn">採点する</button>
      <button type="button" id="fillBtn" class="secondary">練習用にサンプルを埋める</button>
      <div id="scoreArea" class="score">スコア: —</div>
    </div>
  </form>

  <div id="feedback" style="margin-top:14px"></div>

  <details style="margin-top:16px"><summary>模範解答を表示(クリック)</summary>
    <div class="solution" id="solutions"></div>
  </details>

  <script>
    // ユーティリティ: 0埋め
    const z = n => (n<10? '0'+n: ''+n);

    // フォーマット YYYY-MM-DD HH:mm
    function formatLocalYMDHM(d){
      return d.getFullYear() + '-' + z(d.getMonth()+1) + '-' + z(d.getDate()) + ' ' + z(d.getHours()) + ':' + z(d.getMinutes());
    }
    function formatYMD(d){
      return d.getFullYear() + '-' + z(d.getMonth()+1) + '-' + z(d.getDate());
    }

    // 解答作成(模範)
    function makeSolutions(){
      const sol = {};
      // 1: 2025-12-25 15:30 (local) -> ISO (UTC)
      const d1 = new Date(2025,11,25,15,30,0); sol.q1 = d1.toISOString();

      // 2: parse 2025-03-01T09:00:00Z -> local formatted
      const d2 = new Date('2025-03-01T09:00:00Z'); sol.q2 = formatLocalYMDHM(d2);

      // 3: now + 48 hours -> ISO
      const now = new Date();
      const d3 = new Date(now.getTime() + 48 * 60 * 60 * 1000); sol.q3 = d3.toISOString();

      // 4: add 1 month to 2025-01-31
      // Approach: try to preserve day-of-month; if overflow, JS Date auto-adjusts
      const base4 = new Date(2025,0,31,0,0,0); // 2025-01-31
      const copy4 = new Date(base4.getTime());
      copy4.setMonth(copy4.getMonth() + 1);
      sol.q4 = formatYMD(copy4);

      // 5: timezone offset (minutes)
      sol.q5 = (new Date()).getTimezoneOffset().toString();

      return sol;
    }

    const solutions = makeSolutions();
    document.getElementById('solutions').textContent =
`問題 1: ${solutions.q1}
問題 2: ${solutions.q2}
問題 3: ${solutions.q3}
問題 4: ${solutions.q4}
問題 5: ${solutions.q5}

※ 問題3は採点時点の現在時刻に依存します。
※ 問題4は JavaScript の Date の "月をセットするときの自動補正" の結果です。`;

    // 採点ロジック
    function grade(){
      const out = [];
      let score = 0, total = 5;
      const q1 = document.getElementById('q1').value.trim();
      const q2 = document.getElementById('q2').value.trim();
      const q3 = document.getElementById('q3').value.trim();
      const q4 = document.getElementById('q4').value.trim();
      const q5 = document.getElementById('q5').value.trim();

      // q1: compare ISO strings (allow some leeway: ignore milliseconds differences when possible)
      try{
        const expected1 = solutions.q1;
        const e1 = new Date(expected1).toISOString();
        const a1 = new Date(q1).toISOString();
        if(a1 === e1){ score++; out.push({ok:true,msg:'問題1: 正解'});
        } else { out.push({ok:false,msg:`問題1: 不正解(期待: ${e1})`}); }
      }catch(e){ out.push({ok:false,msg:'問題1: 不正解(パースできません)'}); }

      // q2: exact string match with expected format
      try{
        const expected2 = solutions.q2;
        if(q2 === expected2){ score++; out.push({ok:true,msg:'問題2: 正解'});
        } else { out.push({ok:false,msg:`問題2: 不正解(期待: ${expected2})`}); }
      }catch(e){ out.push({ok:false,msg:'問題2: エラー'}); }

      // q3: allow a small tolerance (1 second) because now was captured at load time
      try{
        const expected3 = solutions.q3;
        const got3 = new Date(q3).toISOString();
        // compare to expected by parsing times (allow 1 second difference)
        const tExp = Date.parse(expected3);
        const tGot = Date.parse(got3);
        if(Math.abs(tExp - tGot) <= 1000){ score++; out.push({ok:true,msg:'問題3: 正解'});
        } else { out.push({ok:false,msg:`問題3: 不正解(期待: ${expected3})`}); }
      }catch(e){ out.push({ok:false,msg:'問題3: 不正解(パースできません)'}); }

      // q4: exact match to JS's setMonth behavior
      try{
        const expected4 = solutions.q4;
        if(q4 === expected4){ score++; out.push({ok:true,msg:'問題4: 正解'});
        } else { out.push({ok:false,msg:`問題4: 不正解(期待: ${expected4})`}); }
      }catch(e){ out.push({ok:false,msg:'問題4: エラー'}); }

      // q5: numeric compare (string equality ok)
      try{
        const expected5 = solutions.q5;
        if(q5 === expected5){ score++; out.push({ok:true,msg:'問題5: 正解'});
        } else { out.push({ok:false,msg:`問題5: 不正解(期待: ${expected5})`}); }
      }catch(e){ out.push({ok:false,msg:'問題5: エラー'}); }

      // render
      const fb = document.getElementById('feedback'); fb.innerHTML = '';
      out.forEach(o=>{
        const d = document.createElement('div'); d.className = 'result '+(o.ok? 'ok':'ng'); d.textContent = o.msg; fb.appendChild(d);
      });
      document.getElementById('scoreArea').textContent = `スコア: ${score} / ${total}`;
    }

    document.getElementById('checkBtn').addEventListener('click', grade);

    // サンプルを埋める(練習モード)
    document.getElementById('fillBtn').addEventListener('click', ()=>{
      document.getElementById('q1').value = solutions.q1;
      document.getElementById('q2').value = solutions.q2;
      document.getElementById('q3').value = solutions.q3;
      document.getElementById('q4').value = solutions.q4;
      document.getElementById('q5').value = solutions.q5;
      document.getElementById('scoreArea').textContent = 'スコア: —';
      document.getElementById('feedback').innerHTML = '';
    });

    // 戻ってきたときに solutions を最新化(特に問題3, q5 はページロード時点)
    window.addEventListener('focus', ()=>{
      Object.assign(window, { __sol: makeSolutions() });
    });

    // 初期化: update solutions area with a live note
    document.getElementById('solutions').textContent += '\n\n(注)問題3と問題5は「現在時刻」に依存します。サンプルを埋めた後で採点してください。';
  </script>
</body>
</html>
HTML

このアプリは:

  • 年月日で Date を作る練習(ISO 形式での出力)
  • ISO 文字列からのパースとローカル時刻表示の確認
  • 今から 48 時間後(ミリ秒による加算)の計算
  • 1か月を加算したときの JS の自動補正(例:1月31日 → ?)の動作確認
  • getTimezoneOffset() の確認(環境のUTC差)

を含み、ボタンで 自動採点、模範解答表示、練習用サンプル入力ができます。

追加問題セット(おすすめ構成)

初級(基礎の確認)

  1. 現在の日付を取得して表示
    • new Date() を使う
    • 形式:YYYY-MM-DD
      ✅ 採点例:「きょうの年月日が正しく出力されているか」
  2. 特定の日付を作って曜日を求める
    • 入力:2000-01-01
    • 出力:「土曜日」
      ✅ 採点:getDay() の結果を曜日文字列に変換できるか

中級(計算・加算)

  1. 7日後の日付を求める
    • 入力:任意の日付
    • 処理:setDate(getDate() + 7)
    • 出力:7日後の日付(ローカル)
      ✅ 採点:計算結果が正しいか(自動でDate差を比較)
  2. 「月をまたぐ」加算の確認
    • 入力:2025-01-31
    • 処理:1か月加算(setMonth(getMonth() + 1))
    • 出力:2025-03-03(自動補正の動作を理解)
      ✅ 採点:結果がDateの仕様通りになっているか

上級(時刻・UTC・タイムゾーン)

  1. UTC からローカルへの変換
    • 入力:2025-10-13T06:00:00Z
    • 出力:ローカル時刻(例:日本なら 15:00)
      ✅ 採点:toLocaleString() の結果がUTCとの差分を反映しているか
  2. タイムゾーン差を求める
    • 出力:getTimezoneOffset() の結果(分単位)を時・分に直して表示
      ✅ 採点:「例:-540 → 9時間(日本標準時)」と正しく計算できているか
  3. 未来のイベントまでの残り時間
    • 入力:未来の日時(例:2025-12-31 23:59:59)
    • 出力:「残り◯日◯時間◯分◯秒」
      ✅ 採点:差分をミリ秒単位で計算できているか

発展(挑戦問題)

  1. 2つの日付の差(日数)を求める
    • 入力:2つの日付フォーム
    • 出力:「○日間」
      ✅ 採点:小数点以下切り捨てで正しい日数か確認
  2. ISO 文字列の生成
    • 入力:年月日時分秒
    • 出力:toISOString() の形式(例:2025-10-13T06:30:00.000Z
      ✅ 採点:正しいISO形式か

これらをHTMLアプリに組み込むと、レベル選択(初級/中級/上級)式にして段階的に遊べるようになります。
たとえば:

<select id="level">
  <option>初級</option>
  <option>中級</option>
  <option>上級</option>
</select>
JavaScript

を使って、問題を切り替えながら採点します。

全レベル(初級〜上級)の練習問題

<!DOCTYPE html>
<html lang="ja">
<head>
  <meta charset="UTF-8" />
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
  <title>JavaScript Date 練習(自動採点付き)</title>
  <style>
    body { font-family: 'Segoe UI', sans-serif; margin: 2em; background: #f9fafb; }
    h1 { text-align: center; }
    section { background: #fff; padding: 1em 2em; margin-bottom: 1.5em; border-radius: 12px; box-shadow: 0 2px 5px rgba(0,0,0,0.1); }
    label, button, select { margin-top: 0.5em; display: block; }
    .result { margin-top: 0.5em; font-weight: bold; }
    .correct { color: green; }
    .wrong { color: red; }
  </style>
</head>
<body>
  <h1>🕓 JavaScript Date 練習(自動採点)</h1>
  <p>レベルを選んで日付操作を体験しよう!</p>

  <label for="level">レベル選択:</label>
  <select id="level">
    <option value="beginner">初級</option>
    <option value="intermediate">中級</option>
    <option value="advanced">上級</option>
  </select>

  <section id="problem"></section>
  <button id="checkBtn">採点する</button>
  <div id="feedback" class="result"></div>

  <script>
    const problems = {
      beginner: [
        {
          text: '現在の日付(YYYY-MM-DD)を表示してください。',
          check: ans => ans === new Date().toISOString().slice(0,10)
        },
        {
          text: '2000-01-01 の曜日を表示してください。(例:土曜日)',
          check: ans => /土/.test(ans)
        }
      ],
      intermediate: [
        {
          text: '今日から7日後の日付を表示してください。',
          check: ans => {
            const now = new Date();
            const future = new Date(now);
            future.setDate(now.getDate() + 7);
            return ans === future.toISOString().slice(0,10);
          }
        },
        {
          text: '2025-01-31 に1か月加算した日付を表示してください。',
          check: ans => ans === new Date(2025, 2, 3).toISOString().slice(0,10)
        }
      ],
      advanced: [
        {
          text: 'UTC時刻 2025-10-13T06:00:00Z をローカル時刻で表示してください。',
          check: ans => ans.includes('2025') && ans.includes(':')
        },
        {
          text: 'あなたの環境のUTCとの差(時間)を表示してください。',
          check: ans => /時間/.test(ans)
        },
        {
          text: '未来のイベント(2025-12-31 23:59:59)までの残り時間(日数)を表示してください。',
          check: ans => /日/.test(ans)
        },
        {
          text: '2つの日付の差(日数)を求めて表示してください。例:2025-01-01 と 2025-01-10 → 9日',
          check: ans => /9日/.test(ans)
        },
        {
          text: '入力した年月日時分秒をISO文字列に変換して表示してください。',
          check: ans => /T\d{2}:\d{2}:\d{2}\.\d{3}Z/.test(ans)
        }
      ]
    };

    const problemEl = document.getElementById('problem');
    const feedbackEl = document.getElementById('feedback');
    const checkBtn = document.getElementById('checkBtn');
    const levelSel = document.getElementById('level');

    let currentProblemIndex = 0;

    function renderProblem() {
      const level = levelSel.value;
      const p = problems[level][currentProblemIndex];
      problemEl.innerHTML = `
        <h2>問題 ${currentProblemIndex + 1}</h2>
        <p>${p.text}</p>
        <label>あなたの答え:</label>
        <input id="answer" type="text" style="width:100%;padding:0.5em;" />
      `;
      feedbackEl.textContent = '';
    }

    function checkAnswer() {
      const level = levelSel.value;
      const p = problems[level][currentProblemIndex];
      const ans = document.getElementById('answer').value.trim();
      const ok = p.check(ans);
      feedbackEl.textContent = ok ? '✅ 正解!' : '❌ 不正解...もう一度挑戦!';
      feedbackEl.className = 'result ' + (ok ? 'correct' : 'wrong');
      if (ok) {
        setTimeout(() => {
          currentProblemIndex++;
          if (currentProblemIndex >= problems[level].length) {
            feedbackEl.textContent = '🎉 全問クリア!おめでとう!';
          } else {
            renderProblem();
          }
        }, 1500);
      }
    }

    levelSel.addEventListener('change', () => {
      currentProblemIndex = 0;
      renderProblem();
    });

    checkBtn.addEventListener('click', checkAnswer);

    renderProblem();
  </script>
</body>
</html>
HTML

中級・上級の実践寄り

今の練習アプリでは以下の内容を体験できます:

  • 🧮 中級: 日付差・加算・月跨ぎの処理、うるう年・タイムゾーンの扱い
  • 🚀 上級: 夏時間の境界テスト、UTCとローカルの相互変換、フォーマット関数の実装
<!DOCTYPE html>
<html lang="ja">
<head>
  <meta charset="UTF-8" />
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
  <title>JavaScript Date 実践練習(中級・上級)</title>
  <style>
    body { font-family: 'Segoe UI', sans-serif; margin: 2em; background: #f9fafb; }
    h1 { text-align: center; }
    section { background: #fff; padding: 1em 2em; margin-bottom: 1.5em; border-radius: 12px; box-shadow: 0 2px 5px rgba(0,0,0,0.1); }
    label, button, select { margin-top: 0.5em; display: block; }
    .result { margin-top: 0.5em; font-weight: bold; }
    .correct { color: green; }
    .wrong { color: red; }
  </style>
</head>
<body>
  <h1>🧭 JavaScript Date 実践練習(中級・上級)</h1>
  <p>中級〜上級問題で、日付と時刻の扱いを実践的にマスターしよう。</p>

  <label for="level">レベル選択:</label>
  <select id="level">
    <option value="intermediate">中級</option>
    <option value="advanced">上級</option>
  </select>

  <section id="problem"></section>
  <button id="checkBtn">採点する</button>
  <div id="feedback" class="result"></div>

  <script>
    const problems = {
      intermediate: [
        {
          text: '今日から7日後の日付を表示してください。',
          check: ans => {
            const now = new Date();
            const future = new Date(now);
            future.setDate(now.getDate() + 7);
            return ans === future.toISOString().slice(0,10);
          }
        },
        {
          text: '2025-01-31 に1か月加算した日付を表示してください。',
          check: ans => ans === new Date(2025, 2, 3).toISOString().slice(0,10)
        },
        {
          text: '2つの日付(2025-01-01 と 2025-01-10)の差(日数)を表示してください。',
          check: ans => /9日/.test(ans)
        }
      ],
      advanced: [
        {
          text: 'UTC時刻 2025-10-13T06:00:00Z をローカル時刻で表示してください。',
          check: ans => ans.includes('2025') && ans.includes(':')
        },
        {
          text: 'あなたの環境のUTCとの差(時間)を表示してください。',
          check: ans => /時間/.test(ans)
        },
        {
          text: '未来のイベント(2025-12-31 23:59:59)までの残り時間(日数)を表示してください。',
          check: ans => /日/.test(ans)
        },
        {
          text: '入力した年月日時分秒をISO文字列に変換して表示してください。',
          check: ans => /T\d{2}:\d{2}:\d{2}\.\d{3}Z/.test(ans)
        }
      ]
    };

    const problemEl = document.getElementById('problem');
    const feedbackEl = document.getElementById('feedback');
    const checkBtn = document.getElementById('checkBtn');
    const levelSel = document.getElementById('level');

    let currentProblemIndex = 0;

    function renderProblem() {
      const level = levelSel.value;
      const p = problems[level][currentProblemIndex];
      problemEl.innerHTML = `
        <h2>問題 ${currentProblemIndex + 1}</h2>
        <p>${p.text}</p>
        <label>あなたの答え:</label>
        <input id="answer" type="text" style="width:100%;padding:0.5em;" />
      `;
      feedbackEl.textContent = '';
    }

    function checkAnswer() {
      const level = levelSel.value;
      const p = problems[level][currentProblemIndex];
      const ans = document.getElementById('answer').value.trim();
      const ok = p.check(ans);
      feedbackEl.textContent = ok ? '✅ 正解!' : '❌ 不正解...もう一度挑戦!';
      feedbackEl.className = 'result ' + (ok ? 'correct' : 'wrong');
      if (ok) {
        setTimeout(() => {
          currentProblemIndex++;
          if (currentProblemIndex >= problems[level].length) {
            feedbackEl.textContent = '🎉 全問クリア!おめでとう!';
          } else {
            renderProblem();
          }
        }, 1500);
      }
    }

    levelSel.addEventListener('change', () => {
      currentProblemIndex = 0;
      renderProblem();
    });

    checkBtn.addEventListener('click', checkAnswer);

    renderProblem();
  </script>
</body>
</html>
HTML
タイトルとURLをコピーしました