PagerDuty のシフト回数を数える pd-shift というツールを作成した 〜オンコール待機手当の計算を効率化する〜

24/365 で運用しているサービスであれば、オンコール体制を整備するために PagerDuty を利用しているサービスも多いことでしょう。また、業務時間外に待機することに対して、待機手当なるものが支給されることもあるでしょう。
待機手当が待機回数に応じて支給される場合、人手で待機回数を数えるのは地味に大変だったりします。そんなわけで、pd-shift というツールを作成しました。
現状はシフト回数を数える機能しかありませんが、将来的に pd よりも手軽に override を作成する機能も提供したいと思っていて、少し抽象的な名前になっています。

使用例

インストール方法は README の Installation、一般的な使い方は README の Usage を読んでいただくとして、README でも利用している次のスケジュール (ID: P4DRALL) を使っていくつかの使用例を見ていきます。

設定は次のとおりで、handoff time を月曜日の 05:00 としています。

集計対象期間は 2025-07-01 05:00 〜 2025-07-08 05:00 とします。

なお、PagerDuty の API key が PD_SHIFT_API_KEY 環境変数にセットされているものとします。API key がなければ PagerDuty のドキュメント に従って発行してください。

平日・土日関係なく待機日数をカウントしたい場合

とてもシンプルな例で、次のように実行すれば良いです。

pd-shift count \
  --time-zone Asia/Tokyo \
  --schedule-ids P4DRALL \
  --handoff-times 05:00 \
  --since 2025-07-01 \
  --until 2025-07-08

結果は次のとおりです。

# Summary

- John Smith: 5.75
- Takeshi Arabiki: 1.25
- Total: 7.00
- Expected total: 7

# Details

- Tue, 2025-07-01 05:00+0900 - Wed, 2025-07-02 05:00+0900
    - Weekly Rotation
        - John Smith: 1.00 (05:00 - 05:00)
- Wed, 2025-07-02 05:00+0900 - Thu, 2025-07-03 05:00+0900
    - Weekly Rotation
        - John Smith: 1.00 (05:00 - 05:00)
- Thu, 2025-07-03 05:00+0900 - Fri, 2025-07-04 05:00+0900
    - Weekly Rotation
        - John Smith: 1.00 (05:00 - 05:00)
- Fri, 2025-07-04 05:00+0900 - Sat, 2025-07-05 05:00+0900
    - Weekly Rotation
        - John Smith: 1.00 (05:00 - 05:00)
- Sat, 2025-07-05 05:00+0900 - Sun, 2025-07-06 05:00+0900
    - Weekly Rotation
        - John Smith: 0.17 (05:00 - 09:00)
        - Takeshi Arabiki: 0.25 (09:00 - 15:00)
        - John Smith: 0.58 (15:00 - 05:00)
- Sun, 2025-07-06 05:00+0900 - Mon, 2025-07-07 05:00+0900
    - Weekly Rotation
        - John Smith: 1.00 (05:00 - 05:00)
- Mon, 2025-07-07 05:00+0900 - Tue, 2025-07-08 05:00+0900
    - Weekly Rotation
        - Takeshi Arabiki: 1.00 (05:00 - 05:00)

# PagerDuty schedules

## Weekly Rotation

- 2025-07-01T05:00:00+09:00 - 2025-07-05T09:00:00+09:00: John Smith
- 2025-07-05T09:00:00+09:00 - 2025-07-05T15:00:00+09:00: Takeshi Arabiki
- 2025-07-05T15:00:00+09:00 - 2025-07-07T05:00:00+09:00: John Smith
- 2025-07-07T05:00:00+09:00 - 2025-07-08T05:00:00+09:00: Takeshi Arabiki

平日は夜間のみ、休日は日中と夜間別々にカウントしたい場合

業務時間外のみが待機手当の対象で、平日は夜間のみ、休日は日中と夜間それぞれでカウントされるものとします。休日丸々待機していれば 2 とカウントされる感じですね。
このような集計をしたい場合、--non-working-days で休日を定義し、--include で working-days のカウント対象を夜間に限定する必要があります。
例えば、土日・祝日が休日で、夜間を 17:00 から翌朝 05:00 とした場合、次のように実行します。

pd-shift count \
  --time-zone Asia/Tokyo \
  --schedule-ids P4DRALL \
  --handoff-times 05:00,17:00 \
  --include working-days:17:00-05:00,non-working-days \
  --non-working-days 'JP holidays,Sat,Sun,Dec 29,Dec 30,Dec 31,Jan 1,Jan 2,Jan 3' \
  --since 2025-07-01 \
  --until 2025-07-08

