Using NSDate to get date for Easter

点点圈 提交于 2019-12-24 13:18:06

问题


I'm working on an application that requires the use of getting dates for national holidays.

Below, I was able to get Memorial Day:

// Set the components for Memorial Day (last Monday of May)

let memorialDayComps = NSDateComponents()
memorialDayComps.weekday = 2
memorialDayComps.month = 5
memorialDayComps.year = currentYear

var mondaysOfMay = [NSDate]()

for var i = 1; i <= 5; i++ {
    memorialDayComps.weekdayOrdinal = i
    let monday = calendar.dateFromComponents(memorialDayComps)
    let components = calendar.components(.CalendarUnitMonth, fromDate: monday!)
    if components.month == 5 {
        mondaysOfMay.append(monday!)
    }
}
let memorialDayDate = mondaysOfMay.last

Because the dates are pretty well set, I am able to successfully create NSDate instances for the following holidays:

  • New Year's Day
  • Martin Luther King, Jr. Day
  • Presidents' Day
  • Memorial Day
  • Independence Day
  • Labor Day
  • Thanksgiving Day
  • Christmas Day

However, the only one that I am having difficulty figuring out how to get is Easter. It varies every year, so I'm curious as to whether anyone else has been able so successfully get the date for Easter via an API or other means.


回答1:


I was able to find a gist on GitHub that has a solution that was accurate for calculating and returning an NSDate for Easter.

The code below is what the gist contains:

// Easter calculation in swift after Anonymous Gregorian algorithm
// Also known as Meeus/Jones/Butcher algorithm

func easter(Y : Int) -> NSDate {
  let a = Y % 19
  let b = Int(floor(Double(Y) / 100))
  let c = Y % 100
  let d = Int(floor(Double(b) / 4))
  let e = b % 4
  let f = Int(floor(Double(b+8) / 25))
  let g = Int(floor(Double(b-f+1) / 3))
  let h = (19*a + b - d - g + 15) % 30
  let i = Int(floor(Double(c) / 4))
  let k = c % 4
  let L = (32 + 2*e + 2*i - h - k) % 7
  let m = Int(floor(Double(a + 11*h + 22*L) / 451))
  let components = NSDateComponents()
  components.year = Y
  components.month = Int(floor(Double(h + L - 7*m + 114) / 31))
  components.day = ((h + L - 7*m + 114) % 31) + 1
  components.timeZone = NSTimeZone(forSecondsFromGMT: 0)
  let cal = NSCalendar(calendarIdentifier: NSGregorianCalendar)
  return cal.dateFromComponents(components)
}

println(easter(2014))  // "2014-04-20 00:00:00 +0000"



回答2:


That Easter algorithm works great! Using with Swift 4.0 and pattern matching. Pattern matching made it easier for me to add other days based on month, day, weekday, weekdayOrdinal.

extension Date {

    var isUSHoliday: Bool {

        let components = Calendar.current.dateComponents([.year, .month, .day, .weekday, .weekdayOrdinal], from: self)
        guard let year = components.year,
            let month = components.month,
            let day = components.day,
            let weekday = components.weekday,
            let weekdayOrdinal = components.weekdayOrdinal else { return false }

        let easterDateComponents = Date.dateComponentsForEaster(year: year)
        let easterMonth: Int = easterDateComponents?.month ?? -1
        let easterDay: Int = easterDateComponents?.day ?? -1
        let memorialDay = Date.dateComponentsForMemorialDay(year: year)?.day ?? -1

        // weekday is Sunday==1 ... Saturday==7
        // weekdayOrdinal is nth instance of weekday in month

        switch (month, day, weekday, weekdayOrdinal) {
        case (1, 1, _, _): return true                      // Happy New Years
        case (1, 0, 2, 3): return true                      // MLK - 3rd Mon in Jan
        case (2, 0, 2, 3): return true                      // Washington - 3rd Mon in Feb
        case (easterMonth, easterDay, _, _): return true    // Easter - rocket science calculation
        case (5, memorialDay, _, _): return true            // Memorial Day
        case (7, 4, _, _): return true                      // Independence Day
        case (9, 0, 2, 1): return true                      // Labor Day - 1st Mon in Sept
        case (10, 0, 2, 2): return true                     // Columbus Day - 2nd Mon in Oct
        case (11, 11, _, _): return true                    // Veterans Day
        case (11, 0, 5, 4): return true                     // Happy Thanksgiving - 4th Thurs in Nov
        case (12, 25, _, _): return true                    // Happy Holidays
        case (12, 31, _, _): return true                    // New years Eve
        default: return false
        }

    }

    static func dateComponentsForMemorialDay(year: Int) -> DateComponents? {
        guard let memorialDay = Date.memorialDay(year: year) else { return nil }
        return NSCalendar.current.dateComponents([.year, .month, .day, .weekday, .weekdayOrdinal], from: memorialDay)
    }

