DateComponents オブジェクトの拡張
DateComponents オブジェクトのプロパティに String? 型の jpHoliday を追加します。代替休日も対象としています。休日でない場合は nil です。このプロパティは文字列を保持していないので、取得する度にプログラムが動作することに注意してください。
Consoleアプリ例
print(DateComponents(year: 2024, month: 3, day: 20).jpHoliday!)
実行結果
春分の日
JPHoliday クラス
JPHoliday クラスが実際の休日名を求める処理です。
JPHoliday オブジェクトを作成して使用することはありません。
静的メソッド
メソッド | 内容 | 代替 |
---|---|---|
holidays(_ year: Int) -> [DateComponents] | 年間の休日一覧 | × |
holidays(_ year: Int, _ month: Int) -> [DateComponents] | 月間の休日一覧 | × |
holiday(_ cdate: DateComponents) -> String? | 休日名を取得 | ○ |
holiday(_ year: Int, _ month: Int, _ day: Int) -> String? | 休日名を取得 | ○ |
vernalEquinox(_ year: Int) -> DateComponents | 春分の日を取得 | - |
autumnEquinox(_ year: Int) -> DateComponents | 秋分の日を取得 | - |
注意:春分/秋分の日は大雑把な計算で求めています
Consoleアプリ例
JPHoliday.holidays(2024).forEach({
let wd = ["", "日","月","火","水","木","金","土"]
let m = $0.month! < 10 ? " \($0.month!)" : "\($0.month!)";
let d = $0.day! < 10 ? " \($0.day!)" : "\($0.day!)";
print("\($0.year!)年\(m)月\(d)日(\(wd[$0.weekday!])): \($0.jpHoliday!)")
})
実行結果
2024年 1月 1日(月): 元日
2024年 1月 8日(月): 成人の日
2024年 2月11日(日): 建国記念の日
2024年 2月23日(金): 天皇誕生日
2024年 3月20日(水): 春分の日
2024年 4月29日(月): 昭和の日
2024年 5月 3日(金): 憲法記念日
2024年 5月 4日(土): みどりの日
2024年 5月 5日(日): こどもの日
2024年 7月15日(月): 海の日
2024年 8月11日(日): 山の日
2024年 9月16日(月): 敬老の日
2024年 9月22日(日): 秋分の日
2024年10月14日(月): スポーツの日
2024年11月 3日(日): 文化の日
2024年11月23日(土): 勤労感謝の日
DateComponents 拡張コード
jpholiday.swift
import Foundation
/// JPHoliday 休日情報クラス
///
/// 休日名に対して年月日を指定する情報を保持
///
class JPHoliday: CustomStringConvertible {
/// 休日名
let name: String
/// 月
let month: Int
/// 日、第N曜日、春分、秋分
let day: Any
/// 開始年
let start: Int?
/// 終了年
let end: Int?
private let dayCode: Int
private let startYear: Int
private let endYear: Int
init(_ parameter: [Any?]) {
let pday = parameter[2]!
let sday = pday as? String
let nday = pday as? Int ?? [
"M1": 32 + 0,
"M2": 32 + 1,
"M3": 32 + 2,
"M4": 32 + 3,
"VE": 64 + 0,
"AE": 64 + 1,
][sday]
name = parameter[0] as! String
month = parameter[1] as! Int
day = pday;
start = parameter[3] as? Int
end = parameter[4] as? Int
dayCode = nday!;
startYear = start ?? 1984
endYear = end ?? 9999
}
/// 適用年調査
/// - parameters:
/// - year : 調査年
/// - returns: 有効なら true
func valid(_ year: Int) -> Bool {
return startYear <= year && year <= endYear
}
/// 日を取得(移動休日は年から算出)
/// - parameters:
/// - year : 年
/// - returns: 日
func getDay(_ year: Int) -> Int {
switch (dayCode >> 5) {
case 0:
return dayCode
case 1:
let mtw = JPHoliday.weekDay(JPHoliday.date(year, month, 1))
let wpos = (dayCode & 31)
return 1 + (8 - mtw) % 7 + wpos * 7
case 2:
switch (dayCode & 31) {
case 0:
return JPHoliday.vernalEquinox(year).day!
case 1:
return JPHoliday.autumnEquinox(year).day!
default:
break
}
break
default:
break
}
return -1
}
/// 年から日を特定して DateComponents 型を取得する
/// - parameters:
/// - year : 年
/// - returns: DateComponents 型
func getDateComponents(_ year: Int)
-> DateComponents {
let mday = getDay(year)
let dt = JPHoliday.date(year, month, mday)
return JPHoliday.dateComponents(dt)
}
var description: String {
get {
var sday = "ERROR"
switch (dayCode >> 5) {
case 0:
sday = "\(dayCode)日"
case 1:
sday = "(第\(1 + (dayCode & 15))月曜)"
case 2:
switch (dayCode & 31) {
case 0: sday = "(春分)"
case 1: sday = "(秋分)"
default: break
}
default:
break
}
return "\(name):\(month)月\(sday)"
}
}
// MARK: - グレゴリオ暦とタイムゾーン
private static let timeZone = TimeZone(identifier: "Asia/Tokyo")!
private static func getCalendar() -> Calendar {
var calendar = Calendar(identifier: .gregorian)
calendar.timeZone = JPHoliday.timeZone
return calendar;
}
private static let gregorian = JPHoliday.getCalendar();
private static func date(_ year: Int, _ month: Int, _ day: Int) -> Date {
let cdate = DateComponents(
calendar: gregorian, timeZone: timeZone, era: 1,
year: year, month: month, day: day)
return gregorian.date(from: cdate)!
}
private static func dateComponents(_ date: Date) -> DateComponents {
return gregorian.dateComponents([
.calendar, .timeZone, .era, .year, .month, .day, .weekday
], from: date)
}
private static func jpFixDateComponents(_ timeIntervalSince1970: UInt64)
-> DateComponents {
let tzjp = UInt64(9 * 60 * 60)
let rgmt = timeIntervalSince1970 - tzjp
let jpdate = tzjp + rgmt - (rgmt % (24 * 60 * 60))
return dateComponents(Date(timeIntervalSince1970: Double(jpdate)))
}
private static func weekDay(_ components: DateComponents) -> Int {
return (components.weekday! - 1) % 7
}
private static func weekDay(_ date: Date) -> Int {
return weekDay(dateComponents(date))
}
// MARK: - 休日データ
private static let holidayTable: [[Any?]] = [
["元日", 1, 1, nil, nil],
["成人の日", 1, 15, 0, 1999],
["成人の日", 1, "M2", 2000, nil],
["建国記念の日", 2, 11, nil, nil],
["春分の日", 3, "VE", nil, nil],
["憲法記念日", 5, 3, nil, nil],
["みどりの日", 5, 4, 2007, nil],
["こどもの日", 5, 5, nil, nil],
["海の日", 7, 20, 1996, 2002],
["海の日", 7, "M3", 2003, nil],
["山の日", 8, 11, 2016, nil],
["敬老の日", 9, 15, 1966, 2002],
["敬老の日", 9, "M3", 2003, nil],
["秋分の日", 9, "AE", nil, nil],
["体育の日", 10, 10, 0, 1999],
["体育の日", 10, "M2", 2000, 2019],
["スポーツの日", 10, "M2", 2020, nil],
["文化の日", 11, 3, nil, nil],
["勤労感謝の日", 11, 23, nil, nil],
// 昭和
["天皇誕生日", 4, 29, 0, 1988],
["みどりの日", 4, 29, 1989, 2006],
["昭和の日", 4, 29, 2007, nil],
// 平成
["天皇誕生日", 12, 23, 1989, 2018],
// 令和
["天皇の即位の日", 5, 1, 2019, 2019],
["即位礼正殿の儀が行われる日", 10, 22, 2019, 2019],
["天皇誕生日", 2, 23, 2020, nil],
]
private static let instances: [JPHoliday] = Array(
unsafeUninitializedCapacity: JPHoliday.holidayTable.count
) {
buffer, initializedCount in
let tab = JPHoliday.holidayTable
for n in 0..<tab.count {
buffer[n] = JPHoliday(tab[n])
}
initializedCount = tab.count
}
private static let monthTable: [[Int:[JPHoliday]]] =
Array<[Int:[JPHoliday]]>(unsafeUninitializedCapacity: 13) {
buffer, initializedCount in
for n in 0...12 {
buffer[n] = [:]
}
for h in JPHoliday.instances {
var mtab = buffer[h.month]
var mday: [JPHoliday]? = mtab[h.dayCode]
if mday == nil {
mday = [h]
} else {
mday!.append(h)
}
mtab[h.dayCode] = mday
buffer[h.month] = mtab
}
initializedCount = 13
}
// MARK: - 代替休日を含む休日処理
private static func basicHoliday(_ date: Date) -> String? {
let comp = dateComponents(date);
let year = comp.year!, month = comp.month!, day = comp.day!
let check = ({(h: JPHoliday) -> Bool in h.valid(year)})
let mtab = monthTable[month]
let dtab = mtab[day]
if dtab != nil {
let days = dtab!.filter(check)
if days.count != 0 {
return days[0].name
}
}
let wday = weekDay(comp)
if wday == 1 {
let mweek = (day - 1) / 7
let monday = mtab[32 + mweek]
if monday != nil {
let t = monday!.filter(check)
if t.count != 0 {
return t[0].name
}
}
}
let tve = mtab[64+0]
if tve != nil && day == vernalEquinox(year).day {
return tve![0].name
}
let tae = mtab[64+1]
if tae != nil && day == autumnEquinox(year).day {
return tae![0].name
}
return nil;
}
private static var weekCache: [Int:[String?]] = [:]
private static func weekLine(_ year: Int, _ month: Int, _ day: Int)
-> [String?] {
if year < 1948 {
return Array(repeating: nil, count: 8)
}
let wtop = weekDay(date(year, month, 1))
let mweek = (wtop + day - 1) / 7
let index = (year * 12 + month) * 6 + mweek
let cache = weekCache[index]
if cache != nil {
return cache!
}
let iday = UInt64(24 * 60 * 60)
let dtcur = date(year, month, day)
let dtcmp = dateComponents(dtcur)
let wday = UInt64(weekDay(dtcmp))
let mdtop = UInt64(dtcur.timeIntervalSince1970) - wday * iday
var line: [String?] = Array<String?>(repeating: nil, count: 8)
for i in 0...7 {
let sec = Double(mdtop + iday * UInt64(i))
let wdate = Date(timeIntervalSince1970: sec)
let h = basicHoliday(wdate)
if h != nil {
line[i] = h!
}
}
let altname = "休日"
if year >= 1973 && line[0] != nil {
for i in 1...6 {
if line[i] == nil {
line[i] = altname
break
}
}
}
if year >= 1985 {
for i in 1...6 {
if line[i] == nil && line[i-1] != nil && line[i+1] != nil {
line[i] = altname
break
}
}
}
weekCache[index] = line
return line
}
// MARK: - 春分 / 秋分
///
/// 年から春分の日を取得する
/// - parameters:
/// - year : 年
/// - returns: 春分の日の DateComponents 型
static func vernalEquinox(_ year: Int) -> DateComponents {
let ve = UInt64(315569255616 * (year - 1970) + 68619916800) / 10000
return jpFixDateComponents(ve)
}
/// 年から秋分の日を取得する
/// - parameters:
/// - year : 年
/// - returns: 秋分の日の DateComponents 型
static func autumnEquinox(_ year: Int) -> DateComponents {
let ae = UInt64(315569255616 * (year - 1970) + 229674407040) / 10000
return jpFixDateComponents(ae)
}
// MARK: - 休日の一覧を取得
/// 月間の休日一覧を取得
/// - parameters:
/// - year : 年
/// - month : 月
/// - returns: DateComponents の一覧
static func holidays(_ year: Int, _ month: Int) -> [DateComponents] {
let mtab = monthTable[month]
let info = mtab.values.map {
$0.filter({(h: JPHoliday) -> Bool in h.valid(year)})
}.joined()
var rval = Array(info).map { $0.getDateComponents(year) }
rval.sort {
switch ($0, $1) {
default:
if $0.year! < $1.year! { return true }
if $0.month! < $1.month! { return true }
if $0.day! < $1.day! { return true }
return false;
}
}
return rval
}
/// 年間の休日一覧を取得
/// - parameters:
/// - year : 年
/// - returns: DateComponents の一覧
static func holidays(_ year: Int) -> [DateComponents] {
var rval: [DateComponents] = []
for month in 1...12 {
rval += JPHoliday.holidays(year, month)
}
return rval
}
// MARK: - 日付から休日名を取得
/// DateComponents から休日名(代替休日も対象)を取得する
/// - parameters:
/// - cdate: year, month, day, の入った DateComponents 型
/// - returns: 休日名または nil
static func holiday(_ cdate: DateComponents) -> String? {
let calendar = cdate.calendar
if calendar != nil &&
calendar!.identifier != Calendar.Identifier.gregorian {
return holiday(dateComponents(calendar!.date(from: cdate)!))
}
if cdate.era != nil && cdate.era! == 0 {
return nil
}
let year = cdate.year
let month = cdate.month
let day = cdate.day
if year == nil || month == nil || day == nil {
return nil
}
let week = weekLine(year!, month!, day!)
let wday = dateComponents(date(year!, month!, day!)).weekday!
return week[wday - 1]
}
/// 年月日から休日名(代替休日も対象)を取得する
/// - parameters:
/// - year: 年
/// - month: 月
/// - day: 日
/// - returns: 休日名または nil
static func holiday(_ year: Int, _ month: Int, _ day: Int) -> String? {
return holiday(dateComponents(date(year, month, day)))
}
}
extension DateComponents {
/// 休日名を取得
var jpHoliday: String? {
get {
return JPHoliday.holiday(self)
}
}
}
他言語プログラム
JavaScript版
Python版