結果は次のとおりで、土日に関しては日中と夜間で別々にカウントされていることがわかるかと思います。

# Summary

- John Smith: 7.50
- Takeshi Arabiki: 1.50
- Total: 9.00
- Expected total: 9

# Details

- Tue, 2025-07-01 17:00+0900 - Wed, 2025-07-02 05:00+0900
    - Weekly Rotation
        - John Smith: 1.00 (17:00 - 05:00)
- Wed, 2025-07-02 17:00+0900 - Thu, 2025-07-03 05:00+0900
    - Weekly Rotation
        - John Smith: 1.00 (17:00 - 05:00)
- Thu, 2025-07-03 17:00+0900 - Fri, 2025-07-04 05:00+0900
    - Weekly Rotation
        - John Smith: 1.00 (17:00 - 05:00)
- Fri, 2025-07-04 17:00+0900 - Sat, 2025-07-05 05:00+0900
    - Weekly Rotation
        - John Smith: 1.00 (17:00 - 05:00)
- Sat, 2025-07-05 05:00+0900 - Sat, 2025-07-05 17:00+0900
    - Weekly Rotation
        - John Smith: 0.33 (05:00 - 09:00)
        - Takeshi Arabiki: 0.50 (09:00 - 15:00)
        - John Smith: 0.17 (15:00 - 17:00)
- Sat, 2025-07-05 17:00+0900 - Sun, 2025-07-06 05:00+0900
    - Weekly Rotation
        - John Smith: 1.00 (17:00 - 05:00)
- Sun, 2025-07-06 05:00+0900 - Sun, 2025-07-06 17:00+0900
    - Weekly Rotation
        - John Smith: 1.00 (05:00 - 17:00)
- Sun, 2025-07-06 17:00+0900 - Mon, 2025-07-07 05:00+0900
    - Weekly Rotation
        - John Smith: 1.00 (17:00 - 05:00)
- Mon, 2025-07-07 17:00+0900 - Tue, 2025-07-08 05:00+0900
    - Weekly Rotation
        - Takeshi Arabiki: 1.00 (17:00 - 05:00)

# PagerDuty schedules

## Weekly Rotation

- 2025-07-01T05:00:00+09:00 - 2025-07-05T09:00:00+09:00: John Smith
- 2025-07-05T09:00:00+09:00 - 2025-07-05T15:00:00+09:00: Takeshi Arabiki
- 2025-07-05T15:00:00+09:00 - 2025-07-07T05:00:00+09:00: John Smith
- 2025-07-07T05:00:00+09:00 - 2025-07-08T05:00:00+09:00: Takeshi Arabiki

なお、集計対象期間が 2025-07-01 〜 2025-07-08 であれば --non-working-days 'Sat,Sun' で十分なんですが、日本の祝日や年末年始休暇が集計期間になるケースも考慮して --non-working-days 'JP holidays,Sat,Sun,Dec 29,Dec 30,Dec 31,Jan 1,Jan 2,Jan 3' としています。

もし土日と水曜日が休みの週休 3 日制の会社だと --non-working-days 'JP holidays,Wed,Sat,Sun,Dec 29,Dec 30,Dec 31,Jan 1,Jan 2,Jan 3' です。
その場合、出力結果は水曜日の日中もカウントされるので次のようになります。

# Summary

- John Smith: 8.50
- Takeshi Arabiki: 1.50
- Total: 10.00
- Expected total: 10

# Details

- Tue, 2025-07-01 17:00+0900 - Wed, 2025-07-02 05:00+0900
    - Weekly Rotation
        - John Smith: 1.00 (17:00 - 05:00)
- Wed, 2025-07-02 05:00+0900 - Wed, 2025-07-02 17:00+0900
    - Weekly Rotation
        - John Smith: 1.00 (05:00 - 17:00)
- Wed, 2025-07-02 17:00+0900 - Thu, 2025-07-03 05:00+0900
    - Weekly Rotation
        - John Smith: 1.00 (17:00 - 05:00)
- Thu, 2025-07-03 17:00+0900 - Fri, 2025-07-04 05:00+0900
    - Weekly Rotation
        - John Smith: 1.00 (17:00 - 05:00)
- Fri, 2025-07-04 17:00+0900 - Sat, 2025-07-05 05:00+0900
    - Weekly Rotation
        - John Smith: 1.00 (17:00 - 05:00)
