設定難易度

結婚記念日、付き合った日、入籍記念日、誕生日など。

大切な記念日まで「あと何日?」を、iPhoneのホーム画面ですぐ確認できるScriptable用の記念日カウントダウンウィジェットです。

記念日って日付そのものは覚えていても、「今年で何周年やったかな?」「あと何日?」となることが意外とあります。カレンダーを開いて確認するほどでもないので、ホーム画面を見ればパッと分かるように作りました。
一度日付を登録しておけば、あとは毎日自動で計算。次の記念日までの日数だけでなく、次で何周年になるのかも自動で表示します。

複数の記念日も登録できるので、結婚記念日や誕生日などをまとめて管理できます。

ウィジェットの表示内容

記念日当日は「今日です!」と表示し、過ぎた記念日は翌年の同じ日までのカウントダウンに切り替えます。

サイズごとの表示

  • 小:次に近い記念日を1件表示します。
  • 中:最大3件、大:最大5件を、次に来る記念日が近い順に表示します。
  • 各記念日の名前・残り日数・次で何周年か・開始日を確認できます。

設定手順

上から順に進めてください。コードの保存と設定のコピーに必要な操作欄は、それぞれの手順の中にあります。

  1. Scriptableを用意する

    App StoreでScriptableを開き、iPhoneにインストールします。すでに入っている場合は次へ進んでください。

  2. コードをコピーして保存する

    下のコード欄の「コピー」を押します。Scriptableへ移動し、「+」から新しいスクリプトを作ってコード全文を貼り付け、記念日ウィジェットなどの名前で保存してください。

    保存したらこのページに戻り、次の手順へ進みます。

    // Version: 1.0.0
    // =====================================================
    // 記念日カウントダウン - Scriptable Widget
    // =====================================================
    //
    // ウィジェットのパラメータに「日付,ラベル」を設定
    //
    // 例:
    // 2019-06-15,結婚記念日
    //
    // 複数登録:
    // 2019-06-15,結婚記念日;2022-03-01,同棲記念日
    //
    // 設定:
    // ホーム画面のウィジェットを長押し
    // →「ウィジェットを編集」
    // → パラメータ欄に入力
    // =====================================================
    
    
    // =====================================================
    // パラメータ
    // =====================================================
    
    const rawParam =
      (typeof args !== "undefined" && args.widgetParameter)
        ? args.widgetParameter
        : "";
    
    const hasParam = rawParam.trim().length > 0;
    
    const DEFAULT_ENTRIES = [
      {
        dateStr: "2019-06-15",
        label: "結婚記念日"
      }
    ];
    
    
    // =====================================================
    // パラメータ解析
    // =====================================================
    
    function parseEntries(raw) {
    
      if (!raw.trim()) {
        return DEFAULT_ENTRIES;
      }
    
      return raw
        .split(";")
        .map(chunk => chunk.trim())
        .filter(chunk => chunk.length > 0)
        .map(chunk => {
    
          const parts =
            chunk
              .split(",")
              .map(s => s.trim());
    
          const dateStr = parts.shift();
    
          return {
            dateStr,
            label: parts.join(",") || "記念日"
          };
    
        });
    
    }
    
    
    // =====================================================
    // 日付処理
    // =====================================================
    
    function parseDate(str) {
    
      const d =
        new Date(str + "T00:00:00");
    
      return isNaN(d.getTime())
        ? null
        : d;
    
    }
    
    
    function daysUntilNextAnniversary(start, now) {
    
      const today =
        new Date(
          now.getFullYear(),
          now.getMonth(),
          now.getDate()
        );
    
      let next =
        new Date(
          now.getFullYear(),
          start.getMonth(),
          start.getDate()
        );
    
      if (next < today) {
    
        next =
          new Date(
            now.getFullYear() + 1,
            start.getMonth(),
            start.getDate()
          );
    
      }
    
      const daysUntil =
        Math.round(
          (next - today) / 86400000
        );
    
      const anniversaryNumber =
        next.getFullYear() - start.getFullYear();
    
      return {
        daysUntil,
        anniversaryNumber,
        nextDate: next
      };
    
    }
    
    
    // =====================================================
    // データ作成
    // =====================================================
    
    const now = new Date();
    
    const rawEntries =
      parseEntries(rawParam);
    
    const entries =
      rawEntries
        .map(e => {
    
          const start =
            parseDate(e.dateStr);
    
          if (!start) {
            return null;
          }
    
          const result =
            daysUntilNextAnniversary(
              start,
              now
            );
    
          return {
            label: e.label,
            start,
            daysUntil: result.daysUntil,
            anniversaryNumber: result.anniversaryNumber,
            nextDate: result.nextDate
          };
    
        })
        .filter(e => e !== null)
        .sort(
          (a, b) =>
            a.daysUntil - b.daysUntil
        );
    
    
    // データが無い場合
    
    if (entries.length === 0) {
    
      entries.push({
        label: "記念日",
        start: new Date("2019-06-15T00:00:00"),
        daysUntil: 0,
        anniversaryNumber: 1
      });
    
    }
    
    
    // =====================================================
    // 日付フォーマット
    // =====================================================
    
    const dateFormatter =
      new DateFormatter();
    
    dateFormatter.dateFormat =
      "yyyy年M月d日";
    
    
    // =====================================================
    // カラー
    // =====================================================
    
    const COLOR_MAIN =
      Color.white();
    
    const COLOR_SUB =
      new Color("#D8D2EA");
    
    const COLOR_ACCENT =
      new Color("#FFD166");
    
    const COLOR_BADGE =
      new Color("#FFFFFF", 0.12);
    
    const COLOR_WARNING =
      new Color("#FFAA8C");
    
    
    // =====================================================
    // ウィジェット
    // =====================================================
    
    const widget =
      new ListWidget();
    
    
    // 背景グラデーション
    
    const gradient =
      new LinearGradient();
    
    gradient.locations =
      [0, 1];
    
    gradient.colors = [
      new Color("#292542"),
      new Color("#514476")
    ];
    
    widget.backgroundGradient =
      gradient;
    
    
    // =====================================================
    // サイズ判定
    // =====================================================
    
    const family =
      config.widgetFamily || "small";
    
    const isSmall =
      family === "small";
    
    const isMedium =
      family === "medium";
    
    const maxItems =
      family === "large"
        ? 5
        : family === "medium"
          ? 3
          : 1;
    
    
    // =====================================================
    // 余白
    // =====================================================
    
    widget.setPadding(
      isSmall ? 15 : 16,
      isSmall ? 15 : 18,
      isSmall ? 14 : 16,
      isSmall ? 15 : 18
    );
    
    
    // =====================================================
    // Small用デザイン
    // =====================================================
    
    function addSmallEntry(
      container,
      entry
    ) {
    
      // ---------------------------------
      // 記念日名
      // ---------------------------------
    
      const header =
        container.addStack();
    
      header.centerAlignContent();
    
    
      const label =
        header.addText(entry.label);
    
      label.font =
        Font.semiboldSystemFont(12);
    
      label.textColor =
        COLOR_SUB;
    
      label.lineLimit = 1;
    
      label.minimumScaleFactor =
        0.7;
    
    
      // ---------------------------------
      // メイン
      // ---------------------------------
    
      container.addSpacer(9);
    
    
      const main =
        container.addStack();
    
      main.layoutHorizontally();
    
      main.centerAlignContent();
    
    
      if (entry.daysUntil === 0) {
    
        const today =
          main.addText("今日です!");
    
        today.font =
          Font.boldSystemFont(27);
    
        today.textColor =
          COLOR_ACCENT;
    
      } else {
    
        const prefix =
          main.addText("あと");
    
        prefix.font =
          Font.semiboldSystemFont(15);
    
        prefix.textColor =
          COLOR_SUB;
    
    
        main.addSpacer(3);
    
    
        const number =
          main.addText(
            String(entry.daysUntil)
          );
    
        number.font =
          Font.boldSystemFont(38);
    
        number.textColor =
          COLOR_MAIN;
    
        number.minimumScaleFactor =
          0.65;
    
    
        main.addSpacer(2);
    
    
        const unit =
          main.addText("日");
    
        unit.font =
          Font.boldSystemFont(16);
    
        unit.textColor =
          COLOR_MAIN;
    
      }
    
    
      // ---------------------------------
      // 周年バッジ
      // ---------------------------------
    
      container.addSpacer(8);
    
    
      const badgeRow =
        container.addStack();
    
      badgeRow.centerAlignContent();
    
    
      const badge =
        badgeRow.addStack();
    
      badge.backgroundColor =
        COLOR_BADGE;
    
      badge.cornerRadius =
        7;
    
      badge.setPadding(
        4,
        8,
        4,
        8
      );
    
    
      const badgeText =
        badge.addText(
          `次で ${entry.anniversaryNumber}周年`
        );
    
      badgeText.font =
        Font.semiboldSystemFont(10);
    
      badgeText.textColor =
        COLOR_ACCENT;
    
    
      // ---------------------------------
      // 開始日
      // ---------------------------------
    
      container.addSpacer(7);
    
    
      const date =
        container.addText(
          `${dateFormatter.string(entry.start)} 〜`
        );
    
      date.font =
        Font.systemFont(9);
    
      date.textColor =
        COLOR_SUB;
    
      date.minimumScaleFactor =
        0.7;
    
    }
    
    
    // =====================================================
    // Medium / Large用
    // =====================================================
    
    function addEntry(
      container,
      entry,
      isFirst
    ) {
    
      const row =
        container.addStack();
    
      row.layoutHorizontally();
    
      row.centerAlignContent();
    
    
      // ---------------------------------
      // 左側
      // ---------------------------------
    
      const left =
        row.addStack();
    
      left.layoutVertically();
    
    
      const label =
        left.addText(entry.label);
    
      label.font =
        Font.semiboldSystemFont(
          isFirst ? 12 : 11
        );
    
      label.textColor =
        COLOR_SUB;
    
      label.lineLimit = 1;
    
    
      left.addSpacer(3);
    
    
      const date =
        left.addText(
          `${dateFormatter.string(entry.start)} 〜`
        );
    
      date.font =
        Font.systemFont(8);
    
      date.textColor =
        COLOR_SUB;
    
    
      // ---------------------------------
      // 中央スペース
      // ---------------------------------
    
      row.addSpacer();
    
    
      // ---------------------------------
      // 周年
      // ---------------------------------
    
      const anniversary =
        row.addStack();
    
      anniversary.backgroundColor =
        COLOR_BADGE;
    
      anniversary.cornerRadius =
        6;
    
      anniversary.setPadding(
        4,
        7,
        4,
        7
      );
    
    
      const anniversaryText =
        anniversary.addText(
          `${entry.anniversaryNumber}周年`
        );
    
      anniversaryText.font =
        Font.semiboldSystemFont(9);
    
      anniversaryText.textColor =
        COLOR_ACCENT;
    
    
      row.addSpacer(10);
    
    
      // ---------------------------------
      // 残り日数
      // ---------------------------------
    
      const days =
        row.addText(
          entry.daysUntil === 0
            ? "今日!"
            : `あと${entry.daysUntil}日`
        );
    
      days.font =
        Font.boldSystemFont(
          isFirst ? 20 : 16
        );
    
      days.textColor =
        entry.daysUntil === 0
          ? COLOR_ACCENT
          : COLOR_MAIN;
    
      days.minimumScaleFactor =
        0.6;
    
    }
    
    
    // =====================================================
    // 表示
    // =====================================================
    
    const shown =
      entries.slice(
        0,
        maxItems
      );
    
    
    if (isSmall) {
    
      addSmallEntry(
        widget,
        shown[0]
      );
    
    } else {
    
      shown.forEach(
        (entry, index) => {
    
          if (index > 0) {
    
            widget.addSpacer(8);
    
    
            const divider =
              widget.addStack();
    
            divider.size =
              new Size(0, 1);
    
            divider.backgroundColor =
              new Color(
                "#FFFFFF",
                0.12
              );
    
    
            widget.addSpacer(8);
    
          }
    
    
          addEntry(
            widget,
            entry,
            index === 0
          );
    
        }
      );
    
    }
    
    
    // =====================================================
    // パラメータ未設定
    // =====================================================
    
    if (!hasParam) {
    
      widget.addSpacer();
    
    
      const hint =
        widget.addText(
          "⚠︎ サンプル表示中"
        );
    
      hint.font =
        Font.systemFont(8);
    
      hint.textColor =
        COLOR_WARNING;
    
    }
    
    
    // =====================================================
    // 表示
    // =====================================================
    
    if (config.runsInWidget) {
    
      Script.setWidget(widget);
    
    } else {
    
      if (isSmall) {
    
        await widget.presentSmall();
    
      } else if (isMedium) {
    
        await widget.presentMedium();
    
      } else {
    
        await widget.presentLarge();
    
      }
    
    }
    
    Script.complete();
  3. ウィジェットの設定を作る

    下の入力欄で自分用の設定を作り、生成されたパラメーターをコピーします。詳しい設定仕様は、設定手順の下でも確認できます。

    記念日パラメーター作成

    記念日を入力すると、ウィジェット用のパラメーターを自動で作成します。
    複数の記念日もまとめて登録できます。

    記念日の設定

    記念日 1


    生成されたパラメーター

    記念日を入力してください

    コピーしました

    コピーできたら、次の手順でホーム画面のウィジェットへ貼り付けます。

  4. ホーム画面に追加して設定する

    iPhoneのホーム画面を長押しし、ウィジェット追加画面からScriptableを選びます。この記事の対応サイズの説明に合わせてサイズを選んで追加してください。

    追加したウィジェットを長押しして「ウィジェットを編集」を開き、「Script」に保存したスクリプトを選びます。

    「Parameter」に直前の手順でコピーした値を貼り付けます。設定を変えるときも、同じ入力欄で作り直して貼り替えてください。

    編集画面を閉じて表示を確認します。ホーム画面への反映には少し時間がかかる場合があります。

