宽窄优行-由【嘉易行】项目成品而来
younger_times
2023-04-06 a1ae6802080a22e6e6ce6d0935e95facb1daca5c
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
//
//  SwiftDate
//  Parse, validate, manipulate, and display dates, time and timezones in Swift
//
//  Created by Daniele Margutti
//   - Web: https://www.danielemargutti.com
//   - Twitter: https://twitter.com/danielemargutti
//   - Mail: hello@danielemargutti.com
//
//  Copyright © 2019 Daniele Margutti. Licensed under MIT License.
//
 
import Foundation
 
public extension DateInRegion {
 
    /// Indicates whether the month is a leap month.
    var isLeapMonth: Bool {
        let calendar = region.calendar
        // Library function for leap contains a bug for Gregorian calendars, implemented workaround
        if calendar.identifier == Calendar.Identifier.gregorian && year > 1582 {
            guard let range: Range<Int> = calendar.range(of: .day, in: .month, for: date) else {
                return false
            }
            return ((range.upperBound - range.lowerBound) == 29)
        }
        // For other calendars:
        return calendar.dateComponents([.day, .month, .year], from: date).isLeapMonth!
    }
 
    /// Indicates whether the year is a leap year.
    var isLeapYear: Bool {
        let calendar = region.calendar
        // Library function for leap contains a bug for Gregorian calendars, implemented workaround
        if calendar.identifier == Calendar.Identifier.gregorian {
            var newComponents = dateComponents
            newComponents.month = 2
            newComponents.day = 10
            let testDate = DateInRegion(components: newComponents, region: region)
            return testDate!.isLeapMonth
        } else if calendar.identifier == Calendar.Identifier.chinese {
            /// There are 12 or 13 months in each year and 29 or 30 days in each month.
            /// A 13-month year is a leap year, which meaning more than 376 days is a leap year.
            return ( dateAtStartOf(.year).toUnit(.day, to: dateAtEndOf(.year)) > 375 )
        }
        // For other calendars:
        return calendar.dateComponents([.day, .month, .year], from: date).isLeapMonth!
    }
 
    /// Julian day is the continuous count of days since the beginning of
    /// the Julian Period used primarily by astronomers.
    var julianDay: Double {
        let destRegion = Region(calendar: Calendars.gregorian, zone: Zones.gmt, locale: Locales.english)
        let utc = convertTo(region: destRegion)
 
        let year = Double(utc.year)
        let month = Double(utc.month)
        let day = Double(utc.day)
        let hour = Double(utc.hour) + Double(utc.minute) / 60.0 + (Double(utc.second) + Double(utc.nanosecond) / 1e9) / 3600.0
 
        var jd = 367.0 * year - floor( 7.0 * ( year + floor((month + 9.0) / 12.0)) / 4.0 )
        jd -= floor( 3.0 * (floor( (year + (month - 9.0) / 7.0) / 100.0 ) + 1.0) / 4.0 )
        jd += floor(275.0 * month / 9.0) + day + 1_721_028.5 + hour / 24.0
 
        return jd
    }
 
    /// The Modified Julian Date (MJD) was introduced by the Smithsonian Astrophysical Observatory
    /// in 1957 to record the orbit of Sputnik via an IBM 704 (36-bit machine)
    /// and using only 18 bits until August 7, 2576.
    var modifiedJulianDay: Double {
        return julianDay - 2_400_000.5
    }
 
    /// Return elapsed time expressed in given components since the current receiver and a reference date.
    /// Time is evaluated with the fixed measumerent of each unity.
    ///
    /// - Parameters:
    ///   - refDate: reference date (`nil` to use current date in the same region of the receiver)
    ///   - component: time unit to extract.
    /// - Returns: value
    func getInterval(toDate: DateInRegion?, component: Calendar.Component) -> Int64 {
        let refDate = (toDate ?? region.nowInThisRegion())
        switch component {
        case .year:
            let end = calendar.ordinality(of: .year, in: .era, for: refDate.date)
            let start = calendar.ordinality(of: .year, in: .era, for: date)
            return Int64(end! - start!)
 
        case .month:
            let end = calendar.ordinality(of: .month, in: .era, for: refDate.date)
            let start = calendar.ordinality(of: .month, in: .era, for: date)
            return Int64(end! - start!)
 
        case .day:
            let end = calendar.ordinality(of: .day, in: .era, for: refDate.date)
            let start = calendar.ordinality(of: .day, in: .era, for: date)
            return Int64(end! - start!)
 
        case .hour:
            let interval = refDate.date.timeIntervalSince(date)
            return Int64(interval / 1.hours.timeInterval)
 
        case .minute:
            let interval = refDate.date.timeIntervalSince(date)
            return Int64(interval / 1.minutes.timeInterval)
 
        case .second:
            return Int64(refDate.date.timeIntervalSince(date))
 
        case .weekday:
            let end = calendar.ordinality(of: .weekday, in: .era, for: refDate.date)
            let start = calendar.ordinality(of: .weekday, in: .era, for: date)
            return Int64(end! - start!)
 
        case .weekdayOrdinal:
            let end = calendar.ordinality(of: .weekdayOrdinal, in: .era, for: refDate.date)
            let start = calendar.ordinality(of: .weekdayOrdinal, in: .era, for: date)
            return Int64(end! - start!)
 
        case .weekOfYear:
            let end = calendar.ordinality(of: .weekOfYear, in: .era, for: refDate.date)
            let start = calendar.ordinality(of: .weekOfYear, in: .era, for: date)
            return Int64(end! - start!)
 
        default:
            debugPrint("Passed component cannot be used to extract values using interval() function between two dates. Returning 0.")
            return 0
        }
    }
 
    /// The interval between the receiver and the another parameter.
    /// If the receiver is earlier than anotherDate, the return value is negative.
    /// If anotherDate is nil, the results are undefined.
    ///
    /// - Parameter date: The date with which to compare the receiver.
    /// - Returns: time interval between two dates
    func timeIntervalSince(_ date: DateInRegion) -> TimeInterval {
        return self.date.timeIntervalSince(date.date)
    }
 
    /// Extract DateComponents from the difference between two dates.
    ///
    /// - Parameter rhs: date to compare
    /// - Returns: components
    func componentsTo(_ rhs: DateInRegion) -> DateComponents {
        return calendar.dateComponents(DateComponents.allComponentsSet, from: rhs.date, to: date)
    }
 
    /// Returns the difference between two dates (`date - self`) expressed as date components.
    ///
    /// - Parameters:
    ///   - date: reference date as initial date (left operand)
    ///   - components: components to extract, `nil` to use default `DateComponents.allComponentsSet`
    /// - Returns: extracted date components
    func componentsSince(_ date: DateInRegion, components: [Calendar.Component]? = nil) -> DateComponents {
        if date.calendar != calendar {
            debugPrint("Date has different calendar, results maybe wrong")
        }
        let cmps = (components != nil ? Calendar.Component.toSet(components!) : DateComponents.allComponentsSet)
        return date.calendar.dateComponents(cmps, from: date.date, to: self.date)
    }
 
}