add all api to sort by month
This commit is contained in:
@@ -0,0 +1,326 @@
|
||||
//
|
||||
// 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
|
||||
|
||||
// MARK: - Comparing DateInRegion
|
||||
|
||||
public func == (lhs: DateInRegion, rhs: DateInRegion) -> Bool {
|
||||
return (lhs.date.timeIntervalSince1970 == rhs.date.timeIntervalSince1970)
|
||||
}
|
||||
|
||||
public func <= (lhs: DateInRegion, rhs: DateInRegion) -> Bool {
|
||||
let result = lhs.date.compare(rhs.date)
|
||||
return (result == .orderedAscending || result == .orderedSame)
|
||||
}
|
||||
|
||||
public func >= (lhs: DateInRegion, rhs: DateInRegion) -> Bool {
|
||||
let result = lhs.date.compare(rhs.date)
|
||||
return (result == .orderedDescending || result == .orderedSame)
|
||||
}
|
||||
|
||||
public func < (lhs: DateInRegion, rhs: DateInRegion) -> Bool {
|
||||
return lhs.date.compare(rhs.date) == .orderedAscending
|
||||
}
|
||||
|
||||
public func > (lhs: DateInRegion, rhs: DateInRegion) -> Bool {
|
||||
return lhs.date.compare(rhs.date) == .orderedDescending
|
||||
}
|
||||
|
||||
// The type of comparison to do against today's date or with the suplied date.
|
||||
///
|
||||
/// - isToday: hecks if date today.
|
||||
/// - isTomorrow: Checks if date is tomorrow.
|
||||
/// - isYesterday: Checks if date is yesterday.
|
||||
/// - isSameDay: Compares date days
|
||||
/// - isThisWeek: Checks if date is in this week.
|
||||
/// - isNextWeek: Checks if date is in next week.
|
||||
/// - isLastWeek: Checks if date is in last week.
|
||||
/// - isSameWeek: Compares date weeks
|
||||
/// - isThisMonth: Checks if date is in this month.
|
||||
/// - isNextMonth: Checks if date is in next month.
|
||||
/// - isLastMonth: Checks if date is in last month.
|
||||
/// - isSameMonth: Compares date months
|
||||
/// - isThisYear: Checks if date is in this year.
|
||||
/// - isNextYear: Checks if date is in next year.
|
||||
/// - isLastYear: Checks if date is in last year.
|
||||
/// - isSameYear: Compare date years
|
||||
/// - isInTheFuture: Checks if it's a future date
|
||||
/// - isInThePast: Checks if the date has passed
|
||||
/// - isEarlier: Checks if earlier than date
|
||||
/// - isLater: Checks if later than date
|
||||
/// - isWeekday: Checks if it's a weekday
|
||||
/// - isWeekend: Checks if it's a weekend
|
||||
/// - isInDST: Indicates whether the represented date uses daylight saving time.
|
||||
/// - isMorning: Return true if date is in the morning (>=5 - <12)
|
||||
/// - isAfternoon: Return true if date is in the afternoon (>=12 - <17)
|
||||
/// - isEvening: Return true if date is in the morning (>=17 - <21)
|
||||
/// - isNight: Return true if date is in the morning (>=21 - <5)
|
||||
public enum DateComparisonType {
|
||||
|
||||
// Days
|
||||
case isToday
|
||||
case isTomorrow
|
||||
case isYesterday
|
||||
case isSameDay(_ : DateRepresentable)
|
||||
|
||||
// Weeks
|
||||
case isThisWeek
|
||||
case isNextWeek
|
||||
case isLastWeek
|
||||
case isSameWeek(_: DateRepresentable)
|
||||
|
||||
// Months
|
||||
case isThisMonth
|
||||
case isNextMonth
|
||||
case isLastMonth
|
||||
case isSameMonth(_: DateRepresentable)
|
||||
|
||||
// Years
|
||||
case isThisYear
|
||||
case isNextYear
|
||||
case isLastYear
|
||||
case isSameYear(_: DateRepresentable)
|
||||
|
||||
// Relative Time
|
||||
case isInTheFuture
|
||||
case isInThePast
|
||||
case isEarlier(than: DateRepresentable)
|
||||
case isLater(than: DateRepresentable)
|
||||
case isWeekday
|
||||
case isWeekend
|
||||
|
||||
// Day time
|
||||
case isMorning
|
||||
case isAfternoon
|
||||
case isEvening
|
||||
case isNight
|
||||
|
||||
// TZ
|
||||
case isInDST
|
||||
}
|
||||
|
||||
public extension DateInRegion {
|
||||
|
||||
/// Decides whether a DATE is "close by" another one passed in parameter,
|
||||
/// where "Being close" is measured using a precision argument
|
||||
/// which is initialized a 300 seconds, or 5 minutes.
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - refDate: reference date compare against to.
|
||||
/// - precision: The precision of the comparison (default is 5 minutes, or 300 seconds).
|
||||
/// - Returns: A boolean; true if close by, false otherwise.
|
||||
func compareCloseTo(_ refDate: DateInRegion, precision: TimeInterval = 300) -> Bool {
|
||||
return (abs(date.timeIntervalSince(refDate.date)) <= precision)
|
||||
}
|
||||
|
||||
/// Compare the date with the rule specified in the `compareType` parameter.
|
||||
///
|
||||
/// - Parameter compareType: comparison type.
|
||||
/// - Returns: `true` if comparison succeded, `false` otherwise
|
||||
func compare(_ compareType: DateComparisonType) -> Bool {
|
||||
switch compareType {
|
||||
case .isToday:
|
||||
return compare(.isSameDay(region.nowInThisRegion()))
|
||||
|
||||
case .isTomorrow:
|
||||
let tomorrow = DateInRegion(region: region).dateByAdding(1, .day)
|
||||
return compare(.isSameDay(tomorrow))
|
||||
|
||||
case .isYesterday:
|
||||
let yesterday = DateInRegion(region: region).dateByAdding(-1, .day)
|
||||
return compare(.isSameDay(yesterday))
|
||||
|
||||
case .isSameDay(let refDate):
|
||||
return calendar.isDate(date, inSameDayAs: refDate.date)
|
||||
|
||||
case .isThisWeek:
|
||||
return compare(.isSameWeek(region.nowInThisRegion()))
|
||||
|
||||
case .isNextWeek:
|
||||
let nextWeek = region.nowInThisRegion().dateByAdding(1, .weekOfYear)
|
||||
return compare(.isSameWeek(nextWeek))
|
||||
|
||||
case .isLastWeek:
|
||||
let lastWeek = region.nowInThisRegion().dateByAdding(-1, .weekOfYear)
|
||||
return compare(.isSameWeek(lastWeek))
|
||||
|
||||
case .isSameWeek(let refDate):
|
||||
guard weekOfYear == refDate.weekOfYear else {
|
||||
return false
|
||||
}
|
||||
// Ensure time interval is under 1 week
|
||||
return (abs(date.timeIntervalSince(refDate.date)) < 1.weeks.timeInterval)
|
||||
|
||||
case .isThisMonth:
|
||||
return compare(.isSameMonth(region.nowInThisRegion()))
|
||||
|
||||
case .isNextMonth:
|
||||
let nextMonth = region.nowInThisRegion().dateByAdding(1, .month)
|
||||
return compare(.isSameMonth(nextMonth))
|
||||
|
||||
case .isLastMonth:
|
||||
let lastMonth = region.nowInThisRegion().dateByAdding(-1, .month)
|
||||
return compare(.isSameMonth(lastMonth))
|
||||
|
||||
case .isSameMonth(let refDate):
|
||||
return (date.year == refDate.date.year) && (date.month == refDate.date.month)
|
||||
|
||||
case .isThisYear:
|
||||
return compare(.isSameYear(region.nowInThisRegion()))
|
||||
|
||||
case .isNextYear:
|
||||
let nextYear = region.nowInThisRegion().dateByAdding(1, .year)
|
||||
return compare(.isSameYear(nextYear))
|
||||
|
||||
case .isLastYear:
|
||||
let lastYear = region.nowInThisRegion().dateByAdding(-1, .year)
|
||||
return compare(.isSameYear(lastYear))
|
||||
|
||||
case .isSameYear(let refDate):
|
||||
return (date.year == refDate.date.year)
|
||||
|
||||
case .isInTheFuture:
|
||||
return compare(.isLater(than: region.nowInThisRegion()))
|
||||
|
||||
case .isInThePast:
|
||||
return compare(.isEarlier(than: region.nowInThisRegion()))
|
||||
|
||||
case .isEarlier(let refDate):
|
||||
return ((date as NSDate).earlierDate(refDate.date) == date)
|
||||
|
||||
case .isLater(let refDate):
|
||||
return ((date as NSDate).laterDate(refDate.date) == date)
|
||||
|
||||
case .isWeekday:
|
||||
return !compare(.isWeekend)
|
||||
|
||||
case .isWeekend:
|
||||
let range = calendar.maximumRange(of: Calendar.Component.weekday)!
|
||||
return (weekday == range.lowerBound || weekday == range.upperBound - range.lowerBound)
|
||||
|
||||
case .isInDST:
|
||||
return region.timeZone.isDaylightSavingTime(for: date)
|
||||
|
||||
case .isMorning:
|
||||
return (hour >= 5 && hour < 12)
|
||||
|
||||
case .isAfternoon:
|
||||
return (hour >= 12 && hour < 17)
|
||||
|
||||
case .isEvening:
|
||||
return (hour >= 17 && hour < 21)
|
||||
|
||||
case .isNight:
|
||||
return (hour >= 21 || hour < 5)
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns a ComparisonResult value that indicates the ordering of two given dates based on
|
||||
/// their components down to a given unit granularity.
|
||||
///
|
||||
/// - parameter date: date to compare.
|
||||
/// - parameter granularity: The smallest unit that must, along with all larger units
|
||||
/// - returns: `ComparisonResult`
|
||||
func compare(toDate refDate: DateInRegion, granularity: Calendar.Component) -> ComparisonResult {
|
||||
switch granularity {
|
||||
case .nanosecond:
|
||||
// There is a possible rounding error using Calendar to compare two dates below the minute granularity
|
||||
// So we've added this trick and use standard Date compare which return correct results in this case
|
||||
// https://github.com/malcommac/SwiftDate/issues/346
|
||||
return date.compare(refDate.date)
|
||||
default:
|
||||
return region.calendar.compare(date, to: refDate.date, toGranularity: granularity)
|
||||
}
|
||||
}
|
||||
|
||||
/// Compares whether the receiver is before/before equal `date` based on their components down to a given unit granularity.
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - refDate: reference date
|
||||
/// - orEqual: `true` to also check for equality
|
||||
/// - granularity: smallest unit that must, along with all larger units, be less for the given dates
|
||||
/// - Returns: Boolean
|
||||
func isBeforeDate(_ date: DateInRegion, orEqual: Bool = false, granularity: Calendar.Component) -> Bool {
|
||||
let result = compare(toDate: date, granularity: granularity)
|
||||
return (orEqual ? (result == .orderedSame || result == .orderedAscending) : result == .orderedAscending)
|
||||
}
|
||||
|
||||
/// Compares whether the receiver is after `date` based on their components down to a given unit granularity.
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - refDate: reference date
|
||||
/// - orEqual: `true` to also check for equality
|
||||
/// - granularity: Smallest unit that must, along with all larger units, be greater for the given dates.
|
||||
/// - Returns: Boolean
|
||||
func isAfterDate(_ refDate: DateInRegion, orEqual: Bool = false, granularity: Calendar.Component) -> Bool {
|
||||
let result = compare(toDate: refDate, granularity: granularity)
|
||||
return (orEqual ? (result == .orderedSame || result == .orderedDescending) : result == .orderedDescending)
|
||||
}
|
||||
|
||||
/// Compares equality of two given dates based on their components down to a given unit
|
||||
/// granularity.
|
||||
///
|
||||
/// - parameter date: date to compare
|
||||
/// - parameter granularity: The smallest unit that must, along with all larger units, be equal for the given
|
||||
/// dates to be considered the same.
|
||||
///
|
||||
/// - returns: `true` if the dates are the same down to the given granularity, otherwise `false`
|
||||
func isInside(date: DateInRegion, granularity: Calendar.Component) -> Bool {
|
||||
return (compare(toDate: date, granularity: granularity) == .orderedSame)
|
||||
}
|
||||
|
||||
/// Return `true` if receiver data is contained in the range specified by two dates.
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - startDate: range upper bound date
|
||||
/// - endDate: range lower bound date
|
||||
/// - orEqual: `true` to also check for equality on date and date2, default is `true`
|
||||
/// - granularity: smallest unit that must, along with all larger units, be greater
|
||||
/// - Returns: Boolean
|
||||
func isInRange(date startDate: DateInRegion, and endDate: DateInRegion, orEqual: Bool = true, granularity: Calendar.Component = .nanosecond) -> Bool {
|
||||
return isAfterDate(startDate, orEqual: orEqual, granularity: granularity) && isBeforeDate(endDate, orEqual: orEqual, granularity: granularity)
|
||||
}
|
||||
|
||||
// MARK: - Date Earlier/Later
|
||||
|
||||
/// Return the earlier of two dates, between self and a given date.
|
||||
///
|
||||
/// - Parameter date: The date to compare to self
|
||||
/// - Returns: The date that is earlier
|
||||
func earlierDate(_ date: DateInRegion) -> DateInRegion {
|
||||
return self.date.timeIntervalSince(date.date) <= 0 ? self : date
|
||||
}
|
||||
|
||||
/// Return the later of two dates, between self and a given date.
|
||||
///
|
||||
/// - Parameter date: The date to compare to self
|
||||
/// - Returns: The date that is later
|
||||
func laterDate(_ date: DateInRegion) -> DateInRegion {
|
||||
return self.date.timeIntervalSince(date.date) >= 0 ? self : date
|
||||
}
|
||||
|
||||
/// Returns the difference in the calendar component given (like day, month or year)
|
||||
/// with respect to the other date as a positive integer
|
||||
func difference(in component: Calendar.Component, from other: DateInRegion) -> Int? {
|
||||
return self.date.difference(in: component, from: other.date)
|
||||
}
|
||||
|
||||
/// Returns the differences in the calendar components given (like day, month and year)
|
||||
/// with respect to the other date as dictionary with the calendar component as the key
|
||||
/// and the diffrence as a positive integer as the value
|
||||
func differences(in components: Set<Calendar.Component>, from other: DateInRegion) -> [Calendar.Component: Int] {
|
||||
return self.date.differences(in: components, from: other.date)
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,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)
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,650 @@
|
||||
//
|
||||
// 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 {
|
||||
|
||||
// MARK: - Random Date Generator
|
||||
|
||||
/// Generate a sequence of dates between a range.
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - count: number of dates to generate.
|
||||
/// - initial: lower date bound.
|
||||
/// - final: upper date bound.
|
||||
/// - region: region of the dates.
|
||||
/// - Returns: array of dates
|
||||
static func randomDates(count: Int, between initial: DateInRegion, and final: DateInRegion,
|
||||
region: Region = SwiftDate.defaultRegion) -> [DateInRegion] {
|
||||
var list: [DateInRegion] = []
|
||||
for _ in 0..<count {
|
||||
list.append(DateInRegion.randomDate(between: initial, and: final, region: region))
|
||||
}
|
||||
return list
|
||||
}
|
||||
|
||||
/// Return a date between now and a specified amount days ealier.
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - days: days range
|
||||
/// - region: destination region, `nil` to use the default region
|
||||
/// - Returns: random date
|
||||
static func randomDate(withinDaysBeforeToday days: Int,
|
||||
region: Region = SwiftDate.defaultRegion) -> DateInRegion {
|
||||
let today = DateInRegion(region: region)
|
||||
let earliest = DateInRegion(today.date.addingTimeInterval(TimeInterval(-days * 24 * 60 * 60)), region: region)
|
||||
return DateInRegion.randomDate(between: earliest, and: today)
|
||||
}
|
||||
|
||||
/// Generate a random date in given region.
|
||||
///
|
||||
/// - Parameter region: destination region, `nil` to use the default region
|
||||
/// - Returns: random date
|
||||
static func randomDate(region: Region = SwiftDate.defaultRegion) -> DateInRegion {
|
||||
let randomTime = TimeInterval(UInt32.random(in: UInt32.min..<UInt32.max))
|
||||
let absoluteDate = Date(timeIntervalSince1970: randomTime)
|
||||
return DateInRegion(absoluteDate, region: region)
|
||||
}
|
||||
|
||||
/// Generate a random date between two intervals.
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - initial: lower bound date
|
||||
/// - final: upper bound date
|
||||
/// - region: destination region, `nil` to use the default region
|
||||
/// - Returns: random Date
|
||||
static func randomDate(between initial: DateInRegion, and final: DateInRegion,
|
||||
region: Region = SwiftDate.defaultRegion) -> DateInRegion {
|
||||
let interval = final.timeIntervalSince(initial)
|
||||
let randomInterval = TimeInterval(UInt32.random(in: UInt32.min..<UInt32(interval)))
|
||||
return initial.addingTimeInterval(randomInterval)
|
||||
}
|
||||
|
||||
/// Return the oldest date in given list (timezone is ignored, comparison uses absolute date).
|
||||
///
|
||||
/// - Parameter list: list of dates
|
||||
/// - Returns: a tuple with the index of the oldest date and its instance.
|
||||
static func oldestIn(list: [DateInRegion]) -> DateInRegion? {
|
||||
guard list.count > 0 else { return nil }
|
||||
guard list.count > 1 else { return list.first! }
|
||||
return list.min(by: {
|
||||
return $0 < $1
|
||||
})
|
||||
}
|
||||
|
||||
/// Sort date by oldest, with the oldest date on top.
|
||||
///
|
||||
/// - Parameter list: list to sort
|
||||
/// - Returns: sorted array
|
||||
static func sortedByOldest(list: [DateInRegion]) -> [DateInRegion] {
|
||||
return list.sorted(by: { $0.date.compare($1.date) == .orderedAscending })
|
||||
}
|
||||
|
||||
/// Sort date by newest, with the newest date on top.
|
||||
///
|
||||
/// - Parameter list: list to sort
|
||||
/// - Returns: sorted array
|
||||
static func sortedByNewest(list: [DateInRegion]) -> [DateInRegion] {
|
||||
return list.sorted(by: { $0.date.compare($1.date) == .orderedDescending })
|
||||
}
|
||||
|
||||
/// Return the newest date in given list (timezone is ignored, comparison uses absolute date).
|
||||
///
|
||||
/// - Parameter list: list of dates
|
||||
/// - Returns: a tuple with the index of the newest date and its instance.
|
||||
static func newestIn(list: [DateInRegion]) -> DateInRegion? {
|
||||
guard list.count > 0 else { return nil }
|
||||
guard list.count > 1 else { return list.first! }
|
||||
return list.max(by: {
|
||||
return $0 < $1
|
||||
})
|
||||
}
|
||||
|
||||
/// Enumerate dates between two intervals by adding specified time components and return an array of dates.
|
||||
/// `startDate` interval will be the first item of the resulting array.
|
||||
/// The last item of the array is evaluated automatically and maybe not equal to `endDate`.
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - start: starting date
|
||||
/// - endDate: ending date
|
||||
/// - increment: components to add
|
||||
/// - Returns: array of dates
|
||||
static func enumerateDates(from startDate: DateInRegion, to endDate: DateInRegion, increment: DateComponents) -> [DateInRegion] {
|
||||
return DateInRegion.enumerateDates(from: startDate, to: endDate, increment: { _ in
|
||||
return increment
|
||||
})
|
||||
}
|
||||
|
||||
/// Enumerate dates between two intervals by adding specified time components defined in a closure and return an array of dates.
|
||||
/// `startDate` interval will be the first item of the resulting array.
|
||||
/// The last item of the array is evaluated automatically and maybe not equal to `endDate`.
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - start: starting date
|
||||
/// - endDate: ending date
|
||||
/// - increment: increment function. It get the last generated date and require a valida `DateComponents` instance which define the increment
|
||||
/// - Returns: array of dates
|
||||
static func enumerateDates(from startDate: DateInRegion, to endDate: DateInRegion, increment: ((DateInRegion) -> (DateComponents))) -> [DateInRegion] {
|
||||
guard startDate.calendar == endDate.calendar else {
|
||||
debugPrint("Cannot enumerate dates between two different region's calendars. Return empty array.")
|
||||
return []
|
||||
}
|
||||
|
||||
var dates: [DateInRegion] = []
|
||||
var currentDate = startDate
|
||||
while currentDate <= endDate {
|
||||
dates.append(currentDate)
|
||||
currentDate = (currentDate + increment(currentDate))
|
||||
}
|
||||
return dates
|
||||
}
|
||||
|
||||
/// Returns a new DateInRegion that is initialized at the start of a specified unit of time.
|
||||
///
|
||||
/// - Parameter unit: time unit value.
|
||||
/// - Returns: instance at the beginning of the time unit; `self` if fails.
|
||||
func dateAtStartOf(_ unit: Calendar.Component) -> DateInRegion {
|
||||
#if os(Linux)
|
||||
guard let result = (region.calendar as NSCalendar).range(of: unit.nsCalendarUnit, for: date) else {
|
||||
return self
|
||||
}
|
||||
return DateInRegion(result.start, region: region)
|
||||
#else
|
||||
var start: NSDate?
|
||||
var interval: TimeInterval = 0
|
||||
guard (region.calendar as NSCalendar).range(of: unit.nsCalendarUnit, start: &start, interval: &interval, for: date),
|
||||
let startDate = start else {
|
||||
return self
|
||||
}
|
||||
return DateInRegion(startDate as Date, region: region)
|
||||
#endif
|
||||
}
|
||||
|
||||
/// Return a new DateInRegion that is initialized at the start of the specified components
|
||||
/// executed in order.
|
||||
///
|
||||
/// - Parameter units: sequence of transformations as time unit components
|
||||
/// - Returns: new date at the beginning of the passed components, intermediate results if fails.
|
||||
func dateAtStartOf(_ units: [Calendar.Component]) -> DateInRegion {
|
||||
return units.reduce(self) { (currentDate, currentUnit) -> DateInRegion in
|
||||
return currentDate.dateAtStartOf(currentUnit)
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns a new Moment that is initialized at the end of a specified unit of time.
|
||||
///
|
||||
/// - parameter unit: time unit value.
|
||||
///
|
||||
/// - returns: A new Moment instance.
|
||||
func dateAtEndOf(_ unit: Calendar.Component) -> DateInRegion {
|
||||
// RangeOfUnit returns the start of the next unit; we will subtract one thousandth of a second
|
||||
#if os(Linux)
|
||||
guard let result = (region.calendar as NSCalendar).range(of: unit.nsCalendarUnit, for: date) else {
|
||||
return self
|
||||
}
|
||||
let startOfNextUnit = result.start.addingTimeInterval(result.duration)
|
||||
let endOfThisUnit = Date(timeInterval: -0.001, since: startOfNextUnit)
|
||||
return DateInRegion(endOfThisUnit, region: region)
|
||||
#else
|
||||
var start: NSDate?
|
||||
var interval: TimeInterval = 0
|
||||
guard (self.region.calendar as NSCalendar).range(of: unit.nsCalendarUnit, start: &start, interval: &interval, for: date),
|
||||
let startDate = start else {
|
||||
return self
|
||||
}
|
||||
let startOfNextUnit = startDate.addingTimeInterval(interval)
|
||||
let endOfThisUnit = Date(timeInterval: -0.001, since: startOfNextUnit as Date)
|
||||
return DateInRegion(endOfThisUnit, region: region)
|
||||
#endif
|
||||
}
|
||||
|
||||
/// Return a new DateInRegion that is initialized at the end of the specified components
|
||||
/// executed in order.
|
||||
///
|
||||
/// - Parameter units: sequence of transformations as time unit components
|
||||
/// - Returns: new date at the end of the passed components, intermediate results if fails.
|
||||
func dateAtEndOf(_ units: [Calendar.Component]) -> DateInRegion {
|
||||
return units.reduce(self) { (currentDate, currentUnit) -> DateInRegion in
|
||||
return currentDate.dateAtEndOf(currentUnit)
|
||||
}
|
||||
}
|
||||
|
||||
/// Create a new date by altering specified components of the receiver.
|
||||
/// Note: `calendar` and `timezone` are ignored.
|
||||
/// Note: some components may alter the date cyclically (like setting both `.year` and `.yearForWeekOfYear`) and
|
||||
/// may results in a wrong evaluated date.
|
||||
///
|
||||
/// - Parameter components: components to alter with their new values.
|
||||
/// - Returns: new altered `DateInRegion` instance
|
||||
func dateBySet(_ components: [Calendar.Component: Int?]) -> DateInRegion? {
|
||||
var dateComponents = DateComponents()
|
||||
dateComponents.year = (components[.year] ?? year)
|
||||
dateComponents.month = (components[.month] ?? month)
|
||||
dateComponents.day = (components[.day] ?? day)
|
||||
dateComponents.hour = (components[.hour] ?? hour)
|
||||
dateComponents.minute = (components[.minute] ?? minute)
|
||||
dateComponents.second = (components[.second] ?? second)
|
||||
dateComponents.nanosecond = (components[.nanosecond] ?? nanosecond)
|
||||
|
||||
// Some components may interfer with others, so we'll set it them only if explicitly set.
|
||||
if let weekday = components[.weekday] {
|
||||
dateComponents.weekday = weekday
|
||||
}
|
||||
if let weekOfYear = components[.weekOfYear] {
|
||||
dateComponents.weekOfYear = weekOfYear
|
||||
}
|
||||
if let weekdayOrdinal = components[.weekdayOrdinal] {
|
||||
dateComponents.weekdayOrdinal = weekdayOrdinal
|
||||
}
|
||||
if let yearForWeekOfYear = components[.yearForWeekOfYear] {
|
||||
dateComponents.yearForWeekOfYear = yearForWeekOfYear
|
||||
}
|
||||
|
||||
guard let newDate = calendar.date(from: dateComponents) else { return nil }
|
||||
return DateInRegion(newDate, region: region)
|
||||
}
|
||||
|
||||
/// Create a new date by altering specified time components.
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - hour: hour to set (`nil` to leave it unaltered)
|
||||
/// - min: min to set (`nil` to leave it unaltered)
|
||||
/// - secs: sec to set (`nil` to leave it unaltered)
|
||||
/// - ms: milliseconds to set (`nil` to leave it unaltered)
|
||||
/// - options: options for calculation
|
||||
/// - Returns: new altered `DateInRegion` instance
|
||||
func dateBySet(hour: Int?, min: Int?, secs: Int?, ms: Int? = nil, options: TimeCalculationOptions = TimeCalculationOptions()) -> DateInRegion? {
|
||||
guard let date = calendar.date(bySettingHour: (hour ?? self.hour),
|
||||
minute: (min ?? self.minute),
|
||||
second: (secs ?? self.second),
|
||||
of: self.date,
|
||||
matchingPolicy: options.matchingPolicy,
|
||||
repeatedTimePolicy: options.repeatedTimePolicy,
|
||||
direction: options.direction) else { return nil }
|
||||
guard let ms = ms else {
|
||||
return DateInRegion(date, region: region)
|
||||
}
|
||||
var timestamp = date.timeIntervalSince1970.rounded(.down)
|
||||
timestamp += Double(ms) / 1000.0
|
||||
return DateInRegion(Date(timeIntervalSince1970: timestamp), region: region)
|
||||
}
|
||||
|
||||
/// Creates a new instance by truncating the components
|
||||
///
|
||||
/// - Parameter components: components to truncate.
|
||||
/// - Returns: new date with truncated components.
|
||||
func dateTruncated(at components: [Calendar.Component]) -> DateInRegion? {
|
||||
var dateComponents = self.dateComponents
|
||||
|
||||
for component in components {
|
||||
switch component {
|
||||
case .month: dateComponents.month = 1
|
||||
case .day: dateComponents.day = 1
|
||||
case .hour: dateComponents.hour = 0
|
||||
case .minute: dateComponents.minute = 0
|
||||
case .second: dateComponents.second = 0
|
||||
case .nanosecond: dateComponents.nanosecond = 0
|
||||
default: continue
|
||||
}
|
||||
}
|
||||
|
||||
guard let newDate = calendar.date(from: dateComponents) else { return nil }
|
||||
return DateInRegion(newDate, region: region)
|
||||
}
|
||||
|
||||
/// Creates a new instance by truncating the components starting from given components down the granurality.
|
||||
///
|
||||
/// - Parameter component: The component to be truncated from.
|
||||
/// - Returns: new date with truncated components.
|
||||
func dateTruncated(from component: Calendar.Component) -> DateInRegion? {
|
||||
switch component {
|
||||
case .month: return dateTruncated(at: [.month, .day, .hour, .minute, .second, .nanosecond])
|
||||
case .day: return dateTruncated(at: [.day, .hour, .minute, .second, .nanosecond])
|
||||
case .hour: return dateTruncated(at: [.hour, .minute, .second, .nanosecond])
|
||||
case .minute: return dateTruncated(at: [.minute, .second, .nanosecond])
|
||||
case .second: return dateTruncated(at: [.second, .nanosecond])
|
||||
case .nanosecond: return dateTruncated(at: [.nanosecond])
|
||||
default: return self
|
||||
}
|
||||
}
|
||||
|
||||
/// Round a given date time to the passed style (off|up|down).
|
||||
///
|
||||
/// - Parameter style: rounding mode.
|
||||
/// - Returns: rounded date
|
||||
func dateRoundedAt(_ style: RoundDateMode) -> DateInRegion {
|
||||
switch style {
|
||||
case .to5Mins: return dateRoundedAt(.toMins(5))
|
||||
case .to10Mins: return dateRoundedAt(.toMins(10))
|
||||
case .to30Mins: return dateRoundedAt(.toMins(30))
|
||||
case .toCeil5Mins: return dateRoundedAt(.toCeilMins(5))
|
||||
case .toCeil10Mins: return dateRoundedAt(.toCeilMins(10))
|
||||
case .toCeil30Mins: return dateRoundedAt(.toCeilMins(30))
|
||||
case .toFloor5Mins: return dateRoundedAt(.toFloorMins(5))
|
||||
case .toFloor10Mins: return dateRoundedAt(.toFloorMins(10))
|
||||
case .toFloor30Mins: return dateRoundedAt(.toFloorMins(30))
|
||||
|
||||
case .toMins(let minuteInterval):
|
||||
let onesDigit: Int = (minute % 10)
|
||||
if onesDigit < 5 {
|
||||
return dateRoundedAt(.toFloorMins(minuteInterval))
|
||||
} else {
|
||||
return dateRoundedAt(.toCeilMins(minuteInterval))
|
||||
}
|
||||
|
||||
case .toCeilMins(let minuteInterval):
|
||||
let remain: Int = (minute % minuteInterval)
|
||||
let value = (( Int(1.minutes.timeInterval) * (minuteInterval - remain)) - second)
|
||||
return dateByAdding(value, .second)
|
||||
|
||||
case .toFloorMins(let minuteInterval):
|
||||
let remain: Int = (minute % minuteInterval)
|
||||
let value = -((Int(1.minutes.timeInterval) * remain) + second)
|
||||
return dateByAdding(value, .second)
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
/// Offset a date by n calendar components.
|
||||
/// Note: This operation can be functionally chained.
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - count: value of the offset (maybe negative).
|
||||
/// - component: component to offset.
|
||||
/// - Returns: new altered date.
|
||||
func dateByAdding(_ count: Int, _ component: Calendar.Component) -> DateInRegion {
|
||||
var newComponent = DateComponents(second: 0)
|
||||
switch component {
|
||||
case .era: newComponent = DateComponents(era: count)
|
||||
case .year: newComponent = DateComponents(year: count)
|
||||
case .month: newComponent = DateComponents(month: count)
|
||||
case .day: newComponent = DateComponents(day: count)
|
||||
case .hour: newComponent = DateComponents(hour: count)
|
||||
case .minute: newComponent = DateComponents(minute: count)
|
||||
case .second: newComponent = DateComponents(second: count)
|
||||
case .weekday: newComponent = DateComponents(weekday: count)
|
||||
case .weekdayOrdinal: newComponent = DateComponents(weekdayOrdinal: count)
|
||||
case .quarter: newComponent = DateComponents(quarter: count)
|
||||
case .weekOfMonth: newComponent = DateComponents(weekOfMonth: count)
|
||||
case .weekOfYear: newComponent = DateComponents(weekOfYear: count)
|
||||
case .yearForWeekOfYear: newComponent = DateComponents(yearForWeekOfYear: count)
|
||||
case .nanosecond: newComponent = DateComponents(nanosecond: count)
|
||||
default: break // .calendar and .timezone does nothing in this context
|
||||
}
|
||||
|
||||
guard let newDate = region.calendar.date(byAdding: newComponent, to: date) else {
|
||||
return self // failed to add component, return unmodified date
|
||||
}
|
||||
return DateInRegion(newDate, region: region)
|
||||
}
|
||||
|
||||
/// Return related date starting from the receiver attributes.
|
||||
///
|
||||
/// - Parameter type: related date to obtain.
|
||||
/// - Returns: instance of the related date; if fails the same unmodified date is returned
|
||||
func dateAt(_ type: DateRelatedType) -> DateInRegion {
|
||||
switch type {
|
||||
case .startOfDay:
|
||||
return calendar.startOfDay(for: date).in(region: region)
|
||||
case .endOfDay:
|
||||
return dateByAdding(1, .day).dateAt(.startOfDay).dateByAdding(-1, .second)
|
||||
case .startOfWeek:
|
||||
let components = calendar.dateComponents([.yearForWeekOfYear, .weekOfYear], from: date)
|
||||
return calendar.date(from: components)!.in(region: region)
|
||||
case .endOfWeek:
|
||||
return dateAt(.startOfWeek).dateByAdding(7, .day).dateByAdding(-1, .second)
|
||||
case .startOfMonth:
|
||||
return dateBySet([.day: 1, .hour: 1, .minute: 1, .second: 1, .nanosecond: 1])!
|
||||
case .endOfMonth:
|
||||
return dateByAdding((monthDays - day), .day).dateAtEndOf(.day)
|
||||
case .tomorrow:
|
||||
return dateByAdding(1, .day)
|
||||
case .tomorrowAtStart:
|
||||
return dateByAdding(1, .day).dateAtStartOf(.day)
|
||||
case .yesterday:
|
||||
return dateByAdding(-1, .day)
|
||||
case .yesterdayAtStart:
|
||||
return dateByAdding(-1, .day).dateAtStartOf(.day)
|
||||
case .nearestMinute(let nearest):
|
||||
let minutes = (minute + nearest / 2) / nearest * nearest
|
||||
return dateBySet([.minute: minutes])!
|
||||
case .nearestHour(let nearest):
|
||||
let hours = (hour + nearest / 2) / nearest * nearest
|
||||
return dateBySet([.hour: hours, .minute: 0])!
|
||||
case .nextWeekday(let weekday):
|
||||
var cal = Calendar(identifier: calendar.identifier)
|
||||
cal.firstWeekday = 2 // Sunday = 1, Saturday = 7
|
||||
var components = DateComponents()
|
||||
components.weekday = weekday.rawValue
|
||||
guard let next = cal.nextDate(after: date, matching: components, matchingPolicy: .nextTimePreservingSmallerComponents) else {
|
||||
return self
|
||||
}
|
||||
return DateInRegion(next, region: region)
|
||||
case .nextDSTDate:
|
||||
guard let nextDate = region.timeZone.nextDaylightSavingTimeTransition(after: date) else {
|
||||
return self
|
||||
}
|
||||
return DateInRegion(nextDate, region: region)
|
||||
case .prevMonth:
|
||||
return dateByAdding(-1, .month).dateAtStartOf(.month).dateAtStartOf(.day)
|
||||
case .nextMonth:
|
||||
return dateByAdding(1, .month).dateAtStartOf(.month).dateAtStartOf(.day)
|
||||
case .prevWeek:
|
||||
return dateByAdding(-1, .weekOfYear).dateAtStartOf(.weekOfYear).dateAtStartOf(.day)
|
||||
case .nextWeek:
|
||||
return dateByAdding(1, .weekOfYear).dateAtStartOf(.weekOfYear).dateAtStartOf(.day)
|
||||
case .nextYear:
|
||||
return dateByAdding(1, .year).dateAtStartOf(.year)
|
||||
case .prevYear:
|
||||
return dateByAdding(-1, .year).dateAtStartOf(.year)
|
||||
case .nextDSTTransition:
|
||||
guard let transitionDate = region.timeZone.nextDaylightSavingTimeTransition(after: date) else {
|
||||
return self
|
||||
}
|
||||
return DateInRegion(transitionDate, region: region)
|
||||
}
|
||||
}
|
||||
|
||||
/// Create a new instance of the date in the same region with time shifted by given time interval.
|
||||
///
|
||||
/// - Parameter interval: time interval to shift; maybe negative.
|
||||
/// - Returns: new instance of the `DateInRegion`
|
||||
func addingTimeInterval(_ interval: TimeInterval) -> DateInRegion {
|
||||
return DateInRegion(date.addingTimeInterval(interval), region: region)
|
||||
}
|
||||
|
||||
// MARK: - Conversion
|
||||
|
||||
/// Convert a date to a new calendar/timezone/locale.
|
||||
/// Only non `nil` values are used, other values are inherithed by the receiver's region.
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - calendar: non `nil` value to change the calendar
|
||||
/// - timezone: non `nil` value to change the timezone
|
||||
/// - locale: non `nil` value to change the locale
|
||||
/// - Returns: converted date
|
||||
func convertTo(calendar: CalendarConvertible? = nil, timezone: ZoneConvertible? = nil, locale: LocaleConvertible? = nil) -> DateInRegion {
|
||||
let newRegion = Region(calendar: (calendar ?? region.calendar),
|
||||
zone: (timezone ?? region.timeZone),
|
||||
locale: (locale ?? region.locale))
|
||||
return convertTo(region: newRegion)
|
||||
}
|
||||
|
||||
/// Return the dates for a specific weekday inside given month of specified year.
|
||||
/// Ie. get me all the saturdays of Feb 2018.
|
||||
/// NOTE: Values are returned in order.
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - weekday: weekday target.
|
||||
/// - month: month target.
|
||||
/// - year: year target.
|
||||
/// - region: region target, omit to use `SwiftDate.defaultRegion`
|
||||
/// - Returns: Ordered list of the dates for given weekday into given month.
|
||||
static func datesForWeekday(_ weekday: WeekDay, inMonth month: Int, ofYear year: Int,
|
||||
region: Region = SwiftDate.defaultRegion) -> [DateInRegion] {
|
||||
let fromDate = DateInRegion(year: year, month: month, day: 1, hour: 0, minute: 0, second: 0, nanosecond: 0, region: region)
|
||||
let toDate = fromDate.dateAt(.endOfMonth)
|
||||
return DateInRegion.datesForWeekday(weekday, from: fromDate, to: toDate, region: region)
|
||||
}
|
||||
|
||||
/// Return the dates for a specific weekday inside a specified date range.
|
||||
/// NOTE: Values are returned in order.
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - weekday: weekday target.
|
||||
/// - startDate: from date of the range.
|
||||
/// - endDate: to date of the range.
|
||||
/// - region: region target, omit to use `SwiftDate.defaultRegion`
|
||||
/// - Returns: Ordered list of the dates for given weekday in passed range.
|
||||
static func datesForWeekday(_ weekday: WeekDay, from startDate: DateInRegion, to endDate: DateInRegion,
|
||||
region: Region = SwiftDate.defaultRegion) -> [DateInRegion] {
|
||||
|
||||
let calendarObj = region.calendar
|
||||
let startDateWeekDay = Int(calendarObj.component(.weekday, from: startDate.date))
|
||||
let desiredDay = weekday.rawValue
|
||||
|
||||
let offset = (desiredDay - startDateWeekDay + 7) % 7
|
||||
let firstOccurrence = calendarObj.startOfDay(for: calendarObj.date(byAdding: DateComponents(day: offset), to: startDate.date)!)
|
||||
guard firstOccurrence.timeIntervalSince1970 < endDate.timeIntervalSince1970 else {
|
||||
return []
|
||||
}
|
||||
var dateOccurrences = [DateInRegion(firstOccurrence, region: region)]
|
||||
while true {
|
||||
let nextDate = DateInRegion(calendarObj.date(byAdding: DateComponents(day: 7), to: dateOccurrences.last!.date)!,
|
||||
region: region)
|
||||
guard nextDate < endDate else {
|
||||
break
|
||||
}
|
||||
dateOccurrences.append(nextDate)
|
||||
}
|
||||
return dateOccurrences
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public extension DateInRegion {
|
||||
|
||||
/// Returns the date at the given week number and week day preserving smaller components (hour, minute, seconds)
|
||||
///
|
||||
/// For example: to get the third friday of next month
|
||||
/// let today = DateInRegion()
|
||||
/// let result = today.dateAt(weekdayOrdinal: 3, weekday: .friday, monthNumber: today.month + 1)
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - weekdayOrdinal: the week number (by set position in a recurrence rule)
|
||||
/// - weekday: WeekDay
|
||||
/// - monthNumber: a number from 1 to 12 representing the month, optional parameter
|
||||
/// - yearNumber: a number representing the year, optional parameter
|
||||
/// - Returns: new date created with the given parameters
|
||||
func dateAt(weekdayOrdinal: Int, weekday: WeekDay, monthNumber: Int? = nil,
|
||||
yearNumber: Int? = nil) -> DateInRegion {
|
||||
let monthNum = monthNumber ?? month
|
||||
let yearNum = yearNumber ?? year
|
||||
|
||||
var requiredWeekNum = weekdayOrdinal
|
||||
var result = DateInRegion(year: yearNum, month: monthNum, day: 1, hour: hour,
|
||||
minute: minute, second: second, nanosecond: nanosecond, region: region)
|
||||
|
||||
if result.weekday == weekday.rawValue {
|
||||
requiredWeekNum -= 1
|
||||
}
|
||||
|
||||
while requiredWeekNum > 0 {
|
||||
result = result.nextWeekday(weekday)
|
||||
requiredWeekNum -= 1
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
/// Returns the date on the given day of month preserving smaller components
|
||||
func dateAt(dayOfMonth: Int, monthNumber: Int? = nil,
|
||||
yearNumber: Int? = nil) -> DateInRegion {
|
||||
let monthNum = monthNumber ?? month
|
||||
let yearNum = yearNumber ?? year
|
||||
|
||||
let result = DateInRegion(year: yearNum, month: monthNum, day: dayOfMonth,
|
||||
hour: hour, minute: minute, second: second,
|
||||
nanosecond: nanosecond, region: region)
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
/// Returns the date after given number of weeks on the given day of week
|
||||
func dateAfter(weeks count: Int, on weekday: WeekDay) -> DateInRegion {
|
||||
var result = self.dateByAdding(count, .weekOfMonth)
|
||||
if result.weekday == weekday.rawValue {
|
||||
return result
|
||||
} else if result.weekday > weekday.rawValue {
|
||||
result = result.dateByAdding(-1, .weekOfMonth)
|
||||
}
|
||||
return result.nextWeekday(weekday)
|
||||
}
|
||||
|
||||
/// Returns the next weekday preserving smaller components
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - weekday: weekday to get.
|
||||
/// - region: region target, omit to use `SwiftDate.defaultRegion`
|
||||
/// - Returns: `DateInRegion`
|
||||
func nextWeekday(_ weekday: WeekDay) -> DateInRegion {
|
||||
var components = DateComponents()
|
||||
components.weekday = weekday.rawValue
|
||||
components.hour = hour
|
||||
components.second = second
|
||||
components.minute = minute
|
||||
|
||||
guard let next = region.calendar.nextDate(after: date, matching: components,
|
||||
matchingPolicy: .nextTimePreservingSmallerComponents) else {
|
||||
return self
|
||||
}
|
||||
|
||||
return DateInRegion(next, region: region)
|
||||
}
|
||||
|
||||
/// Returns next date with the given weekday and the given week number
|
||||
func next(_ weekday: WeekDay, withWeekOfMonth weekNumber: Int,
|
||||
andMonthNumber monthNumber: Int? = nil) -> DateInRegion {
|
||||
var result = self.dateAt(weekdayOrdinal: weekNumber, weekday: weekday, monthNumber: monthNumber)
|
||||
|
||||
if result <= self {
|
||||
|
||||
if let monthNum = monthNumber {
|
||||
result = self.dateAt(weekdayOrdinal: weekNumber, weekday: weekday,
|
||||
monthNumber: monthNum, yearNumber: self.year + 1)
|
||||
} else {
|
||||
result = self.dateAt(weekdayOrdinal: weekNumber, weekday: weekday, monthNumber: self.month + 1)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
/// Returns the next day of month preserving smaller components (hour, minute, seconds)
|
||||
func next(dayOfMonth: Int, monthOfYear: Int? = nil) -> DateInRegion {
|
||||
var components = DateComponents()
|
||||
components.day = dayOfMonth
|
||||
components.month = monthOfYear
|
||||
components.hour = hour
|
||||
components.second = second
|
||||
components.minute = minute
|
||||
|
||||
guard let next = region.calendar.nextDate(after: date, matching: components,
|
||||
matchingPolicy: .nextTimePreservingSmallerComponents) else {
|
||||
return self
|
||||
}
|
||||
|
||||
return DateInRegion(next, region: region)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,82 @@
|
||||
//
|
||||
// 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
|
||||
|
||||
// MARK: - Math Operation DateInRegion - DateInRegion
|
||||
|
||||
public func - (lhs: DateInRegion, rhs: DateInRegion) -> TimeInterval {
|
||||
return lhs.timeIntervalSince(rhs)
|
||||
}
|
||||
|
||||
// MARK: - Math Operation DateInRegion - Date Components
|
||||
|
||||
public func + (lhs: DateInRegion, rhs: DateComponents) -> DateInRegion {
|
||||
let nextDate = lhs.calendar.date(byAdding: rhs, to: lhs.date)
|
||||
return DateInRegion(nextDate!, region: lhs.region)
|
||||
}
|
||||
|
||||
public func - (lhs: DateInRegion, rhs: DateComponents) -> DateInRegion {
|
||||
return lhs + (-rhs)
|
||||
}
|
||||
|
||||
// MARK: - Math Operation DateInRegion - Calendar.Component
|
||||
|
||||
public func + (lhs: DateInRegion, rhs: [Calendar.Component: Int]) -> DateInRegion {
|
||||
let cmps = DateInRegion.componentsFrom(values: rhs)
|
||||
return lhs + cmps
|
||||
}
|
||||
|
||||
public func - (lhs: DateInRegion, rhs: [Calendar.Component: Int]) -> DateInRegion {
|
||||
var invertedCmps: [Calendar.Component: Int] = [:]
|
||||
rhs.forEach { invertedCmps[$0.key] = -$0.value }
|
||||
return lhs + invertedCmps
|
||||
}
|
||||
|
||||
// MARK: - Internal DateInRegion Extension
|
||||
|
||||
extension DateInRegion {
|
||||
|
||||
/// Return a `DateComponent` object from a given set of `Calendar.Component` object with associated values and a specific region
|
||||
///
|
||||
/// - parameter values: calendar components to set (with their values)
|
||||
/// - parameter multipler: optional multipler (by default is nil; to make an inverse component value it should be multipled by -1)
|
||||
/// - parameter region: optional region to set
|
||||
///
|
||||
/// - returns: a `DateComponents` object
|
||||
internal static func componentsFrom(values: [Calendar.Component: Int], multipler: Int? = nil, setRegion region: Region? = nil) -> DateComponents {
|
||||
var cmps = DateComponents()
|
||||
if region != nil {
|
||||
cmps.calendar = region!.calendar
|
||||
cmps.calendar!.locale = region!.locale
|
||||
cmps.timeZone = region!.timeZone
|
||||
}
|
||||
values.forEach { pair in
|
||||
if pair.key != .timeZone && pair.key != .calendar {
|
||||
cmps.setValue( (multipler == nil ? pair.value : pair.value * multipler!), for: pair.key)
|
||||
}
|
||||
}
|
||||
return cmps
|
||||
}
|
||||
|
||||
/// Adds a time interval to this date.
|
||||
/// WARNING:
|
||||
/// This only adjusts an absolute value. If you wish to add calendrical concepts like hours,
|
||||
/// days, months then you must use a Calendar.
|
||||
/// That will take into account complexities like daylight saving time,
|
||||
/// months with different numbers of days, and more.
|
||||
///
|
||||
/// - Parameter timeInterval: The value to add, in seconds.
|
||||
public mutating func addTimeInterval(_ timeInterval: TimeInterval) {
|
||||
date.addTimeInterval(timeInterval)
|
||||
}
|
||||
}
|
||||
185
Sources/App/Libraries/SwiftDate/DateInRegion/DateInRegion.swift
Normal file
185
Sources/App/Libraries/SwiftDate/DateInRegion/DateInRegion.swift
Normal file
@@ -0,0 +1,185 @@
|
||||
//
|
||||
// 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 struct DateInRegion: DateRepresentable, Decodable, Encodable, CustomStringConvertible, Comparable, Hashable {
|
||||
|
||||
/// Absolute date represented. This date is not associated with any timezone or calendar
|
||||
/// but represent the absolute number of seconds since Jan 1, 2001 at 00:00:00 UTC.
|
||||
public internal(set) var date: Date
|
||||
|
||||
/// Associated region which define where the date is represented into the world.
|
||||
public let region: Region
|
||||
|
||||
/// Formatter used to transform this object in a string. By default is `nil` because SwiftDate
|
||||
/// uses the thread shared formatter in order to avoid expensive init of the `DateFormatter` object.
|
||||
/// However, if you need of a custom behaviour you can set a valid value.
|
||||
public var customFormatter: DateFormatter?
|
||||
|
||||
/// Extract date components by taking care of the region in which the date is expressed.
|
||||
public var dateComponents: DateComponents {
|
||||
return region.calendar.dateComponents(DateComponents.allComponentsSet, from: date)
|
||||
}
|
||||
|
||||
/// Description of the date
|
||||
public var description: String {
|
||||
let absISODate = DateFormatter.sharedFormatter(forRegion: Region.UTC).string(from: date)
|
||||
let representedDate = formatter(format: DateFormats.iso8601).string(from: date)
|
||||
return "{abs_date='\(absISODate)', rep_date='\(representedDate)', region=\(region.description)"
|
||||
}
|
||||
|
||||
/// The interval between the date value and 00:00:00 UTC on 1 January 1970.
|
||||
public var timeIntervalSince1970: TimeInterval {
|
||||
return date.timeIntervalSince1970
|
||||
}
|
||||
|
||||
/// Initialize with an absolute date and represent it into given geographic region.
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - date: absolute date to represent.
|
||||
/// - region: region in which the date is represented. If ignored `defaultRegion` is used instead.
|
||||
public init(_ date: Date = Date(), region: Region = SwiftDate.defaultRegion) {
|
||||
self.date = date
|
||||
self.region = region
|
||||
}
|
||||
|
||||
/// Initialize a new `DateInRegion` by parsing given string.
|
||||
/// If you know the format of the string you should pass it in order to speed up the parsing process.
|
||||
/// If you don't know the format leave it `nil` and parse is done between all formats in `DateFormats.builtInAutoFormats`
|
||||
/// and the ordered list you can provide in `SwiftDate.autoParseFormats` (with attempt priority set on your list).
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - string: string with the date.
|
||||
/// - format: format of the date.
|
||||
/// - region: region in which the date is expressed.
|
||||
public init?(_ string: String, format: String? = nil, region: Region = SwiftDate.defaultRegion) {
|
||||
guard let date = DateFormats.parse(string: string,
|
||||
format: format,
|
||||
region: region) else {
|
||||
return nil // failed to parse date
|
||||
}
|
||||
self.date = date
|
||||
self.region = region
|
||||
}
|
||||
|
||||
/// Initialize a new `DateInRegion` by parsing given string with the ordered list of passed formats.
|
||||
/// If you know the format of the string you should pass it in order to speed up the parsing process.
|
||||
/// If you don't know the format leave it `nil` and parse is done between all formats in `DateFormats.builtInAutoFormats`
|
||||
/// and the ordered list you can provide in `SwiftDate.autoParseFormats` (with attempt priority set on your list).
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - string: string with the date.
|
||||
/// - formats: ordered list of formats to use.
|
||||
/// - region: region in which the date is expressed.
|
||||
public init?(_ string: String, formats: [String]?, region: Region = SwiftDate.defaultRegion) {
|
||||
guard let date = DateFormats.parse(string: string,
|
||||
formats: (formats ?? SwiftDate.autoFormats),
|
||||
region: region) else {
|
||||
return nil // failed to parse date
|
||||
}
|
||||
self.date = date
|
||||
self.region = region
|
||||
}
|
||||
|
||||
/// Initialize a new date from the number of seconds passed since Unix Epoch.
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - interval: seconds since Unix Epoch.
|
||||
/// - region: the region in which the date must be expressed, `nil` uses the default region at UTC timezone
|
||||
public init(seconds interval: TimeInterval, region: Region = Region.UTC) {
|
||||
self.date = Date(timeIntervalSince1970: interval)
|
||||
self.region = region
|
||||
}
|
||||
|
||||
/// Initialize a new date corresponding to the number of milliseconds since the Unix Epoch.
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - interval: seconds since the Unix Epoch timestamp.
|
||||
/// - region: region in which the date must be expressed, `nil` uses the default region at UTC timezone
|
||||
public init(milliseconds interval: Int, region: Region = Region.UTC) {
|
||||
self.date = Date(timeIntervalSince1970: TimeInterval(interval) / 1000)
|
||||
self.region = region
|
||||
}
|
||||
|
||||
/// Initialize a new date with the opportunity to configure single date components via builder pattern.
|
||||
/// Date is therfore expressed in passed region (`DateComponents`'s `timezone`,`calendar` and `locale` are ignored
|
||||
/// and overwritten by the region if not `nil`).
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - configuration: configuration callback
|
||||
/// - region: region in which the date is expressed.
|
||||
/// Ignore to use `SwiftDate.defaultRegion`, `nil` to use `DateComponents` data.
|
||||
public init?(components configuration: ((inout DateComponents) -> Void), region: Region? = SwiftDate.defaultRegion) {
|
||||
var components = DateComponents()
|
||||
configuration(&components)
|
||||
let r = (region ?? Region(fromDateComponents: components))
|
||||
guard let date = r.calendar.date(from: components) else {
|
||||
return nil
|
||||
}
|
||||
self.date = date
|
||||
self.region = r
|
||||
}
|
||||
|
||||
/// Initialize a new date with given components.
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - components: components of the date.
|
||||
/// - region: region in which the date is expressed.
|
||||
/// Ignore to use `SwiftDate.defaultRegion`, `nil` to use `DateComponents` data.
|
||||
public init?(components: DateComponents, region: Region?) {
|
||||
let r = (region ?? Region(fromDateComponents: components))
|
||||
guard let date = r.calendar.date(from: components) else {
|
||||
return nil
|
||||
}
|
||||
self.date = date
|
||||
self.region = r
|
||||
}
|
||||
|
||||
/// Initialize a new date with given components.
|
||||
public init(year: Int, month: Int, day: Int, hour: Int = 0, minute: Int = 0, second: Int = 0, nanosecond: Int = 0, region: Region = SwiftDate.defaultRegion) {
|
||||
var components = DateComponents()
|
||||
components.year = year
|
||||
components.month = month
|
||||
components.day = day
|
||||
components.hour = hour
|
||||
components.minute = minute
|
||||
components.second = second
|
||||
components.nanosecond = nanosecond
|
||||
components.timeZone = region.timeZone
|
||||
components.calendar = region.calendar
|
||||
self.date = region.calendar.date(from: components)!
|
||||
self.region = region
|
||||
}
|
||||
|
||||
/// Return a date in the distant past.
|
||||
///
|
||||
/// - Returns: Date instance.
|
||||
public static func past() -> DateInRegion {
|
||||
return DateInRegion(Date.distantPast, region: SwiftDate.defaultRegion)
|
||||
}
|
||||
|
||||
/// Return a date in the distant future.
|
||||
///
|
||||
/// - Returns: Date instance.
|
||||
public static func future() -> DateInRegion {
|
||||
return DateInRegion(Date.distantFuture, region: SwiftDate.defaultRegion)
|
||||
}
|
||||
|
||||
// MARK: - Codable Support
|
||||
|
||||
enum CodingKeys: String, CodingKey {
|
||||
case date
|
||||
case region
|
||||
}
|
||||
|
||||
}
|
||||
155
Sources/App/Libraries/SwiftDate/DateInRegion/Region.swift
Normal file
155
Sources/App/Libraries/SwiftDate/DateInRegion/Region.swift
Normal file
@@ -0,0 +1,155 @@
|
||||
//
|
||||
// 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
|
||||
|
||||
/// Region define a context both for `Date` and `DateInRegion`.
|
||||
/// Each `Date` is assigned to the currently set `SwiftDate.default
|
||||
public struct Region: Decodable, Encodable, Equatable, Hashable, CustomStringConvertible {
|
||||
|
||||
// MARK: - Properties
|
||||
|
||||
/// Calendar associated with region
|
||||
public let calendar: Calendar
|
||||
|
||||
/// Locale associated with region
|
||||
public var locale: Locale { return calendar.locale! }
|
||||
|
||||
/// Timezone associated with region
|
||||
public var timeZone: TimeZone { return calendar.timeZone }
|
||||
|
||||
/// Description of the object
|
||||
public var description: String {
|
||||
return "{calendar='\(calendar.identifier)', timezone='\(timeZone.identifier)', locale='\(locale.identifier)'}"
|
||||
}
|
||||
|
||||
public func hash(into hasher: inout Hasher) {
|
||||
hasher.combine(calendar)
|
||||
}
|
||||
|
||||
// MARK: Initialization
|
||||
|
||||
/// Initialize a new region with given parameters.
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - calendar: calendar for region, if not specified `defaultRegions`'s calendar is used instead.
|
||||
/// - timezone: timezone for region, if not specified `defaultRegions`'s timezone is used instead.
|
||||
/// - locale: locale for region, if not specified `defaultRegions`'s locale is used instead.
|
||||
public init(calendar: CalendarConvertible = SwiftDate.defaultRegion.calendar,
|
||||
zone: ZoneConvertible = SwiftDate.defaultRegion.timeZone,
|
||||
locale: LocaleConvertible = SwiftDate.defaultRegion.locale) {
|
||||
self.calendar = Calendar.newCalendar(calendar, configure: {
|
||||
$0.timeZone = zone.toTimezone()
|
||||
$0.locale = locale.toLocale()
|
||||
})
|
||||
}
|
||||
|
||||
/// Initialize a new Region by reading the `timeZone`,`calendar` and `locale`
|
||||
/// parameters from the passed `DateComponents` instance.
|
||||
/// For any `nil` parameter the correspondent `SwiftDate.defaultRegion` is used instead.
|
||||
///
|
||||
/// - Parameter fromDateComponents: date components
|
||||
public init(fromDateComponents components: DateComponents) {
|
||||
let tz = (components.timeZone ?? Zones.current.toTimezone())
|
||||
let cal = (components.calendar ?? Calendars.gregorian.toCalendar())
|
||||
let loc = (cal.locale ?? Locales.current.toLocale())
|
||||
self.init(calendar: cal, zone: tz, locale: loc)
|
||||
}
|
||||
|
||||
public static var UTC: Region {
|
||||
return Region(calendar: Calendar.autoupdatingCurrent,
|
||||
zone: Zones.gmt.toTimezone(),
|
||||
locale: Locale.autoupdatingCurrent)
|
||||
}
|
||||
|
||||
/// Return the current local device's region where all attributes are set to the device's values.
|
||||
///
|
||||
/// - Returns: Region
|
||||
public static var local: Region {
|
||||
return Region(calendar: Calendar.autoupdatingCurrent,
|
||||
zone: TimeZone.autoupdatingCurrent,
|
||||
locale: Locale.autoupdatingCurrent)
|
||||
}
|
||||
|
||||
/// ISO Region is defined by the gregorian calendar, gmt timezone and english posix locale
|
||||
public static var ISO: Region {
|
||||
return Region(calendar: Calendars.gregorian.toCalendar(),
|
||||
zone: Zones.gmt.toTimezone(),
|
||||
locale: Locales.englishUnitedStatesComputer)
|
||||
}
|
||||
|
||||
/// Return an auto updating region where all settings are obtained from the current's device settings.
|
||||
///
|
||||
/// - Returns: Region
|
||||
public static var current: Region {
|
||||
return Region(calendar: Calendar.autoupdatingCurrent,
|
||||
zone: TimeZone.autoupdatingCurrent,
|
||||
locale: Locale.autoupdatingCurrent)
|
||||
}
|
||||
|
||||
/// Return a new region in current's device timezone with optional adjust of the calendar and locale.
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - locale: locale to set
|
||||
/// - calendar: calendar to set
|
||||
/// - Returns: region
|
||||
public static func currentIn(locale: LocaleConvertible? = nil, calendar: CalendarConvertible? = nil) -> Region {
|
||||
return Region(calendar: (calendar ?? SwiftDate.defaultRegion.calendar),
|
||||
zone: SwiftDate.defaultRegion.timeZone,
|
||||
locale: (locale ?? SwiftDate.defaultRegion.locale))
|
||||
}
|
||||
|
||||
/// Return the current date expressed into the receiver region.
|
||||
///
|
||||
/// - Returns: `DateInRegion` instance
|
||||
public func nowInThisRegion() -> DateInRegion {
|
||||
return DateInRegion(Date(), region: self)
|
||||
}
|
||||
|
||||
// MARK: - Codable Support
|
||||
|
||||
enum CodingKeys: String, CodingKey {
|
||||
case calendar
|
||||
case locale
|
||||
case timezone
|
||||
}
|
||||
|
||||
public func encode(to encoder: Encoder) throws {
|
||||
var container = encoder.container(keyedBy: CodingKeys.self)
|
||||
try container.encode(calendar.timeZone.identifier, forKey: .timezone)
|
||||
try container.encode(calendar.locale!.identifier, forKey: .locale)
|
||||
try container.encode(calendar.identifier.description, forKey: .calendar)
|
||||
}
|
||||
|
||||
public init(from decoder: Decoder) throws {
|
||||
let values = try decoder.container(keyedBy: CodingKeys.self)
|
||||
let calId = Calendar.Identifier( try values.decode(String.self, forKey: .calendar))
|
||||
let tz = (TimeZone(identifier: try values.decode(String.self, forKey: .timezone)) ?? SwiftDate.defaultRegion.timeZone)
|
||||
let lc = Locale(identifier: try values.decode(String.self, forKey: .locale))
|
||||
calendar = Calendar.newCalendar(calId, configure: {
|
||||
$0.timeZone = tz
|
||||
$0.locale = lc
|
||||
})
|
||||
}
|
||||
|
||||
// MARK: - Comparable
|
||||
|
||||
public static func == (lhs: Region, rhs: Region) -> Bool {
|
||||
// Note: equality does not consider other parameters than the identifier of the major
|
||||
// attributes (calendar, timezone and locale). Deeper comparisor must be made directly
|
||||
// between Calendar (it may fail when you encode/decode autoUpdating calendars).
|
||||
return
|
||||
(lhs.calendar.identifier == rhs.calendar.identifier) &&
|
||||
(lhs.timeZone.identifier == rhs.timeZone.identifier) &&
|
||||
(lhs.locale.identifier == rhs.locale.identifier)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user