パラメータ・カスタマイズ

パラメーターに記念日を設定

このウィジェットでは、Parameter(パラメータ)に日付と記念日の名前を設定します。
ウィジェットを長押しして、

「ウィジェットを編集」→「Parameter」

に、下記の形式で日付と記念日の名前を入力してください。

例:日付,記念日の名前

と入力します。

例えば、2019年6月15日が結婚記念日の場合は、

2019-06-15,結婚記念日

と入力します。
設定すると、記念日までの「あと○日」と「次で○周年」が自動的に計算されます。

複数の記念日を登録する場合

中・大サイズでは、複数の記念日をまとめて表示できます。
複数登録する場合は、それぞれの記念日を ;(セミコロン)で区切ります。
例えば、

2019-06-15,結婚記念日;2022-03-01,同棲記念日;2024-01-01,入籍記念日

と入力します。
登録した順番ではなく、次にやってくる記念日が近い順に自動で表示されます。 

設定手順内のジェネレーターでパラメーターを生成してコピーすると簡単です。

記念日ごとにウィジェットを分ける場合

「結婚記念日と誕生日を、それぞれ別の小ウィジェットで表示したい」という使い方もできます。
同じ「記念日ウィジェット」のスクリプトを使って、ホーム画面にScriptableウィジェットを複数配置して、それぞれにパラメーターを設定してください。
スクリプトを複製する必要はありません。
1つのスクリプトを複数のウィジェットから使用し、それぞれ別のパラメーターを設定できます。 

