28
Help us understand the problem. What are the problem?

More than 3 years have passed since last update.

posted at

【Swift】Calendarに日付操作のメソッドを追加してみた

Swiftで日付や時刻を扱う場合はDateDateComponentsCalendarなどを使います。Calendarには日付に関する便利メソッドがいくつか実装されています。

例えば以下のコードで日付の始まり(当日の午前0時0分0秒)を取得できます。

let date = Date()
let today = Calendar.current.startOfDay(for: date)

また特定のDateが今日か、明日か、昨日か、週末かを判別できます。

let date = Date()
let isToday = Calendar.current.isDateInToday(date)
let isTomorrow = Calendar.current.isDateInTomorrow(date)
let isYesterday = Calendar.current.isDateInYesterday(date)
let isWeekend = Calendar.current.isDateInWeekend(date)

2つのDateが同じ日かどうか判別できます。

let date1 = Date()
let date2 = date1.addingTimeInterval(3600)
let isSameDay = Calendar.current.isDate(date1, inSameDayAs: date2)

他にもamSymbolpmSymbol, monthSymbols, weekdaySymbolsで「午前」「午後」「1月」「2月」「日曜日」「月曜日」などの文字列を取得したりもできます。

日付の始まりと終わり

startOfDayで日付の始まりを取得できましたが、例えばendOfDay(翌日の午前0時0分0秒)を取得したり、明日、昨日、などのDateを取得するようなメソッドを追加してみます。

extension Calendar {

    //MARK: - Day operations

    func endOfDay(for date:Date) -> Date {
        return nextDay(for: date)
    }

    func previousDay(for date:Date) -> Date {
        return move(date, byDays: -1)
    }

    func nextDay(for date:Date) -> Date {
        return move(date, byDays: 1)
    }

    //MARK: - Move operation

    func move(_ date:Date, byDays days:Int) -> Date {
        return self.date(byAdding: .day, value: days, to: startOfDay(for: date))!
    }
}

特にendOfDayは、EventKitや外部APIなどで日付の範囲を指定する時などに使うことができます。endOfDayの実装はnextDayと同じですが、コードを見たときに分かりやすいので別々にしています。

let date = Date()
let start = Calendar.current.startOfDay(for: date)
let end = Calendar.current.endOfDay(for: date)

// 今日1日のイベントを取得
let eventStore = EKEventStore()
let predicate = eventStore.predicateForEvents(withStart: start, end: end, calendars: nil)
let events = eventStore.events(matching: predicate)

週、月の始まりと終わり

週、月に関するメソッドも追加してみます。


extension Calendar {

    //MARK: - Week operations

    func startOfWeek(for date:Date) -> Date {
        let comps = self.dateComponents([.weekOfYear, .yearForWeekOfYear], from: date)
        return self.date(from: comps)!
    }

    func endOfWeek(for date:Date) -> Date {
        return nextWeek(for: date)
    }

    func previousWeek(for date:Date) -> Date {
        return move(date, byWeeks: -1)
    }

    func nextWeek(for date:Date) -> Date {
        return move(date, byWeeks: 1)
    }

    //MARK: - Month operations

    func startOfMonth(for date:Date) -> Date {
        let comps = dateComponents([.month, .year], from: date)
        return self.date(from: comps)!
    }

    func endOfMonth(for date:Date) -> Date {
        return nextMonth(for: date)
    }

    func previousMonth(for date:Date) -> Date {
        return move(date, byMonths: -1)
    }

    func nextMonth(for date:Date) -> Date {
        return move(date, byMonths: 1)
    }

    //MARK: - Move operations

    func move(_ date:Date, byWeeks weeks:Int) -> Date {
        return self.date(byAdding: .weekOfYear, value: weeks, to: startOfWeek(for: date))!
    }

    func move(_ date:Date, byMonths months:Int) -> Date {
        return self.date(byAdding: .month, value: months, to: startOfMonth(for: date))!
    }
}

以下のコードで昨日、明日、先週、来週、先月、来月の始まりのDateを取得できます。

