バックグラウンドサーバースクリプトに cron 式を直接指定する
1.5.8.1 のバックグラウンドサーバースクリプトのスケジュールは、毎時・毎日・毎週・毎月・一回のみの 5 種類から選び、時刻や曜日を入力する方式です。このページは、cron 式を直接入力できるようにし、入力中に「どう動くか」(説明文と次回実行日時)を表示する本体改修の設計メモで、本体の標準機能ではありません。調査は 1.5.1.0 を対象に行い、前提にした現行実装は 1.5.8.1 のソースで確かめています。
前提にした現行実装
詳しくは バックグラウンドサーバースクリプトの仕組み の「スケジュールと cron 式」にあります。要点は次のとおりです。
- スケジュール(
BackgroundSchedule)は種別ごとに別のプロパティ(ScheduleHourlyTime・ScheduleDailyTime・ScheduleWeeklyWeek・ScheduleWeeklyTime・ScheduleMonthlyMonth・ScheduleMonthlyDay・ScheduleMonthlyTime・ScheduleOnlyOnceTime)を持ちます。使わない種別のプロパティも JSON に残ります(BackgroundSchedule.cs#L7-L21)。 GetTrigger()がこれを Quartz の 7 フィールドの cron 式に組み立て、WithCronSchedule()に渡しています(BackgroundServerScriptUtilities.cs#L112-L179)。内部はすでに cron 式なので、入力を cron 式にしても後段は変わりません。- 画面はテナント管理のスケジュールのダイアログで、種別のドロップダウンに応じて
ServerScriptScheduleHourlyFieldなどのフィールドセットを出し分けます(TenantUtilities.cs#L2438-L2563)。 - サーバー側の検証は時刻の
HH:mmの正規表現だけです(BackgroundScheduleValidators.cs#L22-L39)。
現行の方式でできないこと:
| 制約 | 内容 |
|---|---|
| 分の自由度が低い | 毎時は 5 分刻みのドロップダウンだけ |
| 複合条件が書けない | 平日だけ、第 N 曜日、9〜17 時の間だけ、など |
| 間隔が書けない | 15 分ごと、3 時間ごと、など |
| 種別が固定 | 新しいパターンにはコードの改修が要る |
Quartz の cron 式
Quartz.NET の cron 式は、Unix の cron(5 フィールド)と違い 7 フィールドです。
| 位置 | フィールド | 値 | 特殊文字 |
|---|---|---|---|
| 1 | 秒 | 0-59 | , - * / |
| 2 | 分 | 0-59 | , - * / |
| 3 | 時 | 0-23 | , - * / |
| 4 | 日 | 1-31 | , - * / ? L W |
| 5 | 月 | 1-12 または JAN-DEC | , - * / |
| 6 | 曜日 | 1-7(1 が日曜)または SUN-SAT | , - * / ? L # |
| 7 | 年 | 空 または 1970-2099 | , - * /(省略可) |
日と曜日はどちらか一方を ? にします。直接入力なら、次のような指定ができるようになります。
| cron 式 | 意味 |
|---|---|
0 */15 9-17 ? * MON-FRI * | 平日 9:00〜17:59 に 15 分ごと |
0 0 9 ? * 3#2 * | 毎月第 2 火曜日の 9:00 |
0 0 0 L * ? * | 毎月末日の 0:00 |
0 0 */3 * * ? * | 3 時間ごと |
0 30 8,12,18 * * ? * | 毎日 8:30・12:30・18:30 |
Quartz.NET の CronExpression クラスで検証と次回実行日時の計算ができます。
bool isValid = CronExpression.IsValidExpression("0 */15 9-17 ? * MON-FRI *");
var cron = new CronExpression("0 */15 9-17 ? * MON-FRI *");
cron.TimeZone = TimeZoneInfo.FindSystemTimeZoneById("Asia/Tokyo");
DateTimeOffset? next = cron.GetNextValidTimeAfter(DateTimeOffset.UtcNow);画面の設計
既存の簡易設定を残すかで 3 案を比べ、B を採用します。
| 案 | 長所 | 短所 |
|---|---|---|
| A:cron 式だけ | データが単純になる | cron に慣れない人には難しい |
| B:簡易設定と cron 式を切り替え | 既存の操作と設定データをそのまま使える | 画面が少し複雑になる |
| C:簡易設定を cron 式に変換して表示 | 学習になる | cron 式から簡易設定への逆変換が難しい |
図を読み込み中…
簡易設定から cron 式に切り替えたとき、今の設定を cron 式にして表示すると学習にもなります。
データと処理の変更
BackgroundScheduleにCronExpression(文字列)を追加し、ScheduleTypeに"cron"を足します。既存の種別とプロパティは変えません。GetTrigger()にcase "cron":を足し、CronExpression.IsValidExpression()が通ればその文字列をそのまま使います。現行は未知の種別でArgumentExceptionを投げるので(BackgroundServerScriptUtilities.cs#L165-L166)、分岐の追加は必須です。タイムゾーンの決め方は既存のままです。
プレビュー
| 方式 | 応答 | 実装コスト | サーバー負荷 |
|---|---|---|---|
| A:クライアント側の JavaScript ライブラリ | 即時 | 低 | なし |
| B:サーバーの API(Ajax) | 遅れあり | 中 | あり |
| C:Svelte のコンポーネント+ライブラリ | 即時 | 中 | なし |
説明文はクライアント側(方式 A)で作り、次回実行日時は Quartz の独自拡張(L・W・#)とタイムゾーンを正しく扱えるサーバー側で計算します。
| ライブラリ | 機能 | サイズ | 日本語 | Quartz の 7 フィールド |
|---|---|---|---|---|
| cronstrue | cron 式 → 説明文 | 約 10 KB | 対応 | 対応 |
| cron-parser | 次回実行日時 | 約 15 KB | - | 5 フィールドだけ |
cronstrue は ?・L・W・#・年・秒を正しく説明文にします。dayOfWeekStartIndexZero: false を必ず指定します。 既定値のままだと曜日の数字を Unix 式(0 が日曜)で解釈し、Quartz(1 が日曜)と 1 日ずれます(MON などの名前は影響を受けません)。
function describeCron(cronExpression) {
try {
return cronstrue.toString(cronExpression, {
locale: 'ja',
dayOfWeekStartIndexZero: false, // 必須:Quartz は 1 が日曜
use24HourTimeFormat: true,
});
} catch (e) {
return null;
}
}| cron 式 | cronstrue の出力(日本語) |
|---|---|
0 */15 9-17 ? * MON-FRI * | 15 分ごと, 09:00 と 17:59 の間、月曜日 から 金曜日 まで |
0 0 9 ? * 2#2 * | 09:00、月のうち 2 番目 月曜日 |
0 0 0 L * ? * | 00:00、最終日に |
0 0 12 15W * ? * | 12:00、月の 15 日の直近の平日 に |
0 0 12 ? * 6L * | 12:00、月の最後の 金曜日 に |
0 0 9 15 3 ? 2026 | 09:00、月の 15 日目、3月 でのみ、2026 でのみ |
*/30 * * * * ? * | 30 秒ごと |
図を読み込み中…
プレビュー用には TenantsController に、CronExpression と TimeZoneId を受け取り、IsValidExpression() の結果と次回実行日時 5 件を返すエンドポイントを足します(任意)。
検証
| 層 | タイミング | 内容 |
|---|---|---|
| クライアント | 入力中 | フィールド数が 6〜7 か、cronstrue で解釈できるか |
| サーバーの API | プレビュー時 | CronExpression.IsValidExpression() と次回実行日時の計算 |
| サーバーの保存時 | 追加・変更 | 空でないこと、IsValidExpression()、次回実行日時があること(過去しか指さない式を拒否) |
保存時の検証は BackgroundScheduleValidators に足し、エラーの種類として InvalidCronExpression を新設します。
改修箇所
| ファイル | 内容 |
|---|---|
Libraries/Settings/BackgroundSchedule.cs | CronExpression を追加 |
Libraries/Settings/BackgroundServerScriptUtilities.cs | GetTrigger() に case "cron" |
Libraries/Settings/BackgroundScheduleValidators.cs | cron 式の検証 |
Models/Tenants/TenantUtilities.cs | 種別に "cron" を足し、入力欄とプレビュー領域を追加 |
Controllers/TenantsController.cs | プレビュー用のエンドポイント(任意) |
Libraries/General/Error.cs | エラーの種類 InvalidCronExpression とメッセージ |
Implem.DefinitionAccessor の表示文字列 | cron 関連の文言 |
Implem.PleasanterFrontend/wwwroot/src/scripts/generals/tenants.js | 入力イベントでの説明文表示とプレビューの要求 |
Implem.Pleasanter/wwwroot/Extensions/ | cronstrue の minify 版を配置(既存の mermaid-11.9.0.min.js などと同じ置き方) |