Help us understand the problem. What is going on with this article?

日本の祝祭日を計算してカレンダ-に表示するアプリサンプル

More than 3 years have passed since last update.

1. はじめに

アプリ開発を始めたての頃に一番最初に作成したのが日本の祝祭日を考慮したカレンダーの作成をしたことがきっかけで、その後アプリ開発へ本格的にのめり込んでいったこともあり改めてSwiftできちんと書き直してみようと思い立ち、今回着手をしました。(その当時はObjective-Cで書いていました)

今回のサンプルの動きは下記のようになります。

calendar1.jpg

カレンダーそれ自体に関してはシンプルなものにはなりますが、もしカレンダーアプリ等で活用したい場合や実装の参考にする場合等で活用して頂ければと思います。

実装及びデバッグに関しては@akio0911さんに多くのお力添えを頂きまして本当に感謝をしております。遅くはなってしまいましたが、本当に感謝しておりますm(_ _)m

2. 日本の祝祭日の計算に当たっての参考資料

普段がPHPやRuby等サーバーサイド寄りの業務が多いので、祝祭日及び振替休日の判定プログラムの実装に関しては下記の記事を参考にしました。

2-1. 祝祭日の判定ロジックの参考資料

PHPで作成されたサンプルを基にSwiftに置き換えて計算ロジックを整理するような方針で実装してみました。

公式の資料やテストケースを作成するにあたって参考に使用したものは下記の資料になります。
このカレンダーサンプルで使用しているライブラリの祝祭日に関しては、1948年(昭和23年)7月20日に公布・即日施行された日本の法律である。通称祝日法に則っています。

2-2. 日本独特の祝祭日に関しての参考資料

あとは日本特有の祝祭日として算出が結構面倒くさかったものとしては、

  1. 春分の日と秋分の日(計算によって算出する必要がある)
  2. ゴールデンウィーク(5/3〜5/5のいずれかが日曜日になる場合は5/6が振替休日となる)
  3. シルバーウィーク(敬老の日と秋分の日の間が1日の場合はその日を国民の休日となる)

の算出でした。この部分は下記にピックアップしている資料をもとに算出ロジックを作成しています。

特にSwiftでは型を合わせないと計算ができないので、春分の日・秋分の日の計算式の実装とテストについては若干苦労した部分ではありました。

このサンプルや資料に基づき、日本の祝祭日に対応したカレンダーの実装を行っていきます。

3. 実装のポイントなる部分をピックアップ

日本の祝祭日を計算で判定する際に最も厄介な部分は条件式が複雑になってしまうところです。
(自分で最初に書いたコードはかなり荒っぽいものだったので、バグ潰しでは苦労しました...)

@akio0911さんのリファクタリングのお力添えもあったおかげで、初期と比べても見違える程に見通しよくなおかつロジックの整理ができました。

ここでは祝祭日の判定ロジックの実装部分にてポイントとなる部分に関してピックアップしていきます。

3-1. 祝祭日の条件分岐の見通しを良くする工夫

日本の祝祭日はハッピーマンデー法があるので祝祭日によっては、

  • ⚪︎⚪︎月××日と決まっているもの(例:元旦や昭和の日など)
  • ⚪︎⚪︎月の第×月曜日のようにハッピーマンデー法の影響を受けるもの(例:海の日や体育の日など)

というケースがあります。特にハッピーマンデー法については取り得る値の範囲を設定して判定を行う必要があります。(この部分はWikipediaにて調査をしました)

そのまま条件判定をif文でごにょごにょして...とすると後々の保守も大変になってしまいます。
そこで判定に必要な値を一度タプルにして、それぞれの値を(a, b, c)のような組みにします。

今回は祝祭日の条件判定で必要となるのは、

  • year[年]
  • month[月]
  • day[日]
  • weekday[曜日]

になるのでそれをひとまとめにするタプルを作成し、そのタプルに対してswitch文で条件分岐をします。条件文に関してもwhere句が使えるので各々のケースに対しても、

case (year[年], month[月], day[日], weekday[曜日]) where 条件式:

というような形で下記のように各ケースを記述します。

