From 945f78c39a56b5ff7df11b62a030f33887fd1214 Mon Sep 17 00:00:00 2001 From: Stephen Booth Date: Mon, 30 Oct 2023 22:14:06 -0500 Subject: [PATCH] Add support for Islamic calendar (#3) * Add Islamic calendar support * Add isIslamicCalendarLeapYear() * Basic Islamic leap year tests * Preliminary Islamic calendar to JDN tests * Add round-trip testing for Islamic calendar * Improve documentation * Reword comment * Reword documentation comments * Add Islamic Calendar JD support * Add Islamic calendar to README --- README.md | 2 +- .../JulianDayNumber/GregorianChangeover.swift | 2 +- Sources/JulianDayNumber/JD.swift | 93 ++++++++++++----- .../JDN+GregorianCalendar.swift | 7 +- .../JulianDayNumber/JDN+IslamicCalendar.swift | 99 +++++++++++++++++++ .../JulianDayNumber/JDN+JulianCalendar.swift | 6 +- Sources/JulianDayNumber/JDN.swift | 11 +-- Sources/JulianDayNumber/LeapYear.swift | 14 +++ .../JDNRoundTripTests.swift | 23 +++++ Tests/JulianDayNumberTests/JDNTests.swift | 7 ++ .../JulianDayNumberTests/LeapYearTests.swift | 33 +++++++ 11 files changed, 261 insertions(+), 36 deletions(-) create mode 100644 Sources/JulianDayNumber/JDN+IslamicCalendar.swift diff --git a/README.md b/README.md index 690a74e..ddf4fe7 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # JulianDayNumber -Julian day number (JDN) and Julian date (JD) calculations supporting proleptic Julian and Gregorian calendars. +Julian day number (JDN) and Julian date (JD) calculations supporting Julian, Gregorian, and Islamic calendars. Conversion to and from dates in the range `-9999-01-01` to `99999-12-31` is supported. diff --git a/Sources/JulianDayNumber/GregorianChangeover.swift b/Sources/JulianDayNumber/GregorianChangeover.swift index dc28928..d435e4f 100644 --- a/Sources/JulianDayNumber/GregorianChangeover.swift +++ b/Sources/JulianDayNumber/GregorianChangeover.swift @@ -32,7 +32,7 @@ public let gregorianCalendarChangeoverJDN = 2299161 /// The changeover occurred on 1582-10-15. Julian Thursday, 1582-10-04 /// was followed by Gregorian 1582-10-15. /// -/// JD values less than this value are typically interpreted in the Julian calendar while +/// Julian date values less than this value are typically interpreted in the Julian calendar while /// greater or equal JD values are interpreted in the Gregorian calendar. /// /// This JD corresponds to 1582-10-15 00:00 in the Gregorian calendar. diff --git a/Sources/JulianDayNumber/JD.swift b/Sources/JulianDayNumber/JD.swift index f624515..7621adc 100644 --- a/Sources/JulianDayNumber/JD.swift +++ b/Sources/JulianDayNumber/JD.swift @@ -6,11 +6,10 @@ import Foundation -/// Converts a calendar date to a Julian date. +/// Converts a date in the Julian or Gregorian calendar to a Julian date. /// -/// The Julian date (JD) of an instant is the Julian Day Number (JDN) plus the fraction of a day since the preceding noon in Universal Time. -/// Julian dates are expressed as a JDN with a decimal fraction added. For example, the Julian date for 2013-01-01 00:30:00.0 UT -/// is 2456293.520833. +/// The Julian date (JD) is the Julian Day Number (JDN) plus the fraction of a day since the preceding noon in Universal Time. +/// Julian dates are expressed as a JDN with a decimal fraction added. /// /// Dates before 1582-10-15 are interpreted in the Julian calendar while later dates are interpreted in the Gregorian calendar. /// @@ -28,11 +27,10 @@ public func calendarDateToJulianDate(year Y: Int, month M: Int, day D: Int, hour Double(calendarDateToJulianDayNumber(year: Y, month: M, day: D)) - 0.5 + timeToFractionalDay(hour: h, minute: m, second: s) } -/// Converts a calendar date to a Julian date. +/// Converts a date in the Julian or Gregorian calendar to a Julian date. /// -/// The Julian date (JD) of an instant is the Julian Day Number (JDN) plus the fraction of a day since the preceding noon in Universal Time. -/// Julian dates are expressed as a JDN with a decimal fraction added. For example, the Julian date for 2013-01-01 00:30:00.0 UT -/// is 2456293.520833. +/// The Julian date (JD) is the Julian Day Number (JDN) plus the fraction of a day since the preceding noon in Universal Time. +/// Julian dates are expressed as a JDN with a decimal fraction added. /// /// Dates before 1582-10-15 are interpreted in the Julian calendar while later dates are interpreted in the Gregorian calendar. /// @@ -50,9 +48,8 @@ public func calendarDateToJulianDate(year Y: Int, month M: Int, day D: Double) - /// Converts a date in the Julian calendar to a Julian date. /// -/// The Julian date (JD) of an instant is the Julian Day Number (JDN) plus the fraction of a day since the preceding noon in Universal Time. -/// Julian dates are expressed as a JDN with a decimal fraction added. For example, the Julian date for 2013-01-01 00:30:00.0 UT -/// is 2456293.520833. +/// The Julian date (JD) is the Julian Day Number (JDN) plus the fraction of a day since the preceding noon in Universal Time. +/// Julian dates are expressed as a JDN with a decimal fraction added. /// /// - note: No validation checks are performed on the date values. /// @@ -70,9 +67,8 @@ public func julianCalendarDateToJulianDate(year Y: Int, month M: Int, day D: Int /// Converts a date in the Julian calendar to a Julian date. /// -/// The Julian date (JD) of an instant is the Julian Day Number (JDN) plus the fraction of a day since the preceding noon in Universal Time. -/// Julian dates are expressed as a JDN with a decimal fraction added. For example, the Julian date for 2013-01-01 00:30:00.0 UT -/// is 2456293.520833. +/// The Julian date (JD) is the Julian Day Number (JDN) plus the fraction of a day since the preceding noon in Universal Time. +/// Julian dates are expressed as a JDN with a decimal fraction added. /// /// - note: No validation checks are performed on the date values. /// @@ -88,9 +84,8 @@ public func julianCalendarDateToJulianDate(year Y: Int, month M: Int, day D: Dou /// Converts a date in the Gregorian calendar to a Julian date. /// -/// The Julian date (JD) of an instant is the Julian Day Number (JDN) plus the fraction of a day since the preceding noon in Universal Time. -/// Julian dates are expressed as a JDN with a decimal fraction added. For example, the Julian date for 2013-01-01 00:30:00.0 UT -/// is 2456293.520833. +/// The Julian date (JD) is the Julian Day Number (JDN) plus the fraction of a day since the preceding noon in Universal Time. +/// Julian dates are expressed as a JDN with a decimal fraction added. /// /// - note: No validation checks are performed on the date values. /// @@ -108,9 +103,8 @@ public func gregorianCalendarDateToJulianDate(year Y: Int, month M: Int, day D: /// Converts a date in the Gregorian calendar to a Julian date. /// -/// The Julian date (JD) of an instant is the Julian Day Number (JDN) plus the fraction of a day since the preceding noon in Universal Time. -/// Julian dates are expressed as a JDN with a decimal fraction added. For example, the Julian date for 2013-01-01 00:30:00.0 UT -/// is 2456293.520833. +/// The Julian date (JD) is the Julian Day Number (JDN) plus the fraction of a day since the preceding noon in Universal Time. +/// Julian dates are expressed as a JDN with a decimal fraction added. /// /// - note: No validation checks are performed on the date values. /// @@ -124,6 +118,42 @@ public func gregorianCalendarDateToJulianDate(year Y: Int, month M: Int, day D: return Double(gregorianCalendarDateToJulianDayNumber(year: Y, month: M, day: Int(day))) - 0.5 + dayFraction } +/// Converts a date in the Islamic calendar to a Julian date. +/// +/// The Julian date (JD) is the Julian Day Number (JDN) plus the fraction of a day since the preceding noon in Universal Time. +/// Julian dates are expressed as a JDN with a decimal fraction added. +/// +/// - note: No validation checks are performed on the date values. +/// +/// - parameter Y: A year number between `-9999` and `99999`. +/// - parameter M: A month number between `1` (Muharram) and `12` (Dhu ́’l-Hijjab). +/// - parameter D: A day number between `1` and the maximum number of days in month `M` for year `Y`. +/// - parameter h: An hour number between `0` and `23`. +/// - parameter m: A minute number between `0` and `59`. +/// - parameter s: A second number between `0` and `59`. +/// +/// - returns: The JD corresponding to the requested date. +public func islamicCalendarDateToJulianDate(year Y: Int, month M: Int, day D: Int, hour h: Int = 0, minute m: Int = 0, second s: Double = 0) -> Double { + Double(islamicCalendarDateToJulianDayNumber(year: Y, month: M, day: D)) - 0.5 + timeToFractionalDay(hour: h, minute: m, second: s) +} + +/// Converts a date in the Islamic calendar to a Julian date. +/// +/// The Julian date (JD) is the Julian Day Number (JDN) plus the fraction of a day since the preceding noon in Universal Time. +/// Julian dates are expressed as a JDN with a decimal fraction added. +/// +/// - note: No validation checks are performed on the date values. +/// +/// - parameter Y: A year number between `-9999` and `99999`. +/// - parameter M: A month number between `1` (Muharram) and `12` (Dhu ́’l-Hijjab). +/// - parameter D: A decimal day between `1` and the maximum number of days in month `M` for year `Y`. +/// +/// - returns: The JD corresponding to the requested date. +public func islamicCalendarDateToJulianDate(year Y: Int, month M: Int, day D: Double) -> Double { + let (day, dayFraction) = modf(D) + return Double(islamicCalendarDateToJulianDayNumber(year: Y, month: M, day: Int(day))) - 0.5 + dayFraction +} + /// The earliest supported JD. /// /// This JD corresponds to -9999-01-01 00:00:00 in the Julian calendar. @@ -134,9 +164,9 @@ let earliestSupportedJD = -1931076.5 /// This JD corresponds to 99999-12-31 00:00:00 in the Gregorian calendar. let latestSupportedJD = 38245308.5 -/// Converts the Julian date `JD` to a calendar date. +/// Converts the Julian date `JD` to a date in the Julian or Gregorian calendar. /// -/// JD values less than `2299160.5` are interpreted in the Julian calendar while greater or equal JD values are interpreted in the Gregorian calendar. +/// Julian date values less than `2299160.5` are interpreted in the Julian calendar while greater or equal Julian date values are interpreted in the Gregorian calendar. /// /// - parameter JD: A Julian date between `-1931076.5` and `38245308.5`. /// @@ -183,6 +213,25 @@ public func julianDateToGregorianCalendarDate(_ JD: Double) -> (year: Int, month convertJDToCalendarDate(JD, usingJDNConversionFunction: julianDayNumberToGregorianCalendarDate) } +/// The earliest supported JD using the Islamic calendar. +/// +/// This JD corresponds to -9999-01-01 00:00:00 in the Islamic calendar. +let earliestSupportedIslamicCalendarJD = -1595227.5 + +/// The latest supported JD using the Islamic calendar. +/// +/// This JD corresponds to 99999-12-29 00:00:00 in the Islamic calendar. +let latestSupportedIslamicCalendarJD = 37384750.5 + +/// Converts the Julian date `JD` to a date in the Islamic calendar. +/// +/// - parameter JD: A Julian date between `-1595227.5` and `37384750.5`. +/// +/// - returns: A tuple specifying the requested date. +public func julianDateToIslamicCalendarDate(_ JD: Double) -> (year: Int, month: Int, day: Int, hour: Int, minute: Int, second: Double) { + convertJDToCalendarDate(JD, usingJDNConversionFunction: julianDayNumberToIslamicCalendarDate) +} + /// Returns the decimal fractional day represented by the time comprised of hour `h`, minute `m`, and second `s`. /// /// - parameter h: An hour number between `0` and `23`. diff --git a/Sources/JulianDayNumber/JDN+GregorianCalendar.swift b/Sources/JulianDayNumber/JDN+GregorianCalendar.swift index 694293c..2a0a161 100644 --- a/Sources/JulianDayNumber/JDN+GregorianCalendar.swift +++ b/Sources/JulianDayNumber/JDN+GregorianCalendar.swift @@ -9,7 +9,8 @@ import Foundation /// Converts a date in the Gregorian calendar to a Julian day number. /// /// The Julian day number (JDN) is the integer assigned to a whole solar day in the Julian day count starting from noon Universal Time, -/// with JDN 0 assigned to the day starting at noon on November 24, 4714 BC (-4713-11-24 12:00:00) in the proleptic Gregorian calendar. +/// with JDN 0 assigned to the day starting at noon on Monday, January 1, 4713 BC (-4712-01-01 12:00:00) in the proleptic Julian calendar. +/// This date is November 24, 4714 BC (-4713-11-24 12:00:00) in the proleptic Gregorian calendar. /// /// - note: No validation checks are performed on the date values. /// @@ -43,12 +44,12 @@ public func gregorianCalendarDateToJulianDayNumber(year Y: Int, month M: Int, da return J } -/// The earliest supported JDN using the Gregorian calendar. +/// The earliest supported Julian day number using the Gregorian calendar. /// /// This JDN corresponds to -9999-01-01 12:00:00 in the Gregorian calendar. let earliestSupportedGregorianCalendarJDN = -1930999 -/// The latest supported JDN using the Gregorian calendar. +/// The latest supported Julian day number using the Gregorian calendar. /// /// This JDN corresponds to 99999-12-31 12:00:00 in the Gregorian calendar. let latestSupportedGregorianCalendarJDN = latestSupportedJDN diff --git a/Sources/JulianDayNumber/JDN+IslamicCalendar.swift b/Sources/JulianDayNumber/JDN+IslamicCalendar.swift new file mode 100644 index 0000000..1a14c4d --- /dev/null +++ b/Sources/JulianDayNumber/JDN+IslamicCalendar.swift @@ -0,0 +1,99 @@ +// +// +// Copyright © 2021-2023 Stephen F. Booth +// Part of https://github.com/sbooth/JulianDayNumber +// MIT license +// + +import Foundation + +/// Converts a date in the Islamic calendar to a Julian day number. +/// +/// The Julian day number (JDN) is the integer assigned to a whole solar day in the Julian day count starting from noon Universal Time, +/// with JDN 0 assigned to the day starting at noon on Monday, January 1, 4713 BC (-4712-01-01 12:00:00) in the proleptic Julian calendar. +/// This date is Shaabán 17, 5499 B.H. (-5498-08-16 12:00:00) in the proleptic Islamic calendar. +/// +/// - note: No validation checks are performed on the date values. +/// +/// - parameter Y: A year number between `-9999` and `99999`. +/// - parameter M: A month number between `1` (Muharram) and `12` (Dhu ́’l-Hijjab). +/// - parameter D: A day number between `1` and the maximum number of days in month `M` for year `Y`. +/// +/// - returns: The JDN corresponding to the requested date. +public func islamicCalendarDateToJulianDayNumber(year Y: Int, month M: Int, day D: Int) -> Int { + // Richards' algorithm is only valid for positive JDNs. + // JDN 0 is -5498-08-16 in the proleptic Islamic calendar. + // Adjust the year of earlier dates forward in time by a multiple of + // 30 (to account for leap years in the Islamic calendar) + // before calculating the JDN and then translate the result backward + // in time by the period of adjustment. + if Y < -5498 || (Y == -5498 && (M < 8 || (M == 8 && D < 16))) { + // 30 years = 10,631 days (19 years of 354 days and 11 leap years of 355 days) + let periods = (-5498 - Y) / 30 + 1 + let mappedY = Y + periods * 30 + let mappedJ = islamicCalendarDateToJulianDayNumber(year: mappedY, month: M, day: D) + return mappedJ - periods * 10631 + } + + let h = M - m + let g = Y + y - (n - h) / n + let f = (h - 1 + n) % n + let e = (p * g + q) / r + D - 1 - j + let J = e + (s * f + t) / u + + return J +} + +/// The earliest supported Julian day number using the Islamic calendar. +/// +/// This JDN corresponds to -9999-01-01 12:00:00 in the Islamic calendar +let earliestSupportedIslamicCalendarJDN = -1595227 + +/// The latest supported Julian day number using the Islamic calendar. +/// +/// This JDN corresponds to 99999-12-29 12:00:00 in the Islamic calendar +let latestSupportedIslamicCalendarJDN = 37384751 + +/// Converts the Julian day number `J` to a date in the Islamic calendar. +/// +/// - parameter J: A Julian day number between `-1595227` and `37384751`. +/// +/// - returns: The calendar date corresponding to `J`. +public func julianDayNumberToIslamicCalendarDate(_ J: Int) -> (year: Int, month: Int, day: Int) { + // Richards' algorithm is only valid for positive JDNs. + // Adjust negative JDNs forward in time by a multiple of + // 30 years (to account for leap years in the Islamic calendar) + // before calculating the proleptic Islamic date and then translate + // the result backward in time by the amount of forward adjustment. + if J < 0 { + // 30 years = 10,631 days (19 years of 354 days and 11 leap years of 355 days) + let periods = -J / 10631 + 1 + let mappedJ = J + periods * 10631 + let mappedYMD = julianDayNumberToIslamicCalendarDate(mappedJ) + return (mappedYMD.year - periods * 30, mappedYMD.month, mappedYMD.day) + } + + let f = J + j + let e = r * f + v + let g = (e % p) / r + let h = u * g + w + let D = (h % s) / u + 1 + let M = ((h / s + m) % n) + 1 + let Y = e / p - y + (n + m - M) / n + + return (Y, M, D) +} + +// Constants for Islamic calendar conversions +private let y = 5519 +private let j = 7664 +private let m = 0 +private let n = 12 +private let r = 30 +private let p = 10631 +private let q = 14 +private let v = 15 +private let u = 100 +private let s = 2951 +private let t = 51 +private let w = 10 diff --git a/Sources/JulianDayNumber/JDN+JulianCalendar.swift b/Sources/JulianDayNumber/JDN+JulianCalendar.swift index 40686b7..cc4f46f 100644 --- a/Sources/JulianDayNumber/JDN+JulianCalendar.swift +++ b/Sources/JulianDayNumber/JDN+JulianCalendar.swift @@ -9,7 +9,7 @@ import Foundation /// Converts a date in the Julian calendar to a Julian day number. /// /// The Julian day number (JDN) is the integer assigned to a whole solar day in the Julian day count starting from noon Universal Time, -/// with JDN 0 assigned to the day starting at noon on Monday, January 1, 4713 BC (-4712-01-01 12:00:00) in the proleptic Julian calendar +/// with JDN 0 assigned to the day starting at noon on Monday, January 1, 4713 BC (-4712-01-01 12:00:00) in the proleptic Julian calendar. /// /// - note: No validation checks are performed on the date values. /// @@ -42,12 +42,12 @@ public func julianCalendarDateToJulianDayNumber(year Y: Int, month M: Int, day D return J } -/// The earliest supported JDN using the Julian calendar. +/// The earliest supported Julian day number using the Julian calendar. /// /// This JDN corresponds to -9999-01-01 12:00:00 in the Julian calendar let earliestSupportedJulianCalendarJDN = earliestSupportedJDN -/// The latest supported JDN using the Julian calendar. +/// The latest supported Julian day number using the Julian calendar. /// /// This JDN corresponds to 99999-12-31 12:00:00 in the Julian calendar let latestSupportedJulianCalendarJDN = 38246057 diff --git a/Sources/JulianDayNumber/JDN.swift b/Sources/JulianDayNumber/JDN.swift index 6e7f9b5..c78bba3 100644 --- a/Sources/JulianDayNumber/JDN.swift +++ b/Sources/JulianDayNumber/JDN.swift @@ -8,11 +8,10 @@ import Foundation // From the Explanatory Supplement to the Astronomical Almanac, 3rd edition, S.E Urban and P.K. Seidelmann eds., (Mill Valley, CA: University Science Books), Chapter 15, pp. 585-624. -/// Converts a calendar date to a Julian day number. +/// Converts a date in the Julian or Gregorian calendar to a Julian day number. /// /// The Julian day number (JDN) is the integer assigned to a whole solar day in the Julian day count starting from noon Universal Time, -/// with JDN 0 assigned to the day starting at noon on Monday, January 1, 4713 BC (-4712-01-01 12:00:00) in the proleptic Julian calendar -/// (November 24, 4714 BC [-4713-11-24 12:00:00] in the proleptic Gregorian calendar). +/// with JDN 0 assigned to the day starting at noon on Monday, January 1, 4713 BC (-4712-01-01 12:00:00) in the proleptic Julian calendar. /// /// Dates before 1582-10-15 are interpreted in the Julian calendar while later dates are interpreted in the Gregorian calendar. /// @@ -31,17 +30,17 @@ public func calendarDateToJulianDayNumber(year Y: Int, month M: Int, day D: Int) } } -/// The earliest supported JDN. +/// The earliest supported Julian day number. /// /// This JDN corresponds to -9999-01-01 12:00:00 in the Julian calendar let earliestSupportedJDN = -1931076 -/// The latest supported JDN. +/// The latest supported Julian day number. /// /// This JDN corresponds to 99999-12-31 12:00:00 in the Gregorian calendar. let latestSupportedJDN = 38245309 -/// Converts the Julian day number `J` to a calendar date. +/// Converts the Julian day number `J` to a date in the Julian or Gregorian calendar. /// /// JDN values less than `2299161` are interpreted in the Julian calendar while greater or equal JDN values are interpreted in the Gregorian calendar. /// diff --git a/Sources/JulianDayNumber/LeapYear.swift b/Sources/JulianDayNumber/LeapYear.swift index 17758be..89c258f 100644 --- a/Sources/JulianDayNumber/LeapYear.swift +++ b/Sources/JulianDayNumber/LeapYear.swift @@ -40,3 +40,17 @@ public func isGregorianCalendarLeapYear(_ Y: Int) -> Bool { public func isJulianCalendarLeapYear(_ Y: Int) -> Bool { Y % 4 == 0 } + +/// Returns `true` if `Y` is a leap year according to the Islamic calendar. +/// +/// There are eleven leap years in a cycle of thirty years. +/// These are years 2, 5, 7, 10, 13, 16, 18, 21, 24, 26, and 29 of the cycle. +/// The year 1 A.H. was the first of a cycle. +/// +/// - parameter Y: A year number. +/// +/// - returns: `true` if `Y` is a leap year in the Islamic calendar. +func isIslamicCalendarLeapYear(_ year: Int) -> Bool { + let yearInCycle = (year - 1) % 30 + (year < 1 ? 31 : 1) + return yearInCycle == 2 || yearInCycle == 5 || yearInCycle == 7 || yearInCycle == 10 || yearInCycle == 13 || yearInCycle == 16 || yearInCycle == 18 || yearInCycle == 21 || yearInCycle == 24 || yearInCycle == 26 || yearInCycle == 29 +} diff --git a/Tests/JulianDayNumberTests/JDNRoundTripTests.swift b/Tests/JulianDayNumberTests/JDNRoundTripTests.swift index 7b1ca91..01331d1 100644 --- a/Tests/JulianDayNumberTests/JDNRoundTripTests.swift +++ b/Tests/JulianDayNumberTests/JDNRoundTripTests.swift @@ -79,4 +79,27 @@ final class JDNRoundTripTests: XCTestCase { } } } + + // Round-trip test of all supported JDNs using the Islamic calendar + func testJDNRoundTripIslamic() { + let monthLengths = [ 30, 29, 30, 29, 30, 29, 30, 29, 30, 29, 30, 29 ] + for year in stride(from: -9999, through: 99999, by: 1) { + let isLeapYear = isIslamicCalendarLeapYear(year) + for month in stride(from: 1, through: 12, by: 1) { + let days: Int + if month == 12 { + days = isLeapYear ? 30 : 29 + } else { + days = monthLengths[month - 1] + } + for day in stride(from: 1, through: days, by: 1) { + let jdn = islamicCalendarDateToJulianDayNumber(year: year, month: month, day: day) + let (Y, M, D) = julianDayNumberToIslamicCalendarDate(jdn) + XCTAssertEqual(year, Y) + XCTAssertEqual(month, M) + XCTAssertEqual(day, D) + } + } + } + } } diff --git a/Tests/JulianDayNumberTests/JDNTests.swift b/Tests/JulianDayNumberTests/JDNTests.swift index 7b3a2f1..bef0e01 100644 --- a/Tests/JulianDayNumberTests/JDNTests.swift +++ b/Tests/JulianDayNumberTests/JDNTests.swift @@ -121,4 +121,11 @@ final class JDNTests: XCTestCase { jdn = gregorianCalendarDateToJulianDayNumber(year: y, month: m, day: d) XCTAssertEqual(largestJDNForGregorianCalendar, jdn) } + + func testJDNIslamic() { + // From Richards + XCTAssertEqual(islamicCalendarDateToJulianDayNumber(year: 1, month: 1, day: 1), 1948440) + // From Meeus + XCTAssertEqual(islamicCalendarDateToJulianDayNumber(year: 1421, month: 1, day: 1), 2451641) + } } diff --git a/Tests/JulianDayNumberTests/LeapYearTests.swift b/Tests/JulianDayNumberTests/LeapYearTests.swift index 753105b..4dd5d98 100644 --- a/Tests/JulianDayNumberTests/LeapYearTests.swift +++ b/Tests/JulianDayNumberTests/LeapYearTests.swift @@ -28,4 +28,37 @@ final class LeapYearTests: XCTestCase { XCTAssertFalse(isGregorianCalendarLeapYear(1900)) XCTAssertFalse(isGregorianCalendarLeapYear(2100)) } + + func testIslamicLeapYear() { + XCTAssertFalse(isIslamicCalendarLeapYear(1)) + XCTAssertTrue(isIslamicCalendarLeapYear(2)) + XCTAssertFalse(isIslamicCalendarLeapYear(3)) + XCTAssertFalse(isIslamicCalendarLeapYear(4)) + XCTAssertTrue(isIslamicCalendarLeapYear(5)) + XCTAssertFalse(isIslamicCalendarLeapYear(6)) + XCTAssertTrue(isIslamicCalendarLeapYear(7)) + XCTAssertFalse(isIslamicCalendarLeapYear(8)) + XCTAssertFalse(isIslamicCalendarLeapYear(9)) + XCTAssertTrue(isIslamicCalendarLeapYear(10)) + XCTAssertFalse(isIslamicCalendarLeapYear(11)) + XCTAssertFalse(isIslamicCalendarLeapYear(12)) + XCTAssertTrue(isIslamicCalendarLeapYear(13)) + XCTAssertFalse(isIslamicCalendarLeapYear(14)) + XCTAssertFalse(isIslamicCalendarLeapYear(15)) + XCTAssertTrue(isIslamicCalendarLeapYear(16)) + XCTAssertFalse(isIslamicCalendarLeapYear(17)) + XCTAssertTrue(isIslamicCalendarLeapYear(18)) + XCTAssertFalse(isIslamicCalendarLeapYear(19)) + XCTAssertFalse(isIslamicCalendarLeapYear(20)) + XCTAssertTrue(isIslamicCalendarLeapYear(21)) + XCTAssertFalse(isIslamicCalendarLeapYear(22)) + XCTAssertFalse(isIslamicCalendarLeapYear(23)) + XCTAssertTrue(isIslamicCalendarLeapYear(24)) + XCTAssertFalse(isIslamicCalendarLeapYear(25)) + XCTAssertTrue(isIslamicCalendarLeapYear(26)) + XCTAssertFalse(isIslamicCalendarLeapYear(27)) + XCTAssertFalse(isIslamicCalendarLeapYear(28)) + XCTAssertTrue(isIslamicCalendarLeapYear(29)) + XCTAssertFalse(isIslamicCalendarLeapYear(30)) + } }