torutkのブログ

ソフトウェア・エンジニアのブログ

JavaFXでカレンダー表示プログラムを作る(DatePickerのポップアップ利用)(続々々々々)

JavaFXでカレンダー表示プログラムを作る(DatePickerのポップアップ利用)(続々々々) - torutkのブログ の続きです。

ここまで作ってきたカレンダー表示プログラムは、実行したときの日付をずっと「今日」として表示します。そのため、実時間(システムクロック)が翌日になっても、カレンダー表示の「今日」はそのままです。

そこで、実時間の日付が変わったらカレンダー表示の「今日」も合わせて変更するようにします。

実現方針

UNIX系OSのcronや、Windows OSのタスクスケジューラであれば、日付が変わったときに処理(プログラム)を実行する機能がありますが、実行中のJavaプログラム内で日付が変わったことを知るのは一筋縄ではいかないようです。

オープンソースライブラリにはQuartz Enterprise Job Schedulerなどのcron的な機能を提供するものがありますが、今回はJava SE標準機能だけでプログラムを動かしたいので(jarファイル1つだけを配布して動かす)、Java SE標準で実現することとします。

Java SE標準では、コンカレントパッケージ(java.util.concurrent)でスレッド関係の機能が提供されているので、これを使って日付が変わるタイミングで処理を実行するようにします。

日付が変わるときに処理を実行

カレンダー表示プログラムが実行された時刻から、その日が終わるまでの時間を計算し、ScheduledExecutorServiceのscheduleメソッドでその時間が経過したら実行する処理を指定します。

今回は、privateメソッド secondsTillTomorrow を作り、その日が終わるまでの秒数を計算します。

    private long secondsTillTomorrow(LocalDateTime time) {
        LocalDateTime tomorrow = time.plusDays(1).with(LocalTime.MIDNIGHT);
        return Duration.between(time, tomorrow).getSeconds();
    }
  • 引数で計算の起点となる日時を指定、その日時の翌日0時までの間隔を秒で取得しています。
  • LocalDateTimeで翌日はplusDaysメソッドで生成できます。時分秒は withメソッドでMIDNIGHT(00:00:00)を指定することで日が変わったときの日時を作成しました。
  • 起点の日時と日が変わったときの間をDurationで取り、秒単位とします。

次に、java.util.concurrentパッケージのScheduledExecutorServiceを使って、指定時間経過後に処理(RunnableまたはCallable)を実行するようにします。

public class Calendar extends Application {
    private ScheduledExecutorService executor;
      :
    public void start(Stage primaryStage) {
          :
        executor = Executors.newSingleThreadScheduledExecutor();
        executor.schedule(this::crossoverDate, secondsTillTomorrow(LocalDateTime.now()) + 1, TimeUnit.SECONDS);
          :

日の更新処理はprivateメソッド crossoverDate に記述し、メソッド参照でscheduleメソッドに渡しています。

    private void crossoverDate() {
        LocalDateTime now = LocalDateTime.now();
        if (today.isBefore(now.toLocalDate())) {
            Platform.runLater(() -> {
                today = now.toLocalDate();
                rootPane.getChildren().remove(calendar);
                rootPane.getChildren().add(createDatePickerPopup(today));
            });
        }
        executor.schedule(this::crossoverDate, secondsTillTomorrow(now) + 1, TimeUnit.SECONDS);
    }


スケジュールされた時間に呼ばれた際、日付が変わっているかを判別し、変わっていたらカレンダー表示を作り直してシーングラフにある既存のカレンダーと差し替えます。

これで、日付が変わるときに、カレンダー表示の「今日」も変わります。

ここまでのコード

ここまでのプログラムのコードは、次にあります。

https://github.com/torutk/calendar/tree/v0.1.5

ウィンドウを閉じてもプロセスが終了しない

executorにスケジュールされたタスクがあると、ウィンドウを閉じても(Windows OSの場合、タスクバー上のカレンダー表示プログラムを右クリックし、[ウィンドウを閉じる]を実行)、表示は消えますがプロセスは残ってしまいます。

ウィンドウを閉じるときにプロセスを終了させるために、ウィンドウを閉じる操作のイベントで、executorを明示的に終了させます。

        primaryStage.setOnCloseRequest(e -> executor.shutdownNow());