CalculateCalendarLogic.swift
switch (year, month, day, weekday) {

    //1月1日: 元旦
    case (_, 1, 1, _):
        return true

    //1月2日: 振替休日
    case (_, 1, 2, .Mon):
        return true

    //(1).1月15日(1999年まで)、(2).1月の第2月曜日(2000年から): 成人の日
    case (year, 1, 15, _) where year <= 1999:
        return true

    case (year, 1, 8...14, .Mon) where year >= 2000:
        return true

    //2月11日: 成人の日
    case (_, 2, 11, _):
        return true

    //2月12日: 振替休日
    case (_, 2, 12, .Mon):
        return true

・・・以後続く

このように条件判定に使用する値に関してもタプルでひとまとめにした上で、必要な値に対しての条件判定を行うようにすることで複雑な条件で条件分岐についてもシンプルにできると思います。

Swiftのタプルに関しては本格的に使ったのは初めてでしたが、改めて便利だなーと感じました。
特に日本の祝祭日は法律が施行された時期が異なったりするのでかなり助かりました。

3-2. enum内にメソッドを定義して判定に必要な値をあらかじめ定義しておく

今回の祝祭日判定で、曜日の部分に関してはenumで定義しています。またSwiftからはenum内にメソッドを追加できるようになり、非常に便利になりました。

  • 曜日の判定(月曜日: 0 〜 土曜日: 6) ※アクセスはpublic
  • 春分の日と秋分の日が何日かの算出 ※アクセスはprivate

として2つのenumを定義してあげます。

曜日の判定を行うenum

CalculateCalendarLogic.swift
public enum Weekday: Int {
    case Sun, Mon, Tue, Wed, Thu, Fri, Sat

    //失敗する可能性のあるコンストラクタを定義(初期値を持った状態にしておく)
    init?(year: Int, month: Int, day: Int) {
        let cal = NSCalendar.currentCalendar()
        guard let date = cal.dateWithEra(AD, year: year, month: month, day: day, hour: 0, minute: 0, second: 0, nanosecond: 0) else { return nil }
        let weekdayNum = cal.component(.Weekday, fromDate: date)  // 1:日曜日 ~ 7:土曜日
        self.init(rawValue: weekdayNum - 1)
    }

    //曜日の日本語表記の定義
    var shortName: String {
        switch self {
        case .Sun: return "日"
        case .Mon: return "月"
        case .Tue: return "火"
        case .Wed: return "水"
        case .Thu: return "木"
        case .Fri: return "金"
        case .Sat: return "土"
        }
    }

    var mediumName: String {
        return shortName + "曜"
    }

    var longName: String {
        return shortName + "曜日"
    }
}

上記のようにenumの中にコンストラクタを定義したり、enum内にメソッドを定義することでenumをより柔軟に扱うことができます。ここではenumを変数として保持する際に初期値を入れておくためにこのような定義になっています。

実際に判定で使用しているjudgeJapaneseHoliday(year: Int, month: Int, day: Int)に関しても与えられた引数から曜日を出す処理をしています。

CalculateCalendarLogicTests.swift
let cal = NSCalendar.currentCalendar()
guard let date = cal.dateWithEra(
    AD, year: year, month: month, day: day, hour: 0, minute: 0, second: 0, nanosecond: 0) else {
    fatalError()
}
let weekdayNum = cal.component(.Weekday, fromDate: date) // 1:日曜日 ~ 7:土曜日

guard let weekday = Weekday(rawValue: weekdayNum - 1) else { fatalError("weekdayIndex is invalid.") }

ここでも上で定義したenumのどの値に合致するかをrawValueから判定しています。曜日の判定はiOS8から追加されたdateWithEraメソッドを使用しています。

春分の日と秋分の日の算出用enum

春分の日・秋分の日に関してもenum内で定義しておきます。計算式自体は前の説明で紹介をしたリンクに基づいたものですが、扱いやすくするためにenum内のメソッドで計算するようにしています。

CalculateCalendarLogic.swift
public struct CalculateCalendarLogic {

    private enum SpringAutumn {
        /// 春分の日
        case Spring

        /// 秋分の日
        case Autumn

        var constant: Double {
            switch self {
            case .Spring: return 20.69115
            case .Autumn: return 23.09000
            }
        }

        /// 春分の日・秋分の日を計算する
        /// 参考:http://koyomi8.com/reki_doc/doc_0330.htm
        func calcDay(year year: Int) -> Int {
            let x1: Double = Double(year - 2000) * 0.242194
            let x2: Int = Int(Double(year - 2000) / 4)
            return Int(constant + x1 - Double(x2))
        }
    }

・・・
}

実際に春分の日・秋分の日の条件判定で使用する際は下記のようになります。

  • 例1)3月20日 or 21日: 春分の日(計算値によって算出)
    case (year, 3, day, _) where PublicHolidaysLawYear < year && day == SpringAutumn.Spring.calcDay(year: year):

  • 例2)9月22日 or 23日: 秋分の日(計算値によって算出)
    case (year, 9, day, _) where PublicHolidaysLawYear <= year && day == SpringAutumn.Autumn.calcDay(year: year):