- Sat, 2025-07-05 05:00+0900 - Sat, 2025-07-05 17:00+0900
    - Weekly Rotation
        - John Smith: 0.33 (05:00 - 09:00)
        - Takeshi Arabiki: 0.50 (09:00 - 15:00)
        - John Smith: 0.17 (15:00 - 17:00)
- Sat, 2025-07-05 17:00+0900 - Sun, 2025-07-06 05:00+0900
    - Weekly Rotation
        - John Smith: 1.00 (17:00 - 05:00)
- Sun, 2025-07-06 05:00+0900 - Sun, 2025-07-06 17:00+0900
    - Weekly Rotation
        - John Smith: 1.00 (05:00 - 17:00)
- Sun, 2025-07-06 17:00+0900 - Mon, 2025-07-07 05:00+0900
    - Weekly Rotation
        - John Smith: 1.00 (17:00 - 05:00)
- Mon, 2025-07-07 17:00+0900 - Tue, 2025-07-08 05:00+0900
    - Weekly Rotation
        - Takeshi Arabiki: 1.00 (17:00 - 05:00)

# PagerDuty schedules

## Weekly Rotation

- 2025-07-01T05:00:00+09:00 - 2025-07-05T09:00:00+09:00: John Smith
- 2025-07-05T09:00:00+09:00 - 2025-07-05T15:00:00+09:00: Takeshi Arabiki
- 2025-07-05T15:00:00+09:00 - 2025-07-07T05:00:00+09:00: John Smith
- 2025-07-07T05:00:00+09:00 - 2025-07-08T05:00:00+09:00: Takeshi Arabiki

休日のみ待機回数をカウントしたい場合

グローバルな会社で、follo-the-sun schedule になっている場合、休日のみカウントすることになるでしょう。
05:00, 13:00, 22:00 でシフトが変わる場合、次のように実行します。

pd-shift count \
  --time-zone Asia/Tokyo \
  --schedule-ids P4DRALL \
  --handoff-times 05:00,13:00,22:00 \
  --include non-working-days \
  --non-working-days 'JP holidays,Sat,Sun,Dec 29,Dec 30,Dec 31,Jan 1,Jan 2,Jan 3' \
  --since 2025-07-01 \
  --until 2025-07-08

結果は次のとおりです。

# Summary

- John Smith: 5.28
- Takeshi Arabiki: 0.72
- Total: 6.00
- Expected total: 6

# Details

- Sat, 2025-07-05 05:00+0900 - Sat, 2025-07-05 13:00+0900
    - Weekly Rotation
        - John Smith: 0.50 (05:00 - 09:00)
        - Takeshi Arabiki: 0.50 (09:00 - 13:00)
- Sat, 2025-07-05 13:00+0900 - Sat, 2025-07-05 22:00+0900
    - Weekly Rotation
        - Takeshi Arabiki: 0.22 (13:00 - 15:00)
        - John Smith: 0.78 (15:00 - 22:00)
- Sat, 2025-07-05 22:00+0900 - Sun, 2025-07-06 05:00+0900
    - Weekly Rotation
        - John Smith: 1.00 (22:00 - 05:00)
- Sun, 2025-07-06 05:00+0900 - Sun, 2025-07-06 13:00+0900
    - Weekly Rotation
        - John Smith: 1.00 (05:00 - 13:00)
- Sun, 2025-07-06 13:00+0900 - Sun, 2025-07-06 22:00+0900
    - Weekly Rotation
        - John Smith: 1.00 (13:00 - 22:00)
- Sun, 2025-07-06 22:00+0900 - Mon, 2025-07-07 05:00+0900
    - Weekly Rotation
        - John Smith: 1.00 (22:00 - 05:00)

# PagerDuty schedules

## Weekly Rotation

- 2025-07-01T05:00:00+09:00 - 2025-07-05T09:00:00+09:00: John Smith
- 2025-07-05T09:00:00+09:00 - 2025-07-05T15:00:00+09:00: Takeshi Arabiki
- 2025-07-05T15:00:00+09:00 - 2025-07-07T05:00:00+09:00: John Smith
- 2025-07-07T05:00:00+09:00 - 2025-07-08T05:00:00+09:00: Takeshi Arabiki

なお、拠点ごとに休日が異なるケースには対応していません。

Primary/secondary 合わせた待機回数をカウントしたい場合

