3
4

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

[Swift]カレンダーは月曜始まりの地域があるから気をつけてねという話

Posted at

Simulator Screen Shot - iPhone 11 - 2020-06-08 at 19.28.34.png

こんな感じの自作カレンダーを搭載したアプリを2018年にリリースして、放置していました。
すると最近、イタリア語らしき言語で「日付と曜日が違うんだけど」といったニュアンスのレビューがつきました。
最初はLocalの設定とかに問題があるのかな程度に思っていたのですが、よくよく調べてみるとCalendarクラスを使った実装方法に問題があったため、2年間気付かなかった自戒の意味も込めてここで供養します:pray:

既存実装

カレンダーはUICollectionViewを使って実装していて、曜日を表示しているセクションと日付を表示しているセクションの2セクション構成としています。
余談ですが、左右にスワイプで月が切り替わります。

UICollectionViewを使ったカレンダー実装はたくさん記事があるのでここでは割愛させていただいて、今回問題になった部分のコードだけ掲載します。

曜日を用意する部分

section0で表示している曜日の文字列配列を用意している部分のコードです。

let symbols = Calendar.current.shortStandaloneWeekdaySymbols

日付を用意する部分

section1で表示している日付の配列を用意している部分のコードです。

    //月の配列
    func dateForCellAtIndexPathForDate(selectedDate: Date) -> [Date] {
        var dates = [Date]()
        let firstDate = firstDateOfMonth(date: selectedDate)
        let ordinalityOfFirstDay = Calendar.current.ordinality(of: .day, in: .weekOfMonth, for: firstDate)!
        
        let numberOfWeeks = Calendar.current.range(of: .weekOfMonth, in: .month, for: firstDate)!.count
        let number = numberOfWeeks * 7
        
        for i in 0..<number{
            let dateComponents = DateComponents()
            dateComponents.day = i - (ordinalityOfFirstDay - 1)
            let date = Calendar.current.date(byAdding: dateComponents, to: firstDate)!
            dates.append(date)
        }
        return dates
    }


    //月の初日を取得
    func firstDateOfMonth(date: Date) -> Date {
        var components = Calendar.current.dateComponents([.year, .month, .day], from: date)
        components.day = 1
        let firstDateMonth = Calendar.current.date(from: components)!
        return firstDateMonth
    }

*掲載用にここでは強制アンラップしています

内容を簡単にいうと

  • Calendar.current.shortStandaloneWeekdaySymbolsでデバイスの言語設定にあわせた日〜土の曜日配列を取得してそれをsection0に表示
  • Calendar.current.ordinality(of: .day, in: .weekOfMonth, for: firstDate)で該当月の第一週で1日が何番目かを取得して、そこから該当月の週数x7した数だけ1日ずつ進めて、該当月のカレンダーに表示する日付の配列を作成し、section1に表示

といった手順を行っていました。
Simulator Screen Shot - iPhone 11 - 2020-06-09 at 00.10.29.png

曜日がずれる

この状態で、端末の地域設定をイタリアに変更してみます。
Simulator Screen Shot - iPhone 11 - 2020-06-09 at 00.17.10.png

日本 イタリア
Simulator Screen Shot - iPhone 11 - 2020-06-08 at 19.28.34.png Simulator Screen Shot - iPhone 11 - 2020-06-09 at 00.19.47.png

ズレてる!!

原因

これはタイトルにある通り、イタリアがある欧州などの一部の地域ではカレンダーが月曜始まりである事が原因です。
今回のロジックで言うと、Calendar.current.ordinality(of: .day, in: .weekOfMonth, for: firstDate)の結果が異なる事が原因です。

実際に、2020年7月の結果を出力してみます。

日本 イタリア
4 3

このように、1日ズレてしまいます。
なぜズレるのか、理由はどうやらordinalityのweekが絡むケースの計算ロジックでfirstWeekDayというやつが使われているようなんです。
(詳細に興味がある方はこの辺をご覧ください。)

このfirstWeekDayCalendar.current.firstWeekdayで取得できるため、各地域設定で出力してみます。

日本 イタリア
1 2

おぉ、違いますね!
これは週の始まりが何曜日かを示していて、1は日曜日で2は月曜日です。

対応

今回どのように対応したかというと、素直に月曜始まりの地域では曜日の並びを月曜始まりで表示する事としました。
そもそもCalendar.current.shortStandaloneWeekdaySymbolsfirstWeekDayを考慮した配列を返してくれれば良かったのですが、現状そういうわけではないようなので、自分で対応します。。。

    // 曜日の配列
    func shortWeekdaySymbols() -> [String] {
        var symbols = Calendar.current.shortStandaloneWeekdaySymbols
        let firstWeekday = symbols[Calendar.current.firstWeekday - 1]
        while symbols.first! != firstWeekday {
            let symbol = symbols.removeFirst()
            symbols.append(symbol)
        }
        return symbols
    }

現状は日曜始まりと月曜始まりしかないとは思っていますが、自分が未知の地域や今後文化の変化でどうなるか分からなかったので一応どの曜日始まりでも対応できるようにしておきました。

結果

日本 イタリア
Simulator Screen Shot - iPhone 11 - 2020-06-08 at 19.28.34.png Simulator Screen Shot - iPhone 11 - 2020-06-09 at 01.24.33.png

うまくいきました!
他に良い方法があればぜひコメントお願いします:bow:

最後に

このカレンダーを実装したアプリはこちらです。
デザイン、機能などまだまだ改善の余地があるのでよろしければレビューお願いします!
ありがとうございました!!

3
4
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
4

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?