このような感じでまとめるといい感じにシンプルにできます。

3-3. ゴールデンウィークやシルバーウィークの休日判定

ゴールデンウィークとシルバーウィークは下記のようなルールがあります。この部分が日本の祝祭日の特徴的な部分でもあり、一番実現するのに苦労した部分でもありました。

  • ゴールデンウィーク(5/3〜5/5のいずれかが日曜日になる場合は5/6が振替休日となる)
  • シルバーウィーク(敬老の日と秋分の日の間が1日の場合はその日を国民の休日となる)

この条件判定を実装するために、下記のようなprivateメソッドを定義します。

CalculateCalendarLogic.swift
/**
 *
 * ゴールデンウィークの振替休日を判定する
 * 2007年以降で5/6が月・火・水(5/3 or 5/4 or 5/5が日曜日)なら5/6を祝日とする
 * See also: https://www.bengo4.com/other/1146/1288/n_1412/
 *
 */
private func getGoldenWeekAlterHoliday(year: Int, weekday: Weekday) -> Bool {
    switch weekday {
    case .Mon, .Tue, .Wed where 2007 <= year:
        return true
    default:
        return false
    }
}

/**
 *
 * シルバーウィークの振替休日を判定する
 * 敬老の日の2日後が秋分の日ならば間に挟まれた期間は国民の休日とする
 *
 */
private func getAlterHolidaySliverWeek(year year: Int) -> Bool {
    return oldPeopleDay(year: year) + 2 == SpringAutumn.Autumn.calcDay(year: year)
}

/**
 * 指定した年の敬老の日を調べる
 */
internal func oldPeopleDay(year year: Int) -> Int {
    let cal = NSCalendar.currentCalendar()

    func dateFromDay(day day: Int) -> NSDate? {
        return cal.dateWithEra(AD, year: year, month: 9, day: day, hour: 0, minute: 0, second: 0, nanosecond: 0)
    }

    func weekdayAndDayFromDate(date date: NSDate) -> (weekday: Int, day: Int) {
        return (
            weekday: cal.component(.Weekday, fromDate: date),
            day:     cal.component(.Day, fromDate: date)
        )
    }

    let monday = 2
    return (15...21)
        .map(dateFromDay)
        .flatMap{ $0 }
        .map(weekdayAndDayFromDate)
        .filter{ $0.weekday == monday }
        .first!
        .day
}

この中でinternalというものがoldPeopleDay(year year: Int)についていますが、internalをはじめSwiftのアクセスコントロールに関しては下記の資料が参考になりました。

実際に上記で定義したメソッド用いて、ゴールデンウィークの振替休日とシルバーウィークの国民の休日条件判定で使用する際は下記のようになります。

  • 例1)ゴールデンウィークの振替休日
    case (_, 5, 6, _) where getGoldenWeekAlterHoliday(year, weekday: weekday):

  • 例2)シルバーウィークの振替休日である
    case (_, 9, _, _) where oldPeopleDay(year: year) < day && day < SpringAutumn.Autumn.calcDay(year: year) && getAlterHolidaySliverWeek(year: year):

愚直に計算するとかなり面倒臭い条件判定ではありますが、メソッド単位でできるだけ処理をシンプルに分割する工夫を施すことで、こちらもまたいい感じにシンプルにできます。

3-4. 実装に関するまとめと感想

今回リファクタリングをして頂く中で、Swiftをより深く理解することで、複雑な処理でも工夫して見通しをよくすることができるんだなと改めて感じるとともに、実は「自分はもしかすると言うてもSwiftの便利なところをまだまだ使いこなしていなかったのでは?」とも感じました。

