宽窄优行-由【嘉易行】项目成品而来
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
165
166
167
168
//
//  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 TimeInterval {
 
    struct ComponentsFormatterOptions {
 
        /// Fractional units may be used when a value cannot be exactly represented using the available units.
        /// For example, if minutes are not allowed, the value “1h 30m” could be formatted as “1.5h”.
        public var allowsFractionalUnits: Bool?
 
        /// Specify the units that can be used in the output.
        public var allowedUnits: NSCalendar.Unit?
 
        /// A Boolean value indicating whether to collapse the largest unit into smaller units when a certain threshold is met.
        public var collapsesLargestUnit: Bool?
 
        /// The maximum number of time units to include in the output string.
        /// If 0 does not cause the elimination of any units.
        public var maximumUnitCount: Int?
 
        /// The formatting style for units whose value is 0.
        public var zeroFormattingBehavior: DateComponentsFormatter.ZeroFormattingBehavior?
 
        /// The preferred style for units.
        public var unitsStyle: DateComponentsFormatter.UnitsStyle?
 
        /// Locale of the formatter
        public var locale: LocaleConvertible? {
            set { calendar.locale = newValue?.toLocale() }
            get { return calendar.locale }
        }
 
        /// Calendar
        public var calendar = Calendar.autoupdatingCurrent
 
        public func apply(toFormatter formatter: DateComponentsFormatter) {
            formatter.calendar = calendar
 
            if let allowsFractionalUnits = self.allowsFractionalUnits {
                formatter.allowsFractionalUnits = allowsFractionalUnits
            }
            if let allowedUnits = self.allowedUnits {
                formatter.allowedUnits = allowedUnits
            }
            if let collapsesLargestUnit = self.collapsesLargestUnit {
                formatter.collapsesLargestUnit = collapsesLargestUnit
            }
            if let maximumUnitCount = self.maximumUnitCount {
                formatter.maximumUnitCount = maximumUnitCount
            }
            if let zeroFormattingBehavior = self.zeroFormattingBehavior {
                formatter.zeroFormattingBehavior = zeroFormattingBehavior
            }
            if let unitsStyle = self.unitsStyle {
                formatter.unitsStyle = unitsStyle
            }
        }
 
        public init() {}
    }
 
    /// Return the local thread shared formatter for date components
    private static func sharedFormatter() -> DateComponentsFormatter {
        let name = "SwiftDate_\(NSStringFromClass(DateComponentsFormatter.self))"
        return threadSharedObject(key: name, create: {
            let formatter = DateComponentsFormatter()
            formatter.includesApproximationPhrase = false
            formatter.includesTimeRemainingPhrase = false
            return formatter
        })
    }
 
    //@available(*, deprecated: 5.0.13, obsoleted: 5.1, message: "Use toIntervalString function instead")
    func toString(options callback: ((inout ComponentsFormatterOptions) -> Void)? = nil) -> String {
        return self.toIntervalString(options: callback)
    }
 
    /// Format a time interval in a string with desidered components with passed style.
    ///
    /// - Parameters:
    ///   - units: units to include in string.
    ///   - style: style of the units, by default is `.abbreviated`
    /// - Returns: string representation
    func toIntervalString(options callback: ((inout ComponentsFormatterOptions) -> Void)? = nil) -> String {
        let formatter = DateComponentsFormatter()
        var options = ComponentsFormatterOptions()
        callback?(&options)
        options.apply(toFormatter: formatter)
 
        let formattedValue = formatter.string(from: self)!
        if options.zeroFormattingBehavior?.contains(.pad) ?? false {
            // for some strange reason padding is not added at the very beginning positional item.
            // we'll add it manually if necessaru
            if let index = formattedValue.firstIndex(of: ":"), index.utf16Offset(in: formattedValue) < 2 {
                return "0\(formattedValue)"
            }
        }
        return formattedValue
    }
 
    /// Format a time interval in a string with desidered components with passed style.
    ///
    /// - Parameter options: options for formatting.
    /// - Returns: string representation
    func toString(options: ComponentsFormatterOptions) -> String {
        let formatter = TimeInterval.sharedFormatter()
        options.apply(toFormatter: formatter)
        return (formatter.string(from: self) ?? "")
    }
 
    /// Return a string representation of the time interval in form of clock countdown (ie. 57:00:00)
    ///
    /// - Parameter zero: behaviour with zero.
    /// - Returns: string representation
    func toClock(zero: DateComponentsFormatter.ZeroFormattingBehavior =  [.pad, .dropLeading]) -> String {
        return toIntervalString(options: {
            $0.collapsesLargestUnit = true
            $0.maximumUnitCount = 0
            $0.unitsStyle = .positional
            $0.locale = Locales.englishUnitedStatesComputer
            $0.zeroFormattingBehavior = zero
        })
    }
 
    /// Extract requeste time units components from given interval.
    /// Reference date's calendar is used to make the extraction.
    ///
    /// NOTE:
    ///        Extraction is calendar/date based; if you specify a `refDate` calculation is made
    ///     between the `refDate` and `refDate + interval`.
    ///     If `refDate` is `nil` evaluation is made from `now()` and `now() + interval` in the context
    ///     of the `SwiftDate.defaultRegion` set.
    ///
    /// - Parameters:
    ///   - units: units to extract
    ///   - from: starting reference date, `nil` means `now()` in the context of the default region set.
    /// - Returns: dictionary with extracted components
    func toUnits(_ units: Set<Calendar.Component>, to refDate: DateInRegion? = nil) -> [Calendar.Component: Int] {
        let dateTo = (refDate ?? DateInRegion())
        let dateFrom = dateTo.addingTimeInterval(-self)
        let components = dateFrom.calendar.dateComponents(units, from: dateFrom.date, to: dateTo.date)
        return components.toDict()
    }
 
    /// Express a time interval (expressed in seconds) in another time unit you choose.
    /// Reference date's calendar is used to make the extraction.
    ///
    /// - parameter component: time unit in which you want to express the calendar component
    /// - parameter from: starting reference date, `nil` means `now()` in the context of the default region set.
    ///
    /// - returns: the value of interval expressed in selected `Calendar.Component`
    func toUnit(_ component: Calendar.Component, to refDate: DateInRegion? = nil) -> Int? {
        return toUnits([component], to: refDate)[component]
    }
 
}