Swiftで日付や時刻を扱う場合はDate
やDateComponents
、Calendar
などを使います。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)
他にもamSymbol
、pmSymbol
, 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
を使います。Calendar
にTimeZone
を指定すると、例えば「アメリカ西海岸の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
またstartOfWeek
やnextWeek
などのメソッドもfirstWeekday
に基づいて正しいDate
が返されます。