    static func memorialDay(year: Int) -> Date? {
        let calendar = Calendar.current
        var firstMondayJune = DateComponents()
        firstMondayJune.month = 6
        firstMondayJune.weekdayOrdinal = 1  // 1st in month
        firstMondayJune.weekday = 2 // Monday
        firstMondayJune.year = year
        guard let refDate = calendar.date(from: firstMondayJune) else { return nil }
        var timeMachine = DateComponents()
        timeMachine.weekOfMonth = -1
        return calendar.date(byAdding: timeMachine, to: refDate)
    }

    static func easterHoliday(year: Int) -> Date? {
        guard let dateComponents = Date.dateComponentsForEaster(year: year) else { return nil }
        return Calendar.current.date(from: dateComponents)
    }

    static func dateComponentsForEaster(year: Int) -> DateComponents? {
        // Easter calculation from Anonymous Gregorian algorithm
        // AKA Meeus/Jones/Butcher algorithm
        let a = year % 19
        let b = Int(floor(Double(year) / 100))
        let c = year % 100
        let d = Int(floor(Double(b) / 4))
        let e = b % 4
        let f = Int(floor(Double(b+8) / 25))
        let g = Int(floor(Double(b-f+1) / 3))
        let h = (19*a + b - d - g + 15) % 30
        let i = Int(floor(Double(c) / 4))
        let k = c % 4
        let L = (32 + 2*e + 2*i - h - k) % 7
        let m = Int(floor(Double(a + 11*h + 22*L) / 451))
        var dateComponents = DateComponents()
        dateComponents.month = Int(floor(Double(h + L - 7*m + 114) / 31))
        dateComponents.day = ((h + L - 7*m + 114) % 31) + 1
        dateComponents.year = year
        guard let easter = Calendar.current.date(from: dateComponents) else { return nil } // Convert to calculate weekday, weekdayOrdinal
        return Calendar.current.dateComponents([.year, .month, .day, .weekday, .weekdayOrdinal], from: easter)
    }

}



回答3:


Swift 4:

func easter(Y : Int) -> Date {
    let a = Y % 19
    let b = Int(floor(Double(Y) / 100))
    let c = Y % 100
    let d = Int(floor(Double(b) / 4))
    let e = b % 4
    let f = Int(floor(Double(b+8) / 25))
    let g = Int(floor(Double(b-f+1) / 3))
    let h = (19*a + b - d - g + 15) % 30
    let i = Int(floor(Double(c) / 4))
    let k = c % 4
    let L = (32 + 2*e + 2*i - h - k) % 7
    let m = Int(floor(Double(a + 11*h + 22*L) / 451))
    var components = DateComponents()
    components.year = Y
    components.month = Int(floor(Double(h + L - 7*m + 114) / 31))
    components.day = ((h + L - 7*m + 114) % 31) + 1
    components.timeZone = TimeZone(secondsFromGMT: 0)
    return Calendar.autoupdatingCurrent.date(from: components)!
}

print(easter(Y: 2018)) // "2018-04-01 00:00:00 +0000"



回答4:


OBJECTIVE-C!

-(void) easterMonthAndDayForYear: (NSInteger) Y {
        NSInteger a = Y % 19;
        NSInteger b = (int) (floor( ((double)Y) / 100.0));
        NSInteger c = Y % 100;
        NSInteger d = (int)(floor(((double)b) / 4.0));
        NSInteger e = b % 4;
        NSInteger f = (int)(floor(((double)(b+8)) / 25.0));
        NSInteger g = (int)(floor(((double)(b-f+1)) / 3.0));
        NSInteger h = (19*a + b - d - g + 15) % 30;
        NSInteger i = (int)(floor(((double)c) / 4.0));
        NSInteger k = c % 4;
        NSInteger L = (32 + 2*e + 2*i - h - k) % 7;
        NSInteger m = (int)(floor(((double)(a + 11*h + 22*L)) / 451.0));
        NSDateComponents *components = [[NSCalendar currentCalendar] components:NSCalendarUnitDay | NSCalendarUnitMonth | NSCalendarUnitYear |NSCalendarUnitTimeZone  fromDate:[NSDate date]];
        components.year = Y;
        components.month = (int)(floor((double)(h + L - 7*m + 114) / 31.0));
        components.day = ((h + L - 7*m + 114) % 31) + 1;
        components.timeZone = [NSTimeZone timeZoneForSecondsFromGMT:0];

        self.easterDayCache[@(Y)] = @{@"month": @(components.month), @"day":@(components.day)
};


来源:https://stackoverflow.com/questions/30836183/using-nsdate-to-get-date-for-easter

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!