以下の内容はhttps://kouki.hatenadiary.com/entry/2026/02/16/113141より取得しました。


和暦対応は大変

iPhoneでは、OSの設定としてカレンダーの暦法を設定できます。日本では、西暦、和暦、タイ仏暦から選べます。*1

以前、和暦のみで起きるバグに遭遇してから、iPhoneを和暦にして使うようにしていました。

developer.hatenastaff.com

Declared Age Rangeフレームワーク

ところで、iOS 26からDeclared Age Rangeフレームワークというものが導入されました。これは、年齢によるアプリの機能制限をプライバシーに配慮した形で行えるようにするものです。実際の生年月日をアプリデベロッパに知らせることがなく、アプリを使用しているユーザーの年齢範囲を取得できます。アプリの使用制限のためであれば生年月日を知る必要がないので、Appleらしいプライバシーに配慮した、実用性のあるAPIになっています。年齢は自己申告ですが、ペアレンタルコントロールが有効な場合などでは親が年齢を入力するため、信頼性はある程度あるものになっています。

let response = try await AgeRangeService.shared.requestAgeRange(ageGates: 13, 15, 18)
guard let lowerBound = response.lowerBound else {
    // 13歳以下向けのコンテンツを表示
    return
}
if lowerBound >= 18 {
    // 18歳以上向けのコンテンツを表示
} else if lowerBound >= 15 {
    // 15歳以上向けのコンテンツを表示
} else if lowerBound >= 13 {
    // 13歳以上向けのコンテンツを表示
}

先日、このAPIをClaudeで使っている例を見かけました。初めてこの機能を使っているアプリを見かけたので、「あっ、これ、WWDCでやったところだ!」と思って、年齢確認を実施しました。すると、機能が使えなくなりました・・!*2

よく見ると、共有する年齢範囲が17歳以下となっています。つまり、Claudeには17歳以下だと伝わってしまっている。Claudeはおそらくtry await AgeRangeService.shared.requestAgeRange(ageGates: 18)という感じでこのAPIを呼び出していて、18歳以上のみのユーザーにアクセスを許可するようにしているのだと思います。

OSに登録してある生年月日を間違えたかなと思ったけど間違えていない。気になって自分でこのAPIを使う機能を試してみたのですが、なぜか僕の実機でだけ常に0歳以下になってしまう。友達に頼んで試してみてもらったらちゃんと動くという感じで、全くわからないと思っていた時に、思い出したのが僕は暦法を和暦にしているということ。

試しに西暦に変えてみると、正しく成人という年齢範囲が共有されるようになりました。

多分Declared Age Rangeフレームワークのバグだと思うので、フィードバックアシスタントで送信済みです。Appleでも間違うことがあるんだから、複数の暦があることを意識してコードを書かなきゃいけないのは大変ですね。ここからは、どういうコードを書くとこのような挙動をして、どのように実装すると良いのかを考察していきます。

年齢を計算するには、Calendarを使って以下のようなコードを使うことが考えられます。

let age = Calendar.current
    .dateComponents(
        [.year],
        from: birthDate,
        to: Date() // 引数なしで呼び出すと現在時間
    )
    .year

ここで、birthDate をどのように取得するかを考えます。例えば、年月日をそれぞれ西暦の数字で保持しておくというアイデアが考えられます。以下のような構造体を保存しておくイメージです。

/// 誕生日を表す構造体。yearは西暦で扱う
struct BirthDate: Codable {
    var year: Int
    var month: Int
    var day: Int
}

そして、この構造体をDateに変換するメソッドは以下のように書けます。

extension BirthDate {
    func toDate() -> Date {
        let dateComponents = DateComponents(year: year, month: month, day: day)
        return Calendar.current.date(from: dateComponents)!
    }
}

実はこのコード、和暦の端末で僕の誕生日である1992年12月8日を入れると、西暦4010年12月8日生まれということになります。どういうことかというと、Calendar.currentはデバイスに設定された暦法を使用するため、令和1992年という扱いになります。令和元年が西暦2019年なので、令和1992年は西暦4010年です。

つまり、このようなコードを書いてしまうと、4010年に生まれた僕は2025年2月の現在はマイナス1984歳で、成人には程遠いということになってしまいます。

ここで、Calendar.currentを使用しているのが問題なので、BirthDateのパース部分のCalendarをgregorianに固定すると直ります。

extension BirthDate {
    func toDate() -> Date {
        let dateComponents = DateComponents(year: year, month: month, day: day)
        return Calendar(identifier: .gregorian).date(from: dateComponents)!
    }
}

他にも年齢がうまく確認できないパターンがいくつかあるかもしれませんが、一番単純なのはこの例だと思います。日付の扱いは難しいですね。iOSアプリ開発でCalendarを使う時はついCalendar.current を使いたくなってしまいますが、これは暦法が和暦になっていたりする可能性があるので、使うタイミングをちゃんと考えないと不具合を引き起こしてしまいがちです。気をつけましょう

おまけ

最近ランニングをしている中で、ずっとVO2maxという心肺機能を表す数値がApple Watchで取得できないことがずっと悩み事でした。VO2maxはランニングやウォーキングをした時に自動で取得されるとヘルプの説明には書いてあります。

support.apple.com

このヘルプの説明に、年齢を使用してこの値を推定すると書いてあることを、Declared Age Rangeフレームワークのことを調べた後に見かけました。すると、もしかしてこっちでも同じロジックが使われているのでは・・?とひらめき、試しに端末の暦法を西暦にして走ってみました。すると、これまで測られなかったVO2maxが測られるようになりました・・!

やっぱり和暦対応は難しい。この件もあり、僕のiPhoneの暦法も和暦から西暦に戻すことにしました。和暦にしているとApple標準カレンダーなどでも令和や平成と表示されるので、西暦に変換するのが地味にめんどくさかったんですよね。これで快適になりました。

和暦対応のバグには気づけなくなるけど、もう5年近く和暦で過ごしていたので体に染み付いているでしょう、ということにします。

*1:なんでタイ仏暦があるのかは調べてみたけどよくわからなかった。誰か知っている人がいたら教えてください!

*2:ClaudeアプリでAppleアカウントによるアカウント作成を試みると、この機能を試せると思います。他にもあるかも。




以上の内容はhttps://kouki.hatenadiary.com/entry/2026/02/16/113141より取得しました。
このページはhttp://font.textar.tv/のウェブフォントを使用してます

不具合報告/要望等はこちらへお願いします。
モバイルやる夫Viewer Ver0.14