次のように --scheduled-ids に複数のスケジュールを指定すると、primary と secondary 両方の待機回数をカウントできます。

pd-shift count \
  --time-zone Asia/Tokyo \
  --schedule-ids P4DRALL,P5ESBMM \
  --handoff-times 05:00 \
  --since 2025-07-01 \
  --until 2025-07-08

特定の時間帯だけ secondary の schedule を primary の人で override することで、一時的に primary 一人体制になる時間帯もあるかと思いますが、現状は同一人物の待機回数がダブルカウントされるようになっています。

前月の待機回数をカウントしたい場合

集計対象期間は 2025-07-01 〜 2025-07-08 の固定としていましたが、待機手当の支給を目的とする場合、今月や前月の待機回数をカウントしたいことが多いでしょう。pd-shift にそのような指定ができるようにしようかとも思ったんですが、date コマンドと併用することで実現できるのでサポートしていません。
例えば前月の月初は BSD date であれば date -v -1m -jf %F $(date +%Y-%m-01) +%F、GNU date であれば date -d $(date '+%Y-%m-01')-1month +%F で出力することができます。月初に実行するのであればシンプルに date -v -1m +%Y-%m-01date -d -1month +%Y-%m-01 で大丈夫です。翌月月初は -1m となっている箇所を +1m に変えるだけです。

よって、Mac で前月の待機回数をカウントするには次のように実行します。

pd-shift count \
  --time-zone Asia/Tokyo \
  --schedule-ids P4DRALL \
  --handoff-times 05:00 \
  --since $(date -v -1m -jf %F $(date +%Y-%m-01) +%F) \
  --until $(date '+%Y-%m-01')

結果は次のとおりです。

# Summary

- John Smith: 14.00
- Takeshi Arabiki: 17.00
- Total: 31.00
- Expected total: 31

# Details

- Thu, 2025-05-01 05:00+0900 - Fri, 2025-05-02 05:00+0900
    - Weekly Rotation
        - Takeshi Arabiki: 1.00 (05:00 - 05:00)
- Fri, 2025-05-02 05:00+0900 - Sat, 2025-05-03 05:00+0900
    - Weekly Rotation
        - Takeshi Arabiki: 1.00 (05:00 - 05:00)
- Sat, 2025-05-03 05:00+0900 - Sun, 2025-05-04 05:00+0900
    - Weekly Rotation
        - Takeshi Arabiki: 1.00 (05:00 - 05:00)
- Sun, 2025-05-04 05:00+0900 - Mon, 2025-05-05 05:00+0900
    - Weekly Rotation
        - Takeshi Arabiki: 1.00 (05:00 - 05:00)
- Mon, 2025-05-05 05:00+0900 - Tue, 2025-05-06 05:00+0900
    - Weekly Rotation
        - Takeshi Arabiki: 1.00 (05:00 - 05:00)
- Tue, 2025-05-06 05:00+0900 - Wed, 2025-05-07 05:00+0900
    - Weekly Rotation
        - Takeshi Arabiki: 1.00 (05:00 - 05:00)
- Wed, 2025-05-07 05:00+0900 - Thu, 2025-05-08 05:00+0900
    - Weekly Rotation
        - Takeshi Arabiki: 0.62 (05:00 - 20:00)
        - John Smith: 0.38 (20:00 - 05:00)
- Thu, 2025-05-08 05:00+0900 - Fri, 2025-05-09 05:00+0900
    - Weekly Rotation
        - John Smith: 1.00 (05:00 - 05:00)
- Fri, 2025-05-09 05:00+0900 - Sat, 2025-05-10 05:00+0900
    - Weekly Rotation
        - John Smith: 1.00 (05:00 - 05:00)
- Sat, 2025-05-10 05:00+0900 - Sun, 2025-05-11 05:00+0900
    - Weekly Rotation
        - John Smith: 1.00 (05:00 - 05:00)
- Sun, 2025-05-11 05:00+0900 - Mon, 2025-05-12 05:00+0900
    - Weekly Rotation
        - John Smith: 1.00 (05:00 - 05:00)
- Mon, 2025-05-12 05:00+0900 - Tue, 2025-05-13 05:00+0900
    - Weekly Rotation
        - John Smith: 1.00 (05:00 - 05:00)
- Tue, 2025-05-13 05:00+0900 - Wed, 2025-05-14 05:00+0900
    - Weekly Rotation
        - John Smith: 1.00 (05:00 - 05:00)
