In part 1, I did show you how to build a simple Calendar App with SwiftUI. This part 2 will continue to show how to load data from SQLite so we can see the lunar date of each solar date.
Add SQLite package to Xcode project
I am using this package for working with SQLite on Swift. You just need to add it to Xcode like this.
After adding SQLite, we add this file for connecting SQLite lunar_calendar.db
.
LunarDatabase.swift
import Foundation
import SQLite
struct LunarDate: Hashable {
let solarDate: String
let year: Int
let month: Int
let day: Int
let isLeapMonth: Bool
}
class LunarDatabase {
private let db: Connection
init?() {
guard let dbPath = Bundle.main.path(forResource: "lunar_calendar", ofType: "db") else {
print("❌ Database file not found in bundle")
return nil
}
do {
db = try Connection(dbPath, readonly: true)
} catch {
print("❌ Failed to open DB: \(error)")
return nil
}
}
func query(for date: Date) -> LunarDate? {
let formatter = DateFormatter()
formatter.dateFormat = "yyyy-MM-dd"
let dateStr = formatter.string(from: date)
do {
// Raw SQL with parameter binding
let sql = "SELECT solar_date, lunar_year, lunar_month, lunar_day, is_leap_month FROM lunar_dates WHERE solar_date = ? LIMIT 1"
let stmt = try db.prepare(sql)
// Run the statement with dateStr as parameter
for row in try stmt.run(dateStr) {
return LunarDate(
solarDate: row[0] as! String,
year: Int(row[1] as! Int64), // SQLite returns Int64
month: Int(row[2] as! Int64),
day: Int(row[3] as! Int64),
isLeapMonth: (row[4] as! Int64) == 1
)
}
} catch {
print("Query error: \(error)")
}
return nil
}
We have a table called lunar_dates
with schema like this:
We now can extend the Date by adding toLunar
method to load lunar date from SQLite.
Date+Extension.swift
let lunarDB = LunarDatabase()
extension Date {
func toLunar() -> LunarDate? {
return lunarDB?.query(for: self)
}
}
Now, let's extend Calendar by adding methods to collect week days, month days like below:
Calendar+Extension.swift
struct CalendarDay: Identifiable, Hashable {
var id: Date { solar } // unique per day
let solar: Date
let lunar: LunarDate
let gregorianDay: Int
let lunarDay: String
let isToday: Bool
let isHoliday: Bool
let isSpecialDay: Bool
}
extension Calendar {
func lunarDay(for solar: Date = Date()) -> CalendarDay {
let lunar = solar.toLunar()!
let lunarDay = lunar.day
var lunarDayStr = "\(lunarDay)"
if lunarDay == 1 {
let lunarMonth = lunar.month
lunarDayStr = "\(lunarDay)/\(lunarMonth)"
}
return CalendarDay(
solar: solar,
lunar: lunar,
gregorianDay: component(.day, from: solar),
lunarDay: lunarDayStr,
isToday: isDateInToday(solar),
isHoliday: holiday(solar, lunar) != nil,
isSpecialDay: specialDay(solar, lunar) != nil
)
}
func weekDays(for date: Date) -> [CalendarDay] {
let startOfWeek = self.startOfWeek(for: date)
return (0..<7).map { offset in
let d = self.date(byAdding: .day, value: offset, to: startOfWeek)!
return self.lunarDay(for: d)
}
}
func monthDays(for date: Date) -> [CalendarDay] {
guard let monthInterval = self.dateInterval(of: .month, for: date),
let monthFirstWeek = self.dateInterval(of: .weekOfMonth, for: monthInterval.start),
let monthLastWeek = self.dateInterval(of: .weekOfMonth, for: monthInterval.end.addingTimeInterval(-1))
else { return [] }
// Correct end: subtract 1 day because `end` is exclusive
let lastVisibleDay = self.date(byAdding: .day, value: -1, to: monthLastWeek.end)!
let fullRange = monthFirstWeek.start...lastVisibleDay
var days: [CalendarDay] = []
var current = fullRange.lowerBound
while current <= fullRange.upperBound {
days.append(self.lunarDay(for: current))
current = self.date(byAdding: .day, value: 1, to: current)!
}
return days
}
func weekNumber(for date: Date) -> Int {
return self.component(.weekOfYear, from: date)
}
func startOfWeek(for date: Date) -> Date {
let comps = dateComponents([.yearForWeekOfYear, .weekOfYear], from: date)
return self.date(from: comps)!
}
}
These added methods will be used for producing input for MonthView and WeekView of the app.
You can check full source code here.
Next time, I will show you how to generate the lunar_calendar.db
using lunardate Python package.
Top comments (0)