今回の開発で威力を発揮した、異なる型のものをひとまとめにして扱うことができるようにするタプルやSwiftになってより柔軟かつ強力なenumに関してはこれからももっと深堀りして、実際のアプリ開発のなかでもさらに活用していければと思った次第です。

4. XCTestで正しく祝祭日の計算ができているかの検証

日本の祝祭日判定の部分に関しては、カレンダーの目視確認だけでは到底網羅できないと思ったので、今回はXCTestでパッと見の確認が難しい部分を重点的にテストをしました。

今回のライブラリについては、CalculateCalendarLogic.swiftに全て記載しているので、このstruct(構造体)に関して行う形にします。祝祭日判定のメソッドはjudgeJapaneseHolidayメソッドで行い、戻り値はBool型なのでメソッドで判定した結果と期待する結果が正しいかを判定します。

※こちらもリファクタリングを行って頂いたおかげでかなり綺麗な形になって嬉しいです。

4-1. テストケースの書き方と解説

中のテストケースに関しては下記のように記述していきます。

パターン1: 今年2016年の祝祭日のテストケース

CalculateCalendarLogicTests.swift
class CalculateCalendarLogicTests: XCTestCase {

・・・

    /**
     *
     * 今年(2016年)の祝祭日の判定テスト
     *
     */
    func testCurrentYear() {

        let test = CalculateCalendarLogic()

        // 2016年の場合のテストケース
        let testCases: [(Int,Int,Int,Bool)] =
            [

                // ↓↓↓↓↓ テストケースここから ↓↓↓↓↓

                // 元日: 2016年1月1日(金曜日)
                (2016,  1,  1, true),

                // 成人の日: 2016年1月11日(月曜日)
                (2016,  1, 11, true),

                // 建国記念の日: 2016年2月11日(木曜日)
                (2016,  2, 11, true),

                // 春分の日の振替休日: 2016年3月21日(月曜日)
                (2016,  3, 21, true),

                ・・・(途中は省略します)・・・

                // 体育の日: 2016年10月10日(月曜日)
                (2016, 10, 10, true),

                // 文化の日: 2016年11月3日(木曜日)
                (2016, 11,  3, true),

                // 勤労感謝の日: 2016年11月23日(水曜日)
                (2016, 11, 23, true),

                // 天皇誕生日: 2016年12月23日(金曜日)
                (2016, 12, 23, true)

                // ↑↑↑↑↑ テストケースここまで ↑↑↑↑↑
        ]

        // タプルを格納した配列をループさせて、判定結果が正しいかを検証する
        testCases.forEach { year, month, day, expected in
            let result = test.judgeJapaneseHoliday(year, month: month, day: day)
            guard let weekday = Weekday(year: year, month: month, day: day) else { XCTFail() ; return }
            let message = "\(year)\(month)\(day)日(\(weekday.longName)):\(result)"
            if expected {
                XCTAssertTrue (result, message)
            }else{
                XCTAssertFalse(result, message)
            }
        }
    }

・・・

}

このように、検証対象の日と値と期待される結果をタプル ('検証対象の年','検証対象の月','検証対象の日','判定結果') にまずは格納します。そしてそれぞれの祝祭日検証対象の日を同様に作成して、配列の中に格納します。このようにタプルを使ってひとまとめにするととても便利ですね。

あとはこのタプルを格納した配列をループさせて、判定結果が正しいかを検証すればOKです。

4-2. その他のテストケースに関して

このロジックを他のテストケースについても同様に使用していきます。

パターン2: ゴールデンウィークで5月6日が振替休日があるかのテストケース

CalculateCalendarLogicTests.swift
class CalculateCalendarLogicTests: XCTestCase {

・・・