動作について

登録するのは「今年の記念日」ではなく、最初の記念日の日付です。
一度登録しておけば、現在の日付から次回の記念日までの日数を自動的に計算します。
年が変わっても設定し直す必要はなく、「次で何周年」も毎年自動的に更新されます。 

動作確認・使用結果

確認した端末 iPhone 17 ProMAX
iOS iOS27
Scriptable 1.7.19
確認したサイズ 小・中・大
バージョン 1.0.0
動作確認日 2026-09-18

表示・使用時のポイント

記念日当日は「今日です!」と表示し、過ぎた記念日は翌年の同じ日までのカウントダウンに切り替えます。複数登録しても日付順に並びます。

更新履歴

1.0.0

  • 初回リリース

よくあるエラーと対処

確認すること
表示されない場合は、ウィジェット編集画面の「Script」に保存したスクリプトが選ばれているかも確認してください。コードを貼り直すときは全文を置き換えます。
周年数が違う
今年の日付ではなく、最初にその記念日を迎えた年月日を登録してください。
複数の記念日が表示されない
小サイズは1件だけです。中・大サイズを選び、項目間を半角セミコロンで区切ってください。
名前や日付が読み取れない
下の設定フォームで作り直してコピーし、Parameterを全文置き換えてください。

注意事項・制限

  • 日付単位のカウントダウンです。時刻単位の通知機能はありません。
  • 同じスクリプトを複数のウィジェットから使えます。各ウィジェットに別のParameterを設定してください。
  • ホーム画面の更新タイミングはiOSが管理するため、操作やデータの変更がすぐに反映されない場合があります。

データ提供・参考資料

このウィジェットにはデータ提供や参考資料は特にありません。

SHARE

この記事をシェア