【Bラボデジタルサイネージ9】現在の時間を表示する「DateModule」

![](https://qiita-user-contents.imgix.net/https%3A%2F%2Fuser-images.githubusercontent.com%2F29194025%2F137117015-af05fe4f-7c32-4e38-876f-e153ff2f2f7d.png?ixlib=rb-4.0.0&auto=format&gif-q=60&q=75&w=1400&fit=max&s=2bbf46fb5c1d8dec16f2fcb5d089fde2)

https://github.com/ohayota/BLabo-digital-signage

本記事は、[株式会社 函館ラボラトリ](http://www.hakolab.co.jp)が運営する「[Bラボ](http://www.hakolab.co.jp/b-lab/index.html)」における、大人向け「看板アプリ（デジタルサイネージ）を作るコース」用教材テキストです。

http://www.hakolab.co.jp/b-lab/index.html

1. [【Bラボデジタルサイネージ1】Processing+ラズパイでデジタルサイネージを作る！](https://qiita.com/ohayota/items/f4aa3e1b452060eb52ad)
2. [【Bラボデジタルサイネージ2】ラズパイ初期設定](https://qiita.com/ohayota/items/88fbcf177e6606311efb)
3. [【Bラボデジタルサイネージ3】準備プログラム作成＆機能実現の方針決定](https://qiita.com/ohayota/items/41a746d2258673726541)
4. [【Bラボデジタルサイネージ4】レイアウトの基準になるグリッドを表示する「GridModule」](https://qiita.com/ohayota/items/df704f523b5aa4dbb180)
5. [【Bラボデジタルサイネージ5】レイアウトの基準になる枠を表示する「PlaceholderModule」](https://qiita.com/ohayota/items/f52a10660009794afd9f)
6. [【Bラボデジタルサイネージ6】画像を全画面表示する「FullImageModule」](https://qiita.com/ohayota/items/4a9c8423459c251f4911)
7. [【Bラボデジタルサイネージ7】ページの自動切り替え](https://qiita.com/ohayota/items/9d2c28f9cbf2f559be31)
8. [【Bラボデジタルサイネージ8】設置されている場所の名前を表示する「LocationModule」](https://qiita.com/ohayota/items/046b361d945a709c4605)
9. **【Bラボデジタルサイネージ9】現在の時間を表示する「DateModule」（本記事）**
10. [【Bラボデジタルサイネージ10】ページ切り替えの時間が分かる「ProgressBarModule」](https://qiita.com/ohayota/items/9f3b6f9f32b7d93ef2a7)
11. [【Bラボデジタルサイネージ11】現在の表示中のページが分かる「PageControlModule」](https://qiita.com/ohayota/items/23acc993dca47286fffb)
12. [【Bラボデジタルサイネージ12】現在の天気を表示する「WeatherRModule」](https://qiita.com/ohayota/items/d9b1d21037dcedcfd78c)
13. [【Bラボデジタルサイネージ13】直近2件のバス時刻表を表示する「BusRModule」](https://qiita.com/ohayota/items/93d7c9b5328188b3c5b6)
14. [【Bラボデジタルサイネージ14】ごみ出しカレンダーを表示する「GomiRModule」](https://qiita.com/ohayota/items/40ed5db352a688637f83)
15. [【Bラボデジタルサイネージ15】（発展編）ツイートを表示する「TwitterRModule」](https://qiita.com/ohayota/items/6367fad7beaf9ad9ea1c)
16. [【Bラボデジタルサイネージ16】開店／閉店を表示する「OpenCloseRModule」](https://qiita.com/ohayota/items/284b5c7e7101f602016f)
17. [【Bラボデジタルサイネージ17】部屋の温度を表示する「TemperatureRModule」](https://qiita.com/ohayota/items/386c27a021601fb3ccfd)
18. [【Bラボデジタルサイネージ18】部屋の明るさを表示する「BrightnessRModule」](https://qiita.com/ohayota/items/76279927b4ef35eaf628)
19. [【Bラボデジタルサイネージ19】起動画面を表示する「LaunchingScreenModule」](https://qiita.com/ohayota/items/0b6bdeda68c36707f762)
20. [【Bラボデジタルサイネージ20】RModuleの影を実装](https://qiita.com/ohayota/items/57242f18653ccf7eb83b)


# 作るもの

今日の日付や今の時刻を表示できるようにします。

![0036.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/368192/d47a553d-ab7a-7358-3201-d4925223e932.png)

# 日付と時刻の取得

Processingには、日付と時刻を取得するための関数が定義されています。

| 取得したい値 | 使用する関数 | 返り値の型 | 
| :-- | :-- | :-- |
| 年（西暦） | `year()` | 整数型 |
| 月 | `month()` | 整数型 |
| 日 | `day()` | 整数型 |
| 時（24時間表示） | `hour()` | 整数型 |
| 分 | `minute()` | 整数型 |
| 秒 | `second()` | 整数型 |

これらの値を取得して保存するための変数を定義します。

``` java:DigitalSignage.pde
/* 略 */

color GREEN_COLOR;

int year; /* 追加 */
int month; /* 追加 */
int day; /* 追加 */
int hour; /* 追加 */
int minute; /* 追加 */
int second; /* 追加 */

/* 略 */
```

定義した6つの変数に対して、現在の時刻を取得し代入する処理を書きます。
新しいファイル`M_Date.pde`を作成し、その中に新しい関数`updateDate()`を作ります。

``` java:M_Date.pde
void updateDate() {
  year = year();
  month = month();
  day = day();
  hour = hour();
  minute = minute();
  second = second();
}
```

これで6つの変数を更新する処理ができたので、関数`updateDate()`を関数`updateDatas()`の中で呼び出すように書き換えましょう。
関数`updateDate()`内で`second()`を使っている部分は、定義した変数`second`に置き換えましょう。

``` java:DigitalSignage.pde
/* 略 */

void updateDatas() {
  updateDate(); /* 追加 */
  if (second % STAY_SECOND == 0) { /* second()をsecondに変更 */
    updateNowPageID(true);
  }
}

/* 略 */
```

これで、毎秒時刻を取得できるようになりました。


# 曜日の取得

日付が取得できたら曜日も取得したいものです。
しかし、Processingでは、曜日を取得するための関数は用意されていません。
自分で関数を定義し、日付から曜日を算出できるようにします。

まず、7つの曜日を、enum（列挙型）を使って定義します。

``` java:DigitalSignage.pde
/* 略 */
  area7, 
  area8
}

/* ここから追加 */
enum Youbi {
  Sun, 
  Mon, 
  Tue, 
  Wed, 
  Thu, 
  Fri, 
  Sat
}
/* ここまで追加 */

/* 略 */
```

次に、曜日を表す変数を定義します。

``` java:DigitalSignage.pde
/* 略 */
int minute;
int second;

String youbiString; /* 追加 */
Youbi youbi; /* 追加 */

/* 略 */
```

`youbiString`は、曜日の日本語表記で`"日"` `"月"`などが入ります。
`youbi`は、列挙型で定義した曜日のことで、`Youbi.Sun` `Youbi.Mon`などが入ります。

次に、曜日を算出する関数を定義します。
`M_Date.pde`内に、新しい関数`calcYoubi()`を定義します。

``` java:M_Date.pde
/* 略 */

/* ここから追加 */
Youbi calcYoubi(int year, int month, int day) {
}
/* ここまで追加 */
```

曜日の算出には、[Zellerの公式](https://ja.wikipedia.org/wiki/ツェラーの公式)を使います。
Zellerの公式を使うと、指定した年月日が日曜なら`0`、月曜なら`1`...という風に値が得られます。
公式をプログラムで実装します。

``` java:M_Date.pde
/* 略 */

Youbi calcYoubi(int year, int month, int day) {
  /* ここから追加 */
  final Youbi[] youbi = Youbi.values();
  if (month < 3) {
    year--;
    month += 12;
  }
  return youbi[(year+year/4-year/100+year/400+(13*month+8)/5+day)%7];
  /* ここまで追加 */
}
```

関数内の`youbi`という配列には、`Youbi.Sun`から`Youbi.Sat`までの7つの曜日が入っています。
Zellerの公式の計算結果を配列の添字に使い、正しい曜日を返します。

曜日の算出のための関数が実装できたので、この関数を実際に呼び出すよう書き換えます。

``` java:DigitalSignage.pde
/* 略 */

void updateDatas() {
  updateDate();
  youbi = calcYoubi(year, month, day); /* 追加 */
  if (second % STAY_SECOND == 0) {
    updateNowPageID(true);
  }
}
```

この時点で、年月日が何曜日かを算出できました。
次は、`youbi`に入っている値を`"日"` `"月"`のような日本語表記に変換してみましょう。
`M_Date.pde`内に、新しい関数`youbiToString()`を作ります。

``` java:M_Date.pde
/* 略 */

/* ここから追加 */
String youbiToString(Youbi youbi) {
  String str = "";
  return str;
}
/* ここまで追加 */
```

引数で持ってきた`youbi`に対応する日本語表記は、以下の通りです。

| `Youbi youbi` | `String youbiString` | 
| :-- | :-- | :-- |
| `Youbi.Sun` | `"日"` |
| `Youbi.Mon` | `"月"` |
| `Youbi.Tue` | `"火"` |
| `Youbi.Wed` | `"水"` |
| `Youbi.Thu` | `"木"` |
| `Youbi.Fri` | `"金"` |
| `Youbi.Sat` | `"土"` |

表のように対応する条件分岐を入れると、関数はこのように書けます。

``` java:M_Date.pde
/* 略 */

String youbiToString(Youbi youbi) {
  String str = "";
  /* ここから追加 */
  switch (youbi) {
    case Sun:
      str = "日";
      break;
    case Mon:
      str = "月";
      break;
    case Tue:
      str = "火";
      break;
    case Wed:
      str = "水";
      break;
    case Thu:
      str = "木";
      break;
    case Fri:
      str = "金";
      break;
    case Sat:
      str = "土";
      break;
  }
  /* ここまで追加 */
  return str;
}
```

曜日を日本語表記にする関数が実装できたので、実際に呼び出してみましょう。

``` java:DigitalSignage.pde
/* 略 */

void updateDatas() {
  updateDate();
  youbi = calcYoubi(year, month, day);
  youbiString = youbiToString(youbi); /* 追加 */
  if (second % STAY_SECOND == 0) {
    updateNowPageID(true);
  }
}
```

# 初期化関数の作成

起動時に値を取得できるようにします。
新しい関数`initializeDate()`を作ります。

``` java:Initialize.pde
/* 略 */

void initializeDate() {
  updateDate();
  youbi = calcYoubi(year(), month(), day());
  youbiString = youbiToString(youbi);
}
```

この関数を`initialize()`内の一番最初に呼び出すようにします。

``` java:Initalize.pde
void initialize() {
  initializeDate(); /* 追加 */
  initializeImage();
  initializeGrid();
  initializePlaceholder();
}
```

# 処理が必要以上に実行されないようにする

``` java
void updateDatas() {
  updateDate();
  youbi = calcYoubi(year, month, day);
  youbiString = youbiToString(youbi);
  if (second % STAY_SECOND == 0) {
    updateNowPageID(true);
  }
}
```

関数`updateDatas()`では、1秒に1回、日時の更新と曜日の算出を実行しています。
この中で、「曜日の算出」については、1日に1回しか実行する必要のない処理になります（日付は24時間に1回しか変わらないため）。
また、今後`frameRate`を1よりも大きくする場合（アニメーションを実装したいときなど）には、1秒に何回も日時を更新しなくても良いはずです。
必要以上に関数の実行回数を増やすと処理が重くなってしまうため、必要なときに実行されるようにしてみましょう。

実装するのは2つのパターンです。

- 日付が変わったときに実行
- 1秒間隔で実行


## 日付が変わったときに実行

日付が変わったときの判定は、「1フレーム前の`day`と、今の`day`が異なる値である」という条件で実現できます。
このときの「1フレーム前の`day`」を保持しておく変数を追加します。

``` java:DigitalSignage.pde
/* 略 */

int year;
int month;
int day;
int hour;
int minute;
int second;
int beforeDay = day(); /* 追加 */

/* 略 */
```

定義した`beforeDay`を使い、「1フレーム前の`day`と、今の`day`が異なる値である」という条件分岐を実現します。
1日に1回だけ更新が必要な変数は`youbi`と`youbiString`です。

``` java:DigitalSignage.pde
void updateDatas() {
  updateDate();
  
  /* ここから追加 */
  final boolean isUpdatedDay = (day != beforeDay);

  if (isUpdatedDay) {
    println("日付が変わりました。");

    youbi = calcYoubi(year, month, day);
    youbiString = youbiToString(youbi);

    beforeDay = day;
  }
  /* ここまで追加 */

  if (second % STAY_SECOND == 0) {
    updateNowPageID(true);
  }
}
```

`isUpdatedDay`は、条件に当てはまれば`true`になります。
`true`のとき（日付が更新されたとき）だけ、if文で囲まれた部分の処理を実行します。
if文の最後で、必ず`beforeDay`を今の`day`に更新します。


## 1秒間隔で実行、1時間間隔で実行

秒が更新されたかの判定は、「1フレーム前の`second`と、今の`second`が異なる値である」という条件で実現できます。
このときの「1フレーム前の`second`」を保持しておく変数を追加します。

``` java:DigitalSignage.pde
/* 略 */

int year;
int month;
int day;
int hour;
int minute;
int second;
int beforeDay = day();
int beforeSecond = second(); /* 追加 */

/* 略 */
```

定義した`beforeSecond`を使い、「1フレーム前の`second`と、今の`second`が異なる値である」という条件分岐を実現します。
1秒に1回だけ実行するのは、「画面切り替えの処理」です。

``` java:DigitalSignage.pde
void updateDatas() {
  updateDate();
  
  final boolean isUpdatedDay = (day != beforeDay);
  final boolean isUpdatedSecond = (second != beforeSecond); /* 追加 */

  if (isUpdatedDay) {
    println("日付が変わりました。");

    youbi = calcYoubi(year, month, day);
    youbiString = youbiToString(youbi);

    beforeDay = day;
  }
  
  /* ここから追加 */
  if (isUpdatedSecond) {
    if (second % STAY_SECOND == 0) {
      updateNowPageID(true);
    }

    beforeSecond = second;
  }
  /* ここまで追加 */
}
```

`isUpdatedSecond`は、条件に当てはまれば`true`になります。
`true`のとき（1秒経ったとき）だけ、if文で囲まれた部分の処理を実行します。
if文の最後で、必ず`beforeSecond`を今の`second`に更新します。

ついでに、「1時間経過したとき」の条件も実装してみます。
「1時間経過したとき」は、「1秒経ったとき」かつ「分と秒がどちらも0のとき」です。

``` java:DigitalSignage.pde
void updateDatas() {
  updateDate();
  
  final boolean isUpdatedDay = (day != beforeDay);
  final boolean isUpdatedSecond = (second != beforeSecond);

  if (isUpdatedDay) {
    println("日付が変わりました。");

    youbi = calcYoubi(year, month, day);
    youbiString = youbiToString(youbi);

    beforeDay = day;
  }
  
  if (isUpdatedSecond) {
    if (second % STAY_SECOND == 0) {
      updateNowPageID(true);
    }

    /* ここから追加 */
    if (minute + second == 0) {
      println(hour + "時になりました。");
    }
    /* ここまで追加 */

    beforeSecond = second;
  }
}
```

「1時間経過したとき」に実行するのは、あとの章で実装する「WeatherModule」などのデータ取得処理です。


# 祝日判定

平日とされる曜日（月〜金）であっても、祝日になっている日があります。
実は祝日なのに平日として過ごすと、バスの時刻表などを間違えて見てしまうことに繋がります。
このような間違いにつながらないよう、「今日が祝日なのか」を判定する処理を実装します。

祝日の場合、各曜日は以下のような表記にします。
（なお、判定ロジックの仕様上、土日のときは祝日かどうか判定できないため、祝日であっても「・祝」は付きません。）

| `Youbi youbi` | `String youbiString` | 
| :-- | :-- | :-- |
| `Youbi.Sun` | `"日"` |
| `Youbi.Mon` | `"月・祝"` |
| `Youbi.Tue` | `"火・祝"` |
| `Youbi.Wed` | `"水・祝"` |
| `Youbi.Thu` | `"木・祝"` |
| `Youbi.Fri` | `"金・祝"` |
| `Youbi.Sat` | `"土"` |

祝日かどうかの判定には、今回は**[holiday api](http://s-proj.com/utils/holiday.html)**を利用します。

http://s-proj.com/utils/holiday.html

APIには、下記からアクセスできます。

http://s-proj.com/utils/checkHoliday.php

今日が土日祝のいずれかであれば、「holiday」と表示されます。
土日祝のいずれでもなければ、それ以外の表示になります。
祝日判定では、出力が「holiday」かどうかだけ考えます。

APIの出力を取得するにあたって、Javaのライブラリを二つ使用します。

| ライブラリ | 用途 | 
| :-- | :-- |
| java.io | 入出力に関わる操作に使う。APIの出力結果を読み込む。 |
| java.net | ネットワーク通信で使う。Web上のAPIへのアクセス。 |

プログラム内でライブラリを読み込んでおきます。

``` java:DigitalSignage.pde
import java.io.*; /* 追加 */
import java.net.*; /* 追加 */

/* 略 */
```

次に、「祝日かどうか」を保持する変数を定義します。

``` java:DigitalSignage.pde
/* 略 */

int second;
int beforeDay = day();
int beforeSecond = second();

String youbiString;
Youbi youbi;
boolean isHoliday; /* 追加 */

/* 略 */
```

祝日判定用の関数`updateIsHoliday()`を作ります。
この関数は、実行したときに`isHoliday`の値を更新します。
関数の返り値では、APIからの出力の取得が成功したときに`true`、失敗したときに`false`を返します。

``` java:M_Date.pde
/* 略 */

/* ここから追加 */
boolean updateIsHoliday() {
  return true;
}
/* ここまで追加 */
```

この関数に、APIの出力を取得するための処理を書きます。

``` java:M_Date.pde
/* 略 */

boolean updateIsHoliday() {
  /* ここから追加 */
  URL url = new URL("http://s-proj.com/utils/checkHoliday.php");
  InputStream is = url.openStream();
  InputStreamReader isr = new InputStreamReader(is, "utf8");
  BufferedReader br = new BufferedReader(isr);
  isHoliday = br.readLine().equals("holiday");
  /* ここまで追加 */

  return true;
}
```

`InputStream`と`InputStreamReader`と`BufferedReader`を使い、指定したURL先にあるテキストを読み込んでいます。
`br.readLine().equals("holiday")`では、読み込んだテキストが`holiday`かどうかを判定しています。

このようにして値を取得できれば判定は成功ですが、ネットワーク通信を用いるときには、何らかの問題でアクセスできない場合があります。
たとえば、機器がネットワークに接続されていないときには、APIから値を取得することができません。
エラーが起きた時の例外に対処できなければ、プログラムの停止につながります。
そこで、APIから値を取得できなかったときの処理も書きます。
**try-catch文**を使います。

``` java:M_Date.pde
/* 略 */

boolean updateIsHoliday() {
  /* ここから変更 */
  try {
    URL url = new URL("http://s-proj.com/utils/checkHoliday.php");
    InputStream is = url.openStream();
    InputStreamReader isr = new InputStreamReader(is, "utf8");
    BufferedReader br = new BufferedReader(isr);
    isHoliday = br.readLine().equals("holiday");
  } catch (Exception e) {
    println("isHoliday(): 土日祝判定を取得できませんでした。" + e);
    return false;
  }
  /* ここまで変更 */

  return true;
}
```

APIからの値が取得できなかったとき、`catch`で囲まれた部分でエラーを出力します。
エラーが起きなかった時の出力も実装します。

``` java:M_Date.pde
/* 略 */

boolean updateIsHoliday() {
  try {
    URL url = new URL("http://s-proj.com/utils/checkHoliday.php");
    InputStream is = url.openStream();
    InputStreamReader isr = new InputStreamReader(is, "utf8");
    BufferedReader br = new BufferedReader(isr);
    isHoliday = br.readLine().equals("holiday");
  } catch (Exception e) {
    println("isHoliday(): 土日祝判定を取得できませんでした。" + e);
    return false;
  }

  /* ここから追加 */
  print("isHoliday(): 土日祝判定を取得しました。");
  if (isHoliday) {
    println("今日は土日祝に含まれます。");
  } else {
    println("今日は平日です。");
  }
  /* ここまで追加 */

  return true;
}
```

これで関数が実装できました。
関数`updateIsHoliday()`を実際に呼び出しましょう。
呼び出す場所とタイミングは以下の2点です。

- `initializeDate()`で、日時が初期化されたとき
- `updateDatas()`で、日付が更新されたとき

それぞれ実装しましょう。

``` java:Initialize.pde
/* 略 */

void initializeDate() {
  updateDate();

  /* ここから追加 */
  // 祝日APIの値が取得できなければ、ネットワークかAPI側のどちらかの問題
  if (!updateIsHoliday()) println("initializeDate(): ネットワークに接続できているか確認してください。");
  /* ここまで追加 */

  youbi = calcYoubi(year(), month(), day());
  youbiString = youbiToString(youbi);
}

/* 略 */
```

``` java:DigitalSignage.pde
void updateDatas() {
  updateDate();
  
  final boolean isUpdatedDay = (day != beforeDay);
  final boolean isUpdatedSecond = (second != beforeSecond);

  if (isUpdatedDay) {
    println("日付が変わりました。");

    /* ここから追加 */
    if (!updateIsHoliday()) {
      println("ネットワークに接続できていない可能性があります。接続できているか確認してください。");
    }
    /* ここまで追加 */

    youbi = calcYoubi(year, month, day);
    youbiString = youbiToString(youbi);

    beforeDay = day;
  }
  
  if (isUpdatedSecond) {
    if (second % STAY_SECOND == 0) {
      updateNowPageID(true);
    }
    if (minute + second == 0) {
      println(hour + "時になりました。");
    }
    
    beforeSecond = second;
  }
}
```

これで祝日かどうかを示す`isHoliday`が更新されるようになったので、曜日の日本語表記も`isHoliday`に応じて変わるようにしましょう。
たとえば、月曜日かつ祝日の場合には、「月・祝」のように、末尾に「・祝」を追加します。
関数`youbiToString()`に追記します。

``` java:M_Date.pde
/* 略 */

String youbiToString(Youbi youbi) {
  String str = "";
  switch (youbi) {
    case Sun:
      str = "日";
      break;
    case Mon:
      str = "月";
      if (isHoliday) str += "・祝"; /* 追加 */
      break;
    case Tue:
      str = "火";
      if (isHoliday) str += "・祝"; /* 追加 */
      break;
    case Wed:
      str = "水";
      if (isHoliday) str += "・祝"; /* 追加 */
      break;
    case Thu:
      str = "木";
      if (isHoliday) str += "・祝"; /* 追加 */
      break;
    case Fri:
      str = "金";
      if (isHoliday) str += "・祝"; /* 追加 */
      break;
    case Sat:
      str = "土";
      break;
  }
  return str;
}

/* 略 */
```


# DateModuleを表示する

表示に必要な変数はすべて揃いました。
DateModuleを表示するための関数`drawDateModule()`を作ります。

``` java:M_Date.pde
/* ここから追加 */
void drawDateModule() {
  blendMode(EXCLUSION);
  
  String timeText = month + "月" + day + "日" + "（" + youbiString + "）" + nf(hour, 2) + ":" + nf(minute, 2) + ":" + nf(second, 2);
  drawText(LEFT, BASELINE, WHITE_COLOR, 36, timeText, 100, 30);
  
  blendMode(BLEND);
}
/* ここまで追加 */

/* 略 */
```

`drawDateModule()`は、`drawModules()`の中で呼び出します。

``` java:DigitalSignage.pde
/* 略 */

void drawModules() {
  if (nowPageID == 0) {
    drawFullImageModule(background);
    drawGridModule();
    drawPlaceholderModule();
  } else if (nowPageID == 1) {
    drawFullImageModule(adImage[0]);
  }
  drawDateModule(); /* 追加 */
  drawLocationModule();
}

/* 略 */
```

実行してみましょう。
下図のような出力になっていれば、DateModuleの実装は完了です。

![0036.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/368192/d47a553d-ab7a-7358-3201-d4925223e932.png)


# 最後に

これで、現在の時間を表示する「DateModule」が実装できました。
次は、**[ページ切り替えの時間が分かる「ProgressBarModule」](https://qiita.com/ohayota/items/9f3b6f9f32b7d93ef2a7)**を実装してみましょう。

https://qiita.com/ohayota/items/9f3b6f9f32b7d93ef2a7