    /**
     *
     * ゴールデンウィークの振替休日がある場合の判定のテスト
     *
     */
    func testGoldenWeek() {

        let test = CalculateCalendarLogic()

        let testCases: [(Int,Int,Int,Bool)] =
            [

                // ↓↓↓↓↓ テストケースここから ↓↓↓↓↓

                // 2017年: 5月6日は振替休日ではない
                (2017, 5, 2, false),
                (2017, 5, 3, true ),
                (2017, 5, 4, true ),
                (2017, 5, 5, true ),
                (2017, 5, 6, false),

                // 2019年: 5月6日は振替休日
                (2019, 5, 2, false),
                (2019, 5, 3, true ),
                (2019, 5, 4, true ),
                (2019, 5, 5, true ),
                (2019, 5, 6, true ),

                // 2020年: 5月6日は振替休日
                (2020, 5, 2, false),
                (2020, 5, 3, true ),
                (2020, 5, 4, true ),
                (2020, 5, 5, true ),
                (2020, 5, 6, true ),
                (2020, 5, 7, false),

                // 2021年: 5月6日は振替休日ではない
                (2021, 5, 2, false),
                (2021, 5, 3, true ),
                (2021, 5, 4, true ),
                (2021, 5, 5, true ),
                (2021, 5, 6, false)

                // ↑↑↑↑↑ テストケースここまで ↑↑↑↑↑
        ]

        // タプルを格納した配列をループさせて、判定結果が正しいかを検証する
        testCases.forEach { year, month, day, expected in
            let result = test.judgeJapaneseHoliday(year, month: month, day: day)
            guard let weekday = Weekday(year: year, month: month, day: day) else { XCTFail() ; return }
            let message = "\(year)\(month)\(day)日(\(weekday.longName)):\(result)"
            if expected {
                XCTAssertTrue (result, message)
            }else{
                XCTAssertFalse(result, message)
            }
        }
    }


・・・

}

パターン3: シルバーウィークで敬老の日と秋分の日の間が国民の休日になるかのテストケース

こちらも上記のゴールデンウィークの振替休日の判定と同様にテストケースを記載していきます。

CalculateCalendarLogicTests.swift
class CalculateCalendarLogicTests: XCTestCase {

・・・

    /**
     *
     * シルバーウィークの国民の祝日判定のテスト
     * 該当テストケース1: 2015年
     * 該当テストケース2: 2026年
     * 該当テストケース3: 2032年
     *
     */
    func testSilverWeek() {

        let test = CalculateCalendarLogic()

        let testCases: [(Int,Int,Int,Bool)] =
            [

                // ↓↓↓↓↓ テストケースここから ↓↓↓↓↓

                // 2015年: 9月22日は国民の休日になる
                (2015, 9, 19, false),
                (2015, 9, 20, false),
                (2015, 9, 21, true),
                (2015, 9, 22, true),
                (2015, 9, 23, true),
                (2015, 9, 24, false),

                // 2016年: 9月20日・9月21日は国民の休日ではない
                (2016, 9, 19, true),
                (2016, 9, 20, false),
                (2016, 9, 21, false),
                (2016, 9, 22, true),
                (2016, 9, 23, false),
                (2016, 9, 24, false),

                // 2026年: 9月22日は国民の休日になる
                (2026, 9, 19, false),
                (2026, 9, 20, false),
                (2026, 9, 21, true),
                (2026, 9, 22, true),
                (2026, 9, 23, true),
                (2026, 9, 24, false),

                // 2032年: 9月21日は国民の休日になる
                (2032, 9, 18, false),
                (2032, 9, 19, false),
                (2032, 9, 20, true),
                (2032, 9, 21, true),
                (2032, 9, 22, true),
                (2032, 9, 23, false)

                // ↑↑↑↑↑ テストケースここまで ↑↑↑↑↑
        ]

        // タプルを格納した配列をループさせて、判定結果が正しいかを検証する
        testCases.forEach { year, month, day, expected in
            let result = test.judgeJapaneseHoliday(year, month: month, day: day)
            guard let weekday = Weekday(year: year, month: month, day: day) else { XCTFail() ; return }
            let message = "\(year)\(month)\(day)日(\(weekday.longName)):\(result)"
            if expected {
                XCTAssertTrue (result, message)
            }else{
                XCTAssertFalse(result, message)
            }
        }
    }

・・・

}

パターン4: 2000年〜2030年の春分の日・秋分の日が正しいかのテストケース

計算式に合致するかのテストケースをWikipediaに記載されている日付を基に設定していきます。

CalculateCalendarLogicTests.swift
class CalculateCalendarLogicTests: XCTestCase {

・・・

