😇

UNCalendarNotificationTriggerで日時指定のローカルPUSHが飛ばない

2021/08/20に公開

ことのあらまし

ちょっと気が向いてアラームアプリを作っていて、アプリがバックグラウンドに入った状態で設定時間に到達したことを通知するためにローカルPUSHを使おうと思い、以下のようなコードを書いていました。

let triggerDate = Calendar.current.dateComponents(in: TimeZone.current, from: finishedAt)
let trigger = UNCalendarNotificationTrigger(dateMatching: triggerDate, repeats: false)

※ finishedAtはDate型の終了日時が入っています。

ところが、待てども待てども通知が来ない。
さぁ困った、という話です。

調査

UNCalendarNotificationTriggerには nextTriggerDateというプロパティがあります。
https://developer.apple.com/documentation/usernotifications/uncalendarnotificationtrigger/1649775-nexttriggerdate
要するに次にいつ発火するか、が入っているプロパティなわけですが、これをprintしてみます。

print(trigger.nextTriggerDate() ?? "nil")

実行してみると、nilが出ていました。

次に、雑にdatecomponentsを定義して渡してみます。

var dc = DateComponents()
dc.year = 2021
dc.month = 8
dc.day = 20
dc.hour = 15

let trigger2 = UNCalendarNotificationTrigger(dateMatching: dc, repeats: false)
print(trigger2.nextTriggerDate() ?? "hoge")

上記のコードでは、 2021-08-20 06:00:00 +0000 と想定通りの値が出力されました。
なるほど・・・。

じゃあ実際DateComponentsってどういう値が入っているの?ということで次のステップ。

var dc = DateComponents()
dc.year = 2021
dc.month = 8
dc.day = 20
dc.hour = 12

dump(dc)

let dc2 = Calendar.current.dateComponents(in: TimeZone.current, from: Date())
dump(dc2)

実行すると以下のような形。

<NSDateComponents: 0x00005590536baae0>
  - year: 2021
  - month: 8
  - day: 20
  - hour: 12

▿ <NSDateComponents: 0x0000559053720bc0>
  ▿ calendar: gregorian (current)
    - identifier: Foundation.Calendar.Identifier.gregorian
    - kind: "current"
    ▿ locale: Optional(<NSLocale: 0x0000559053721570>)
      ▿ some: <NSLocale: 0x0000559053721570>
        ▿ _wrapped: <NSLocale: 0x0000559053721570> #0
          - super: Foundation.NSObject
          ▿ _base: Foundation._CFInfo
            - info: 14210
            - pad: 0
          ▿ _identifier: Optional(0x00007f261a55df58)
            ▿ some: 0x00007f261a55df58
              - pointerValue: 139801627320152
          ▿ _cache: Optional(0x00005590537221f0)
            ▿ some: 0x00005590537221f0
              - pointerValue: 94078363640304
          - _prefs: nil
          - _lock: 0
          - _nullLocale: false
        - _autoupdating: false
    ▿ timeZone: GMT (current)
      - identifier: "GMT"
      - kind: "current"
      ▿ abbreviation: Optional("GMT")
        - some: "GMT"
      - secondsFromGMT: 0
      - isDaylightSavingTime: false
    - firstWeekday: 1
    - minimumDaysInFirstWeek: 1
  ▿ timeZone: GMT (current)
    - identifier: "GMT"
    - kind: "current"
    ▿ abbreviation: Optional("GMT")
      - some: "GMT"
    - secondsFromGMT: 0
    - isDaylightSavingTime: false
  - era: 1
  - year: 2021
  - month: 8
  - day: 20
  - hour: 3
  - minute: 13
  - second: 25
  - nanosecond: 71038961
  - weekday: 6
  - weekdayOrdinal: 3
  - quarter: 0
  - weekOfMonth: 3
  - weekOfYear: 34
  - yearForWeekOfYear: 2021
  - isLeapMonth: false

後者のほうがtriggerがnilになるパターンのDateComponentsで、このdumpをよく見ると quarter が0になっています。8月なのに0・・・?

ということで、DateCompnents quarter zero みたいなクエリでグーグルさんに聞いて回った結果、こういうのを見つけました。

http://www.openradar.me/35247464

やはりquarterがおかしそう。

対処(パワープレイ)

quarterがおかしくてDateComponentsがTriggerに認識されていない(言い方があってるかわかりませんが)のであれば、ということで以下のようにしてみました。

// let -> varに変更
var triggerDate = Calendar.current.dateComponents(in: TimeZone.current, from: finishedAt)
// 無理やりquarterをnilに
triggerDate.quarter = nil
let trigger = UNCalendarNotificationTrigger(dateMatching: triggerDate, repeats: false)

すると、何事もなかったかのように trigger.nextTriggerDate() は値を返し、ローカルPUSHも無事送信されるようになりました。

めでたし めでたし

Discussion