- Wed, 2025-05-14 05:00+0900 - Thu, 2025-05-15 05:00+0900
    - Weekly Rotation
        - John Smith: 0.62 (05:00 - 20:00)
        - Takeshi Arabiki: 0.38 (20:00 - 05:00)
- Thu, 2025-05-15 05:00+0900 - Fri, 2025-05-16 05:00+0900
    - Weekly Rotation
        - Takeshi Arabiki: 1.00 (05:00 - 05:00)
- Fri, 2025-05-16 05:00+0900 - Sat, 2025-05-17 05:00+0900
    - Weekly Rotation
        - Takeshi Arabiki: 1.00 (05:00 - 05:00)
- Sat, 2025-05-17 05:00+0900 - Sun, 2025-05-18 05:00+0900
    - Weekly Rotation
        - Takeshi Arabiki: 1.00 (05:00 - 05:00)
- Sun, 2025-05-18 05:00+0900 - Mon, 2025-05-19 05:00+0900
    - Weekly Rotation
        - Takeshi Arabiki: 1.00 (05:00 - 05:00)
- Mon, 2025-05-19 05:00+0900 - Tue, 2025-05-20 05:00+0900
    - Weekly Rotation
        - Takeshi Arabiki: 1.00 (05:00 - 05:00)
- Tue, 2025-05-20 05:00+0900 - Wed, 2025-05-21 05:00+0900
    - Weekly Rotation
        - Takeshi Arabiki: 1.00 (05:00 - 05:00)
- Wed, 2025-05-21 05:00+0900 - Thu, 2025-05-22 05:00+0900
    - Weekly Rotation
        - Takeshi Arabiki: 0.62 (05:00 - 20:00)
        - John Smith: 0.38 (20:00 - 05:00)
- Thu, 2025-05-22 05:00+0900 - Fri, 2025-05-23 05:00+0900
    - Weekly Rotation
        - John Smith: 1.00 (05:00 - 05:00)
- Fri, 2025-05-23 05:00+0900 - Sat, 2025-05-24 05:00+0900
    - Weekly Rotation
        - John Smith: 1.00 (05:00 - 05:00)
- Sat, 2025-05-24 05:00+0900 - Sun, 2025-05-25 05:00+0900
    - Weekly Rotation
        - John Smith: 1.00 (05:00 - 05:00)
- Sun, 2025-05-25 05:00+0900 - Mon, 2025-05-26 05:00+0900
    - Weekly Rotation
        - John Smith: 1.00 (05:00 - 05:00)
- Mon, 2025-05-26 05:00+0900 - Tue, 2025-05-27 05:00+0900
    - Weekly Rotation
        - John Smith: 1.00 (05:00 - 05:00)
- Tue, 2025-05-27 05:00+0900 - Wed, 2025-05-28 05:00+0900
    - Weekly Rotation
        - John Smith: 1.00 (05:00 - 05:00)
- Wed, 2025-05-28 05:00+0900 - Thu, 2025-05-29 05:00+0900
    - Weekly Rotation
        - John Smith: 0.62 (05:00 - 20:00)
        - Takeshi Arabiki: 0.38 (20:00 - 05:00)
- Thu, 2025-05-29 05:00+0900 - Fri, 2025-05-30 05:00+0900
    - Weekly Rotation
        - Takeshi Arabiki: 1.00 (05:00 - 05:00)
- Fri, 2025-05-30 05:00+0900 - Sat, 2025-05-31 05:00+0900
    - Weekly Rotation
        - Takeshi Arabiki: 1.00 (05:00 - 05:00)
- Sat, 2025-05-31 05:00+0900 - Sun, 2025-06-01 05:00+0900
    - Weekly Rotation
        - Takeshi Arabiki: 1.00 (05:00 - 05:00)

# PagerDuty schedules

## Weekly Rotation

- 2025-05-01T05:00:00+09:00 - 2025-05-07T20:00:00+09:00: Takeshi Arabiki
- 2025-05-07T20:00:00+09:00 - 2025-05-14T20:00:00+09:00: John Smith
- 2025-05-14T20:00:00+09:00 - 2025-05-21T20:00:00+09:00: Takeshi Arabiki
- 2025-05-21T20:00:00+09:00 - 2025-05-28T20:00:00+09:00: John Smith
- 2025-05-28T20:00:00+09:00 - 2025-06-01T05:00:00+09:00: Takeshi Arabiki

おわりに

以上、pd-shift の紹介でした。このツールが事務作業の負担軽減に繋がると幸いです。