let date = Date()
let yesterday = Calendar.current.previousDay(for: date)
let tomorrow = Calendar.current.nextDay(for: date)

let lastWeek = Calendar.current.previousWeek(for: date)
let nextWeek = Calendar.current.nextWeek(for: date)

let lastMonth = Calendar.current.previousMonth(for: date)
let nextMonth = Calendar.current.nextMonth(for: date)

また2日前、3週間後、半年前などは以下のように取得できます。

let date = Date()
let twoDaysAgo = Calendar.current.move(date, byDays: -2)
let threeWeeksFromNow = Calendar.current.move(date, byWeeks: 3)
let sixMonthsAgo = Calendar.current.move(date, byMonths: -6)

同じ週、月か

2つのDateが同じ週にあるか、同じ月にあるかを判別できるメソッドを追加しました。

extension Calendar {

    //MARK: - inSame operations

    func isDate(_ date1:Date, inSameWeekAs date2:Date) -> Bool {
        return isDate(date1, equalTo: date2, toGranularity: .weekOfYear)
    }

    func isDate(_ date1:Date, inSameMonthAs date2:Date) -> Bool {
        return isDate(date1, equalTo: date2, toGranularity: .month)
    }
}

月の日数・週数

ひと月の日数や週の数を取得するメソッドも追加しました。

extension Calendar {

    //MARK: - Range operations

    func daysInMonth(for date:Date) -> Int {
        return range(of: .day, in: .month, for: date)!.count
    }

    func weeksInMonth(for date:Date) -> Int {
        return range(of: .weekOfMonth, in: .month, for: date)!.count
    }
}

ひと月の日数や週数は以下のコードで取得できます。

let date = Date()
let days = Calendar.current.daysInMonth(for: date)
let weeks = Calendar.current.weeksInMonth(for: date)

2つのDate間の日数

2つのDate間の日数を取得するメソッドを追加しました。

extension Calendar {

    //MARK: - Range operations

    func days(from date1:Date, to date2:Date) -> Int {
        let comps = dateComponents([.day], from: startOfDay(for: date1), to: startOfDay(for: date2))
        return comps.day!
    }
}

使い方はこのようになります。

let date1 = Date()
let date2 = Calendar.current.move(date1, byDays: 7)
let days = Calendar.current.days(from: date1, to: date2)

Calendarに実装されているメソッドを使って、まだまだ色々な便利メソッドを追加できそうです。

TimeZoneについて

世界には時差があるので、日付や時刻を扱う場合は常にそのことを意識する必要があります。そのためにTimeZoneを使います。CalendarTimeZoneを指定すると、例えば「アメリカ西海岸の1日の始まりと終わり」を以下のように取得できます。

let date = Date()
if let timeZone = TimeZone(identifier: "America/Los_Angeles") {
    var calendar = Calendar.current
    calendar.timeZone = timeZone
    let start = calendar.startOfDay(for: date)
    let end = calendar.endOfDay(for: date)
}

Calendarに日付関連のメソッドを追加する大きな理由のひとつに、TimeZone対応が挙げられます。今回追加した全てのメソッドはタイムゾーンに対応しています。

週の始まりについて

週の始まりは日米では日曜日、欧州では月曜日が一般的です。CalendarのメソッドはすでにfirstWeekdayに対応しているため、今回追加したメソッドもこれに対応済みとなります。

例えばweeksInMonthは週の始まりが日曜日か月曜日かで結果が変わります。

var c = DateComponents()
c.year = 2018
c.month = 4
c.day = 1
let date = Calendar.current.date(from: c)!

var calendar = Calendar.current
calendar.firstWeekday = 1 // 日曜日
print(calendar.weeksInMonth(for: date)) // 実行結果:5

calendar.firstWeekday = 2 // 月曜日
print(calendar.weeksInMonth(for: date)) // 実行結果:6

またstartOfWeeknextWeekなどのメソッドもfirstWeekdayに基づいて正しいDateが返されます。

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Sign upLogin
28
Help us understand the problem. What are the problem?