    /**
     *
     * 春分の日・秋分の日の組み合わせが正しいかのテスト
     * 計算式算出の参考:http://koyomi8.com/reki_doc/doc_0330.htm
     * テストケース参考:http://www.nao.ac.jp/faq/a0301.html
     *
     */
    func testShunbunAndShubun() {

        let test = CalculateCalendarLogic()

        let testCases: [(Int,Int,Int,Bool)] =
            [

                // ↓↓↓↓↓ テストケースここから ↓↓↓↓↓

                // 2000年: 春分の日[3月20日]・秋分の日[9月23日]
                (2000, 3, 20, true),
                (2000, 9, 23, true),

                // 2001年: 春分の日[3月20日]・秋分の日[9月23日]
                (2001, 3, 20, true),
                (2001, 9, 23, true),

                // 2002年: 春分の日[3月21日]・秋分の日[9月23日]
                (2002, 3, 21, true),
                (2002, 9, 23, true),

                // 2003年: 春分の日[3月21日]・秋分の日[9月23日]
                (2003, 3, 21, true),
                (2003, 9, 23, true),

                ・・・(途中は省略します)・・・

                // 2027年: 春分の日[3月21日]・秋分の日[9月23日]
                (2027, 3, 21, true),
                (2027, 9, 23, true),

                // 2028年: 春分の日[3月20日]・秋分の日[9月22日]
                (2028, 3, 20, true),
                (2028, 9, 22, true),

                // 2029年: 春分の日[3月20日]・秋分の日[9月23日]
                (2029, 3, 20, true),
                (2029, 9, 23, true),

                // 2030年: 春分の日[3月20日]・秋分の日[9月23日]
                (2030, 3, 20, true),
                (2030, 9, 23, true)

                // ↑↑↑↑↑ テストケースここまで ↑↑↑↑↑
        ]

        // タプルを格納した配列をループさせて、判定結果が正しいかを検証する
        testCases.forEach { year, month, day, expected in
            let result = test.judgeJapaneseHoliday(year, month: month, day: day)
            guard let weekday = Weekday(year: year, month: month, day: day) else { XCTFail() ; return }
            let message = "\(year)\(month)\(day)日(\(weekday.longName)):\(result)"
            if expected {
                XCTAssertTrue (result, message)
            }else{
                XCTAssertFalse(result, message)
            }
        }
    }

・・・

}

上記の祝祭日の判定ロジックと同様に判定に使用する引数がどうしても多くなる場合やテストケースが多くある場合についてもSwiftのタプルを活用して記述をよりシンプルにしたり、ロジックの見通しを良くする工夫ができるのはとてもいいですね。

5. カレンダーの実装で参考にしたもの等

ここまでは祝祭日の計算に関してのコード解説とテストに関するものがメインでしたがここでは実際のカレンダーを作る上で参考にしたものを紹介します。

以前自分でもカレンダーサンプルを作成したことはありましたので、一応そのカレンダーサンプルにも導入してみました。今回の祝祭日ライブラリについてはCarthage及びCocoaPodsに対応していますので、導入の際はこのライブラリのREADMEや下記のリンクを参考に導入してみて下さい。

【自分が以前に作成したカレンダーのリポジトリ】

calendar2.jpg

こちらはiPhoneアプリ開発を始めたての頃に作成したカレンダーになります。こちらはCathageでライブラリをインストールする方式ではなく、該当のライブラリファイルを手動でインポートして、呼び出すことも可能です。

【今回参考にさせて頂いたもの】

今回のサンプルでのカレンダー本体につきましては、下記の記事を参考にして作成しました。

上記のカレンダーと日付の算出ロジックは若干異なってはいますが(1日始まりのものロジックを自分は採用しました)、以前自分が作成したすごくカレンダーよりもかなり綺麗に作成できそうだった為記事を基に作成してみました。

6. 今後の実装等に関しての予定

今後に関してはよりご安心してお使い頂けるような取り組みを随時していくと同時に、更にリファクタリング等も定期的に行っていければと思います。またこちらのライブラリは全て計算に基づいて祝祭日と振替休日を算出しているので日本の法律が変わってしまった場合には、ロジックの変更が必要になるのでご注意くださいませ。

参考資料. CocoaPods化の大まかな流れと参考資料について

今回のライブラリをCocoaPods化するにあたっては、下記の資料を参考に作成を行いました。
(今回の場合はすでにCarthage化も行っていたので、あらかじめサンプルとロジックが分離されていた状態ですのでPodspecファイルの作成とCocoaPodsへの登録作業で済みました)

上記の参考資料を基に、手順の流れや必要な作業をまとめると下記のようになります。

