こんな感じの自作カレンダーを搭載したアプリを2018年にリリースして、放置していました。
すると最近、イタリア語らしき言語で「日付と曜日が違うんだけど」といったニュアンスのレビューがつきました。
最初はLocalの設定とかに問題があるのかな程度に思っていたのですが、よくよく調べてみるとCalendarクラスを使った実装方法に問題があったため、2年間気付かなかった自戒の意味も込めてここで供養します
既存実装
カレンダーは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
に表示
曜日がずれる
日本 | イタリア |
---|---|
![]() |
![]() |
ズレてる!!
原因
これはタイトルにある通り、イタリアがある欧州などの一部の地域ではカレンダーが月曜始まりである事が原因です。
今回のロジックで言うと、Calendar.current.ordinality(of: .day, in: .weekOfMonth, for: firstDate)
の結果が異なる事が原因です。
実際に、2020年7月の結果を出力してみます。
日本 | イタリア |
---|---|
4 | 3 |
このように、1日ズレてしまいます。
なぜズレるのか、理由はどうやらordinality
のweekが絡むケースの計算ロジックでfirstWeekDay
というやつが使われているようなんです。
(詳細に興味がある方はこの辺をご覧ください。)
このfirstWeekDay
はCalendar.current.firstWeekday
で取得できるため、各地域設定で出力してみます。
日本 | イタリア |
---|---|
1 | 2 |
おぉ、違いますね!
これは週の始まりが何曜日かを示していて、1
は日曜日で2
は月曜日です。
対応
今回どのように対応したかというと、素直に月曜始まりの地域では曜日の並びを月曜始まりで表示する事としました。
そもそもCalendar.current.shortStandaloneWeekdaySymbols
でfirstWeekDay
を考慮した配列を返してくれれば良かったのですが、現状そういうわけではないようなので、自分で対応します。。。
// 曜日の配列
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
}
現状は日曜始まりと月曜始まりしかないとは思っていますが、自分が未知の地域や今後文化の変化でどうなるか分からなかったので一応どの曜日始まりでも対応できるようにしておきました。
結果
日本 | イタリア |
---|---|
![]() |
![]() |
うまくいきました!
他に良い方法があればぜひコメントお願いします
最後に
このカレンダーを実装したアプリはこちらです。
デザイン、機能などまだまだ改善の余地があるのでよろしければレビューお願いします!
ありがとうございました!!