1. プロジェクトを作成する際に「Framework & Library」を選択して、その後にDemoフォルダ内にサンプルプロジェクトを作成
(ターミナルで行う際には$ pod lib create [ライブラリ名]でもOK)

2. ライブラリ及びライブラリを使用したサンプルアプリの作成

3. PodSpecファイル([ライブラリ名].podspec)の作成
今回のPodspecファイルは、こんな感じになっていますので記述の際に参考にしていただければ幸いです。

CalculateCalendarLogic.podspec
Pod::Spec.new do |s|
  s.name                  = "CalculateCalendarLogic"
  s.version               = "0.0.2"
  s.summary               = "This library CalculateCalendarLogic (sample project name is handMadeCalendarAdvance) can judge a holiday in Japan."
  s.description           = <<-DESC
                          This library 'CalculateCalendarLogic' can judge a holiday in Japan.
                          When you use this library, you can judge can judge a holiday in Japan very easily.
                          A method which named 'judgeJapaneseHoliday' method stores variables year, month, and day in an argument, it returns true or false.
                          DESC
  s.homepage              = "http://qiita.com/fumiyasac@github/items/33bfc07ad36dfffcdf8f"
  s.license               = { :type => "MIT", :file => "LICENSE" }
  s.author                = { "Fumiya Sakai" => "fumiya.def.mathmatica@gmail.com" }
  s.platform              = :ios, "8.0"
  s.source                = { :git => "https://github.com/fumiyasac/handMadeCalendarAdvance.git", :tag => "#{s.version}" }
  s.social_media_url      = "https://twitter.com/fumisac"
  s.ios.deployment_target = '8.0'
  s.source_files          = "CalculateCalendarLogic/*"
  s.requires_arc          = true
end

特に他のフレームワークやライブラリと依存関係があるライブラリに関しては、その記述も忘れないようにしてください。
またPodspecファイルについては間違いや記述漏れのないように、$ pod lib lint [ライブラリ名].podspecコマンドをこまめに実行して、記述などに抜け漏れがないかをチェックしましょう。
そして、忘れていけないのはライブラリの活用法や導入方法を記載したREADME.mdもしっかりと準備をしておくとよいと思います。

4. タグ付けとCocoaPodsへの登録
CocoaPodsの公開作業を行う前に、ライブラリをバージョン管理用のタグ付け作業を行います。
(この段階では自分のGithub等のリポジトリにライブラリやPodspecファイルがpushし終えていることが前提として話します)

タグはPodspecの記載バージョンと同じになるように してください。

gitコマンドではタグ付け下記のようになります。※バージョン0.0.2というタグを付与するケースの場合です。

//追加時
$ git tag 0.0.2 (タグを作成)
$ git push origin 0.0.2 (タグをリモートへプッシュ)

//削除時
$ git tag -d 0.0.2 (ローカルのタグを削除)
$ git push origin :0.0.2 (リモートのタグを削除)

タグ付けが終わったら、CocoaPodsへの登録作業を下記のコマンドで行います。

$ pod trunk register [Eメール] '[登録名]'

これで特にエラー等が出なければ、CocoaPodsからメールが届くのでそのメール内にあるリンクをクリックして登録完了です。

5. CocoaPodsへの公開作業

タグ付けとcocoapods.orgへの登録が終わったら、CocoaPodsへの登録作業を下記のコマンドで行います。

$ pod trunk push [ライブラリ名].podspec

これでエラーが出なければ、少し待っていると晴れてcocoapods.orgに自分のライブラリが公開されます。
(ライブラリを修正する際は、上記の3.~5.の手順を行う形になります。)

※githubのリリースノートやCHANGELOGをつけておくとより親切です。

CocoaPodsでライブラリを作る際の手順は「難しそう」とはじめは感じるかも知れませんが、上記の開発の流れや必要なファイル等を知っておけば問題なく作業できると思います。

追記とその他

2016.09.03:更新
こちらのライブラリをCocoaPodsに対応致しました!詳細は下記のREADMEを参照して頂けましたら幸いです。

またCocoaPods化につきましては@keygxさんのご協力をいただき誠にありがとうございました。

2016.06.29:更新
こちらの記事は「集まれSwift好き!Swift愛好会 #7」にて、「祝祭日付きのカレンダーを作ったおはなし」という内容で登壇した際の解説テキストとしても使用しました。 SlideShareでの資料はこちら

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
Comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  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