Repository: malcommac/SwiftDate Branch: master Commit: 5d943224c3bb Files: 93 Total size: 651.0 KB Directory structure: gitextract_nwg7vbkr/ ├── .github/ │ └── FUNDING.yml ├── .gitignore ├── .sourcery/ │ └── LinuxMain.stencil ├── .swiftlint.yml ├── .swiftpm/ │ └── xcode/ │ └── package.xcworkspace/ │ └── contents.xcworkspacedata ├── .travis.yml ├── Configs/ │ ├── SwiftDate.plist │ └── SwiftDateTests.plist ├── Documentation/ │ ├── 0.Informations.md │ ├── 1.Introduction.md │ ├── 10.Upgrading_SwiftDate4.md │ ├── 11.Related_Projects.md │ ├── 12.Timer_Periods.md │ ├── 2.Date_Parsing.md │ ├── 3.Manipulate_Date.md │ ├── 4.Compare_Dates.md │ ├── 5.Date_Formatting.md │ ├── 6.TimeInterval_Formatting.md │ ├── 7.Format_UnicodeTable.md │ ├── 8.Customize_ColloquialFormatter.md │ ├── 9.ColloquialSupportedLanguages.md │ └── Index.md ├── LICENSE ├── Package.swift ├── Playgrounds/ │ └── SwiftDate.playground/ │ ├── Contents.o │ ├── Pages/ │ │ ├── Compare Dates.xcplaygroundpage/ │ │ │ └── Contents.swift │ │ ├── Date Formatting.xcplaygroundpage/ │ │ │ └── Contents.swift │ │ ├── Date Manipulation.xcplaygroundpage/ │ │ │ └── Contents.swift │ │ ├── Date Parsing.xcplaygroundpage/ │ │ │ └── Contents.swift │ │ └── Introduction.xcplaygroundpage/ │ │ └── Contents.swift │ └── contents.xcplayground ├── README.md ├── Sources/ │ └── SwiftDate/ │ ├── Date/ │ │ ├── Date+Compare.swift │ │ ├── Date+Components.swift │ │ ├── Date+Create.swift │ │ ├── Date+Math.swift │ │ └── Date.swift │ ├── DateInRegion/ │ │ ├── DateInRegion+Compare.swift │ │ ├── DateInRegion+Components.swift │ │ ├── DateInRegion+Create.swift │ │ ├── DateInRegion+Math.swift │ │ ├── DateInRegion.swift │ │ └── Region.swift │ ├── DateRepresentable.swift │ ├── Formatters/ │ │ ├── DotNetParserFormatter.swift │ │ ├── Formatter+Protocols.swift │ │ ├── ISOFormatter.swift │ │ └── ISOParser.swift │ ├── Foundation+Extras/ │ │ ├── DateComponents+Extras.swift │ │ ├── Int+DateComponents.swift │ │ ├── String+Parser.swift │ │ └── TimeInterval+Formatter.swift │ ├── Supports/ │ │ ├── AssociatedValues.swift │ │ ├── Calendars.swift │ │ ├── Commons.swift │ │ ├── Locales.swift │ │ ├── TimeStructures.swift │ │ └── Zones.swift │ ├── SwiftDate.swift │ └── TimePeriod/ │ ├── Groups/ │ │ ├── TimePeriodChain.swift │ │ ├── TimePeriodCollection.swift │ │ └── TimePeriodGroup.swift │ ├── TimePeriod+Support.swift │ ├── TimePeriod.swift │ └── TimePeriodProtocol.swift ├── SwiftDate.podspec ├── SwiftDate.xcodeproj/ │ ├── project.pbxproj │ ├── project.xcworkspace/ │ │ ├── contents.xcworkspacedata │ │ └── xcshareddata/ │ │ └── IDEWorkspaceChecks.plist │ └── xcshareddata/ │ └── xcschemes/ │ ├── SwiftDate-iOS Tests.xcscheme │ ├── SwiftDate-iOS.xcscheme │ ├── SwiftDate-macOS.xcscheme │ ├── SwiftDate-tvOS.xcscheme │ └── SwiftDate-watchOS.xcscheme ├── TestApplication/ │ ├── AppDelegate.swift │ ├── Assets.xcassets/ │ │ ├── AppIcon.appiconset/ │ │ │ └── Contents.json │ │ └── Contents.json │ ├── Base.lproj/ │ │ ├── LaunchScreen.storyboard │ │ └── Main.storyboard │ ├── Info.plist │ └── ViewController.swift ├── Tests/ │ ├── LinuxMain.swift │ └── SwiftDateTests/ │ ├── TestDataStructures.swift │ ├── TestDate.swift │ ├── TestDateInRegion+Compare.swift │ ├── TestDateInRegion+Components.swift │ ├── TestDateInRegion+Create.swift │ ├── TestDateInRegion+Math.swift │ ├── TestDateInRegion.swift │ ├── TestFormatters.swift │ ├── TestRegion.swift │ └── TestSwiftDate.swift └── generateLinuxTests.sh ================================================ FILE CONTENTS ================================================ ================================================ FILE: .github/FUNDING.yml ================================================ # These are supported funding model platforms custom: https://www.paypal.com/paypalme2/danielemargutti ================================================ FILE: .gitignore ================================================ *~ .DS_Store # Xcode # # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore ## Build generated build/ .build/ DerivedData ## Various settings *.pbxuser !default.pbxuser *.mode1v3 !default.mode1v3 *.mode2v3 !default.mode2v3 *.perspectivev3 !default.perspectivev3 xcuserdata ## Other *.xccheckout *.moved-aside *.xcuserstate *.xcscmblueprint ## Other *.moved-aside *.xccheckout *.xcscmblueprint ## Obj-C/Swift specific *.hmap *.ipa ## Playgrounds timeline.xctimeline playground.xcworkspace # Swift Package Manager # # Add this line if you want to avoid checking in source code from Swift Package Manager dependencies. # Packages/ .build/ # Bundler .bundle # CocoaPods # # We recommend against adding the Pods directory to your .gitignore. However # you should judge for yourself, the pros and cons are mentioned at: # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control # Pods/ # Carthage # # Add this line if you want to avoid checking in source code from Carthage dependencies. # Carthage/Checkouts # Carthage/Build Carthage # fastlane # # It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the # screenshots whenever they are needed. # For more information about the recommended setup visit: # https://github.com/fastlane/fastlane/blob/master/docs/Gitignore.md fastlane/report.xml fastlane/screenshots .DS_ ================================================ FILE: .sourcery/LinuxMain.stencil ================================================ @testable import SwiftDateTests import XCTest // swiftlint:disable line_length file_length {% for type in types.classes|based:"XCTestCase" %} extension {{ type.name }} { static var allTests: [(String, ({{ type.name }}) -> () throws -> Void)] = [ {% for method in type.methods where method.parameters.count == 0 and method.shortName|hasPrefix:"test" and method|!annotated:"skipTestOnLinux" %} ("{{ method.shortName }}", {{ method.shortName }}){% if not forloop.last %},{% endif %} {% endfor %}] } {% endfor %} XCTMain([ {% for type in types.classes|based:"XCTestCase" %} testCase({{ type.name }}.allTests){% if not forloop.last %},{% endif %} {% endfor %}]) ================================================ FILE: .swiftlint.yml ================================================ disabled_rules: # rule identifiers to exclude from running - line_length - function_body_length - cyclomatic_complexity - multiple_closures_with_trailing_closure - xctfail_message - vertical_parameter_alignment - identifier_name # Swift 3 rules that do not make sense for Swift 2.3 - implicit_getter identifier_name: allowed_symbols: "_" min_length: warning: 1 error: 1 max_length: warning: 60 error: 80 excluded: - id type_name: allowed_symbols: "_" max_length: warning: 60 error: 100 type_body_length: warning: 400 error: 500 file_length: warning: 700 error: 800 excluded: - Unit Tests/GeneratedCode - Pods function_parameter_count: warning: 7 error: 10 large_tuple: warning: 3 opt_in_rules: # some rules are only opt-in - closure_end_indentation - closure_spacing - syntactic_sugar - redundant_nil_coalescing - number_separator - sorted_imports - overridden_super_call - object_literal - explicit_init - first_where - operator_usage_whitespace number_separator: minimum_length: 7 custom_rules: double_space: # from https://github.com/IBM-Swift/Package-Builder include: "*.swift" name: "Double space" regex: '([a-z,A-Z] \s+)' message: "Double space between keywords" match_kinds: keyword severity: warning comments_space: # from https://github.com/brandenr/swiftlintconfig name: "Space After Comment" regex: '(^ *//\w+)' message: "There should be a space after //" severity: warning empty_line_after_guard: # from https://github.com/brandenr/swiftlintconfig name: "Empty Line After Guard" regex: '(^ *guard[ a-zA-Z0-9=?.\(\),> ================================================ FILE: .travis.yml ================================================ language: objective-c osx_image: xcode10.2 env: global: - LC_CTYPE=en_US.UTF-8 - LANG=en_US.UTF-8 - PROJECT=SwiftDate.xcodeproj - IOS_FRAMEWORK_SCHEME="SwiftDate-iOS" - MACOS_FRAMEWORK_SCHEME="SwiftDate-macOS" - TVOS_FRAMEWORK_SCHEME="SwiftDate-tvOS" - WATCHOS_FRAMEWORK_SCHEME="SwiftDate-watchOS" matrix: - DESTINATION="OS=12.2,name=iPhone X" SCHEME="$IOS_FRAMEWORK_SCHEME" - DESTINATION="OS=12.2,name=Apple TV 4K" SCHEME="$TVOS_FRAMEWORK_SCHEME" - DESTINATION="arch=x86_64" SCHEME="$MACOS_FRAMEWORK_SCHEME" script: # Ensures that the return code from xcodebuild is passed along to xcpretty - set -o pipefail # Runs the tests in Debug and Release configurations | xcpretty - xcodebuild -project "$PROJECT" -scheme "$SCHEME" -destination "$DESTINATION" -configuration Debug ONLY_ACTIVE_ARCH=NO ENABLE_TESTABILITY=YES test | xcpretty - xcodebuild -project "$PROJECT" -scheme "$SCHEME" -destination "$DESTINATION" -configuration Release ONLY_ACTIVE_ARCH=NO ENABLE_TESTABILITY=YES test | xcpretty ================================================ FILE: Configs/SwiftDate.plist ================================================ CFBundleDevelopmentRegion en CFBundleExecutable $(EXECUTABLE_NAME) CFBundleIdentifier $(PRODUCT_BUNDLE_IDENTIFIER) CFBundleInfoDictionaryVersion 6.0 CFBundleName $(PRODUCT_NAME) CFBundlePackageType FMWK CFBundleShortVersionString $(MARKETING_VERSION) CFBundleSignature ???? CFBundleVersion $(CURRENT_PROJECT_VERSION) NSHumanReadableCopyright Copyright © 2018 Daniele Margutti. All rights reserved. NSPrincipalClass ================================================ FILE: Configs/SwiftDateTests.plist ================================================ CFBundleDevelopmentRegion en CFBundleExecutable $(EXECUTABLE_NAME) CFBundleIdentifier $(PRODUCT_BUNDLE_IDENTIFIER) CFBundleInfoDictionaryVersion 6.0 CFBundleName $(PRODUCT_NAME) CFBundlePackageType BNDL CFBundleShortVersionString 1.0 CFBundleSignature ???? CFBundleVersion 1 ================================================ FILE: Documentation/0.Informations.md ================================================ ![](./SwiftDateArt.png) - [**Index**: Table Of Contents](#Index.md) - [**Next Chapter**: Introduction](#1.Introduction.md) ## Info & Install - [Methods Documentation](0.Informations.md#methods) - [Unit Tests](0.Informations.md#unittest) - [Linux Compatibility](0.Informations.md#linux) - [Future Plans (ToDo List)](0.Informations.md#futureplans) - [Communication](0.Informations.md#communication) - [Requirements](0.Informations.md#compatibility) - [Installation (CocoaPods,Carthage,SwiftPM](0.Informations.md#installation) - [License](0.Informations.md#license) ## Methods Documentation All methods and variables have been documented and are available for option+click inspection, just like the SDK classes. This includes an explanation of the methods as well as what their input and output parameters are for. Please raise an issue if you ever feel documentation is confusing or misleading and we will get it fixed up! ## Unit Tests Unit tests were performed on all the major classes in the library for quality assurance. You can find theses under the "Tests" folder at the top of the library. Currently SwiftDate has ~90$ of code coverage. If you ever find a test case that is incomplete, please open an issue so we can get it fixed. ## Linux Compatibility Since SwiftDate 5.0.13 the package compile successfully on Linux environment with the latest Swift 4.2 snapshot. However there is potential issue with Calendar's compare to granularity method; to know more follow the [issue #596](https://github.com/malcommac/SwiftDate/issues/568) and the radar posted on Swift's group [SR-9101](https://bugs.swift.org/browse/SR-9101). Last Update: 2018-10-28 ## Future Plans (ToDo List) Current plans for 5.5 are: - [x] 100% Code Coverage - [x] Support for Recurrences support (rrule) Any other suggestion is welcomed! ## Requirements SwiftDate 5.x is compatible with Swift 4.x+ and the following platforms: - iOS 9+ - macOS 10.10+ - watchOS 2.0+ - tvOS 10.0+ - any Linux platform which supports Swift 4+ ## Communication - If you **need help**, use [Stack Overflow](http://stackoverflow.com/questions/tagged/swiftdate). (Tag 'swiftdate') - If you'd like to **ask a general question**, use [Stack Overflow](http://stackoverflow.com/questions/tagged/swiftdate). - If you **found a bug**, open an issue. - If you **have a feature request**, open an issue. - If you **want to contribute**, submit a pull request. ## Installation ### CocoaPods [CocoaPods](http://cocoapods.org) is a dependency manager for Cocoa projects. You can install it with the following command: ```bash $ gem install cocoapods ``` > CocoaPods 1.1+ is required to build SwiftDate. To integrate SwiftDate into your Xcode project using CocoaPods, specify it in your `Podfile`: ```ruby source 'https://github.com/CocoaPods/Specs.git' platform :ios, '10.0' use_frameworks! target '' do pod 'SwiftDate', '~> 5.0' end ``` Then, run the following command: ```bash $ pod install ``` ### Carthage [Carthage](https://github.com/Carthage/Carthage) is a decentralized dependency manager that builds your dependencies and provides you with binary frameworks. You can install Carthage with [Homebrew](http://brew.sh/) using the following command: ```bash $ brew update $ brew install carthage ``` To integrate SwiftDate into your Xcode project using Carthage, specify it in your `Cartfile`: ```ogdl github "malcommac/SwiftDate" ~> 6.0 ``` Run `carthage update` to build the framework and drag the built `SwiftDate.framework` into your Xcode project. ### Swift Package Manager The [Swift Package Manager](https://swift.org/package-manager/) is a tool for automating the distribution of Swift code and is integrated into the `swift` compiler. It is in early development, but SwiftDate does support its use on supported platforms. Once you have your Swift package set up, adding SwiftDate as a dependency is as easy as adding it to the `dependencies` value of your `Package.swift`. ```swift dependencies: [ .package(url: "https://github.com/malcommac/SwiftDate.git", from: "5.0.0") ] ``` ## License SwiftDate is released under the MIT license. The MIT License (MIT) Copyright (c) 2018 Daniele Margutti Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: Documentation/1.Introduction.md ================================================ ![](./SwiftDate.png) - [**Index**: Table Of Contents](#Index.md) - [**Prev Chapter**: Informations](#0.Informations.md) - [**Next Chapter**: Parsing Dates](#2.Parsing_Dates.md) ## Introduction to SwiftDate - [1.0 - Dates & Cocoa](1.Introduction.md#datesandcocoa) - [1.1 - Region & DateInRegion](1.Introduction.md#region_dateinregion) - [1.2 - The Default Region](1.Introduction.md#default_region) - [1.3 - Create Region](1.Introduction.md#creating_region) - [1.4 - Create DateInRegion](1.Introduction.md#creating_dateinregion) - [1.4.1 - From String](1.Introduction.md#initfromstring) - [1.4.2 - From Date Components](1.Introduction.md#initfromcomponents) - [1.4.3 - From TimeInterval](1.Introduction.md#initfromtimeinterval) - [1.4.4 - From Date](1.Introduction.md#initfromplaindate) ## 1.0 - Dates & Cocoa Generally when you talk about dates you are brought to think about a particular instance of time in a particular location of the world. However, in order to get a fully generic implementation Apple made `Date` fully independent from any particular geographic location, calendar or locale. A plain `Date` object just represent an absolute value: in fact it count the number of seconds elapsed since January 1, 2001. This is what we call **Universal Time because it represent the same moment everywhere around the world**. You can see absolute time as the moment that someone in the USA has a telephone conversation with someone in Dubai; both have that conversation at the same moment (the absolute time) but the local time will be different due to time zones, different calendars, alphabets or notation methods. ```swift let now = Date() print("\(now.timeIntervalSinceReferenceDate) seconds elapsed since Jan 1, 2001 @ 00:00 UTC") ``` However, we often need to represent a date in a more specific context: **a particular place in the world, printing them using the rules of a specified locale and more**. In order to accomplish it we need to introduce several other objects: a `Calendar`, a `TimeZone` and a `Locale`; combining these attributes we're finally ready to provide a **representation** of the date into the real world. SwiftDate allows you to parse, create, manipulate and inspect dates in an easy and more natural way than Cocoa itself. [^ Top](#introduction) ## 1.1 - Region & DateInRegion In order to simplify the date management in a specific context SwiftDate introduces two simple structs: - `Region` is a struct which define a region in the world (`TimeZone`) a language (`Locale`) and reference calendar (`Calendar`). - `DateInRegion` represent an absolute date in a specific region. When you work with this object all components are evaluated in the context of the region in which the object was created. Inside a DateInRegion you will have an `absoluteDate` and `region` properties. [^ Top](#introduction) ## 1.2 - The Default Region In SwiftDate you can work both with `DateInRegion` and `Date` instances. Even plain Date objects uses `Region` when you need to extract time units, compare dates or evaluate specific operations. However this is a special region called **Default Region** and - by default - it has the following attributes: - **Time Zone** = GMT - this allows to keep a coerent behaviour with the default Date managment unless you change it. - **Calendar** = current's device calendar (auto updating) - **Locale** = current's device locale (auto updating) While it's a good choice to always uses `DateInRegion` you can also work with `Date` by changing the default region as follow: ```swift let rome = Region(calendar: Calendars.gregorian, zone: Zones.europeRome, locale: Locales.italian) SwiftDate.defaultRegion = rome ``` Since now all `Date` instances uses `rome` as default region both for parsing and evaluating date components: ```swift let dateInRome = "2018-01-01 00:00:00".toDate()! print("Current year is \(dateInRome.year) and hour is \(dateInRome.hour)") // "Current year is 2018 and hour is 0\n" ``` We can still convert this date to the default absolute representation in UTC using the `convertTo(region:)` function: ```swift let dateInUTC = dateInRome.convertTo(region: Region.UTC) print("Current year is \(dateInUTC.year) and hour is \(dateInUTC.hour)") // "Current year is 2017 and hour is 23\n" ``` Be careful while setting the default region. We still reccomends to use the `DateInRegion` instances instead, this allows you to read the region explicitly. [^ Top](#introduction) ## 1.3 - Create Region As you seen from previous example creating a new `Region` is pretty straightforward; you need to specify the locale (used to print localized values like month or weekday name of the date), a timezone and a calendar (usually gregorian). Region instances accept the following parameters in form of protocols: - Time zone as `ZoneConvertible` conform object. You can pass both a `TimeZone` instance or any of the predefined timezones region defined inside the `Zones` enumeration. - Calendar as `CalendarConvertible` conform object. You can pass both a `Calendar` instance or any of the predefined calendars available inside the `Calendars` enumeration. - Locale as `LocaleConvertible` conform object. You can pass a both a `Locale` instance or any of the predefined locales available inside the `Locales` enumeration. Using `Zones`, `Calendars` and `Locales` enumeration values is the easiest way to create a region and compiler can also suggest you the best match for your search. The following example create two regions with different attributes: ```swift let regionNY = Region(calendar: Calendars.gregorian, zone: Zones.americaNewYork, locale: Locales.englishUnitedStates) let regionTokyo = Region(calendar: Calendars.gregorian, zone: Zones.asiaTokyo, locale: Locales.japanese) ``` [^ Top](#introduction) ## 1.4 - Create DateInRegion Now you are ready to create a new `DateInRegion`. There are many different ways to create a new date: parsing a string, setting time components, derivating it from another date or from a given time intervals. Each initialization method require a region parameter which defines the region in which the date is expressed (default values may vary based upon the init and are listed below). ### 1.4.1 - From String The most common case is to parse a string and transform it to a date. As you know `DateFormatter` is an expensive object to create and if you need to parse multiple strings you should avoid creating a new instance in your loop. Don't worry: using SwiftDate the library helps you by reusing its own parser, shared along the caller thread. `DateInRegion`'s `init(_:format:region)` can be used to initialize a new date from a string (various shortcut are available under the `toXXX` prefix of `String` extensions. This object takes three parameters: - the `string` to parse (`String`) - the format of the string (`String`): this represent the format in which the string is expressed. It's a unicode format ([See the table of fields](7.Format_UnicodeTable.md)). If you skip this parameter SwiftDate attempts to parse the date using one of the built-in formats defined in `SwiftDate.autoFormats` array. If you know the format of the date you should explicitly set it in order to get better performances. - the `region` in which the date is expressed (`Region`). By default is set to `SwiftDate.defaultRegion`. ```swift let date1 = DateInRegion("2016-01-05", format: "yyyy-MM-dd", region: regionNY) let date2 = DateInRegion("2015-09-24T13:20:55", region: regionNY) ``` ### 1.4.2 - From Components You can create a `DateInRegion` also by setting the date components. The following method create a date from `DateComponents` instance passed via builder pattern: ```swift let date3 = DateInRegion(components: { $0.year = 2018 $0.month = 2 $0.day = 1 $0.hour = 23 }, region: regionNY) ``` You can also instance it by passing single (optional) components: ```swift let date4 = DateInRegion(year: 2015, month: 2, day: 4, hour: 20, minute: 00, second: 00, region: regionNY) ``` ### 1.4.3 - From TimeInterval As plain `Date` you can create a new `DateInRegion` just passing an absolute time interval which represent the seconds/milliseconds from Unix epoch. The following method create a date 1 year after the Unix Epoch (1971-01-01T00:00:00Z): ```swift let date5 = DateInRegion(seconds: 1.years.timeInterval, region: regionNY) let date6 = DateInRegion(milliseconds: 5000, region: regionNY) ``` ### 1.4.4 - From Date Finally you can init a new `DateInRegion` directly specifyng an absolute `Date` and a destination region: ```swift let absoluteDate: Date = (Date() - 2.months).dateAt(.startOfDay) let date7 = DateInRegion(absoluteDate, region: regionNY) ``` [^ Top](#introduction) -- - [**Index**: Table Of Contents](#Index.md) - [**Next Chapter**: Parsing Dates](#2.Parsing_Dates.md) ================================================ FILE: Documentation/10.Upgrading_SwiftDate4.md ================================================ ![](./SwiftDate.png) - [**Index**: Table Of Contents](#Index.md) ### → [Searching for old SwiftDate 4 builds?](https://github.com/malcommac/SwiftDate/milestones) ## Old Versions - for **Swift 3.x**: Latest compatible version is 4.3.0 [Download here](https://github.com/malcommac/SwiftDate/releases/tag/4.3.0). If you are using CocoaPods be sure to fix the release (`pod 'SwiftDate', '~> 4.3.0'`) ## Upgrading from Swift 4.x SwiftDate 5.x is a complete rewrite of the library. While it introduces several new features a great part of the work is about a consistent naming of the functions: some was renamed while deprecated ones was removed. **If you miss a features or you are interested in a new one create a new Issue.** ### Important Note about Default Region In SwiftDate 4.x the default region is automatically set to local region, where all attributes are set automatically to the current device's locale, timezone and calendar. **Since SwiftDate 5, in order to be more comply with `Date`'s default behaviour, the default region's timezone is set to GMT+0 (UTC)**. If you want to restore the old behaviour just set it to `Region.local` just after the launch of the app. ```swift func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool { SwiftDate.defaultRegion = Region.local // set region to local device attributes // ... do something else return true } ``` ### Added/Removed/Replaced Methods - `Date.defaultRegion` was renamed to `SwiftDate.defaultRegion` and you can both read and set it. - `Date.setDefaultRegion(:)` was removed; use `SwiftDate.defaultRegion`'s setter. - `TimeZoneName` is now `Zones` and its conform to the new `ZonesConvertible` protocol. All parameters (like `Region` inits function) takes now a generic `ZonesConvertible` instance: this mean you can pass one of the enum values of the zone or just an instance of `TimeZone` object (or you can conform your own class to it). - `CalendarName` is now `Calendars` and its conform to the new `CalendarConvertible` protocol. All parameters (like `Region` inits function) takes now a generic `CalendarConvertible ` instance: this mean you can pass one of the enum values of the zone or just an instance of `Calendar` object (or you can conform your own class to it). - `LocaleName` is now `Locales` and its conform to the new `LocaleConvertible` protocol. All parameters (like `Region` inits function) takes now a generic `LocaleConvertible ` instance: this mean you can pass one of the enum values of the zone or just an instance of `Calendar` object (or you can conform your own class to it). - `Date/DateInRegion`: `isBetween()` function was renamed `isInRange()` - `Date/DateInRegion`: `isAfter()` was renamed to `isAfterDate()` - `Date/DateInRegion`: `isBefore()` was renamed to `isBeforeDate()` - `String` extension methods `date(format:fromRegion)` and `date(formats:fromRegion)` are now replaced by `toDate(_:region)` (while similar shortcuts methods are added to parse common formats: `toISODate()`, `toDotNETDate()`, `toRSSDate()` and `toSQLDate()`. - Removed `Int` extension `second,minute,hour,day,week,month,year` due to inconsistencies (use plural versions `seconds,minutes` etc. - Related dates generation is now grouped under the `dateAt()` function as enum list. Therefore `nextWeekend`, `previo - `DateInRegion`: `roundedAt()` is now replaced by `dateRoundedAt()` which now compacts paramters in a single enum. - `DateInRegion`: `startOf()` and `endOf()` are now replaced by `dateAtStartOf()` and `dateAtEndOf()`and include support for both single and multiple units. - `DateInRegion`: `atTime()` is now replaced by `dateBySet(hour:min:secs:options)` - `DateInRegion`: `DateInRegion`'s static `distantFuture()` and `distantPast` are now `future()` and `past()` - `DateInRegion`:`next(day:)` is now part of the `dateAt()` function with enum `.weekday(_)`. - `DateInRegion`: `add(components:)` is now replaced by `dateByAdding(_:_:)` - `DateInRegion`: `at(unit:value:)` and `at(values:keep:)` are now replaced by `dateBySet()`. - `nextDSTTransitionDate` property is now part of the enum of `dateAt()` - `recentDate()` and `oldestDate()` are now called `newestIn()` and `oldestIn()` - `TimeInterval` Extension: `in(_:toDate:of:)` is now replaced by `toUnits(_:from:)` and `toUnit(_:from:)` - `TimeInterval` Extension: `string()` is now replaced by `toString()` - `DateTimeInterval` class is now replaced by `TimePeriod` class and its descendants - `DateInRegion`: `dates(between:and:increment:)` to enumerate dates is now replaced by `enumerateDates(from:to:increment:)` - `DateInRegion`: colloquial formatter `colloquial(toDate:options:)` is now replaced by `toRelative(since:style:locale:)` - Formatting method `iso8601()` is now `toISO8601()` and other methods `to..()` was added to simplify formatting task. - `Date/DateInRegion`: Introduced `compareCloseTo()` to compare two dates against a precision value. - Added several shortcut methods as extensions of `String` to parse dates: `toISODate()`, `toDotNETDate()`, `toRSSDate()` and `toSQLDate()`. - Comparison methods are now grouped by `compare()` function and a list of all available enums. -- [**Index**: Table Of Contents](#Index.md) ================================================ FILE: Documentation/11.Related_Projects.md ================================================ ![](./SwiftDate.png) - [**Index**: Table Of Contents](#Index.md) ## Related Projects I'm also working on several other projects you may like. Take a look below:

| Library | Description | |-----------------|--------------------------------------------------| | [**SwiftDate**](https://github.com/malcommac/SwiftDate) | The best way to manage date/timezones in Swift | | [**Hydra**](https://github.com/malcommac/Hydra) | Write better async code: async/await & promises | | [**FlowKit**](https://github.com/malcommac/FlowKit) | A new declarative approach to table managment. Forget datasource & delegates. | | [**SwiftRichString**](https://github.com/malcommac/SwiftRichString) | Elegant & Painless NSAttributedString in Swift | | [**SwiftLocation**](https://github.com/malcommac/SwiftLocation) | Efficient location manager | | [**SwiftMsgPack**](https://github.com/malcommac/SwiftMsgPack) | Fast/efficient msgPack encoder/decoder |

================================================ FILE: Documentation/12.Timer_Periods.md ================================================ ![](./SwiftDate.png)
- [**Index**: Table Of Contents](#Index.md) ## Time Periods *NOTE: The following documentation and part of the implementation is heavely inspired by the original work of Matthew York with its [DateTools](https://github.com/MatthewYork/DateTools/) project.* Dates are important, but the real world is a little less discrete than that. Life is made up of spans of time, like an afternoon appointment or a weeklong vacation. In DateTools, time periods are represented by the TimePeriod class and come with a suite of initializaiton, manipulation, and comparison methods to make working with them a breeze. Time periods consist of a Date start date and end date. ```swift let toDate = DateInRegion().dateAtStartOf(.day) // now at 00:00 let fromDate = toDate - 3.days // 3 days ago at 00:00 let period = TimePeriod(start: fromDate , end: toDate) ``` or, if you would like to create a time period of a known length that starts or ends at a certain time, try out a few other init methods. The method below, for example, creates a time period starting at the current time that is exactly 3 days long. ```swift let _ = TimePeriod(start: DateInRegion(), duration: 3.days) ``` ## Time Period Info A host of methods have been extended to give information about an instance of TimePeriod: - `hasFiniteRange` - true if period has both start and end date - `hasStart`/`hasEnd` - true if period has start/end date - `isMoment()` - Returns true if the period has the same start and end date or the difference is between a defined range. - `durationIn()` - Returns the length of the time period in the requested unit/s ## Manipulation Time periods may also be manipulated. They may be shifted earlier or later as well as expanded and contracted. ### Shifting When a time period is shifted, the start dates and end dates are both moved earlier or later by the amounts requested. To shift a time period earlier, call `. p.shifted(by:)` function with positive or negative components to shift earlier or later in time. ```swift // Create a time period at the start of the current week and end at the end of the week let fromDate = DateInRegion().dateAtStartOf([.weekOfYear,.day]) let toDate = fromDate.dateAtEndOf([.weekOfMonth,.day]) let thisWeek = TimePeriod(start: fromDate, end: toDate) // Shift the period by 7 days in the future let shiftedByOneWeek = thisWeek.shifted(by: 7.days) ``` ### Lengthening/Shortening When a time periods is lengthened or shortened, it does so anchoring one date of the time period and then changing the other one. There is also an option to anchor the centerpoint of the time period, changing both the start and end dates. An example of lengthening a time period is shown below: ```swift let oneMinutePeriod = TimePeriod(end: DateInRegion(), duration: 1.minutes) let lengthed = oneMinutePeriod.lengthened(by: 1.minutes.timeInterval, at: .end) ``` This doubles a time period of duration 1 minute to duration 2 minutes. The end date of "now" is retained and only the start date is shifted 1 minute earlier. ### Relationships There may come a need, say when you are making a scheduling app, when it might be good to know how two time periods relate to one another. Are they the same? Is one inside of another? All these questions may be asked using the relationship methods of TimePeriod. Below is a chart of all the possible relationships between two time periods: ![](TimePeriodRelations.png) A suite of methods have been extended to check for the basic relationships. They are listed below: - `isEqualToPeriod` - `isInside` - `contains` - `overlapsWith` - `intersects` You can also check for the official relationship (like those shown in the chart) with the following method: ```swift let relationship = periodA.relation(to: periodB) ``` This function returns the right value from `TimePeriodRelation` enum. ## Time Period Groups Time period groups are the final abstraction of date and time. Here, time periods are gathered and organized into something useful. There are two main types of time period groups, `TimePeriodCollection` and `TimePeriodChain`. At a high level, think about a collection as a loose group where overlaps may occur and a chain a more linear, tight group where overlaps are not allowed. Both collections and chains operate like an array. You may add, insert and remove `TimePeriod` objects from them just as you would objects in an array. The difference is how these periods are handled under the hood. ### Time Period Collections Time period collections serve as loose sets of time periods. They are unorganized unless you decide to sort them, and have their own characteristics like a StartDate and EndDate that are extrapolated from the time periods within. Time period collections allow overlaps within their set of time periods. ![](TimePeriodCollection.png) To make a new collection, call the class method like so: ```swift // Create collection let collection = TimePeriodCollection() // Create a few time periods let firstPeriod = TimePeriod(start: "2014-11-05 18:15:12".toDate()!, end: "2015-11-05 18:20:12".toDate()!) let secondPeriod = TimePeriod(start: "2014-11-05 18:30:12".toDate()!, end: "2015-11-05 18:35:12".toDate()!) collection.append([firstPeriod,secondPeriod]) ``` Sorting Sorting time periods in a collection is easy, just call one of the sort methods. Sorting can take in place (by modifying the original collection) or can return a derivated collection. You can therefore use: - `sorted(by type: SortType)`: to return a collection with sorted items - `sort(by type: SortType)`: to modify the collection in place both of these methods takes a `SortType` enum which defines the type of sorting you can apply: - `start(_: SortMode)`: sort by start date (`SortMode` can be `.ascending` or `.descending`) - `end(_: SortMode)`: sort by end date (`SortMode` can be `.ascending` or `.descending`) - `duration(_: SortMode)`: sort by duration (`SortMode` can be `.ascending` or `.descending`) - `custom(_: ((TimePeriodProtocol, TimePeriodProtocol) -> Bool))`: sort using custom function Operations It is also possible to check an Date's or TimePeriod's relationship to the collection. For instance, if you would like to see all the time periods that intersect with a certain date, you can call the periodsIntersectedByDate: method. The result is a new TimePeriodCollection with all those periods that intersect the provided date. There are a host of other methods to try out as well, including a full equality check between two collections. ![](TimePeriodCollectionOperations.png) ### Time Period Chains Time period chains serve as a tightly coupled set of time periods. They are always organized by start and end date, and have their own characteristics like a `start` and `end` that are extrapolated from the time periods within. Time period chains do not allow overlaps within their set of time periods. This type of group is ideal for modelling schedules like sequential meetings or appointments. ![](TimePeriodChain.png) To make a new chain, call the class method like so: ```swift ``` Any time a date is added to the time chain, it retains its duration, but is modified to have its StartDate be the same as the latest period in the chain's EndDate. This helps keep the tightly coupled structure of the chain's time periods. Inserts (besides those at index 0) shift dates after insertion index by the duration of the new time period while leaving those at indexes before untouched. Insertions at index 0 shift the start date of the collection by the duration of the new time period. A full list of operations can be seen below. **Operations** Like collections, chains have an equality check and the ability to be shifted earlier and later. Here is a short list of other operations. ![TimePeriodChainOperations](TimePeriodChainOperations.png) ================================================ FILE: Documentation/2.Date_Parsing.md ================================================ ![](./SwiftDate.png) - [**Index**: Table Of Contents](#Index.md) - [**Prev Chapter**: Introduction to SwiftDate](#1.Introduction.md) - [**Next Chapter**: Manipulate & Derivate Dates](#3.Manipulate_Date.md) ## Date Parsing - [2.0 - Parse Custom Format](2.Date_Parsing.md#autoparsing) - [2.1 - Parse ISO8601](2.Date_Parsing.md#iso8601) - [2.2 - Parse .NET](2.Date_Parsing.md#dotnet) - [2.3 - Parse RSS & AltRSS](2.Date_Parsing.md#rssaltrss) - [2.4 - Parse SQL](2.Date_Parsing.md#sql) Related documents: - [Unicode Format for Strings](7.Format_UnicodeTable.md) Parsing dates is pretty straighforward in SwiftDate; library can parse strings with dates automatically by recognizing one of the most common patterns. Moreover you can provide your own formats or use one of the built-in parsers. In the following chapter you will learn how to transform a string to a date. ## 2.0 - Parse Custom Format The easiest way to transform an input string to a valid date is to use one of the `.toDate()` functions available as `String`'s instance extensions. The purpose of these method is to get the best format can represent the input string and use it to generate a valid `DateInRegion`. As like other libs like moment.js, SwiftDate has a list of built-in formats it can use in order to obtain valid results. You can get the list of these formats by calling `SwiftDate.autoFormats`. The order of this array is important because SwiftDate iterates over this list until a valid date is returned (the order itself allows the lib to reduce the list of false positives). You can alter this list by adding/removing or replacing the contents of this array. - `.toDate(_ format: String?, region: Region?)` - `.toDate(_ formats: [String]?, region: Region?)` functions takes as input two arguments: - `format (String|Array)`: it's optional and allows you to set explictly the format (or ordered list of formats) SwiftDate must use to parse the date. Allowed values are listed in Unicode DateTime Table [you can found here](7.Format_UnicodeTable.md). If omitted SwiftDates attempts to parse the string iterating over the list of auto patterns listed in `SwiftDate.autoFormats`. - `region (Region)`: describe the region (locale/calendar/timezone) in which the date is expressed. If omitted the default region (`SwiftDate.defaultRegion`) is used istead. The result of these functions is an optional `DateInRegion` instance (`nil` is returned if parsing fails). Some examples: ```swift let _ = "2018-01-01 15:00".toDate() let _ = "15:40:50".toDate("HH:mm:ss") let _ = "2015-01-01 at 14".toDate("yyyy-MM-dd 'at' HH", region: rome) // Support for locale let itRegion = Region(calendar: Calendars.gregorian, zone: Zones.europeRome, locale: Locales.italian) let enRegion = Region(calendar: Calendars.gregorian, zone: Zones.europeRome, locale: Locales.english) let srcString = "July 15 - 15:30" // it returns nil because itRegion has Locales.italian let _ = srcString.toDate(["yyyy-MM-dd","MMM dd '-' HH:mm"], region: itRegion) // it's okay because enRegion has locale set to english let _ = srcString.toDate(["yyyy-MM-dd","MMM dd '-' HH:mm"], region: enRegion) ``` > **PERFORMANCES** In order to preserve performances you should pass the `format` parameter if you know the input format. > **LOCALE PARAMETER** If you use readable unit names (like `MMM` for months) be sure to select the right locale inside the `region` parameter in order to get valid results. [^ Top](#index) ## 2.1 Parse ISO8601 A special note must be made for ISO8601. This format (the extended version and all its variants) may include the timezone information. If you need to parse an ISO8601 datetime you should therefore use the `.toISODate()` function of `String` in order to get a complete result. > **NOTE** ISO8601 parser (via `.toISODate()` func) is capable of recognizing all the variants of the 8601 formats; if your date is in this formt use this function instead of passing custom time format. It will lead in better results. The following function: `func toISODate(_ options: ISOParser.Options?, region: Region) -> DateInRegion?` takes 2 arguments: - `options | ISOParserOptions` allows you to customize some parser attributes: `timeSeparator` (by default is `:`), `calendar` (by default is the gregorian calendar) and the strict parsing behaviour (by default is `false`). Usually you can omit this parameter. - `region | Region` used to define the region of the date. By default is set to `Region.ISO` (gregorian/gmt/english posix). If parsed string also contains `timezone` it will override the parameter passed by the region. You can use `convertTo(region:)` to perform any additional conversion. Examples: ```swift let date = "2017-08-05T16:04:03+02:00".toISODate(region: Region.ISO)! // returned date's region.zone is GMT+2 not the default's Region.ISO's GMT0. // This because value is read from the string itself. ``` [^ Top](#index) ## 2.2 - Parse .NET CSOM DateTime (aka .NET DateTime) is a format defined by Microsoft as the number of 100-nanosecond intervals that have elapsed since 12:00 A.M., January 1, 0001 ([learn more on MSDN documentation page](https://msdn.microsoft.com/en-us/library/dd948679)). You can parse a CSOM datetime string using the `toDotNETDate()` function. > **NOTE:** As for ISO8601 even .NET datetime may contain information about timezone. When you set the region as input parameter of the conversion function remember: it will be overriden by default parsed timezone (GMT if not specified). Region is used for `locale` only. `func toDotNETDate(region: Region = SwiftDate.defaultRegion) -> DateInRegion?` takes a single parameter: - `region | Region`: the region in which the date is represented (only `locale` parameter is used). If you omit this parameter the `SwiftDate.ISO` is used instead. You can use `convertTo(region:)` to perform any additional conversion. Example: ```swift // This is the 2017-07-22T18:27:02+02:00 date. let _ = "/Date(1500740822000+0200)/".toDotNETDate() ``` [^ Top](#index) ## 2.3 - Parse RSS & AltRSS RSS & AltRSS datetime format are used in RSS feed files. Parsing in SwiftDate is pretty easy; just call the `.toRSSDate()` function. > **NOTE:** As for ISO8601 even RSS/AltRSS datetime contain information about timezone. When you set the region as input parameter of the conversion function remember: it will be overriden by default parsed timezone (GMT if not specified). Region is used for `locale` only. `func toRSSDate(alt: Bool, region: Region = Region.ISO) -> DateInRegion?` takes two arguments: - `alt: Bool`: set to `false` if represented string is RSS standard format, `true` to use the Alt-RSS parser. - `region: Region`: region in which the date is expressed. Only the `locale` parameter is used, `timezone` is read from string and calendar is gregorian. If you omit this parameter the `SwiftDate.ISO` is used instead. You can use `convertTo(region:)` to perform any additional conversion. ```swift // This is the ISO8601: 2017-07-22T18:27:02+02:00 let _ = "Sat, 22 Jul 2017 18:27:02 +0200".toRSSDate(alt: false)! let _ = "22 Jul 2017 18:27:02 +0200".toRSSDate(alt: true)! // NOTE: // Even if we set a random region with a custom locale, // calendar and timezone, final parsed date still correct. // Only the locale parameter is set. // Other region's parameter are ignored and read from the string itself. let regionAny = Region(calendar: Calendars.buddhist, zone: Zones.indianMayotte, locale: Locales.italian) let date1 = "Tue, 20 Jun 2017 14:49:19 +0200".toRSSDate(alt: false, region: regionAny) ``` [^ Top](#index) ## 2.4 - Parse SQL SQL datetime is the format used in all SQL-compatible schemas. You can parse a string in this format using `toSQLDate()` function. `func toSQLDate(region: Region = Region.ISO) -> DateInRegion?` takes one argument: - `region: Region`: region in which the date is expressed. Only the `locale` parameter is used, `timezone` is read from string and calendar is gregorian. If you omit this parameter the `SwiftDate.ISO` is used instead. You can use `convertTo(region:)` to perform any additional conversion. Example: ```swift // Date in ISO is 2016-04-14T11:58:58+02:00 let _ = "2016-04-14T11:58:58.000+02".toSQLDate() ``` [^ Top](#index) -- [**Next Chapter**: Manipulate & Derivate Dates](#3.Manipulate_Date.md) ================================================ FILE: Documentation/3.Manipulate_Date.md ================================================ ![](./SwiftDate.png) - [**Index**: Table Of Contents](#Index.md) - [**Prev Chapter**: Date Parsing](#3.Date_Parsing.md) - [**Next Chapter**: Compare Dates](#4.CompareDates.md) ## Manipulate & Derivate Dates - [3.0 - Add & Subtract Time Units from Date](3.Manipulate_Date.md#mathdate) - [3.1 - Get DateTime Components](3.Manipulate_Date.md#datecomponents) - [3.2 - Get Interval Between Dates](3.Manipulate_Date.md#interval) - [3.3 - Convert Date's Region (Locale/TimeZone/Calendar)](3.Manipulate_Date.md#convert) - [3.4 - Rounding Date](3.Manipulate_Date.md#roundingdate) - [3.5 - Truncating Date](3.Manipulate_Date.md#truncatingdate) - [3.6 - Set Time in Date](3.Manipulate_Date.md#altertimedate) - [3.7 - Set DateTime Components](3.Manipulate_Date.md#altercomponents) - [3.8 - Generate Related Dates (`nextYear, nextWeeekday, startOfMonth, startOfWeek, prevMonth`...)](3.Manipulate_Date.md#relateddates) - [3.9 - Date at start/end of time component](3.Manipulate_Date.md#startendcomponent) - [3.10 - Enumerate Dates](3.Manipulate_Date.md#enumeratedates) - [3.11 - Enumerate Dates for Weekday in Range](3.Manipulate_Date.md#enumerateweekdays) - [3.12 - Random Dates](3.Manipulate_Date.md#randomdates) - [3.13 - Sort Dates](3.Manipulate_Date.md#sort) - [3.14 - Get the next weekday](3.Manipulate_Date.md#nextWeekDay) - [3.15 - Get date at given week number/weekday](3.Manipulate_Date.md#dateAtWeeknumberWeekday) - [3.16 - Difference between dates with components](3.Manipulate_Date.md#differenceBetweenDates) - [3.17 - Create date at components preserving small components](3.Manipulate_Date.md#dateAtComponents) - [3.18 - Date at given weekday after # weeks](3.Manipulate_Date.md#dateAfterWeeks) - [3.19 - Next Date](3.Manipulate_Date.md#nextDate) Dates can be manipulated as you need by using classic math operators and readable time units. ## 3.0 - Add & Subtract Time Units from Date SwiftDate allows you to use numbers to work with time components in dates. By extending the `Int` type it defines a list of time units: - `nanoseconds` - `seconds` - `minutes` - `days` - `weeks` - `months` - `quarters` - `years` You can use a value followed by one of these unit specifications to add or remove the component from a date. So, for example, you can produce a date which can be a mix of intuitive math operations: ```swift let oneYearAhead = DateInRegion() + 1.years let someMinutesAgo = date1 - 2.minutes let fancyDate = date1 + 3.hours - 5.minutes + 1.weeks ``` > IMPORTANT NOTE: These values are converted automatically to `DateComponents` evaluated in the same context of the target `Date` or `DateInRegion`'s `calendar`. Another way to add time components to a date is to use the `dateByAdding()` function: `func dateByAdding(_ count: Int, _ component: Calendar.Component) -> DateInRegion` takes two arguments: - `count | Int` value to add (maybe negative) - `component | Calendar.Component` the time unit components to add. > IMPORTANT NOTE: New date is evaluated in the same context of the target `Date` or `DateInRegion`'s `calendar`. ```swift let nextDate = dateA.dateByAdding(5, .years) // 5 years from dateA ``` [^ Top](#index) ## 3.1 - Get DateTime Components Components With SwiftDate you have several convenience properties to inspect each datetime unit of a date, both for `Date` and `DateInRegion`. These properties are strictly correlated to the date's calendar (and some also with locale): if you are manipulating a `DateInRegion` remember these properties return values in the context of the associated `region` attributes (Locale, TimeZone and Calendar). > **IMPORTANT NOTE**: If you are working with plain `Date` properties uses as reference the currently set `SwiftDate.defaultRegion` which, unless you modify it, is set to Gregorian/UTC/Device's Language. This a complete list of the properties you can inspect for a date object: | PROPERTY | DESCRIPTION | |-------------------------------------------------------------------------------------------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | `year` | current year number | | `month` | current month number (1 is January) | | `monthName(_ style: SymbolFormatStyle)` | name of the current month with given style (uses region's locale) | | `monthDays` | number of the days into the current month | | `day` | day number in the current month | | `dayOfYear` | day of the year | | `ordinalDay` | The number of day in ordinal style format for the receiver in current locale. | | `hour` | current hour | | `nearestHour` | nearest rounded hour | | `minute` | current minute | | `second` | current second | | `nanosecond` | current nanosecond | | `msInDay` | "Milliseconds in day of the receiver. This field behaves exactly like a composite of all time-related fields, not including the zone fields.As such, it also reflects discontinuities of those fields on DST transition days. | | On a day of DST onset, it will jump forward. On a day of DST cessation, it will jump backward. | | | This reflects the fact that is must be combined with the offset field to obtain a unique local time value." | | | `weekday` | Weekday unit of the receiver. The weekday units are the numbers 1-N (where for the Gregorian calendar N=7 and 1 is Sunday). | | `weekdayName(_ style: SymbolFormatStyle)` | Name of the weekday expressed in given format style. | | `weekOfYear` | Week of a year of the receiver. | | `weekOfMonth` | Week of a month of the receiver. | | `weekdayOrdinal` | Ordinal position within the month unit of the corresponding weekday unit. | | `firstDayOfWeek` | Return the first day number of the week where the receiver date is located | | `lastDayOfWeek` | Return the last day number of the week where the receiver date is located. | | `yearForWeekOfYear` | Relative year for a week within a year calendar unit. | | `quarter` | Quarter value of the receiver. | | `quarterName(_ style: SymbolFormatStyle)` | Quarter name expressed in given format style | | `era` | Era value of the receiver. | | `eraName(_ style: SymbolFormatStyle)` | Name of the era expressed in given format style | | `DSTOffset` | The current daylight saving time offset of the represented date | Several other properties defines additional attributes of the date: - `date | Date`: return the absolute date instance (for `Date` it just return itself) - `region | Region`: return the region associated with date (for `Date` it return `SwiftDate.defaultRegion`). - `calendar | Calendar`: return the associated calendar - `dateComponents | DateComponents`: return all the date components of the date in the context of its associated region. [^ Top](#index) ## 3.2 - Get Interval Between Dates You can get the interval between two dates and express it in form of time units easily with SwiftDate. The `.getInterval(toDate:component:)` function allows you to express the difference between to dates in form of a passed time component. `func getInterval(toDate: DateInRegion?, component: Calendar.Component) -> Int64` Takes two arguments: - `toDate | DateInRegion`: reference date to compare against - `component | Calendar.Component`: the component in which the difference must be returned. Examples: ```swift let dateA = DateInRegion("2017-07-22 00:00:00", format: format, region: rome)! let dateB = DateInRegion("2017-07-23 12:00:00", format: format, region: rome)! let hours = dateA.getInterval(toDate: dateB, component: .hour) // 36 hours let days = dateA.getInterval(toDate: dateB, component: .day) // 1 day ``` [^ Top](#index) ## 3.3 - Convert Date's Region (Locale/TimeZone/Calendar) `DateInRegion` can be converted easily to another region just using `.convertTo(region:)` or `.convertTo(calendar: timezone:locale:)` functions. - `convertTo(region:)` convert the receiver date to another region. Region may include a different time zone for example, or a locale. - `convertTo(calendar:timezone:locale:)` allows to convert the receiver date instance to a specific calendar/timezone/locale. All parameters are optional and only non-nil parameters alter the final region. For a nil param the current receiver's region attribute is kept. Examples: ```swift // Create a date in NY: "2001-09-11 12:00:05" let regionNY = Region(calendar: Calendars.gregorian, zone: Zones.americaNewYork, locale: Locales.english) let dateInNY = DateInRegion(components: { $0.year = 2001 $0.month = 9 $0.day = 11 $0.hour = 12 $0.minute = 0 $0.second = 5 }, region: regionNY) // Convert to GMT timezone (and locale) let inGMT = dateInNY.convertTo(region: Region.UTC) // date is now: "2001-09-11 16:00:05" ``` [^ Top](#index) ## 3.4 - Rounding Date `.dateRoundedAt()` function allows to round a given date time to the passed style: off, up or down. `func dateRoundedAt(at style: RoundDateMode) -> DateInRegion` takes a single argument: - `style | RoundDateMode`: define the style of rounding: it can be `off`, `ceil` (up) or `floor` (down). `RoundDateMode` defines a list of convenience rounding to 5,10,30 minutes but you can use `.toMins()`, `.toCeilMins()` or `.toFloorMins()` and pass your own rounding values expressed in minutes. Example: ```swift let rome = Region(...) let format = "yyyy-MM-dd HH:mm:ss" // Round down 10mins let date = DateInRegion("2017-07-22 00:03:50", format: format, region: rome) let r10min = date.dateRoundedAt(.to10Mins) // 2017-07-22T00:00:00+02:00 // Round up 30min let r30min = "2015-01-24 15:07:20".toDate(format: format, region: rome).dateRoundedAt(.toCeil30Mins) // 2015-01-24T15:30:00+01:00 ``` [^ Top](#index) ## 3.5 - Truncating Date Sometimes you may need to truncate a date by zeroing all values below certain time unit. `.dateTruncated(from:) and .dateTruncated(to:)` functions can be used for this scope. #### Truncating From It creates a new instance by truncating the components starting from given components down the granurality. `func dateTruncated(from component: Calendar.Component) -> DateInRegion?` #### Truncated At It creates a new instance by truncating all passed components. `func dateTruncated(at components: [Calendar.Component]) -> DateInRegion?` Examples: ```swift let rome = Region(calendar: Calendars.gregorian, zone: Zones.europeRome, locale: Locales.italian) let date = "2017-07-22 15:03:50".toDate("yyyy-MM-dd HH:mm:ss", region: rome) let truncatedTime = date.dateTruncated(from: .hour) // 2017-07-22T00:00:00+02:00 let truncatedCmps = date.dateTruncated(at: [.month, .day, .minute]) // 2017-01-01T15:00:50+01:00 ``` [^ Top](#index) ## 3.6 - Set Time in Date Sometimes you may need to alter time in a specified date. SwiftDate allows you to perform this using `.dateBySet(hour:min:secs:options:)` function. `func dateBySet(hour: Int?, min: Int?, secs: Int?, ms: Int?, options: TimeAlterOptions = TimeAlterOptions()) -> DateInRegion?` takes five arguments: - `hour | Int?`: set a non `nil` value to change the hour value - `min | Int?`: set a non `nil` value to change the minute value - `secs | Int?`: set a non `nil` value to change the seconds value - `ms | Int?`: set a non `nil` value to change the milliseconds value - `options: TimeCalculationOptions`: allows to specify calculation options attributes (usually you don't need to set it). Example: ```swift let rome = Region(calendar: Calendars.gregorian, zone: Zones.europeRome, locale: Locales.italian) let date = DateInRegion("2010-01-01 00:00:00", format: "yyyy-MM-dd HH:mm:ss", region: rome)! let alteredDate = date.dateBySet(hour: 20, min: 13, secs: 15) // 2010-01-01 20:13:15 ``` [^ Top](#index) ## 3.7 - Set DateTime Components SwiftDate allows you to return a new date representing the date calculated by setting specific components of a given date to given values, while trying to keep lower components the same (altering more components at the same time may result in different-than-expected results, this is because lower components maybe need to be recalculated). `dateBySet(_ components: [Calendar.Component: Int]) -> DateInRegion?` Takes one argument: - `components | [Calendar.Component: Int]` a dictionary of key/values for each component you want to alter. > **NOTE:** While the algorithm try to keep lower compoments the same, the resulting date may differ from what expected when you alter more than a component a time. Example: ```swift let _ = date.dateBySet([.month: 1, .day: 1, .hour: 9, .minute: 26, .second: 0]) ``` [^ Top](#index) ## 3.8 - Generate Related Dates (`nextYear, nextWeeekday, startOfMonth, startOfWeek, prevMonth`...) Sometimes you may need to generate a related date from a specified instance; maybe the next sunday, the first day of the next week or the start datetime of a date. SwiftDate includes 20+ different "interesting" dates you can obtain by calling `.dateAt()` function from any `Date` or `DateInRegion` instance. `func dateAt(_ type: DateRelatedType) -> DateInRegion` takes just an argument which defines the type of date you want to obtain starting from the receiver date. `DateRelatedType` is an enum which has the following options: - `startOfDay` - `endOfDay` - `startOfWeek` - `endOfWeek` - `startOfMonth` - `endOfMonth` - `tomorrow` - `tomorrowAtStart` - `yesterday` - `yesterdayAtStart` - `nearestMinute(minute:Int)` - `nearestHour(hour:Int)` - `nextWeekday(_: WeekDay)` - `nextDSTDate` - `prevMonth` - `nextMonth` - `prevWeek` - `nextWeek` - `nextYear` - `prevYear` > **CONTRIBUTE!** Have you a new related date you want to be part of this list? Create a [new PR](https://github.com/malcommac/SwiftDate/compare) with the code and unit tests and we'll be happy to add it to the list! Examples: ```swift // Return today's datetime at 00:00:00 let _ = DateInRegion().dateAt(.startOfDay) // Return today's datetime at 23:59:59 let _ = DateInRegion().dateAt(.endOfDay) // Return the date at the start of this week let _ = DateInRegion().dateAt(.startOfWeek) // Return current time tomorrow let _ = DateInRegion().dateAt(.tomorrow) // Return the next sunday from specified date let _ = date.dateAt(.nextWeekday(.sunday)) // and so on... ``` [^ Top](#index) ## 3.9 - Date at start/end of time component Two functions called `.dateAtStartOf()` and `.dateAtEndOf()` allows you to get the related date from a `Date`/`DateInRegion` instance moved at the start or end of the specified component. You can, for example, get the date at the start of the week, or the year, or a the end of the quarter. - `func dateAtStartOf(_ unit: Calendar.Component) -> DateInRegion`: return the date at the start of the specified time component. - `func dateAtEndOf(_ unit: Calendar.Component) -> DateInRegion`: return the date at the end of the specified time component. Examples: ```swift // Return today's date at 23:59:59 let _ = DateInRegion().dateAtEndOf(.day) // Return the first day's date of the month described in date1 let _ = date1.dateAtStartOf(.month) // Return the first day of this year at 00:00:00 let _ = DateInRegion().dateAtStartOf(.year) ``` [^ Top](#index) ## 3.10 - Enumerate Dates Dates enumeration function allows you to generate a list of dates in a closed date intervals incrementing date components by a fixed or variable interval at each new date. - **VARIABLE INCREMENT** `static func enumerateDates(from startDate: DateInRegion, to endDate: DateInRegion, increment: ((DateInRegion) -> (DateComponents))) -> [DateInRegion]` - **FIXED INCREMENT** `static func enumerateDates(from startDate: DateInRegion, to endDate: DateInRegion, increment: DateComponents) -> [DateInRegion]` Both of these functions are pretty similar; it takes: - `startDate | DateInRegion`: the initial date of the enumeration (it will be item #0 of the final array) - `endDate | DateInRegion`: the upper bound limit date. The last item of the array is evaluated automatically and maybe not equal to `endDate`. - `increment | DateComponents or Function`: for fixed increment it takes a `DateComponents` instance which define the increment of time components at each new date; for variable it provides the latest date generated and require as return object of the closure the increment as `DateComponents`. Examples: ```swift let increment = DateComponents.create { $0.hour = 1 $0.minute = 30 } // Generate an array of dates where the first item is fromDate // and each new date is incremented by 1h30m from the previous. // Latest date is < endDate (but maybe not the same). let dates = DateInRegion.enumerateDates(from: fromDate, to: toDate, increment: increment) ``` [^ Top](#index) ## 3.12 - Enumerate Dates for Weekday in Range The following function allows you to enumerate all dates of a particular weekdays which are in a determinated date range. Both `Date` and `DateInRegion` implements this static function: ```swift public static func datesForWeekday(_ weekday: WeekDay, from startDate: Date, to endDate: Date, region: Region = SwiftDate.defaultRegion) -> [Date] ``` Another shortcut method allows you to get weekdays in a particular month of a year: ```swift public static func datesForWeekday(_ weekday: WeekDay, inMonth month: Int, ofYear year: Int, region: Region = SwiftDate.defaultRegion) -> [Date] ``` Examples: ```swift // Get all mondays in Jan 2019 let mondaysInJan2019 = Date.datesForWeekday(.monday, inMonth: 1, ofYear: 2019) // You will get 4 results // - 2019-01-07T00:00:00Z // - 2019-01-14T00:00:00Z // - 2019-01-21T00:00:00Z // - 2019-01-28T00:00:00Z // Get all fridays between May 27, 2019 and June 8, 2019 let fromDate = Date(year: 2019, month: 5, day: 27, hour: 0, minute: 0) let toDate = Date(year: 2019, month: 6, day: 8, hour: 0, minute: 0) let fridaysInJunePartial = Date.datesForWeekday(.friday, from: fromDate, to: toDate, region: Region.UTC) // You will get 2 results: // - 2019-05-31T00:00:00Z // - 2019-06-07T00:00:00Z ``` ## 3.12 - Random Dates SwiftDate exposes a set of functions to generate a random date or array of random dates in a bounds. There are several functions to perform this operation: ### Single Random Date - `randomDate(region:)` generate a random date into the specified region. - `randomDate(withinDaysBeforeToday:region:)` generate a random date between now and a specified amount days ealier into the specified region. - `randomDate(between:and:region:)` generate a random date into the specified region between two given date bounds. ### Array of Random Dates - `randomDates(count:between:and:region:)` return `count` random generated dates into the specified region between two given date bounds. > **IMPORTANT**: For all of thes function if you don't specify the `region`, `SwiftDate.defaultRegion` is used instead. Examples: ```swift // Generate random dates array in limit let now = DateInRegion() let someYearsAgo = (upperBound - 3.years) // generate 40 random dates between 3 years ago today and today let randomDates = DateInRegion.randomDates(count: 40, between: someYearsAgo, and: now) // Generate a random date between now and 7 days ago let rome: Region = ... let aDate = DateInRegion.randomDate(withinDaysBeforeToday: 7, region: rome) ``` [^ Top](#index) ## 3.13 - Sort Dates Two conveniences function allows you to sort an array of dates by newest or oldest. Naming is pretty simple: - `sortedByOldest()`: sort dates by the oldest - `sortedByNewest()`: sort dates by the newest Two other functions allows you to get the oldest/newest date in array: - `oldestIn()`: return the oldest date in array - `newestIn()`: return the newest date in array Examples: ```swift let arrayOfDates: [DateInRegion] = [...] // ordered array with the newest on top let orderedByNewest = DateInRegion.sortedByNewest(list: datesArray) // get the oldest date of the list let oldestDate = DateInRegion.oldestIn(list: arrayOfDates) ``` [^ Top](#index) ## 3.14 - Get the next weekday In order to get the next weekday preserving smaller components (hour, minute, seconds) you can use the `nextWeekday()` function: ```swift let date1 = DateInRegion("2019-05-11 00:00:00", format: dateFormat, region: regionRome)! let nextFriday = date1.nextWeekday(.friday) // 2019-05-17T00:00:00+02:00 ``` [^ Top](#index) ## 3.15 - Get date at given week number/weekday To returns the date at the given week number and week day preserving smaller components (hour, minute, seconds) you can use the `dateAt(weekdayOrdinal:weekday:monthNumber:yearNumber:)` function: ```swift let date = DateInRegion("2019-05-11 00:00:00", format: dateFormat, region: regionRome)! let _ = date1.dateAt(weekdayOrdinal: 3, weekday: .friday, monthNumber: date1.month + 1) // 2019-06-21T00:00:00+02:00 ``` ## 3.16 - Difference between dates with components To get differences between two dates using given time components you can choose between two methods: - `difference(in:from:)`: Returns the difference in the calendar component given (like day, month or year) with respect to the other date as a positive integer. - `differences(in:from:)`: 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. Example: ```swift let format = "yyyy-MM-dd HH:mm" let d1 = "2019-01-01 00:00".toDate(format, region: Region.current) let d2 = "2019-01-10 14:20".toDate(format, region: Region.current) let diff = d1!.differences(in: [.day, .hour], from: d2!) // 9 days, 14 hours ``` [^ Top](#index) ## 3.17 - Create date at components preserving small components You can create a derivated date from a receiver by preserving small components (hour, minute and second) by using the: `dateAt(dayOfMonth:monthNumber:yearNumber:)` method: ```swift let date = "2019-01-01 00:00".toDate("yyyy-MM-dd HH:mm", region: Region.current)! // Rome zone let onMarch = date.dateAt(dayOfMonth: 31, monthNumber: 3) // 2019-03-31T00:00:00+01:00 ``` [^ Top](#index) ## 3.18 - Date at given weekday after # weeks Returns the date after given number of weeks on the given day of week using `dateAfter(weeks:on:)` function. ```swift let d = "2019-09-14 00:00".toDate("yyyy-MM-dd HH:mm", region: Region.current)! // Rome Region let nextTwoMondays = d.dateAfter(weeks: 2, on: .monday) // 2019-09-23T00:00:00+02:00 ``` [^ Top](#index) ## 3.19 - Next Date There are 3 functions to get the next date starting from a receiver date: - `nextWeekday()`: Returns the next weekday preserving smaller components - `nextWeekday(:withWeekOfMonth:andMonthNumber)`: Returns next date with the given weekday and the given week number - `next(dayOfMonth:monthOfYear:)`: Returns the next day of month preserving smaller components (hour, minute, seconds) [**Next Chapter**: Compare Dates](#4.CompareDates.md) ================================================ FILE: Documentation/4.Compare_Dates.md ================================================ ![](./SwiftDate.png) - [**Index**: Table Of Contents](#Index.md) - [**Prev Chapter**: Manipulate & Derivate Dates](#3,Manipulate_Dates.md) - [**Next Chapter**: Date Formatting](#5.Date_Formatting.md) ## Compare Dates Date comparison is available both via simple math operators (`<,>,<=,>=`) or throught several other functions which allows a more fined grained control of the comparison. - [4.0 - Compare Dates](4.Compare_Dates.md#standard) - [4.1 - Extended Comparison with Presets (`isToday, isTomorrow, isSameWeek, isNextYear` etc.)](4.Compare_Dates.md#extended) - [4.2 - Comparison with Granularity](4.Compare_Dates.md#granularity) - [4.3 - Check if Date is Close to Another](4.Compare_Dates.md#close) - [4.4 - Check if Date is Inside another](4.Compare_Dates.md#isinside) - [4.5 - Check if Date is Inside a Range](4.Compare_Dates.md#range) ## 4.0 - Compare Dates Standard comparison between dates can be done using the classic `.compare()` functions or mathematical operators. SwiftDate also introduces two additional convenience methods: - `isBeforeDate(_:orEqual:granularity:)` Compares whether the receiver is before/before equal `date` based on their components down to a given unit granularity. - `isAfterDate(_:orEqual:granularity:)` Compares whether the receiver is after `date` based on their components down to a given unit granularity. ## 4.1 - Extended Comparison with Presets While standard comparison between two dates can be done by using mathematical operators, extended comparison is made via `.compare()` method which offer more than 25+ different types of relevant comparisons. `func compare(_ compareType: DateComparisonType) -> Bool` takes only one argument: - `compareType | DateComparisonType`: the type of comparison to make `DateComparisonType` is an enum which defines the type of comparison to make. This is the actual list of compare functions you can use: **For Days** - `isToday` - `isTomorrow` - `isYesterday` - `isSameDay(_ : DateRepresentable)` **For Weeks** - `isThisWeek` - `isNextWeek` - `isLastWeek` - `isSameWeek(_: DateRepresentable)` **For Months** - `isThisMonth` - `isNextMonth` - `isLastMonth` - `isSameMonth(_: DateRepresentable)` **For Years** - `isThisYear` - `isNextYear` - `isLastYear` - `isSameYear(_: DateRepresentable)` **For Relative Time** - `isInTheFuture` - `isInThePast` - `isEarlier(than: DateRepresentable)` - `isLater(than: DateRepresentable)` - `isWeekday` - `isWeekend` **For Day Time** - `isMorning` - `isAfternoon` - `isEvening` - `isNight` **For TZ** - `isInDST` > **CONTRIBUTE!** Have you a new related date you want to be part of this list? Create a [new PR](https://github.com/malcommac/SwiftDate/compare) with the code and unit tests and we'll be happy to add it to the list! Examples: ```swift // return false let _ = DateInRegion().dateAt(.endOfDay).compare(.isTomorrow) // return true let _ = DateInRegion() + 7.days).compare(.isNextWeek) // return true let _ = DateInRegion().dateAt(.startOfWeek) - 1.days).compare(.isLastWeek) ``` ## 4.2 - Comparison with Granularity A more fined grained control for dates comparison can be obtained using the `.compare(toDate:granularity:)` function which offers to return a `ComparisonResult` value that indicates the ordering of two given dates based on their components down to a given unit granularity. `func compare(toDate refDate: DateInRegion, granularity: Calendar.Component) -> ComparisonResult` takes two arguments: - `refDate | DateInRegion`: date to compare against to. - `granularity | Calendar.Component`: The smallest unit that must, along with all larger units, be less for the given dates Example: ```swift ``` [^ Top](#index) ## 4.3 - Check if Date is Close to Another Decides whether a Date is "close by" another one passed in parameter, where "Being close" is measured using a precision argument which is initialized with a 300 second interval (5 minute) or a specified interval. The function is called `.compareCloseTo(_:precision:)` and takes two arguments: - `refDate | DateInRegion` reference date compare against to - `precision | TimeInterval` The precision of the comparison. Examples: ```swift let date = DateInRegion("2015-01-01 04:00:00", format: dateFormat, region: regionRome)! let refDate = DateInRegion("2015-01-01 00:00:00", format: dateFormat, region: regionRome)! // return true because prevision is set to 5 hours and date differs for only 4 hours let _ = dateC.compareCloseTo(refDate, precision: 5.hours.timeInterval) ``` ## 4.4 - Check if Date is Inside another Compares equality of two given dates based on their components down to a given unit granularity. The function is called `.isInside(date:granularity:)` and takes two arguments: - `date`: date to compare - `granularity`: The smallest unit that must, along with all larger units, be equal for the given dates to be considered the same. ## 4.5 - Check if Date is Inside a Range Using `.isInRange()` function you can check if a given date is inside the range between two dates. `func isInRange(date startDate: Date, and endDate: Date, orEqual: Bool = false, granularity: Calendar.Component = .nanosecond) -> Bool` takes 4 arguments: - `startDate`: the lower bound limit of the range - `endDate`: the upper bound limit of the range - `orEqualt`: true to also validate the equality - `granularity`: smallest unit that must, along with all larger units, be greater for the given dates Example: ```swift let lowerBound = DateInRegion("2018-05-31 23:00:00", format: dateFormat, region: regionRome)! let upperBound = DateInRegion("2018-06-01 01:00:00", format: dateFormat, region: regionRome)! let testDate = DateInRegion("2018-06-01 00:02:00", format: dateFormat, region: regionRome)! // return true, date is inside the hour granularity let _ = testDate.isInRange(date: lowerBound, and: upperBound, orEqual: true, granularity: .hour) ``` [^ Top](#index) [**Next Chapter**: Date Formatting](#5.Date_Formatting.md) ================================================ FILE: Documentation/5.Date_Formatting.md ================================================ ![](./SwiftDate.png) - [**Index**: Table Of Contents](#Index.md) - [**Prev Chapter**: Parsing Dates](#Parsing_Dates.md) - [**Next Chapter**: Time Interval Formatting](#6.TimeInterval_Formatting.md) ## Date Formatting Formatting dates and transforms to string representation is really easy with SwiftDate. All the major formats are supported and are really easy to configure. - [5.0 - Format Custom Style](5.Date_Formatting.md#customformatted) - [5.1 - ISO8601 Formatted String](5.Date_Formatting.md#isoformatted) - [5.2 - .NET Formatted String](5.Date_Formatting.md#dotnet) - [5.3 - RSS/AltRSS Formatted String](5.Date_Formatting.md#rss) - [5.4 - SQL Formatted String](5.Date_Formatting.md#sql) - [5.5 - Relative/Colloquial Formatted String](5.Date_Formatting.md#colloquial) - [5.6 - Mixed Date/Time Style](5.Date_Formatting.md#mixeddatetime) ## 5.0 - Format Custom Style If you need to format a `Date` or `DateInRegion` in a String using a custom format you need to use `toFormat(_:locale:)` function. `func toFormat(_ format: String, locale: LocaleConvertible?) -> String` it takes two arguments: - `format | String`: the format of the string. It's defined by the Unicode DateTime format specs you [can found here](7.Format_UnicodeTable.md). - `locale | LocaleConvertible?`: Set a non nil value to force the formatter to use a different locale than the one assigned to the date itself (from date's `region.locale` property). Leave it `nil` to use the one assigned by the region itself (or, for plain `Date` instances the one set as `SwiftDate.defaultRegion`). Example: ```swift let rome = Region(calendar: Calendars.gregorian, zone: Zones.europeRome, locale: Locales.italian) let date = DateInRegion(year: 2015, month: 1, day: 15, hour: 20, minute: 00, second: 5, nanosecond: 0, region: rome) // Even if date's locale is set to `italian` we can still // print in a different language by passing a non nil locale // to the function. let formattedString = date.toFormat("MMM dd yyyy", locale: Locales.english) // "Jan 15 2015" ``` [^ Top](#index) ## 5.1 - ISO8601 Formatted String SwiftDate allows you to print date instances using a configurable ISO8601 formatter which is also compatible with older versions of iOS where Apple's own class is not available. To use the ISO formatter call `.toISO()` function `func toISO(_ options: ISOFormatter.Options?) -> String` takes one optional argument: - `options | ISOFormatter.Options`: allows to customize the format of the output string by defining which kind of date must be into the final string (if you omit it `withInternetDateTime` is used). `ISOFormatter.Options` defines an `OptionSet` with the following values: - `withYear`: The date representation includes the year. The format for year is inferred based on the other specified options. If `withWeekOfYear` is specified, `YYYY` is used. Otherwise, `yyyy` is used. - `withMonth`: The date representation includes the month. The format for month is `MM`. - `withWeekOfYear`: The date representation includes the week of the year. The format for week of year is `ww`, including the `W` prefix. - `withDay`: The date representation includes the day. The format for day is inferred based on provided options: If `withMonth` is specified, `dd` is used. If `withWeekOfYear` is specified, `ee` is used. Otherwise, `DDD` is used. - `withTime`: The date representation includes the time. The format for time is `HH:mm:ss`. - `withTimeZone`: The date representation includes the timezone. The format for timezone is `ZZZZZ`. - `withSpaceBetweenDateAndTime`: The date representation uses a space (` `) instead of `T` between the date and time. - `withDashSeparatorInDate`: The date representation uses the dash separator (`-`) in the date. - `withFullDate`: The date representation uses the colon separator (`:`) in the time. - `withFullTime`: The date representation includes the hour, minute, and second. - `withInternetDateTime`: The format used for internet date times, according to the RFC 3339 standard. Equivalent to specifying `withFullDate`, `withFullTime`, `withDashSeparatorInDate`, `withColonSeparatorInTime`, and `withColonSeparatorInTimeZone`. - `withInternetDateTimeExtended`: The format used for internet date times; it's similar to `.withInternetDateTime` but include milliseconds (`yyyy-MM-dd'T'HH:mm:ss.SSSZZZZZ`). - `withoutTZSeparators`: Print timezone of the date without time separator (`+0200` instead of `+02:00`). You can combine it with the following other options: `withInternetDateTimeExtended`, `withInternetDateTime` and when `withTimeZone == false && withTimeZone == true` Examples: ```swift let regionRome = Region(calendar: Calendars.gregorian, zone: Zones.europeRome, locale: Locales.italian) let date = DateInRegion("2017-07-22 00:00:00", format: "yyyy-MM-dd HH:mm:ss", region: regionRome)! // ISO Formatting let _ = date.toISO() // "2017-07-22T00:00:00+02:00" let _ = date.toISO([.withFullDate]) // "2017-07-22" let _ = date.toISO([.withFullDate, .withFullTime, .withDashSeparatorInDate, .withSpaceBetweenDateAndTime]) // "2017-07-22 00:00:00+02:00" ``` [^ Top](#index) ## 5.2 - .NET Formatted String CSOM DateTime (aka .NET DateTime) is a format defined by Microsoft as the number of 100-nanosecond intervals that have elapsed since 12:00 A.M., January 1, 0001 ([learn more on MSDN documentation page](https://msdn.microsoft.com/en-us/library/dd948679)). Use the `.toDotNET()` function to create a string from a date instance. `func toDotNET() -> String` ```swift let date = "2017-06-20T14:49:19+02:00".toISODate()! let dotNetString = date.toDotNET() // "/Date(1497962959000+0200)/" ``` [^ Top](#index) ## 5.3 - RSS/AltRSS Formatted String RSS and AltRSS formatted string can be generated from an instance of `Date` or `DateInRegion` using the `.toRSS()` function. `func toRSS(alt: Bool) -> String` takes only one argument: - `alt | Bool`: true to print the AltRSS variant, false to print the default RSS formatted string. Examples: ```swift let date = ... // 2017-06-20T14:49:19+02:00 let rssString = date.toRSS(alt: false) // "Tue, 20 Jun 2017 14:49:19 +0200" let altRSSString = date.toRSS(alt: true) // "20 Jun 2017 14:49:19 +0200" ``` [^ Top](#index) ## 5.4 - SQL Formatted String To print a SQL formatted string from a date instance you need to use the `.toSQL()` function. `func toSQL() -> String` Examples: ```swift let date = ... // 2015-11-19T22:20:40+01:00 let sqlString = date.toSQL() // "2015-11-19T22:20:40.000+01" ``` [^ Top](#index) ## 5.5 - Relative/Colloquial Formatted String Colloquial format allows you to produce a human friendly string as result of the difference between a date and a a reference date (typically now). Examples of colloquial formatted strings are `3 mins ago`, `2 days ago` or `just now`. SwiftDate supports over 140+ languages to produce colloquial formatted strings; the entire engine behind the library is fully customizable so, if you need, you can override default strings and produce your own variants. To print a colloquial variant of a string just call `.toRelative()` function from a `Date` or `DateInRegion` instance. `func toRelative(since: DateInRegion?, style: RelativeFormatter.Style?, locale: LocaleConvertible?) -> String` It takes three arguments: - `since | DateInRegion?`: reference date, if you omit it current date is set as reference date. - `style | RelativeFormatter.Style?`: the style used to print the formatted value. There are 3 styles: `RelativeFormatter.defaultStyle()`, `RelativeFormatter.timeStyle()` and `RelativeFormstter.twitterStyle()`. - `locale | LocaleConvertible?`: pass non nil value to override the receiver region's `locale` and print the representation using passed locale. Examples: ```swift let ago5Mins = DateInRegion() - 5.minutes let _ = ago5Mins.toRelative(style: RelativeFormatter.defaultStyle(), locale: Locales.italian) // "5 minuti fa" let justNow2 = DateInRegion() - 2.hours let _ = justNow2.toRelative(style: RelativeFormatter.twitterStyle(), locale: Locales.italian) // "2h fa" let justNow = DateInRegion() - 10.seconds let _ = justNow.toRelative(style: RelativeFormatter.twitterStyle(), locale: Locales.italian) // "ora" ``` You can fully customize the language and results. See the guide ["Customize Colloquial Formatter"](Customize_ColloquialFormatter.md). [^ Top](#index) ## 5.6 - Mixed Date/Time Style If you to format date with different date/time styles you can use the `. dateTimeMixed` formatter option where you can choose a format both for date and time (`DateFormatter.Style`). ```swify // Just print full date with no time let formatted = date.toString(.dateTimeMixed(dateStyle: .full, timeStyle: .none)) ``` [^ Top](#index) [**Next Chapter**: Time Interval Formatting](#6.TimeInterval_Formatting.md) ================================================ FILE: Documentation/6.TimeInterval_Formatting.md ================================================ ![](./SwiftDate.png) - [**Index**: Table Of Contents](#Index.md) - [**Prev Chapter**: Date Formatting](#5.Date_Formatting.md) ## Time Interval Formatting - [6.0 - Format Interval as String](6.TimeInterval_Formatting.md#format) - [6.1 - Format Interval as Clock](6.TimeInterval_Formatting.md#clock) - [6.2 - Convert TimeInterval to Time Units](6.TimeInterval_Formatting.md#express) The following methods are part of the `TimeInterval` extension provided by SwiftDate. ## 6.0 - Format Interval as String Formatting a time interval as a string is pretty simple, you just need to call the `.toString()` function of the `TimeInterval`. It allows you to pick the right formatting options to represent the interval as a valid string. `func toString(options callback: ((inout ComponentsFormatterOptions) -> Void)? = nil) -> String` Using a callback function you can configure the formatter as you need. - `options: ComponentsFormatterOptions`: allows to define the formatting options of the string. `ComponentsFormatterOptions` is a struct with the following properties: - `allowsFractionalUnits | Bool`: 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”. The default value of this property is false. - `allowedUnits | NSCalendar.Unit`: Specify the units that can be used in the output. By default `[.year, .month, .weekOfMonth, .day, .hour, .minute, .second]` are used. - `collapsesLargestUnit | Bool`: A Boolean value indicating whether to collapse the largest unit into smaller units when a certain threshold is met. By default is `false`. - `maximumUnitCount | Int`: The maximum number of time units to include in the output string. The default value of this property is 0, which does not cause the elimination of any units. - `zeroFormattingBehavior | DateComponentsFormatter.ZeroFormattingBehavior`: The formatting style for units whose value is 0. By default is `.default` - `unitsStyle | DateComponentsFormatter.UnitsStyle`: The preferred style for units. By default is `.abbreviated`. Examples: ```swift // "2 hours, 5 minutes, 32 seconds" let _ = (2.hours + 5.minutes + 32.seconds).timeInterval.toString { $0.unitsStyle = .full $0.collapsesLargestUnit = false $0.allowsFractionalUnits = true } // "2d 5h" let _ = (5.hours + 2.days).timeInterval.toString { $0.unitsStyle = .abbreviated } ``` [^ Top](#index) ## 6.1 - Format Interval as Clock SwiftDate allows to format a `TimeInterval` in the form of a clock or a countdown timer. (ie. `57:00:00`). To format an interval using this style use `.toClock()` function. `func toClock(zero: DateComponentsFormatter.ZeroFormattingBehavior)` takes one argument: - `zero | DateComponentsFormatter.ZeroFormattingBehavior`: define the representation of the zero values into the destination string. By default is set to `.pad`. Example: ```swift let _ = (2.hours + 5.minutes).timeInterval.toClock() // "2:05:00" let _ = (4.minutes + 50.minutes).timeInterval.toClock(zero: .dropAll) // "54:00" ``` [^ Top](#index) ## 6.2 - Convert TimeInterval to Time Units While SwiftDate allows to carefully evaluate the differences between two dates and express them with the requested time unit (see ["Get Intervals Between Two Dates"](Date_Manipulation.md#interval), sometimes you may need to calculate this value from an absolute - calendar/locale indipendent - value expressed as `TimeInterval`. The methods used to convert a an absolute amount of seconds is `toUnits()` (for multiple extraction) and `toUnit()` for single component extraction. `func toUnits(_ units: Set, to refDate: DateInRegion? = nil) -> [Calendar.Component: Int]` `func toUnit(_ unit: Calendar.Component, to refDate: DateInRegion? = nil) -> Int?` takes two arguments: - `units | Set`: units to extract - `refDate | DateInRegion`: ending reference date, `nil` means `now()` in the context of the default region set. If `refDate` is `nil` evaluation is made from `now()` and `now() - interval` in the context of the `SwiftDate.defaultRegion` set. - `calendar | CalendarConvertible`: reference calendar; uses Example: ```swift // "[.day: 10, .hour: 12]" let _ = (36.hours + 2.days + 1.weeks).timeInterval.toUnits([.day, .hour]) ``` [^ Top](#index) ================================================ FILE: Documentation/7.Format_UnicodeTable.md ================================================ ![](./SwiftDate.png) - [**Index**: Table Of Contents](#Index.md) ## Unicode Table for Date Formats The following table is a summary of the most common configuration for Unicode Date Formatting. If you want to know more check out [Date Field Symbol Table](https://unicode.org/reports/tr35/tr35-dates.html#Date_Field_Symbol_Table) on unicode.org. | Format | Description | Example | | ------------- | ------------- | ------------- | | "y" | 1 digit min year | 1, 42, 2017 | | "yy" | 2 digit year | 01, 42, 17 | | "yyy" | 3 digit min year | 001, 042, 2017 | | "yyyy" | 4 digit min year | 0001, 0042, 2017 | | "M" | 1 digit min month | 7, 12 | | "MM" | 2 digit month | 07, 12 | | "MMM" | 3 letter month abbr. | Jul, Dec | | "MMMM" | Full month | July, December | | "MMMMM" | 1 letter month abbr. | J, D | | "d" | 1 digit min day | 4, 25 | | "dd" | 2 digit day | 04, 25 | | "E", "EE", "EEE" | 3 letter day name abbr. | Wed, Thu | | "EEEE" | full day name | Wednesday, Thursday | | "EEEEE" | 1 letter day name abbr. | W, T | | "EEEEEE" | 2 letter day name abbr. | We, Th | | "a" | Period of day | AM, PM | | "h" | AM/PM 1 digit min hour | 5, 7 | | "hh" | AM/PM 2 digit hour | 05, 07 | | "H" | 24 hr 1 digit min hour | 17, 7 | | "HH" | 24 hr 2 digit hour | 17, 07 | | "m" | 1 digit min minute | 1, 40 | | "mm" | 2 digit minute | 01, 40 | | "s" | 1 digit min second | 1, 40 | | "ss" | 2 digit second | 01, 40 | | "S" | 10th's place of fractional second | 123ms -> 1, 7ms -> 0 | | "SS" | 10th's & 100th's place of fractional second | 123ms -> 12, 7ms -> 00 | | "SSS" | 10th's & 100th's & 1,000's place of fractional second | 123ms -> 123, 7ms -> 007 | [**Index**: Table Of Contents](#Index.md) ================================================ FILE: Documentation/8.Customize_ColloquialFormatter.md ================================================ ![](./SwiftDate.png) - [**Index**: Table Of Contents](#Index.md) ## Customize Colloquial Formatter *To Be Completed* ================================================ FILE: Documentation/9.ColloquialSupportedLanguages.md ================================================ ![](./SwiftDate.png) - [**Index**: Table Of Contents](#Index.md) ## Colloquial Formatter Supported Languages Currently supported languages for relative formatter are (in long form): - Northern Sami - Persian - Turkmen - Faroese - Friulian - Hebrew - Polish - Romanian - Chinese (Simplified, Singapore) - Bangla - Indonesian - Northern Sami (Finland) - Slovak - Uyghur - Urdu (India) - Danish - Walser - Lao - Norwegian Nynorsk - Sinhala - Portuguese - Mazanderani - Kalaallisut - Spanish (Argentina) - Bulgarian - Lower Sorbian - Scottish Gaelic - Telugu - Punjabi - Pashto - Nepali - Albanian - Estonian - Welsh - Konkani - Colognian - Lakota - Malay - Chinese (Simplified, Macau China) - Bosnian - Swahili - Sakha - Filipino - Icelandic - Finnish - Tigrinya - Ngomba - English - Slovenian - Ewe - Spanish - Serbian (Latin) - Khmer - Japanese - Turkish - Sindhi - Kannada - Kazakh - Arabic (United Arab Emirates) - Uzbek - Western Frisian - Cantonese (Simplified) - German - Korean - Lithuanian - Tongan - Kyrgyz - French (Canada) - Odia - Belarusian - Mongolian - Tamil - Bosnian (Cyrillic) - Basque - Chinese (Simplified, Hong Kong China) - Chinese (Traditional, Hong Kong China) - Gujarati - Galician - Ukrainian - Greek - Spanish (United States) - Spanish (Mexico) - Malayalam - Vietnamese - Maltese - Chinese (Traditional) - Italian - Croatian - Assamese - Latvian - Chinese - Urdu - Catalan - Czech - Spanish (Paraguay) - Norwegian Bokmål - Burmese - Uzbek (Cyrillic) - Amharic - Afrikaans - Hungarian - Breton - Kabuverdianu - Luxembourgish - Russian - Cantonese (Traditional) - Marathi - Irish - Swedish - Thai - Armenian - Zulu - Georgian - Macedonian - Dzongkha - Dutch - Yiddish - Hindi - Arabic - French - Upper Sorbian ================================================ FILE: Documentation/Index.md ================================================ ![](./SwiftDate.png) ## Documentation - Reference Version: **6.3.0** - Last Update: **Nov 2020** The following documentation explores all the major features of the library. If you are interested in a detailed, method by method documentation you can refeer to the Jazzy documentation generated by CocoaPods (you can also install in Dash). ### Table Of Contents ### [0 - Info & Install](0.Informations.md) - [Future Plans](0.Informations.md#futureplans) - [Compatibility & Requirements](0.Informations.md#compatibility) - [Installation (CocoaPods,Carthage,SwiftPM](0.Informations.md#installation) ### [1 - Introduction to SwiftDate](1.Introduction.md) - [1.0 - Dates & Cocoa](1.Introduction.md#datesandcocoa) - [1.1 - Region & DateInRegion](1.Introduction.md#region_dateinregion) - [1.2 - The Default Region](1.Introduction.md#default_region) - [1.3 - Create Region](1.Introduction.md#creating_region) - [1.4 - Create DateInRegion](1.Introduction.md#creating_dateinregion) - [1.4.1 - From String](1.Introduction.md#initfromstring) - [1.4.2 - From Date Components](1.Introduction.md#initfromcomponents) - [1.4.3 - From TimeInterval](1.Introduction.md#initfromtimeinterval) - [1.4.4 - From Date](1.Introduction.md#initfromplaindate) ### [2 - Date Parsing](2.Date_Parsing.md) - [2.0 - Parse Custom Format](2.Date_Parsing.md#autoparsing) - [2.1 - Parse ISO8601](2.Date_Parsing.md#iso8601) - [2.2 - Parse .NET](2.Date_Parsing.md#dotnet) - [2.3 - Parse RSS & AltRSS](2.Date_Parsing.md#rssaltrss) - [2.4 - Parse SQL](2.Date_Parsing.md#sql) ### [3 - Date Manipulation & Creation](3.Manipulate_Date.md) - [3.0 - Add & Subtract Time Units from Date](3.Manipulate_Date.md#mathdate) - [3.1 - Get DateTime Components](3.Manipulate_Date.md#datecomponents) - [3.2 - Get Interval Between Dates](3.Manipulate_Date.md#interval) - [3.3 - Convert Date's Region (Locale/TimeZone/Calendar)](3.Manipulate_Date.md#convert) - [3.4 - Rounding Date](3.Manipulate_Date.md#roundingdate) - [3.5 - Trouncating Date](3.Manipulate_Date.md#trouncatingdate) - [3.6 - Set Time in Date](3.Manipulate_Date.md#altertimedate) - [3.7 - Set DateTime Components](3.Manipulate_Date.md#altercomponents) - [3.8 - Generate Related Dates (`nextYear, nextWeeekday, startOfMonth, startOfWeek, prevMonth`...)](3.Manipulate_Date.md#relateddates) - [3.9 - Date at start/end of time component](3.Manipulate_Date.md#startendcomponent) - [3.10 - Enumerate Dates](3.Manipulate_Date.md#enumeratedates) - [3.11 - Enumerate Dates for Weekday in Range](3.Manipulate_Date.md#enumerateweekdays) - [3.12 - Random Dates](3.Manipulate_Date.md#randomdates) - [3.13 - Sort Dates](3.Manipulate_Date.md#sort) - [3.14 - Get the next weekday](3.Manipulate_Date.md#nextWeekDay) - [3.15 - Get date at given week number/weekday](3.Manipulate_Date.md#dateAtWeeknumberWeekday) - [3.16 - Difference between dates with components](3.Manipulate_Date.md#differenceBetweenDates) - [3.17 - Create date at components preserving small components](3.Manipulate_Date.md#dateAtComponents) - [3.18 - Date at given weekday after # weeks](3.Manipulate_Date.md#dateAfterWeeks) - [3.19 - Next Date](3.Manipulate_Date.md#nextDate) ### [4 - Compare Dates](4.Compare_Dates.md) - [4.0 - Compare Dates](4.Compare_Dates.md#standard) - [4.1 - Extended Comparison with Presets (`isToday, isTomorrow, isSameWeek, isNextYear` etc.)](4.Compare_Dates.md#extended) - [4.2 - Comparison with Granularity](4.Compare_Dates.md#granularity) - [4.3 - Check if Date is Close to Another](4.Compare_Dates.md#close) - [4.4 - Check if Date is Inside a Range](4.Compare_Dates.md#range) ### [5 - Date Formatting](5.Date_Formatting.md) - [5.0 - Format Custom Style](5.Date_Formatting.md#customformatted) - [5.1 - ISO8601 Formatted String](5.Date_Formatting.md#isoformatted) - [5.2 - .NET Formatted String](5.Date_Formatting.md#dotnet) - [5.3 - RSS/AltRSS Formatted String](5.Date_Formatting.md#rss) - [5.4 - SQL Formatted String](5.Date_Formatting.md#sql) - [5.5 - Relative/Colloquial Formatted String](5.Date_Formatting.md#colloquial) - [5.6 - Mixed Date/Time Style](5.Date_Formatting.md#mixeddatetime) ### [6 - Time Intervals Formatting](6.TimeInterval_Formatting.md) - [6.0 - Format Interval as String](6.TimeInterval_Formatting.md#format) - [6.1 - Format Interval as Clock](6.TimeInterval_Formatting.md#clock) - [6.2 - Convert TimeInterval to Time Units](6.TimeInterval_Formatting.md#express) #### Other Links - [7 - Unicode Table for Date Formats](7.Format_UnicodeTable.md) - [8 - Customize Colloquial Formatter](8.Customize_ColloquialFormatter.md) - [9 - Colloquial Formatter Supported Languages](9.ColloquialSupportedLanguages.md) ================================================ FILE: LICENSE ================================================ The MIT License (MIT) Copyright (c) 2018 Daniele Margutti Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: Package.swift ================================================ // swift-tools-version:5.5 import PackageDescription let package = Package( name: "SwiftDate", defaultLocalization: "it", platforms: [ .macOS(.v10_15), .iOS(.v13), .watchOS(.v6), .tvOS(.v13) ], products: [ // Products define the executables and libraries produced by a package, and make them visible to other packages. .library(name: "SwiftDate", targets: ["SwiftDate"]) ], dependencies: [], targets: [ .target( name: "SwiftDate", dependencies: [], resources: [ .copy("Formatters/RelativeFormatter/langs"), .process("Resources") ]), .testTarget( name: "SwiftDateTests", dependencies: ["SwiftDate"]) ] ) ================================================ FILE: Playgrounds/SwiftDate.playground/Pages/Compare Dates.xcplaygroundpage/Contents.swift ================================================ import Foundation import SwiftDate ================================================ FILE: Playgrounds/SwiftDate.playground/Pages/Date Formatting.xcplaygroundpage/Contents.swift ================================================ import Foundation import SwiftDate /*: ## 5.0 - Format Custom Style If you need to format a `Date` or `DateInRegion` in a String using a custom format you need to use `toFormat(_:locale:)` function. */ let rome = Region(calendar: Calendars.gregorian, zone: Zones.europeRome, locale: Locales.italian) let date1 = DateInRegion(year: 2015, month: 1, day: 15, hour: 20, minute: 00, second: 5, nanosecond: 0, region: rome) // Even if date's locale is set to `italin` we can still // print in a different language by passing a non nil locale // to the function. let formattedString = date1.toFormat("MMM dd yyyy", locale: Locales.english) // "Jan 15 2015" /*: ## 5.1 - ISO8601 Formatted String SwiftDate allows to print date instances using a configurable ISO8601 formatter which is also compatible with older version of iOS where the Apple's own class is not available. To use the ISO formatter call `.toISO()` function */ let date2 = DateInRegion("2017-07-22 00:00:00", format: "yyyy-MM-dd HH:mm:ss", region: rome)! // ISO Formatting let date2_str1 = date2.toISO() // "2017-07-22T00:00:00+02:00" let date2_str2 = date2.toISO([.withFullDate]) // "2017-07-22" let date2_str3 = date2.toISO([.withFullDate, .withFullTime, .withDashSeparatorInDate, .withSpaceBetweenDateAndTime]) // "2017-07-22 00:00:00+02:00" /*: ## 5.2 - .NET Formatted String CSOM DateTime (aka .NET DateTime) is a format defined by Microsoft as the number of 100-nanosecond intervals that have elapsed since 12:00 A.M., January 1, 0001 ([learn more on MSDN documentation page](https://msdn.microsoft.com/en-us/library/dd948679)). Use the `.toDotNET()` function to create a string from a date instance. */ let date3 = "2017-06-20T14:49:19+02:00".toISODate()! let date3_dotNetString = date3.toDotNET() // "/Date(1497962959000+0200)/" /*: ## 5.3 - RSS/AltRSS Formatted String RSS and AltRSS formatted string can be generated from an instance of `Date` or `DateInRegion` using the `.toRSS()` function. */ let rssString = date3.toRSS(alt: false) // "Tue, 20 Jun 2017 14:49:19 +0200" let altRSSString = date3.toRSS(alt: true) // "20 Jun 2017 14:49:19 +0200" /*: ## 5.4 - SQL Formatted String To print SQL formatted string from a date instance you need to use the `.toSQL()` function. */ let sqlString = date3.toSQL() // "2015-11-19T22:20:40.000+01" /*: ## 5.5 - Relative/Colloquial Formatted String Colloquial format allows you to produce human friendly string as result of the difference between a date and a a reference date (typically now). Examples of colloquial formatted strings are `3 mins ago`, `2 days ago` or `just now`. SwiftDate supports over 140+ languages to produce colloquial formatted strings; the entire engine behind the library is fully customizable so, if you need, you can override default strings and produce your own variants. To print a colloquial variant of a string just call `.toRelative()` function from a `Date` or `DateInRegion` instance. */ let ago5Mins = DateInRegion() - 5.minutes _ = ago5Mins.toRelative(style: RelativeFormatter.defaultStyle(), locale: Locales.italian) // "5 minuti fa" let justNow2 = DateInRegion() - 2.hours _ = justNow2.toRelative(style: RelativeFormatter.twitterStyle(), locale: Locales.italian) // "2h fa" let justNow = DateInRegion() - 10.seconds _ = justNow.toRelative(style: RelativeFormatter.twitterStyle(), locale: Locales.italian) // "ora" ================================================ FILE: Playgrounds/SwiftDate.playground/Pages/Date Manipulation.xcplaygroundpage/Contents.swift ================================================ import Foundation import SwiftDate /*: ## 3.0 - Add & Subtract Time Units from Date SwiftDate allows to use numbers to work with time components in dates. By extending the `Int` type it defines a list of time units: - `nanoseconds` - `seconds` - `minutes` - `days` - `weeks` - `months` - `quarters` - `years` You can use a value followed by one of these unit specifications to add or remove the component from a date. So, for example, you can produce a date which can be a mix of intuitive math operations: */ let date1 = DateInRegion() + 1.years let date2 = DateInRegion() - 2.minutes let date3 = date2 + 3.hours - 5.minutes + 1.weeks /*: > IMPORTANT NOTE: These values are converted automatically to `DateComponents` evaluated in the same context of the target `Date` or `DateInRegion`'s `calendar`. Another way to add time components to a date is to use the `dateByAdding()` function: */ let date4 = DateInRegion().dateByAdding(5, .month) /*: ## 3.1 - Get DateTime Components Components With SwiftDate you have several convenience properties to inspect each datetime unit of a date, both for `Date` and `DateInRegion`. These properties are strictly correlated to the date's calendar (and some also with locale): if you are manipulating a `DateInRegion` remember these properties return values in the context of the associated `region` attributes (Locale, TimeZone and Calendar). > **IMPORTANT NOTE**: If you are working with plain `Date` properties uses as reference the currently set `SwiftDate.defaultRegion` which, unless you modify it, is set to Gregorian/UTC/Device's Language. */ let regionNY = Region(calendar: Calendars.gregorian, zone: Zones.americaNewYork, locale: Locales.english) let date5 = DateInRegion(components: { $0.year = 2015 $0.month = 6 $0.day = 1 $0.hour = 23 }, region: regionNY)! print("Origin date in NY: \(date5.hour):\(date5.minute) of \(date5.day)/\(date5.month)") print("Month is \(date5.monthName(.default).uppercased())") print("Month in italian is \(date5.convertTo(locale: Locales.italian).monthName(.default).uppercased())") // We can convert it to UTC and get the same properties which are now updated! let date5InUTC = date5.convertTo(region: Region.UTC) print("Converted date in UTC: \(date5InUTC.hour):\(date5InUTC.minute) of \(date5InUTC.day)/\(date5InUTC.month)") /*: ## 3.2 - Get Interval Between Dates You can get the interval between two dates and express it in form of time units easily with SwiftDate. */ let format = "yyyy-MM-dd HH:mm:ss" let date6 = DateInRegion("2017-07-22 00:00:00", format: format, region: regionNY)! let date7 = DateInRegion("2017-07-23 12:00:00", format: format, region: regionNY)! let hours = date6.getInterval(toDate: date7, component: .hour) // 36 hours let days = date6.getInterval(toDate: date7, component: .day) // 1 day /*: ## 3.3 - Convert Date's Region (Locale/TimeZone/Calendar) `DateInRegion` can be converted easily to anothe region just using `.convertTo(region:)` or `.convertTo(calendar: timezone:locale:)` functions. - `convertTo(region:)` convert the receiver date to another region. Region may include a different time zone for example, or a locale. - `convertTo(calendar:timezone:locale:)` allows to convert the receiver date instance to a specific calendar/timezone/locale. All parameters are optional and only non-nil parameters alter the final region. For a nil param the current receiver's region attribute is kept. Examples: */ // Create a date in NY: "2001-09-11 12:00:05" let date8 = DateInRegion(components: { $0.year = 2001 $0.month = 9 $0.day = 11 $0.hour = 12 $0.minute = 0 $0.second = 5 }, region: regionNY)! // Convert to GMT timezone (and locale) let date8inGMT = date8.convertTo(region: Region.UTC) // date is now: "2001-09-11 16:00:05" print(date8inGMT.toISO()) /*: ## 3.4 - Rounding Date `.dateRoundedAt()` function allows to round a given date time to the passed style: off, up or down. */ let regionRome = Region(calendar: Calendars.gregorian, zone: Zones.europeRome, locale: Locales.italian) // Round down 10mins let date9 = DateInRegion("2017-07-22 00:03:50", format: format, region: regionRome)! let r10min = date9.dateRoundedAt(.to10Mins) /*: ## 3.5 - Trouncating Date Sometimes you may need to truncate a date by zeroing all values below certain time unit. `.dateTruncated(from:) and .dateTruncated(to:)` functions can be used for this scope. #### Truncating From It creates a new instance by truncating the components starting from given components down the granurality. `func dateTruncated(from component: Calendar.Component) -> DateInRegion?` #### Truncated At It creates a new instance by truncating all passed components. `func dateTruncated(at components: [Calendar.Component]) -> DateInRegion?` */ let date10 = "2017-07-22 15:03:50".toDate("yyyy-MM-dd HH:mm:ss", region: regionRome)! let truncatedTime = date10.dateTruncated(from: .hour) let truncatedCmps = date10.dateTruncated(at: [.month, .day, .minute]) /*: ## 3.6 - Set Time in Date Sometimes you may need to alter time in a specified date. SwiftDate allows you to perform this using `.dateBySet(hour:min:secs:options:)` function. */ let date11 = DateInRegion("2010-01-01 00:00:00", format: "yyyy-MM-dd HH:mm:ss", region: regionRome)! let alteredDate = date11.dateBySet(hour: 20, min: 13, secs: 15) /*: ## 3.7 - Set DateTime Components SwiftDate allows you to return new date representing the date calculated by setting a specific components of a given date to given values, while trying to keep lower components the same (altering more components at the same time may result in different-than-expected results, this because lower components maybe need to be recalculated). */ let date12 = date11.dateBySet([.month: 1, .day: 1, .hour: 9, .minute: 26, .second: 0]) /*: ## 3.8 - Generate Related Dates (`nextYear, nextWeeekday, startOfMonth, startOfWeek, prevMonth`...) Sometimes you may need to generate a related date from a specified instance; maybe the next sunday, the first day of the next week or the start datetime of a date. SwiftDate includes 20+ different "interesting" dates you can obtain by calling `.dateAt()` function from any `Date` or `DateInRegion` instance. */ // Return today's datetime at 00:00:00 let date13 = DateInRegion().dateAt(.startOfDay) // Return today's datetime at 23:59:59 let date14 = DateInRegion().dateAt(.endOfDay) // Return the date at the start of this week let date15 = DateInRegion().dateAt(.startOfWeek) // Return current time tomorrow let date16 = DateInRegion().dateAt(.tomorrow) // Return the next sunday from specified date let date17 = date16.dateAt(.nextWeekday(.sunday)) // and so on... /*: ## 3.9 - Date at start/end of time component Two functions called `.dateAtStartOf()` and `.dateAtEndOf()` allows you to get the related date from a `Date`/`DateInRegion` instance moved at the start or end of the specified component. */ // Return today's date at 23:59:59 let date18 = DateInRegion().dateAtEndOf(.day) // Return the first day's date of the month described in date1 let date19 = DateInRegion().dateAtStartOf(.month) // Return the first day of the current week at 00:00 let date20 = DateInRegion().dateAtStartOf([.week, .day]) /*: ## 3.10 - Enumerate Dates Dates enumeration function allows you to generate a list of dates in a closed date intervals incrementing date components by a fixed or variable interval at each new date. - **VARIABLE INCREMENT** `static func enumerateDates(from startDate: DateInRegion, to endDate: DateInRegion, increment: ((DateInRegion) -> (DateComponents))) -> [DateInRegion]` - **FIXED INCREMENT** `static func enumerateDates(from startDate: DateInRegion, to endDate: DateInRegion, increment: DateComponents) -> [DateInRegion]` */ let toDate = DateInRegion() let fromDate = toDate - 30.days let dates = DateInRegion.enumerateDates(from: fromDate, to: toDate, increment: DateComponents.create { $0.hour = 1 $0.minute = 30 }) /*: ## 3.11 - Random Dates SwiftDate exposes a set of functions to generate a random date or array of random dates in a bounds. There are several functions to perform this operation: */ let randomDates = DateInRegion.randomDates(count: 40, between: fromDate, and: toDate) let randomDate = DateInRegion.randomDate(withinDaysBeforeToday: 7, region: regionRome) /*: ## 3.12 - Sort Dates Two conveniences function allows you to sort an array of dates by newest or oldest. Naming is pretty simple: */ let orderedByNewest = DateInRegion.sortedByNewest(list: randomDates) let oldestDate = DateInRegion.oldestIn(list: randomDates) ================================================ FILE: Playgrounds/SwiftDate.playground/Pages/Date Parsing.xcplaygroundpage/Contents.swift ================================================ import Foundation import SwiftDate /*: Parsing dates is pretty straighforward in SwiftDate; library can parse strings with dates automatically by recognizing one of the most common patterns. Moreover you can provide your own formats or use one of the built-in parsers. In the following chapter you will learn how to transform a string to a date. ## 2.0 - Parse Custom Format The easiest way to transform an input string to a valid date is to use one of the `.toDate()` functions available as `String`'s instance extensions. The purpose of these method is to get the best format can represent the input string and use it to generate a valid `DateInRegion`. As like other libs like moment.js, SwiftDate has a list of built-in formats it can use in order to obtain valid results. You can get the list of these formats by calling `SwiftDate.autoFormats`. The order of this array is important because SwiftDate iterates over this list until a valid date is returned (the order itself allows the lib to reduce the list of false positives). */ let itRegion = Region(calendar: Calendars.gregorian, zone: Zones.europeRome, locale: Locales.italian) let enRegion = Region(calendar: Calendars.gregorian, zone: Zones.europeRome, locale: Locales.english) let date1 = "2018-01-01 15:00".toDate() let date2 = "15:40:50".toDate("HH:mm:ss") let date3 = "2015-01-01 at 14".toDate("yyyy-MM-dd 'at' HH", region: itRegion) let srcString = "July 15 - 15:30" // it returns nil because itRegion has Locales.italian let date4 = srcString.toDate(["yyyy-MM-dd", "MMM dd '-' HH:mm"], region: itRegion) // it's okay because enRegion has locale set to english let date5 = srcString.toDate(["yyyy-MM-dd", "MMM dd '-' HH:mm"], region: enRegion) /*: > **PERFORMANCES** In order to preserve performances you should pass the `format` parameter if you know the input format. > **LOCALE PARAMETER** If you use readable unit names (like `MMM` for months) be sure to select the right locale inside the `region` parameter in order to get valid results. ## 2.1 Parse ISO8601 A special note must be made for ISO8601. This format (the extended version and all its variants) may include the timezone information. If you need to parse an ISO8601 datetime you should therefore use the `.toISODate()` function of `String` in order to get a complete result. > **NOTE** ISO8601 parser (via `.toISODate()` func) is capable of recognizing all the variants of the 8601 formats; if your date is in this formt use this function instead of passing custom time format. It will lead in better results. */ let date = "2017-08-05T16:04:03+02:00".toISODate(region: Region.ISO)! // returned date's region.zone is GMT+2 not the default's Region.ISO's GMT0. // This because value is read from the string itself. print("Date is located in \(date.region)") /*: ## 2.2 - Parse .NET CSOM DateTime (aka .NET DateTime) is a format defined by Microsoft as the number of 100-nanosecond intervals that have elapsed since 12:00 A.M., January 1, 0001 ([learn more on MSDN documentation page](https://msdn.microsoft.com/en-us/library/dd948679)). You can parse a CSOM datetime string using the `toDotNETDate()` function. > **NOTE:** As for ISO8601 even .NET datetime may contain information about timezone. When you set the region as input parameter of the conversion function remember: it will be overriden by default parsed timezone (GMT if not specified). Region is used for `locale` only. */ // This is the 2017-07-22T18:27:02+02:00 date. let date6 = "/Date(1500740822000+0200)/".toDotNETDate()! print("Date is \(date6.toISO())") /*: ## 2.3 - Parse RSS & AltRSS RSS & AltRSS datetime format are used in RSS feed files. Parsing in SwiftDate is pretty easy; just call the `.toRSSDate()` function. > **NOTE:** As for ISO8601 even RSS/AltRSS datetime contain information about timezone. When you set the region as input parameter of the conversion function remember: it will be overriden by default parsed timezone (GMT if not specified). Region is used for `locale` only. */ // This is the ISO8601: 2017-07-22T18:27:02+02:00 let date7 = "Sat, 22 Jul 2017 18:27:02 +0200".toRSSDate(alt: false)! print("RSS Date is \(date7.toISO())") // NOTE: // Even if we set a random region with a custom locale, // calendar and timezone, final parsed date still correct. // Only the locale parameter is set. // Other region's parameter are ignored and read from the string itself. let regionAny = Region(calendar: Calendars.buddhist, zone: Zones.indianMayotte, locale: Locales.italian) let date8 = "Tue, 20 Jun 2017 14:49:19 +0200".toRSSDate(alt: false, region: regionAny)! print("RSS Date is \(date8.toISO())") /*: ## 2.4 - Parse SQL SQL datetime is the format used in all SQL-compatible schemas. You can parse a string in this format using `toSQLDate()` function. */ // Date in ISO is 2016-04-14T11:58:58+02:00 let date9 = "2016-04-14T11:58:58.000+02".toSQLDate()! print("SQL Date is \(date9.toISO())") ================================================ FILE: Playgrounds/SwiftDate.playground/Pages/Introduction.xcplaygroundpage/Contents.swift ================================================ import Foundation import SwiftDate /*: ## 1.0 - Dates & Cocoa Generally when you talk about dates you are brought to think about a particular instance of time in a particular location of the world. However, in order to get a fully generic implementationn Apple made `Date` fully indipendent from any particular geographic location, calendar or locale. A plain `Date` object just represent an absolute value: in fact it count the number of seconds elapsed since January 1, 2001. This is what we call **Universal Time because it represent the same moment everywhere around the world**. You can see absolute time as the moment that someone in the USA has a telephone conversation with someone in Dubai; both have that conversation at the same moment (the absolute time) but the local time will be different due to time zones, different calendars, alphabets or notation methods. */ let now = Date() print("\(now.timeIntervalSinceReferenceDate) seconds elapsed since Jan 1, 2001 @ 00:00 UTC") /*: However, we often need to represent a date in a more specific context: **a particular place in the world, printing them using the rules of a specified locale and more**. In order to accomplish it we need to introduce several other objects: a `Calendar`, a `TimeZone` and a `Locale`; combining these attributes we're finally ready to provide a **representation** of the date into the real world. SwiftDate allows you to parse, create, manipulate and inspect dates in an easy and more natural way than Cocoa itself. ## 1.1 - Region & DateInRegion In order to simplify the date management in a specific context SwiftDate introduces two simple structs: - `Region` is a struct which define a region in the world (`TimeZone`) a language (`Locale`) and reference calendar (`Calendar`). - `DateInRegion` represent an absolute date in a specific region. When you work with this object all components are evaluated in the context of the region in which the object was created. Inside a DateInRegion you will have an `absoluteDate` and `region` properties. ## 1.2 - The Default Region In SwiftDate you can work both with `DateInRegion` and `Date` instances. Even plain Date objects uses `Region` when you need to extract time units, compare dates or evaluate specific operations. However this is a special region called **Default Region** and - by default - it has the following attributes: - **Time Zone** = GMT - this allows to keep a coerent behaviour with the default Date managment unless you change it. - **Calendar** = current's device calendar (auto updating) - **Locale** = current's device lcoale (auto updating) While it's a good choice to always uses `DateInRegion` you can also work with `Date` by changing the default region as follow: */ let rome = Region(calendar: Calendars.gregorian, zone: Zones.europeRome, locale: Locales.italian) SwiftDate.defaultRegion = rome /*: Since now all `Date` instances uses `rome` as default region both for parsing and evaluating date components: */ let dateInRome = "2018-01-01 00:00:00".toDate()! print("Current year is \(dateInRome.year) and hour is \(dateInRome.hour)") // "Current year is 2018 and hour is 0\n" /*: We can still convert this date to the default absolute representation in UTC using the `convertTo(region:)` function: */ let dateInUTC = dateInRome.convertTo(region: Region.UTC) print("Current year is \(dateInUTC.year) and hour is \(dateInUTC.hour)") // "Current year is 2017 and hour is 23\n" /*: Be careful while setting the default region. We still reccomends to use the `DateInRegion` instances instead, this allows you to read the region explicitly. ## 1.3 - Create Region As you seen from previous example creating a new `Region` is pretty straightforward; you need to specify the locale (used to print localized values like month or weekday name of the date), a timezone and a calendar (usually gregorian). Region instances accept the following parameters in form of protocols: - Time zone as `ZoneConvertible` conform object. You can pass both a `TimeZone` instance or any of the predefined timezones region defined inside the `Zones` enumeration. - Calendar as `CalendarConvertible` conform object. You can pass both a `Calendar` instance or any of the predefined calendars available inside the `Calendars` enumeration. - Locale as `LocaleConvertible` conform object. You can pass a both a `Locale` instance or any of the predefined locales available inside the `Locales` enumeration. Using `Zones`, `Calendars` and `Locales` enumeration values is the easiest way to create a region and compiler can also suggest you the best match for your search. The following example create two regions with different attributes: */ let regionNY = Region(calendar: Calendars.gregorian, zone: Zones.americaNewYork, locale: Locales.englishUnitedStates) let regionTokyo = Region(calendar: Calendars.gregorian, zone: Zones.asiaTokyo, locale: Locales.japanese) /*: ## 1.4 - Create DateInRegion Now you are ready to create a new `DateInRegion`. There are many different ways to create a new date: parsing a string, setting time components, derivating it from another date or from a given time intervals. Each initialization method require a region parameter which defines the region in which the date is expressed (default values may vary based upon the init and are listed below). ### 1.4.1 - From String The most common case is to parse a string and transform it to a date. As you know `DateFormatter` is an expensive object to create and if you need to parse multiple strings you should avoid creating a new instance in your loop. Don't worry: using SwiftDate the library helps you by reusing its own parser, shared along the caller thread. `DateInRegion`'s `init(_:format:region)` can be used to initialize a new date from a string (various shortcut are available under the `toXXX` prefix of `String` extensions. This object takes three parameters: - the `string` to parse (`String`) - the format of the string (`String`): this represent the format in which the string is expressed. It's a unicode format ([See the table of fields](7.Format_UnicodeTable.md)). If you skip this parameter SwiftDate attempts to parse the date using one of the built-in formats defined in `SwiftDate.autoFormats` array. If you know the format of the date you should explicitly set it in order to get better performances. - the `region` in which the date is expressed (`Region`). By default is set to `SwiftDate.defaultRegion`. */ let date1 = DateInRegion("2016-01-05", format: "yyyy-MM-dd", region: regionNY) let date2 = DateInRegion("2015-09-24T13:20:55", region: regionNY) /*: ### 1.4.2 - From Components You can create a `DateInRegion` also by setting the date components. The following method create a date from `DateComponents` instance passed via builder pattern: */ let date3 = DateInRegion(components: { $0.year = 2018 $0.month = 2 $0.day = 1 $0.hour = 23 }, region: regionNY) /*: You can also instance it by passing single (optional) components: */ let date4 = DateInRegion(year: 2015, month: 2, day: 4, hour: 20, minute: 00, second: 00, region: regionNY) /*: ### 1.4.3 - From TimeInterval As plain `Date` you can create a new `DateInRegion` just passing an absolute time interval which represent the seconds/milliseconds from Unix epoch. The following method create a date 1 year after the Unix Epoch (1971-01-01T00:00:00Z): */ let date5 = DateInRegion(seconds: 1.years.timeInterval, region: regionNY) let date6 = DateInRegion(milliseconds: 5000, region: regionNY) /*: ### 1.4.4 - From Date Finally you can init a new `DateInRegion` directly specifyng an absolute `Date` and a destination region: */ let absoluteDate: Date = (Date() - 2.months).dateAt(.startOfDay) let date7 = DateInRegion(absoluteDate, region: regionNY) ================================================ FILE: Playgrounds/SwiftDate.playground/contents.xcplayground ================================================ ================================================ FILE: README.md ================================================

SwiftDate

Toolkit to parse, validate, manipulate, compare and display dates, time & timezones in Swift.

## What's This? SwiftDate is the **definitive toolchain to manipulate and display dates and time zones** on all Apple platform and even on Linux and Swift Server Side frameworks like Vapor or Kitura. **Over 3 million of downloads on [CocoaPods](https://cocoapods.org/pods/SwiftDate).** From simple date manipulation to complex business logic SwiftDate maybe the right choice for your next project. - [x] **Easy Date Parsing** (custom formats, iso8601, rss & many more) - [x] **Easy Date Formatting** even with colloquial formatter and 140+ supported languages - [x] **Easy math operations with time units** (`2.hours + 5.minutes`...) - [x] **Intuitive components extraction** (`day, hour, nearestHour, weekdayNameShort` etc.) - [x] **Derivated dates generation** (`nextWeek, nextMonth, nextWeekday, tomorrow`...) - [x] Over **20+ fine grained date comparison** functions (`isToday, isTomorrow, isSameWeek, isNextYear`...) - [x] Swift 4's **Codable Support** - [x] **Random dates** generation - [x] **Fine grained date enumeration** functions - [x] **Time period** support - [x] **Convert TimeIntervals** to other units (`2.hours.toUnits(.minutes)`) and of course... - **IT'S TESTED!**. As 5.x the project has 90% of code coverage (want help us? write some unit tests and make a PR) - **IT'S FULLY DOCUMENTED!**, [both with a complete guide](/Documentation/Index.md) and with Jazzy! - **WE LOVE PLAYGROUND!** [Check out](/Playgrounds/SwiftDate.playground) our interactive playground! ## Start with SwiftDate The entire library is fully documented both via XCode method inspector and a complete markdown documentation you can found below. - → **[Full Documentation](/Documentation/Index.md)** - → **[Requirements, Install, License & More](/Documentation/0.Informations.md)** - → **[Upgrading from SwiftDate 4](/Documentation/10.Upgrading_SwiftDate4.md)** ### Explore SwiftDate From simple date manipulation to complex business logic SwiftDate maybe the right choice for your next project. Let me show to you the main features of the library: - [Date Parsing](#1) - [Date Manipulation](#2) - [Date Comparsion](#3) - [Date Creation with Region (Timezone, Calendar & Locale)](#4) - [Derivated Dates](#5) - [Components Extraction](#6) - [Switch between timezones/locale and calendars](#7) - [Date Formatting](#8) - [Relative Date Formatting (fully customizable!)](#9) - [Codable Support](#10) - [Time Periods](#11)
### 1. Date Parsing SwiftDate can recognize all the major datetime formats automatically (ISO8601, RSS, Alt RSS, .NET, SQL, HTTP...) and you can also provide your own formats. Creating a new date has never been so easy! ```swift // All default datetime formats (15+) are recognized automatically let _ = "2010-05-20 15:30:00".toDate() // You can also provide your own format! let _ = "2010-05-20 15:30".toDate("yyyy-MM-dd HH:mm") // All ISO8601 variants are supported too with timezone parsing! let _ = "2017-09-17T11:59:29+02:00".toISODate() // RSS, Extended, HTTP, SQL, .NET and all the major variants are supported! let _ = "19 Nov 2015 22:20:40 +0100".toRSS(alt: true) ``` ### 2. Date Manipulation Date can be manipulated by adding or removing time components using a natural language; time unit extraction is also easy and includes the support for timezone, calendar and locales! Manipulation can be done with standard math operators and between dates, time intervals, date components and relevant time units! ```swift // Math operations support time units let _ = ("2010-05-20 15:30:00".toDate() + 3.months - 2.days) let _ = Date() + 3.hours let _ = date1 + [.year:1, .month:2, .hour:5] let _ = date1 + date2 // extract single time unit components from date manipulation let over1Year = (date3 - date2).year > 1 ``` ### 3. Date Comparison SwiftDate include an extensive set of comparison functions; you can compare two dates by granularity, check if a date is an particular day, range and practically any other comparison you ever need. Comparison is also available via standard math operators like (`>, >=, <, <=`). ```swift // Standard math comparison is allowed let _ = dateA >= dateB || dateC < dateB // Complex comparisons includes granularity support let _ = dateA.compare(toDate: dateB, granularity: .hour) == .orderedSame let _ = dateA.isAfterDate(dateB, orEqual: true, granularity: .month) // > until month granularity let _ = dateC.isInRange(date: dateA, and: dateB, orEqual: true, granularity: .day) // > until day granularity let _ = dateA.earlierDate(dateB) // earlier date let _ = dateA.laterDate(dateB) // later date // Check if date is close to another with a given precision let _ = dateA.compareCloseTo(dateB, precision: 1.hours.timeInterval // Compare for relevant events: // .isToday, .isYesterday, .isTomorrow, .isWeekend, isNextWeek // .isSameDay, .isMorning, .isWeekday ... let _ = date.compare(.isToday) let _ = date.compare(.isNight) let _ = date.compare(.isNextWeek) let _ = date.compare(.isThisMonth) let _ = date.compare(.startOfWeek) let _ = date.compare(.isNextYear) // ...and MORE THAN 30 OTHER COMPARISONS BUILT IN // Operation in arrays (oldestIn, newestIn, sortedByNewest, sortedByOldest...) let _ = DateInRegion.oldestIn(list: datesArray) let _ = DateInRegion.sortedByNewest(list: datesArray) ``` ### 4. Date Creation with Region (Timezone, Calendar & Locale) You can create new dates from a string, time intervals or using date components. SwiftDate offers a wide set of functions to create and derivate your dates even with random generation! ```swift // All dates includes timezone, calendar and locales! // Create from string let rome = Region(calendar: Calendars.gregorian, zone: Zones.europeRome, locale: Locales.italian) let date1 = DateInRegion("2010-01-01 00:00:00", region: rome)! // Create date from intervals let _ = DateInRegion(seconds: 39940, region: rome) let _ = DateInRegion(milliseconds: 5000, region: rome) // Date from components let _ = DateInRegion(components: { $0.year = 2001 $0.month = 9 $0.day = 11 $0.hour = 12 $0.minute = 0 }, region: rome) let _ = DateInRegion(year: 2001, month: 1, day: 5, hour: 23, minute: 30, second: 0, region: rome) // Random date generation with/without bounds let _ = DateInRegion.randomDate(region: rome) let _ = DateInRegion.randomDate(withinDaysBeforeToday: 5) let _ = DateInRegion.randomDates(count: 50, between: lowerLimitDate, and: upperLimitDate, region: rome) ``` ### 5. Derivated Dates Date can be also generated starting from other dates; SwiftDate includes an extensive set of functions to generate. Over 20 different derivated dates can be created easily using `dateAt()` function. ```swift let _ = DateInRegion().dateAt(.endOfDay) // today at the end of the day // Over 20 different relevant dates including .startOfDay, // .endOfDay, .startOfWeek, .tomorrow, .nextWeekday, .nextMonth, .prevYear, .nearestMinute and many others! let _ = dateA.nextWeekday(.friday) // the next friday after dateA let _ = (date.dateAt(.startOfMonth) - 3.days) let _ = dateA.compare(.endOfWeek) // Enumerate dates in range by providing your own custom // increment expressed in date components let from = DateInRegion("2015-01-01 10:00:00", region: rome)! let to = DateInRegion("2015-01-02 03:00:00", region: rome)! let increment2 = DateComponents.create { $0.hour = 1 $0.minute = 30 $0.second = 10 } // generate dates in range by incrementing +1h,30m,10s each new date let dates = DateInRegion.enumerateDates(from: fromDate2, to: toDate2, increment: increment2) // Get all mondays in Jan 2019 let mondaysInJan2019 = Date.datesForWeekday(.monday, inMonth: 1, ofYear: 2019) // Altering time components let _ = dateA.dateBySet(hour: 10, min: 0, secs: 0) // Truncating a date let _ = dateA.dateTruncated(at: [.year,.month,.day]) // reset all time components keeping only date // Rounding a date let _ = dateA.dateRoundedAt(.toMins(10)) let _ = dateA.dateRoundedAt(.toFloor30Mins) // Adding components let _ = dateA.dateByAdding(5,.year) // Date at the start/end of any time component let _ = dateA.dateAtEndOf(.year) // 31 of Dec at 23:59:59 let _ = dateA.dateAtStartOf(.day) // at 00:00:00 of the same day let _ = dateA.dateAtStartOf(.month) // at 00:00:00 of the first day of the month ``` ### 6. Components Extraction You can extract components directly from dates and it includes the right value expressed in date's region (the right timezone and set locale!). ```swift // Create a date in a region, London but with the lcoale set to IT let london = Region(calendar: .gregorian, zone: .europeLondon, locale: .italian) let date = DateInRegion("2018-02-05 23:14:45", format: dateFormat, region: london)! // You can extract any of the all available time units. // VALUES ARE EXPRESSED IN THE REGION OF THE DATE (THE RIGHT TIMEZONE). // (you can still get the UTC/absolute value by getting the inner's absoluteDate). let _ = date.year // 2018 let _ = date.month // 2 let _ = date.monthNameDefault // 'Febbraio' as the locale is the to IT! let _ = date.firstDayOfWeek // 5 let _ = date.weekdayNameShort // 'Lun' as locale is the to IT // ... all components are supported: .year, .month, .day, .hour, .minute, .second, // .monthName, .weekday, .nearestHour, .firstDayOfWeek. .quarter and so on... ``` ### 7. Switch between timezones/locale and calendars You can easily convert any date to another region (aka another calendar, locale or timezone) easily! New date contains all values expressed into the destination reason ```swift // Conversion between timezones is easy using convertTo(region:) function let rNY = Region(calendar: Calendars.gregorian, zone: Zones.americaNewYork, locale: Locales.english) let rRome = Region(calendar: Calendars.gregorian, zone: Zones.europeRome, locale: Locales.italian) let dateInNY = "2017-01-01 00:00:00".toDate(region: rNY) let dateInRome = dateInNY?.convertTo(region: rRome)! print(dateInRome.toString()) // "dom gen 01 06:00:00 +0100 2017\n" // You can also convert single region's attributes let dateInIndia = dateInNY?.convertTo(timezone: Zones.indianChristmas, locale: Locales.nepaliIndia) print("\(dateInIndia!.toString())") // "आइत जनवरी ०१ १२:००:०० +0700 २०१७\n" ``` ### 8. Date Formatting Date formatting is easy, you can specify your own format, locale or use any of the provided ones. ```swift // Date Formatting let london = Region(calendar: .gregorian, zone: .europeLondon, locale: .english) let date = ... // 2017-07-22T18:27:02+02:00 in london region let _ = date.toDotNET() // /Date(1500740822000+0200)/ let _ = date.toISODate() // 2017-07-22T18:27:02+02:00 let _ = date.toFormat("dd MMM yyyy 'at' HH:mm") // "22 July 2017 at 18:27" // You can also easily change locale when formatting a region let _ = date.toFormat("dd MMM", locale: .italian) // "22 Luglio" // Time Interval Formatting as Countdown let interval: TimeInterval = (2.hours.timeInterval) + (34.minutes.timeInterval) + (5.seconds.timeInterval) let _ = interval.toClock() // "2:34:05" // Time Interval Formatting by Components let _ = interval.toString { $0.maximumUnitCount = 4 $0.allowedUnits = [.day, .hour, .minute] $0.collapsesLargestUnit = true $0.unitsStyle = .abbreviated } // "2h 34m" ``` ### 9. Relative Date Formatting (fully customizable!) Relative formatting is all new in SwiftDate; it supports 120+ languages with two different styles (`.default, .twitter`), 9 flavours (`.long, .longTime, .longConvenient, .short, .shortTime, .shortConvenient, .narrow, .tiny, .quantify`) and all of them are customizable as you need. The extensible format allows you to provide your own translations and rules to override the default behaviour. ```swift // Twitter Style let _ = (Date() - 3.minutes).toRelative(style: RelativeFormatter.twitterStyle(), locale: Locales.english) // "3m" let _ = (Date() - 6.minutes).toRelative(style: RelativeFormatter.twitterStyle(), locale: Locales.italian) // "6 min fa" // Default Style let _ = (now2 - 5.hours).toRelative(style: RelativeFormatter.defaultStyle(), locale: Locales.english) // "5 hours ago" let y = (now2 - 40.minutes).toRelative(style: RelativeFormatter.defaultStyle(), locale: Locales.italian) // "45 minuti fa" ``` ### 10. Codable Support Both `DateInRegion` and `Region` fully support the new Swift's `Codable` protocol. This mean you can safely encode/decode them: ```swift // Encoding/Decoding a Region let region = Region(calendar: Calendars.gregorian, zone: Zones.europeOslo, locale: Locales.english) let encodedJSON = try JSONEncoder().encode(region) let decodedRegion = try JSONDecoder().decode(Region.self, from: encodedJSON) // Encoding/Decoding a DateInRegion let date = DateInRegion("2015-09-24T13:20:55", region: region) let encodedDate = try JSONEncoder().encode(date) let decodedDate = try JSONDecoder().decode(DateInRegion.self, from: encodedDate) ``` ### 11. Time Periods SwiftDate integrates the great Matthew York's [DateTools](https://github.com/MatthewYork/DateTools) module in order to support Time Periods. See [Time Periods](/Documentation/12.Timer_Periods.md) section of the documentation. ================================================ FILE: Sources/SwiftDate/Date/Date+Compare.swift ================================================ // // 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 Date { // MARK: - Comparing Close /// 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: Date, precision: TimeInterval = 300) -> Bool { (abs(timeIntervalSince(refDate)) < precision) } // MARK: - Extendend Compare /// 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 { inDefaultRegion().compare(compareType) } /// 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 be less for the given dates /// - returns: `ComparisonResult` func compare(toDate refDate: Date, granularity: Calendar.Component) -> ComparisonResult { inDefaultRegion().compare(toDate: refDate.inDefaultRegion(), granularity: 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(_ refDate: Date, orEqual: Bool = false, granularity: Calendar.Component) -> Bool { inDefaultRegion().isBeforeDate(refDate.inDefaultRegion(), orEqual: orEqual, granularity: granularity) } /// 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: Date, orEqual: Bool = false, granularity: Calendar.Component) -> Bool { inDefaultRegion().isAfterDate(refDate.inDefaultRegion(), orEqual: orEqual, granularity: granularity) } /// Returns a value between 0.0 and 1.0 or nil, that is the position of current date between 2 other dates. /// /// - Parameters: /// - startDate: range upper bound date /// - endDate: range lower bound date /// - Returns: `nil` if current date is not between `startDate` and `endDate`. Otherwise returns position between `startDate` and `endDate`. func positionInRange(date startDate: Date, and endDate: Date) -> Double? { inDefaultRegion().positionInRange(date: startDate.inDefaultRegion(), and: endDate.inDefaultRegion()) } /// Return true if receiver date 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 /// - granularity: smallest unit that must, along with all larger units, be greater for the given dates. /// - Returns: Boolean func isInRange(date startDate: Date, and endDate: Date, orEqual: Bool = false, granularity: Calendar.Component = .nanosecond) -> Bool { inDefaultRegion().isInRange(date: startDate.inDefaultRegion(), and: endDate.inDefaultRegion(), orEqual: orEqual, granularity: granularity) } /// 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: Date, granularity: Calendar.Component) -> Bool { (compare(toDate: date, granularity: granularity) == .orderedSame) } // 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: Date) -> Date { timeIntervalSince(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: Date) -> Date { timeIntervalSince(date) >= 0 ? self : date } } extension Date { /// Returns the difference in the calendar component given (like day, month or year) /// with respect to the other date as a positive integer public func difference(in component: Calendar.Component, from other: Date) -> Int? { let (max, min) = orderDate(with: other) let result = calendar.dateComponents([component], from: min, to: max) return getValue(of: component, from: result) } /// 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 public func differences(in components: Set, from other: Date) -> [Calendar.Component: Int] { let (max, min) = orderDate(with: other) let differenceInDates = calendar.dateComponents(components, from: min, to: max) var result = [Calendar.Component: Int]() for component in components { if let value = getValue(of: component, from: differenceInDates) { result[component] = value } } return result } private func getValue(of component: Calendar.Component, from dateComponents: DateComponents) -> Int? { switch component { case .era: return dateComponents.era case .year: return dateComponents.year case .month: return dateComponents.month case .day: return dateComponents.day case .hour: return dateComponents.hour case .minute: return dateComponents.minute case .second: return dateComponents.second case .weekday: return dateComponents.weekday case .weekdayOrdinal: return dateComponents.weekdayOrdinal case .quarter: return dateComponents.quarter case .weekOfMonth: return dateComponents.weekOfMonth case .weekOfYear: return dateComponents.weekOfYear case .yearForWeekOfYear: return dateComponents.yearForWeekOfYear case .nanosecond: return dateComponents.nanosecond case .calendar, .timeZone: return nil @unknown default: assert(false, "unknown date component") } return nil } private func orderDate(with other: Date) -> (Date, Date) { let first = self.timeIntervalSince1970 let second = other.timeIntervalSince1970 if first >= second { return (self, other) } return (other, self) } } ================================================ FILE: Sources/SwiftDate/Date/Date+Components.swift ================================================ // // 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 Date { /// Indicates whether the month is a leap month. var isLeapMonth: Bool { inDefaultRegion().isLeapMonth } /// Indicates whether the year is a leap year. var isLeapYear: Bool { inDefaultRegion().isLeapYear } /// Julian day is the continuous count of days since the beginning of /// the Julian Period used primarily by astronomers. var julianDay: Double { inDefaultRegion().julianDay } /// 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 { inDefaultRegion().modifiedJulianDay } /// Return elapsed time expressed in given components since the current receiver and a reference date. /// /// - 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: Date?, component: Calendar.Component) -> Int64 { inDefaultRegion().getInterval(toDate: toDate?.inDefaultRegion(), component: component) } } ================================================ FILE: Sources/SwiftDate/Date/Date+Create.swift ================================================ // // 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 Date { /// Return the oldest date in given list. /// /// - Parameter list: list of dates /// - Returns: a tuple with the index of the oldest date and its instance. static func oldestIn(list: [Date]) -> Date? { guard list.count > 0 else { return nil } guard list.count > 1 else { return list.first! } return list.min(by: { return $0 < $1 }) } /// Return the newest date in given list. /// /// - Parameter list: list of dates /// - Returns: a tuple with the index of the oldest date and its instance. static func newestIn(list: [Date]) -> Date? { 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 defined by a function 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: Date, to endDate: Date, increment: ((Date) -> (DateComponents))) -> [Date] { var dates: [Date] = [] var currentDate = startDate while currentDate <= endDate { dates.append(currentDate) currentDate = (currentDate + increment(currentDate)) } return dates } /// 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: Date, to endDate: Date, increment: DateComponents) -> [Date] { Date.enumerateDates(from: startDate, to: endDate, increment: { _ in return increment }) } /// Round a given date time to the passed style (off|up|down). /// /// - Parameter style: rounding mode. /// - Returns: rounded date func dateRoundedAt(at style: RoundDateMode) -> Date { inDefaultRegion().dateRoundedAt(style).date } /// 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) -> Date { inDefaultRegion().dateAtStartOf(unit).date } /// 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]) -> Date { units.reduce(self) { (currentDate, currentUnit) -> Date in return currentDate.dateAtStartOf(currentUnit) } } /// Returns a new Moment that is initialized at the end of a specified unit of time. /// /// - parameter unit: A TimeUnit value. /// /// - returns: A new Moment instance. func dateAtEndOf(_ unit: Calendar.Component) -> Date { inDefaultRegion().dateAtEndOf(unit).date } /// 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]) -> Date { units.reduce(self) { (currentDate, currentUnit) -> Date in return currentDate.dateAtEndOf(currentUnit) } } /// Create a new date by altering specified components of the receiver. /// /// - Parameter components: components to alter with their new values. /// - Returns: new altered `DateInRegion` instance func dateBySet(_ components: [Calendar.Component: Int]) -> Date? { DateInRegion(self, region: SwiftDate.defaultRegion).dateBySet(components)?.date } /// 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()) -> Date? { let srcDate = DateInRegion(self, region: SwiftDate.defaultRegion) return srcDate.dateBySet(hour: hour, min: min, secs: secs, ms: ms, options: options)?.date } /// Creates a new instance by truncating the components /// /// - Parameter components: components to truncate. /// - Returns: new date with truncated components. func dateTruncated(_ components: [Calendar.Component]) -> Date? { DateInRegion(self, region: SwiftDate.defaultRegion).dateTruncated(at: components)?.date } /// 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) -> Date? { DateInRegion(self, region: SwiftDate.defaultRegion).dateTruncated(from: component)?.date } /// Offset a date by n calendar components. /// Note: This operation can be functionally chained. /// /// - Parameters: /// - count: value of the offset. /// - component: component to offset. /// - Returns: new altered date. func dateByAdding(_ count: Int, _ component: Calendar.Component) -> DateInRegion { DateInRegion(self, region: SwiftDate.defaultRegion).dateByAdding(count, component) } /// Return related date starting from the receiver attributes. /// /// - Parameter type: related date to obtain. /// - Returns: instance of the related date. func dateAt(_ type: DateRelatedType) -> Date { inDefaultRegion().dateAt(type).date } /// Create a new date at now and extract the related date using passed rule type. /// /// - Parameter type: related date to obtain. /// - Returns: instance of the related date. static func nowAt(_ type: DateRelatedType) -> Date { Date().dateAt(type) } /// 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) -> [Date] { let fromDate = DateInRegion(Date(year: year, month: month, day: 1, hour: 0, minute: 0), region: region) let toDate = fromDate.dateAt(.endOfMonth) return DateInRegion.datesForWeekday(weekday, from: fromDate, to: toDate, region: region).map { $0.date } } /// 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: Date, to endDate: Date, region: Region = SwiftDate.defaultRegion) -> [Date] { let fromDate = DateInRegion(startDate, region: region) let toDate = DateInRegion(endDate, region: region) return DateInRegion.datesForWeekday(weekday, from: fromDate, to: toDate, region: region).map { $0.date } } /// 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) -> Date { let date = DateInRegion(self, region: region) return date.dateAt(weekdayOrdinal: weekdayOrdinal, weekday: weekday, monthNumber: monthNumber, yearNumber: yearNumber).date } /// Returns the next weekday preserving smaller components (hour, minute, seconds) /// /// - Parameters: /// - weekday: weekday to get. /// - region: region target, omit to use `SwiftDate.defaultRegion` /// - Returns: `Date` func nextWeekday(_ weekday: WeekDay, region: Region = SwiftDate.defaultRegion) -> Date { let date = DateInRegion(self, region: region) return date.nextWeekday(weekday).date } } ================================================ FILE: Sources/SwiftDate/Date/Date+Math.swift ================================================ // // 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 /// Subtracts two dates and returns the relative components from `lhs` to `rhs`. /// Follows this mathematical pattern: /// let difference = lhs - rhs /// rhs + difference = lhs public func - (lhs: Date, rhs: Date) -> DateComponents { SwiftDate.defaultRegion.calendar.dateComponents(DateComponents.allComponentsSet, from: rhs, to: lhs) } /// Adds date components to a date and returns a new date. public func + (lhs: Date, rhs: DateComponents) -> Date { rhs.from(lhs)! } /// Adds date components to a date and returns a new date. public func + (lhs: DateComponents, rhs: Date) -> Date { (rhs + lhs) } /// Subtracts date components from a date and returns a new date. public func - (lhs: Date, rhs: DateComponents) -> Date { (lhs + (-rhs)) } public func + (lhs: Date, rhs: TimeInterval) -> Date { lhs.addingTimeInterval(rhs) } ================================================ FILE: Sources/SwiftDate/Date/Date.swift ================================================ // // 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 internal enum AssociatedKeys: String { case customDateFormatter = "SwiftDate.CustomDateFormatter" } extension Date: DateRepresentable { /// Just return itself to be compliant with `DateRepresentable` protocol. public var date: Date { return self } /// For absolute Date object the default region is obtained from the global `defaultRegion` variable. public var region: Region { SwiftDate.defaultRegion } /// Assign a custom formatter if you need a special behaviour during formatting of the object. /// Usually you will not need to do it, SwiftDate uses the local thread date formatter in order to /// optimize the formatting process. By default is `nil`. public var customFormatter: DateFormatter? { get { let formatter: DateFormatter? = getAssociatedValue(key: AssociatedKeys.customDateFormatter.rawValue, object: self as AnyObject) return formatter } set { set(associatedValue: newValue, key: AssociatedKeys.customDateFormatter.rawValue, object: self as AnyObject) } } /// Extract the date components. public var dateComponents: DateComponents { region.calendar.dateComponents(DateComponents.allComponentsSet, from: self) } /// Initialize a new date object from string expressed in given region. /// /// - Parameters: /// - string: date expressed as string. /// - format: format of the date (`nil` uses provided list of auto formats patterns. /// Pass it if you can in order to optimize the parse task). /// - region: region in which the date is expressed. `nil` uses the `SwiftDate.defaultRegion`. public init?(_ string: String, format: String? = nil, region: Region = SwiftDate.defaultRegion) { guard let dateInRegion = DateInRegion(string, format: format, region: region) else { return nil } self = dateInRegion.date } /// Initialize a new date from the number of seconds passed since Unix Epoch. /// /// - Parameter interval: seconds /// Initialize a new date from the number of seconds passed since Unix Epoch. /// /// - Parameters: /// - interval: seconds from Unix epoch time. /// - region: region in which the date, `nil` uses the default region at UTC timezone public init(seconds interval: TimeInterval, region: Region = Region.UTC) { self = DateInRegion(seconds: interval, region: region).date } /// 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 = DateInRegion(milliseconds: interval, region: region).date } /// 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) { guard let date = DateInRegion(components: configuration, region: region)?.date else { return nil } self = date } /// 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?) { guard let date = DateInRegion(components: components, region: region)?.date else { return nil } self = date } /// Initialize a new date with given components. public init(year: Int, month: Int, day: Int, hour: Int, minute: Int, 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 = region.calendar.date(from: components)! } /// Express given absolute date in the context of the default region. /// /// - Returns: `DateInRegion` public func inDefaultRegion() -> DateInRegion { DateInRegion(self, region: SwiftDate.defaultRegion) } /// Express given absolute date in the context of passed region. /// /// - Parameter region: destination region. /// - Returns: `DateInRegion` public func `in`(region: Region) -> DateInRegion { DateInRegion(self, region: region) } /// Return a date in the distant past. /// /// - Returns: Date instance. public static func past() -> Date { Date.distantPast } /// Return a date in the distant future. /// /// - Returns: Date instance. public static func future() -> Date { Date.distantFuture } } ================================================ FILE: Sources/SwiftDate/DateInRegion/DateInRegion+Compare.swift ================================================ // // 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 { (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 { lhs.date.compare(rhs.date) == .orderedAscending } public func > (lhs: DateInRegion, rhs: DateInRegion) -> Bool { 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 { (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 (year == refDate.year) && (month == refDate.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 (year == refDate.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 { (compare(toDate: date, granularity: granularity) == .orderedSame) } /// Returns a value between 0.0 and 1.0 or nil, that is the position of current date between 2 other dates. /// /// - Parameters: /// - startDate: range upper bound date /// - endDate: range lower bound date /// - Returns: `nil` if current date is not between `startDate` and `endDate`. Otherwise returns position between `startDate` and `endDate`. func positionInRange(date startDate: DateInRegion, and endDate: DateInRegion) -> Double? { let diffCurrentDateAndStartDate = self - startDate guard diffCurrentDateAndStartDate >= 0 else { return nil } let diffEndDateAndStartDate = endDate - startDate guard diffEndDateAndStartDate > 0, diffCurrentDateAndStartDate <= diffEndDateAndStartDate else { return nil } return diffCurrentDateAndStartDate / diffEndDateAndStartDate } /// 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 { 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 { 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 { 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? { 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, from other: DateInRegion) -> [Calendar.Component: Int] { self.date.differences(in: components, from: other.date) } } ================================================ FILE: Sources/SwiftDate/DateInRegion/DateInRegion+Components.swift ================================================ // // 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 = 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 { 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 { 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 { 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) } } ================================================ FILE: Sources/SwiftDate/DateInRegion/DateInRegion+Create.swift ================================================ // // 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.. 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.. DateInRegion { let interval = final.timeIntervalSince(initial) let randomInterval = TimeInterval(UInt32.random(in: UInt32.min.. 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] { 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] { 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] { 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 { 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) } /// 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 { 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 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) } /// 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 { 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: 0, .minute: 0, .second: 0, .nanosecond: 0])! 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 { 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) } } ================================================ FILE: Sources/SwiftDate/DateInRegion/DateInRegion+Math.swift ================================================ // // 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 { 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 { 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) } } ================================================ FILE: Sources/SwiftDate/DateInRegion/DateInRegion.swift ================================================ // // 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 { DateInRegion(Date.distantPast, region: SwiftDate.defaultRegion) } /// Return a date in the distant future. /// /// - Returns: Date instance. public static func future() -> DateInRegion { DateInRegion(Date.distantFuture, region: SwiftDate.defaultRegion) } // MARK: - Codable Support enum CodingKeys: String, CodingKey { case date case region } } ================================================ FILE: Sources/SwiftDate/DateInRegion/Region.swift ================================================ // // 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 { "{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) } } ================================================ FILE: Sources/SwiftDate/DateRepresentable.swift ================================================ // // 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 protocol DateRepresentable { // MARK: - Date Components var year: Int { get } /// Represented month var month: Int { get } /// Represented month name with given style. /// /// - Parameter style: style in which the name must be formatted. /// - Returns: name of the month func monthName(_ style: SymbolFormatStyle) -> String /// Number of the days in the receiver. var monthDays: Int { get } /// Day unit of the receiver. var day: Int { get } /// Day of year unit of the receiver var dayOfYear: Int { get } /// The number of day in ordinal style format for the receiver in current locale. /// For example, in the en_US locale, the number 3 is represented as 3rd; /// in the fr_FR locale, the number 3 is represented as 3e. @available(iOS 9.0, macOS 10.11, *) var ordinalDay: String { get } /// Hour unit of the receiver. var hour: Int { get } /// Nearest rounded hour from the date var nearestHour: Int { get } /// Minute unit of the receiver. var minute: Int { get } /// Second unit of the receiver. var second: Int { get } /// Nanosecond unit of the receiver. var nanosecond: Int { get } /// Milliseconds in day of the receiver /// This field behaves exactly like a composite of all time-related fields, not including the zone fields. /// As such, it also reflects discontinuities of those fields on DST transition days. /// On a day of DST onset, it will jump forward. On a day of DST cessation, it will jump backward. /// This reflects the fact that is must be combined with the offset field to obtain a unique local time value. var msInDay: Int { get } /// Weekday unit of the receiver. /// The weekday units are the numbers 1-N (where for the Gregorian calendar N=7 and 1 is Sunday). var weekday: Int { get } /// Name of the weekday expressed in given format style. /// /// - Parameter style: style to express the value. /// - Parameter locale: locale to use; ignore it to use default's region locale. /// - Returns: weekday name func weekdayName(_ style: SymbolFormatStyle, locale: LocaleConvertible?) -> String /// Week of a year of the receiver. var weekOfYear: Int { get } /// Week of a month of the receiver. var weekOfMonth: Int { get } /// Ordinal position within the month unit of the corresponding weekday unit. /// For example, in the Gregorian calendar a weekday ordinal unit of 2 for a /// weekday unit 3 indicates "the second Tuesday in the month". var weekdayOrdinal: Int { get } /// Return the first day number of the week where the receiver date is located. var firstDayOfWeek: Int { get } /// Return the last day number of the week where the receiver date is located. var lastDayOfWeek: Int { get } /// Relative year for a week within a year calendar unit. var yearForWeekOfYear: Int { get } /// Quarter value of the receiver. var quarter: Int { get } /// Quarter name expressed in given format style. /// /// - Parameter style: style to express the value. /// - Parameter locale: locale to use; ignore it to use default's region locale. /// - Returns: quarter name func quarterName(_ style: SymbolFormatStyle, locale: LocaleConvertible?) -> String /// Era value of the receiver. var era: Int { get } /// Name of the era expressed in given format style. /// /// - Parameter style: style to express the value. /// - Parameter locale: locale to use; ignore it to use default's region locale. /// - Returns: era func eraName(_ style: SymbolFormatStyle, locale: LocaleConvertible?) -> String /// The current daylight saving time offset of the represented date. var DSTOffset: TimeInterval { get } // MARK: - Common Properties /// Absolute representation of the date var date: Date { get } /// Associated region var region: Region { get } /// Associated calendar var calendar: Calendar { get } /// Extract the date components from the date var dateComponents: DateComponents { get } /// Returns whether the given date is in today as boolean. var isToday: Bool { get } /// Returns whether the given date is in yesterday. var isYesterday: Bool { get } /// Returns whether the given date is in tomorrow. var isTomorrow: Bool { get } /// Returns whether the given date is in the weekend. var isInWeekend: Bool { get } /// Return true if given date represent a passed date var isInPast: Bool { get } /// Return true if given date represent a future date var isInFuture: Bool { get } /// Use this object to format the date object. /// By default this object return the `customFormatter` instance (if set) or the /// local thread shared formatter (via `sharedFormatter()` func; this is the most typical scenario). /// /// - Parameters: /// - format: format string to set. /// - configuration: optional callback used to configure the object inline. /// - Returns: formatter instance func formatter(format: String?, configuration: ((DateFormatter) -> Void)?) -> DateFormatter /// User this object to get an DateFormatter already configured to format the data object with the associated region. /// By default this object return the `customFormatter` instance (if set) configured for region or the /// local thread shared formatter even configured for region (via `sharedFormatter()` func; this is the most typical scenario). /// /// - format: format string to set. /// - configuration: optional callback used to configure the object inline. /// - Returns: formatter instance func formatterForRegion(format: String?, configuration: ((inout DateFormatter) -> Void)?) -> DateFormatter /// Set a custom formatter for this object. /// Typically you should not need to set a value for this property. /// With a `nil` value SwiftDate will uses the threa shared formatter returned by `sharedFormatter()` function. /// In case you need to a custom formatter instance you can override the default behaviour by setting a value here. var customFormatter: DateFormatter? { get set } /// Return a formatter instance created as singleton into the current caller's thread. /// This object is used for formatting when no `dateFormatter` is set for the object /// (this is the common scenario where you want to avoid multiple formatter instances to /// parse dates; instances of DateFormatter are very expensive to create and you should /// use a single instance in each thread to perform this kind of tasks). /// /// - Returns: formatter instance var sharedFormatter: DateFormatter { get } // MARK: - Init /// Initialize a new date by parsing a string. /// /// - Parameters: /// - string: string with the date. /// - format: format used to parse date. Pass `nil` to use built-in formats /// (if you know you should pass it to optimize the parsing process) /// - region: region in which the date in `string` is expressed. init?(_ string: String, format: String?, region: Region) /// Initialize a new date from a number of seconds since the Unix Epoch. /// /// - Parameters: /// - interval: seconds since the Unix Epoch timestamp. /// - region: region in which the date must be expressed. init(seconds interval: TimeInterval, 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. init(milliseconds interval: Int, 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. init?(components configuration: ((inout DateComponents) -> Void), region: Region?) /// Initialize a new date with time components passed. /// /// - Parameters: /// - components: date components /// - region: region in which the date is expressed. Ignore to use `SwiftDate.defaultRegion`, /// `nil` to use `DateComponents` data. init?(components: DateComponents, region: Region?) /// Initialize a new date with given components. init(year: Int, month: Int, day: Int, hour: Int, minute: Int, second: Int, nanosecond: Int, region: Region) // MARK: - Conversion /// Convert a date to another region. /// /// - Parameter region: destination region in which the date must be represented. /// - Returns: converted date func convertTo(region: Region) -> DateInRegion // MARK: - To String Formatting /// Convert date to a string using passed pre-defined style. /// /// - Parameter style: formatter style, `nil` to use `standard` style /// - Returns: string representation of the date func toString(_ style: DateToStringStyles?) -> String /// Convert date to a string using custom date format. /// /// - Parameters: /// - format: format of the string representation /// - locale: locale to fix a custom locale, `nil` to use associated region's locale /// - Returns: string representation of the date func toFormat(_ format: String, locale: LocaleConvertible?) -> String /// Convert a date to a string representation relative to another reference date (or current /// if not passed). func toRelative(since: DateInRegion?, dateTimeStyle: RelativeDateTimeFormatter.DateTimeStyle, unitsStyle: RelativeDateTimeFormatter.UnitsStyle) -> String /// Return ISO8601 representation of the date /// /// - Parameter options: optional options, if nil extended iso format is used func toISO(_ options: ISOFormatter.Options?) -> String /// Return DOTNET compatible representation of the date. /// /// - Returns: string representation of the date func toDotNET() -> String /// Return SQL compatible representation of the date. /// /// - Returns: string represenation of the date func toSQL() -> String /// Return RSS compatible representation of the date /// /// - Parameter alt: `true` to return altRSS version, `false` to return the standard RSS representation /// - Returns: string representation of the date func toRSS(alt: Bool) -> String // MARK: - Extract Components /// Extract time components for elapsed interval between the receiver date /// and a reference date. /// /// - Parameters: /// - units: units to extract. /// - refDate: reference date /// - Returns: extracted time units func toUnits(_ units: Set, to refDate: DateRepresentable) -> [Calendar.Component: Int] /// Extract time unit component from given date. /// /// - Parameters: /// - unit: time component to extract /// - refDate: reference date /// - Returns: extracted time unit value func toUnit(_ unit: Calendar.Component, to refDate: DateRepresentable) -> Int } public extension DateRepresentable { // MARK: - Common Properties var calendar: Calendar { return region.calendar } // MARK: - Date Components Properties var year: Int { return dateComponents.year! } var month: Int { return dateComponents.month! } var monthDays: Int { return calendar.range(of: .day, in: .month, for: date)!.count } func monthName(_ style: SymbolFormatStyle) -> String { let formatter = self.formatter(format: nil) let idx = (month - 1) switch style { case .default: return formatter.monthSymbols[idx] case .defaultStandalone: return formatter.standaloneMonthSymbols[idx] case .short: return formatter.shortMonthSymbols[idx] case .standaloneShort: return formatter.shortStandaloneMonthSymbols[idx] case .veryShort: return formatter.veryShortMonthSymbols[idx] case .standaloneVeryShort: return formatter.veryShortStandaloneMonthSymbols[idx] } } var day: Int { return dateComponents.day! } var dayOfYear: Int { return calendar.ordinality(of: .day, in: .year, for: date)! } @available(iOS 9.0, macOS 10.11, *) var ordinalDay: String { let day = self.day return DateFormatter.sharedOrdinalNumberFormatter(locale: region.locale).string(from: day as NSNumber) ?? "\(day)" } var hour: Int { return dateComponents.hour! } var nearestHour: Int { let newDate = (date + (date.minute >= 30 ? 60 - date.minute : -date.minute).minutes) return newDate.in(region: region).hour } var minute: Int { return dateComponents.minute! } var second: Int { return dateComponents.second! } var nanosecond: Int { return dateComponents.nanosecond! } var msInDay: Int { return (calendar.ordinality(of: .second, in: .day, for: date)! * 1000) } var weekday: Int { return dateComponents.weekday! } func weekdayName(_ style: SymbolFormatStyle, locale: LocaleConvertible? = nil) -> String { let formatter = self.formatter(format: nil) { $0.locale = (locale ?? self.region.locale).toLocale() } let idx = (weekday - 1) switch style { case .default: return formatter.weekdaySymbols[idx] case .defaultStandalone: return formatter.standaloneWeekdaySymbols[idx] case .short: return formatter.shortWeekdaySymbols[idx] case .standaloneShort: return formatter.shortStandaloneWeekdaySymbols[idx] case .veryShort: return formatter.veryShortWeekdaySymbols[idx] case .standaloneVeryShort: return formatter.veryShortStandaloneWeekdaySymbols[idx] } } var weekOfYear: Int { return dateComponents.weekOfYear! } var weekOfMonth: Int { return dateComponents.weekOfMonth! } var weekdayOrdinal: Int { return dateComponents.weekdayOrdinal! } var yearForWeekOfYear: Int { return dateComponents.yearForWeekOfYear! } var firstDayOfWeek: Int { return date.dateAt(.startOfWeek).day } var lastDayOfWeek: Int { return date.dateAt(.endOfWeek).day } var quarter: Int { let monthsInQuarter = Double(Calendar.current.monthSymbols.count) / 4.0 return Int(ceil( Double(month) / monthsInQuarter)) } var isToday: Bool { return calendar.isDateInToday(date) } var isYesterday: Bool { return calendar.isDateInYesterday(date) } var isTomorrow: Bool { return calendar.isDateInTomorrow(date) } var isInWeekend: Bool { return calendar.isDateInWeekend(date) } var isInPast: Bool { return date < Date() } var isInFuture: Bool { return date > Date() } func quarterName(_ style: SymbolFormatStyle, locale: LocaleConvertible? = nil) -> String { let formatter = self.formatter(format: nil) { $0.locale = (locale ?? self.region.locale).toLocale() } let idx = (quarter - 1) switch style { case .default: return formatter.quarterSymbols[idx] case .defaultStandalone: return formatter.standaloneQuarterSymbols[idx] case .short, .veryShort: return formatter.shortQuarterSymbols[idx] case .standaloneShort, .standaloneVeryShort: return formatter.shortStandaloneQuarterSymbols[idx] } } var era: Int { return dateComponents.era! } func eraName(_ style: SymbolFormatStyle, locale: LocaleConvertible? = nil) -> String { let formatter = self.formatter(format: nil) { $0.locale = (locale ?? self.region.locale).toLocale() } let idx = (era - 1) switch style { case .default, .defaultStandalone: return formatter.longEraSymbols[idx] case .short, .standaloneShort, .veryShort, .standaloneVeryShort: return formatter.eraSymbols[idx] } } var DSTOffset: TimeInterval { return region.timeZone.daylightSavingTimeOffset(for: date) } // MARK: - Date Formatters func formatter(format: String? = nil, configuration: ((DateFormatter) -> Void)? = nil) -> DateFormatter { let formatter = (customFormatter ?? sharedFormatter) if let dFormat = format { formatter.dateFormat = dFormat } configuration?(formatter) return formatter } func formatterForRegion(format: String? = nil, configuration: ((inout DateFormatter) -> Void)? = nil) -> DateFormatter { var formatter = self.formatter(format: format, configuration: { $0.timeZone = self.region.timeZone $0.calendar = self.calendar $0.locale = self.region.locale }) configuration?(&formatter) return formatter } var sharedFormatter: DateFormatter { return DateFormatter.sharedFormatter(forRegion: region) } func toString(_ style: DateToStringStyles? = nil) -> String { guard let style = style else { return DateToStringStyles.standard.toString(self) } return style.toString(self) } func toFormat(_ format: String, locale: LocaleConvertible? = nil) -> String { guard let fixedLocale = locale else { return DateToStringStyles.custom(format).toString(self) } let fixedRegion = Region(calendar: region.calendar, zone: region.timeZone, locale: fixedLocale) let fixedDate = DateInRegion(date.date, region: fixedRegion) return DateToStringStyles.custom(format).toString(fixedDate) } func toRelative(since: DateInRegion?, dateTimeStyle: RelativeDateTimeFormatter.DateTimeStyle = .named, unitsStyle: RelativeDateTimeFormatter.UnitsStyle = .short) -> String { let formatter = RelativeDateTimeFormatter() formatter.dateTimeStyle = dateTimeStyle formatter.unitsStyle = unitsStyle return formatter.localizedString(for: self.date, relativeTo: since?.date ?? Date()) } func toISO(_ options: ISOFormatter.Options? = nil) -> String { return DateToStringStyles.iso( (options ?? ISOFormatter.Options([.withInternetDateTime])) ).toString(self) } func toDotNET() -> String { return DOTNETFormatter.format(self, options: nil) } func toRSS(alt: Bool) -> String { switch alt { case true: return DateToStringStyles.altRSS.toString(self) case false: return DateToStringStyles.rss.toString(self) } } func toSQL() -> String { return DateToStringStyles.sql.toString(self) } // MARK: - Conversion func convertTo(region: Region) -> DateInRegion { return DateInRegion(date, region: region) } // MARK: - Extract Time Components func toUnits(_ units: Set, to refDate: DateRepresentable) -> [Calendar.Component: Int] { let cal = region.calendar let components = cal.dateComponents(units, from: date, to: refDate.date) return components.toDict() } func toUnit(_ unit: Calendar.Component, to refDate: DateRepresentable) -> Int { let cal = region.calendar let components = cal.dateComponents([unit], from: date, to: refDate.date) return components.value(for: unit)! } } ================================================ FILE: Sources/SwiftDate/Formatters/DotNetParserFormatter.swift ================================================ // // 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 class DOTNETParser: StringToDateTransformable { internal static let pattern = "\\/Date\\((-?\\d+)((?:[\\+\\-]\\d+)?)\\)\\/" public static func parse(_ string: String) -> (seconds: TimeInterval, tz: TimeZone)? { do { let parser = try NSRegularExpression(pattern: DOTNETParser.pattern, options: .caseInsensitive) guard let match = parser.firstMatch(in: string, options: .reportCompletion, range: NSRange(location: 0, length: string.count)) else { return nil } guard let milliseconds = TimeInterval((string as NSString).substring(with: match.range(at: 1))) else { return nil } // Parse timezone let raw_tz = ((string as NSString).substring(with: match.range(at: 2)) as NSString) guard raw_tz.length > 1 else { return nil } let tz_sign: String = raw_tz.substring(to: 1) if tz_sign != "+" && tz_sign != "-" { return nil } let tz_hours: String = raw_tz.substring(with: NSRange(location: 1, length: 2)) let tz_minutes: String = raw_tz.substring(with: NSRange(location: 3, length: 2)) let tz_offset = (Int(tz_hours)! * 60 * 60) + ( Int(tz_minutes)! * 60 ) guard let tz_obj = TimeZone(secondsFromGMT: tz_offset) else { return nil } return ( (milliseconds / 1000), tz_obj ) } catch { return nil } } public static func parse(_ string: String, region: Region?, options: Any?) -> DateInRegion? { guard let result = DOTNETParser.parse(string) else { return nil } let regionSet = region ?? Region.ISO let adaptedRegion = Region(calendar: regionSet.calendar, zone: regionSet.timeZone, locale: regionSet.locale) return DateInRegion(seconds: result.seconds, region: adaptedRegion) } } public class DOTNETFormatter: DateToStringTrasformable { public static func format(_ date: DateRepresentable, options: Any?) -> String { let milliseconds = (date.date.timeIntervalSince1970 * 1000.0) let tzOffsets = (date.region.timeZone.secondsFromGMT(for: date.date) / 3600) let formattedStr = String(format: "/Date(%.0f%+03d00)/", milliseconds, tzOffsets) return formattedStr } } ================================================ FILE: Sources/SwiftDate/Formatters/Formatter+Protocols.swift ================================================ // // 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 protocol DateToStringTrasformable { static func format(_ date: DateRepresentable, options: Any?) -> String } public protocol StringToDateTransformable { static func parse(_ string: String, region: Region?, options: Any?) -> DateInRegion? } // MARK: - Formatters /// Format to represent a date to string /// /// - iso: standard iso format. The ISO8601 formatted date, time and millisec "yyyy-MM-dd'T'HH:mm:ssZZZZZ" /// - extended: Extended format. "eee dd-MMM-yyyy GG HH:mm:ss.SSS zzz" /// - rss: The RSS formatted date "EEE, d MMM yyyy HH:mm:ss ZZZ" i.e. "Fri, 09 Sep 2011 15:26:08 +0200" /// - altRSS: The Alternative RSS formatted date "d MMM yyyy HH:mm:ss ZZZ" i.e. "09 Sep 2011 15:26:08 +0200" /// - dotNet: The dotNet formatted date "/Date(%d%d)/" i.e. "/Date(1268123281843)/" /// - httpHeader: The http header formatted date "EEE, dd MMM yyyy HH:mm:ss zzz" i.e. "Tue, 15 Nov 1994 12:45:26 GMT" /// - custom: custom string format /// - standard: A generic standard format date i.e. "EEE MMM dd HH:mm:ss Z yyyy" /// - date: Date only format (short = "2/27/17", medium = "Feb 27, 2017", long = "February 27, 2017", full = "Monday, February 27, 2017" /// - time: Time only format (short = "2:22 PM", medium = "2:22:06 PM", long = "2:22:06 PM EST", full = "2:22:06 PM Eastern Standard Time" /// - dateTime: Date/Time format (short = "2/27/17, 2:22 PM", medium = "Feb 27, 2017, 2:22:06 PM", long = "February 27, 2017 at 2:22:06 PM EST", full = "Monday, February 27, 2017 at 2:22:06 PM Eastern Standard Time" public enum DateToStringStyles { case iso(_: ISOFormatter.Options) case extended case rss case altRSS case dotNet case httpHeader case sql case date(_: DateFormatter.Style) case time(_: DateFormatter.Style) case dateTime(_: DateFormatter.Style) case dateTimeMixed(dateStyle: DateFormatter.Style, timeStyle: DateFormatter.Style) case custom(_: String) case standard case relative(style: RelativeDateTimeFormatter.DateTimeStyle = .named, unitsStyle: RelativeDateTimeFormatter.UnitsStyle = .short) public func toString(_ date: DateRepresentable) -> String { switch self { case .iso(let opts): return ISOFormatter.format(date, options: opts) case .extended: return date.formatterForRegion(format: DateFormats.extended).string(from: date.date) case .rss: return date.formatterForRegion(format: DateFormats.rss).string(from: date.date) case .altRSS: return date.formatterForRegion(format: DateFormats.altRSS).string(from: date.date) case .sql: return date.formatterForRegion(format: DateFormats.sql).string(from: date.date) case .dotNet: return DOTNETFormatter.format(date, options: nil) case .httpHeader: return date.formatterForRegion(format: DateFormats.httpHeader).string(from: date.date) case .custom(let format): return date.formatterForRegion(format: format).string(from: date.date) case .standard: return date.formatterForRegion(format: DateFormats.standard).string(from: date.date) case .date(let style): return date.formatterForRegion(format: nil, configuration: { $0.dateStyle = style $0.timeStyle = .none }).string(from: date.date) case .time(let style): return date.formatterForRegion(format: nil, configuration: { $0.dateStyle = .none $0.timeStyle = style }).string(from: date.date) case .dateTime(let style): return date.formatterForRegion(format: nil, configuration: { $0.dateStyle = style $0.timeStyle = style }).string(from: date.date) case .dateTimeMixed(let dateStyle, let timeStyle): return date.formatterForRegion(format: nil, configuration: { $0.dateStyle = dateStyle $0.timeStyle = timeStyle }).string(from: date.date) case .relative(let style, let unitStyle): return date.toRelative(since: DateInRegion(), dateTimeStyle: style, unitsStyle: unitStyle) } } } // MARK: - Parsers /// String to date transform /// /// - iso: standard automatic iso parser (evaluate the date components automatically) /// - extended: Extended format. "eee dd-MMM-yyyy GG HH:mm:ss.SSS zzz" /// - rss: The RSS formatted date "EEE, d MMM yyyy HH:mm:ss ZZZ" i.e. "Fri, 09 Sep 2011 15:26:08 +0200" /// - altRSS: The Alternative RSS formatted date "d MMM yyyy HH:mm:ss ZZZ" i.e. "09 Sep 2011 15:26:08 +0200" /// - dotNet: The dotNet formatted date "/Date(%d%d)/" i.e. "/Date(1268123281843)/" /// - httpHeader: The http header formatted date "EEE, dd MMM yyyy HH:mm:ss zzz" i.e. "Tue, 15 Nov 1994 12:45:26 GMT" /// - strict: custom string format with lenient options active /// - custom: custom string format /// - standard: A generic standard format date i.e. "EEE MMM dd HH:mm:ss Z yyyy" public enum StringToDateStyles { case iso(_: ISOParser.Options) case extended case rss case altRSS case dotNet case sql case httpHeader case strict(_: String) case custom(_: String) case standard public func toDate(_ string: String, region: Region) -> DateInRegion? { switch self { case .iso(let options): return ISOParser.parse(string, region: region, options: options) case .custom(let format): return DateInRegion(string, format: format, region: region) case .extended: return DateInRegion(string, format: DateFormats.extended, region: region) case .sql: return DateInRegion(string, format: DateFormats.sql, region: region) case .rss: return DateInRegion(string, format: DateFormats.rss, region: Region.ISO)?.convertTo(locale: region.locale) case .altRSS: return DateInRegion(string, format: DateFormats.altRSS, region: Region.ISO)?.convertTo(locale: region.locale) case .dotNet: return DOTNETParser.parse(string, region: region, options: nil) case .httpHeader: return DateInRegion(string, format: DateFormats.httpHeader, region: region) case .standard: return DateInRegion(string, format: DateFormats.standard, region: region) case .strict(let format): let formatter = DateFormatter.sharedFormatter(forRegion: region, format: format) formatter.isLenient = false guard let absDate = formatter.date(from: string) else { return nil } return DateInRegion(absDate, region: region) } } } ================================================ FILE: Sources/SwiftDate/Formatters/ISOFormatter.swift ================================================ // // 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 class ISOFormatter: DateToStringTrasformable { public struct Options: OptionSet { public let rawValue: Int public init(rawValue: Int) { self.rawValue = rawValue } /// The date representation includes the year. The format for year is inferred based on the other specified options. /// - If withWeekOfYear is specified, YYYY is used. /// - Otherwise, yyyy is used. public static let withYear = ISOFormatter.Options(rawValue: 1 << 0) /// The date representation includes the month. The format for month is MM. public static let withMonth = ISOFormatter.Options(rawValue: 1 << 1) /// The date representation includes the week of the year. /// The format for week of year is ww, including the W prefix. public static let withWeekOfYear = ISOFormatter.Options(rawValue: 1 << 2) /// The date representation includes the day. The format for day is inferred based on provided options: /// - If withMonth is specified, dd is used. /// - If withWeekOfYear is specified, ee is used. /// - Otherwise, DDD is used. public static let withDay = ISOFormatter.Options(rawValue: 1 << 3) /// The date representation includes the time. The format for time is HH:mm:ss. public static let withTime = ISOFormatter.Options(rawValue: 1 << 4) /// The date representation includes the timezone. The format for timezone is ZZZZZ. public static let withTimeZone = ISOFormatter.Options(rawValue: 1 << 5) /// The date representation uses a space ( ) instead of T between the date and time. public static let withSpaceBetweenDateAndTime = ISOFormatter.Options(rawValue: 1 << 6) /// The date representation uses the dash separator (-) in the date. public static let withDashSeparatorInDate = ISOFormatter.Options(rawValue: 1 << 7) /// The date representation uses the colon separator (:) in the time. public static let withFullDate = ISOFormatter.Options(rawValue: 1 << 8) /// The date representation includes the hour, minute, and second. public static let withFullTime = ISOFormatter.Options(rawValue: 1 << 9) /// The format used for internet date times, according to the RFC 3339 standard. /// Equivalent to specifying withFullDate, withFullTime, withDashSeparatorInDate, /// withColonSeparatorInTime, and withColonSeparatorInTimeZone. public static let withInternetDateTime = ISOFormatter.Options(rawValue: 1 << 10) // The format used for internet date times; it's similar to .withInternetDateTime // but include milliseconds ('yyyy-MM-dd'T'HH:mm:ss.SSSZZZZZ'). public static let withInternetDateTimeExtended = ISOFormatter.Options(rawValue: 1 << 11) /// Print the timezone in format `ZZZ` instead of `ZZZZZ` /// An example outout maybe be `+0200` instead of `+02:00`. public static let withoutTZSeparators = ISOFormatter.Options(rawValue: 1 << 12) /// Evaluate formatting string public var dateFormat: String { if contains(.withInternetDateTimeExtended) || contains(.withoutTZSeparators) { if contains(.withoutTZSeparators) { return "yyyy-MM-dd'T'HH:mm:ss.SSSZZZ" } return "yyyy-MM-dd'T'HH:mm:ss.SSSZZZZZ" } if contains(.withInternetDateTime) { if contains(.withoutTZSeparators) { return "yyyy-MM-dd'T'HH:mm:ss.SSSZZZ" } return "yyyy-MM-dd'T'HH:mm:ssZZZZZ" } var format: String = "" if contains(.withFullDate) { format += "yyyy-MM-dd" } else { if contains(.withYear) { if contains(.withWeekOfYear) { format += "YYYY" } else if contains(.withMonth) || contains(.withDay) { format += "yyyy" } else { // not valid } } if contains(.withMonth) { if contains(.withYear) || contains(.withDay) || contains(.withWeekOfYear) { format += "MM" } else { // not valid } } if contains(.withWeekOfYear) { if contains(.withDay) { format += "'W'ww" } else { if contains(.withYear) || contains(.withMonth) { if contains(.withDashSeparatorInDate) { format += "-'W'ww" } else { format += "'W'ww" } } else { // not valid } } } if contains(.withDay) { if contains(.withWeekOfYear) { format += "FF" } else if contains(.withMonth) { format += "dd" } else if contains(.withYear) { if contains(.withDashSeparatorInDate) { format += "-DDD" } else { format += "DDD" } } else { // not valid } } } let hasDate = (contains(.withFullDate) || contains(.withMonth) || contains(.withDay) || contains(.withWeekOfYear) || contains(.withYear)) if hasDate && (contains(.withFullTime) || contains(.withTimeZone) || contains(.withTime)) { if contains(.withSpaceBetweenDateAndTime) { format += " " } else { format += "'T'" } } if contains(.withFullTime) { format += "HH:mm:ssZZZZZ" } else { if contains(.withTime) { format += "HH:mm:ss" } if contains(.withTimeZone) { if contains(.withoutTZSeparators) { return "yyyy-MM-dd'T'HH:mm:ss.SSSZZZ" } format += "ZZZZZ" } } return format } } public static func format(_ date: DateRepresentable, options: Any?) -> String { let formatOptions = ((options as? ISOFormatter.Options) ?? ISOFormatter.Options([.withInternetDateTime])) let formatter = date.formatter(format: formatOptions.dateFormat) { $0.locale = Locales.englishUnitedStatesComputer.toLocale() // fix for 12/24h $0.timeZone = date.region.timeZone $0.calendar = Calendars.gregorian.toCalendar() } return formatter.string(from: date.date) } } ================================================ FILE: Sources/SwiftDate/Formatters/ISOParser.swift ================================================ // // 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. // // swiftlint:disable file_length import Foundation /// This defines all possible errors you can encounter parsing ISO8601 string /// /// - eof: end of file /// - notDigit: expected digit, value cannot be parsed as int /// - notDouble: expected double digit, value cannot be parsed as double /// - invalid: invalid state reached. Something in the format is not correct public enum ISO8601ParserError: Error { case eof case notDigit case notDouble case invalid } fileprivate extension Int { /// Return `true` if current year is a leap year, `false` otherwise var isLeapYear: Bool { return ((self % 4) == 0) && (((self % 100) != 0) || ((self % 400) == 0)) } } // MARK: - Internal Extension for UnicodeScalar type internal extension UnicodeScalar { /// return `true` if current character is a digit (arabic), `false` otherwise var isDigit: Bool { return "0"..."9" ~= self } /// return `true` if current character is a space var isSpace: Bool { return CharacterSet.whitespaces.contains(self) } } /// This is the ISO8601 Parser class: it evaluates automatically the format of the ISO8601 date /// and attempt to parse it in a valid `Date` object. /// Resulting date also includes Time Zone settings and a property which allows you to inspect /// single date components. /// /// This work is inspired to the original ISO8601DateFormatter class written in ObjC by /// Peter Hosey (available here https://bitbucket.org/boredzo/iso-8601-parser-unparser). /// I've made a Swift porting and fixed some issues when parsing several ISO8601 date variants. // swiftlint:disable type_body_length public class ISOParser: StringToDateTransformable { /// Internal structure internal enum Weekday: Int { case monday = 0 case tuesday = 1 case wednesday = 2 case thursday = 3 } public struct Options { /// Time separator character. By default is `:`. var time_separator: ISOParser.ISOChar = ":" /// Strict parsing. By default is `false`. var strict: Bool = false public init(strict: Bool = false) { self.strict = strict } } /// Some typealias to make the code cleaner public typealias ISOString = String.UnicodeScalarView public typealias ISOIndex = String.UnicodeScalarView.Index public typealias ISOChar = UnicodeScalar public typealias ISOParsedDate = (date: Date?, timezone: TimeZone?) /// This represent the internal parser status representation public struct ParsedDate { /// Type of date parsed /// /// - monthAndDate: month and date style /// - week: date with week number /// - dateOnly: date only // swiftlint:disable nesting public enum DateStyle { case monthAndDate case week case dateOnly } /// Parsed year value var year: Int = 0 /// Parsed month or week number var month_or_week: Int = 0 /// Parsed day value var day: Int = 0 /// Parsed hour value var hour: Int = 0 /// Parsed minutes value var minute: TimeInterval = 0.0 /// Parsed seconds value var seconds: TimeInterval = 0.0 /// Parsed nanoseconds value var nanoseconds: TimeInterval = 0.0 /// Parsed weekday number (1=monday, 7=sunday) /// If `nil` source string has not specs about weekday. var weekday: Int? /// Timezone parsed hour value var tz_hour: Int = 0 /// Timezone parsed minute value var tz_minute: Int = 0 /// Type of parsed date var type: DateStyle = .monthAndDate /// Parsed timezone object var timezone: TimeZone? } /// Source generation calendar. private var srcCalendar = Calendars.gregorian.toCalendar() /// Source raw parsed values private var date = ParsedDate() /// Source string represented as unicode scalars private var string: ISOString /// Current position of the parser in source string. /// Initially is equal to `string.startIndex` private var cIdx: ISOIndex /// Just a shortcut to the last index in source string private var eIdx: ISOIndex /// Lenght of the string private var length: Int /// Number of hyphens characters found before any value /// Consequential "-" are used to define implicit values in dates. private var hyphens: Int = 0 /// Private date components used for default values private var now_cmps: DateComponents /// Configuration used for parser private var options: ISOParser.Options /// Date components parsed private(set) var date_components: DateComponents? /// Parsed date private(set) var parsedDate: Date? /// Parsed timezone private(set) var parsedTimeZone: TimeZone? /// Date adjusted at parsed timezone private var dateInTimezone: Date? { get { srcCalendar.timeZone = date.timezone ?? TimeZone(identifier: "UTC")! return srcCalendar.date(from: date_components!) } } /// Initialize a new parser with a source ISO8601 string to parse /// Parsing is done during initialization; any exception is reported /// before allocating. /// /// - Parameters: /// - src: source ISO8601 string /// - config: configuration used for parsing /// - Throws: throw an `ISO8601Error` if parsing operation fails public init?(_ src: String, options: ISOParser.Options? = nil) { let src_trimmed = src.trimmingCharacters(in: CharacterSet.whitespacesAndNewlines) guard src_trimmed.count > 0 else { return nil } string = src_trimmed.unicodeScalars length = src_trimmed.count cIdx = string.startIndex eIdx = string.endIndex self.options = (options ?? ISOParser.Options()) self.now_cmps = srcCalendar.dateComponents([.year, .month, .day], from: Date()) var idx = cIdx while idx < eIdx { if string[idx] == "-" { hyphens += 1 } else { break } idx = string.index(after: idx) } do { try parse() } catch { return nil } } // MARK: - Internal Parser /// Private parsing function /// /// - Throws: throw an `ISO8601Error` if parsing operation fails @discardableResult private func parse() throws -> ISOParsedDate { // PARSE DATE if current() == "T" { // There is no date here, only a time. // Set the date to now; then we'll parse the time. next() guard current()?.isDigit ?? false else { throw ISO8601ParserError.invalid } date.year = now_cmps.year! date.month_or_week = now_cmps.month! date.day = now_cmps.day! } else { moveUntil(is: "-") let is_time_only = (string.contains("T") == false && string.contains(":") && !string.contains("-")) if is_time_only == false { var (num_digits, segment) = try read_int() switch num_digits { case 0: try parse_digits_0(num_digits, &segment) case 8: try parse_digits_8(num_digits, &segment) case 6: try parse_digits_6(num_digits, &segment) case 4: try parse_digits_4(num_digits, &segment) case 5: try parse_digits_5(num_digits, &segment) case 1: try parse_digits_1(num_digits, &segment) case 2: try parse_digits_2(num_digits, &segment) case 7: try parse_digits_7(num_digits, &segment) //YYYY DDD (ordinal date) case 3: try parse_digits_3(num_digits, &segment) //--DDD (ordinal date, implicit year) default: throw ISO8601ParserError.invalid } } else { date.year = now_cmps.year! date.month_or_week = now_cmps.month! date.day = now_cmps.day! } } var hasTime = false if current()?.isSpace ?? false || current() == "T" { hasTime = true next() } // PARSE TIME if current()?.isDigit ?? false == true { let time_sep = options.time_separator let hasTimeSeparator = string.contains(time_sep) date.hour = try read_int(2).value if hasTimeSeparator == false && hasTime { date.minute = TimeInterval(try read_int(2).value) } else if current() == time_sep { next() if time_sep == "," || time_sep == "." { //We can't do fractional minutes when '.' is the segment separator. //Only allow whole minutes and whole seconds. date.minute = TimeInterval(try read_int(2).value) if current() == time_sep { next() date.seconds = TimeInterval(try read_int(2).value) } } else { //Allow a fractional minute. //If we don't get a fraction, look for a seconds segment. //Otherwise, the fraction of a minute is the seconds. date.minute = try read_double().value if current() != ":" { var int_part: Double = 0.0 var frac_part: Double = 0.0 frac_part = modf(date.minute, &int_part) date.minute = int_part date.seconds = frac_part if date.seconds > Double.ulpOfOne { // Convert fraction (e.g. .5) into seconds (e.g. 30). date.seconds *= 60 } else if current() == time_sep { next() // date.seconds = try read_double().value let value = try modf(read_double().value) date.nanoseconds = TimeInterval(round(value.1 * 1000) * 1_000_000) date.seconds = TimeInterval(value.0) } } else { // fractional minutes next() let value = try modf(read_double().value) date.nanoseconds = TimeInterval(round(value.1 * 1000) * 1_000_000) date.seconds = TimeInterval(value.0) } } } if options.strict == false { if cIdx != eIdx && current()?.isSpace ?? false == true { next() } } if cIdx != eIdx { switch current() { case "Z": date.timezone = TimeZone(abbreviation: "UTC") case "+", "-": let is_negative = current() == "-" next() if current()?.isDigit ?? false == true { //Read hour offset. date.tz_hour = try read_int(2).value if is_negative == true { date.tz_hour = -date.tz_hour } // Optional separator if current() == time_sep { next() } if current()?.isDigit ?? false { // Read minute offset date.tz_minute = try read_int(2).value if is_negative == true { date.tz_minute = -date.tz_minute } } let timezone_offset = (date.tz_hour * 3600) + (date.tz_minute * 60) date.timezone = TimeZone(secondsFromGMT: timezone_offset) } default: break } } } date_components = DateComponents() date_components!.year = date.year date_components!.day = date.day date_components!.hour = date.hour date_components!.minute = Int(date.minute) date_components!.second = Int(date.seconds) date_components!.nanosecond = Int(date.nanoseconds) switch date.type { case .monthAndDate: date_components!.month = date.month_or_week case .week: //Adapted from . //This works by converting the week date into an ordinal date, then letting the next case handle it. let prevYear = date.year - 1 let YY = prevYear % 100 let prevC = prevYear - YY let prevG = YY + YY / 4 let isLeapYear = (((prevC / 100) % 4) * 5) let jan1Weekday = ((isLeapYear + prevG) % 7) var day = ((8 - jan1Weekday) + (7 * (jan1Weekday > Weekday.thursday.rawValue ? 1 : 0))) day += (date.day - 1) + (7 * (date.month_or_week - 2)) if let weekday = date.weekday { //date_components!.weekday = weekday date_components!.day = day + weekday } else { date_components!.day = day } case .dateOnly: //An "ordinal date". break } //cfg.calendar.timeZone = date.timezone ?? TimeZone(identifier: "UTC")! //parsedDate = cfg.calendar.date(from: date_components!) let tz = date.timezone ?? TimeZone(identifier: "UTC")! parsedTimeZone = tz srcCalendar.timeZone = tz parsedDate = srcCalendar.date(from: date_components!) return (parsedDate, parsedTimeZone) } private func parse_digits_3(_ num_digits: Int, _ segment: inout Int) throws { //Technically, the standard only allows one hyphen. But it says that two hyphens is the logical implementation, and one was dropped for brevity. So I have chosen to allow the missing hyphen. if hyphens < 1 || (hyphens > 2 && options.strict == false) { throw ISO8601ParserError.invalid } date.day = segment date.year = now_cmps.year! date.type = .dateOnly if options.strict == true && (date.day > (365 + (date.year.isLeapYear ? 1 : 0))) { throw ISO8601ParserError.invalid } } private func parse_digits_7(_ num_digits: Int, _ segment: inout Int) throws { guard hyphens == 0 else { throw ISO8601ParserError.invalid } date.day = segment % 1000 date.year = segment / 1000 date.type = .dateOnly if options.strict == true && (date.day > (365 + (date.year.isLeapYear ? 1 : 0))) { throw ISO8601ParserError.invalid } } private func parse_digits_2(_ num_digits: Int, _ segment: inout Int) throws { func parse_hyphens_3(_ num_digits: Int, _ segment: inout Int) throws { date.year = now_cmps.year! date.month_or_week = now_cmps.month! date.day = segment } func parse_hyphens_2(_ num_digits: Int, _ segment: inout Int) throws { date.year = now_cmps.year! date.month_or_week = segment if current() == "-" { next() date.day = try read_int(2).value } else { date.day = 1 } } func parse_hyphens_1(_ num_digits: Int, _ segment: inout Int) throws { let current_year = now_cmps.year! let current_century = (current_year % 100) date.year = segment + (current_year - current_century) if num_digits == 1 { // implied decade date.year += current_century - (current_year % 10) } if current() == "-" { next() if current() == "W" { next() date.type = .week } date.month_or_week = try read_int(2).value if current() == "-" { next() if date.type == .week { // weekday number let weekday = try read_int().value if weekday > 7 { throw ISO8601ParserError.invalid } date.weekday = weekday } else { date.day = try read_int().value if date.day == 0 { date.day = 1 } if date.month_or_week == 0 { date.month_or_week = 1 } } } else { date.day = 1 } } else { date.month_or_week = 1 date.day = 1 } } func parse_hyphens_0(_ num_digits: Int, _ segment: inout Int) throws { if current() == "-" { // Implicit century date.year = now_cmps.year! date.year -= (date.year % 100) date.year += segment next() if current() == "W" { try parseWeekAndDay() } else if current()?.isDigit ?? false == false { try centuryOnly(&segment) } else { // Get month and/or date. let (v_count, v_seg) = try read_int() switch v_count { case 4: // YY-MMDD date.day = v_seg % 100 date.month_or_week = v_seg / 100 case 1: // YY-M; YY-M-DD (extension) if options.strict == true { throw ISO8601ParserError.invalid } case 2: // YY-MM; YY-MM-DD date.month_or_week = v_seg if current() == "-" { next() if current()?.isDigit ?? false == true { date.day = try read_int(2).value } else { date.day = 1 } } else { date.day = 1 } case 3: // Ordinal date date.day = v_seg date.type = .dateOnly default: break } } } else if current() == "W" { date.year = now_cmps.year! date.year -= (date.year % 100) date.year += segment try parseWeekAndDay() } else { try centuryOnly(&segment) } } switch hyphens { case 0: try parse_hyphens_0(num_digits, &segment) case 1: try parse_hyphens_1(num_digits, &segment) //-YY; -YY-MM (implicit century) case 2: try parse_hyphens_2(num_digits, &segment) //--MM; --MM-DD case 3: try parse_hyphens_3(num_digits, &segment) //---DD default: throw ISO8601ParserError.invalid } } private func parse_digits_1(_ num_digits: Int, _ segment: inout Int) throws { if options.strict == true { // Two digits only - never just one. guard hyphens == 1 else { throw ISO8601ParserError.invalid } if current() == "-" { next() } next() guard current() == "W" else { throw ISO8601ParserError.invalid } date.year = now_cmps.year! date.year -= (date.year % 10) date.year += segment } else { try parse_digits_2(num_digits, &segment) } } private func parse_digits_5(_ num_digits: Int, _ segment: inout Int) throws { guard hyphens == 0 else { throw ISO8601ParserError.invalid } // YYDDD date.year = now_cmps.year! date.year -= (date.year % 100) date.year += segment / 1000 date.day = segment % 1000 date.type = .dateOnly } private func parse_digits_4(_ num_digits: Int, _ segment: inout Int) throws { func parse_hyphens_0(_ num_digits: Int, _ segment: inout Int) throws { date.year = segment if current() == "-" { next() } if current()?.isDigit ?? false == false { if current() == "W" { try parseWeekAndDay() } else { date.month_or_week = 1 date.day = 1 } } else { let (v_num, v_seg) = try read_int() switch v_num { case 4: // MMDD date.day = v_seg % 100 date.month_or_week = v_seg / 100 case 2: // MM date.month_or_week = v_seg if current() == "-" { next() } if current()?.isDigit ?? false == false { date.day = 1 } else { date.day = try read_int().value } case 3: // DDD date.day = v_seg % 1000 date.type = .dateOnly if options.strict == true && (date.day > 365 + (date.year.isLeapYear ? 1 : 0)) { throw ISO8601ParserError.invalid } default: throw ISO8601ParserError.invalid } } } func parse_hyphens_1(_ num_digits: Int, _ segment: inout Int) throws { date.month_or_week = segment % 100 date.year = segment / 100 if current() == "-" { next() } if current()?.isDigit ?? false == false { date.day = 1 } else { date.day = try read_int().value } } func parse_hyphens_2(_ num_digits: Int, _ segment: inout Int) throws { date.day = segment % 100 date.month_or_week = segment / 100 date.year = now_cmps.year! } switch hyphens { case 0: try parse_hyphens_0(num_digits, &segment) // YYYY case 1: try parse_hyphens_1(num_digits, &segment) // YYMM case 2: try parse_hyphens_2(num_digits, &segment) // MMDD default: throw ISO8601ParserError.invalid } } private func parse_digits_6(_ num_digits: Int, _ segment: inout Int) throws { // YYMMDD (implicit century) guard hyphens == 0 else { throw ISO8601ParserError.invalid } date.day = segment % 100 segment /= 100 date.month_or_week = segment % 100 date.year = now_cmps.year! date.year -= (date.year % 100) date.year += (segment / 100) } private func parse_digits_8(_ num_digits: Int, _ segment: inout Int) throws { // YYYY MM DD guard hyphens == 0 else { throw ISO8601ParserError.invalid } date.day = segment % 100 segment /= 100 date.month_or_week = segment % 100 date.year = segment / 100 } private func parse_digits_0(_ num_digits: Int, _ segment: inout Int) throws { guard current() == "W" else { throw ISO8601ParserError.invalid } if seek(1) == "-" && isDigit(seek(2)) && ((hyphens == 1 || hyphens == 2) && options.strict == false) { date.year = now_cmps.year! date.month_or_week = 1 next(2) try parseDayAfterWeek() } else if hyphens == 1 { date.year = now_cmps.year! if current() == "W" { next() date.month_or_week = try read_int(2).value date.type = .week try parseWeekday() } else { try parseDayAfterWeek() } } else { throw ISO8601ParserError.invalid } } private func parseWeekday() throws { if current() == "-" { next() } let weekday = try read_int().value if weekday > 7 { throw ISO8601ParserError.invalid } date.type = .week date.weekday = weekday } private func parseWeekAndDay() throws { next() if current()?.isDigit ?? false == false { //Not really a week-based date; just a year followed by '-W'. guard options.strict == false else { throw ISO8601ParserError.invalid } date.month_or_week = 1 date.day = 1 } else { date.month_or_week = try read_int(2).value try parseWeekday() } } private func parseDayAfterWeek() throws { date.day = current()?.isDigit ?? false == true ? try read_int(2).value : 1 date.type = .week } private func centuryOnly(_ segment: inout Int) throws { date.year = segment * 100 + now_cmps.year! % 100 date.month_or_week = 1 date.day = 1 } /// Return `true` if given character is a char /// /// - Parameter char: char to evaluate /// - Returns: `true` if char is a digit, `false` otherwise private func isDigit(_ char: UnicodeScalar?) -> Bool { guard let char = char else { return false } return char.isDigit } // MARK: - Scanner internal functions /// Get the value at specified offset from current scanner position without /// moving the current scanner's index. /// /// - Parameter offset: offset to move /// - Returns: char at given position, `nil` if not found @discardableResult public func seek(_ offset: Int = 1) -> ISOChar? { let move_idx = string.index(cIdx, offsetBy: offset) guard move_idx < eIdx else { return nil } return string[move_idx] } /// Return the char at the current position of the scanner /// /// - Parameter next: if `true` return the current char and move to the next position /// - Returns: the char sat the current position of the scanner @discardableResult public func current(_ next: Bool = false) -> ISOChar? { guard cIdx != eIdx else { return nil } let current = string[cIdx] if next == true { cIdx = string.index(after: cIdx) } return current } /// Move by `offset` characters the index of the scanner and return the char at the current /// position. If EOF is reached `nil` is returned. /// /// - Parameter offset: offset value (use negative number to move backwards) /// - Returns: character at the current position. @discardableResult private func next(_ offset: Int = 1) -> ISOChar? { let next = string.index(cIdx, offsetBy: offset) guard next < eIdx else { return nil } cIdx = next return string[cIdx] } /// Read from the current scanner index and parse the value as Int. /// /// - Parameter max_count: number of characters to move. If nil scanners continues until a non /// digit value is encountered. /// - Returns: parsed value /// - Throws: throw an exception if parser fails @discardableResult private func read_int(_ max_count: Int? = nil) throws -> (count: Int, value: Int) { var move_idx = cIdx var count = 0 while move_idx < eIdx { if let max = max_count, count >= max { break } if string[move_idx].isDigit == false { break } count += 1 move_idx = string.index(after: move_idx) } let raw_value = String(string[cIdx.. (count: Int, value: Double) { var move_idx = cIdx var count = 0 var fractional_start = false while move_idx < eIdx { let char = string[move_idx] if char == "." || char == "," { if fractional_start == true { throw ISO8601ParserError.notDouble } else { fractional_start = true } } else { if char.isDigit == false { break } } count += 1 move_idx = string.index(after: move_idx) } let raw_value = String(string[cIdx.. Int { var move_idx = cIdx var count = 0 while move_idx < eIdx { guard string[move_idx] == char else { break } move_idx = string.index(after: move_idx) count += 1 } cIdx = move_idx return count } /// Move the current scanner index to the next position until passed `char` value is /// encountered or `eof` is reached. /// /// - Parameter char: char /// - Returns: the number of characters passed @discardableResult private func moveUntil(isNot char: UnicodeScalar) -> Int { var move_idx = cIdx var count = 0 while move_idx < eIdx { guard string[move_idx] != char else { break } move_idx = string.index(after: move_idx) count += 1 } cIdx = move_idx return count } /// Return a date parsed from a valid ISO8601 string /// /// - Parameter string: source string /// - Returns: a valid `Date` object or `nil` if date cannot be parsed public static func date(from string: String) -> ISOParsedDate? { guard let parser = ISOParser(string) else { return nil } return (parser.parsedDate, parser.parsedTimeZone) } public static func parse(_ string: String, region: Region?, options: Any?) -> DateInRegion? { let formatOptions = options as? ISOParser.Options guard let parser = ISOParser(string, options: formatOptions), let date = parser.parsedDate else { return nil } let parsedRegion = Region(calendar: region?.calendar ?? Region.ISO.calendar, zone: (region?.timeZone ?? parser.parsedTimeZone ?? Region.ISO.timeZone), locale: region?.locale ?? Region.ISO.locale) return DateInRegion(date, region: parsedRegion) } } ================================================ FILE: Sources/SwiftDate/Foundation+Extras/DateComponents+Extras.swift ================================================ // // 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: - Date Components Extensions public extension Calendar.Component { /// Return a description of the calendar component in seconds. /// Note: Values for `era`,`weekday`,`weekdayOrdinal`, `yearForWeekOfYear`, `calendar`, `timezone` are `nil`. /// For `weekOfYear` it return the same value of `weekOfMonth`. var timeInterval: Double? { switch self { case .era: return nil case .year: return (Calendar.Component.day.timeInterval! * 365.0) case .month: return (Calendar.Component.minute.timeInterval! * 43800) case .day: return 86400 case .hour: return 3600 case .minute: return 60 case .second: return 1 case .quarter: return (Calendar.Component.day.timeInterval! * 91.25) case .weekOfMonth, .weekOfYear: return (Calendar.Component.day.timeInterval! * 7) case .nanosecond: return 1e-9 default: return nil } } /// Return the localized identifier of a calendar component /// /// - parameter unit: unit /// - parameter value: value /// /// - returns: return the plural or singular form of the time unit used to compose a valid identifier for search a localized /// string in resource bundle internal func localizedKey(forValue value: Int) -> String { let locKey = localizedKey let absValue = abs(value) switch absValue { case 0: // zero difference for this unit return "0\(locKey)" case 1: // one unit of difference return locKey default: // more than 1 unit of difference return "\(locKey)\(locKey)" } } internal var localizedKey: String { switch self { case .year: return "y" case .month: return "m" case .weekOfYear: return "w" case .day: return "d" case .hour: return "h" case .minute: return "M" case .second: return "s" default: return "" } } } public extension DateComponents { /// Shortcut for 'all calendar components'. static var allComponentsSet: Set { return [.era, .year, .month, .day, .hour, .minute, .second, .weekday, .weekdayOrdinal, .quarter, .weekOfMonth, .weekOfYear, .yearForWeekOfYear, .nanosecond, .calendar, .timeZone] } internal static let allComponents: [Calendar.Component] = [.nanosecond, .second, .minute, .hour, .day, .month, .year, .yearForWeekOfYear, .weekOfYear, .weekday, .quarter, .weekdayOrdinal, .weekOfMonth] /// This function return the absolute amount of seconds described by the components of the receiver. /// Note: evaluated value maybe not strictly exact because it ignore the context (calendar/date) of /// the date components. In details: /// - The following keys are ignored: `era`,`weekday`,`weekdayOrdinal`, /// `weekOfYear`, `yearForWeekOfYear`, `calendar`, `timezone /// /// Some other values dependant from dates are fixed. This is a complete table: /// - `year` is 365.0 `days` /// - `month` is 30.4167 `days` (or 43800 minutes) /// - `quarter` is 91.25 `days` /// - `weekOfMonth` is 7 `days` /// - `day` is 86400 `seconds` /// - `hour` is 3600 `seconds` /// - `minute` is 60 `seconds` /// - `nanosecond` is 1e-9 `seconds` var timeInterval: TimeInterval { var totalAmount: TimeInterval = 0 DateComponents.allComponents.forEach { if let multipler = $0.timeInterval, let value = value(for: $0), value != Int(NSDateComponentUndefined) { totalAmount += (TimeInterval(value) * multipler) } } return totalAmount } /// Create a new `DateComponents` instance with builder pattern. /// /// - Parameter builder: callback for builder /// - Returns: new instance static func create(_ builder: ((inout DateComponents) -> Void)) -> DateComponents { var components = DateComponents() builder(&components) return components } /// Return the current date plus the receive's interval /// The default calendar used is the `SwiftDate.defaultRegion`'s calendar. var fromNow: Date { return SwiftDate.defaultRegion.calendar.date(byAdding: (self as DateComponents) as DateComponents, to: Date() as Date)! } /// Returns the current date minus the receiver's interval /// The default calendar used is the `SwiftDate.defaultRegion`'s calendar. var ago: Date { return SwiftDate.defaultRegion.calendar.date(byAdding: -self as DateComponents, to: Date())! } /// - returns: the date that will occur once the receiver's components pass after the provide date. func from(_ date: DateRepresentable) -> Date? { return date.calendar.date(byAdding: self, to: date.date) } /// Return `true` if all interval components are zeroes var isZero: Bool { for component in DateComponents.allComponents { if let value = value(for: component), value != 0 { return false } } return true } /// Transform a `DateComponents` instance to a dictionary where key is the `Calendar.Component` and value is the /// value associated. /// /// - returns: a new `[Calendar.Component : Int]` dict representing source `DateComponents` instance internal func toDict() -> [Calendar.Component: Int] { var list: [Calendar.Component: Int] = [:] DateComponents.allComponents.forEach { component in let value = self.value(for: component) if value != nil && value != Int(NSDateComponentUndefined) { list[component] = value! } } return list } /// Alter date components specified into passed dictionary. /// /// - Parameter components: components dictionary with their values. internal mutating func alterComponents(_ components: [Calendar.Component: Int?]) { components.forEach { if let v = $0.value { setValue(v, for: $0.key) } } } /// Adds two NSDateComponents and returns their combined individual components. static func + (lhs: DateComponents, rhs: DateComponents) -> DateComponents { return combine(lhs, rhs: rhs, transform: +) } /// Subtracts two NSDateComponents and returns the relative difference between them. static func - (lhs: DateComponents, rhs: DateComponents) -> DateComponents { return lhs + (-rhs) } /// Applies the `transform` to the two `T` provided, defaulting either of them if it's /// `nil` internal static func bimap(_ a: T?, _ b: T?, default: T, _ transform: (T, T) -> T) -> T? { if a == nil && b == nil { return nil } return transform(a ?? `default`, b ?? `default`) } /// - returns: a new `NSDateComponents` that represents the negative of all values within the /// components that are not `NSDateComponentUndefined`. static prefix func - (rhs: DateComponents) -> DateComponents { var components = DateComponents() components.era = rhs.era.map(-) components.year = rhs.year.map(-) components.month = rhs.month.map(-) components.day = rhs.day.map(-) components.hour = rhs.hour.map(-) components.minute = rhs.minute.map(-) components.second = rhs.second.map(-) components.nanosecond = rhs.nanosecond.map(-) components.weekday = rhs.weekday.map(-) components.weekdayOrdinal = rhs.weekdayOrdinal.map(-) components.quarter = rhs.quarter.map(-) components.weekOfMonth = rhs.weekOfMonth.map(-) components.weekOfYear = rhs.weekOfYear.map(-) components.yearForWeekOfYear = rhs.yearForWeekOfYear.map(-) return components } /// Combines two date components using the provided `transform` on all /// values within the components that are not `NSDateComponentUndefined`. private static func combine(_ lhs: DateComponents, rhs: DateComponents, transform: (Int, Int) -> Int) -> DateComponents { var components = DateComponents() components.era = bimap(lhs.era, rhs.era, default: 0, transform) components.year = bimap(lhs.year, rhs.year, default: 0, transform) components.month = bimap(lhs.month, rhs.month, default: 0, transform) components.day = bimap(lhs.day, rhs.day, default: 0, transform) components.hour = bimap(lhs.hour, rhs.hour, default: 0, transform) components.minute = bimap(lhs.minute, rhs.minute, default: 0, transform) components.second = bimap(lhs.second, rhs.second, default: 0, transform) components.nanosecond = bimap(lhs.nanosecond, rhs.nanosecond, default: 0, transform) components.weekday = bimap(lhs.weekday, rhs.weekday, default: 0, transform) components.weekdayOrdinal = bimap(lhs.weekdayOrdinal, rhs.weekdayOrdinal, default: 0, transform) components.quarter = bimap(lhs.quarter, rhs.quarter, default: 0, transform) components.weekOfMonth = bimap(lhs.weekOfMonth, rhs.weekOfMonth, default: 0, transform) components.weekOfYear = bimap(lhs.weekOfYear, rhs.weekOfYear, default: 0, transform) components.yearForWeekOfYear = bimap(lhs.yearForWeekOfYear, rhs.yearForWeekOfYear, default: 0, transform) return components } /// Subscription support for `DateComponents` instances. /// ie. `cmps[.day] = 5` /// /// Note: This does not take into account any built-in errors, `Int.max` returned instead of `nil`. /// /// - Parameter component: component to get subscript(component: Calendar.Component) -> Int? { switch component { case .era: return era case .year: return year case .month: return month case .day: return day case .hour: return hour case .minute: return minute case .second: return second case .weekday: return weekday case .weekdayOrdinal: return weekdayOrdinal case .quarter: return quarter case .weekOfMonth: return weekOfMonth case .weekOfYear: return weekOfYear case .yearForWeekOfYear: return yearForWeekOfYear case .nanosecond: return nanosecond default: return nil // `calendar` and `timezone` are ignored in this context } } /// Express a `DateComponents` instance in another time unit you choose. /// /// - parameter component: time component /// - parameter calendar: context calendar to use /// /// - returns: the value of interval expressed in selected `Calendar.Component` func `in`(_ component: Calendar.Component, of calendar: CalendarConvertible? = nil) -> Int? { let cal = (calendar?.toCalendar() ?? SwiftDate.defaultRegion.calendar) let dateFrom = Date() let dateTo = (dateFrom + self) let components: Set = [component] let value = cal.dateComponents(components, from: dateFrom, to: dateTo).value(for: component) return value } /// Express a `DateComponents` instance in a set of time units you choose. /// /// - Parameters: /// - component: time component /// - calendar: context calendar to use /// - Returns: a dictionary of extract values. func `in`(_ components: Set, of calendar: CalendarConvertible? = nil) -> [Calendar.Component: Int] { let cal = (calendar?.toCalendar() ?? SwiftDate.defaultRegion.calendar) let dateFrom = Date() let dateTo = (dateFrom + self) let extractedCmps = cal.dateComponents(components, from: dateFrom, to: dateTo) return extractedCmps.toDict() } } ================================================ FILE: Sources/SwiftDate/Foundation+Extras/Int+DateComponents.swift ================================================ // // 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: Int Extension /// This allows us to transform a literal number in a `DateComponents` and use it in math operations /// For example `5.days` will create a new `DateComponents` where `.day = 5`. public extension Int { /// Internal transformation function /// /// - parameter type: component to use /// /// - returns: return self value in form of `DateComponents` where given `Calendar.Component` has `self` as value internal func toDateComponents(type: Calendar.Component) -> DateComponents { var dateComponents = DateComponents() DateComponents.allComponents.forEach( { dateComponents.setValue(0, for: $0 )}) dateComponents.setValue(self, for: type) dateComponents.setValue(0, for: .era) return dateComponents } /// Create a `DateComponents` with `self` value set as nanoseconds var nanoseconds: DateComponents { toDateComponents(type: .nanosecond) } /// Create a `DateComponents` with `self` value set as seconds var seconds: DateComponents { toDateComponents(type: .second) } /// Create a `DateComponents` with `self` value set as minutes var minutes: DateComponents { toDateComponents(type: .minute) } /// Create a `DateComponents` with `self` value set as hours var hours: DateComponents { toDateComponents(type: .hour) } /// Create a `DateComponents` with `self` value set as days var days: DateComponents { toDateComponents(type: .day) } /// Create a `DateComponents` with `self` value set as weeks var weeks: DateComponents { toDateComponents(type: .weekOfYear) } /// Create a `DateComponents` with `self` value set as months var months: DateComponents { toDateComponents(type: .month) } /// Create a `DateComponents` with `self` value set as years var years: DateComponents { toDateComponents(type: .year) } /// Create a `DateComponents` with `self` value set as quarters var quarters: DateComponents { toDateComponents(type: .quarter) } } ================================================ FILE: Sources/SwiftDate/Foundation+Extras/String+Parser.swift ================================================ // // 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: - DataParsable Protocol public protocol DateParsable { /// Convert a string to a `DateInRegion` instance by parsing it with given parser /// or using one of the built-in parser (if you know the format of the date you /// should consider explicitly pass it to avoid unecessary computations). /// /// - Parameters: /// - format: format of the date, `nil` to leave the library to found the best /// one via `SwiftDate.autoFormats` /// - region: region in which the date should be expressed in. /// Region's locale is used to format the date when using long readable unit names (like MMM /// for month). /// - Returns: date in region representation, `nil` if parse fails func toDate(_ format: String?, region: Region) -> DateInRegion? /// Convert a string to a `DateInRegion` instance by parsing it with the ordered /// list of provided formats. /// If `formats` array is not provided it uses the `SwiftDate.autoFormats` array instead. /// Note: if you knwo the format of the date you should consider explicitly pass it to avoid /// unecessary computations. /// /// - Parameters: /// - format: ordered formats to parse date (if you don't have a list of formats you can pass `SwiftDate.autoFormats`) /// - region: region in which the date should be expressed in. /// Region's locale is used to format the date when using long readable unit names (like MMM /// for month). /// - Returns: date in region representation, `nil` if parse fails func toDate(_ formats: [String], region: Region) -> DateInRegion? /// Convert a string to a valid `DateInRegion` using passed style. /// /// - Parameters: /// - style: parsing style. /// - region: region in which the date should be expressed in /// - Returns: date in region representation, `nil` if parse fails func toDate(style: StringToDateStyles, region: Region) -> DateInRegion? /// Convert to date from a valid ISO8601 string /// /// - Parameters: /// - options: options of the parser /// - region: region in which the date should be expressed in (timzone is ignored and evaluated automatically) /// - Returns: date in region representation, `nil` if parse fails func toISODate(_ options: ISOParser.Options?, region: Region?) -> DateInRegion? /// Convert to date from a valid DOTNET string /// /// - region: region in which the date should be expressed in (timzone is ignored and evaluated automatically) /// - Returns: date in region representation, `nil` if parse fails func toDotNETDate(region: Region) -> DateInRegion? /// Convert to a date from a valid RSS/ALT RSS string /// /// - Parameters: /// - alt: `true` if string represent an ALT RSS formatted date, `false` if a standard RSS formatted date. /// - region: region in which the date should be expressed in (timzone is ignored and evaluated automatically) /// - Returns: date in region representation, `nil` if parse fails func toRSSDate(alt: Bool, region: Region) -> DateInRegion? /// Convert to a date from a valid SQL format string. /// /// - Parameters: /// - region: region in which the date should be expressed in (timzone is ignored and evaluated automatically) /// - Returns: date in region representation, `nil` if parse fails func toSQLDate(region: Region) -> DateInRegion? } // MARK: - DataParsable Implementation for Strings extension String: DateParsable { public func toDate(_ format: String? = nil, region: Region = SwiftDate.defaultRegion) -> DateInRegion? { DateInRegion(self, format: format, region: region) } public func toDate(_ formats: [String], region: Region) -> DateInRegion? { DateInRegion(self, formats: formats, region: region) } public func toDate(style: StringToDateStyles, region: Region = SwiftDate.defaultRegion) -> DateInRegion? { style.toDate(self, region: region) } public func toISODate(_ options: ISOParser.Options? = nil, region: Region? = nil) -> DateInRegion? { ISOParser.parse(self, region: region, options: options) } public func toDotNETDate(region: Region = Region.ISO) -> DateInRegion? { DOTNETParser.parse(self, region: region, options: nil) } public func toRSSDate(alt: Bool, region: Region = Region.ISO) -> DateInRegion? { switch alt { case true: return StringToDateStyles.altRSS.toDate(self, region: region) case false: return StringToDateStyles.rss.toDate(self, region: region) } } public func toSQLDate(region: Region = Region.ISO) -> DateInRegion? { StringToDateStyles.sql.toDate(self, region: region) } public func asLocale() -> Locale { Locale(identifier: self) } } ================================================ FILE: Sources/SwiftDate/Foundation+Extras/TimeInterval+Formatter.swift ================================================ // // 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, 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? { toUnits([component], to: refDate)[component] } } ================================================ FILE: Sources/SwiftDate/Supports/AssociatedValues.swift ================================================ // // 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 import ObjectiveC.runtime internal func getAssociatedValue(key: String, object: AnyObject) -> T? { (objc_getAssociatedObject(object, key.address) as? AssociatedValue)?.value as? T } internal func getAssociatedValue(key: String, object: AnyObject, initialValue: @autoclosure () -> T) -> T { getAssociatedValue(key: key, object: object) ?? setAndReturn(initialValue: initialValue(), key: key, object: object) } internal func getAssociatedValue(key: String, object: AnyObject, initialValue: () -> T) -> T { getAssociatedValue(key: key, object: object) ?? setAndReturn(initialValue: initialValue(), key: key, object: object) } private func setAndReturn(initialValue: T, key: String, object: AnyObject) -> T { set(associatedValue: initialValue, key: key, object: object) return initialValue } internal func set(associatedValue: T?, key: String, object: AnyObject) { set(associatedValue: AssociatedValue(associatedValue), key: key, object: object) } internal func set(weakAssociatedValue: T?, key: String, object: AnyObject) { set(associatedValue: AssociatedValue(weak: weakAssociatedValue), key: key, object: object) } extension String { fileprivate var address: UnsafeRawPointer { return UnsafeRawPointer(bitPattern: abs(hashValue))! } } private func set(associatedValue: AssociatedValue, key: String, object: AnyObject) { objc_setAssociatedObject(object, key.address, associatedValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC) } private class AssociatedValue { weak var _weakValue: AnyObject? var _value: Any? var value: Any? { return _weakValue ?? _value } init(_ value: Any?) { _value = value } init(weak: AnyObject?) { _weakValue = weak } } ================================================ FILE: Sources/SwiftDate/Supports/Calendars.swift ================================================ // // 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 typealias Calendars = Calendar.Identifier public protocol CalendarConvertible { func toCalendar() -> Calendar } extension Calendar: CalendarConvertible { public func toCalendar() -> Calendar { return self } internal static func newCalendar(_ calendar: CalendarConvertible, configure: ((inout Calendar) -> Void)? = nil) -> Calendar { var cal = calendar.toCalendar() configure?(&cal) return cal } } extension Calendar.Identifier: CalendarConvertible { public func toCalendar() -> Calendar { return Calendar(identifier: self) } } // MARK: - Support for Calendar.Identifier encoding with Codable extension Calendar.Identifier: CustomStringConvertible { public var description: String { switch self { case .gregorian: return "gregorian" case .buddhist: return "buddhist" case .chinese: return "chinese" case .coptic: return "coptic" case .ethiopicAmeteMihret: return "ethiopicAmeteMihret" case .ethiopicAmeteAlem: return "ethiopicAmeteAlem" case .hebrew: return "hebrew" case .iso8601: return "iso8601" case .indian: return "indian" case .islamic: return "islamic" case .islamicCivil: return "islamicCivil" case .japanese: return "japanese" case .persian: return "persian" case .republicOfChina: return "republicOfChina" case .islamicTabular: return "islamicTabular" case .islamicUmmAlQura: return "islamicUmmAlQura" @unknown default: fatalError("Unsupported calendar \(self)") } } public init(_ rawValue: String) { switch rawValue { case Calendar.Identifier.gregorian.description: self = .gregorian case Calendar.Identifier.buddhist.description: self = .buddhist case Calendar.Identifier.chinese.description: self = .chinese case Calendar.Identifier.coptic.description: self = .coptic case Calendar.Identifier.ethiopicAmeteMihret.description: self = .ethiopicAmeteMihret case Calendar.Identifier.ethiopicAmeteAlem.description: self = .ethiopicAmeteAlem case Calendar.Identifier.hebrew.description: self = .hebrew case Calendar.Identifier.iso8601.description: self = .iso8601 case Calendar.Identifier.indian.description: self = .indian case Calendar.Identifier.islamic.description: self = .islamic case Calendar.Identifier.islamicCivil.description: self = .islamicCivil case Calendar.Identifier.japanese.description: self = .japanese case Calendar.Identifier.persian.description: self = .persian case Calendar.Identifier.republicOfChina.description: self = .republicOfChina case Calendar.Identifier.islamicTabular.description: self = .islamicTabular case Calendar.Identifier.islamicTabular.description: self = .islamicTabular case Calendar.Identifier.islamicUmmAlQura.description: self = .islamicUmmAlQura default: let defaultCalendar = SwiftDate.defaultRegion.calendar.identifier debugPrint("Calendar Identifier '\(rawValue)' not recognized. Using default (\(defaultCalendar))") self = defaultCalendar } } } ================================================ FILE: Sources/SwiftDate/Supports/Commons.swift ================================================ // // 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: - Atomic Variable Support @propertyWrapper internal struct Atomic { private let queue = DispatchQueue(label: "com.vadimbulavin.atomic") private var value: Value init(wrappedValue: Value) { self.value = wrappedValue } var wrappedValue: Value { get { return queue.sync { value } } set { queue.sync { value = newValue } } } } // MARK: - DateFormatter public extension DateFormatter { /// Return the local thread shared formatter initialized with the configuration of the region passed. /// /// - Parameters: /// - region: region used to pre-configure the cell. /// - format: optional format used to set the `dateFormat` property. /// - Returns: date formatter instance static func sharedFormatter(forRegion region: Region?, format: String? = nil) -> DateFormatter { let name = "SwiftDate_\(NSStringFromClass(DateFormatter.self))" let formatter: DateFormatter = threadSharedObject(key: name, create: { return DateFormatter() }) if let region = region { formatter.timeZone = region.timeZone formatter.calendar = region.calendar formatter.locale = region.locale } formatter.dateFormat = (format ?? DateFormats.iso8601) return formatter } /// Returned number formatter instance shared along calling thread to format ordinal numbers. /// /// - Parameter locale: locale to set /// - Returns: number formatter instance @available(iOS 9.0, macOS 10.11, *) static func sharedOrdinalNumberFormatter(locale: LocaleConvertible) -> NumberFormatter { let name = "SwiftDate_\(NSStringFromClass(NumberFormatter.self))" let formatter = threadSharedObject(key: name, create: { return NumberFormatter() }) formatter.numberStyle = .ordinal formatter.locale = locale.toLocale() return formatter } } /// This function create (if necessary) and return a thread singleton instance of the /// object you want. /// /// - Parameters: /// - key: identifier of the object. /// - create: create routine used the first time you are about to create the object in thread. /// - Returns: instance of the object for caller's thread. internal func threadSharedObject(key: String, create: () -> T) -> T { if let cachedObj = Thread.current.threadDictionary[key] as? T { return cachedObj } else { let newObject = create() Thread.current.threadDictionary[key] = newObject return newObject } } /// Style used to format month, weekday, quarter symbols. /// Stand-alone properties are for use in places like calendar headers. /// Non-stand-alone properties are for use in context (for example, “Saturday, November 12th”). /// /// - `default`: Default formatter (ie. `4th quarter` for quarter, `April` for months and `Wednesday` for weekdays) /// - defaultStandalone: See `default`; See `short`; stand-alone properties are for use in places like calendar headers. /// - short: Short symbols (ie. `Jun` for months, `Fri` for weekdays, `Q1` for quarters). /// - veryShort: Very short symbols (ie. `J` for months, `F` for weekdays, for quarter it just return `short` variant). /// - standaloneShort: See `short`; stand-alone properties are for use in places like calendar headers. /// - standaloneVeryShort: See `veryShort`; stand-alone properties are for use in places like calendar headers. public enum SymbolFormatStyle { case `default` case defaultStandalone case short case veryShort case standaloneShort case standaloneVeryShort } /// Encapsulate the logic to use date format strings public struct DateFormats { /// This is the built-in list of all supported formats for auto-parsing of a string to a date. internal static let builtInAutoFormat: [String] = [ DateFormats.iso8601, "yyyy'-'MM'-'dd'T'HH':'mm':'ssZ", "yyyy'-'MM'-'dd'T'HH':'mm':'ss'.'SSSZ", "yyyy-MM-dd'T'HH:mm:ss.SSSZ", "yyyy-MM-dd HH:mm:ss", "yyyy-MM-dd HH:mm", "yyyy-MM-dd", "h:mm:ss A", "h:mm A", "MM/dd/yyyy", "MMMM d, yyyy", "MMMM d, yyyy LT", "dddd, MMMM D, yyyy LT", "yyyyyy-MM-dd", "yyyy-MM-dd", "yyyy-'W'ww-E", "GGGG-'['W']'ww-E", "yyyy-'W'ww", "GGGG-'['W']'ww", "yyyy'W'ww", "yyyy-ddd", "HH:mm:ss.SSSS", "HH:mm:ss", "HH:mm", "HH" ] /// This is the ordered list of all formats SwiftDate can use in order to attempt parsing a passaed /// date expressed as string. Evaluation is made in order; you can add or remove new formats as you wish. /// In order to reset the list call `resetAutoFormats()` function. public static var autoFormats: [String] = DateFormats.builtInAutoFormat /// Default ISO8601 format string public static let iso8601: String = "yyyy-MM-dd'T'HH:mm:ssZZZZZ" /// Extended format public static let extended: String = "eee dd-MMM-yyyy GG HH:mm:ss.SSS zzz" /// The Alternative RSS formatted date "d MMM yyyy HH:mm:ss ZZZ" i.e. "09 Sep 2011 15:26:08 +0200" public static let altRSS: String = "d MMM yyyy HH:mm:ss ZZZ" /// The RSS formatted date "EEE, d MMM yyyy HH:mm:ss ZZZ" i.e. "Fri, 09 Sep 2011 15:26:08 +0200" public static let rss: String = "EEE, d MMM yyyy HH:mm:ss ZZZ" /// The http header formatted date "EEE, dd MMM yyyy HH:mm:ss zzz" i.e. "Tue, 15 Nov 1994 12:45:26 GMT" public static let httpHeader: String = "EEE, dd MMM yyyy HH:mm:ss zzz" /// A generic standard format date i.e. "EEE MMM dd HH:mm:ss Z yyyy" public static let standard: String = "EEE MMM dd HH:mm:ss Z yyyy" /// SQL date format public static let sql: String = "yyyy-MM-dd'T'HH:mm:ss.SSSX" /// Reset the list of auto formats to the initial settings. public static func resetAutoFormats() { autoFormats = DateFormats.builtInAutoFormat } /// Parse a new string optionally passing the format in which is encoded. If no format is passed /// an attempt is made by cycling all the formats set in `autoFormats` property. /// /// - Parameters: /// - string: date expressed as string. /// - suggestedFormat: optional format of the date expressed by the string (set it if you can in order to optimize the parse task). /// - region: region in which the date is expressed. /// - Returns: parsed absolute `Date`, `nil` if parse fails. public static func parse(string: String, format: String?, region: Region) -> Date? { let formats = (format != nil ? [format!] : DateFormats.autoFormats) return DateFormats.parse(string: string, formats: formats, region: region) } public static func parse(string: String, formats: [String], region: Region) -> Date? { let formatter = DateFormatter.sharedFormatter(forRegion: region) var parsedDate: Date? for format in formats { formatter.dateFormat = format formatter.locale = region.locale if let date = formatter.date(from: string) { parsedDate = date break } } return parsedDate } } // MARK: - Calendar Extension public extension Calendar.Component { internal static func toSet(_ src: [Calendar.Component]) -> Set { var l: Set = [] src.forEach { l.insert($0) } return l } internal var nsCalendarUnit: NSCalendar.Unit { switch self { case .era: return NSCalendar.Unit.era case .year: return NSCalendar.Unit.year case .month: return NSCalendar.Unit.month case .day: return NSCalendar.Unit.day case .hour: return NSCalendar.Unit.hour case .minute: return NSCalendar.Unit.minute case .second: return NSCalendar.Unit.second case .weekday: return NSCalendar.Unit.weekday case .weekdayOrdinal: return NSCalendar.Unit.weekdayOrdinal case .quarter: return NSCalendar.Unit.quarter case .weekOfMonth: return NSCalendar.Unit.weekOfMonth case .weekOfYear: return NSCalendar.Unit.weekOfYear case .yearForWeekOfYear: return NSCalendar.Unit.yearForWeekOfYear case .nanosecond: return NSCalendar.Unit.nanosecond case .calendar: return NSCalendar.Unit.calendar case .timeZone: return NSCalendar.Unit.timeZone @unknown default: fatalError("Unsupported type \(self)") } } } /// Rounding mode for dates. /// Round off/up (ceil) or down (floor) target date. public enum RoundDateMode { case to5Mins case to10Mins case to30Mins case toMins(_: Int) case toCeil5Mins case toCeil10Mins case toCeil30Mins case toCeilMins(_: Int) case toFloor5Mins case toFloor10Mins case toFloor30Mins case toFloorMins(_: Int) } /// Related type enum to get derivated date from a receiver date. public enum DateRelatedType { case startOfDay case endOfDay case startOfWeek case endOfWeek case startOfMonth case endOfMonth case tomorrow case tomorrowAtStart case yesterday case yesterdayAtStart case nearestMinute(minute: Int) case nearestHour(hour :Int) case nextWeekday(_: WeekDay) case nextDSTDate case prevMonth case nextMonth case prevWeek case nextWeek case nextYear case prevYear case nextDSTTransition } public struct TimeCalculationOptions { /// Specifies the technique the search algorithm uses to find result public var matchingPolicy: Calendar.MatchingPolicy /// Specifies the behavior when multiple matches are found public var repeatedTimePolicy: Calendar.RepeatedTimePolicy /// Specifies the direction in time to search public var direction: Calendar.SearchDirection public init(matching: Calendar.MatchingPolicy = .nextTime, timePolicy: Calendar.RepeatedTimePolicy = .first, direction: Calendar.SearchDirection = .forward) { self.matchingPolicy = matching self.repeatedTimePolicy = timePolicy self.direction = direction } } // MARK: - compactMap for Swift 4.0 (not necessary > 4.0) #if swift(>=4.1) #else extension Collection { func compactMap( _ transform: (Element) throws -> ElementOfResult? ) rethrows -> [ElementOfResult] { return try flatMap(transform) } } #endif // MARK: - Foundation Bundle private class BundleFinder {} extension Foundation.Bundle { /// Returns the resource bundle associated with the current Swift module. /// This is used instead of `module` to allows compatibility outside the SwiftPM environment (ie. CocoaPods). static var appModule: Bundle? = { let bundleName = "SwiftDate_SwiftDate" let candidates = [ // Bundle should be present here when the package is linked into an App. Bundle.main.resourceURL, // Bundle should be present here when the package is linked into a framework. Bundle(for: BundleFinder.self).resourceURL, // For command-line tools. Bundle.main.bundleURL, ] for candidate in candidates { let bundlePath = candidate?.appendingPathComponent(bundleName + ".bundle") if let bundle = bundlePath.flatMap(Bundle.init(url:)) { return bundle } } return nil }() } ================================================ FILE: Sources/SwiftDate/Supports/Locales.swift ================================================ // // 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. // // swiftlint:disable file_length import Foundation public protocol LocaleConvertible { func toLocale() -> Locale } extension Locale: LocaleConvertible { public func toLocale() -> Locale { return self } } // swiftlint:disable type_body_length public enum Locales: String, LocaleConvertible { case current = "current" case autoUpdating = "currentAutoUpdating" case afrikaans = "af" case afrikaansNamibia = "af_NA" case afrikaansSouthAfrica = "af_ZA" case aghem = "agq" case aghemCameroon = "agq_CM" case akan = "ak" case akanGhana = "ak_GH" case albanian = "sq" case albanianAlbania = "sq_AL" case albanianKosovo = "sq_XK" case albanianMacedonia = "sq_MK" case amharic = "am" case amharicEthiopia = "am_ET" case arabic = "ar" case arabicAlgeria = "ar_DZ" case arabicBahrain = "ar_BH" case arabicChad = "ar_TD" case arabicComoros = "ar_KM" case arabicDjibouti = "ar_DJ" case arabicEgypt = "ar_EG" case arabicEritrea = "ar_ER" case arabicIraq = "ar_IQ" case arabicIsrael = "ar_IL" case arabicJordan = "ar_JO" case arabicKuwait = "ar_KW" case arabicLebanon = "ar_LB" case arabicLibya = "ar_LY" case arabicMauritania = "ar_MR" case arabicMorocco = "ar_MA" case arabicOman = "ar_OM" case arabicPalestinianTerritories = "ar_PS" case arabicQatar = "ar_QA" case arabicSaudiArabia = "ar_SA" case arabicSomalia = "ar_SO" case arabicSouthSudan = "ar_SS" case arabicSudan = "ar_SD" case arabicSyria = "ar_SY" case arabicTunisia = "ar_TN" case arabicUnitedArabEmirates = "ar_AE" case arabicWesternSahara = "ar_EH" case arabicWorld = "ar_001" case arabicYemen = "ar_YE" case armenian = "hy" case armenianArmenia = "hy_AM" case assamese = "as" case assameseIndia = "as_IN" case asu = "asa" case asuTanzania = "asa_TZ" case azerbaijani = "az_Latn" case azerbaijaniAzerbaijan = "az_Latn_AZ" case azerbaijaniCyrillic = "az_Cyrl" case azerbaijaniCyrillicAzerbaijan = "az_Cyrl_AZ" case bafia = "ksf" case bafiaCameroon = "ksf_CM" case bambara = "bm_Latn" case bambaraMali = "bm_Latn_ML" case basaa = "bas" case basaaCameroon = "bas_CM" case basque = "eu" case basqueSpain = "eu_ES" case belarusian = "be" case belarusianBelarus = "be_BY" case bemba = "bem" case bembaZambia = "bem_ZM" case bena = "bez" case benaTanzania = "bez_TZ" case bengali = "bn" case bengaliBangladesh = "bn_BD" case engaliIndia = "bn_IN" case bodo = "brx" case bodoIndia = "brx_IN" case bosnian = "bs_Latn" case bosnianBosniaHerzegovina = "bs_Latn_BA" case bosnianCyrillic = "bs_Cyrl" case bosnianCyrillicBosniaHerzegovina = "bs_Cyrl_BA" case breton = "br" case bretonFrance = "br_FR" case bulgarian = "bg" case bulgarianBulgaria = "bg_BG" case burmese = "my" case burmeseMyanmarBurma = "my_MM" case catalan = "ca" case catalanAndorra = "ca_AD" case catalanFrance = "ca_FR" case catalanItaly = "ca_IT" case catalanSpain = "ca_ES" case centralAtlasTamazight = "tzm_Latn" case centralAtlasTamazightMorocco = "tzm_Latn_MA" case centralKurdish = "ckb" case centralKurdishIran = "ckb_IR" case centralKurdishIraq = "ckb_IQ" case cherokee = "chr" case cherokeeUnitedStates = "chr_US" case chiga = "cgg" case chigaUganda = "cgg_UG" case chinese = "zh" case chineseChina = "zh_Hans_CN" case chineseHongKongSarChina = "zh_Hant_HK" case chineseMacauSarChina = "zh_Hant_MO" case chineseSimplified = "zh_Hans" case chineseSimplifiedHongKongSarChina = "zh_Hans_HK" case chineseSimplifiedMacauSarChina = "zh_Hans_MO" case chineseSingapore = "zh_Hans_SG" case chineseTaiwan = "zh_Hant_TW" case chineseTraditional = "zh_Hant" case colognian = "ksh" case colognianGermany = "ksh_DE" case cornish = "kw" case cornishUnitedKingdom = "kw_GB" case croatian = "hr" case croatianBosniaHerzegovina = "hr_BA" case croatianCroatia = "hr_HR" case czech = "cs" case czechCzechRepublic = "cs_CZ" case danish = "da" case danishDenmark = "da_DK" case danishGreenland = "da_GL" case duala = "dua" case dualaCameroon = "dua_CM" case dutch = "nl" case dutchAruba = "nl_AW" case dutchBelgium = "nl_BE" case dutchCaribbeanNetherlands = "nl_BQ" case dutchCuraao = "nl_CW" case dutchNetherlands = "nl_NL" case dutchSintMaarten = "nl_SX" case dutchSuriname = "nl_SR" case dzongkha = "dz" case dzongkhaBhutan = "dz_BT" case embu = "ebu" case embuKenya = "ebu_KE" case english = "en" case englishAlbania = "en_AL" case englishAmericanSamoa = "en_AS" case englishAndorra = "en_AD" case englishAnguilla = "en_AI" case englishAntiguaBarbuda = "en_AG" case englishAustralia = "en_AU" case englishAustria = "en_AT" case englishBahamas = "en_BS" case englishBarbados = "en_BB" case englishBelgium = "en_BE" case englishBelize = "en_BZ" case englishBermuda = "en_BM" case englishBosniaHerzegovina = "en_BA" case englishBotswana = "en_BW" case englishBritishIndianOceanTerritory = "en_IO" case englishBritishVirginIslands = "en_VG" case englishCameroon = "en_CM" case englishCanada = "en_CA" case englishCaymanIslands = "en_KY" case englishChristmasIsland = "en_CX" case englishCocosKeelingIslands = "en_CC" case englishCookIslands = "en_CK" case englishCroatia = "en_HR" case englishCyprus = "en_CY" case englishCzechRepublic = "en_CZ" case englishDenmark = "en_DK" case englishDiegoGarcia = "en_DG" case englishDominica = "en_DM" case englishEritrea = "en_ER" case englishEstonia = "en_EE" case englishEurope = "en_150" case englishFalklandIslands = "en_FK" case englishFiji = "en_FJ" case englishFinland = "en_FI" case englishFrance = "en_FR" case englishGambia = "en_GM" case englishGermany = "en_DE" case englishGhana = "en_GH" case englishGibraltar = "en_GI" case englishGreece = "en_GR" case englishGrenada = "en_GD" case englishGuam = "en_GU" case englishGuernsey = "en_GG" case englishGuyana = "en_GY" case englishHongKongSarChina = "en_HK" case englishHungary = "en_HU" case englishIceland = "en_IS" case englishIndia = "en_IN" case englishIreland = "en_IE" case englishIsleOfMan = "en_IM" case englishIsrael = "en_IL" case englishItaly = "en_IT" case englishJamaica = "en_JM" case englishJersey = "en_JE" case englishKenya = "en_KE" case englishKiribati = "en_KI" case englishLatvia = "en_LV" case englishLesotho = "en_LS" case englishLiberia = "en_LR" case englishLithuania = "en_LT" case englishLuxembourg = "en_LU" case englishMacauSarChina = "en_MO" case englishMadagascar = "en_MG" case englishMalawi = "en_MW" case englishMalaysia = "en_MY" case englishMalta = "en_MT" case englishMarshallIslands = "en_MH" case englishMauritius = "en_MU" case englishMicronesia = "en_FM" case englishMontenegro = "en_ME" case englishMontserrat = "en_MS" case englishNamibia = "en_NA" case englishNauru = "en_NR" case englishNetherlands = "en_NL" case englishNewZealand = "en_NZ" case englishNigeria = "en_NG" case englishNiue = "en_NU" case englishNorfolkIsland = "en_NF" case englishNorthernMarianaIslands = "en_MP" case englishNorway = "en_NO" case englishPakistan = "en_PK" case englishPalau = "en_PW" case englishPapuaNewGuinea = "en_PG" case englishPhilippines = "en_PH" case englishPitcairnIslands = "en_PN" case englishPoland = "en_PL" case englishPortugal = "en_PT" case englishPuertoRico = "en_PR" case englishRomania = "en_RO" case englishRussia = "en_RU" case englishRwanda = "en_RW" case englishSamoa = "en_WS" case englishSeychelles = "en_SC" case englishSierraLeone = "en_SL" case englishSingapore = "en_SG" case englishSintMaarten = "en_SX" case englishSlovakia = "en_SK" case englishSlovenia = "en_SI" case englishSolomonIslands = "en_SB" case englishSouthAfrica = "en_ZA" case englishSouthSudan = "en_SS" case englishSpain = "en_ES" case englishStHelena = "en_SH" case englishStKittsNevis = "en_KN" case englishStLucia = "en_LC" case englishStVincentGrenadines = "en_VC" case englishSudan = "en_SD" case englishSwaziland = "en_SZ" case englishSweden = "en_SE" case englishSwitzerland = "en_CH" case englishTanzania = "en_TZ" case englishTokelau = "en_TK" case englishTonga = "en_TO" case englishTrinidadTobago = "en_TT" case englishTurkey = "en_TR" case englishTurksCaicosIslands = "en_TC" case englishTuvalu = "en_TV" case englishUSOutlyingIslands = "en_UM" case englishUSVirginIslands = "en_VI" case englishUganda = "en_UG" case englishUnitedKingdom = "en_GB" case englishUnitedStates = "en_US" case englishUnitedStatesComputer = "en_US_POSIX" case englishVanuatu = "en_VU" case englishWorld = "en_001" case englishZambia = "en_ZM" case englishZimbabwe = "en_ZW" case esperanto = "eo" case estonian = "et" case estonianEstonia = "et_EE" case ewe = "ee" case eweGhana = "ee_GH" case eweTogo = "ee_TG" case ewondo = "ewo" case ewondoCameroon = "ewo_CM" case faroese = "fo" case faroeseFaroeIslands = "fo_FO" case filipino = "fil" case filipinoPhilippines = "fil_PH" case finnish = "fi" case finnishFinland = "fi_FI" case french = "fr" case frenchAlgeria = "fr_DZ" case frenchBelgium = "fr_BE" case frenchBenin = "fr_BJ" case frenchBurkinaFaso = "fr_BF" case frenchBurundi = "fr_BI" case frenchCameroon = "fr_CM" case frenchCanada = "fr_CA" case frenchCentralAfricanRepublic = "fr_CF" case frenchChad = "fr_TD" case frenchComoros = "fr_KM" case frenchCongoBrazzaville = "fr_CG" case frenchCongoKinshasa = "fr_CD" case frenchCteDivoire = "fr_CI" case frenchDjibouti = "fr_DJ" case frenchEquatorialGuinea = "fr_GQ" case frenchFrance = "fr_FR" case frenchFrenchGuiana = "fr_GF" case frenchFrenchPolynesia = "fr_PF" case frenchGabon = "fr_GA" case frenchGuadeloupe = "fr_GP" case frenchGuinea = "fr_GN" case frenchHaiti = "fr_HT" case frenchLuxembourg = "fr_LU" case frenchMadagascar = "fr_MG" case frenchMali = "fr_ML" case frenchMartinique = "fr_MQ" case frenchMauritania = "fr_MR" case frenchMauritius = "fr_MU" case frenchMayotte = "fr_YT" case frenchMonaco = "fr_MC" case frenchMorocco = "fr_MA" case frenchNewCaledonia = "fr_NC" case frenchNiger = "fr_NE" case frenchRunion = "fr_RE" case frenchRwanda = "fr_RW" case frenchSenegal = "fr_SN" case frenchSeychelles = "fr_SC" case frenchStBarthlemy = "fr_BL" case frenchStMartin = "fr_MF" case frenchStPierreMiquelon = "fr_PM" case frenchSwitzerland = "fr_CH" case frenchSyria = "fr_SY" case frenchTogo = "fr_TG" case frenchTunisia = "fr_TN" case frenchVanuatu = "fr_VU" case frenchWallisFutuna = "fr_WF" case friulian = "fur" case friulianItaly = "fur_IT" case fulah = "ff" case fulahCameroon = "ff_CM" case fulahGuinea = "ff_GN" case fulahMauritania = "ff_MR" case fulahSenegal = "ff_SN" case galician = "gl" case galicianSpain = "gl_ES" case ganda = "lg" case gandaUganda = "lg_UG" case georgian = "ka" case georgianGeorgia = "ka_GE" case german = "de" case germanAustria = "de_AT" case germanBelgium = "de_BE" case germanGermany = "de_DE" case germanLiechtenstein = "de_LI" case germanLuxembourg = "de_LU" case germanSwitzerland = "de_CH" case greek = "el" case greekCyprus = "el_CY" case greekGreece = "el_GR" case gujarati = "gu" case gujaratiIndia = "gu_IN" case gusii = "guz" case gusiiKenya = "guz_KE" case hausa = "ha_Latn" case hausaGhana = "ha_Latn_GH" case hausaNiger = "ha_Latn_NE" case hausaNigeria = "ha_Latn_NG" case hawaiian = "haw" case hawaiianUnitedStates = "haw_US" case hebrew = "he" case hebrewIsrael = "he_IL" case hindi = "hi" case hindiIndia = "hi_IN" case hungarian = "hu" case hungarianHungary = "hu_HU" case icelandic = "is" case icelandicIceland = "is_IS" case igbo = "ig" case igboNigeria = "ig_NG" case inariSami = "smn" case inariSamiFinland = "smn_FI" case indonesian = "id" case indonesianIndonesia = "id_ID" case inuktitut = "iu" case inuktitutUnifiedCanadianAboriginalSyllabics = "iu_Cans" case inuktitutUnifiedCanadianAboriginalSyllabicsCanada = "iu_Cans_CA" case irish = "ga" case irishIreland = "ga_IE" case italian = "it" case italianItaly = "it_IT" case italianSanMarino = "it_SM" case italianSwitzerland = "it_CH" case japanese = "ja" case japaneseJapan = "ja_JP" case jolaFonyi = "dyo" case jolaFonyiSenegal = "dyo_SN" case kabuverdianu = "kea" case kabuverdianuCapeVerde = "kea_CV" case kabyle = "kab" case kabyleAlgeria = "kab_DZ" case kako = "kkj" case kakoCameroon = "kkj_CM" case kalaallisut = "kl" case kalaallisutGreenland = "kl_GL" case kalenjin = "kln" case kalenjinKenya = "kln_KE" case kamba = "kam" case kambaKenya = "kam_KE" case kannada = "kn" case kannadaIndia = "kn_IN" case kashmiri = "ks" case kashmiriArabic = "ks_Arab" case kashmiriArabicIndia = "ks_Arab_IN" case kazakh = "kk_Cyrl" case kazakhKazakhstan = "kk_Cyrl_KZ" case khmer = "km" case khmerCambodia = "km_KH" case kikuyu = "ki" case kikuyuKenya = "ki_KE" case kinyarwanda = "rw" case kinyarwandaRwanda = "rw_RW" case konkani = "kok" case konkaniIndia = "kok_IN" case korean = "ko" case koreanNorthKorea = "ko_KP" case koreanSouthKorea = "ko_KR" case koyraChiini = "khq" case koyraChiiniMali = "khq_ML" case koyraboroSenni = "ses" case koyraboroSenniMali = "ses_ML" case kwasio = "nmg" case kwasioCameroon = "nmg_CM" case kyrgyz = "ky_Cyrl" case kyrgyzKyrgyzstan = "ky_Cyrl_KG" case lakota = "lkt" case lakotaUnitedStates = "lkt_US" case langi = "lag" case langiTanzania = "lag_TZ" case lao = "lo" case laoLaos = "lo_LA" case latvian = "lv" case latvianLatvia = "lv_LV" case lingala = "ln" case lingalaAngola = "ln_AO" case lingalaCentralAfricanRepublic = "ln_CF" case lingalaCongoBrazzaville = "ln_CG" case lingalaCongoKinshasa = "ln_CD" case lithuanian = "lt" case lithuanianLithuania = "lt_LT" case lowerSorbian = "dsb" case lowerSorbianGermany = "dsb_DE" case lubaKatanga = "lu" case lubaKatangaCongoKinshasa = "lu_CD" case luo = "luo" case luoKenya = "luo_KE" case luxembourgish = "lb" case luxembourgishLuxembourg = "lb_LU" case luyia = "luy" case luyiaKenya = "luy_KE" case macedonian = "mk" case macedonianMacedonia = "mk_MK" case machame = "jmc" case machameTanzania = "jmc_TZ" case makhuwaMeetto = "mgh" case makhuwaMeettoMozambique = "mgh_MZ" case makonde = "kde" case makondeTanzania = "kde_TZ" case malagasy = "mg" case malagasyMadagascar = "mg_MG" case malay = "ms_Latn" case malayArabic = "ms_Arab" case malayArabicBrunei = "ms_Arab_BN" case malayArabicMalaysia = "ms_Arab_MY" case malayBrunei = "ms_Latn_BN" case malayMalaysia = "ms_Latn_MY" case malaySingapore = "ms_Latn_SG" case malayalam = "ml" case malayalamIndia = "ml_IN" case maltese = "mt" case malteseMalta = "mt_MT" case manx = "gv" case manxIsleOfMan = "gv_IM" case marathi = "mr" case marathiIndia = "mr_IN" case masai = "mas" case masaiKenya = "mas_KE" case masaiTanzania = "mas_TZ" case meru = "mer" case meruKenya = "mer_KE" case meta = "mgo" case metaCameroon = "mgo_CM" case mongolian = "mn_Cyrl" case mongolianMongolia = "mn_Cyrl_MN" case morisyen = "mfe" case morisyenMauritius = "mfe_MU" case mundang = "mua" case mundangCameroon = "mua_CM" case nama = "naq" case namaNamibia = "naq_NA" case nepali = "ne" case nepaliIndia = "ne_IN" case nepaliNepal = "ne_NP" case ngiemboon = "nnh" case ngiemboonCameroon = "nnh_CM" case ngomba = "jgo" case ngombaCameroon = "jgo_CM" case northNdebele = "nd" case northNdebeleZimbabwe = "nd_ZW" case northernSami = "se" case northernSamiFinland = "se_FI" case northernSamiNorway = "se_NO" case northernSamiSweden = "se_SE" case norwegianBokml = "nb" case norwegianBokmlNorway = "nb_NO" case norwegianBokmlSvalbardJanMayen = "nb_SJ" case norwegianNynorsk = "nn" case norwegianNynorskNorway = "nn_NO" case nuer = "nus" case nuerSudan = "nus_SD" case nyankole = "nyn" case nyankoleUganda = "nyn_UG" case oriya = "or" case oriyaIndia = "or_IN" case oromo = "om" case oromoEthiopia = "om_ET" case oromoKenya = "om_KE" case ossetic = "os" case osseticGeorgia = "os_GE" case osseticRussia = "os_RU" case pashto = "ps" case pashtoAfghanistan = "ps_AF" case persian = "fa" case persianAfghanistan = "fa_AF" case persianIran = "fa_IR" case polish = "pl" case polishPoland = "pl_PL" case portuguese = "pt" case portugueseAngola = "pt_AO" case portugueseBrazil = "pt_BR" case portugueseCapeVerde = "pt_CV" case portugueseGuineaBissau = "pt_GW" case portugueseMacauSarChina = "pt_MO" case portugueseMozambique = "pt_MZ" case portuguesePortugal = "pt_PT" case portugueseSoTomPrncipe = "pt_ST" case portugueseTimorLeste = "pt_TL" case punjabi = "pa_Guru" case punjabiArabic = "pa_Arab" case punjabiArabicPakistan = "pa_Arab_PK" case punjabiIndia = "pa_Guru_IN" case quechua = "qu" case quechuaBolivia = "qu_BO" case quechuaEcuador = "qu_EC" case quechuaPeru = "qu_PE" case romanian = "ro" case romanianMoldova = "ro_MD" case romanianRomania = "ro_RO" case romansh = "rm" case romanshSwitzerland = "rm_CH" case rombo = "rof" case romboTanzania = "rof_TZ" case rundi = "rn" case rundiBurundi = "rn_BI" case russian = "ru" case russianBelarus = "ru_BY" case russianKazakhstan = "ru_KZ" case russianKyrgyzstan = "ru_KG" case russianMoldova = "ru_MD" case russianRussia = "ru_RU" case russianUkraine = "ru_UA" case rwa = "rwk" case rwaTanzania = "rwk_TZ" case sakha = "sah" case sakhaRussia = "sah_RU" case samburu = "saq" case samburuKenya = "saq_KE" case sango = "sg" case sangoCentralAfricanRepublic = "sg_CF" case sangu = "sbp" case sanguTanzania = "sbp_TZ" case scottishGaelic = "gd" case scottishGaelicUnitedKingdom = "gd_GB" case sena = "seh" case senaMozambique = "seh_MZ" case serbian = "sr_Cyrl" case serbianBosniaHerzegovina = "sr_Cyrl_BA" case serbianKosovo = "sr_Cyrl_XK" case serbianLatin = "sr_Latn" case serbianLatinBosniaHerzegovina = "sr_Latn_BA" case serbianLatinKosovo = "sr_Latn_XK" case serbianLatinMontenegro = "sr_Latn_ME" case serbianLatinSerbia = "sr_Latn_RS" case serbianMontenegro = "sr_Cyrl_ME" case serbianSerbia = "sr_Cyrl_RS" case shambala = "ksb" case shambalaTanzania = "ksb_TZ" case shona = "sn" case shonaZimbabwe = "sn_ZW" case sichuanYi = "ii" case sichuanYiChina = "ii_CN" case sinhala = "si" case sinhalaSriLanka = "si_LK" case slovak = "sk" case slovakSlovakia = "sk_SK" case slovenian = "sl" case slovenianSlovenia = "sl_SI" case soga = "xog" case sogaUganda = "xog_UG" case somali = "so" case somaliDjibouti = "so_DJ" case somaliEthiopia = "so_ET" case somaliKenya = "so_KE" case somaliSomalia = "so_SO" case spanish = "es" case spanishArgentina = "es_AR" case spanishBolivia = "es_BO" case spanishCanaryIslands = "es_IC" case spanishCeutaMelilla = "es_EA" case spanishChile = "es_CL" case spanishColombia = "es_CO" case spanishCostaRica = "es_CR" case spanishCuba = "es_CU" case spanishDominicanRepublic = "es_DO" case spanishEcuador = "es_EC" case spanishElSalvador = "es_SV" case spanishEquatorialGuinea = "es_GQ" case spanishGuatemala = "es_GT" case spanishHonduras = "es_HN" case spanishLatinAmerica = "es_419" case spanishMexico = "es_MX" case spanishNicaragua = "es_NI" case spanishPanama = "es_PA" case spanishParaguay = "es_PY" case spanishPeru = "es_PE" case spanishPhilippines = "es_PH" case spanishPuertoRico = "es_PR" case spanishSpain = "es_ES" case spanishUnitedStates = "es_US" case spanishUruguay = "es_UY" case spanishVenezuela = "es_VE" case standardMoroccanTamazight = "zgh" case standardMoroccanTamazightMorocco = "zgh_MA" case swahili = "sw" case swahiliCongoKinshasa = "sw_CD" case swahiliKenya = "sw_KE" case swahiliTanzania = "sw_TZ" case swahiliUganda = "sw_UG" case swedish = "sv" case swedishlandIslands = "sv_AX" case swedishFinland = "sv_FI" case swedishSweden = "sv_SE" case swissGerman = "gsw" case swissGermanFrance = "gsw_FR" case swissGermanLiechtenstein = "gsw_LI" case swissGermanSwitzerland = "gsw_CH" case tachelhit = "shi_Latn" case tachelhitMorocco = "shi_Latn_MA" case tachelhitTifinagh = "shi_Tfng" case tachelhitTifinaghMorocco = "shi_Tfng_MA" case taita = "dav" case taitaKenya = "dav_KE" case tajik = "tg_Cyrl" case tajikTajikistan = "tg_Cyrl_TJ" case tamil = "ta" case tamilIndia = "ta_IN" case tamilMalaysia = "ta_MY" case tamilSingapore = "ta_SG" case tamilSriLanka = "ta_LK" case tasawaq = "twq" case tasawaqNiger = "twq_NE" case telugu = "te" case teluguIndia = "te_IN" case teso = "teo" case tesoKenya = "teo_KE" case tesoUganda = "teo_UG" case thai = "th" case thaiThailand = "th_TH" case tibetan = "bo" case tibetanChina = "bo_CN" case tibetanIndia = "bo_IN" case tigrinya = "ti" case tigrinyaEritrea = "ti_ER" case tigrinyaEthiopia = "ti_ET" case tongan = "to" case tonganTonga = "to_TO" case turkish = "tr" case turkishCyprus = "tr_CY" case turkishTurkey = "tr_TR" case turkmen = "tk_Latn" case turkmenTurkmenistan = "tk_Latn_TM" case ukrainian = "uk" case ukrainianUkraine = "uk_UA" case upperSorbian = "hsb" case upperSorbianGermany = "hsb_DE" case urdu = "ur" case urduIndia = "ur_IN" case urduPakistan = "ur_PK" case uyghur = "ug" case uyghurArabic = "ug_Arab" case uyghurArabicChina = "ug_Arab_CN" case uzbek = "uz_Cyrl" case uzbekArabic = "uz_Arab" case uzbekArabicAfghanistan = "uz_Arab_AF" case uzbekLatin = "uz_Latn" case uzbekLatinUzbekistan = "uz_Latn_UZ" case uzbekUzbekistan = "uz_Cyrl_UZ" case vai = "vai_Vaii" case vaiLatin = "vai_Latn" case vaiLatinLiberia = "vai_Latn_LR" case vaiLiberia = "vai_Vaii_LR" case vietnamese = "vi" case vietnameseVietnam = "vi_VN" case vunjo = "vun" case vunjoTanzania = "vun_TZ" case walser = "wae" case walserSwitzerland = "wae_CH" case welsh = "cy" case welshUnitedKingdom = "cy_GB" case westernFrisian = "fy" case westernFrisianNetherlands = "fy_NL" case yangben = "yav" case yangbenCameroon = "yav_CM" case yiddish = "yi" case yiddishWorld = "yi_001" case yoruba = "yo" case yorubaBenin = "yo_BJ" case yorubaNigeria = "yo_NG" case zarma = "dje" case zarmaNiger = "dje_NE" case zulu = "zu" case zuluSouthAfrica = "zu_ZA" /// Return a valid `Locale` instance from current selected locale enum public func toLocale() -> Locale { switch self { case .current: return Locale.current case .autoUpdating: return Locale.autoupdatingCurrent default: return Locale(identifier: rawValue) } } } ================================================ FILE: Sources/SwiftDate/Supports/TimeStructures.swift ================================================ // // 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: - Weekday /// This define the weekdays for some functions. public enum WeekDay: Int { case sunday = 1, monday, tuesday, wednesday, thursday, friday, saturday /// Returns the name of the day given a specific locale. /// For example, for the `Friday` enum value, the en_AU locale would return "Friday" and fr_FR would return "samedi" /// /// - Parameter locale: locale of the output, omit to use the `defaultRegion`'s locale. /// - Returns: display name public func name(style: SymbolFormatStyle = .`default`, locale: LocaleConvertible = SwiftDate.defaultRegion.locale) -> String { let region = Region(calendar: SwiftDate.defaultRegion.calendar, zone: SwiftDate.defaultRegion.timeZone, locale: locale) let formatter = DateFormatter.sharedFormatter(forRegion: region, format: nil) let idx = (self.rawValue - 1) switch style { case .default: return formatter.weekdaySymbols[idx] case .defaultStandalone: return formatter.standaloneWeekdaySymbols[idx] case .short: return formatter.shortWeekdaySymbols[idx] case .standaloneShort: return formatter.shortStandaloneWeekdaySymbols[idx] case .veryShort: return formatter.veryShortWeekdaySymbols[idx] case .standaloneVeryShort: return formatter.veryShortStandaloneWeekdaySymbols[idx] } } /// Adds a number of days to the current weekday and returns the new weekday. /// /// - Parameter months: number of months to add /// - Returns: new month. public func add(days: Int) -> WeekDay { let normalized = days % 7 return WeekDay(rawValue: ((self.rawValue + normalized + 7 - 1) % 7) + 1)! } /// Subtracts a number of days from the current weekday and returns the new weekday. /// /// - Parameter months: number of days to subtract. May be negative, in which case it will be added /// - Returns: new weekday. public func subtract(days: Int) -> WeekDay { return add(days: -(days % 7)) } } // MARK: - Year public struct Year: CustomStringConvertible, Equatable { let year: Int public var description: String { return "\(self.year)" } /// Constructs a `Year` from the passed value. /// /// - Parameter year: year value. Can be negative. public init(_ year: Int) { self.year = year } /// Returns whether this year is a leap year /// /// - Returns: A boolean indicating whether this year is a leap year public func isLeap() -> Bool { return ((year & 3) == 0) && ((year % 100) != 0 || (year % 400) == 0) } /// Returns the number of days in this year /// /// - Returns: The number of days in this year public func numberOfDays() -> Int { return self.isLeap() ? 366 : 365 } } // MARK: - Month /// Defines months in a year public enum Month: Int, CustomStringConvertible, Equatable { case january = 0, february, march, april, may, june, july, august, september, october, november, december public var description: String { return self.name() } /// Returns the name of the month given a specific locale. /// For example, for the `January` enum value, the en_AU locale would return "January" and fr_FR would return "janvier" /// /// - Parameter locale: locale of the output, omit to use the `defaultRegion`'s locale. /// - Returns: display name public func name(style: SymbolFormatStyle = .`default`, locale: LocaleConvertible = SwiftDate.defaultRegion.locale) -> String { let region = Region(calendar: SwiftDate.defaultRegion.calendar, zone: SwiftDate.defaultRegion.timeZone, locale: locale) let formatter = DateFormatter.sharedFormatter(forRegion: region, format: nil) switch style { case .default: return formatter.monthSymbols[self.rawValue] case .defaultStandalone: return formatter.standaloneMonthSymbols[self.rawValue] case .short: return formatter.shortMonthSymbols[self.rawValue] case .standaloneShort: return formatter.shortStandaloneMonthSymbols[self.rawValue] case .veryShort: return formatter.veryShortMonthSymbols[self.rawValue] case .standaloneVeryShort: return formatter.veryShortStandaloneMonthSymbols[self.rawValue] } } /// Adds a number of months to the current month and returns the new month. /// /// - Parameter months: number of months to add /// - Returns: new month. public func add(months: Int) -> Month { let normalized = months % 12 return Month(rawValue: (self.rawValue + normalized + 12) % 12)! } /// Subtracts a number of months from the current month and returns the new month. /// /// - Parameter months: number of months to subtract. May be negative, in which case it will be added /// - Returns: new month. public func subtract(months: Int) -> Month { return add(months: -(months % 12)) } /// Returns the number of days in a this month for a given year /// /// - Parameter year: reference year. /// - Returns: The number of days in this month. public func numberOfDays(year: Int) -> Int { switch self { case .february: return Year(year).isLeap() ? 29 : 28 case .april, .june, .september, .november: return 30 default: return 31 } } } ================================================ FILE: Sources/SwiftDate/Supports/Zones.swift ================================================ // // 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 protocol ZoneConvertible { func toTimezone() -> TimeZone } extension TimeZone: ZoneConvertible { public func toTimezone() -> TimeZone { return self } } // swiftlint:disable type_body_length public enum Zones: String, ZoneConvertible { case current = "Current" case autoUpdating = "CurrentAutoUpdating" case africaAbidjan = "Africa/Abidjan" case africaAccra = "Africa/Accra" case africaAddisAbaba = "Africa/Addis_Ababa" case africaAlgiers = "Africa/Algiers" case africaAsmara = "Africa/Asmara" case africaBamako = "Africa/Bamako" case africaBangui = "Africa/Bangui" case africaBanjul = "Africa/Banjul" case africaBissau = "Africa/Bissau" case africaBlantyre = "Africa/Blantyre" case africaBrazzaville = "Africa/Brazzaville" case africaBujumbura = "Africa/Bujumbura" case africaCairo = "Africa/Cairo" case africaCasablanca = "Africa/Casablanca" case africaCeuta = "Africa/Ceuta" case africaConakry = "Africa/Conakry" case africaDakar = "Africa/Dakar" case africaDarEsSalaam = "Africa/Dar_es_Salaam" case africaDjibouti = "Africa/Djibouti" case africaDouala = "Africa/Douala" case africaElAaiun = "Africa/El_Aaiun" case africaFreetown = "Africa/Freetown" case africaGaborone = "Africa/Gaborone" case africaHarare = "Africa/Harare" case africaJohannesburg = "Africa/Johannesburg" case africaJuba = "Africa/Juba" case africaKampala = "Africa/Kampala" case africaKhartoum = "Africa/Khartoum" case fricaKigali = "Africa/Kigali" case africaKinshasa = "Africa/Kinshasa" case africaLagos = "Africa/Lagos" case africaLibreville = "Africa/Libreville" case africaLome = "Africa/Lome" case africaLuanda = "Africa/Luanda" case africaLubumbashi = "Africa/Lubumbashi" case africaLusaka = "Africa/Lusaka" case africaMalabo = "Africa/Malabo" case africaMaputo = "Africa/Maputo" case africaMaseru = "Africa/Maseru" case africaMbabane = "Africa/Mbabane" case africaMogadishu = "Africa/Mogadishu" case africaMonrovia = "Africa/Monrovia" case africaNairobi = "Africa/Nairobi" case africaNdjamena = "Africa/Ndjamena" case africaNiamey = "Africa/Niamey" case africaNouakchott = "Africa/Nouakchott" case africaOuagadougou = "Africa/Ouagadougou" case africaPortoNovo = "Africa/Porto-Novo" case africaSaoTome = "Africa/Sao_Tome" case africaTripoli = "Africa/Tripoli" case africaTunis = "Africa/Tunis" case africaWindhoek = "Africa/Windhoek" case americaAdak = "America/Adak" case americaAnchorage = "America/Anchorage" case americaAnguilla = "America/Anguilla" case americaAntigua = "America/Antigua" case americaAraguaina = "America/Araguaina" case americaArgentinaBuenosAires = "America/Argentina/Buenos_Aires" case americaArgentinaCatamarca = "America/Argentina/Catamarca" case americaArgentinaCordoba = "America/Argentina/Cordoba" case americaArgentinaJujuy = "America/Argentina/Jujuy" case americaArgentinaLaRioja = "America/Argentina/La_Rioja" case americaArgentinaMendoza = "America/Argentina/Mendoza" case americaArgentinaRioGallegos = "America/Argentina/Rio_Gallegos" case americaArgentinaSalta = "America/Argentina/Salta" case americaArgentinaSanJuan = "America/Argentina/San_Juan" case americaArgentinaSanLuis = "America/Argentina/San_Luis" case americaArgentinaTucuman = "America/Argentina/Tucuman" case americaArgentinaUshuaia = "America/Argentina/Ushuaia" case americaAruba = "America/Aruba" case americaAsuncion = "America/Asuncion" case americaAtikokan = "America/Atikokan" case americaBahia = "America/Bahia" case americaBahiaBanderas = "America/Bahia_Banderas" case americaBarbados = "America/Barbados" case americaBelem = "America/Belem" case americaBelize = "America/Belize" case americaBlancSablon = "America/Blanc-Sablon" case americaBoaVista = "America/Boa_Vista" case americaBogota = "America/Bogota" case americaBoise = "America/Boise" case americaCambridgeBay = "America/Cambridge_Bay" case americaCampoGrande = "America/Campo_Grande" case americaCancun = "America/Cancun" case americaCaracas = "America/Caracas" case americaCayenne = "America/Cayenne" case americaCayman = "America/Cayman" case americaChicago = "America/Chicago" case americaChihuahua = "America/Chihuahua" case americaCostaRica = "America/Costa_Rica" case americaCreston = "America/Creston" case americaCuiaba = "America/Cuiaba" case americaCuracao = "America/Curacao" case americaDanmarkshavn = "America/Danmarkshavn" case americaDawson = "America/Dawson" case americaDawsonCreek = "America/Dawson_Creek" case americaDenver = "America/Denver" case americaDetroit = "America/Detroit" case americaDominica = "America/Dominica" case americaEdmonton = "America/Edmonton" case americaEirunepe = "America/Eirunepe" case americaElSalvador = "America/El_Salvador" case americaFortNelson = "America/Fort_Nelson" case americaFortaleza = "America/Fortaleza" case americaGlaceBay = "America/Glace_Bay" case americaGodthab = "America/Godthab" case americaGooseBay = "America/Goose_Bay" case americaGrandTurk = "America/Grand_Turk" case americaGrenada = "America/Grenada" case americaGuadeloupe = "America/Guadeloupe" case americaGuatemala = "America/Guatemala" case americaGuayaquil = "America/Guayaquil" case americaGuyana = "America/Guyana" case americaHalifax = "America/Halifax" case americaHavana = "America/Havana" case americaHermosillo = "America/Hermosillo" case americaIndianaIndianapolis = "America/Indiana/Indianapolis" case americaIndianaKnox = "America/Indiana/Knox" case americaIndianaMarengo = "America/Indiana/Marengo" case americaIndianaPetersburg = "America/Indiana/Petersburg" case americaIndianaTellCity = "America/Indiana/Tell_City" case americaIndianaVevay = "America/Indiana/Vevay" case americaIndianaVincennes = "America/Indiana/Vincennes" case americaIndianaWinamac = "America/Indiana/Winamac" case americaInuvik = "America/Inuvik" case americaIqaluit = "America/Iqaluit" case americaJamaica = "America/Jamaica" case americaJuneau = "America/Juneau" case americaKentuckyLouisville = "America/Kentucky/Louisville" case americaKentuckyMonticello = "America/Kentucky/Monticello" case americaKralendijk = "America/Kralendijk" case americaLaPaz = "America/La_Paz" case americaLima = "America/Lima" case americaLosAngeles = "America/Los_Angeles" case americaLowerPrinces = "America/Lower_Princes" case americaMaceio = "America/Maceio" case americaManagua = "America/Managua" case americaManaus = "America/Manaus" case americaMarigot = "America/Marigot" case americaMartinique = "America/Martinique" case americaMatamoros = "America/Matamoros" case americaMazatlan = "America/Mazatlan" case americaMenominee = "America/Menominee" case americaMerida = "America/Merida" case americaMetlakatla = "America/Metlakatla" case americaMexicoCity = "America/Mexico_City" case americaMiquelon = "America/Miquelon" case americaMoncton = "America/Moncton" case americaMonterrey = "America/Monterrey" case americaMontevideo = "America/Montevideo" case americaMontreal = "America/Montreal" case americaMontserrat = "America/Montserrat" case americaNassau = "America/Nassau" case americaNewYork = "America/New_York" case americaNipigon = "America/Nipigon" case americaNome = "America/Nome" case americaNoronha = "America/Noronha" case americaNorthDakotaBeulah = "America/North_Dakota/Beulah" case americaNorthDakotaCenter = "America/North_Dakota/Center" case americaNorthDakotaNewSalem = "America/North_Dakota/New_Salem" case americaOjinaga = "America/Ojinaga" case americaPanama = "America/Panama" case americaPangnirtung = "America/Pangnirtung" case americaParamaribo = "America/Paramaribo" case americaPhoenix = "America/Phoenix" case americaPortAuPrince = "America/Port-au-Prince" case americaPortOfSpain = "America/Port_of_Spain" case americaPortoVelho = "America/Porto_Velho" case americaPuertoRico = "America/Puerto_Rico" case americaRainyRiver = "America/Rainy_River" case americaRankinInlet = "America/Rankin_Inlet" case americaRecife = "America/Recife" case americaRegina = "America/Regina" case americaResolute = "America/Resolute" case americaRioBranco = "America/Rio_Branco" case americaSantaIsabel = "America/Santa_Isabel" case americaSantarem = "America/Santarem" case americaSantiago = "America/Santiago" case americaSantoDomingo = "America/Santo_Domingo" case americaSaoPaulo = "America/Sao_Paulo" case americaScoresbysund = "America/Scoresbysund" case americaShiprock = "America/Shiprock" case americaSitka = "America/Sitka" case americaStBarthelemy = "America/St_Barthelemy" case americaStJohns = "America/St_Johns" case americaStKitts = "America/St_Kitts" case americaStLucia = "America/St_Lucia" case americaStThomas = "America/St_Thomas" case americaStVincent = "America/St_Vincent" case americaSwiftCurrent = "America/Swift_Current" case americaTegucigalpa = "America/Tegucigalpa" case americaThule = "America/Thule" case americaThunderBay = "America/Thunder_Bay" case americaTijuana = "America/Tijuana" case americaToronto = "America/Toronto" case americaTortola = "America/Tortola" case americaVancouver = "America/Vancouver" case americaWhitehorse = "America/Whitehorse" case americaWinnipeg = "America/Winnipeg" case americaYakutat = "America/Yakutat" case americaYellowknife = "America/Yellowknife" case antarcticaCasey = "Antarctica/Casey" case antarcticaDavis = "Antarctica/Davis" case antarcticaDumontdurville = "Antarctica/DumontDUrville" case antarcticaMacquarie = "Antarctica/Macquarie" case antarcticaMawson = "Antarctica/Mawson" case antarcticaMcmurdo = "Antarctica/McMurdo" case antarcticaPalmer = "Antarctica/Palmer" case antarcticaRothera = "Antarctica/Rothera" case antarcticaSouthPole = "Antarctica/South_Pole" case antarcticaSyowa = "Antarctica/Syowa" case antarcticaTroll = "Antarctica/Troll" case antarcticaVostok = "Antarctica/Vostok" case arcticLongyearbyen = "Arctic/Longyearbyen" case asiaAden = "Asia/Aden" case asiaAlmaty = "Asia/Almaty" case asiaAmman = "Asia/Amman" case asiaAnadyr = "Asia/Anadyr" case asiaAqtau = "Asia/Aqtau" case asiaAqtobe = "Asia/Aqtobe" case asiaAshgabat = "Asia/Ashgabat" case asiaBaghdad = "Asia/Baghdad" case asiaBahrain = "Asia/Bahrain" case asiaBaku = "Asia/Baku" case asiaBangkok = "Asia/Bangkok" case asiaBeirut = "Asia/Beirut" case asiaBishkek = "Asia/Bishkek" case asiaBrunei = "Asia/Brunei" case asiaChita = "Asia/Chita" case asiaChoibalsan = "Asia/Choibalsan" case asiaChongqing = "Asia/Chongqing" case asiaColombo = "Asia/Colombo" case asiaDamascus = "Asia/Damascus" case asiaDhaka = "Asia/Dhaka" case asiaDili = "Asia/Dili" case asiaDubai = "Asia/Dubai" case asiaDushanbe = "Asia/Dushanbe" case asiaGaza = "Asia/Gaza" case asiaHarbin = "Asia/Harbin" case asiaHebron = "Asia/Hebron" case asiaHoChiMinh = "Asia/Ho_Chi_Minh" case asiaSaigon = "Asia/Saigon" case asiaHongKong = "Asia/Hong_Kong" case asiaHovd = "Asia/Hovd" case asiaIrkutsk = "Asia/Irkutsk" case asiaJakarta = "Asia/Jakarta" case asiaJayapura = "Asia/Jayapura" case asiaJerusalem = "Asia/Jerusalem" case asiaKabul = "Asia/Kabul" case asiaKamchatka = "Asia/Kamchatka" case asiaKarachi = "Asia/Karachi" case asiaKashgar = "Asia/Kashgar" case asiaKathmandu = "Asia/Kathmandu" case asiaKatmandu = "Asia/Katmandu" case asiaKhandyga = "Asia/Khandyga" case asiaKolkata = "Asia/Kolkata" case asiaKrasnoyarsk = "Asia/Krasnoyarsk" case asiaKualaLumpur = "Asia/Kuala_Lumpur" case asiaKuching = "Asia/Kuching" case asiaKuwait = "Asia/Kuwait" case asiaMacau = "Asia/Macau" case asiaMagadan = "Asia/Magadan" case asiaMakassar = "Asia/Makassar" case asiaManila = "Asia/Manila" case asiaMuscat = "Asia/Muscat" case asiaNicosia = "Asia/Nicosia" case asiaNovokuznetsk = "Asia/Novokuznetsk" case asiaNovosibirsk = "Asia/Novosibirsk" case asiaOmsk = "Asia/Omsk" case asiaOral = "Asia/Oral" case asiaPhnomPenh = "Asia/Phnom_Penh" case asiaPontianak = "Asia/Pontianak" case asiaPyongyang = "Asia/Pyongyang" case asiaQatar = "Asia/Qatar" case asiaQyzylorda = "Asia/Qyzylorda" case asiaRangoon = "Asia/Rangoon" case asiaRiyadh = "Asia/Riyadh" case asiaSakhalin = "Asia/Sakhalin" case asiaSamarkand = "Asia/Samarkand" case asiaSeoul = "Asia/Seoul" case asiaShanghai = "Asia/Shanghai" case asiaSingapore = "Asia/Singapore" case asiaSrednekolymsk = "Asia/Srednekolymsk" case asiaTaipei = "Asia/Taipei" case asiaTashkent = "Asia/Tashkent" case asiaTbilisi = "Asia/Tbilisi" case asiaTehran = "Asia/Tehran" case asiaThimphu = "Asia/Thimphu" case asiaTokyo = "Asia/Tokyo" case asiaUlaanbaatar = "Asia/Ulaanbaatar" case asiaUrumqi = "Asia/Urumqi" case asiaUstNera = "Asia/Ust-Nera" case asiaVientiane = "Asia/Vientiane" case asiaVladivostok = "Asia/Vladivostok" case asiaYakutsk = "Asia/Yakutsk" case asiaYekaterinburg = "Asia/Yekaterinburg" case asiaYerevan = "Asia/Yerevan" case atlanticAzores = "Atlantic/Azores" case atlanticBermuda = "Atlantic/Bermuda" case atlanticCanary = "Atlantic/Canary" case atlanticCapeVerde = "Atlantic/Cape_Verde" case atlanticFaroe = "Atlantic/Faroe" case atlanticMadeira = "Atlantic/Madeira" case atlanticReykjavik = "Atlantic/Reykjavik" case atlanticSouthGeorgia = "Atlantic/South_Georgia" case atlanticStHelena = "Atlantic/St_Helena" case atlanticStanley = "Atlantic/Stanley" case australiaAdelaide = "Australia/Adelaide" case australiaBrisbane = "Australia/Brisbane" case australiaBrokenHill = "Australia/Broken_Hill" case australiaCurrie = "Australia/Currie" case australiaDarwin = "Australia/Darwin" case australiaEucla = "Australia/Eucla" case australiaHobart = "Australia/Hobart" case australiaLindeman = "Australia/Lindeman" case australiaLordHowe = "Australia/Lord_Howe" case australiaMelbourne = "Australia/Melbourne" case australiaPerth = "Australia/Perth" case australiaSydney = "Australia/Sydney" case europeAmsterdam = "Europe/Amsterdam" case europeAndorra = "Europe/Andorra" case europeAthens = "Europe/Athens" case europeBelgrade = "Europe/Belgrade" case europeBerlin = "Europe/Berlin" case europeBratislava = "Europe/Bratislava" case europeBrussels = "Europe/Brussels" case europeBucharest = "Europe/Bucharest" case europeBudapest = "Europe/Budapest" case europeBusingen = "Europe/Busingen" case europeChisinau = "Europe/Chisinau" case europeCopenhagen = "Europe/Copenhagen" case europeDublin = "Europe/Dublin" case europeGibraltar = "Europe/Gibraltar" case europeGuernsey = "Europe/Guernsey" case europeHelsinki = "Europe/Helsinki" case europeIsleOfMan = "Europe/Isle_of_Man" case europeIstanbul = "Europe/Istanbul" case europeJersey = "Europe/Jersey" case europeKaliningrad = "Europe/Kaliningrad" case europeKiev = "Europe/Kiev" case europeLisbon = "Europe/Lisbon" case europeLjubljana = "Europe/Ljubljana" case europeLondon = "Europe/London" case europeLuxembourg = "Europe/Luxembourg" case europeMadrid = "Europe/Madrid" case europeMalta = "Europe/Malta" case europeMariehamn = "Europe/Mariehamn" case europeMinsk = "Europe/Minsk" case europeMonaco = "Europe/Monaco" case europeMoscow = "Europe/Moscow" case europeOslo = "Europe/Oslo" case europeParis = "Europe/Paris" case europePodgorica = "Europe/Podgorica" case europePrague = "Europe/Prague" case europeRiga = "Europe/Riga" case europeRome = "Europe/Rome" case europeSamara = "Europe/Samara" case europeSanMarino = "Europe/San_Marino" case europeSarajevo = "Europe/Sarajevo" case europeSimferopol = "Europe/Simferopol" case europeSkopje = "Europe/Skopje" case europeSofia = "Europe/Sofia" case europeStockholm = "Europe/Stockholm" case europeTallinn = "Europe/Tallinn" case europeTirane = "Europe/Tirane" case europeUzhgorod = "Europe/Uzhgorod" case europeVaduz = "Europe/Vaduz" case europeVatican = "Europe/Vatican" case europeVienna = "Europe/Vienna" case europeVilnius = "Europe/Vilnius" case europeVolgograd = "Europe/Volgograd" case europeWarsaw = "Europe/Warsaw" case europeZagreb = "Europe/Zagreb" case europeZaporozhye = "Europe/Zaporozhye" case europeZurich = "Europe/Zurich" case gmt = "GMT" case indianAntananarivo = "Indian/Antananarivo" case indianChagos = "Indian/Chagos" case indianChristmas = "Indian/Christmas" case indianCocos = "Indian/Cocos" case indianComoro = "Indian/Comoro" case indianKerguelen = "Indian/Kerguelen" case indianMahe = "Indian/Mahe" case indianMaldives = "Indian/Maldives" case indianMauritius = "Indian/Mauritius" case indianMayotte = "Indian/Mayotte" case indianReunion = "Indian/Reunion" case pacificApia = "Pacific/Apia" case pacificAuckland = "Pacific/Auckland" case pacificBougainville = "Pacific/Bougainville" case pacificChatham = "Pacific/Chatham" case pacificChuuk = "Pacific/Chuuk" case pacificEaster = "Pacific/Easter" case pacificEfate = "Pacific/Efate" case pacificEnderbury = "Pacific/Enderbury" case pacificFakaofo = "Pacific/Fakaofo" case pacificFiji = "Pacific/Fiji" case pacificFunafuti = "Pacific/Funafuti" case pacificGalapagos = "Pacific/Galapagos" case pacificGambier = "Pacific/Gambier" case pacificGuadalcanal = "Pacific/Guadalcanal" case pacificGuam = "Pacific/Guam" case pacificHonolulu = "Pacific/Honolulu" case pacificJohnston = "Pacific/Johnston" case pacificKiritimati = "Pacific/Kiritimati" case pacificKosrae = "Pacific/Kosrae" case pacificKwajalein = "Pacific/Kwajalein" case pacificMajuro = "Pacific/Majuro" case pacificMarquesas = "Pacific/Marquesas" case pacificMidway = "Pacific/Midway" case pacificNauru = "Pacific/Nauru" case pacificNiue = "Pacific/Niue" case pacificNorfolk = "Pacific/Norfolk" case pacificNoumea = "Pacific/Noumea" case pacificPagoPago = "Pacific/Pago_Pago" case pacificPalau = "Pacific/Palau" case pacificPitcairn = "Pacific/Pitcairn" case pacificPohnpei = "Pacific/Pohnpei" case pacificPonape = "Pacific/Ponape" case pacificPortMoresby = "Pacific/Port_Moresby" case pacificRarotonga = "Pacific/Rarotonga" case pacificSaipan = "Pacific/Saipan" case pacificTahiti = "Pacific/Tahiti" case pacificTarawa = "Pacific/Tarawa" case pacificTongatapu = "Pacific/Tongatapu" case pacificTruk = "Pacific/Truk" case pacificWake = "Pacific/Wake" case pacificWallis = "Pacific/Wallis" public func toTimezone() -> TimeZone { switch self { case .current: return TimeZone.current case .autoUpdating: return TimeZone.autoupdatingCurrent default: return TimeZone(identifier: rawValue)! } } } ================================================ FILE: Sources/SwiftDate/SwiftDate.swift ================================================ // // 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 SwiftDate { private init() { } /// The default region is used to manipulate and work with plain `Date` object and /// wherever a region parameter is optional. By default region is the to GMT timezone /// along with the default device's locale and calendar (both autoupdating). public static var defaultRegion = Region.UTC /// This is the ordered list of all formats SwiftDate can use in order to attempt parsing a passaed /// date expressed as string. Evaluation is made in order; you can add or remove new formats as you wish. /// In order to reset the list call `resetAutoFormats()` function. public static var autoFormats: [String] { set { DateFormats.autoFormats = newValue } get { return DateFormats.autoFormats } } /// Reset the list of all built-in auto formats patterns. public static func resetAutoFormats() { DateFormats.resetAutoFormats() } } ================================================ FILE: Sources/SwiftDate/TimePeriod/Groups/TimePeriodChain.swift ================================================ // // 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 /// Time period chains serve as a tightly coupled set of time periods. /// They are always organized by start and end date, and have their own characteristics like /// a StartDate and EndDate that are extrapolated from the time periods within. /// Time period chains do not allow overlaps within their set of time periods. /// This type of group is ideal for modeling schedules like sequential meetings or appointments. open class TimePeriodChain: TimePeriodGroup { // MARK: - Chain Existence Manipulation /** * Append a TimePeriodProtocol to the periods array and update the Chain's * beginning and end. * * - parameter period: TimePeriodProtocol to add to the collection */ public func append(_ period: TimePeriodProtocol) { let beginning = (periods.count > 0) ? periods.last!.end! : period.start let newPeriod = TimePeriod(start: beginning!, duration: period.duration) periods.append(newPeriod) //Update updateExtremes if periods.count == 1 { start = period.start end = period.end } else { end = end?.addingTimeInterval(period.duration) } } /** * Append a TimePeriodProtocol array to the periods array and update the Chain's * beginning and end. * * - parameter periodArray: TimePeriodProtocol list to add to the collection */ public func append(contentsOf group: G) { for period in group.periods { let beginning = (periods.count > 0) ? periods.last!.end! : period.start let newPeriod = TimePeriod(start: beginning!, duration: period.duration) periods.append(newPeriod) //Update updateExtremes if periods.count == 1 { start = period.start end = period.end } else { end = end?.addingTimeInterval(period.duration) } } } /// Insert period into periods array at given index. /// /// - Parameters: /// - period: The period to insert /// - index: Index to insert period at public func insert(_ period: TimePeriodProtocol, at index: Int) { //Check for special zero case which takes the beginning date if index == 0 && period.start != nil && period.end != nil { //Insert new period periods.insert(period, at: index) } else if period.start != nil && period.end != nil { //Insert new period periods.insert(period, at: index) } else { print("All TimePeriods in a TimePeriodChain must contain a defined start and end date") return } //Shift all periods after inserted period for i in 0.. index && i > 0 { let currentPeriod = TimePeriod(start: period.start, end: period.end) periods[i].start = periods[i - 1].end periods[i].end = periods[i].start!.addingTimeInterval(currentPeriod.duration) } } updateExtremes() } /// Remove from period array at the given index. /// /// - Parameter index: The index in the collection to remove public func remove(at index: Int) { //Retrieve duration of period to be removed let duration = periods[index].duration //Remove period periods.remove(at: index) //Shift all periods after inserted period for i in index..(_ transform: (TimePeriodProtocol) throws -> T) rethrows -> [T] { return try periods.map(transform) } public override func filter(_ isIncluded: (TimePeriodProtocol) throws -> Bool) rethrows -> [TimePeriodProtocol] { return try periods.filter(isIncluded) } internal override func reduce(_ initialResult: Result, _ nextPartialResult: (Result, TimePeriodProtocol) throws -> Result) rethrows -> Result { return try periods.reduce(initialResult, nextPartialResult) } /// Removes the last object from the `TimePeriodChain` and returns it public func pop() -> TimePeriodProtocol? { let period = periods.popLast() updateExtremes() return period } internal func updateExtremes() { start = periods.first?.start end = periods.last?.end } } ================================================ FILE: Sources/SwiftDate/TimePeriod/Groups/TimePeriodCollection.swift ================================================ // // 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 /// Sort type /// /// - ascending: sort in ascending order /// - descending: sort in descending order public enum SortMode { case ascending case descending } /// Sorting type /// /// - start: sort by start date /// - end: sort by end date /// - duration: sort by duration /// - custom: sort using custom function public enum SortType { case start(_: SortMode) case end(_: SortMode) case duration(_: SortMode) case custom(_: ((TimePeriodProtocol, TimePeriodProtocol) -> Bool)) } /// Time period collections serve as loose sets of time periods. /// They are unorganized unless you decide to sort them, and have their own characteristics /// like a `start` and `end` that are extrapolated from the time periods within. /// Time period collections allow overlaps within their set of time periods. open class TimePeriodCollection: TimePeriodGroup { // MARK: - Collection Manipulation /// Append a TimePeriodProtocol to the periods array and check if the Collection's start and end should change. /// /// - Parameter period: TimePeriodProtocol to add to the collection public func append(_ period: TimePeriodProtocol) { periods.append(period) updateExtremes(period: period) } /// Append a TimePeriodProtocol array to the periods array and check if the Collection's /// start and end should change. /// /// - Parameter periodArray: TimePeriodProtocol list to add to the collection public func append(_ periodArray: [TimePeriodProtocol]) { for period in periodArray { periods.append(period) updateExtremes(period: period) } } /// Append a TimePeriodGroup's periods array to the periods array of self and check if the Collection's /// start and end should change. /// /// - Parameter newPeriods: TimePeriodGroup to merge periods arrays with public func append(contentsOf newPeriods: C) { for period in newPeriods as TimePeriodGroup { periods.append(period) updateExtremes(period: period) } } /// Insert period into periods array at given index. /// /// - Parameters: /// - newElement: The period to insert /// - index: Index to insert period at public func insert(_ newElement: TimePeriodProtocol, at index: Int) { periods.insert(newElement, at: index) updateExtremes(period: newElement) } /// Remove from period array at the given index. /// /// - Parameter at: The index in the collection to remove public func remove(at: Int) { periods.remove(at: at) updateExtremes() } /// Remove all periods from period array. public func removeAll() { periods.removeAll() updateExtremes() } // MARK: - Sorting /// Sort elements in place using given method. /// /// - Parameter type: sorting method public func sort(by type: SortType) { switch type { case .duration(let mode): periods.sort(by: sortFuncDuration(mode)) case .start(let mode): periods.sort(by: sortFunc(byStart: true, type: mode)) case .end(let mode): periods.sort(by: sortFunc(byStart: false, type: mode)) case .custom(let f): periods.sort(by: f) } } /// Generate a new `TimePeriodCollection` where items are sorted with specified method. /// /// - Parameters: /// - type: sorting method /// - Returns: collection ordered by given function public func sorted(by type: SortType) -> TimePeriodCollection { var sortedList: [TimePeriodProtocol]! switch type { case .duration(let mode): sortedList = periods.sorted(by: sortFuncDuration(mode)) case .start(let mode): sortedList = periods.sorted(by: sortFunc(byStart: true, type: mode)) case .end(let mode): sortedList = periods.sorted(by: sortFunc(byStart: false, type: mode)) case .custom(let f): sortedList = periods.sorted(by: f) } return TimePeriodCollection(sortedList) } // MARK: - Collection Relationship /// Returns from the `TimePeriodCollection` a sub-collection of `TimePeriod`s /// whose start and end dates fall completely inside the interval of the given `TimePeriod`. /// /// - Parameter period: The period to compare each other period against /// - Returns: Collection of periods inside the given period public func periodsInside(period: TimePeriodProtocol) -> TimePeriodCollection { return TimePeriodCollection(periods.filter({ $0.isInside(period) })) } // Returns from the `TimePeriodCollection` a sub-collection of `TimePeriod`s containing the given date. /// /// - Parameter date: The date to compare each period to /// - Returns: Collection of periods intersected by the given date public func periodsIntersected(by date: DateInRegion) -> TimePeriodCollection { return TimePeriodCollection(periods.filter({ $0.contains(date: date, interval: .closed) })) } /// Returns from the `TimePeriodCollection` a sub-collection of `TimePeriod`s /// containing either the start date or the end date--or both--of the given `TimePeriod`. /// /// - Parameter period: The period to compare each other period to /// - Returns: Collection of periods intersected by the given period public func periodsIntersected(by period: TimePeriodProtocol) -> TimePeriodCollection { return TimePeriodCollection(periods.filter({ $0.intersects(with: period) })) } /// Returns an instance of DTTimePeriodCollection with all the time periods in the receiver that overlap a given time period. /// Overlap with the given time period does NOT include other time periods that simply touch it. /// (i.e. one's start date is equal to another's end date) /// /// - Parameter period: The time period to check against the receiver's time periods. /// - Returns: Collection of periods overlapped by the given period public func periodsOverlappedBy(_ period: TimePeriodProtocol) -> TimePeriodCollection { return TimePeriodCollection(periods.filter({ $0.overlaps(with: period) })) } // MARK: - Map public func map(_ transform: (TimePeriodProtocol) throws -> TimePeriodProtocol) rethrows -> TimePeriodCollection { var mappedArray = [TimePeriodProtocol]() mappedArray = try periods.map(transform) let mappedCollection = TimePeriodCollection() for period in mappedArray { mappedCollection.periods.append(period) mappedCollection.updateExtremes(period: period) } return mappedCollection } // MARK: - Helpers private func sortFuncDuration(_ type: SortMode) -> ((TimePeriodProtocol, TimePeriodProtocol) -> Bool) { switch type { case .ascending: return { $0.duration < $1.duration } case .descending: return { $0.duration > $1.duration } } } private func sortFunc(byStart start: Bool = true, type: SortMode) -> ((TimePeriodProtocol, TimePeriodProtocol) -> Bool) { return { let date0 = (start ? $0.start : $0.end) let date1 = (start ? $1.start : $1.end) if date0 == nil && date1 == nil { return false } else if date0 == nil { return true } else if date1 == nil { return false } else { return (type == .ascending ? date1! > date0! : date0! > date1!) } } } private func updateExtremes(period: TimePeriodProtocol) { //Check incoming period against previous start and end date guard count != 1 else { start = period.start end = period.end return } start = nilOrEarlier(date1: start, date2: period.start) end = nilOrLater(date1: end, date2: period.end) } private func updateExtremes() { guard periods.count > 0 else { start = nil end = nil return } start = periods.first!.start end = periods.first!.end for i in 1.. DateInRegion? { guard date1 != nil && date2 != nil else { return nil } return date1!.earlierDate(date2!) } private func nilOrLater(date1: DateInRegion?, date2: DateInRegion?) -> DateInRegion? { guard date1 != nil && date2 != nil else { return nil } return date1!.laterDate(date2!) } } ================================================ FILE: Sources/SwiftDate/TimePeriod/Groups/TimePeriodGroup.swift ================================================ // // 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 /// Time period groups are the final abstraction of date and time in DateTools. /// Here, time periods are gathered and organized into something useful. /// There are two main types of time period groups, `TimePeriodCollection` and `TimePeriodChain`. open class TimePeriodGroup: Sequence, Equatable { /// Array of periods that define the group. internal var periods: [TimePeriodProtocol] = [] /// The earliest beginning date of a `TimePeriod` in the group. /// `nil` if any `TimePeriod` in group has a nil beginning date (indefinite). public internal(set) var start: DateInRegion? /// The latest end date of a `TimePeriod` in the group. /// `nil` if any `TimePeriod` in group has a nil end date (indefinite). public internal(set) var end: DateInRegion? /// The total amount of time between the earliest and latest dates stored in the periods array. /// `nil` if any beginning or end date in any contained period is `nil`. public var duration: TimeInterval? { guard let start = start, let end = end else { return nil } return end.timeIntervalSince(start) } /// The number of periods in the periods array. public var count: Int { return periods.count } // MARK: - Equatable public static func == (lhs: TimePeriodGroup, rhs: TimePeriodGroup) -> Bool { return TimePeriodGroup.hasSameElements(array1: lhs.periods, rhs.periods) } // MARK: - Initializers public init(_ periods: [TimePeriodProtocol]? = nil) { self.periods = (periods ?? []) } // MARK: - Sequence Protocol public func makeIterator() -> IndexingIterator<[TimePeriodProtocol]> { return periods.makeIterator() } public func map(_ transform: (TimePeriodProtocol) throws -> T) rethrows -> [T] { return try periods.map(transform) } public func filter(_ isIncluded: (TimePeriodProtocol) throws -> Bool) rethrows -> [TimePeriodProtocol] { return try periods.filter(isIncluded) } public func forEach(_ body: (TimePeriodProtocol) throws -> Void) rethrows { return try periods.forEach(body) } public func split(maxSplits: Int, omittingEmptySubsequences: Bool, whereSeparator isSeparator: (TimePeriodProtocol) throws -> Bool) rethrows -> [AnySequence] { return try periods.split(maxSplits: maxSplits, omittingEmptySubsequences: omittingEmptySubsequences, whereSeparator: isSeparator).map(AnySequence.init) } public subscript(index: Int) -> TimePeriodProtocol { get { return periods[index] } } internal func reduce(_ initialResult: Result, _ nextPartialResult: (Result, TimePeriodProtocol) throws -> Result) rethrows -> Result { return try periods.reduce(initialResult, nextPartialResult) } // MARK: - Internal Helper Functions internal static func hasSameElements(array1: [TimePeriodProtocol], _ array2: [TimePeriodProtocol]) -> Bool { guard array1.count == array2.count else { return false // No need to sorting if they already have different counts } let compArray1: [TimePeriodProtocol] = array1.sorted { (period1: TimePeriodProtocol, period2: TimePeriodProtocol) -> Bool in if period1.start == nil && period2.start == nil { return false } else if period1.start == nil { return true } else if period2.start == nil { return false } else { return period2.start! < period1.start! } } let compArray2: [TimePeriodProtocol] = array2.sorted { (period1: TimePeriodProtocol, period2: TimePeriodProtocol) -> Bool in if period1.start == nil && period2.start == nil { return false } else if period1.start == nil { return true } else if period2.start == nil { return false } else { return period2.start! < period1.start! } } for x in 0.. TimePeriod { return TimePeriod(start: DateInRegion.past(), end: DateInRegion.future()) } // MARK: - Shifted /// Shift the `TimePeriod` by a `TimeInterval` /// /// - Parameter timeInterval: The time interval to shift the period by /// - Returns: The new, shifted `TimePeriod` public func shifted(by timeInterval: TimeInterval) -> TimePeriod { let timePeriod = TimePeriod() timePeriod.start = start?.addingTimeInterval(timeInterval) timePeriod.end = end?.addingTimeInterval(timeInterval) return timePeriod } /// Shift the `TimePeriod` by the specified components value. /// ie. `let shifted = period.shifted(by: 3.days)` /// /// - Parameter components: components to shift /// - Returns: new period public func shifted(by components: DateComponents) -> TimePeriod { let timePeriod = TimePeriod() timePeriod.start = (hasStart ? (start! + components) : nil) timePeriod.end = (hasEnd ? (end! + components) : nil) return timePeriod } // MARK: - Lengthen / Shorten /// Lengthen the `TimePeriod` by a `TimeInterval` /// /// - Parameters: /// - timeInterval: The time interval to lengthen the period by /// - anchor: The anchor point from which to make the change /// - Returns: The new, lengthened `TimePeriod` public func lengthened(by timeInterval: TimeInterval, at anchor: TimePeriodAnchor) -> TimePeriod { let timePeriod = TimePeriod() switch anchor { case .beginning: timePeriod.start = start timePeriod.end = end?.addingTimeInterval(timeInterval) case .center: timePeriod.start = start?.addingTimeInterval(-timeInterval) timePeriod.end = end?.addingTimeInterval(timeInterval) case .end: timePeriod.start = start?.addingTimeInterval(-timeInterval) timePeriod.end = end } return timePeriod } /// Shorten the `TimePeriod` by a `TimeInterval` /// /// - Parameters: /// - timeInterval: The time interval to shorten the period by /// - anchor: The anchor point from which to make the change /// - Returns: The new, shortened `TimePeriod` public func shortened(by timeInterval: TimeInterval, at anchor: TimePeriodAnchor) -> TimePeriod { let timePeriod = TimePeriod() switch anchor { case .beginning: timePeriod.start = start timePeriod.end = end?.addingTimeInterval(-timeInterval) case .center: timePeriod.start = start?.addingTimeInterval(-timeInterval / 2) timePeriod.end = end?.addingTimeInterval(timeInterval / 2) case .end: timePeriod.start = start?.addingTimeInterval(timeInterval) timePeriod.end = end } return timePeriod } // MARK: - Operator Overloads /// Default anchor = beginning /// Operator overload for lengthening a `TimePeriod` by a `TimeInterval` public static func + (leftAddend: TimePeriod, rightAddend: TimeInterval) -> TimePeriod { return leftAddend.lengthened(by: rightAddend, at: .beginning) } /// Default anchor = beginning /// Operator overload for shortening a `TimePeriod` by a `TimeInterval` public static func - (minuend: TimePeriod, subtrahend: TimeInterval) -> TimePeriod { return minuend.shortened(by: subtrahend, at: .beginning) } /// Operator overload for checking if a `TimePeriod` is equal to a `TimePeriodProtocol` public static func == (left: TimePeriod, right: TimePeriodProtocol) -> Bool { return left.equals(right) } } public extension TimePeriod { /// The start date of the time period var startDate: Date? { return start?.date } /// The end date of the time period var endDate: Date? { return end?.date } /// Create a new time period with the given start date, end date and region (default is UTC) convenience init(startDate: Date, endDate: Date, region: Region = Region.UTC) { let start = DateInRegion(startDate, region: region) let end = DateInRegion(endDate, region: region) self.init(start: start, end: end) } } ================================================ FILE: Sources/SwiftDate/TimePeriod/TimePeriodProtocol.swift ================================================ // // 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 protocol TimePeriodProtocol { /// The start date for a TimePeriod representing the starting boundary of the time period var start: DateInRegion? { get set } /// The end date for a TimePeriod representing the ending boundary of the time period var end: DateInRegion? { get set } } public extension TimePeriodProtocol { /// Return `true` if time period has both start and end dates var hasFiniteRange: Bool { guard start != nil && end != nil else { return false } return true } /// Return `true` if period has a start date var hasStart: Bool { return (start != nil) } /// Return `true` if period has a end date var hasEnd: Bool { return (end != nil) } /// Check if receiver is equal to given period (both start/end groups are equals) /// /// - Parameter period: period to compare against to. /// - Returns: true if are equals func equals(_ period: TimePeriodProtocol) -> Bool { return (start == period.start && end == period.end) } /// If the given `TimePeriod`'s beginning is before `beginning` and /// if the given 'TimePeriod`'s end is after `end`. /// /// - Parameter period: The time period to compare to self /// - Returns: True if self is inside of the given `TimePeriod` func isInside(_ period: TimePeriodProtocol) -> Bool { guard hasFiniteRange, period.hasFiniteRange else { return false } return (period.start! <= start! && period.end! >= end!) } /// If the given Date is after `beginning` and before `end`. /// /// - Parameters: /// - date: The time period to compare to self /// - interval: Whether the edge of the date is included in the calculation /// - Returns: True if the given `TimePeriod` is inside of self func contains(date: DateInRegion, interval: IntervalType = .closed) -> Bool { guard hasFiniteRange else { return false } switch interval { case .closed: return (start! <= date && end! >= date) case .open: return (start! < date && end! > date) } } /// If the given `TimePeriod`'s beginning is after `beginning` and /// if the given 'TimePeriod`'s after is after `end`. /// /// - Parameter period: The time period to compare to self /// - Returns: True if the given `TimePeriod` is inside of self func contains(_ period: TimePeriodProtocol) -> Bool { guard hasFiniteRange, period.hasFiniteRange else { return false } if period.start! < start! && period.end! > start! { return true // Outside -> Inside } else if period.start! >= start! && period.end! <= end! { return true // Enclosing } else if period.start! < end! && period.end! > end! { return true // Inside -> Out } return false } /// If self and the given `TimePeriod` share any sub-`TimePeriod`. /// /// - Parameter period: The time period to compare to self /// - Returns: True if there is a period of time that is shared by both `TimePeriod`s func overlaps(with period: TimePeriodProtocol) -> Bool { if period.start! < start! && period.end! > start! { return true // Outside -> Inside } else if period.start! >= start! && period.end! <= end! { return true // Enclosing } else if period.start! < end! && period.end! > end! { return true // Inside -> Out } return false } /// If self and the given `TimePeriod` overlap or the period's edges touch. /// /// - Parameter period: The time period to compare to self /// - Returns: True if there is a period of time or moment that is shared by both `TimePeriod`s func intersects(with period: TimePeriodProtocol) -> Bool { let relation = self.relation(to: period) return (relation != .after && relation != .before) } /// If self is before the given `TimePeriod` chronologically. (A gap must exist between the two). /// /// - Parameter period: The time period to compare to self /// - Returns: True if self is after the given `TimePeriod` func isBefore(_ period: TimePeriodProtocol) -> Bool { return (relation(to: period) == .before) } /// If self is after the given `TimePeriod` chronologically. (A gap must exist between the two). /// /// - Parameter period: The time period to compare to self /// - Returns: True if self is after the given `TimePeriod` func isAfter(_ period: TimePeriodProtocol) -> Bool { return (relation(to: period) == .after) } /// The period of time between self and the given `TimePeriod` not contained by either. /// /// - Parameter period: The time period to compare to self /// - Returns: The gap between the periods. Zero if there is no gap. func hasGap(between period: TimePeriodProtocol) -> Bool { return (isBefore(period) || isAfter(period)) } /// The period of time between self and the given `TimePeriod` not contained by either. /// /// - Parameter period: The time period to compare to self /// - Returns: The gap between the periods. Zero if there is no gap. func gap(between period: TimePeriodProtocol) -> TimeInterval { guard hasFiniteRange, period.hasFiniteRange else { return TimeInterval.greatestFiniteMagnitude } if end! < period.start! { return abs(end!.timeIntervalSince(period.start!)) } else if period.end! < start! { return abs(end!.timeIntervalSince(start!)) } return 0 } /// In place, shift the `TimePeriod` by a `TimeInterval` /// /// - Parameter timeInterval: The time interval to shift the period by mutating func shift(by timeInterval: TimeInterval) { start?.addTimeInterval(timeInterval) end?.addTimeInterval(timeInterval) } /// In place, lengthen the `TimePeriod`, anchored at the beginning, end or center /// /// - Parameters: /// - timeInterval: The time interval to lengthen the period by /// - anchor: The anchor point from which to make the change mutating func lengthen(by timeInterval: TimeInterval, at anchor: TimePeriodAnchor) { switch anchor { case .beginning: end?.addTimeInterval(timeInterval) case .end: start?.addTimeInterval(timeInterval) case .center: start = start?.addingTimeInterval(-timeInterval / 2.0) end = end?.addingTimeInterval(timeInterval / 2.0) } } /// In place, shorten the `TimePeriod`, anchored at the beginning, end or center /// /// - Parameters: /// - timeInterval: The time interval to shorten the period by /// - anchor: The anchor point from which to make the change mutating func shorten(by timeInterval: TimeInterval, at anchor: TimePeriodAnchor) { switch anchor { case .beginning: end?.addTimeInterval(-timeInterval) case .end: start?.addTimeInterval(timeInterval) case .center: start?.addTimeInterval(timeInterval / 2.0) end?.addTimeInterval(-timeInterval / 2.0) } } /// The relationship of the self `TimePeriod` to the given `TimePeriod`. /// Relations are stored in Enums.swift. Formal defnitions available in the provided /// links: /// [GitHub](https://github.com/MatthewYork/DateTools#relationships), /// [CodeProject](http://www.codeproject.com/Articles/168662/Time-Period-Library-for-NET) /// /// - Parameter period: The time period to compare to self /// - Returns: The relationship between self and the given time period func relation(to period: TimePeriodProtocol) -> TimePeriodRelation { //Make sure that all start and end points exist for comparison guard hasFiniteRange, period.hasFiniteRange else { return .none } //Make sure time periods are of positive durations guard start! < end! && period.start! < period.end! else { return .none } //Make comparisons if period.start! < start! { return .after } else if period.end! == start! { return .startTouching } else if period.start! < start! && period.end! < end! { return .startInside } else if period.start! == start! && period.end! > end! { return .insideStartTouching } else if period.start! == start! && period.end! < end! { return .enclosingStartTouching } else if period.start! > start! && period.end! < end! { return .enclosing } else if period.start! > start! && period.end! == end! { return .enclosingEndTouching } else if period.start == start! && period.end! == end! { return .exactMatch } else if period.start! < start! && period.end! > end! { return .inside } else if period.start! < start! && period.end! == end! { return .insideEndTouching } else if period.start! < end! && period.end! > end! { return .endInside } else if period.start! == end! && period.end! > end! { return .endTouching } else if period.start! > end! { return .before } return .none } /// Return `true` if period is zero-seconds long or less than specified precision. /// /// - Parameter precision: precision in seconds; by default is 0. /// - Returns: true if start/end has the same value or less than specified precision func isMoment(precision: TimeInterval = 0) -> Bool { guard hasFiniteRange else { return false } return (abs(start!.date.timeIntervalSince1970 - end!.date.timeIntervalSince1970) <= precision) } /// Returns the duration of the receiver expressed with given time unit. /// If time period has not a finite range it returns `nil`. /// /// - Parameter unit: unit of the duration /// - Returns: duration, `nil` if period has not a finite range func durationIn(_ units: Set) -> DateComponents? { guard hasFiniteRange else { return nil } return start!.calendar.dateComponents(units, from: start!.date, to: end!.date) } /// Returns the duration of the receiver expressed with given time unit. /// If time period has not a finite range it returns `nil`. /// /// - Parameter unit: unit of the duration /// - Returns: duration, `nil` if period has not a finite range func durationIn(_ unit: Calendar.Component) -> Int? { guard hasFiniteRange else { return nil } return start!.calendar.dateComponents([unit], from: start!.date, to: end!.date).value(for: unit) } /// The duration of the `TimePeriod` in years. /// Returns the `Int.max` if beginning or end are `nil`. var years: Int { guard let b = start, let e = end else { return Int.max } return b.toUnit(.year, to: e) } /// The duration of the `TimePeriod` in months. /// Returns the `Int.max` if beginning or end are `nil`. var months: Int { guard let b = start, let e = end else { return Int.max } return b.toUnit(.month, to: e) } /// The duration of the `TimePeriod` in weeks. /// Returns the `Int.max` if beginning or end are `nil`. var weeks: Int { guard let b = start, let e = end else { return Int.max } return b.toUnit(.weekOfMonth, to: e) } /// The duration of the `TimePeriod` in days. /// Returns the `Int.max` if beginning or end are `nil`. var days: Int { guard let b = start, let e = end else { return Int.max } return b.toUnit(.day, to: e) } /// The duration of the `TimePeriod` in hours. /// Returns the `Int.max` if beginning or end are `nil`. var hours: Int { guard let b = start, let e = end else { return Int.max } return b.toUnit(.hour, to: e) } /// The duration of the `TimePeriod` in years. /// Returns the `Int.max` if beginning or end are `nil`. var minutes: Int { guard let b = start, let e = end else { return Int.max } return b.toUnit(.minute, to: e) } /// The duration of the `TimePeriod` in seconds. /// Returns the `Int.max` if beginning or end are `nil`. var seconds: Int { guard let b = start, let e = end else { return Int.max } return b.toUnit(.second, to: e) } /// The length of time between the beginning and end dates of the /// `TimePeriod` as a `TimeInterval`. /// If intervals are not nil returns `Double.greatestFiniteMagnitude` var duration: TimeInterval { guard let b = start, let e = end else { return TimeInterval(Double.greatestFiniteMagnitude) } return abs(b.date.timeIntervalSince(e.date)) } } ================================================ FILE: SwiftDate.podspec ================================================ Pod::Spec.new do |s| s.name = "SwiftDate" s.version = "7.0.0" s.summary = "The best way to deal with Dates & Time Zones in Swift" s.homepage = "https://github.com/malcommac/SwiftDate.git" s.license = { :type => "MIT", :file => "LICENSE" } s.author = { "Daniele Margutti" => "hello@danielemargutti.com" } s.social_media_url = "https://twitter.com/danielemargutti" s.ios.deployment_target = "13.0" s.osx.deployment_target = "10.15" s.watchos.deployment_target = "6.0" s.tvos.deployment_target = "13.0" s.source = { :git => "https://github.com/malcommac/SwiftDate.git", :tag => s.version.to_s } s.source_files = 'Sources/**/*.swift' s.frameworks = "Foundation" s.swift_versions = ['5.5'] end ================================================ FILE: SwiftDate.xcodeproj/project.pbxproj ================================================ // !$*UTF8*$! { archiveVersion = 1; classes = { }; objectVersion = 47; objects = { /* Begin PBXBuildFile section */ 52D6D9871BEFF229002C0205 /* SwiftDate.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 52D6D97C1BEFF229002C0205 /* SwiftDate.framework */; }; 6434DD6120C7FAF6007626EF /* DateInRegion.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6434DD6020C7FAF6007626EF /* DateInRegion.swift */; }; 6434DD6220C7FAF6007626EF /* DateInRegion.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6434DD6020C7FAF6007626EF /* DateInRegion.swift */; }; 6434DD6320C7FAF6007626EF /* DateInRegion.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6434DD6020C7FAF6007626EF /* DateInRegion.swift */; }; 6434DD6420C7FAF6007626EF /* DateInRegion.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6434DD6020C7FAF6007626EF /* DateInRegion.swift */; }; 6434DD6720C7FC6A007626EF /* Region.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6434DD6620C7FC6A007626EF /* Region.swift */; }; 6434DD6820C7FC6A007626EF /* Region.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6434DD6620C7FC6A007626EF /* Region.swift */; }; 6434DD6920C7FC6A007626EF /* Region.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6434DD6620C7FC6A007626EF /* Region.swift */; }; 6434DD6A20C7FC6A007626EF /* Region.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6434DD6620C7FC6A007626EF /* Region.swift */; }; 6434DD6D20C7FEED007626EF /* Zones.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6434DD6C20C7FEED007626EF /* Zones.swift */; }; 6434DD6E20C7FEED007626EF /* Zones.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6434DD6C20C7FEED007626EF /* Zones.swift */; }; 6434DD6F20C7FEED007626EF /* Zones.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6434DD6C20C7FEED007626EF /* Zones.swift */; }; 6434DD7020C7FEED007626EF /* Zones.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6434DD6C20C7FEED007626EF /* Zones.swift */; }; 6434DD7220C80126007626EF /* Calendars.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6434DD7120C80126007626EF /* Calendars.swift */; }; 6434DD7320C80126007626EF /* Calendars.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6434DD7120C80126007626EF /* Calendars.swift */; }; 6434DD7420C80126007626EF /* Calendars.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6434DD7120C80126007626EF /* Calendars.swift */; }; 6434DD7520C80126007626EF /* Calendars.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6434DD7120C80126007626EF /* Calendars.swift */; }; 6434DD7720C80263007626EF /* Locales.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6434DD7620C80263007626EF /* Locales.swift */; }; 6434DD7820C80263007626EF /* Locales.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6434DD7620C80263007626EF /* Locales.swift */; }; 6434DD7920C80263007626EF /* Locales.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6434DD7620C80263007626EF /* Locales.swift */; }; 6434DD7A20C80263007626EF /* Locales.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6434DD7620C80263007626EF /* Locales.swift */; }; 6434DD7C20C803EF007626EF /* SwiftDate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6434DD7B20C803EF007626EF /* SwiftDate.swift */; }; 6434DD7D20C803EF007626EF /* SwiftDate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6434DD7B20C803EF007626EF /* SwiftDate.swift */; }; 6434DD7E20C803EF007626EF /* SwiftDate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6434DD7B20C803EF007626EF /* SwiftDate.swift */; }; 6434DD7F20C803EF007626EF /* SwiftDate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6434DD7B20C803EF007626EF /* SwiftDate.swift */; }; 6434DD8F20C809B3007626EF /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6434DD8E20C809B3007626EF /* AppDelegate.swift */; }; 6434DD9120C809B3007626EF /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6434DD9020C809B3007626EF /* ViewController.swift */; }; 6434DD9420C809B3007626EF /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 6434DD9220C809B3007626EF /* Main.storyboard */; }; 6434DD9620C809B4007626EF /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 6434DD9520C809B4007626EF /* Assets.xcassets */; }; 6434DD9920C809B4007626EF /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 6434DD9720C809B4007626EF /* LaunchScreen.storyboard */; }; 6434DD9E20C809C2007626EF /* SwiftDate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6434DD7B20C803EF007626EF /* SwiftDate.swift */; }; 6434DD9F20C809C2007626EF /* Zones.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6434DD6C20C7FEED007626EF /* Zones.swift */; }; 6434DDA020C809C2007626EF /* Calendars.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6434DD7120C80126007626EF /* Calendars.swift */; }; 6434DDA120C809C2007626EF /* Locales.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6434DD7620C80263007626EF /* Locales.swift */; }; 6434DDA420C809C2007626EF /* DateInRegion.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6434DD6020C7FAF6007626EF /* DateInRegion.swift */; }; 6434DDA520C809C2007626EF /* Region.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6434DD6620C7FC6A007626EF /* Region.swift */; }; 6439231E20D90CB10098EC03 /* TestDateInRegion+Components.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6439231D20D90CB10098EC03 /* TestDateInRegion+Components.swift */; }; 6439231F20D90CB10098EC03 /* TestDateInRegion+Components.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6439231D20D90CB10098EC03 /* TestDateInRegion+Components.swift */; }; 6439232020D90CB10098EC03 /* TestDateInRegion+Components.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6439231D20D90CB10098EC03 /* TestDateInRegion+Components.swift */; }; 6439232220D912670098EC03 /* TestDateInRegion+Math.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6439232120D912670098EC03 /* TestDateInRegion+Math.swift */; }; 6439232320D912670098EC03 /* TestDateInRegion+Math.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6439232120D912670098EC03 /* TestDateInRegion+Math.swift */; }; 6439232420D912670098EC03 /* TestDateInRegion+Math.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6439232120D912670098EC03 /* TestDateInRegion+Math.swift */; }; 6439232620D91D170098EC03 /* TestFormatters.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6439232520D91D170098EC03 /* TestFormatters.swift */; }; 6439232720D91D170098EC03 /* TestFormatters.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6439232520D91D170098EC03 /* TestFormatters.swift */; }; 6439232820D91D170098EC03 /* TestFormatters.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6439232520D91D170098EC03 /* TestFormatters.swift */; }; 6470DD1420D27AF500BC2E74 /* String+Parser.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6470DD1320D27AF500BC2E74 /* String+Parser.swift */; }; 6470DD1520D27AF500BC2E74 /* String+Parser.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6470DD1320D27AF500BC2E74 /* String+Parser.swift */; }; 6470DD1620D27AF500BC2E74 /* String+Parser.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6470DD1320D27AF500BC2E74 /* String+Parser.swift */; }; 6470DD1720D27AF500BC2E74 /* String+Parser.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6470DD1320D27AF500BC2E74 /* String+Parser.swift */; }; 6470DD1820D27AF500BC2E74 /* String+Parser.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6470DD1320D27AF500BC2E74 /* String+Parser.swift */; }; 6470DD1A20D296EA00BC2E74 /* TimeInterval+Formatter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6470DD1920D296EA00BC2E74 /* TimeInterval+Formatter.swift */; }; 6470DD1B20D296EA00BC2E74 /* TimeInterval+Formatter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6470DD1920D296EA00BC2E74 /* TimeInterval+Formatter.swift */; }; 6470DD1C20D296EA00BC2E74 /* TimeInterval+Formatter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6470DD1920D296EA00BC2E74 /* TimeInterval+Formatter.swift */; }; 6470DD1D20D296EA00BC2E74 /* TimeInterval+Formatter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6470DD1920D296EA00BC2E74 /* TimeInterval+Formatter.swift */; }; 6470DD1E20D296EA00BC2E74 /* TimeInterval+Formatter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6470DD1920D296EA00BC2E74 /* TimeInterval+Formatter.swift */; }; 6470DD2120D2A55300BC2E74 /* TimePeriodProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6470DD2020D2A55300BC2E74 /* TimePeriodProtocol.swift */; }; 6470DD2220D2A55300BC2E74 /* TimePeriodProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6470DD2020D2A55300BC2E74 /* TimePeriodProtocol.swift */; }; 6470DD2320D2A55300BC2E74 /* TimePeriodProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6470DD2020D2A55300BC2E74 /* TimePeriodProtocol.swift */; }; 6470DD2420D2A55300BC2E74 /* TimePeriodProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6470DD2020D2A55300BC2E74 /* TimePeriodProtocol.swift */; }; 6470DD2520D2A55300BC2E74 /* TimePeriodProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6470DD2020D2A55300BC2E74 /* TimePeriodProtocol.swift */; }; 6470DD2E20D2B64200BC2E74 /* TimePeriod+Support.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6470DD2D20D2B64200BC2E74 /* TimePeriod+Support.swift */; }; 6470DD2F20D2B64200BC2E74 /* TimePeriod+Support.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6470DD2D20D2B64200BC2E74 /* TimePeriod+Support.swift */; }; 6470DD3020D2B64200BC2E74 /* TimePeriod+Support.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6470DD2D20D2B64200BC2E74 /* TimePeriod+Support.swift */; }; 6470DD3120D2B64200BC2E74 /* TimePeriod+Support.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6470DD2D20D2B64200BC2E74 /* TimePeriod+Support.swift */; }; 6470DD3220D2B64200BC2E74 /* TimePeriod+Support.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6470DD2D20D2B64200BC2E74 /* TimePeriod+Support.swift */; }; 647AD65621F4826100CF787E /* TimeStructures.swift in Sources */ = {isa = PBXBuildFile; fileRef = 647AD65521F4826100CF787E /* TimeStructures.swift */; }; 647AD65721F4826100CF787E /* TimeStructures.swift in Sources */ = {isa = PBXBuildFile; fileRef = 647AD65521F4826100CF787E /* TimeStructures.swift */; }; 647AD65821F4826100CF787E /* TimeStructures.swift in Sources */ = {isa = PBXBuildFile; fileRef = 647AD65521F4826100CF787E /* TimeStructures.swift */; }; 647AD65921F4826100CF787E /* TimeStructures.swift in Sources */ = {isa = PBXBuildFile; fileRef = 647AD65521F4826100CF787E /* TimeStructures.swift */; }; 647AD65A21F4826100CF787E /* TimeStructures.swift in Sources */ = {isa = PBXBuildFile; fileRef = 647AD65521F4826100CF787E /* TimeStructures.swift */; }; 647AD65C21F4851F00CF787E /* TestDataStructures.swift in Sources */ = {isa = PBXBuildFile; fileRef = 647AD65B21F4851F00CF787E /* TestDataStructures.swift */; }; 647AD65D21F4851F00CF787E /* TestDataStructures.swift in Sources */ = {isa = PBXBuildFile; fileRef = 647AD65B21F4851F00CF787E /* TestDataStructures.swift */; }; 647AD65E21F4851F00CF787E /* TestDataStructures.swift in Sources */ = {isa = PBXBuildFile; fileRef = 647AD65B21F4851F00CF787E /* TestDataStructures.swift */; }; 64990AF82286FC31006C427D /* langs in Resources */ = {isa = PBXBuildFile; fileRef = 64990AF72286FC31006C427D /* langs */; }; 64990AF92286FC31006C427D /* langs in Resources */ = {isa = PBXBuildFile; fileRef = 64990AF72286FC31006C427D /* langs */; }; 64990AFA2286FC31006C427D /* langs in Resources */ = {isa = PBXBuildFile; fileRef = 64990AF72286FC31006C427D /* langs */; }; 64990AFB2286FC31006C427D /* langs in Resources */ = {isa = PBXBuildFile; fileRef = 64990AF72286FC31006C427D /* langs */; }; 64990AFC2286FC31006C427D /* langs in Resources */ = {isa = PBXBuildFile; fileRef = 64990AF72286FC31006C427D /* langs */; }; 649D0E032287412C0056D42E /* RelativeFormatterLanguage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 649D0E022287412C0056D42E /* RelativeFormatterLanguage.swift */; }; 649D0E042287412C0056D42E /* RelativeFormatterLanguage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 649D0E022287412C0056D42E /* RelativeFormatterLanguage.swift */; }; 649D0E052287412C0056D42E /* RelativeFormatterLanguage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 649D0E022287412C0056D42E /* RelativeFormatterLanguage.swift */; }; 649D0E062287412C0056D42E /* RelativeFormatterLanguage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 649D0E022287412C0056D42E /* RelativeFormatterLanguage.swift */; }; 649D0E072287412C0056D42E /* RelativeFormatterLanguage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 649D0E022287412C0056D42E /* RelativeFormatterLanguage.swift */; }; 649D473B20C81A2A00513A67 /* DateRepresentable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 649D473A20C81A2A00513A67 /* DateRepresentable.swift */; }; 649D473C20C81A2A00513A67 /* DateRepresentable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 649D473A20C81A2A00513A67 /* DateRepresentable.swift */; }; 649D473D20C81A2A00513A67 /* DateRepresentable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 649D473A20C81A2A00513A67 /* DateRepresentable.swift */; }; 649D473E20C81A2A00513A67 /* DateRepresentable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 649D473A20C81A2A00513A67 /* DateRepresentable.swift */; }; 649D473F20C81A2A00513A67 /* DateRepresentable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 649D473A20C81A2A00513A67 /* DateRepresentable.swift */; }; 649D474120C81A4200513A67 /* Date.swift in Sources */ = {isa = PBXBuildFile; fileRef = 649D474020C81A4200513A67 /* Date.swift */; }; 649D474220C81A4200513A67 /* Date.swift in Sources */ = {isa = PBXBuildFile; fileRef = 649D474020C81A4200513A67 /* Date.swift */; }; 649D474320C81A4200513A67 /* Date.swift in Sources */ = {isa = PBXBuildFile; fileRef = 649D474020C81A4200513A67 /* Date.swift */; }; 649D474420C81A4200513A67 /* Date.swift in Sources */ = {isa = PBXBuildFile; fileRef = 649D474020C81A4200513A67 /* Date.swift */; }; 649D474520C81A4200513A67 /* Date.swift in Sources */ = {isa = PBXBuildFile; fileRef = 649D474020C81A4200513A67 /* Date.swift */; }; 649D474820C8241C00513A67 /* Commons.swift in Sources */ = {isa = PBXBuildFile; fileRef = 649D474720C8241C00513A67 /* Commons.swift */; }; 649D474920C8241C00513A67 /* Commons.swift in Sources */ = {isa = PBXBuildFile; fileRef = 649D474720C8241C00513A67 /* Commons.swift */; }; 649D474A20C8241C00513A67 /* Commons.swift in Sources */ = {isa = PBXBuildFile; fileRef = 649D474720C8241C00513A67 /* Commons.swift */; }; 649D474B20C8241C00513A67 /* Commons.swift in Sources */ = {isa = PBXBuildFile; fileRef = 649D474720C8241C00513A67 /* Commons.swift */; }; 649D474C20C8241C00513A67 /* Commons.swift in Sources */ = {isa = PBXBuildFile; fileRef = 649D474720C8241C00513A67 /* Commons.swift */; }; 649D474E20C827C400513A67 /* AssociatedValues.swift in Sources */ = {isa = PBXBuildFile; fileRef = 649D474D20C827C400513A67 /* AssociatedValues.swift */; }; 649D474F20C827C400513A67 /* AssociatedValues.swift in Sources */ = {isa = PBXBuildFile; fileRef = 649D474D20C827C400513A67 /* AssociatedValues.swift */; }; 649D475020C827C400513A67 /* AssociatedValues.swift in Sources */ = {isa = PBXBuildFile; fileRef = 649D474D20C827C400513A67 /* AssociatedValues.swift */; }; 649D475120C827C400513A67 /* AssociatedValues.swift in Sources */ = {isa = PBXBuildFile; fileRef = 649D474D20C827C400513A67 /* AssociatedValues.swift */; }; 649D475220C827C400513A67 /* AssociatedValues.swift in Sources */ = {isa = PBXBuildFile; fileRef = 649D474D20C827C400513A67 /* AssociatedValues.swift */; }; 649D475A20C84FAC00513A67 /* ISOFormatter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 649D475920C84FAC00513A67 /* ISOFormatter.swift */; }; 649D475B20C84FAC00513A67 /* ISOFormatter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 649D475920C84FAC00513A67 /* ISOFormatter.swift */; }; 649D475C20C84FAC00513A67 /* ISOFormatter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 649D475920C84FAC00513A67 /* ISOFormatter.swift */; }; 649D475D20C84FAC00513A67 /* ISOFormatter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 649D475920C84FAC00513A67 /* ISOFormatter.swift */; }; 649D475E20C84FAC00513A67 /* ISOFormatter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 649D475920C84FAC00513A67 /* ISOFormatter.swift */; }; 649D476120C8529500513A67 /* Formatter+Protocols.swift in Sources */ = {isa = PBXBuildFile; fileRef = 649D476020C8529500513A67 /* Formatter+Protocols.swift */; }; 649D476220C8529500513A67 /* Formatter+Protocols.swift in Sources */ = {isa = PBXBuildFile; fileRef = 649D476020C8529500513A67 /* Formatter+Protocols.swift */; }; 649D476320C8529500513A67 /* Formatter+Protocols.swift in Sources */ = {isa = PBXBuildFile; fileRef = 649D476020C8529500513A67 /* Formatter+Protocols.swift */; }; 649D476420C8529500513A67 /* Formatter+Protocols.swift in Sources */ = {isa = PBXBuildFile; fileRef = 649D476020C8529500513A67 /* Formatter+Protocols.swift */; }; 649D476520C8529500513A67 /* Formatter+Protocols.swift in Sources */ = {isa = PBXBuildFile; fileRef = 649D476020C8529500513A67 /* Formatter+Protocols.swift */; }; 649D476D20C85C7100513A67 /* ISOParser.swift in Sources */ = {isa = PBXBuildFile; fileRef = 649D476C20C85C7100513A67 /* ISOParser.swift */; }; 649D476E20C85C7100513A67 /* ISOParser.swift in Sources */ = {isa = PBXBuildFile; fileRef = 649D476C20C85C7100513A67 /* ISOParser.swift */; }; 649D476F20C85C7100513A67 /* ISOParser.swift in Sources */ = {isa = PBXBuildFile; fileRef = 649D476C20C85C7100513A67 /* ISOParser.swift */; }; 649D477020C85C7100513A67 /* ISOParser.swift in Sources */ = {isa = PBXBuildFile; fileRef = 649D476C20C85C7100513A67 /* ISOParser.swift */; }; 649D477120C85C7100513A67 /* ISOParser.swift in Sources */ = {isa = PBXBuildFile; fileRef = 649D476C20C85C7100513A67 /* ISOParser.swift */; }; 649D477320C872DA00513A67 /* DotNetParserFormatter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 649D477220C872DA00513A67 /* DotNetParserFormatter.swift */; }; 649D477420C872DA00513A67 /* DotNetParserFormatter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 649D477220C872DA00513A67 /* DotNetParserFormatter.swift */; }; 649D477520C872DA00513A67 /* DotNetParserFormatter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 649D477220C872DA00513A67 /* DotNetParserFormatter.swift */; }; 649D477620C872DA00513A67 /* DotNetParserFormatter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 649D477220C872DA00513A67 /* DotNetParserFormatter.swift */; }; 649D477720C872DA00513A67 /* DotNetParserFormatter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 649D477220C872DA00513A67 /* DotNetParserFormatter.swift */; }; 649D477920C877C300513A67 /* DateInRegion+Create.swift in Sources */ = {isa = PBXBuildFile; fileRef = 649D477820C877C300513A67 /* DateInRegion+Create.swift */; }; 649D477A20C877C300513A67 /* DateInRegion+Create.swift in Sources */ = {isa = PBXBuildFile; fileRef = 649D477820C877C300513A67 /* DateInRegion+Create.swift */; }; 649D477B20C877C300513A67 /* DateInRegion+Create.swift in Sources */ = {isa = PBXBuildFile; fileRef = 649D477820C877C300513A67 /* DateInRegion+Create.swift */; }; 649D477C20C877C300513A67 /* DateInRegion+Create.swift in Sources */ = {isa = PBXBuildFile; fileRef = 649D477820C877C300513A67 /* DateInRegion+Create.swift */; }; 649D477D20C877C300513A67 /* DateInRegion+Create.swift in Sources */ = {isa = PBXBuildFile; fileRef = 649D477820C877C300513A67 /* DateInRegion+Create.swift */; }; 649D477F20C880DC00513A67 /* Date+Create.swift in Sources */ = {isa = PBXBuildFile; fileRef = 649D477E20C880DC00513A67 /* Date+Create.swift */; }; 649D478020C880DC00513A67 /* Date+Create.swift in Sources */ = {isa = PBXBuildFile; fileRef = 649D477E20C880DC00513A67 /* Date+Create.swift */; }; 649D478120C880DC00513A67 /* Date+Create.swift in Sources */ = {isa = PBXBuildFile; fileRef = 649D477E20C880DC00513A67 /* Date+Create.swift */; }; 649D478220C880DC00513A67 /* Date+Create.swift in Sources */ = {isa = PBXBuildFile; fileRef = 649D477E20C880DC00513A67 /* Date+Create.swift */; }; 649D478320C880DC00513A67 /* Date+Create.swift in Sources */ = {isa = PBXBuildFile; fileRef = 649D477E20C880DC00513A67 /* Date+Create.swift */; }; 649D478520C8861200513A67 /* DateInRegion+Components.swift in Sources */ = {isa = PBXBuildFile; fileRef = 649D478420C8861200513A67 /* DateInRegion+Components.swift */; }; 649D478620C8861200513A67 /* DateInRegion+Components.swift in Sources */ = {isa = PBXBuildFile; fileRef = 649D478420C8861200513A67 /* DateInRegion+Components.swift */; }; 649D478720C8861200513A67 /* DateInRegion+Components.swift in Sources */ = {isa = PBXBuildFile; fileRef = 649D478420C8861200513A67 /* DateInRegion+Components.swift */; }; 649D478820C8861200513A67 /* DateInRegion+Components.swift in Sources */ = {isa = PBXBuildFile; fileRef = 649D478420C8861200513A67 /* DateInRegion+Components.swift */; }; 649D478920C8861200513A67 /* DateInRegion+Components.swift in Sources */ = {isa = PBXBuildFile; fileRef = 649D478420C8861200513A67 /* DateInRegion+Components.swift */; }; 649D479220C913E200513A67 /* Date+Components.swift in Sources */ = {isa = PBXBuildFile; fileRef = 649D479120C913E200513A67 /* Date+Components.swift */; }; 649D479320C913E200513A67 /* Date+Components.swift in Sources */ = {isa = PBXBuildFile; fileRef = 649D479120C913E200513A67 /* Date+Components.swift */; }; 649D479420C913E200513A67 /* Date+Components.swift in Sources */ = {isa = PBXBuildFile; fileRef = 649D479120C913E200513A67 /* Date+Components.swift */; }; 649D479520C913E200513A67 /* Date+Components.swift in Sources */ = {isa = PBXBuildFile; fileRef = 649D479120C913E200513A67 /* Date+Components.swift */; }; 649D479620C913E200513A67 /* Date+Components.swift in Sources */ = {isa = PBXBuildFile; fileRef = 649D479120C913E200513A67 /* Date+Components.swift */; }; 649D47A420C91BEA00513A67 /* DateInRegion+Compare.swift in Sources */ = {isa = PBXBuildFile; fileRef = 649D47A320C91BEA00513A67 /* DateInRegion+Compare.swift */; }; 649D47A520C91BEA00513A67 /* DateInRegion+Compare.swift in Sources */ = {isa = PBXBuildFile; fileRef = 649D47A320C91BEA00513A67 /* DateInRegion+Compare.swift */; }; 649D47A620C91BEA00513A67 /* DateInRegion+Compare.swift in Sources */ = {isa = PBXBuildFile; fileRef = 649D47A320C91BEA00513A67 /* DateInRegion+Compare.swift */; }; 649D47A720C91BEA00513A67 /* DateInRegion+Compare.swift in Sources */ = {isa = PBXBuildFile; fileRef = 649D47A320C91BEA00513A67 /* DateInRegion+Compare.swift */; }; 649D47A820C91BEA00513A67 /* DateInRegion+Compare.swift in Sources */ = {isa = PBXBuildFile; fileRef = 649D47A320C91BEA00513A67 /* DateInRegion+Compare.swift */; }; 649D47AA20C91C0B00513A67 /* Date+Compare.swift in Sources */ = {isa = PBXBuildFile; fileRef = 649D47A920C91C0B00513A67 /* Date+Compare.swift */; }; 649D47AB20C91C0B00513A67 /* Date+Compare.swift in Sources */ = {isa = PBXBuildFile; fileRef = 649D47A920C91C0B00513A67 /* Date+Compare.swift */; }; 649D47AC20C91C0B00513A67 /* Date+Compare.swift in Sources */ = {isa = PBXBuildFile; fileRef = 649D47A920C91C0B00513A67 /* Date+Compare.swift */; }; 649D47AD20C91C0B00513A67 /* Date+Compare.swift in Sources */ = {isa = PBXBuildFile; fileRef = 649D47A920C91C0B00513A67 /* Date+Compare.swift */; }; 649D47AE20C91C0B00513A67 /* Date+Compare.swift in Sources */ = {isa = PBXBuildFile; fileRef = 649D47A920C91C0B00513A67 /* Date+Compare.swift */; }; 649D47B020C9276400513A67 /* DateComponents+Extras.swift in Sources */ = {isa = PBXBuildFile; fileRef = 649D47AF20C9276400513A67 /* DateComponents+Extras.swift */; }; 649D47B120C9276400513A67 /* DateComponents+Extras.swift in Sources */ = {isa = PBXBuildFile; fileRef = 649D47AF20C9276400513A67 /* DateComponents+Extras.swift */; }; 649D47B220C9276400513A67 /* DateComponents+Extras.swift in Sources */ = {isa = PBXBuildFile; fileRef = 649D47AF20C9276400513A67 /* DateComponents+Extras.swift */; }; 649D47B320C9276400513A67 /* DateComponents+Extras.swift in Sources */ = {isa = PBXBuildFile; fileRef = 649D47AF20C9276400513A67 /* DateComponents+Extras.swift */; }; 649D47B420C9276400513A67 /* DateComponents+Extras.swift in Sources */ = {isa = PBXBuildFile; fileRef = 649D47AF20C9276400513A67 /* DateComponents+Extras.swift */; }; 649D47B620C9586500513A67 /* DateInRegion+Math.swift in Sources */ = {isa = PBXBuildFile; fileRef = 649D47B520C9586500513A67 /* DateInRegion+Math.swift */; }; 649D47B720C9586500513A67 /* DateInRegion+Math.swift in Sources */ = {isa = PBXBuildFile; fileRef = 649D47B520C9586500513A67 /* DateInRegion+Math.swift */; }; 649D47B820C9586500513A67 /* DateInRegion+Math.swift in Sources */ = {isa = PBXBuildFile; fileRef = 649D47B520C9586500513A67 /* DateInRegion+Math.swift */; }; 649D47B920C9586500513A67 /* DateInRegion+Math.swift in Sources */ = {isa = PBXBuildFile; fileRef = 649D47B520C9586500513A67 /* DateInRegion+Math.swift */; }; 649D47BA20C9586500513A67 /* DateInRegion+Math.swift in Sources */ = {isa = PBXBuildFile; fileRef = 649D47B520C9586500513A67 /* DateInRegion+Math.swift */; }; 649D47BC20C959F500513A67 /* Date+Math.swift in Sources */ = {isa = PBXBuildFile; fileRef = 649D47BB20C959F500513A67 /* Date+Math.swift */; }; 649D47BD20C959F500513A67 /* Date+Math.swift in Sources */ = {isa = PBXBuildFile; fileRef = 649D47BB20C959F500513A67 /* Date+Math.swift */; }; 649D47BE20C959F500513A67 /* Date+Math.swift in Sources */ = {isa = PBXBuildFile; fileRef = 649D47BB20C959F500513A67 /* Date+Math.swift */; }; 649D47BF20C959F500513A67 /* Date+Math.swift in Sources */ = {isa = PBXBuildFile; fileRef = 649D47BB20C959F500513A67 /* Date+Math.swift */; }; 649D47C020C959F500513A67 /* Date+Math.swift in Sources */ = {isa = PBXBuildFile; fileRef = 649D47BB20C959F500513A67 /* Date+Math.swift */; }; 649D47C220C964E000513A67 /* Int+DateComponents.swift in Sources */ = {isa = PBXBuildFile; fileRef = 649D47C120C964E000513A67 /* Int+DateComponents.swift */; }; 649D47C320C964E000513A67 /* Int+DateComponents.swift in Sources */ = {isa = PBXBuildFile; fileRef = 649D47C120C964E000513A67 /* Int+DateComponents.swift */; }; 649D47C420C964E000513A67 /* Int+DateComponents.swift in Sources */ = {isa = PBXBuildFile; fileRef = 649D47C120C964E000513A67 /* Int+DateComponents.swift */; }; 649D47C520C964E000513A67 /* Int+DateComponents.swift in Sources */ = {isa = PBXBuildFile; fileRef = 649D47C120C964E000513A67 /* Int+DateComponents.swift */; }; 649D47C620C964E000513A67 /* Int+DateComponents.swift in Sources */ = {isa = PBXBuildFile; fileRef = 649D47C120C964E000513A67 /* Int+DateComponents.swift */; }; 649D47E120CAAB7400513A67 /* RelativeFormatter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 649D47E020CAAB7400513A67 /* RelativeFormatter.swift */; }; 649D47E220CAAB7400513A67 /* RelativeFormatter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 649D47E020CAAB7400513A67 /* RelativeFormatter.swift */; }; 649D47E320CAAB7400513A67 /* RelativeFormatter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 649D47E020CAAB7400513A67 /* RelativeFormatter.swift */; }; 649D47E420CAAB7400513A67 /* RelativeFormatter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 649D47E020CAAB7400513A67 /* RelativeFormatter.swift */; }; 649D47E520CAAB7400513A67 /* RelativeFormatter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 649D47E020CAAB7400513A67 /* RelativeFormatter.swift */; }; 649D47EE20CAB96D00513A67 /* RelativeFormatter+Style.swift in Sources */ = {isa = PBXBuildFile; fileRef = 649D47ED20CAB96D00513A67 /* RelativeFormatter+Style.swift */; }; 649D47EF20CAB96D00513A67 /* RelativeFormatter+Style.swift in Sources */ = {isa = PBXBuildFile; fileRef = 649D47ED20CAB96D00513A67 /* RelativeFormatter+Style.swift */; }; 649D47F020CAB96D00513A67 /* RelativeFormatter+Style.swift in Sources */ = {isa = PBXBuildFile; fileRef = 649D47ED20CAB96D00513A67 /* RelativeFormatter+Style.swift */; }; 649D47F120CAB96D00513A67 /* RelativeFormatter+Style.swift in Sources */ = {isa = PBXBuildFile; fileRef = 649D47ED20CAB96D00513A67 /* RelativeFormatter+Style.swift */; }; 649D47F220CAB96D00513A67 /* RelativeFormatter+Style.swift in Sources */ = {isa = PBXBuildFile; fileRef = 649D47ED20CAB96D00513A67 /* RelativeFormatter+Style.swift */; }; 64B5E25320D306220067EDC1 /* TimePeriod.swift in Sources */ = {isa = PBXBuildFile; fileRef = 64B5E25220D306220067EDC1 /* TimePeriod.swift */; }; 64B5E25420D306220067EDC1 /* TimePeriod.swift in Sources */ = {isa = PBXBuildFile; fileRef = 64B5E25220D306220067EDC1 /* TimePeriod.swift */; }; 64B5E25520D306220067EDC1 /* TimePeriod.swift in Sources */ = {isa = PBXBuildFile; fileRef = 64B5E25220D306220067EDC1 /* TimePeriod.swift */; }; 64B5E25620D306220067EDC1 /* TimePeriod.swift in Sources */ = {isa = PBXBuildFile; fileRef = 64B5E25220D306220067EDC1 /* TimePeriod.swift */; }; 64B5E25720D306220067EDC1 /* TimePeriod.swift in Sources */ = {isa = PBXBuildFile; fileRef = 64B5E25220D306220067EDC1 /* TimePeriod.swift */; }; 64B5E25920D3090A0067EDC1 /* TimePeriodGroup.swift in Sources */ = {isa = PBXBuildFile; fileRef = 64B5E25820D3090A0067EDC1 /* TimePeriodGroup.swift */; }; 64B5E25A20D3090A0067EDC1 /* TimePeriodGroup.swift in Sources */ = {isa = PBXBuildFile; fileRef = 64B5E25820D3090A0067EDC1 /* TimePeriodGroup.swift */; }; 64B5E25B20D3090A0067EDC1 /* TimePeriodGroup.swift in Sources */ = {isa = PBXBuildFile; fileRef = 64B5E25820D3090A0067EDC1 /* TimePeriodGroup.swift */; }; 64B5E25C20D3090A0067EDC1 /* TimePeriodGroup.swift in Sources */ = {isa = PBXBuildFile; fileRef = 64B5E25820D3090A0067EDC1 /* TimePeriodGroup.swift */; }; 64B5E25D20D3090A0067EDC1 /* TimePeriodGroup.swift in Sources */ = {isa = PBXBuildFile; fileRef = 64B5E25820D3090A0067EDC1 /* TimePeriodGroup.swift */; }; 64B5E26020D30AE40067EDC1 /* TimePeriodCollection.swift in Sources */ = {isa = PBXBuildFile; fileRef = 64B5E25F20D30AE40067EDC1 /* TimePeriodCollection.swift */; }; 64B5E26120D30AE40067EDC1 /* TimePeriodCollection.swift in Sources */ = {isa = PBXBuildFile; fileRef = 64B5E25F20D30AE40067EDC1 /* TimePeriodCollection.swift */; }; 64B5E26220D30AE40067EDC1 /* TimePeriodCollection.swift in Sources */ = {isa = PBXBuildFile; fileRef = 64B5E25F20D30AE40067EDC1 /* TimePeriodCollection.swift */; }; 64B5E26320D30AE40067EDC1 /* TimePeriodCollection.swift in Sources */ = {isa = PBXBuildFile; fileRef = 64B5E25F20D30AE40067EDC1 /* TimePeriodCollection.swift */; }; 64B5E26420D30AE40067EDC1 /* TimePeriodCollection.swift in Sources */ = {isa = PBXBuildFile; fileRef = 64B5E25F20D30AE40067EDC1 /* TimePeriodCollection.swift */; }; 64B5E26620D30E620067EDC1 /* TimePeriodChain.swift in Sources */ = {isa = PBXBuildFile; fileRef = 64B5E26520D30E620067EDC1 /* TimePeriodChain.swift */; }; 64B5E26720D30E620067EDC1 /* TimePeriodChain.swift in Sources */ = {isa = PBXBuildFile; fileRef = 64B5E26520D30E620067EDC1 /* TimePeriodChain.swift */; }; 64B5E26820D30E620067EDC1 /* TimePeriodChain.swift in Sources */ = {isa = PBXBuildFile; fileRef = 64B5E26520D30E620067EDC1 /* TimePeriodChain.swift */; }; 64B5E26920D30E620067EDC1 /* TimePeriodChain.swift in Sources */ = {isa = PBXBuildFile; fileRef = 64B5E26520D30E620067EDC1 /* TimePeriodChain.swift */; }; 64B5E26A20D30E620067EDC1 /* TimePeriodChain.swift in Sources */ = {isa = PBXBuildFile; fileRef = 64B5E26520D30E620067EDC1 /* TimePeriodChain.swift */; }; 64BAB12420E63A3A00FEED79 /* TestDateInRegion+Langs.swift in Sources */ = {isa = PBXBuildFile; fileRef = 64BAB12320E63A3A00FEED79 /* TestDateInRegion+Langs.swift */; }; 64BAB12520E63A3A00FEED79 /* TestDateInRegion+Langs.swift in Sources */ = {isa = PBXBuildFile; fileRef = 64BAB12320E63A3A00FEED79 /* TestDateInRegion+Langs.swift */; }; 64BAB12620E63A3A00FEED79 /* TestDateInRegion+Langs.swift in Sources */ = {isa = PBXBuildFile; fileRef = 64BAB12320E63A3A00FEED79 /* TestDateInRegion+Langs.swift */; }; 64BAB12820E6411100FEED79 /* TestSwiftDate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 64BAB12720E6411100FEED79 /* TestSwiftDate.swift */; }; 64BAB12920E6411100FEED79 /* TestSwiftDate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 64BAB12720E6411100FEED79 /* TestSwiftDate.swift */; }; 64BAB12A20E6411100FEED79 /* TestSwiftDate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 64BAB12720E6411100FEED79 /* TestSwiftDate.swift */; }; 64EF3E0320D5518D002793C6 /* TestRegion.swift in Sources */ = {isa = PBXBuildFile; fileRef = 64EF3E0220D5518D002793C6 /* TestRegion.swift */; }; 64EF3E0420D551E4002793C6 /* TestRegion.swift in Sources */ = {isa = PBXBuildFile; fileRef = 64EF3E0220D5518D002793C6 /* TestRegion.swift */; }; 64EF3E0520D551E5002793C6 /* TestRegion.swift in Sources */ = {isa = PBXBuildFile; fileRef = 64EF3E0220D5518D002793C6 /* TestRegion.swift */; }; 64EF3E0720D56038002793C6 /* TestDateInRegion.swift in Sources */ = {isa = PBXBuildFile; fileRef = 64EF3E0620D56038002793C6 /* TestDateInRegion.swift */; }; 64EF3E0820D5622D002793C6 /* TestDateInRegion.swift in Sources */ = {isa = PBXBuildFile; fileRef = 64EF3E0620D56038002793C6 /* TestDateInRegion.swift */; }; 64EF3E0920D5622D002793C6 /* TestDateInRegion.swift in Sources */ = {isa = PBXBuildFile; fileRef = 64EF3E0620D56038002793C6 /* TestDateInRegion.swift */; }; 64EF3E0B20D65329002793C6 /* TestDateInRegion+Create.swift in Sources */ = {isa = PBXBuildFile; fileRef = 64EF3E0A20D65329002793C6 /* TestDateInRegion+Create.swift */; }; 64EF3E0C20D65329002793C6 /* TestDateInRegion+Create.swift in Sources */ = {isa = PBXBuildFile; fileRef = 64EF3E0A20D65329002793C6 /* TestDateInRegion+Create.swift */; }; 64EF3E0D20D65329002793C6 /* TestDateInRegion+Create.swift in Sources */ = {isa = PBXBuildFile; fileRef = 64EF3E0A20D65329002793C6 /* TestDateInRegion+Create.swift */; }; 64EF3E0F20D65478002793C6 /* TestDateInRegion+Compare.swift in Sources */ = {isa = PBXBuildFile; fileRef = 64EF3E0E20D65478002793C6 /* TestDateInRegion+Compare.swift */; }; 64EF3E1020D65478002793C6 /* TestDateInRegion+Compare.swift in Sources */ = {isa = PBXBuildFile; fileRef = 64EF3E0E20D65478002793C6 /* TestDateInRegion+Compare.swift */; }; 64EF3E1120D65478002793C6 /* TestDateInRegion+Compare.swift in Sources */ = {isa = PBXBuildFile; fileRef = 64EF3E0E20D65478002793C6 /* TestDateInRegion+Compare.swift */; }; A89F3FAF22A00019002D1BD0 /* TestDate.swift in Sources */ = {isa = PBXBuildFile; fileRef = A89F3FAE22A00019002D1BD0 /* TestDate.swift */; }; DD7502881C68FEDE006590AF /* SwiftDate.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 52D6DA0F1BF000BD002C0205 /* SwiftDate.framework */; }; DD7502921C690C7A006590AF /* SwiftDate.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 52D6D9F01BEFFFBE002C0205 /* SwiftDate.framework */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ 52D6D9881BEFF229002C0205 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = 52D6D9731BEFF229002C0205 /* Project object */; proxyType = 1; remoteGlobalIDString = 52D6D97B1BEFF229002C0205; remoteInfo = SwiftDate; }; DD7502801C68FCFC006590AF /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = 52D6D9731BEFF229002C0205 /* Project object */; proxyType = 1; remoteGlobalIDString = 52D6DA0E1BF000BD002C0205; remoteInfo = "SwiftDate-macOS"; }; DD7502931C690C7A006590AF /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = 52D6D9731BEFF229002C0205 /* Project object */; proxyType = 1; remoteGlobalIDString = 52D6D9EF1BEFFFBE002C0205; remoteInfo = "SwiftDate-tvOS"; }; /* End PBXContainerItemProxy section */ /* Begin PBXFileReference section */ 52D6D97C1BEFF229002C0205 /* SwiftDate.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = SwiftDate.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 52D6D9861BEFF229002C0205 /* SwiftDate-iOS Tests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "SwiftDate-iOS Tests.xctest"; sourceTree = BUILT_PRODUCTS_DIR; }; 52D6D9E21BEFFF6E002C0205 /* SwiftDate.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = SwiftDate.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 52D6D9F01BEFFFBE002C0205 /* SwiftDate.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = SwiftDate.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 52D6DA0F1BF000BD002C0205 /* SwiftDate.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = SwiftDate.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 6434DD5820C7D1F6007626EF /* .swiftlint.yml */ = {isa = PBXFileReference; lastKnownFileType = text; path = .swiftlint.yml; sourceTree = ""; }; 6434DD5F20C7F729007626EF /* SwiftDate.playground */ = {isa = PBXFileReference; lastKnownFileType = file.playground; name = SwiftDate.playground; path = Playgrounds/SwiftDate.playground; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.swift; }; 6434DD6020C7FAF6007626EF /* DateInRegion.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DateInRegion.swift; sourceTree = ""; }; 6434DD6620C7FC6A007626EF /* Region.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Region.swift; sourceTree = ""; }; 6434DD6C20C7FEED007626EF /* Zones.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Zones.swift; sourceTree = ""; }; 6434DD7120C80126007626EF /* Calendars.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Calendars.swift; sourceTree = ""; }; 6434DD7620C80263007626EF /* Locales.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Locales.swift; sourceTree = ""; }; 6434DD7B20C803EF007626EF /* SwiftDate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = SwiftDate.swift; path = SwiftDate/SwiftDate.swift; sourceTree = ""; }; 6434DD8C20C809B2007626EF /* TestApplication.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = TestApplication.app; sourceTree = BUILT_PRODUCTS_DIR; }; 6434DD8E20C809B3007626EF /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 6434DD9020C809B3007626EF /* ViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = ""; }; 6434DD9320C809B3007626EF /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 6434DD9520C809B4007626EF /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 6434DD9820C809B4007626EF /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 6434DD9A20C809B4007626EF /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 6439231D20D90CB10098EC03 /* TestDateInRegion+Components.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "TestDateInRegion+Components.swift"; sourceTree = ""; }; 6439232120D912670098EC03 /* TestDateInRegion+Math.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "TestDateInRegion+Math.swift"; sourceTree = ""; }; 6439232520D91D170098EC03 /* TestFormatters.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TestFormatters.swift; sourceTree = ""; }; 6470DD1320D27AF500BC2E74 /* String+Parser.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "String+Parser.swift"; sourceTree = ""; }; 6470DD1920D296EA00BC2E74 /* TimeInterval+Formatter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "TimeInterval+Formatter.swift"; sourceTree = ""; }; 6470DD2020D2A55300BC2E74 /* TimePeriodProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimePeriodProtocol.swift; sourceTree = ""; }; 6470DD2D20D2B64200BC2E74 /* TimePeriod+Support.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "TimePeriod+Support.swift"; sourceTree = ""; }; 647AD65521F4826100CF787E /* TimeStructures.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimeStructures.swift; sourceTree = ""; }; 647AD65B21F4851F00CF787E /* TestDataStructures.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TestDataStructures.swift; sourceTree = ""; }; 647DA61A20D3FAB800E20E8C /* Documentation */ = {isa = PBXFileReference; lastKnownFileType = folder; path = Documentation; sourceTree = ""; }; 64990AF72286FC31006C427D /* langs */ = {isa = PBXFileReference; lastKnownFileType = folder; path = langs; sourceTree = ""; }; 649D0E022287412C0056D42E /* RelativeFormatterLanguage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RelativeFormatterLanguage.swift; sourceTree = ""; }; 649D473A20C81A2A00513A67 /* DateRepresentable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = DateRepresentable.swift; path = SwiftDate/DateRepresentable.swift; sourceTree = ""; }; 649D474020C81A4200513A67 /* Date.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Date.swift; sourceTree = ""; }; 649D474720C8241C00513A67 /* Commons.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Commons.swift; sourceTree = ""; }; 649D474D20C827C400513A67 /* AssociatedValues.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AssociatedValues.swift; sourceTree = ""; }; 649D475920C84FAC00513A67 /* ISOFormatter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ISOFormatter.swift; sourceTree = ""; }; 649D476020C8529500513A67 /* Formatter+Protocols.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Formatter+Protocols.swift"; sourceTree = ""; }; 649D476C20C85C7100513A67 /* ISOParser.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ISOParser.swift; sourceTree = ""; }; 649D477220C872DA00513A67 /* DotNetParserFormatter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DotNetParserFormatter.swift; sourceTree = ""; }; 649D477820C877C300513A67 /* DateInRegion+Create.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "DateInRegion+Create.swift"; sourceTree = ""; }; 649D477E20C880DC00513A67 /* Date+Create.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Date+Create.swift"; sourceTree = ""; }; 649D478420C8861200513A67 /* DateInRegion+Components.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "DateInRegion+Components.swift"; sourceTree = ""; }; 649D479120C913E200513A67 /* Date+Components.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Date+Components.swift"; sourceTree = ""; }; 649D47A320C91BEA00513A67 /* DateInRegion+Compare.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "DateInRegion+Compare.swift"; sourceTree = ""; }; 649D47A920C91C0B00513A67 /* Date+Compare.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Date+Compare.swift"; sourceTree = ""; }; 649D47AF20C9276400513A67 /* DateComponents+Extras.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "DateComponents+Extras.swift"; sourceTree = ""; }; 649D47B520C9586500513A67 /* DateInRegion+Math.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "DateInRegion+Math.swift"; sourceTree = ""; }; 649D47BB20C959F500513A67 /* Date+Math.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Date+Math.swift"; sourceTree = ""; }; 649D47C120C964E000513A67 /* Int+DateComponents.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Int+DateComponents.swift"; sourceTree = ""; }; 649D47E020CAAB7400513A67 /* RelativeFormatter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RelativeFormatter.swift; sourceTree = ""; }; 649D47ED20CAB96D00513A67 /* RelativeFormatter+Style.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "RelativeFormatter+Style.swift"; sourceTree = ""; }; 64B5E25220D306220067EDC1 /* TimePeriod.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimePeriod.swift; sourceTree = ""; }; 64B5E25820D3090A0067EDC1 /* TimePeriodGroup.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimePeriodGroup.swift; sourceTree = ""; }; 64B5E25F20D30AE40067EDC1 /* TimePeriodCollection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimePeriodCollection.swift; sourceTree = ""; }; 64B5E26520D30E620067EDC1 /* TimePeriodChain.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimePeriodChain.swift; sourceTree = ""; }; 64BAB12320E63A3A00FEED79 /* TestDateInRegion+Langs.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "TestDateInRegion+Langs.swift"; sourceTree = ""; }; 64BAB12720E6411100FEED79 /* TestSwiftDate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TestSwiftDate.swift; sourceTree = ""; }; 64EF3E0220D5518D002793C6 /* TestRegion.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TestRegion.swift; sourceTree = ""; }; 64EF3E0620D56038002793C6 /* TestDateInRegion.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TestDateInRegion.swift; sourceTree = ""; }; 64EF3E0A20D65329002793C6 /* TestDateInRegion+Create.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "TestDateInRegion+Create.swift"; sourceTree = ""; }; 64EF3E0E20D65478002793C6 /* TestDateInRegion+Compare.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "TestDateInRegion+Compare.swift"; sourceTree = ""; }; A89F3FAE22A00019002D1BD0 /* TestDate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TestDate.swift; sourceTree = ""; }; AD2FAA261CD0B6D800659CF4 /* SwiftDate.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = SwiftDate.plist; sourceTree = ""; }; AD2FAA281CD0B6E100659CF4 /* SwiftDateTests.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = SwiftDateTests.plist; sourceTree = ""; }; DD75027A1C68FCFC006590AF /* SwiftDate-macOS Tests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "SwiftDate-macOS Tests.xctest"; sourceTree = BUILT_PRODUCTS_DIR; }; DD75028D1C690C7A006590AF /* SwiftDate-tvOS Tests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "SwiftDate-tvOS Tests.xctest"; sourceTree = BUILT_PRODUCTS_DIR; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ 52D6D9781BEFF229002C0205 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( ); runOnlyForDeploymentPostprocessing = 0; }; 52D6D9831BEFF229002C0205 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( 52D6D9871BEFF229002C0205 /* SwiftDate.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; 52D6D9DE1BEFFF6E002C0205 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( ); runOnlyForDeploymentPostprocessing = 0; }; 52D6D9EC1BEFFFBE002C0205 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( ); runOnlyForDeploymentPostprocessing = 0; }; 52D6DA0B1BF000BD002C0205 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( ); runOnlyForDeploymentPostprocessing = 0; }; 6434DD8920C809B2007626EF /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( ); runOnlyForDeploymentPostprocessing = 0; }; DD7502771C68FCFC006590AF /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( DD7502881C68FEDE006590AF /* SwiftDate.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; DD75028A1C690C7A006590AF /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( DD7502921C690C7A006590AF /* SwiftDate.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ 52D6D9721BEFF229002C0205 = { isa = PBXGroup; children = ( 6434DD5820C7D1F6007626EF /* .swiftlint.yml */, 6434DD5F20C7F729007626EF /* SwiftDate.playground */, 647DA61A20D3FAB800E20E8C /* Documentation */, 8933C7811EB5B7E0000D00A4 /* Sources */, 8933C7831EB5B7EB000D00A4 /* Tests */, 52D6D99C1BEFF38C002C0205 /* Configs */, 6434DD8D20C809B3007626EF /* TestApplication */, 52D6D97D1BEFF229002C0205 /* Products */, ); sourceTree = ""; }; 52D6D97D1BEFF229002C0205 /* Products */ = { isa = PBXGroup; children = ( 52D6D97C1BEFF229002C0205 /* SwiftDate.framework */, 52D6D9861BEFF229002C0205 /* SwiftDate-iOS Tests.xctest */, 52D6D9E21BEFFF6E002C0205 /* SwiftDate.framework */, 52D6D9F01BEFFFBE002C0205 /* SwiftDate.framework */, 52D6DA0F1BF000BD002C0205 /* SwiftDate.framework */, DD75027A1C68FCFC006590AF /* SwiftDate-macOS Tests.xctest */, DD75028D1C690C7A006590AF /* SwiftDate-tvOS Tests.xctest */, 6434DD8C20C809B2007626EF /* TestApplication.app */, ); name = Products; sourceTree = ""; }; 52D6D99C1BEFF38C002C0205 /* Configs */ = { isa = PBXGroup; children = ( DD7502721C68FC1B006590AF /* Frameworks */, DD7502731C68FC20006590AF /* Tests */, ); path = Configs; sourceTree = ""; }; 6434DD6520C7FAFB007626EF /* DateInRegion */ = { isa = PBXGroup; children = ( 6434DD6020C7FAF6007626EF /* DateInRegion.swift */, 649D477820C877C300513A67 /* DateInRegion+Create.swift */, 649D47A320C91BEA00513A67 /* DateInRegion+Compare.swift */, 649D478420C8861200513A67 /* DateInRegion+Components.swift */, 649D47B520C9586500513A67 /* DateInRegion+Math.swift */, 6434DD6620C7FC6A007626EF /* Region.swift */, ); name = DateInRegion; path = SwiftDate/DateInRegion; sourceTree = ""; }; 6434DD6B20C7FEDB007626EF /* Supports */ = { isa = PBXGroup; children = ( 6434DD6C20C7FEED007626EF /* Zones.swift */, 6434DD7120C80126007626EF /* Calendars.swift */, 6434DD7620C80263007626EF /* Locales.swift */, 649D474720C8241C00513A67 /* Commons.swift */, 647AD65521F4826100CF787E /* TimeStructures.swift */, 649D474D20C827C400513A67 /* AssociatedValues.swift */, ); name = Supports; path = SwiftDate/Supports; sourceTree = ""; }; 6434DD8D20C809B3007626EF /* TestApplication */ = { isa = PBXGroup; children = ( 6434DD8E20C809B3007626EF /* AppDelegate.swift */, 6434DD9020C809B3007626EF /* ViewController.swift */, 6434DD9220C809B3007626EF /* Main.storyboard */, 6434DD9520C809B4007626EF /* Assets.xcassets */, 6434DD9720C809B4007626EF /* LaunchScreen.storyboard */, 6434DD9A20C809B4007626EF /* Info.plist */, ); path = TestApplication; sourceTree = ""; }; 6470DD1F20D2A32600BC2E74 /* TimePeriod */ = { isa = PBXGroup; children = ( 6470DD2D20D2B64200BC2E74 /* TimePeriod+Support.swift */, 6470DD2020D2A55300BC2E74 /* TimePeriodProtocol.swift */, 64B5E25220D306220067EDC1 /* TimePeriod.swift */, 64B5E25E20D309210067EDC1 /* Groups */, ); name = TimePeriod; path = SwiftDate/TimePeriod; sourceTree = ""; }; 6470DD2620D2A5EF00BC2E74 /* Foundation+Extras */ = { isa = PBXGroup; children = ( 6470DD1320D27AF500BC2E74 /* String+Parser.swift */, 6470DD1920D296EA00BC2E74 /* TimeInterval+Formatter.swift */, 649D47AF20C9276400513A67 /* DateComponents+Extras.swift */, 649D47C120C964E000513A67 /* Int+DateComponents.swift */, ); name = "Foundation+Extras"; path = "SwiftDate/Foundation+Extras"; sourceTree = ""; }; 649D474620C81A4800513A67 /* Date */ = { isa = PBXGroup; children = ( 649D474020C81A4200513A67 /* Date.swift */, 649D477E20C880DC00513A67 /* Date+Create.swift */, 649D47A920C91C0B00513A67 /* Date+Compare.swift */, 649D479120C913E200513A67 /* Date+Components.swift */, 649D47BB20C959F500513A67 /* Date+Math.swift */, ); name = Date; path = SwiftDate/Date; sourceTree = ""; }; 649D475F20C84FB000513A67 /* Formatters */ = { isa = PBXGroup; children = ( 649D476020C8529500513A67 /* Formatter+Protocols.swift */, 649D475920C84FAC00513A67 /* ISOFormatter.swift */, 649D476C20C85C7100513A67 /* ISOParser.swift */, 649D477220C872DA00513A67 /* DotNetParserFormatter.swift */, 649D47E620CAB46400513A67 /* RelativeFormatter */, ); name = Formatters; path = SwiftDate/Formatters; sourceTree = ""; }; 649D47E620CAB46400513A67 /* RelativeFormatter */ = { isa = PBXGroup; children = ( 649D47E020CAAB7400513A67 /* RelativeFormatter.swift */, 649D47ED20CAB96D00513A67 /* RelativeFormatter+Style.swift */, 649D0E022287412C0056D42E /* RelativeFormatterLanguage.swift */, 64990AF72286FC31006C427D /* langs */, ); path = RelativeFormatter; sourceTree = ""; }; 64B5E25E20D309210067EDC1 /* Groups */ = { isa = PBXGroup; children = ( 64B5E25820D3090A0067EDC1 /* TimePeriodGroup.swift */, 64B5E25F20D30AE40067EDC1 /* TimePeriodCollection.swift */, 64B5E26520D30E620067EDC1 /* TimePeriodChain.swift */, ); path = Groups; sourceTree = ""; }; 8933C7811EB5B7E0000D00A4 /* Sources */ = { isa = PBXGroup; children = ( 6434DD7B20C803EF007626EF /* SwiftDate.swift */, 649D473A20C81A2A00513A67 /* DateRepresentable.swift */, 6470DD2620D2A5EF00BC2E74 /* Foundation+Extras */, 6434DD6B20C7FEDB007626EF /* Supports */, 6470DD1F20D2A32600BC2E74 /* TimePeriod */, 6434DD6520C7FAFB007626EF /* DateInRegion */, 649D474620C81A4800513A67 /* Date */, 649D475F20C84FB000513A67 /* Formatters */, ); path = Sources; sourceTree = ""; }; 8933C7831EB5B7EB000D00A4 /* Tests */ = { isa = PBXGroup; children = ( 64EF3E0220D5518D002793C6 /* TestRegion.swift */, 64EF3E0620D56038002793C6 /* TestDateInRegion.swift */, 64EF3E0A20D65329002793C6 /* TestDateInRegion+Create.swift */, 64EF3E0E20D65478002793C6 /* TestDateInRegion+Compare.swift */, 6439231D20D90CB10098EC03 /* TestDateInRegion+Components.swift */, 6439232120D912670098EC03 /* TestDateInRegion+Math.swift */, 64BAB12320E63A3A00FEED79 /* TestDateInRegion+Langs.swift */, 6439232520D91D170098EC03 /* TestFormatters.swift */, 64BAB12720E6411100FEED79 /* TestSwiftDate.swift */, 647AD65B21F4851F00CF787E /* TestDataStructures.swift */, A89F3FAE22A00019002D1BD0 /* TestDate.swift */, ); name = Tests; path = Tests/SwiftDateTests; sourceTree = ""; }; DD7502721C68FC1B006590AF /* Frameworks */ = { isa = PBXGroup; children = ( AD2FAA261CD0B6D800659CF4 /* SwiftDate.plist */, ); name = Frameworks; sourceTree = ""; }; DD7502731C68FC20006590AF /* Tests */ = { isa = PBXGroup; children = ( AD2FAA281CD0B6E100659CF4 /* SwiftDateTests.plist */, ); name = Tests; sourceTree = ""; }; /* End PBXGroup section */ /* Begin PBXHeadersBuildPhase section */ 52D6D9791BEFF229002C0205 /* Headers */ = { isa = PBXHeadersBuildPhase; buildActionMask = 2147483647; files = ( ); runOnlyForDeploymentPostprocessing = 0; }; 52D6D9DF1BEFFF6E002C0205 /* Headers */ = { isa = PBXHeadersBuildPhase; buildActionMask = 2147483647; files = ( ); runOnlyForDeploymentPostprocessing = 0; }; 52D6D9ED1BEFFFBE002C0205 /* Headers */ = { isa = PBXHeadersBuildPhase; buildActionMask = 2147483647; files = ( ); runOnlyForDeploymentPostprocessing = 0; }; 52D6DA0C1BF000BD002C0205 /* Headers */ = { isa = PBXHeadersBuildPhase; buildActionMask = 2147483647; files = ( ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXHeadersBuildPhase section */ /* Begin PBXNativeTarget section */ 52D6D97B1BEFF229002C0205 /* SwiftDate-iOS */ = { isa = PBXNativeTarget; buildConfigurationList = 52D6D9901BEFF229002C0205 /* Build configuration list for PBXNativeTarget "SwiftDate-iOS" */; buildPhases = ( 52D6D9771BEFF229002C0205 /* Sources */, 52D6D9781BEFF229002C0205 /* Frameworks */, 52D6D9791BEFF229002C0205 /* Headers */, 52D6D97A1BEFF229002C0205 /* Resources */, 6434DD5720C7D1E5007626EF /* SwiftLint */, ); buildRules = ( ); dependencies = ( ); name = "SwiftDate-iOS"; productName = SwiftDate; productReference = 52D6D97C1BEFF229002C0205 /* SwiftDate.framework */; productType = "com.apple.product-type.framework"; }; 52D6D9851BEFF229002C0205 /* SwiftDate-iOS Tests */ = { isa = PBXNativeTarget; buildConfigurationList = 52D6D9931BEFF229002C0205 /* Build configuration list for PBXNativeTarget "SwiftDate-iOS Tests" */; buildPhases = ( 52D6D9821BEFF229002C0205 /* Sources */, 52D6D9831BEFF229002C0205 /* Frameworks */, 52D6D9841BEFF229002C0205 /* Resources */, ); buildRules = ( ); dependencies = ( 52D6D9891BEFF229002C0205 /* PBXTargetDependency */, ); name = "SwiftDate-iOS Tests"; productName = SwiftDateTests; productReference = 52D6D9861BEFF229002C0205 /* SwiftDate-iOS Tests.xctest */; productType = "com.apple.product-type.bundle.unit-test"; }; 52D6D9E11BEFFF6E002C0205 /* SwiftDate-watchOS */ = { isa = PBXNativeTarget; buildConfigurationList = 52D6D9E71BEFFF6E002C0205 /* Build configuration list for PBXNativeTarget "SwiftDate-watchOS" */; buildPhases = ( 52D6D9DD1BEFFF6E002C0205 /* Sources */, 52D6D9DE1BEFFF6E002C0205 /* Frameworks */, 52D6D9DF1BEFFF6E002C0205 /* Headers */, 52D6D9E01BEFFF6E002C0205 /* Resources */, ); buildRules = ( ); dependencies = ( ); name = "SwiftDate-watchOS"; productName = "SwiftDate-watchOS"; productReference = 52D6D9E21BEFFF6E002C0205 /* SwiftDate.framework */; productType = "com.apple.product-type.framework"; }; 52D6D9EF1BEFFFBE002C0205 /* SwiftDate-tvOS */ = { isa = PBXNativeTarget; buildConfigurationList = 52D6DA011BEFFFBE002C0205 /* Build configuration list for PBXNativeTarget "SwiftDate-tvOS" */; buildPhases = ( 52D6D9EB1BEFFFBE002C0205 /* Sources */, 52D6D9EC1BEFFFBE002C0205 /* Frameworks */, 52D6D9ED1BEFFFBE002C0205 /* Headers */, 52D6D9EE1BEFFFBE002C0205 /* Resources */, ); buildRules = ( ); dependencies = ( ); name = "SwiftDate-tvOS"; productName = "SwiftDate-tvOS"; productReference = 52D6D9F01BEFFFBE002C0205 /* SwiftDate.framework */; productType = "com.apple.product-type.framework"; }; 52D6DA0E1BF000BD002C0205 /* SwiftDate-macOS */ = { isa = PBXNativeTarget; buildConfigurationList = 52D6DA201BF000BD002C0205 /* Build configuration list for PBXNativeTarget "SwiftDate-macOS" */; buildPhases = ( 52D6DA0A1BF000BD002C0205 /* Sources */, 52D6DA0B1BF000BD002C0205 /* Frameworks */, 52D6DA0C1BF000BD002C0205 /* Headers */, 52D6DA0D1BF000BD002C0205 /* Resources */, ); buildRules = ( ); dependencies = ( ); name = "SwiftDate-macOS"; productName = "SwiftDate-macOS"; productReference = 52D6DA0F1BF000BD002C0205 /* SwiftDate.framework */; productType = "com.apple.product-type.framework"; }; 6434DD8B20C809B2007626EF /* TestApplication */ = { isa = PBXNativeTarget; buildConfigurationList = 6434DD9B20C809B4007626EF /* Build configuration list for PBXNativeTarget "TestApplication" */; buildPhases = ( 6434DD8820C809B2007626EF /* Sources */, 6434DD8920C809B2007626EF /* Frameworks */, 6434DD8A20C809B2007626EF /* Resources */, ); buildRules = ( ); dependencies = ( ); name = TestApplication; productName = TestApplication; productReference = 6434DD8C20C809B2007626EF /* TestApplication.app */; productType = "com.apple.product-type.application"; }; DD7502791C68FCFC006590AF /* SwiftDate-macOS Tests */ = { isa = PBXNativeTarget; buildConfigurationList = DD7502821C68FCFC006590AF /* Build configuration list for PBXNativeTarget "SwiftDate-macOS Tests" */; buildPhases = ( DD7502761C68FCFC006590AF /* Sources */, DD7502771C68FCFC006590AF /* Frameworks */, DD7502781C68FCFC006590AF /* Resources */, ); buildRules = ( ); dependencies = ( DD7502811C68FCFC006590AF /* PBXTargetDependency */, ); name = "SwiftDate-macOS Tests"; productName = "SwiftDate-OS Tests"; productReference = DD75027A1C68FCFC006590AF /* SwiftDate-macOS Tests.xctest */; productType = "com.apple.product-type.bundle.unit-test"; }; DD75028C1C690C7A006590AF /* SwiftDate-tvOS Tests */ = { isa = PBXNativeTarget; buildConfigurationList = DD7502951C690C7A006590AF /* Build configuration list for PBXNativeTarget "SwiftDate-tvOS Tests" */; buildPhases = ( DD7502891C690C7A006590AF /* Sources */, DD75028A1C690C7A006590AF /* Frameworks */, DD75028B1C690C7A006590AF /* Resources */, ); buildRules = ( ); dependencies = ( DD7502941C690C7A006590AF /* PBXTargetDependency */, ); name = "SwiftDate-tvOS Tests"; productName = "SwiftDate-tvOS Tests"; productReference = DD75028D1C690C7A006590AF /* SwiftDate-tvOS Tests.xctest */; productType = "com.apple.product-type.bundle.unit-test"; }; /* End PBXNativeTarget section */ /* Begin PBXProject section */ 52D6D9731BEFF229002C0205 /* Project object */ = { isa = PBXProject; attributes = { LastSwiftUpdateCheck = 0940; LastUpgradeCheck = 1220; ORGANIZATIONNAME = SwiftDate; TargetAttributes = { 52D6D97B1BEFF229002C0205 = { CreatedOnToolsVersion = 7.1; LastSwiftMigration = 1020; }; 52D6D9851BEFF229002C0205 = { CreatedOnToolsVersion = 7.1; LastSwiftMigration = 1020; }; 52D6D9E11BEFFF6E002C0205 = { CreatedOnToolsVersion = 7.1; LastSwiftMigration = 0940; }; 52D6D9EF1BEFFFBE002C0205 = { CreatedOnToolsVersion = 7.1; LastSwiftMigration = 0940; }; 52D6DA0E1BF000BD002C0205 = { CreatedOnToolsVersion = 7.1; LastSwiftMigration = 0940; }; 6434DD8B20C809B2007626EF = { CreatedOnToolsVersion = 9.4; DevelopmentTeam = E5DU3FA699; LastSwiftMigration = 1020; ProvisioningStyle = Automatic; }; DD7502791C68FCFC006590AF = { CreatedOnToolsVersion = 7.2.1; LastSwiftMigration = 0800; }; DD75028C1C690C7A006590AF = { CreatedOnToolsVersion = 7.2.1; LastSwiftMigration = 0800; }; }; }; buildConfigurationList = 52D6D9761BEFF229002C0205 /* Build configuration list for PBXProject "SwiftDate" */; compatibilityVersion = "Xcode 6.3"; developmentRegion = en; hasScannedForEncodings = 0; knownRegions = ( en, Base, ); mainGroup = 52D6D9721BEFF229002C0205; productRefGroup = 52D6D97D1BEFF229002C0205 /* Products */; projectDirPath = ""; projectRoot = ""; targets = ( 52D6D97B1BEFF229002C0205 /* SwiftDate-iOS */, 52D6DA0E1BF000BD002C0205 /* SwiftDate-macOS */, 52D6D9E11BEFFF6E002C0205 /* SwiftDate-watchOS */, 52D6D9EF1BEFFFBE002C0205 /* SwiftDate-tvOS */, 52D6D9851BEFF229002C0205 /* SwiftDate-iOS Tests */, DD7502791C68FCFC006590AF /* SwiftDate-macOS Tests */, DD75028C1C690C7A006590AF /* SwiftDate-tvOS Tests */, 6434DD8B20C809B2007626EF /* TestApplication */, ); }; /* End PBXProject section */ /* Begin PBXResourcesBuildPhase section */ 52D6D97A1BEFF229002C0205 /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( 64990AF82286FC31006C427D /* langs in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; 52D6D9841BEFF229002C0205 /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( ); runOnlyForDeploymentPostprocessing = 0; }; 52D6D9E01BEFFF6E002C0205 /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( 64990AFA2286FC31006C427D /* langs in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; 52D6D9EE1BEFFFBE002C0205 /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( 64990AFB2286FC31006C427D /* langs in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; 52D6DA0D1BF000BD002C0205 /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( 64990AF92286FC31006C427D /* langs in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; 6434DD8A20C809B2007626EF /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( 6434DD9920C809B4007626EF /* LaunchScreen.storyboard in Resources */, 6434DD9620C809B4007626EF /* Assets.xcassets in Resources */, 64990AFC2286FC31006C427D /* langs in Resources */, 6434DD9420C809B3007626EF /* Main.storyboard in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; DD7502781C68FCFC006590AF /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( ); runOnlyForDeploymentPostprocessing = 0; }; DD75028B1C690C7A006590AF /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXResourcesBuildPhase section */ /* Begin PBXShellScriptBuildPhase section */ 6434DD5720C7D1E5007626EF /* SwiftLint */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); inputPaths = ( ); name = SwiftLint; outputPaths = ( ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; shellScript = "swiftlint autocorrect\nif which swiftlint >/dev/null; then\nswiftlint\nelse\necho \"warning: SwiftLint not installed, download from https://github.com/realm/SwiftLint\"\nfi\n"; }; /* End PBXShellScriptBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ 52D6D9771BEFF229002C0205 /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( 64B5E26020D30AE40067EDC1 /* TimePeriodCollection.swift in Sources */, 649D473B20C81A2A00513A67 /* DateRepresentable.swift in Sources */, 6470DD1420D27AF500BC2E74 /* String+Parser.swift in Sources */, 649D47E120CAAB7400513A67 /* RelativeFormatter.swift in Sources */, 6470DD2E20D2B64200BC2E74 /* TimePeriod+Support.swift in Sources */, 64B5E26620D30E620067EDC1 /* TimePeriodChain.swift in Sources */, 649D474120C81A4200513A67 /* Date.swift in Sources */, 649D476D20C85C7100513A67 /* ISOParser.swift in Sources */, 6434DD7C20C803EF007626EF /* SwiftDate.swift in Sources */, 649D0E032287412C0056D42E /* RelativeFormatterLanguage.swift in Sources */, 649D47BC20C959F500513A67 /* Date+Math.swift in Sources */, 649D47AA20C91C0B00513A67 /* Date+Compare.swift in Sources */, 6434DD7220C80126007626EF /* Calendars.swift in Sources */, 649D478520C8861200513A67 /* DateInRegion+Components.swift in Sources */, 649D477920C877C300513A67 /* DateInRegion+Create.swift in Sources */, 647AD65621F4826100CF787E /* TimeStructures.swift in Sources */, 64B5E25320D306220067EDC1 /* TimePeriod.swift in Sources */, 649D477F20C880DC00513A67 /* Date+Create.swift in Sources */, 6470DD2120D2A55300BC2E74 /* TimePeriodProtocol.swift in Sources */, 649D474E20C827C400513A67 /* AssociatedValues.swift in Sources */, 649D476120C8529500513A67 /* Formatter+Protocols.swift in Sources */, 649D474820C8241C00513A67 /* Commons.swift in Sources */, 6434DD7720C80263007626EF /* Locales.swift in Sources */, 64B5E25920D3090A0067EDC1 /* TimePeriodGroup.swift in Sources */, 649D477320C872DA00513A67 /* DotNetParserFormatter.swift in Sources */, 6434DD6120C7FAF6007626EF /* DateInRegion.swift in Sources */, 649D47B620C9586500513A67 /* DateInRegion+Math.swift in Sources */, 649D479220C913E200513A67 /* Date+Components.swift in Sources */, 649D47EE20CAB96D00513A67 /* RelativeFormatter+Style.swift in Sources */, 6470DD1A20D296EA00BC2E74 /* TimeInterval+Formatter.swift in Sources */, 6434DD6720C7FC6A007626EF /* Region.swift in Sources */, 649D47A420C91BEA00513A67 /* DateInRegion+Compare.swift in Sources */, 649D47C220C964E000513A67 /* Int+DateComponents.swift in Sources */, 649D47B020C9276400513A67 /* DateComponents+Extras.swift in Sources */, 649D475A20C84FAC00513A67 /* ISOFormatter.swift in Sources */, 6434DD6D20C7FEED007626EF /* Zones.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; 52D6D9821BEFF229002C0205 /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( 64BAB12420E63A3A00FEED79 /* TestDateInRegion+Langs.swift in Sources */, 647AD65C21F4851F00CF787E /* TestDataStructures.swift in Sources */, 6439232220D912670098EC03 /* TestDateInRegion+Math.swift in Sources */, 64EF3E0F20D65478002793C6 /* TestDateInRegion+Compare.swift in Sources */, 6439232620D91D170098EC03 /* TestFormatters.swift in Sources */, 64EF3E0B20D65329002793C6 /* TestDateInRegion+Create.swift in Sources */, 64EF3E0720D56038002793C6 /* TestDateInRegion.swift in Sources */, 6439231E20D90CB10098EC03 /* TestDateInRegion+Components.swift in Sources */, 64EF3E0320D5518D002793C6 /* TestRegion.swift in Sources */, 64BAB12820E6411100FEED79 /* TestSwiftDate.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; 52D6D9DD1BEFFF6E002C0205 /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( 64B5E26220D30AE40067EDC1 /* TimePeriodCollection.swift in Sources */, 649D473D20C81A2A00513A67 /* DateRepresentable.swift in Sources */, 6470DD1620D27AF500BC2E74 /* String+Parser.swift in Sources */, 649D47E320CAAB7400513A67 /* RelativeFormatter.swift in Sources */, 6470DD3020D2B64200BC2E74 /* TimePeriod+Support.swift in Sources */, 64B5E26820D30E620067EDC1 /* TimePeriodChain.swift in Sources */, 649D474320C81A4200513A67 /* Date.swift in Sources */, 649D476F20C85C7100513A67 /* ISOParser.swift in Sources */, 6434DD7E20C803EF007626EF /* SwiftDate.swift in Sources */, 649D0E052287412C0056D42E /* RelativeFormatterLanguage.swift in Sources */, 649D47BE20C959F500513A67 /* Date+Math.swift in Sources */, 649D47AC20C91C0B00513A67 /* Date+Compare.swift in Sources */, 6434DD7420C80126007626EF /* Calendars.swift in Sources */, 649D478720C8861200513A67 /* DateInRegion+Components.swift in Sources */, 649D477B20C877C300513A67 /* DateInRegion+Create.swift in Sources */, 647AD65821F4826100CF787E /* TimeStructures.swift in Sources */, 64B5E25520D306220067EDC1 /* TimePeriod.swift in Sources */, 649D478120C880DC00513A67 /* Date+Create.swift in Sources */, 6470DD2320D2A55300BC2E74 /* TimePeriodProtocol.swift in Sources */, 649D475020C827C400513A67 /* AssociatedValues.swift in Sources */, 649D476320C8529500513A67 /* Formatter+Protocols.swift in Sources */, 649D474A20C8241C00513A67 /* Commons.swift in Sources */, 6434DD7920C80263007626EF /* Locales.swift in Sources */, 64B5E25B20D3090A0067EDC1 /* TimePeriodGroup.swift in Sources */, 649D477520C872DA00513A67 /* DotNetParserFormatter.swift in Sources */, 6434DD6320C7FAF6007626EF /* DateInRegion.swift in Sources */, 649D47B820C9586500513A67 /* DateInRegion+Math.swift in Sources */, 649D479420C913E200513A67 /* Date+Components.swift in Sources */, 649D47F020CAB96D00513A67 /* RelativeFormatter+Style.swift in Sources */, 6470DD1C20D296EA00BC2E74 /* TimeInterval+Formatter.swift in Sources */, 6434DD6920C7FC6A007626EF /* Region.swift in Sources */, 649D47A620C91BEA00513A67 /* DateInRegion+Compare.swift in Sources */, 649D47C420C964E000513A67 /* Int+DateComponents.swift in Sources */, 649D47B220C9276400513A67 /* DateComponents+Extras.swift in Sources */, 649D475C20C84FAC00513A67 /* ISOFormatter.swift in Sources */, 6434DD6F20C7FEED007626EF /* Zones.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; 52D6D9EB1BEFFFBE002C0205 /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( 64B5E26320D30AE40067EDC1 /* TimePeriodCollection.swift in Sources */, 649D473E20C81A2A00513A67 /* DateRepresentable.swift in Sources */, 6470DD1720D27AF500BC2E74 /* String+Parser.swift in Sources */, 649D47E420CAAB7400513A67 /* RelativeFormatter.swift in Sources */, 6470DD3120D2B64200BC2E74 /* TimePeriod+Support.swift in Sources */, 64B5E26920D30E620067EDC1 /* TimePeriodChain.swift in Sources */, 649D474420C81A4200513A67 /* Date.swift in Sources */, 649D477020C85C7100513A67 /* ISOParser.swift in Sources */, 6434DD7F20C803EF007626EF /* SwiftDate.swift in Sources */, 649D0E062287412C0056D42E /* RelativeFormatterLanguage.swift in Sources */, 649D47BF20C959F500513A67 /* Date+Math.swift in Sources */, 649D47AD20C91C0B00513A67 /* Date+Compare.swift in Sources */, 6434DD7520C80126007626EF /* Calendars.swift in Sources */, 649D478820C8861200513A67 /* DateInRegion+Components.swift in Sources */, 649D477C20C877C300513A67 /* DateInRegion+Create.swift in Sources */, 647AD65921F4826100CF787E /* TimeStructures.swift in Sources */, 64B5E25620D306220067EDC1 /* TimePeriod.swift in Sources */, 649D478220C880DC00513A67 /* Date+Create.swift in Sources */, 6470DD2420D2A55300BC2E74 /* TimePeriodProtocol.swift in Sources */, 649D475120C827C400513A67 /* AssociatedValues.swift in Sources */, 649D476420C8529500513A67 /* Formatter+Protocols.swift in Sources */, 649D474B20C8241C00513A67 /* Commons.swift in Sources */, 6434DD7A20C80263007626EF /* Locales.swift in Sources */, 64B5E25C20D3090A0067EDC1 /* TimePeriodGroup.swift in Sources */, 649D477620C872DA00513A67 /* DotNetParserFormatter.swift in Sources */, 6434DD6420C7FAF6007626EF /* DateInRegion.swift in Sources */, 649D47B920C9586500513A67 /* DateInRegion+Math.swift in Sources */, 649D479520C913E200513A67 /* Date+Components.swift in Sources */, 649D47F120CAB96D00513A67 /* RelativeFormatter+Style.swift in Sources */, 6470DD1D20D296EA00BC2E74 /* TimeInterval+Formatter.swift in Sources */, 6434DD6A20C7FC6A007626EF /* Region.swift in Sources */, 649D47A720C91BEA00513A67 /* DateInRegion+Compare.swift in Sources */, 649D47C520C964E000513A67 /* Int+DateComponents.swift in Sources */, 649D47B320C9276400513A67 /* DateComponents+Extras.swift in Sources */, 649D475D20C84FAC00513A67 /* ISOFormatter.swift in Sources */, 6434DD7020C7FEED007626EF /* Zones.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; 52D6DA0A1BF000BD002C0205 /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( 64B5E26120D30AE40067EDC1 /* TimePeriodCollection.swift in Sources */, 649D473C20C81A2A00513A67 /* DateRepresentable.swift in Sources */, 6470DD1520D27AF500BC2E74 /* String+Parser.swift in Sources */, 649D47E220CAAB7400513A67 /* RelativeFormatter.swift in Sources */, 6470DD2F20D2B64200BC2E74 /* TimePeriod+Support.swift in Sources */, 64B5E26720D30E620067EDC1 /* TimePeriodChain.swift in Sources */, 649D474220C81A4200513A67 /* Date.swift in Sources */, 649D476E20C85C7100513A67 /* ISOParser.swift in Sources */, 6434DD7D20C803EF007626EF /* SwiftDate.swift in Sources */, 649D0E042287412C0056D42E /* RelativeFormatterLanguage.swift in Sources */, 649D47BD20C959F500513A67 /* Date+Math.swift in Sources */, 649D47AB20C91C0B00513A67 /* Date+Compare.swift in Sources */, 6434DD7320C80126007626EF /* Calendars.swift in Sources */, 649D478620C8861200513A67 /* DateInRegion+Components.swift in Sources */, 649D477A20C877C300513A67 /* DateInRegion+Create.swift in Sources */, 647AD65721F4826100CF787E /* TimeStructures.swift in Sources */, 64B5E25420D306220067EDC1 /* TimePeriod.swift in Sources */, 649D478020C880DC00513A67 /* Date+Create.swift in Sources */, 6470DD2220D2A55300BC2E74 /* TimePeriodProtocol.swift in Sources */, 649D474F20C827C400513A67 /* AssociatedValues.swift in Sources */, 649D476220C8529500513A67 /* Formatter+Protocols.swift in Sources */, 649D474920C8241C00513A67 /* Commons.swift in Sources */, 6434DD7820C80263007626EF /* Locales.swift in Sources */, 64B5E25A20D3090A0067EDC1 /* TimePeriodGroup.swift in Sources */, 649D477420C872DA00513A67 /* DotNetParserFormatter.swift in Sources */, 6434DD6220C7FAF6007626EF /* DateInRegion.swift in Sources */, 649D47B720C9586500513A67 /* DateInRegion+Math.swift in Sources */, 649D479320C913E200513A67 /* Date+Components.swift in Sources */, 649D47EF20CAB96D00513A67 /* RelativeFormatter+Style.swift in Sources */, 6470DD1B20D296EA00BC2E74 /* TimeInterval+Formatter.swift in Sources */, 6434DD6820C7FC6A007626EF /* Region.swift in Sources */, 649D47A520C91BEA00513A67 /* DateInRegion+Compare.swift in Sources */, 649D47C320C964E000513A67 /* Int+DateComponents.swift in Sources */, 649D47B120C9276400513A67 /* DateComponents+Extras.swift in Sources */, 649D475B20C84FAC00513A67 /* ISOFormatter.swift in Sources */, 6434DD6E20C7FEED007626EF /* Zones.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; 6434DD8820C809B2007626EF /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( 649D0E072287412C0056D42E /* RelativeFormatterLanguage.swift in Sources */, 649D475E20C84FAC00513A67 /* ISOFormatter.swift in Sources */, 64B5E26A20D30E620067EDC1 /* TimePeriodChain.swift in Sources */, 6434DDA020C809C2007626EF /* Calendars.swift in Sources */, 649D47E520CAAB7400513A67 /* RelativeFormatter.swift in Sources */, 6470DD1820D27AF500BC2E74 /* String+Parser.swift in Sources */, 649D475220C827C400513A67 /* AssociatedValues.swift in Sources */, 6434DDA120C809C2007626EF /* Locales.swift in Sources */, 649D476520C8529500513A67 /* Formatter+Protocols.swift in Sources */, 6470DD2520D2A55300BC2E74 /* TimePeriodProtocol.swift in Sources */, 647AD65A21F4826100CF787E /* TimeStructures.swift in Sources */, 6434DD9F20C809C2007626EF /* Zones.swift in Sources */, 6434DDA520C809C2007626EF /* Region.swift in Sources */, 649D474C20C8241C00513A67 /* Commons.swift in Sources */, 6470DD3220D2B64200BC2E74 /* TimePeriod+Support.swift in Sources */, 649D47BA20C9586500513A67 /* DateInRegion+Math.swift in Sources */, 649D479620C913E200513A67 /* Date+Components.swift in Sources */, 649D477D20C877C300513A67 /* DateInRegion+Create.swift in Sources */, 649D47B420C9276400513A67 /* DateComponents+Extras.swift in Sources */, 649D47C020C959F500513A67 /* Date+Math.swift in Sources */, 649D47AE20C91C0B00513A67 /* Date+Compare.swift in Sources */, 649D477720C872DA00513A67 /* DotNetParserFormatter.swift in Sources */, 649D477120C85C7100513A67 /* ISOParser.swift in Sources */, 649D47C620C964E000513A67 /* Int+DateComponents.swift in Sources */, 6434DDA420C809C2007626EF /* DateInRegion.swift in Sources */, 6434DD9120C809B3007626EF /* ViewController.swift in Sources */, 649D47A820C91BEA00513A67 /* DateInRegion+Compare.swift in Sources */, 649D474520C81A4200513A67 /* Date.swift in Sources */, 64B5E26420D30AE40067EDC1 /* TimePeriodCollection.swift in Sources */, 649D473F20C81A2A00513A67 /* DateRepresentable.swift in Sources */, 649D478320C880DC00513A67 /* Date+Create.swift in Sources */, 64B5E25720D306220067EDC1 /* TimePeriod.swift in Sources */, 6470DD1E20D296EA00BC2E74 /* TimeInterval+Formatter.swift in Sources */, 6434DD8F20C809B3007626EF /* AppDelegate.swift in Sources */, 649D478920C8861200513A67 /* DateInRegion+Components.swift in Sources */, 649D47F220CAB96D00513A67 /* RelativeFormatter+Style.swift in Sources */, 6434DD9E20C809C2007626EF /* SwiftDate.swift in Sources */, 64B5E25D20D3090A0067EDC1 /* TimePeriodGroup.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; DD7502761C68FCFC006590AF /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( 64BAB12520E63A3A00FEED79 /* TestDateInRegion+Langs.swift in Sources */, 647AD65D21F4851F00CF787E /* TestDataStructures.swift in Sources */, A89F3FAF22A00019002D1BD0 /* TestDate.swift in Sources */, 6439232320D912670098EC03 /* TestDateInRegion+Math.swift in Sources */, 64EF3E1020D65478002793C6 /* TestDateInRegion+Compare.swift in Sources */, 6439232720D91D170098EC03 /* TestFormatters.swift in Sources */, 64EF3E0C20D65329002793C6 /* TestDateInRegion+Create.swift in Sources */, 64EF3E0820D5622D002793C6 /* TestDateInRegion.swift in Sources */, 6439231F20D90CB10098EC03 /* TestDateInRegion+Components.swift in Sources */, 64EF3E0420D551E4002793C6 /* TestRegion.swift in Sources */, 64BAB12920E6411100FEED79 /* TestSwiftDate.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; DD7502891C690C7A006590AF /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( 64BAB12620E63A3A00FEED79 /* TestDateInRegion+Langs.swift in Sources */, 647AD65E21F4851F00CF787E /* TestDataStructures.swift in Sources */, 6439232420D912670098EC03 /* TestDateInRegion+Math.swift in Sources */, 64EF3E1120D65478002793C6 /* TestDateInRegion+Compare.swift in Sources */, 6439232820D91D170098EC03 /* TestFormatters.swift in Sources */, 64EF3E0D20D65329002793C6 /* TestDateInRegion+Create.swift in Sources */, 64EF3E0920D5622D002793C6 /* TestDateInRegion.swift in Sources */, 6439232020D90CB10098EC03 /* TestDateInRegion+Components.swift in Sources */, 64EF3E0520D551E5002793C6 /* TestRegion.swift in Sources */, 64BAB12A20E6411100FEED79 /* TestSwiftDate.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXSourcesBuildPhase section */ /* Begin PBXTargetDependency section */ 52D6D9891BEFF229002C0205 /* PBXTargetDependency */ = { isa = PBXTargetDependency; target = 52D6D97B1BEFF229002C0205 /* SwiftDate-iOS */; targetProxy = 52D6D9881BEFF229002C0205 /* PBXContainerItemProxy */; }; DD7502811C68FCFC006590AF /* PBXTargetDependency */ = { isa = PBXTargetDependency; target = 52D6DA0E1BF000BD002C0205 /* SwiftDate-macOS */; targetProxy = DD7502801C68FCFC006590AF /* PBXContainerItemProxy */; }; DD7502941C690C7A006590AF /* PBXTargetDependency */ = { isa = PBXTargetDependency; target = 52D6D9EF1BEFFFBE002C0205 /* SwiftDate-tvOS */; targetProxy = DD7502931C690C7A006590AF /* PBXContainerItemProxy */; }; /* End PBXTargetDependency section */ /* Begin PBXVariantGroup section */ 6434DD9220C809B3007626EF /* Main.storyboard */ = { isa = PBXVariantGroup; children = ( 6434DD9320C809B3007626EF /* Base */, ); name = Main.storyboard; sourceTree = ""; }; 6434DD9720C809B4007626EF /* LaunchScreen.storyboard */ = { isa = PBXVariantGroup; children = ( 6434DD9820C809B4007626EF /* Base */, ); name = LaunchScreen.storyboard; sourceTree = ""; }; /* End PBXVariantGroup section */ /* Begin XCBuildConfiguration section */ 52D6D98E1BEFF229002C0205 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_COMMA = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; COPY_PHASE_STRIP = NO; CURRENT_PROJECT_VERSION = 1; DEBUG_INFORMATION_FORMAT = dwarf; ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_TESTABILITY = YES; GCC_C_LANGUAGE_STANDARD = gnu99; GCC_DYNAMIC_NO_PIC = NO; GCC_NO_COMMON_BLOCKS = YES; GCC_OPTIMIZATION_LEVEL = 0; GCC_PREPROCESSOR_DEFINITIONS = ( "DEBUG=1", "$(inherited)", ); GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; GCC_WARN_UNDECLARED_SELECTOR = YES; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; IPHONEOS_DEPLOYMENT_TARGET = 12.0; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = iphoneos; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; VERSIONING_SYSTEM = "apple-generic"; VERSION_INFO_PREFIX = ""; }; name = Debug; }; 52D6D98F1BEFF229002C0205 /* Release */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_COMMA = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; COPY_PHASE_STRIP = NO; CURRENT_PROJECT_VERSION = 1; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; ENABLE_NS_ASSERTIONS = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; GCC_C_LANGUAGE_STANDARD = gnu99; GCC_NO_COMMON_BLOCKS = YES; GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; GCC_WARN_UNDECLARED_SELECTOR = YES; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; IPHONEOS_DEPLOYMENT_TARGET = 12.0; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; VALIDATE_PRODUCT = YES; VERSIONING_SYSTEM = "apple-generic"; VERSION_INFO_PREFIX = ""; }; name = Release; }; 52D6D9911BEFF229002C0205 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { APPLICATION_EXTENSION_API_ONLY = YES; CLANG_ENABLE_MODULES = YES; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; CURRENT_PROJECT_VERSION = 0; DEFINES_MODULE = YES; DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_CURRENT_VERSION = 1; DYLIB_INSTALL_NAME_BASE = "@rpath"; INFOPLIST_FILE = Configs/SwiftDate.plist; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; IPHONEOS_DEPLOYMENT_TARGET = 9.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; MARKETING_VERSION = 6.3.1; ONLY_ACTIVE_ARCH = NO; PRODUCT_BUNDLE_IDENTIFIER = "com.SwiftDate.SwiftDate-iOS"; PRODUCT_NAME = SwiftDate; SKIP_INSTALL = YES; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; }; name = Debug; }; 52D6D9921BEFF229002C0205 /* Release */ = { isa = XCBuildConfiguration; buildSettings = { APPLICATION_EXTENSION_API_ONLY = YES; CLANG_ENABLE_MODULES = YES; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; CURRENT_PROJECT_VERSION = 0; DEFINES_MODULE = YES; DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_CURRENT_VERSION = 1; DYLIB_INSTALL_NAME_BASE = "@rpath"; INFOPLIST_FILE = Configs/SwiftDate.plist; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; IPHONEOS_DEPLOYMENT_TARGET = 9.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; MARKETING_VERSION = 6.3.1; PRODUCT_BUNDLE_IDENTIFIER = "com.SwiftDate.SwiftDate-iOS"; PRODUCT_NAME = SwiftDate; SKIP_INSTALL = YES; SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; }; name = Release; }; 52D6D9941BEFF229002C0205 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; CLANG_ENABLE_MODULES = YES; INFOPLIST_FILE = Configs/SwiftDateTests.plist; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = "com.SwiftDate.SwiftDate-iOS-Tests"; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; }; name = Debug; }; 52D6D9951BEFF229002C0205 /* Release */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; CLANG_ENABLE_MODULES = YES; INFOPLIST_FILE = Configs/SwiftDateTests.plist; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = "com.SwiftDate.SwiftDate-iOS-Tests"; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; }; name = Release; }; 52D6D9E81BEFFF6E002C0205 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { APPLICATION_EXTENSION_API_ONLY = YES; CLANG_ENABLE_MODULES = YES; "CODE_SIGN_IDENTITY[sdk=watchos*]" = ""; CURRENT_PROJECT_VERSION = 0; DEFINES_MODULE = YES; DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_CURRENT_VERSION = 1; DYLIB_INSTALL_NAME_BASE = "@rpath"; INFOPLIST_FILE = Configs/SwiftDate.plist; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; MARKETING_VERSION = 6.3.1; PRODUCT_BUNDLE_IDENTIFIER = "com.SwiftDate.SwiftDate-watchOS"; PRODUCT_NAME = SwiftDate; SDKROOT = watchos; SKIP_INSTALL = YES; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; TARGETED_DEVICE_FAMILY = 4; WATCHOS_DEPLOYMENT_TARGET = 2.0; }; name = Debug; }; 52D6D9E91BEFFF6E002C0205 /* Release */ = { isa = XCBuildConfiguration; buildSettings = { APPLICATION_EXTENSION_API_ONLY = YES; CLANG_ENABLE_MODULES = YES; "CODE_SIGN_IDENTITY[sdk=watchos*]" = ""; CURRENT_PROJECT_VERSION = 0; DEFINES_MODULE = YES; DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_CURRENT_VERSION = 1; DYLIB_INSTALL_NAME_BASE = "@rpath"; INFOPLIST_FILE = Configs/SwiftDate.plist; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; MARKETING_VERSION = 6.3.1; PRODUCT_BUNDLE_IDENTIFIER = "com.SwiftDate.SwiftDate-watchOS"; PRODUCT_NAME = SwiftDate; SDKROOT = watchos; SKIP_INSTALL = YES; SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; TARGETED_DEVICE_FAMILY = 4; WATCHOS_DEPLOYMENT_TARGET = 2.0; }; name = Release; }; 52D6DA021BEFFFBE002C0205 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { APPLICATION_EXTENSION_API_ONLY = YES; CLANG_ENABLE_MODULES = YES; "CODE_SIGN_IDENTITY[sdk=appletvos*]" = ""; CURRENT_PROJECT_VERSION = 0; DEFINES_MODULE = YES; DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_CURRENT_VERSION = 1; DYLIB_INSTALL_NAME_BASE = "@rpath"; INFOPLIST_FILE = Configs/SwiftDate.plist; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; MARKETING_VERSION = 6.3.1; PRODUCT_BUNDLE_IDENTIFIER = "com.SwiftDate.SwiftDate-tvOS"; PRODUCT_NAME = SwiftDate; SDKROOT = appletvos; SKIP_INSTALL = YES; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; TARGETED_DEVICE_FAMILY = 3; TVOS_DEPLOYMENT_TARGET = 9.0; }; name = Debug; }; 52D6DA031BEFFFBE002C0205 /* Release */ = { isa = XCBuildConfiguration; buildSettings = { APPLICATION_EXTENSION_API_ONLY = YES; CLANG_ENABLE_MODULES = YES; "CODE_SIGN_IDENTITY[sdk=appletvos*]" = ""; CURRENT_PROJECT_VERSION = 0; DEFINES_MODULE = YES; DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_CURRENT_VERSION = 1; DYLIB_INSTALL_NAME_BASE = "@rpath"; INFOPLIST_FILE = Configs/SwiftDate.plist; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; MARKETING_VERSION = 6.3.1; PRODUCT_BUNDLE_IDENTIFIER = "com.SwiftDate.SwiftDate-tvOS"; PRODUCT_NAME = SwiftDate; SDKROOT = appletvos; SKIP_INSTALL = YES; SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; TARGETED_DEVICE_FAMILY = 3; TVOS_DEPLOYMENT_TARGET = 9.0; }; name = Release; }; 52D6DA211BF000BD002C0205 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { APPLICATION_EXTENSION_API_ONLY = YES; CLANG_ENABLE_MODULES = YES; CODE_SIGN_IDENTITY = "-"; COMBINE_HIDPI_IMAGES = YES; CURRENT_PROJECT_VERSION = 0; DEFINES_MODULE = YES; DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_CURRENT_VERSION = 1; DYLIB_INSTALL_NAME_BASE = "@rpath"; FRAMEWORK_VERSION = A; INFOPLIST_FILE = Configs/SwiftDate.plist; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/Frameworks"; MACOSX_DEPLOYMENT_TARGET = 10.10; MARKETING_VERSION = 6.3.1; PRODUCT_BUNDLE_IDENTIFIER = "com.SwiftDate.SwiftDate-macOS"; PRODUCT_NAME = SwiftDate; SDKROOT = macosx; SKIP_INSTALL = YES; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; }; name = Debug; }; 52D6DA221BF000BD002C0205 /* Release */ = { isa = XCBuildConfiguration; buildSettings = { APPLICATION_EXTENSION_API_ONLY = YES; CLANG_ENABLE_MODULES = YES; CODE_SIGN_IDENTITY = "-"; COMBINE_HIDPI_IMAGES = YES; CURRENT_PROJECT_VERSION = 0; DEFINES_MODULE = YES; DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_CURRENT_VERSION = 1; DYLIB_INSTALL_NAME_BASE = "@rpath"; FRAMEWORK_VERSION = A; INFOPLIST_FILE = Configs/SwiftDate.plist; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/Frameworks"; MACOSX_DEPLOYMENT_TARGET = 10.10; MARKETING_VERSION = 6.3.1; PRODUCT_BUNDLE_IDENTIFIER = "com.SwiftDate.SwiftDate-macOS"; PRODUCT_NAME = SwiftDate; SDKROOT = macosx; SKIP_INSTALL = YES; SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; }; name = Release; }; 6434DD9C20C809B4007626EF /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; CLANG_ENABLE_OBJC_WEAK = YES; CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; CODE_SIGN_IDENTITY = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; DEVELOPMENT_TEAM = E5DU3FA699; GCC_C_LANGUAGE_STANDARD = gnu11; INFOPLIST_FILE = TestApplication/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 12.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = com.breakfastcode.TestApplication; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; TARGETED_DEVICE_FAMILY = "1,2"; }; name = Debug; }; 6434DD9D20C809B4007626EF /* Release */ = { isa = XCBuildConfiguration; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; CLANG_ENABLE_OBJC_WEAK = YES; CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; CODE_SIGN_IDENTITY = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; DEVELOPMENT_TEAM = E5DU3FA699; GCC_C_LANGUAGE_STANDARD = gnu11; INFOPLIST_FILE = TestApplication/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 12.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = com.breakfastcode.TestApplication; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; TARGETED_DEVICE_FAMILY = "1,2"; }; name = Release; }; DD7502831C68FCFC006590AF /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; CODE_SIGN_IDENTITY = "-"; COMBINE_HIDPI_IMAGES = YES; INFOPLIST_FILE = Configs/SwiftDateTests.plist; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/../Frameworks"; MACOSX_DEPLOYMENT_TARGET = 10.11; PRODUCT_BUNDLE_IDENTIFIER = "com.SwiftDate.SwiftDate-macOS-Tests"; PRODUCT_NAME = "$(TARGET_NAME)"; SDKROOT = macosx; }; name = Debug; }; DD7502841C68FCFC006590AF /* Release */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; CODE_SIGN_IDENTITY = "-"; COMBINE_HIDPI_IMAGES = YES; INFOPLIST_FILE = Configs/SwiftDateTests.plist; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/../Frameworks"; MACOSX_DEPLOYMENT_TARGET = 10.11; PRODUCT_BUNDLE_IDENTIFIER = "com.SwiftDate.SwiftDate-macOS-Tests"; PRODUCT_NAME = "$(TARGET_NAME)"; SDKROOT = macosx; SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; }; name = Release; }; DD7502961C690C7A006590AF /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; INFOPLIST_FILE = Configs/SwiftDateTests.plist; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = "com.SwiftDate.SwiftDate-tvOS-Tests"; PRODUCT_NAME = "$(TARGET_NAME)"; SDKROOT = appletvos; TVOS_DEPLOYMENT_TARGET = 12.0; }; name = Debug; }; DD7502971C690C7A006590AF /* Release */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; INFOPLIST_FILE = Configs/SwiftDateTests.plist; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = "com.SwiftDate.SwiftDate-tvOS-Tests"; PRODUCT_NAME = "$(TARGET_NAME)"; SDKROOT = appletvos; SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; TVOS_DEPLOYMENT_TARGET = 12.0; }; name = Release; }; /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ 52D6D9761BEFF229002C0205 /* Build configuration list for PBXProject "SwiftDate" */ = { isa = XCConfigurationList; buildConfigurations = ( 52D6D98E1BEFF229002C0205 /* Debug */, 52D6D98F1BEFF229002C0205 /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; 52D6D9901BEFF229002C0205 /* Build configuration list for PBXNativeTarget "SwiftDate-iOS" */ = { isa = XCConfigurationList; buildConfigurations = ( 52D6D9911BEFF229002C0205 /* Debug */, 52D6D9921BEFF229002C0205 /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; 52D6D9931BEFF229002C0205 /* Build configuration list for PBXNativeTarget "SwiftDate-iOS Tests" */ = { isa = XCConfigurationList; buildConfigurations = ( 52D6D9941BEFF229002C0205 /* Debug */, 52D6D9951BEFF229002C0205 /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; 52D6D9E71BEFFF6E002C0205 /* Build configuration list for PBXNativeTarget "SwiftDate-watchOS" */ = { isa = XCConfigurationList; buildConfigurations = ( 52D6D9E81BEFFF6E002C0205 /* Debug */, 52D6D9E91BEFFF6E002C0205 /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; 52D6DA011BEFFFBE002C0205 /* Build configuration list for PBXNativeTarget "SwiftDate-tvOS" */ = { isa = XCConfigurationList; buildConfigurations = ( 52D6DA021BEFFFBE002C0205 /* Debug */, 52D6DA031BEFFFBE002C0205 /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; 52D6DA201BF000BD002C0205 /* Build configuration list for PBXNativeTarget "SwiftDate-macOS" */ = { isa = XCConfigurationList; buildConfigurations = ( 52D6DA211BF000BD002C0205 /* Debug */, 52D6DA221BF000BD002C0205 /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; 6434DD9B20C809B4007626EF /* Build configuration list for PBXNativeTarget "TestApplication" */ = { isa = XCConfigurationList; buildConfigurations = ( 6434DD9C20C809B4007626EF /* Debug */, 6434DD9D20C809B4007626EF /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; DD7502821C68FCFC006590AF /* Build configuration list for PBXNativeTarget "SwiftDate-macOS Tests" */ = { isa = XCConfigurationList; buildConfigurations = ( DD7502831C68FCFC006590AF /* Debug */, DD7502841C68FCFC006590AF /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; DD7502951C690C7A006590AF /* Build configuration list for PBXNativeTarget "SwiftDate-tvOS Tests" */ = { isa = XCConfigurationList; buildConfigurations = ( DD7502961C690C7A006590AF /* Debug */, DD7502971C690C7A006590AF /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; /* End XCConfigurationList section */ }; rootObject = 52D6D9731BEFF229002C0205 /* Project object */; } ================================================ FILE: SwiftDate.xcodeproj/project.xcworkspace/contents.xcworkspacedata ================================================ ================================================ FILE: SwiftDate.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist ================================================ IDEDidComputeMac32BitWarning ================================================ FILE: SwiftDate.xcodeproj/xcshareddata/xcschemes/SwiftDate-iOS Tests.xcscheme ================================================ ================================================ FILE: SwiftDate.xcodeproj/xcshareddata/xcschemes/SwiftDate-iOS.xcscheme ================================================ ================================================ FILE: SwiftDate.xcodeproj/xcshareddata/xcschemes/SwiftDate-macOS.xcscheme ================================================ ================================================ FILE: SwiftDate.xcodeproj/xcshareddata/xcschemes/SwiftDate-tvOS.xcscheme ================================================ ================================================ FILE: SwiftDate.xcodeproj/xcshareddata/xcschemes/SwiftDate-watchOS.xcscheme ================================================ ================================================ FILE: TestApplication/AppDelegate.swift ================================================ // // AppDelegate.swift // TestApplication // // Created by Daniele Margutti on 06/06/2018. // Copyright © 2018 SwiftDate. All rights reserved. // import UIKit @UIApplicationMain class AppDelegate: UIResponder, UIApplicationDelegate { var window: UIWindow? func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { // Override point for customization after application launch. return true } func applicationWillResignActive(_ application: UIApplication) { // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state. // Use this method to pause ongoing tasks, disable timers, and invalidate graphics rendering callbacks. Games should use this method to pause the game. } func applicationDidEnterBackground(_ application: UIApplication) { // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later. // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. } func applicationWillEnterForeground(_ application: UIApplication) { // Called as part of the transition from the background to the active state; here you can undo many of the changes made on entering the background. } func applicationDidBecomeActive(_ application: UIApplication) { // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface. } func applicationWillTerminate(_ application: UIApplication) { // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. } } ================================================ FILE: TestApplication/Assets.xcassets/AppIcon.appiconset/Contents.json ================================================ { "images" : [ { "idiom" : "iphone", "size" : "20x20", "scale" : "2x" }, { "idiom" : "iphone", "size" : "20x20", "scale" : "3x" }, { "idiom" : "iphone", "size" : "29x29", "scale" : "2x" }, { "idiom" : "iphone", "size" : "29x29", "scale" : "3x" }, { "idiom" : "iphone", "size" : "40x40", "scale" : "2x" }, { "idiom" : "iphone", "size" : "40x40", "scale" : "3x" }, { "idiom" : "iphone", "size" : "60x60", "scale" : "2x" }, { "idiom" : "iphone", "size" : "60x60", "scale" : "3x" }, { "idiom" : "ipad", "size" : "20x20", "scale" : "1x" }, { "idiom" : "ipad", "size" : "20x20", "scale" : "2x" }, { "idiom" : "ipad", "size" : "29x29", "scale" : "1x" }, { "idiom" : "ipad", "size" : "29x29", "scale" : "2x" }, { "idiom" : "ipad", "size" : "40x40", "scale" : "1x" }, { "idiom" : "ipad", "size" : "40x40", "scale" : "2x" }, { "idiom" : "ipad", "size" : "76x76", "scale" : "1x" }, { "idiom" : "ipad", "size" : "76x76", "scale" : "2x" }, { "idiom" : "ipad", "size" : "83.5x83.5", "scale" : "2x" }, { "idiom" : "ios-marketing", "size" : "1024x1024", "scale" : "1x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: TestApplication/Assets.xcassets/Contents.json ================================================ { "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: TestApplication/Base.lproj/LaunchScreen.storyboard ================================================ ================================================ FILE: TestApplication/Base.lproj/Main.storyboard ================================================ ================================================ FILE: TestApplication/Info.plist ================================================ CFBundleDevelopmentRegion $(DEVELOPMENT_LANGUAGE) CFBundleExecutable $(EXECUTABLE_NAME) CFBundleIdentifier $(PRODUCT_BUNDLE_IDENTIFIER) CFBundleInfoDictionaryVersion 6.0 CFBundleName $(PRODUCT_NAME) CFBundlePackageType APPL CFBundleShortVersionString 1.0 CFBundleVersion 1 LSRequiresIPhoneOS UILaunchStoryboardName LaunchScreen UIMainStoryboardFile Main UIRequiredDeviceCapabilities armv7 UISupportedInterfaceOrientations UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight UISupportedInterfaceOrientations~ipad UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight ================================================ FILE: TestApplication/ViewController.swift ================================================ // // ViewController.swift // TestApplication // // Created by Daniele Margutti on 06/06/2018. // Copyright © 2018 SwiftDate. All rights reserved. // import UIKit class ViewController: UIViewController { override func viewDidLoad() { super.viewDidLoad() } override func didReceiveMemoryWarning() { super.didReceiveMemoryWarning() } } ================================================ FILE: Tests/LinuxMain.swift ================================================ // Generated using Sourcery 0.15.0 — https://github.com/krzysztofzablocki/Sourcery // DO NOT EDIT @testable import SwiftDateTests import XCTest extension TestDateInRegion { static var allTests: [(String, (TestDateInRegion) -> () throws -> Void)] = [ ("testDateInRegion_ParseWithLocale", testDateInRegion_ParseWithLocale), ("testDateInRegion_InitWithDateAndRegion", testDateInRegion_InitWithDateAndRegion), ("testDateInRegion_InitFromTimeInterval", testDateInRegion_InitFromTimeInterval), ("testDateInRegion_InitFromComponents", testDateInRegion_InitFromComponents), ("testDateInRegion_InitFromParams", testDateInRegion_InitFromParams), ("testDateInRegion_PastAndFuture", testDateInRegion_PastAndFuture), ("testDateInRegion_Description", testDateInRegion_Description), ("testDateInRegion_DateComponents", testDateInRegion_DateComponents), ("testDateInRegion_Hash", testDateInRegion_Hash), ("testDateInRegion_InitComponentsCallback", testDateInRegion_InitComponentsCallback), ("testDateInRegion_ExtractComponents", testDateInRegion_ExtractComponents), ("testDateInRegion_InitStringAutoFormat", testDateInRegion_InitStringAutoFormat) ] } extension TestDateInRegion_Compare { static var allTests: [(String, (TestDateInRegion_Compare) -> () throws -> Void)] = [ ("testDateInRegion_compareCloseTo", testDateInRegion_compareCloseTo), ("testDateInRegion_compare", testDateInRegion_compare), ("testDateInRegion_compareGranularity", testDateInRegion_compareGranularity), ("testDateInRegion_isBeforeDate", testDateInRegion_isBeforeDate), ("testDateInRegion_isAfterDate", testDateInRegion_isAfterDate), ("testDateInRegion_isInRange", testDateInRegion_isInRange), ("testDateInRegion_earlierAndLaterDate", testDateInRegion_earlierAndLaterDate), ("testDateInRegion_compareMath", testDateInRegion_compareMath), ("testDateInRange_GranuralityTest", testDateInRange_GranuralityTest) ] } extension TestDateInRegion_Components { static var allTests: [(String, (TestDateInRegion_Components) -> () throws -> Void)] = [ ("testDateInRegion_Components", testDateInRegion_Components), ("testDateInRegion_isLeapMonth", testDateInRegion_isLeapMonth), ("testDateInRegion_dateBySet", testDateInRegion_dateBySet), ("testDateInRegion_isLeapYear", testDateInRegion_isLeapYear), ("testDateInRegion_julianDayAndModifiedJulianDay", testDateInRegion_julianDayAndModifiedJulianDay), ("test_ordinalDay", test_ordinalDay), ("testDateInRegion_ISOFormatterAlt", testDateInRegion_ISOFormatterAlt), ("testDateInRegion_getIntervalForComponentBetweenDates", testDateInRegion_getIntervalForComponentBetweenDates), ("testDateInRegion_timeIntervalSince", testDateInRegion_timeIntervalSince), ("testQuarter", testQuarter), ("testAbsoluteDateISOFormatting", testAbsoluteDateISOFormatting) ] } extension TestDateInRegion_Create { static var allTests: [(String, (TestDateInRegion_Create) -> () throws -> Void)] = [ ("testDateInRegion_DateBySetTime", testDateInRegion_DateBySetTime), ("testDateInRegion_DateBySet", testDateInRegion_DateBySet), ("testDateInRegion_RandomDatesInRange", testDateInRegion_RandomDatesInRange), ("testDateInRegion_RandomDatesBackToDays", testDateInRegion_RandomDatesBackToDays), ("testDateInRegion_EnumareDates", testDateInRegion_EnumareDates), ("testDateInRegion_oldestAndNewestAndSortsIn", testDateInRegion_oldestAndNewestAndSortsIn) ] } extension TestDateInRegion_Langs { static var allTests: [(String, (TestDateInRegion_Langs) -> () throws -> Void)] = [ ("testLanguages", testLanguages) ] } extension TestDateInRegion_Math { static var allTests: [(String, (TestDateInRegion_Math) -> () throws -> Void)] = [ ("testDateInRegion_DateTruncated", testDateInRegion_DateTruncated), ("testDateInRegion_Rounding", testDateInRegion_Rounding), ("testDateInRegion_MathOperations", testDateInRegion_MathOperations) ] } extension TestFormatters { static var allTests: [(String, (TestFormatters) -> () throws -> Void)] = [ ("testDotNETFormatter", testDotNETFormatter), ("testRSSFormatter", testRSSFormatter), ("testRSSAltFormatter", testRSSAltFormatter), ("testSQLFormatter", testSQLFormatter), ("testISOFormatter", testISOFormatter), ("testTZInISOParser", testTZInISOParser), ("testRSSAltLocale", testRSSAltLocale), ("testTimeInterval_Clock", testTimeInterval_Clock), ("testFormatterCustom", testFormatterCustom), ("testTimeInterval_FormatterUnits", testTimeInterval_FormatterUnits), ("testTimeInterval_Formatter", testTimeInterval_Formatter), ("testColloquialFormatter", testColloquialFormatter), ("testISOParser", testISOParser) ] } extension TestRegion { static var allTests: [(String, (TestRegion) -> () throws -> Void)] = [ ("testRegionInit", testRegionInit) ] } extension TestSwiftDate { static var allTests: [(String, (TestSwiftDate) -> () throws -> Void)] = [ ("testAutoFormats", testAutoFormats) ] } XCTMain([ testCase(TestDateInRegion.allTests), testCase(TestDateInRegion_Compare.allTests), testCase(TestDateInRegion_Components.allTests), testCase(TestDateInRegion_Create.allTests), testCase(TestDateInRegion_Langs.allTests), testCase(TestDateInRegion_Math.allTests), testCase(TestFormatters.allTests), testCase(TestRegion.allTests), testCase(TestSwiftDate.allTests) ]) ================================================ FILE: Tests/SwiftDateTests/TestDataStructures.swift ================================================ // // 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 SwiftDate import XCTest class TestDataStructures: XCTestCase { func test_Weekday() { // Circular operations XCTAssert(WeekDay.monday.subtract(days: 2) == .saturday, "Failed to use circular operation for weekday") XCTAssert(WeekDay.friday.add(days: 4) == .tuesday, "Failed to use circular operation for weekday") XCTAssert(WeekDay.monday.add(days: 0) == .monday, "Failed to use circular operation for weekday") XCTAssert(WeekDay.friday.subtract(days: 0) == .friday, "Failed to use circular operation for weekday") XCTAssert(WeekDay.friday.add(days: -1) == .thursday, "Failed to use circular operation for weekday") XCTAssert(WeekDay.friday.subtract(days: -1) == .saturday, "Failed to use circular operation for weekday") // Locale display name XCTAssert(WeekDay.monday.name(locale: Locales.italian).lowercased() == "lunedì", "Failed to get the weekday display name in locale") XCTAssert(WeekDay.sunday.name(locale: Locales.english).lowercased() == "sunday", "Failed to get the weekday display name in locale") XCTAssert(WeekDay.monday.name(locale: Locales.ukrainian).lowercased() == "понеділок", "Failed to get the month display name in locale") } func test_Month() { // Circular operations XCTAssert(Month.january.subtract(months: 2) == .november, "Failed to use circular operation for month") XCTAssert(Month.september.add(months: 5) == .february, "Failed to use circular operation for month") XCTAssert(Month.september.add(months: 0) == .september, "Failed to use circular operation for month") XCTAssert(Month.december.subtract(months: 0) == .december, "Failed to use circular operation for month") XCTAssert(Month.december.add(months: -1) == .november, "Failed to use circular operation for month") XCTAssert(Month.may.subtract(months: -1) == .june, "Failed to use circular operation for month") // Locale display name XCTAssert(Month.january.name(locale: Locales.italian).lowercased() == "gennaio", "Failed to get the month display name in locale") XCTAssert(Month.may.name(locale: Locales.english).lowercased() == "may", "Failed to get the month display name in locale") XCTAssert(Month.may.name(locale: Locales.french).lowercased() == "mai", "Failed to get the month display name in locale") // Number of days in month XCTAssert(Month.february.numberOfDays(year: 2016) == 29, "Failed to get a leap year") XCTAssert(Month.january.numberOfDays(year: 2017) == 31, "Failed to get a leap year") XCTAssert(Month.february.numberOfDays(year: 1924) == 29, "Failed to get a leap year") } func test_Year() { XCTAssert(Year(2016).isLeap() == true, "Failed to get a leap year") XCTAssert(Year(2020).isLeap() == true, "Failed to get a leap year") XCTAssert(Year(2024).isLeap() == true, "Failed to get a leap year") XCTAssert(Year(2028).isLeap() == true, "Failed to get a leap year") XCTAssert(Year(2096).isLeap() == true, "Failed to get a leap year") XCTAssert(Year(1924).isLeap() == true, "Failed to get a leap year") XCTAssert(Year(2067).isLeap() == false, "Failed to get a non leap year") XCTAssert(Year(1924).numberOfDays() == 366, "Failed to get the correct number of day in a year") XCTAssert(Year(1944).numberOfDays() == 366, "Failed to get the correct number of day in a year") XCTAssert(Year(2067).numberOfDays() == 365, "Failed to get the correct number of day in a year") } } ================================================ FILE: Tests/SwiftDateTests/TestDate.swift ================================================ // // TestDate.swift // SwiftDate-macOS Tests // // Created by Imthath M on 30/05/19. // Copyright © 2019 SwiftDate. All rights reserved. // import SwiftDate import XCTest class TestDate: XCTestCase { override func setUp() { // Put setup code here. This method is called before the invocation of each test method in the class. } override func tearDown() { // Put teardown code here. This method is called after the invocation of each test method in the class. } func testDifferencesBetweenDates() { let date = Date() let date2 = "2019-01-05".toDate()!.date let result = date.differences(in: [.hour, .day, .month], from: date2) print(result) } func testDifferenceBetweenDates() { let date = Date() let date2 = "2019-01-05".toDate()!.date let result = date.difference(in: .day, from: date2) print(result!) } func testPositionInRange() { let regionRome = Region(calendar: Calendars.gregorian, zone: Zones.europeRome, locale: Locales.italian) let regionLondon = Region(calendar: Calendars.gregorian, zone: Zones.europeLondon, locale: Locales.english) let dateFormat = "yyyy-MM-dd HH:mm:ss" let lowerBound = "2018-05-31 23:00:00".toDate(dateFormat, region: regionRome)!.date let upperBound = "2018-06-01 01:00:00".toDate(dateFormat, region: regionRome)!.date let testDate1 = "2018-06-01 00:30:00".toDate(dateFormat, region: regionRome)!.date XCTAssertEqual( testDate1.positionInRange(date: lowerBound, and: upperBound), 0.75) let testDate2 = "2018-05-31 22:30:00".toDate(dateFormat, region: regionLondon)!.date XCTAssertEqual( testDate2.positionInRange(date: lowerBound, and: upperBound), 0.25) let testDate3 = "2018-05-31 21:00:00".toDate(dateFormat, region: regionLondon)!.date XCTAssertNil( testDate3.positionInRange(date: lowerBound, and: upperBound)) let testDate4 = "2018-06-01 03:00:00".toDate(dateFormat, region: regionLondon)!.date XCTAssertNil( testDate4.positionInRange(date: lowerBound, and: upperBound)) } } ================================================ FILE: Tests/SwiftDateTests/TestDateInRegion+Compare.swift ================================================ // // 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 SwiftDate import XCTest class TestDateInRegion_Compare: XCTestCase { func testDateInRegion_compareCloseTo() { let regionRome = Region(calendar: Calendars.gregorian, zone: Zones.europeRome, locale: Locales.italian) let dateFormat = "yyyy-MM-dd HH:mm:ss" let refDate = DateInRegion("2015-01-01 00:00:00", format: dateFormat, region: regionRome)! let dateA = DateInRegion("2015-01-01 01:00:00", format: dateFormat, region: regionRome)! let dateB = DateInRegion("2014-12-31 22:30:00", format: dateFormat, region: regionRome)! let dateC = DateInRegion("2015-01-01 04:00:00", format: dateFormat, region: regionRome)! let failMsg = "Failed to evaluate if date is close to another with given interval" XCTAssert( (dateA.compareCloseTo(refDate, precision: 1.hours.timeInterval) == true), failMsg) XCTAssert( (dateB.compareCloseTo(refDate, precision: 1.hours.timeInterval) == false), failMsg) XCTAssert( (dateC.compareCloseTo(refDate, precision: 3.hours.timeInterval) == false), failMsg) XCTAssert( (dateC.compareCloseTo(refDate, precision: 5.hours.timeInterval) == true), failMsg) } func testDateInRegion_compare() { // isToday XCTAssert( DateInRegion().compare(.isToday), "Failed to evaluate isToday") XCTAssert( ((DateInRegion() - 2.days).compare(.isToday) == false), "Failed to evaluate isToday == false") // isTomorrow XCTAssert( (DateInRegion() + 1.days + 5.minutes).compare(.isTomorrow), "Failed to evaluate isTomorrow") XCTAssert( (DateInRegion().dateAt(.endOfDay).compare(.isTomorrow) == false), "Failed to evaluate isTomorrow") // isYesterday XCTAssert( (DateInRegion().dateAt(.startOfDay) - 1.days).compare(.isYesterday), "Failed to evaluate isYesterday") XCTAssert( ((DateInRegion().dateAt(.startOfDay)).compare(.isYesterday) == false), "Failed to evaluate isYesterday == false") // isThisWeek XCTAssert( DateInRegion().compare(.isThisWeek), "Failed to evaluate isThisWeek") XCTAssert( DateInRegion().dateAt(.startOfWeek).compare(.isThisWeek), "Failed to evaluate isThisWeek") // isNextWeek XCTAssert( (DateInRegion() + 7.days).compare(.isNextWeek), "Failed to evaluate isNextWeek") XCTAssert( (DateInRegion().dateAt(.endOfWeek) + 1.days).compare(.isNextWeek), "Failed to evaluate isNextWeek") // isLastWeek XCTAssert( (DateInRegion() - 7.days).compare(.isLastWeek), "Failed to evaluate isLastWeek") XCTAssert( (DateInRegion().dateAt(.startOfWeek) - 1.days).compare(.isLastWeek), "Failed to evaluate isLastWeek") // isSameWeek let regionRome = Region(calendar: Calendars.gregorian, zone: Zones.europeRome, locale: Locales.italian) let dateFormat = "yyyy-MM-dd HH:mm:ss" let dateA = DateInRegion("2018-06-19 01:00:00", format: dateFormat, region: regionRome)! let dateB = DateInRegion("2018-06-23 22:30:00", format: dateFormat, region: regionRome)! let dateC = DateInRegion("2018-06-17 23:59:00", format: dateFormat, region: regionRome)! XCTAssert( dateA.compare(.isSameWeek(dateB)), "Failed to evaluate isSameWeek") XCTAssert( dateA.compare(.isSameWeek(dateC)) == false, "Failed to evaluate isSameWeek == false") // isThisMonth XCTAssert( (DateInRegion().dateAt(.startOfMonth) + 6.days).compare(.isThisMonth), "Failed to evaluate isThisMonth") XCTAssert( DateInRegion().compare(.isThisMonth), "Failed to evaluate isThisMonth") XCTAssert( (DateInRegion().dateAt(.startOfMonth) - 1.days).compare(.isThisMonth) == false, "Failed to evaluate isThisMonth == false") // isNextMonth XCTAssert( (DateInRegion().dateAt(.startOfMonth) - 1.days).compare(.isNextMonth) == false, "Failed to evaluate isNextMonth == false") XCTAssert( (DateInRegion().dateAt(.endOfMonth) + 5.days).compare(.isNextMonth), "Failed to evaluate isNextMonth") XCTAssert( DateInRegion().compare(.isNextMonth) == false, "Failed to evaluate isNextMonth == false") // isLastMonth XCTAssert( (DateInRegion().dateAt(.startOfMonth) - 1.days).compare(.isLastMonth), "Failed to evaluate isLastMonth") XCTAssert( (DateInRegion().dateAt(.endOfMonth) + 5.days).compare(.isLastMonth) == false, "Failed to evaluate isLastMonth == false") XCTAssert( DateInRegion().compare(.isLastMonth) == false, "Failed to evaluate isLastMonth == false") // isSameMonth let dateA1 = DateInRegion("2018-06-19 01:00:00", format: dateFormat, region: regionRome)! let dateB1 = DateInRegion("2018-06-23 22:30:00", format: dateFormat, region: regionRome)! let dateC1 = DateInRegion("2018-07-01 00:00:00", format: dateFormat, region: regionRome)! XCTAssert( dateA1.compare(.isSameMonth(dateB1)), "Failed to evaluate isSameMonth") XCTAssert( dateB1.compare(.isSameMonth(dateC1)) == false, "Failed to evaluate isSameMonth == false") // prevWeek/nextWeek XCTAssert( dateA1.dateAt(.prevWeek).toISO() == "2018-06-11T00:00:00+02:00", "Failed to evaluate prevWeek") XCTAssert( dateA1.dateAt(.nextWeek).toISO() == "2018-06-25T00:00:00+02:00", "Failed to evaluate prevWeek") // isThisYear XCTAssert( DateInRegion().compare(.isThisYear), "Failed to evaluate isThisYear") XCTAssert( (DateInRegion() + 1.years).compare(.isThisYear) == false, "Failed to evaluate isThisYear == false") XCTAssert( (DateInRegion() - 1.years).compare(.isThisYear) == false, "Failed to evaluate isThisYear == false") // isSameYear let dateA2 = DateInRegion("2018-01-01 00:00:00", format: dateFormat, region: regionRome)! let dateB2 = DateInRegion("2017-12-31 23:59:59", format: dateFormat, region: regionRome)! let dateC2 = DateInRegion("2018-12-31 23:59:59", format: dateFormat, region: regionRome)! XCTAssert( dateA2.compare(.isSameYear(dateB2)) == false, "Failed to evaluate isSameYear == false") XCTAssert( dateA2.compare(.isSameYear(dateC2)), "Failed to evaluate isSameYear") XCTAssert( dateB2.compare(.isSameYear(dateC2)) == false, "Failed to evaluate isSameYear == false") // isInTheFuture XCTAssert( (DateInRegion() + 1.seconds).compare(.isInTheFuture), "Failed to evaluate isInTheFuture") XCTAssert( (DateInRegion() - 1.seconds).compare(.isInTheFuture) == false, "Failed to evaluate isInTheFuture == false") XCTAssert( (DateInRegion() + 1.years).compare(.isInTheFuture), "Failed to evaluate isInTheFuture") // isInThePast XCTAssert( (DateInRegion() - 1.seconds).compare(.isInThePast), "Failed to evaluate isInThePast") XCTAssert( (DateInRegion() + 1.seconds).compare(.isInThePast) == false, "Failed to evaluate isInThePast == false") XCTAssert( (DateInRegion() - 1.years).compare(.isInThePast), "Failed to evaluate isInThePast") // isEarlier let dateA3 = DateInRegion("2018-01-01 00:00:00", format: dateFormat, region: regionRome)! let dateB3 = DateInRegion("2017-12-31 23:59:59", format: dateFormat, region: regionRome)! XCTAssert( dateA3.compare(.isEarlier(than: dateB3)) == false, "Failed to evaluate isEarlier == false") XCTAssert( dateB3.compare(.isEarlier(than: dateA3)), "Failed to evaluate isEarlier") // isLater XCTAssert( dateA3.compare(.isLater(than: dateB3)), "Failed to evaluate isLater") XCTAssert( dateB3.compare(.isLater(than: dateA3)) == false, "Failed to evaluate isLater == false") // isWeekday XCTAssert( DateInRegion().dateAt(.endOfWeek).compare(.isWeekday) == false, "Failed to evaluate isWeekday == false") XCTAssert( (DateInRegion().dateAt(.startOfWeek) + 1.days).compare(.isWeekday), "Failed to evaluate isWeekday") // isWeekend XCTAssert( DateInRegion().dateAt(.endOfWeek).compare(.isWeekend), "Failed to evaluate isWeekend") XCTAssert( (DateInRegion().dateAt(.startOfWeek) + 1.days).compare(.isWeekend) == false, "Failed to evaluate isWeekend == false") // isInDST // Region rome: // 25 Mar 2018 - Daylight Saving Time Started // 28 Oct 2018 - Daylight Saving Time Ends XCTAssert( DateInRegion().dateAt(.endOfWeek).compare(.isWeekend), "Failed to evaluate isWeekend") let dateA4 = DateInRegion("2018-03-26 00:00:00", format: dateFormat, region: regionRome)! let dateB4 = DateInRegion("2018-06-01 00:00:00", format: dateFormat, region: regionRome)! let dateC4 = DateInRegion("2018-10-29 00:00:00", format: dateFormat, region: regionRome)! let dateD4 = DateInRegion("2018-12-01 00:00:00", format: dateFormat, region: regionRome)! XCTAssert( dateA4.compare(.isInDST), "Failed to evaluate isInDST") XCTAssert( dateB4.compare(.isInDST), "Failed to evaluate isInDST") XCTAssert( dateC4.compare(.isInDST) == false, "Failed to evaluate isInDST == false") XCTAssert( dateD4.compare(.isInDST) == false, "Failed to evaluate isInDST == false") // isMorning let dateA5 = DateInRegion("2018-03-26 05:00:00", format: dateFormat, region: regionRome)! let dateA6 = DateInRegion("2018-03-26 10:00:00", format: dateFormat, region: regionRome)! let dateA7 = DateInRegion("2018-03-26 11:59:00", format: dateFormat, region: regionRome)! let dateA8 = DateInRegion("2018-03-26 13:59:00", format: dateFormat, region: regionRome)! let dateA9 = DateInRegion("2018-03-26 17:59:00", format: dateFormat, region: regionRome)! let dateA10 = DateInRegion("2018-03-26 23:00:00", format: dateFormat, region: regionRome)! let dateA11 = DateInRegion("2018-03-26 04:00:00", format: dateFormat, region: regionRome)! XCTAssert( dateA5.compare(.isMorning), "Failed to evaluate isMorning") XCTAssert( dateA6.compare(.isMorning), "Failed to evaluate isMorning") XCTAssert( dateA7.compare(.isMorning), "Failed to evaluate isMorning") XCTAssert( dateA8.compare(.isMorning) == false, "Failed to evaluate isMorning == false") // isAfternoon XCTAssert( dateA5.compare(.isAfternoon) == false, "Failed to evaluate isAfternoon == false") XCTAssert( dateA8.compare(.isAfternoon), "Failed to evaluate isAfternoon") XCTAssert( dateA9.compare(.isAfternoon) == false, "Failed to evaluate isAfternoon == false") // isEvening XCTAssert( dateA9.compare(.isEvening), "Failed to evaluate isEvening") XCTAssert( dateA10.compare(.isEvening) == false, "Failed to evaluate isEvening == false") // isNight XCTAssert( dateA5.compare(.isNight) == false, "Failed to evaluate isNight == false") XCTAssert( dateA11.compare(.isNight), "Failed to evaluate isNight") XCTAssert( dateA10.compare(.isNight), "Failed to evaluate isNight") } func testDateInRegion_compareGranularity() { let regionRome = Region(calendar: Calendars.gregorian, zone: Zones.europeRome, locale: Locales.italian) let dateFormat = "yyyy-MM-dd HH:mm:ss" let date1 = DateInRegion("2018-06-19 01:00:00", format: dateFormat, region: regionRome)! let date2 = DateInRegion("2018-06-19 20:00:00", format: dateFormat, region: regionRome)! let date3 = DateInRegion("2018-06-21 20:00:00", format: dateFormat, region: regionRome)! // Same day/month/year granularity XCTAssert( date1.compare(toDate: date2, granularity: .day) == .orderedSame, "Failed to compare with .day granularity") XCTAssert( date1.compare(toDate: date2, granularity: .month) == .orderedSame, "Failed to compare with .month granularity") XCTAssert( date1.compare(toDate: date2, granularity: .year) == .orderedSame, "Failed to compare with .year granularity") // Different day XCTAssert( date2.compare(toDate: date3, granularity: .day) == .orderedAscending, "Failed to compare with .day granularity") XCTAssert( date2.compare(toDate: date3, granularity: .month) == .orderedSame, "Failed to compare with .month granularity") XCTAssert( date2.compare(toDate: date3, granularity: .hour) == .orderedAscending, "Failed to compare with .hour granularity") } func testDateInRegion_isBeforeDate() { let regionRome = Region(calendar: Calendars.gregorian, zone: Zones.europeRome, locale: Locales.italian) let regionLondon = Region(calendar: Calendars.gregorian, zone: Zones.europeLondon, locale: Locales.english) let dateFormat = "yyyy-MM-dd HH:mm:ss" let date1 = DateInRegion("2018-06-19 01:00:00", format: dateFormat, region: regionRome)! let date2 = DateInRegion("2018-06-19 01:00:00", format: dateFormat, region: regionLondon)! let date3 = DateInRegion("2018-06-21 01:10:00", format: dateFormat, region: regionRome)! XCTAssert( date1.isBeforeDate(date2, granularity: .hour), "Failed to isBeforeDate() - .hour") XCTAssert( date1.isBeforeDate(date3, granularity: .hour), "Failed to isBeforeDate() - .hour") XCTAssert( date1.isBeforeDate(date2, granularity: .day) == false, "Failed to isBeforeDate() - .day == false") } func testDateInRegion_isAfterDate() { let regionRome = Region(calendar: Calendars.gregorian, zone: Zones.europeRome, locale: Locales.italian) let regionLondon = Region(calendar: Calendars.gregorian, zone: Zones.europeLondon, locale: Locales.english) let dateFormat = "yyyy-MM-dd HH:mm:ss" let date1 = DateInRegion("2018-06-19 01:00:00", format: dateFormat, region: regionRome)! let date2 = DateInRegion("2018-06-19 01:00:00", format: dateFormat, region: regionLondon)! let date3 = DateInRegion("2018-06-21 01:10:00", format: dateFormat, region: regionRome)! let date4 = DateInRegion("2018-06-30 00:00:00", format: dateFormat, region: regionRome)! let date5 = DateInRegion("2018-06-30 00:00:00", format: dateFormat, region: regionLondon)! XCTAssert( date3.isAfterDate(date1, granularity: .day), "Failed to isAfterDate() - .day") XCTAssert( date1.isAfterDate(date3, granularity: .month) == false, "Failed to isAfterDate() - .month == false") XCTAssert( date4.isAfterDate(date5, granularity: .month) == false, "Failed to isAfterDate() - .day == false") XCTAssert( date2.isAfterDate(date1, granularity: .year) == false, "Failed to isAfterDate() - .year == false") } func testDateInRegion_positionInRange() { let regionRome = Region(calendar: Calendars.gregorian, zone: Zones.europeRome, locale: Locales.italian) let regionLondon = Region(calendar: Calendars.gregorian, zone: Zones.europeLondon, locale: Locales.english) let dateFormat = "yyyy-MM-dd HH:mm:ss" let lowerBound = DateInRegion("2018-05-31 23:00:00", format: dateFormat, region: regionRome)! let upperBound = DateInRegion("2018-06-01 01:00:00", format: dateFormat, region: regionRome)! let testDate1 = DateInRegion("2018-06-01 00:30:00", format: dateFormat, region: regionRome)! XCTAssertEqual( testDate1.positionInRange(date: lowerBound, and: upperBound), 0.75) let testDate2 = DateInRegion("2018-05-31 22:30:00", format: dateFormat, region: regionLondon)! XCTAssertEqual( testDate2.positionInRange(date: lowerBound, and: upperBound), 0.25) let testDate3 = DateInRegion("2018-05-31 21:00:00", format: dateFormat, region: regionLondon)! XCTAssertNil( testDate3.positionInRange(date: lowerBound, and: upperBound)) let testDate4 = DateInRegion("2018-06-01 03:00:00", format: dateFormat, region: regionLondon)! XCTAssertNil( testDate4.positionInRange(date: lowerBound, and: upperBound)) } func testDateInRegion_isInRange() { let regionRome = Region(calendar: Calendars.gregorian, zone: Zones.europeRome, locale: Locales.italian) let regionLondon = Region(calendar: Calendars.gregorian, zone: Zones.europeLondon, locale: Locales.english) let dateFormat = "yyyy-MM-dd HH:mm:ss" let lowerBound = DateInRegion("2018-05-31 23:00:00", format: dateFormat, region: regionRome)! let upperBound = DateInRegion("2018-06-01 01:00:00", format: dateFormat, region: regionRome)! let testDate1 = DateInRegion("2018-06-01 00:02:00", format: dateFormat, region: regionRome)! XCTAssert( testDate1.isInRange(date: lowerBound, and: upperBound, orEqual: true, granularity: .hour), "Failed to isInRange .hour") let testDate2 = DateInRegion("2018-06-01 00:02:00", format: dateFormat, region: regionLondon)! XCTAssert( testDate2.isInRange(date: lowerBound, and: upperBound, orEqual: true, granularity: .hour), "Failed to isInRange .hour") let testDate3 = DateInRegion("2018-06-01 01:00:01", format: dateFormat, region: regionLondon)! XCTAssert( testDate3.isInRange(date: lowerBound, and: upperBound, orEqual: true, granularity: .hour) == false, "Failed to isInRange .hour == false") } func testDateInRegion_earlierAndLaterDate() { let regionRome = Region(calendar: Calendars.gregorian, zone: Zones.europeRome, locale: Locales.italian) let regionLondon = Region(calendar: Calendars.gregorian, zone: Zones.europeLondon, locale: Locales.english) let dateFormat = "yyyy-MM-dd HH:mm:ss" let date1 = DateInRegion("2018-05-31 23:00:00", format: dateFormat, region: regionRome)! let date2 = DateInRegion("2018-06-01 00:00:00", format: dateFormat, region: regionRome)! let date3 = DateInRegion("2018-06-01 01:00:00", format: dateFormat, region: regionLondon)! // earlierDate() XCTAssert( (date1.earlierDate(date2) == date1), "Failed to get .earlierDate()") XCTAssert( (date1.earlierDate(date3) == date1), "Failed to get .earlierDate()") XCTAssert( (date1.date.earlierDate(date2.date) == date1.date), "Failed to get .earlierDate()") XCTAssert( (date1.date.earlierDate(date3.date) == date1.date), "Failed to get .earlierDate()") // laterDate() XCTAssert( (date1.laterDate(date2) == date2), "Failed to get .laterDate()") XCTAssert( (date1.laterDate(date3) == date3), "Failed to get .laterDate()") XCTAssert( (date1.date.laterDate(date2.date) == date2.date), "Failed to get .laterDate()") XCTAssert( (date1.date.laterDate(date3.date) == date3.date), "Failed to get .laterDate()") } func testDateInRegion_compareMath() { let regionRome = Region(calendar: Calendars.gregorian, zone: Zones.europeRome, locale: Locales.italian) let regionNY = Region(calendar: Calendars.gregorian, zone: Zones.americaNewYork, locale: Locales.english) let dateFormat = "yyyy-MM-dd HH:mm:ss" let date1 = DateInRegion("2018-01-01 00:10:00", format: dateFormat, region: regionRome)! let date2 = DateInRegion("2017-12-31 23:00:00", format: dateFormat, region: regionRome)! let date3 = DateInRegion("2018-01-01 00:20:00", format: dateFormat, region: regionNY)! XCTAssert( (date1 >= date2), "Failed to math compare two dates") XCTAssert( (date1 > date2), "Failed to math compare two dates") XCTAssert( (date3 > date1), "Failed to math compare two dates") XCTAssert( (date3 == date3), "Failed to math compare two dates") XCTAssert( (date3 >= date3), "Failed to math compare two dates") XCTAssert( (date3 <= date3), "Failed to math compare two dates") XCTAssert( (date1 <= date3), "Failed to math compare two dates") } func testDateInRange_GranuralityTest() { let startTime = Date(timeIntervalSince1970: 1_538_344_800.0) // 2018-09-30 22:00:00 +0000 let endTime = Date(timeIntervalSince1970: 1_540_940_400.0 + (60 * 60 * 3)) // 2018-10-31 02:00:00 +0000 let checkStart = Date(timeIntervalSince1970: 1_540_976_400.0) // 2018-10-31 09:00:00 +0000 let isInside = checkStart.isInRange(date: startTime, and: endTime, orEqual: true, granularity: .day) // should return false even if its true XCTAssert( (isInside == true), "Failed to compare date with granularity") } } ================================================ FILE: Tests/SwiftDateTests/TestDateInRegion+Components.swift ================================================ // // 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 SwiftDate import XCTest class TestDateInRegion_Components: XCTestCase { func testDateInRegion_Components() { let regionLondon = Region(calendar: Calendars.gregorian, zone: Zones.europeLondon, locale: Locales.italian) let dateFormat = "yyyy-MM-dd HH:mm:ss" // TEST #1: Date In Italian let dateA = DateInRegion("2018-02-05 23:14:45", format: dateFormat, region: regionLondon)! XCTValidateDateComponents(date: dateA, expec: ExpectedDateComponents { $0.year = 2018 $0.month = 2 $0.day = 5 $0.hour = 23 $0.minute = 14 $0.second = 45 $0.monthNameDefault = "febbraio" $0.monthNameDefaultStd = "febbraio" $0.monthNameShort = "feb" $0.monthNameStandaloneShort = "feb" $0.msInDay = 83_686_000 $0.weekday = 2 $0.weekdayNameShort = "lun" $0.weekdayNameVeryShort = "L" $0.weekOfMonth = 2 $0.eraNameShort = "a.C." $0.weekdayOrdinal = 1 $0.nearestHour = 23 $0.yearForWeekOfYear = 2018 $0.quarter = 1 }) // TEST #1: Date In French let regionParis = Region(calendar: Calendars.gregorian, zone: Zones.europeParis, locale: Locales.french) let dateB = DateInRegion("2018-02-05 23:14:45", format: dateFormat, region: regionParis)! XCTValidateDateComponents(date: dateB, expec: ExpectedDateComponents { $0.monthNameDefault = "février" $0.monthNameShort = "févr." $0.eraNameShort = "av. J.-C." $0.weekdayNameDefault = "lundi" }) // TEST #3: Other components XCTAssert( (dateB.region == regionParis), "Failed to assign correct region to date") XCTAssert( (dateB.calendar.identifier == regionParis.calendar.identifier), "Failed to assign correct region's calendar to date") XCTAssert( (dateB.quarterName(.default) == "1er trimestre"), "Failed to get quarterName in default") XCTAssert( (dateB.quarterName(.short) == "T1"), "Failed to get quarterName in short") XCTAssert( (dateB.quarterName(.default, locale: Locales.italian) == "1º trimestre"), "Failed to get quarterName with overwrite of locale") } func testDateInRegion_isLeapMonth() { let regionRome = Region(calendar: Calendars.gregorian, zone: Zones.europeRome, locale: Locales.italian) let dateFormat = "yyyy-MM-dd HH:mm:ss" let dateA = DateInRegion("2018-02-05 00:00:00", format: dateFormat, region: regionRome)! let dateB = DateInRegion("2016-02-22 00:00:00", format: dateFormat, region: regionRome)! let dateC = DateInRegion("2017-12-10 00:00:00", format: dateFormat, region: regionRome)! XCTAssert( dateA.isLeapMonth == false, "Failed to evaluate is date isLeapMonth == false") XCTAssert( dateC.isLeapMonth == false, "Failed to evaluate is date isLeapMonth == false") XCTAssert( dateB.isLeapMonth, "Failed to evaluate is date isLeapMonth") } func testDateInRegion_dateBySet() { let originalDate = "2018-10-10T12:02:16.024".toISODate() let newDate = originalDate?.dateBySet(hour: nil, min: nil, secs: nil, ms: 7) XCTAssert( newDate?.toISO([.withInternetDateTimeExtended]) == "2018-10-10T12:02:16.007Z", "Failed to set milliseconds") } func testDateInRegion_isLeapYear() { let regionRome = Region(calendar: Calendars.gregorian, zone: Zones.europeRome, locale: Locales.italian) let dateFormat = "yyyy-MM-dd HH:mm:ss" let dateA = DateInRegion("2018-04-05 00:00:00", format: dateFormat, region: regionRome)! let dateB = DateInRegion("2016-07-22 00:00:00", format: dateFormat, region: regionRome)! let dateC = DateInRegion("2017-12-10 00:00:00", format: dateFormat, region: regionRome)! XCTAssert( dateA.isLeapYear == false, "Failed to evaluate is date isLeapYear == false") XCTAssert( dateC.isLeapYear == false, "Failed to evaluate is date isLeapYear == false") XCTAssert( dateB.isLeapYear, "Failed to evaluate is date isLeapYear") } func testDateInRegion_julianDayAndModifiedJulianDay() { // swiftlint:disable nesting struct ExpectedJulian { var dateISO: String var julianDay: Double var modifiedJulianDay: Double } let dates = [ ExpectedJulian(dateISO: "2017-12-22T00:06:18+01:00", julianDay: 2_458_109.462_708_333_5, modifiedJulianDay: 58108.962_708_333_51), ExpectedJulian(dateISO: "2018-06-02T02:14:45+02:00", julianDay: 2_458_271.510_243_055_4, modifiedJulianDay: 58271.010_243_055_41), ExpectedJulian(dateISO: "2018-04-04T13:31:12+02:00", julianDay: 2_458_212.98, modifiedJulianDay: 58212.479_999_999_98), ExpectedJulian(dateISO: "2018-03-18T10:11:10+01:00", julianDay: 2_458_195.882_754_629_5, modifiedJulianDay: 58195.382_754_629_48), ExpectedJulian(dateISO: "2018-03-10T18:03:22+01:00", julianDay: 2_458_188.210_671_296_3, modifiedJulianDay: 58187.710_671_296_34), ExpectedJulian(dateISO: "2017-07-14T06:33:47+02:00", julianDay: 2_457_948.690_127_315, modifiedJulianDay: 57948.190_127_315), ExpectedJulian(dateISO: "2018-02-14T16:51:14+01:00", julianDay: 2_458_164.160_578_703_5, modifiedJulianDay: 58163.660_578_703_51), ExpectedJulian(dateISO: "2017-08-15T17:41:44+02:00", julianDay: 2_457_981.153_981_481_7, modifiedJulianDay: 57980.653_981_481_68), ExpectedJulian(dateISO: "2018-03-04T09:54:54+01:00", julianDay: 2_458_181.871_458_333, modifiedJulianDay: 58181.371_458_332_986), ExpectedJulian(dateISO: "2017-09-23T08:18:15+02:00", julianDay: 2_458_019.762_673_611, modifiedJulianDay: 58019.262_673_610_82), ExpectedJulian(dateISO: "2017-12-10T10:29:42+01:00", julianDay: 2_458_097.895625, modifiedJulianDay: 58097.395_624_999_89), ExpectedJulian(dateISO: "2017-11-11T02:49:41+01:00", julianDay: 2_458_068.576_168_981_4, modifiedJulianDay: 58068.076_168_981_38), ExpectedJulian(dateISO: "2017-07-06T04:05:39+02:00", julianDay: 2_457_940.587_256_944_7, modifiedJulianDay: 57940.087_256_944_74), ExpectedJulian(dateISO: "2017-12-02T00:23:52+01:00", julianDay: 2_458_089.474_907_407_5, modifiedJulianDay: 58088.974_907_407_54), ExpectedJulian(dateISO: "2017-11-14T17:59:46+01:00", julianDay: 2_458_072.208_171_296_4, modifiedJulianDay: 58071.708_171_296_4), ExpectedJulian(dateISO: "2018-03-02T10:53:52+01:00", julianDay: 2_458_179.912_407_407_5, modifiedJulianDay: 58179.412_407_407_54), ExpectedJulian(dateISO: "2018-04-14T23:46:35+02:00", julianDay: 2_458_223.407_349_537, modifiedJulianDay: 58222.907_349_537_13), ExpectedJulian(dateISO: "2018-04-28T07:25:22+02:00", julianDay: 2_458_236.725_949_074, modifiedJulianDay: 58236.225_949_074_14), ExpectedJulian(dateISO: "2018-01-06T14:36:53+01:00", julianDay: 2_458_125.067_280_092_3, modifiedJulianDay: 58124.567_280_092_28), ExpectedJulian(dateISO: "2017-09-24T19:58:19+02:00", julianDay: 2_458_021.248_831_019, modifiedJulianDay: 58020.748_831_018_806), ExpectedJulian(dateISO: "2017-12-17T21:12:31+01:00", julianDay: 2_458_105.342_025_463, modifiedJulianDay: 58104.842_025_463_004), ExpectedJulian(dateISO: "2018-05-04T02:28:42+02:00", julianDay: 2_458_242.519_930_555_5, modifiedJulianDay: 58242.019_930_555_485), ExpectedJulian(dateISO: "2018-01-21T18:41:34+01:00", julianDay: 2_458_140.237_199_074, modifiedJulianDay: 58139.737_199_074_12), ExpectedJulian(dateISO: "2018-04-05T02:36:54+02:00", julianDay: 2_458_213.525625, modifiedJulianDay: 58213.025_624_999_78), ExpectedJulian(dateISO: "2018-02-07T13:35:16+01:00", julianDay: 2_458_157.024_490_741, modifiedJulianDay: 58156.524_490_741_08), ExpectedJulian(dateISO: "2017-11-30T00:58:20+01:00", julianDay: 2_458_087.498_842_592_4, modifiedJulianDay: 58086.998_842_592_35), ExpectedJulian(dateISO: "2018-04-10T07:10:34+02:00", julianDay: 2_458_218.715_671_296, modifiedJulianDay: 58218.215_671_296_23), ExpectedJulian(dateISO: "2017-08-11T09:36:56+02:00", julianDay: 2_457_976.817_314_815, modifiedJulianDay: 57976.317_314_814_776), ExpectedJulian(dateISO: "2018-04-28T12:30:18+02:00", julianDay: 2_458_236.937_708_333, modifiedJulianDay: 58236.437_708_333_135), ExpectedJulian(dateISO: "2017-09-17T11:59:29+02:00", julianDay: 2_458_013.916_307_870_3, modifiedJulianDay: 58013.416_307_870_3) ] dates.forEach { let date = $0.dateISO.toISODate()! guard date.julianDay == $0.julianDay else { XCTFail("Failed to evaluate julianDay of '\($0.dateISO)'. Got '\(date.julianDay)', expected '\($0.julianDay)'") return } guard date.modifiedJulianDay == $0.modifiedJulianDay else { XCTFail("Failed to evaluate modifiedJulianDay of '\($0.dateISO)'. Got '\(date.modifiedJulianDay)', expected '\($0.modifiedJulianDay)'") return } } } @available(iOS 9.0, macOS 10.11, *) func test_ordinalDay() { let newYork = Region(calendar: Calendars.gregorian, zone: Zones.americaNewYork, locale: Locales.englishUnitedStates) let localDate = DateInRegion(components: { $0.year = 2002 $0.month = 3 $0.day = 1 $0.hour = 5 $0.minute = 30 }, region: newYork)! XCTAssert(localDate.ordinalDay == "1st", "Failed to get the correct value of the ordinalDay for a date") let localDate2 = DateInRegion(components: { $0.year = 2002 $0.month = 3 $0.day = 2 $0.hour = 5 $0.minute = 30 }, region: newYork)! XCTAssert(localDate2.ordinalDay == "2nd", "Failed to get the correct value of the ordinalDay for a date") let localDate3 = DateInRegion(components: { $0.year = 2002 $0.month = 3 $0.day = 3 $0.hour = 5 $0.minute = 30 }, region: newYork)! XCTAssert(localDate3.ordinalDay == "3rd", "Failed to get the correct value of the ordinalDay for a date") let localDate4 = DateInRegion(components: { $0.year = 2002 $0.month = 3 $0.day = 4 $0.hour = 5 $0.minute = 30 }, region: newYork)! XCTAssert(localDate4.ordinalDay == "4th", "Failed to get the correct value of the ordinalDay for a date") let regionRome = Region(calendar: Calendars.gregorian, zone: Zones.europeRome, locale: Locales.italian) let localDate5 = DateInRegion(components: { $0.year = 2002 $0.month = 3 $0.day = 2 $0.hour = 5 $0.minute = 30 }, region: regionRome)! XCTAssert(localDate5.ordinalDay == "2º", "Failed to get the correct value of the ordinalDay for a date") } func testDateInRegion_ISOFormatterAlt() { let regionRome = Region(calendar: Calendars.gregorian, zone: Zones.europeRome, locale: Locales.italian) let dateFormat = "yyyy-MM-dd HH:mm:ss" let date = DateInRegion("2017-07-22 00:00:00", format: dateFormat, region: regionRome)! XCTAssert( date.toISO() == "2017-07-22T00:00:00+02:00", "Failed to format ISO") XCTAssert( date.toISO([.withFullDate]) == "2017-07-22", "Failed to format ISO") XCTAssert( date.toISO([.withFullDate, .withFullTime, .withDashSeparatorInDate, .withSpaceBetweenDateAndTime]) == "2017-07-22 00:00:00+02:00", "Failed to format ISO") } func testDateInRegion_getIntervalForComponentBetweenDates() { let regionRome = Region(calendar: Calendars.gregorian, zone: Zones.europeRome, locale: Locales.italian) let dateFormat = "yyyy-MM-dd HH:mm:ss" let dateA = DateInRegion("2017-07-22 00:00:00", format: dateFormat, region: regionRome)! let dateB = DateInRegion("2017-07-23 12:00:00", format: dateFormat, region: regionRome)! let dateC = DateInRegion("2017-09-23 12:00:00", format: dateFormat, region: regionRome)! let dateD = DateInRegion("2017-09-23 12:54:00", format: dateFormat, region: regionRome)! XCTAssert( (dateA.getInterval(toDate: dateB, component: .hour) == 36), "Failed to evaluate is hours interval") XCTAssert( (dateA.getInterval(toDate: dateB, component: .day) == 2), "Failed to evaluate is days interval") // 1 days, 12 hours XCTAssert( (dateB.getInterval(toDate: dateC, component: .month) == 2), "Failed to evaluate is months interval") XCTAssert( (dateB.getInterval(toDate: dateC, component: .day) == 62), "Failed to evaluate is days interval") XCTAssert( (dateB.getInterval(toDate: dateC, component: .year) == 0), "Failed to evaluate is years interval") XCTAssert( (dateB.getInterval(toDate: dateC, component: .weekOfYear) == 9), "Failed to evaluate is weeksOfYear interval") XCTAssert( (dateC.getInterval(toDate: dateD, component: .minute) == 54), "Failed to evaluate is minutes interval") XCTAssert( (dateC.getInterval(toDate: dateD, component: .hour) == 0), "Failed to evaluate is hours interval") XCTAssert( (dateA.getInterval(toDate: dateD, component: .minute) == 91494), "Failed to evaluate is minutes interval") } func testDateInRegion_timeIntervalSince() { let regionRome = Region(calendar: Calendars.gregorian, zone: Zones.europeRome, locale: Locales.italian) let regionLondon = Region(calendar: Calendars.gregorian, zone: Zones.europeLondon, locale: Locales.english) let dateFormat = "yyyy-MM-dd HH:mm:ss" let dateA = DateInRegion("2017-07-22 00:00:00", format: dateFormat, region: regionRome)! let dateB = DateInRegion("2017-07-22 00:00:00", format: dateFormat, region: regionRome)! let dateC = DateInRegion("2017-07-23 13:20:15", format: dateFormat, region: regionLondon)! let dateD = DateInRegion("2017-07-23 13:20:20", format: dateFormat, region: regionLondon)! XCTAssert( dateA.timeIntervalSince(dateC) == -138015.0, "Failed to evaluate is minutes interval") XCTAssert( dateA.timeIntervalSince(dateB) == 0, "Failed to evaluate is minutes interval") XCTAssert( dateC.timeIntervalSince(dateD) == -5, "Failed to evaluate is minutes interval") } func testQuarter() { let regionLondon = Region(calendar: Calendars.gregorian, zone: Zones.europeLondon, locale: Locales.english) let dateFormat = "yyyy-MM-dd HH:mm:ss" let dateA = DateInRegion("2018-02-05 23:14:45", format: dateFormat, region: regionLondon)! let dateB = DateInRegion("2018-09-05 23:14:45", format: dateFormat, region: regionLondon)! let dateC = DateInRegion("2018-12-05 23:14:45", format: dateFormat, region: regionLondon)! XCTAssert( dateA.quarter == 1, "Failed to evaluate quarter property") XCTAssert( dateB.quarter == 3, "Failed to evaluate quarter property") XCTAssert( dateC.quarter == 4, "Failed to evaluate quarter property") } func testAbsoluteDateISOFormatting() { let now = DateInRegion() let iso8601_string = now.toISO([.withInternetDateTime]) let absoluteDate = now.date let absoluteDate_iso8601_string = absoluteDate.toISO([.withInternetDateTime]) XCTAssert( absoluteDate_iso8601_string == iso8601_string, "Failed respect the absolute ISO date") } func testComparingTimeUnitsWithDateComponents() { SwiftDate.defaultRegion = .local let now = Date() XCTAssert((now.addingTimeInterval(3600) - now).in(.hour) == 1, "Failed to compare date") XCTAssert((now.addingTimeInterval(3600) - now) == 1.hours, "Failed to compare date") } } ================================================ FILE: Tests/SwiftDateTests/TestDateInRegion+Create.swift ================================================ // // 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 SwiftDate import XCTest public func randomNumber(inRange range: ClosedRange = 1...6) -> T { let length = Int64(range.upperBound - range.lowerBound + 1) let value = Int64.random(in: 0..? switch componentToAlter { case .second, .minute: range = 0...50 case .hour: range = 0...20 case .day: range = 1...28 case .month: range = 1...12 case .year: range = 2000...2050 default: range = nil } if let range = range { let value = randomNumber(inRange: range) components[componentToAlter] = value } } } func verify(date: DateInRegion) { components.keys.forEach { if let value = date.dateComponents.value(for: $0), let expected = components[$0] as? Int { if value != expected { XCTFail("Failed to set value of component \($0). Got \(value), expected \(expected)") return } } } } } for _ in 0..<50 { let randomDate = "2041-05-18T18:00:25Z".toISODate()! //DateInRegion.randomDate(region: regionRome) let alterComponents = ExpectDateBySet() if let alteredDate = randomDate.dateBySet(alterComponents.components) { alterComponents.verify(date: alteredDate) } } } func testDateInRegion_RandomDatesInRange() { // Random dates let regionNorw = Region(calendar: Calendars.gregorian, zone: Zones.europeOslo, locale: Locales.norwegianBokmlSvalbardJanMayen) let randomDateAny = DateInRegion.randomDate(region: regionNorw) guard randomDateAny.region == regionNorw else { XCTFail("Failed to generate a random date in region") return } // Random dates in range let regionRome = Region(calendar: Calendars.gregorian, zone: Zones.europeRome, locale: Locales.italian) let upperLimit = DateInRegion("2015-01-01 00:00:00", format: "yyyy-MM-dd HH:mm:ss", region: regionRome)! let lowerLimit = DateInRegion("2010-01-01 00:00:00", format: "yyyy-MM-dd HH:mm:ss", region: regionRome)! let randomDates = DateInRegion.randomDates(count: 50, between: lowerLimit, and: upperLimit, region: regionRome) randomDates.forEach { guard $0.isInRange(date: lowerLimit, and: upperLimit) else { XCTFail("Random date '\($0.description)' is not in given range") return } } } func testDateInRegion_RandomDatesBackToDays() { for _ in 0..<50 { let daysBack = (Int.random(in: 0..<365) + 1) let randomDate = DateInRegion.randomDate(withinDaysBeforeToday: daysBack) guard randomDate.getInterval(toDate: DateInRegion(), component: .day) <= daysBack else { XCTFail("Failed to generate a random back date back to max \(daysBack) days") return } } } func testDateInRegion_EnumerateDates() { let regionRome = Region(calendar: Calendars.gregorian, zone: Zones.europeRome, locale: Locales.italian) // TEST DATE #1 // +1 days let fromDate1 = DateInRegion("2015-01-01 00:00:00", format: "yyyy-MM-dd HH:mm:ss", region: regionRome)! let toDate1 = DateInRegion("2015-01-02 03:00:00", format: "yyyy-MM-dd HH:mm:ss", region: regionRome)! let increment1 = DateComponents.create { $0.hour = 1 } let enumeratedDates1 = DateInRegion.enumerateDates(from: fromDate1, to: toDate1, increment: increment1) let expectedDates1 = [ "2015-01-01 00:00:00", "2015-01-01 01:00:00", "2015-01-01 02:00:00", "2015-01-01 03:00:00", "2015-01-01 04:00:00", "2015-01-01 05:00:00", "2015-01-01 06:00:00", "2015-01-01 07:00:00", "2015-01-01 08:00:00", "2015-01-01 09:00:00", "2015-01-01 10:00:00", "2015-01-01 11:00:00", "2015-01-01 12:00:00", "2015-01-01 13:00:00", "2015-01-01 14:00:00", "2015-01-01 15:00:00", "2015-01-01 16:00:00", "2015-01-01 17:00:00", "2015-01-01 18:00:00", "2015-01-01 19:00:00", "2015-01-01 20:00:00", "2015-01-01 21:00:00", "2015-01-01 22:00:00", "2015-01-01 23:00:00", "2015-01-02 00:00:00", "2015-01-02 01:00:00", "2015-01-02 02:00:00", "2015-01-02 03:00:00"] XCTExpectedFormattedDates(enumeratedDates1, expected: expectedDates1) // TEST DATE #2 // +1 hour, +30 minutes, +10 seconds let increment2 = DateComponents.create { $0.hour = 1 $0.minute = 30 $0.second = 10 } let fromDate2 = DateInRegion("2015-01-01 10:00:00", format: "yyyy-MM-dd HH:mm:ss", region: regionRome)! let toDate2 = DateInRegion("2015-01-02 03:00:00", format: "yyyy-MM-dd HH:mm:ss", region: regionRome)! let enumeratedDates2 = DateInRegion.enumerateDates(from: fromDate2, to: toDate2, increment: increment2) let expectedDates2 = [ "2015-01-01 10:00:00", "2015-01-01 11:30:10", "2015-01-01 13:00:20", "2015-01-01 14:30:30", "2015-01-01 16:00:40", "2015-01-01 17:30:50", "2015-01-01 19:01:00", "2015-01-01 20:31:10", "2015-01-01 22:01:20", "2015-01-01 23:31:30", "2015-01-02 01:01:40", "2015-01-02 02:31:50" ] XCTExpectedFormattedDates(enumeratedDates2, expected: expectedDates2) // TEST DATE #3 // +1 month let increment3 = DateComponents.create { $0.month = 1 } let fromDate3 = DateInRegion("2015-01-01 01:00:00", format: "yyyy-MM-dd HH:mm:ss", region: regionRome)! let toDate3 = DateInRegion("2016-02-05 02:00:00", format: "yyyy-MM-dd HH:mm:ss", region: regionRome)! let enumeratedDates3 = DateInRegion.enumerateDates(from: fromDate3, to: toDate3, increment: increment3) let expectedDates3 = [ "2015-01-01 01:00:00", "2015-02-01 01:00:00", "2015-03-01 01:00:00", "2015-04-01 01:00:00", "2015-05-01 01:00:00", "2015-06-01 01:00:00", "2015-07-01 01:00:00", "2015-08-01 01:00:00", "2015-09-01 01:00:00", "2015-10-01 01:00:00", "2015-11-01 01:00:00", "2015-12-01 01:00:00", "2016-01-01 01:00:00", "2016-02-01 01:00:00" ] XCTExpectedFormattedDates(enumeratedDates3, expected: expectedDates3) } func XCTExpectedFormattedDates(_ list: [DateInRegion], expected expectedDates: [String]) { list.enumerated().forEach { let formatted = $0.element.toFormat("yyyy-MM-dd HH:mm:ss") let expected = expectedDates[$0.offset] guard expected == formatted else { XCTFail("Failed to enumerate dates. Got '\(formatted)' expected '\(expected)") return } } } func testDateInRegion_oldestAndNewestAndSortsIn() { let regionRome = Region(calendar: Calendars.gregorian, zone: Zones.europeRome, locale: Locales.italian) let regionLondon = Region(calendar: Calendars.gregorian, zone: Zones.europeLondon, locale: Locales.english) let regionNY = Region(calendar: Calendars.gregorian, zone: Zones.americaNewYork, locale: Locales.english) let datesArrayFormat = "yyyy-MM-dd HH:mm:ss" let date1 = DateInRegion("2015-09-24 13:20:55", format: datesArrayFormat, region: regionRome)! let date2 = DateInRegion("2015-11-14 14:44:00", format: datesArrayFormat, region: regionRome)! let date3 = DateInRegion("2017-01-01 00:00:00", format: datesArrayFormat, region: regionRome)! let date4 = DateInRegion("2015-09-24 13:00:55", format: datesArrayFormat, region: regionNY)! let date5 = DateInRegion("2017-11-30 20:00:40", format: datesArrayFormat, region: regionNY)! let date6 = DateInRegion("2017-11-29 23:59:59", format: datesArrayFormat, region: regionLondon)! let datesArray = [date1, date2, date3, date4, date5, date6] // Oldest and Newest XCTAssert( (DateInRegion.oldestIn(list: datesArray) == date1), "Failed to get the oldest date in list") XCTAssert( (DateInRegion.newestIn(list: datesArray) == date5), "Failed to get the newest date in list") // Order by newest let orderedByNewest = DateInRegion.sortedByNewest(list: datesArray) XCTSameDateArray([date5, date6, date3, date2, date4, date1], orderedByNewest) // Order by oldest let orderedByOldest = DateInRegion.sortedByOldest(list: datesArray) XCTSameDateArray([date1, date4, date2, date3, date6, date5], orderedByOldest) } @discardableResult func XCTSameDateArray(_ left: [DateInRegion], _ right: [DateInRegion]) -> Bool { guard left.count == right.count else { return false } // swiftlint:disable for_where for idx in 0.. DateComponents XCTAssert( date1.componentsTo(date2).isZero, "Failed to extract components from math operation between dates") XCTAssert( date3.componentsTo(date2).hour == 10, "Failed to extract components from math operation between dates") XCTAssert( date3.componentsTo(date2).day == 1, "Failed to extract components from math operation between dates") XCTAssert( date3.componentsTo(date2).year == 1, "Failed to extract components from math operation between dates") // TEST #2 // (DateInRegion + DateComponents) -> DateInRegion let components1 = DateComponents.create { $0.day = 1 $0.hour = 2 $0.minute = 30 } let finalDate1 = (date1 + components1).toFormat(dateFormat) XCTAssert( (finalDate1 == "2017-07-23 02:30:00"), "Failed to sum date to components and get the exact final date") // TEST #3 let components2 = DateComponents.create { $0.year = 1 $0.weekOfYear = 2 } let finalDate2 = (date1 + components2).toFormat(dateFormat) XCTAssert( (finalDate2 == "2018-08-05 00:00:00"), "Failed to sum date to components and get the exact final date") // TEST #4 // (DateInRegion - DateComponents) -> DateInRegion let finalDate3 = (date1 - DateComponents.create({ $0.day = 30 $0.hour = 27 })).toFormat(dateFormat) XCTAssert( (finalDate3 == "2017-06-20 21:00:00"), "Failed to minus date to components and get the exact final date") // TEST #5 let finalDate4 = (date1 + [Calendar.Component.year: 1]).toFormat(dateFormat) XCTAssert( (finalDate4 == "2018-07-22 00:00:00"), "Failed to add components dict and get the exact final date") // TEST #6 let finalDate5 = (date1 + [Calendar.Component.day: 20, Calendar.Component.hour: 10]).toFormat(dateFormat) XCTAssert( (finalDate5 == "2017-08-11 10:00:00"), "Failed to add components dict and get the exact final date") } func testNextWeekday() { let regionRome = Region(calendar: Calendars.gregorian, zone: Zones.europeRome, locale: Locales.italian) let dateFormat = "yyyy-MM-dd HH:mm:ss" let date1 = DateInRegion("2019-05-11 00:00:00", format: dateFormat, region: regionRome)! let nextFriday = date1.nextWeekday(.friday) XCTAssert(nextFriday.toISO() == "2019-05-17T00:00:00+02:00", "Failed to get the next weekday from date") } func testDateAtWeekdayOrdinal() { let regionRome = Region(calendar: Calendars.gregorian, zone: Zones.europeRome, locale: Locales.italian) let dateFormat = "yyyy-MM-dd HH:mm:ss" let date1 = DateInRegion("2019-05-11 00:00:00", format: dateFormat, region: regionRome)! let result = date1.dateAt(weekdayOrdinal: 3, weekday: .friday, monthNumber: date1.month + 1) XCTAssert(result.toISO() == "2019-06-21T00:00:00+02:00", "Failed to get the next weekday from date") } } ================================================ FILE: Tests/SwiftDateTests/TestDateInRegion.swift ================================================ // // 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 SwiftDate import XCTest class TestDateInRegion: XCTestCase { let testDate = Date() let testRegion = Region(calendar: Calendars.gregorian, zone: Zones.europeRome, locale: Locales.italian) func testDateInRegion_ParseWithLocale() { let itRegion = Region(calendar: Calendars.gregorian, zone: Zones.europeRome, locale: Locales.italian) let dateIt = "15 Settembre 2001".toDate("dd MMM yyyy", region: itRegion) XCTAssertNotNil(dateIt, "Failed to parse with forced locale it") let dateEnStr = "10 July 2005" let dateEnFail = dateEnStr.toDate("dd MMM yyyy", region: itRegion) XCTAssertNil(dateEnFail, "This date should be not represented") let enRegion = Region(calendar: Calendars.gregorian, zone: Zones.europeRome, locale: Locales.english) let dateEnSuccess = dateEnStr.toDate("dd MMM yyyy", region: enRegion) XCTAssertNotNil(dateEnSuccess, "Failed to parse with forced locale en") } func testDateInRegion_InitWithDateAndRegion() { SwiftDate.defaultRegion = testRegion // test with given date in test region let dateInTestRegion = DateInRegion(testDate, region: testRegion) XCTAssert( (dateInTestRegion.date == testDate), "Failed to create DateInRegion with given date and test region / different date") XCTAssert( (dateInTestRegion.region == testRegion), "Failed to create DateInRegion with given date and test region / different region") // test with current date in test region let nowInTestRegion = DateInRegion(region: testRegion) XCTAssertInTimeIntervalRange(value: (Date().timeIntervalSince1970 - nowInTestRegion.date.timeIntervalSince1970), range: 5, "Failed to create DateInRegion with now date and test region / date is not now") XCTAssert( (nowInTestRegion.region == testRegion), "Failed to create DateInRegion with now date and test region / different region") // test with current date in default region let nowInDefRegion = DateInRegion() XCTAssertInTimeIntervalRange(value: (Date().timeIntervalSince1970 - nowInDefRegion.date.timeIntervalSince1970), range: 5, "Failed to create DateInRegion with now date and default region / date is not now") XCTAssert( (nowInDefRegion.region == SwiftDate.defaultRegion), "Failed to create DateInRegion with now date and default region / different region") } func testDateInRegion_InitFromTimeInterval() { SwiftDate.defaultRegion = Region(calendar: Calendars.buddhist, zone: Zones.indianCocos, locale: Locales.nepaliIndia) // Init with seconds and default region at UTC timezone let secsFromEpoch = DateInRegion(seconds: 10) XCTAssert( (secsFromEpoch.date.timeIntervalSince1970 == 10), "Failed to create DateInRegion from epoch time and UTC region / different date") XCTAssert( (secsFromEpoch.region.timeZone == Region.UTC.timeZone), "Failed to create DateInRegion from epoch time and UTC region / different region than UTC") XCTAssert( (secsFromEpoch.region.locale == Region.UTC.locale), "Failed to create DateInRegion from epoch time and UTC region / no default's region locale") XCTAssert( (secsFromEpoch.region.calendar.identifier == Region.UTC.calendar.identifier), "Failed to create DateInRegion from epoch time and UTC region / no default's region calendar") // Init with milliseconds and default region at UTC timezone let msecsFromEpoch = DateInRegion(milliseconds: 5000) XCTAssert( (msecsFromEpoch.date.timeIntervalSince1970 == 5), "Failed to create DateInRegion from epoch time and UTC region / different date") // Init with second and given region let regionBerlin = Region(calendar: Calendars.gregorian, zone: Zones.europeBerlin, locale: Locales.dutch) let secsFromEpochInBerlin = DateInRegion(seconds: 56673, region: regionBerlin) XCTAssert( (secsFromEpochInBerlin.region == regionBerlin), "Failed to create DateInRegion from epoch time and fixed region / different region") XCTAssert( (secsFromEpochInBerlin.date.timeIntervalSince1970 == 56673), "Failed to create DateInRegion from epoch time and fixed region / different date") // Init with milliseconds and given region let msecsFromEpochInRegion = DateInRegion(milliseconds: 5000, region: regionBerlin) XCTAssert( (msecsFromEpochInRegion.date.timeIntervalSince1970 == 5), "Failed to create DateInRegion from epoch time and fixed region / different date") XCTAssert( (msecsFromEpochInRegion.region == regionBerlin), "Failed to create DateInRegion from epoch time and fixed region / different date") // Init with fraction of seconds let msecsLessThanSeconds = DateInRegion(milliseconds: 10) XCTAssertEqual(round(msecsLessThanSeconds.date.timeIntervalSince1970 * 1000), 10) } func testDateInRegion_InitFromComponents() { let regionRome = Region(calendar: Calendars.gregorian, zone: Zones.europeRome, locale: Locales.italian) let regionUTC = Region.UTC var dComponents = DateComponents() dComponents.year = 1995 dComponents.month = 6 dComponents.day = 15 dComponents.hour = 16 dComponents.minute = 30 dComponents.second = 55 guard let dateInRome = DateInRegion(components: dComponents, region: regionRome) else { XCTFail("Failed to create DateInRegion from valid components") return } // Date must be expressed in Rome Time Zone which is UTC+1 for given date (1995/06/15) // So we expect the same components of the date components and given hour -1 in absolute date (UTC) let validDateTime = ((dateInRome.year == dComponents.year!) && (dateInRome.month == dComponents.month!) && (dateInRome.day == dComponents.day!) && (dateInRome.hour == dComponents.hour!) && (dateInRome.minute == dComponents.minute!) && (dateInRome.second == dComponents.second!)) XCTAssert(validDateTime, "Failed to create a valid DateInRegion in given region from components. One or more date components differs from original DateComponents") XCTAssert( (dateInRome.date.in(region: regionUTC).hour == 14), "DateInRegion absolute date different from expected UTC value (14:30 UTC for given date)") } func testDateInRegion_InitFromParams() { // Init with fixed parameters in a given region let regionLondon = Region(calendar: Calendars.gregorian, zone: Zones.europeLondon, locale: Locales.englishUnitedKingdom) let aDayInLondon = DateInRegion(year: 2001, month: 1, day: 5, hour: 23, minute: 30, second: 0, region: regionLondon) let validDateLondon = (aDayInLondon.year == 2001 && aDayInLondon.month == 1 && aDayInLondon.day == 5 && aDayInLondon.hour == 23 && aDayInLondon.minute == 30 && aDayInLondon.second == 0) XCTAssert(validDateLondon, "Failed to create a valid DateInRegion from paramters. One or more date components differs from original params") // Init with fixed parameters (and some other missings) in default region SwiftDate.defaultRegion = Region(calendar: Calendars.gregorian, zone: Zones.americaNewYork, locale: Locales.englishUnitedStates) let aDayInNY = DateInRegion(year: 1995, month: 1, day: 5) let validDateNY = (aDayInNY.year == 1995 && aDayInNY.month == 1 && aDayInNY.day == 5 && aDayInNY.hour == 0 && aDayInNY.minute == 0 && aDayInNY.second == 0) XCTAssert(validDateNY, "Failed to create a valid DateInRegion from paramters and default region. One or more date components differs from original params") XCTAssert( (aDayInNY.region == SwiftDate.defaultRegion), "Failed to create a valid DateInRegion from paramters and default region.") } func testDateInRegion_PastAndFuture() { XCTAssert( (DateInRegion.past().date == Date.distantPast), "Failed to create DateInRegion with distant past date") XCTAssert( (DateInRegion.future().date == Date.distantFuture), "Failed to create DateInRegion with distant future date") } func testDateInRegion_Description() { let regionParis = Region(calendar: Calendars.gregorian, zone: Zones.europeParis, locale: Locales.frenchFrance) let aDayInParis = DateInRegion(year: 2018, month: 5, day: 1, region: regionParis) let descriptionDayInParis = aDayInParis.description let expectedDescription = "{abs_date='2018-04-30T22:00:00Z', rep_date='2018-05-01T00:00:00+02:00', region={calendar='gregorian', timezone='Europe/Paris', locale='fr_FR'}" XCTAssert( (descriptionDayInParis == expectedDescription), "Failed to create valid description from DateInRegion") } func testDateInRegion_DateComponents() { let regionParis = Region(calendar: Calendars.gregorian, zone: Zones.europeParis, locale: Locales.frenchFrance) let aDayInParis = DateInRegion(year: 2018, month: 5, day: 1, region: regionParis) // Components of the date are expressed into the context of the region in which the date is created let comps = aDayInParis.dateComponents let areValidComponents = (comps.day! == 1 && comps.month! == 5 && comps.year! == 2018 && comps.hour! == 0 && comps.minute! == 0 && comps.second! == 0) XCTAssert(areValidComponents, "Failed to extract valid components in the context of the region of the DateInRegion instance") } func testDateInRegion_Hash() { // let regionParis = Region(calendar: Calendars.gregorian, zone: Zones.europeParis, locale: Locales.frenchFrance) // let aDayInParis = DateInRegion(year: 2018, month: 5, day: 1, region: regionParis) // let sameDayInParis = DateInRegion(year: aDayInParis.year, month: aDayInParis.month, day: aDayInParis.day, region: regionParis) // // XCTAssert( (aDayInParis.hashValue != sameDayInParis.hashValue), "Failed to extract hash value from different date with same values") } func testDateInRegion_InitComponentsCallback() { // From components in fixed region let regionRome = Region(calendar: Calendars.gregorian, zone: Zones.europeRome, locale: Locales.italian) guard let dateInRome = DateInRegion(components: { $0.year = 2001 $0.month = 9 $0.day = 11 $0.hour = 12 $0.minute = 0 $0.second = 5 }, region: regionRome) else { XCTFail("Failed to create valid date from components callback init") return } XCTValidateDateComponents(date: dateInRome, expec: ExpectedDateComponents { $0.year = 2001 $0.month = 9 $0.day = 11 $0.hour = 12 $0.minute = 0 $0.second = 5 }) // From components in default region let regionNY = Region(calendar: Calendars.gregorian, zone: Zones.americaNewYork, locale: Locales.english) SwiftDate.defaultRegion = regionNY guard let dateInNY = DateInRegion(components: { $0.year = 2001 $0.month = 9 $0.day = 11 $0.hour = 12 $0.minute = 0 $0.second = 5 }) else { XCTFail("Failed to create valid date from components callback init") return } let inUTC = dateInNY.convertTo(region: Region.UTC) XCTValidateDateComponents(date: inUTC, expec: ExpectedDateComponents { $0.year = 2001 $0.month = 9 $0.day = 11 $0.hour = 16 $0.minute = 0 $0.second = 5 }) } func testDateInRegion_ExtractComponents() { let regionParis = Region(calendar: Calendars.gregorian, zone: Zones.europeParis, locale: Locales.frenchFrance) let dateInParis = DateInRegion(year: 2006, month: 1, day: 1, hour: 0, minute: 45, second: 30, region: regionParis) // Validate in local region let componentsOfDate = dateInParis.dateComponents XCTAssert((componentsOfDate.year! == 2006), "Failed to extract local timezone date components from date / invalid year") XCTAssert((componentsOfDate.month! == 1), "Failed to extract local timezone date components from date / invalid month") XCTAssert((componentsOfDate.day! == 1), "Failed to extract local timezone date components from date / invalid day") XCTAssert((componentsOfDate.hour! == 0), "Failed to extract local timezone date components from date / invalid hour") XCTAssert((componentsOfDate.minute! == 45), "Failed to extract local timezone date components from date / invalid minute") XCTAssert((componentsOfDate.second! == 30), "Failed to extract local timezone date components from date / invalid second") // Validate in UTC SwiftDate.defaultRegion = Region.UTC let componentsInUTC = dateInParis.date.dateComponents // components are expressed in default zone! UTC XCTAssert((componentsInUTC.year! == 2005), "Failed to extract local timezone date components from date / invalid year") XCTAssert((componentsInUTC.month! == 12), "Failed to extract local timezone date components from date / invalid month") XCTAssert((componentsInUTC.day! == 31), "Failed to extract local timezone date components from date / invalid day") XCTAssert((componentsInUTC.hour! == 23), "Failed to extract local timezone date components from date / invalid hour") XCTAssert((componentsInUTC.minute! == 45), "Failed to extract local timezone date components from date / invalid minute") XCTAssert((componentsInUTC.second! == 30), "Failed to extract local timezone date components from date / invalid second") } func testDateInRegion_InitStringAutoFormat() { let regionParis = Region(calendar: Calendars.gregorian, zone: Zones.europeParis, locale: Locales.frenchFrance) // Init with no format (yyyy-MM-dd'T'HH:mm:ssZZZZZ) let expectedResult = ExpectedDateComponents { $0.year = 2015 $0.month = 9 $0.day = 24 $0.hour = 13 $0.minute = 20 $0.second = 55 } XCTValidateParse(string: "1:20 48055000", format: nil, region: regionParis, expec: ExpectedDateComponents { $0.hour = 13 $0.minute = 20 $0.msInDay = 48_056_000 }) // h:mm A XCTValidateParse(string: "après Jésus-Christ-[4]04-jeu.", format: nil, region: regionParis, expec: ExpectedDateComponents { $0.day = 27 $0.era = 1 }) // GGGG-[W]WW-E XCTValidateParse(string: "après Jésus-Christ-[4]04", format: nil, region: regionParis, expec: ExpectedDateComponents { $0.era = 1 }) // GGGG-[W]WW XCTValidateParse(string: "2015-09-24T13:20:55+02:00", format: nil, region: regionParis, expec: expectedResult) // yyyy-MM-dd'T'HH:mm:ssZZZZZ XCTValidateParse(string: "2015-09-24T13:20:55Z", format: nil, region: regionParis, expec: expectedResult) // yyyy'-'MM'-'dd'T'HH':'mm':'ss'Z' XCTValidateParse(string: "2015-09-24T13:20:55.000Z", format: nil, region: regionParis, expec: expectedResult) // yyyy'-'MM'-'dd'T'HH':'mm':'ss'.'SSS'Z' XCTValidateParse(string: "2015-09-24T13:20:55.000+0200", format: nil, region: regionParis, expec: expectedResult) // yyyy-MM-dd'T'HH:mm:ss.SSSZ XCTValidateParse(string: "2015-09-24", format: nil, region: regionParis, expec: ExpectedDateComponents { $0.year = 2015 $0.month = 9 $0.day = 24 }) // yyyy-MM-dd XCTValidateParse(string: "2015-09-24 13:20:55", format: nil, region: regionParis, expec: expectedResult) // yyyy-MM-dd HH:mm:ss XCTValidateParse(string: "1:20:55 48055000", format: nil, region: regionParis, expec: ExpectedDateComponents { $0.hour = 13 $0.minute = 20 $0.second = 55 $0.msInDay = 48_056_000 }) // h:mm:ss A XCTValidateParse(string: "09/24/2015", format: nil, region: regionParis, expec: ExpectedDateComponents { $0.month = 9 $0.day = 24 $0.year = 2015 $0.hour = 0 $0.minute = 0 $0.second = 0 }) // MM/dd/yyyy XCTValidateParse(string: "septembre 24, 2015", format: nil, region: regionParis, expec: ExpectedDateComponents { $0.month = 9 $0.day = 24 $0.year = 2015 $0.hour = 0 $0.minute = 0 $0.second = 0 }) // MMMM d, yyyy XCTValidateParse(string: "002015-09-24", format: nil, region: regionParis, expec: ExpectedDateComponents { $0.month = 9 $0.day = 24 $0.year = 2015 $0.hour = 0 $0.minute = 0 $0.second = 0 }) // yyyyyy-MM-dd XCTValidateParse(string: "2015-09-24", format: nil, region: regionParis, expec: ExpectedDateComponents { $0.month = 9 $0.day = 24 $0.year = 2015 $0.hour = 0 $0.minute = 0 $0.second = 0 }) // yyyy-MM-dd XCTValidateParse(string: "2015-024", format: nil, region: regionParis, expec: ExpectedDateComponents { $0.dayOfYear = 24 $0.year = 2015 $0.hour = 0 $0.minute = 0 $0.second = 0 }) // yyyy-ddd XCTValidateParse(string: "13:20:55.0000", format: nil, region: regionParis, expec: ExpectedDateComponents { $0.hour = 13 $0.minute = 20 $0.second = 55 }) // HH:mm:ss.SSSS XCTValidateParse(string: "13:20:55", format: nil, region: regionParis, expec: ExpectedDateComponents { $0.hour = 13 $0.minute = 20 $0.second = 55 }) // HH:mm:ss XCTValidateParse(string: "13:20", format: nil, region: regionParis, expec: ExpectedDateComponents { $0.hour = 13 $0.minute = 20 }) // HH:mm XCTValidateParse(string: "13", format: nil, region: regionParis, expec: ExpectedDateComponents { $0.hour = 13 }) // HH } } public struct ExpectedDateComponents { var year: Int? var day: Int? var dayOfYear: Int? var month: Int? var hour: Int? var minute: Int? var second: Int? var weekOfMonth: Int? var msInDay: Int? var era: Int? var monthDays: Int? var weekOfYear: Int? var weekdayOrdinal: Int? var firstDayOfWeek: Int? var lastDayOfWeek: Int? var yearForWeekOfYear: Int? var quarter: Int? var DSTOffset: TimeInterval? var nearestHour: Int? var monthNameDefault: String? var monthNameDefaultStd: String? var monthNameShort: String? var monthNameStandaloneShort: String? var monthNameVeryShort: String? var monthNameStandaloneVeryShort: String? var weekday: Int? var weekdayNameDefault: String? var weekdayNameDefaultStd: String? var weekdayNameShort: String? var weekdayNameShortStd: String? var weekdayNameVeryShort: String? var weekdayNameVeryShortStd: String? var eraNameDefault: String? var eraNameDefaultStd: String? var eraNameShort: String? var eraNameShortStd: String? var eraNameVeryShort: String? var eraNameVeryShortStd: String? public init(_ configure: ((inout ExpectedDateComponents) -> Void)? = nil) { configure?(&self) } func validate(_ date: DateInRegion, _ origin: String) -> String? { if let year = self.year, year != date.year { return "year" } if let day = self.day, day != date.day { return "day" } if let month = self.month, month != date.month { return "month" } if let minute = self.minute, minute != date.minute { return "minute exp='\(self.minute!)' val='\(date.minute)' date='\(date)' origin='\(origin)'" } if let second = self.second, second != date.second { return "second" } if let weekOfMonth = self.weekOfMonth, weekOfMonth != date.weekOfMonth { return "weekOfMonth" } if let dayOfYear = self.dayOfYear, dayOfYear != date.dayOfYear { return "dayOfYear" } if let era = self.era, era != date.era { return "era" } if let msInDay = self.msInDay, msInDay != date.msInDay { return "msInDay" } if let monthNameDefault = self.monthNameDefault, monthNameDefault != date.monthName(.`default`) { return "monthName(.`default`)" } if let monthNameDefaultStd = self.monthNameDefaultStd, monthNameDefaultStd != date.monthName(.defaultStandalone) { return "monthName(.defaultStandalone)" } if let monthNameShort = self.monthNameShort, monthNameShort != date.monthName(.short) { return "monthName(.short)" } if let monthNameVeryShort = self.monthNameVeryShort, monthNameVeryShort != date.monthName(.veryShort) { return "monthName(.veryShort)" } if let monthNameStandaloneShort = self.monthNameStandaloneShort, monthNameStandaloneShort != date.monthName(.standaloneShort) { return "monthName(.standaloneShort)" } if let monthNameStandaloneVeryShort = self.monthNameStandaloneVeryShort, monthNameStandaloneVeryShort != date.weekdayName(.standaloneVeryShort) { return "weekdayName(.veryShortStandalone)" } if let monthDays = self.monthDays, monthDays != date.monthDays { return "monthDays" } if let weekday = self.weekday, weekday != date.weekday { return "weekday" } if let weekdayNameDefault = self.weekdayNameDefault, weekdayNameDefault != date.weekdayName(.`default`) { return "weekdayName(.`default`)" } if let weekdayNameDefaultStd = self.weekdayNameDefaultStd, weekdayNameDefaultStd != date.weekdayName(.defaultStandalone) { return "weekdayName(.defaultStandalone)" } if let weekdayNameShort = self.weekdayNameShort, weekdayNameShort != date.weekdayName(.short) { return "weekdayName(.short)" } if let weekdayNameShortStd = self.weekdayNameShortStd, weekdayNameShortStd != date.weekdayName(.standaloneShort) { return "weekdayName(.shortStandalone)" } if let weekdayNameVeryShort = self.weekdayNameVeryShort, weekdayNameVeryShort != date.weekdayName(.veryShort) { return "weekdayName(.veryShort)" } if let weekdayNameVeryShortStd = self.weekdayNameVeryShortStd, weekdayNameVeryShortStd != date.weekdayName(.standaloneVeryShort) { return "weekdayName(.veryShortStandalone)" } if let weekOfYear = self.weekOfYear, weekOfYear != date.weekOfYear { return "weekOfYear" } if let weekdayOrdinal = self.weekdayOrdinal, weekdayOrdinal != date.weekdayOrdinal { return "weekdayOrdinal" } if let firstDayOfWeek = self.firstDayOfWeek, firstDayOfWeek != date.firstDayOfWeek { return "firstDayOfWeek" } if let lastDayOfWeek = self.lastDayOfWeek, lastDayOfWeek != date.lastDayOfWeek { return "lastDayOfWeek" } if let yearForWeekOfYear = self.yearForWeekOfYear, yearForWeekOfYear != date.yearForWeekOfYear { return "yearForWeekOfYear" } if let quarter = self.quarter, quarter != date.quarter { return "quarter" } if let eraNameDefault = self.eraNameDefault, eraNameDefault != date.eraName(.`default`) { return "eraName(.`default`)" } if let eraNameDefaultStd = self.eraNameDefaultStd, eraNameDefaultStd != date.eraName(.defaultStandalone) { return "eraName(.defaultStandalone)" } if let eraNameShort = self.eraNameShort, eraNameShort != date.eraName(.short) { return "eraName(.short)" } if let eraNameShortStd = self.eraNameShortStd, eraNameShortStd != date.eraName(.standaloneShort) { return "eraName(.shortStandalone)" } if let eraNameVeryShort = self.eraNameVeryShort, eraNameVeryShort != date.eraName(.veryShort) { return "eraName(.veryShort)" } if let eraNameVeryShortStd = self.eraNameVeryShortStd, eraNameVeryShortStd != date.eraName(.standaloneVeryShort) { return "eraName(.veryShortStandalone)" } if let DSTOffset = self.DSTOffset, DSTOffset != date.DSTOffset { return "DSTOffset" } if let nearestHour = self.nearestHour, nearestHour != date.nearestHour { return "nearestHour" } return nil } } func XCTValidateParse(string: String, format: String?, region: Region, expec: ExpectedDateComponents) { guard let date = DateInRegion(string, format: format, region: region) else { XCTFail("Failed to parse date '\(string)' with format: '\(format ?? "")'") return } if let errors = expec.validate(date, string) { XCTFail("Failed to validate components of parsed date string '\(string)' with format: '\(format ?? "")'. One or more components differ from expected: \(errors)") return } } func XCTValidateDateComponents(date: DateInRegion, expec: ExpectedDateComponents) { if let errors = expec.validate(date, "") { XCTFail("Failed to validate components of date '\(date.description)'. One or more components differ from expected: \(errors)") return } } ================================================ FILE: Tests/SwiftDateTests/TestFormatters.swift ================================================ // // 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 SwiftDate import XCTest class TestFormatters: XCTestCase { public func datesList() -> [String: [String: String]] { return [ "2017-07-22T18:27:02+02:00": [ "dotnet": "/Date(1500740822000+0200)/", "rss": "Sat, 22 Jul 2017 18:27:02 +0200", "rss_alt": "22 Jul 2017 18:27:02 +0200", "sql": "2017-07-22T18:27:02.000+02", "iso": "2017-07-22T18:27:02+02:00" ], "2017-02-03T06:30:12+01:00": [ "dotnet": "/Date(1486099812000+0100)/", "rss": "Fri, 3 Feb 2017 06:30:12 +0100", "rss_alt": "3 Feb 2017 06:30:12 +0100", "sql": "2017-02-03T06:30:12.000+01", "iso": "2017-02-03T06:30:12+01:00" ], "2016-03-31T10:57:44+02:00": [ "dotnet": "/Date(1459414664000+0200)/", "rss": "Thu, 31 Mar 2016 10:57:44 +0200", "rss_alt": "31 Mar 2016 10:57:44 +0200", "sql": "2016-03-31T10:57:44.000+02", "iso": "2016-03-31T10:57:44+02:00" ], "2016-07-23T17:05:31+02:00": [ "dotnet": "/Date(1469286331000+0200)/", "rss": "Sat, 23 Jul 2016 17:05:31 +0200", "rss_alt": "23 Jul 2016 17:05:31 +0200", "sql": "2016-07-23T17:05:31.000+02", "iso": "2016-07-23T17:05:31+02:00" ], "2017-05-15T11:26:36+02:00": [ "dotnet": "/Date(1494840396000+0200)/", "rss": "Mon, 15 May 2017 11:26:36 +0200", "rss_alt": "15 May 2017 11:26:36 +0200", "sql": "2017-05-15T11:26:36.000+02", "iso": "2017-05-15T11:26:36+02:00" ], "2018-03-27T20:50:37+02:00": [ "dotnet": "/Date(1522176637000+0200)/", "rss": "Tue, 27 Mar 2018 20:50:37 +0200", "rss_alt": "27 Mar 2018 20:50:37 +0200", "sql": "2018-03-27T20:50:37.000+02", "iso": "2018-03-27T20:50:37+02:00" ], "2018-02-16T14:52:52+01:00": [ "dotnet": "/Date(1518789172000+0100)/", "rss": "Fri, 16 Feb 2018 14:52:52 +0100", "rss_alt": "16 Feb 2018 14:52:52 +0100", "sql": "2018-02-16T14:52:52.000+01", "iso": "2018-02-16T14:52:52+01:00" ], "2016-01-10T16:55:35+01:00": [ "dotnet": "/Date(1452441335000+0100)/", "rss": "Sun, 10 Jan 2016 16:55:35 +0100", "rss_alt": "10 Jan 2016 16:55:35 +0100", "sql": "2016-01-10T16:55:35.000+01", "iso": "2016-01-10T16:55:35+01:00" ], "2017-05-02T23:53:44+02:00": [ "dotnet": "/Date(1493762024000+0200)/", "rss": "Tue, 2 May 2017 23:53:44 +0200", "rss_alt": "2 May 2017 23:53:44 +0200", "sql": "2017-05-02T23:53:44.000+02", "iso": "2017-05-02T23:53:44+02:00" ], "2017-05-13T20:05:38+02:00": [ "dotnet": "/Date(1494698738000+0200)/", "rss": "Sat, 13 May 2017 20:05:38 +0200", "rss_alt": "13 May 2017 20:05:38 +0200", "sql": "2017-05-13T20:05:38.000+02", "iso": "2017-05-13T20:05:38+02:00" ], "2017-10-04T17:25:36+02:00": [ "dotnet": "/Date(1507130736000+0200)/", "rss": "Wed, 4 Oct 2017 17:25:36 +0200", "rss_alt": "4 Oct 2017 17:25:36 +0200", "sql": "2017-10-04T17:25:36.000+02", "iso": "2017-10-04T17:25:36+02:00" ], "2016-04-14T11:58:58+02:00": [ "dotnet": "/Date(1460627938000+0200)/", "rss": "Thu, 14 Apr 2016 11:58:58 +0200", "rss_alt": "14 Apr 2016 11:58:58 +0200", "sql": "2016-04-14T11:58:58.000+02", "iso": "2016-04-14T11:58:58+02:00" ], "2016-10-03T13:15:37+02:00": [ "dotnet": "/Date(1475493337000+0200)/", "rss": "Mon, 3 Oct 2016 13:15:37 +0200", "rss_alt": "3 Oct 2016 13:15:37 +0200", "sql": "2016-10-03T13:15:37.000+02", "iso": "2016-10-03T13:15:37+02:00" ], "2015-08-23T16:28:34+02:00": [ "dotnet": "/Date(1440340114000+0200)/", "rss": "Sun, 23 Aug 2015 16:28:34 +0200", "rss_alt": "23 Aug 2015 16:28:34 +0200", "sql": "2015-08-23T16:28:34.000+02", "iso": "2015-08-23T16:28:34+02:00" ], "2016-09-04T19:52:29+02:00": [ "dotnet": "/Date(1473011549000+0200)/", "rss": "Sun, 4 Sep 2016 19:52:29 +0200", "rss_alt": "4 Sep 2016 19:52:29 +0200", "sql": "2016-09-04T19:52:29.000+02", "iso": "2016-09-04T19:52:29+02:00" ], "2016-05-09T14:09:55+02:00": [ "dotnet": "/Date(1462795795000+0200)/", "rss": "Mon, 9 May 2016 14:09:55 +0200", "rss_alt": "9 May 2016 14:09:55 +0200", "sql": "2016-05-09T14:09:55.000+02", "iso": "2016-05-09T14:09:55+02:00" ], "2016-05-11T02:58:47+02:00": [ "dotnet": "/Date(1462928327000+0200)/", "rss": "Wed, 11 May 2016 02:58:47 +0200", "rss_alt": "11 May 2016 02:58:47 +0200", "sql": "2016-05-11T02:58:47.000+02", "iso": "2016-05-11T02:58:47+02:00" ], "2017-04-08T01:49:29+02:00": [ "dotnet": "/Date(1491608969000+0200)/", "rss": "Sat, 8 Apr 2017 01:49:29 +0200", "rss_alt": "8 Apr 2017 01:49:29 +0200", "sql": "2017-04-08T01:49:29.000+02", "iso": "2017-04-08T01:49:29+02:00" ], "2017-03-14T04:06:47+01:00": [ "dotnet": "/Date(1489460807000+0100)/", "rss": "Tue, 14 Mar 2017 04:06:47 +0100", "rss_alt": "14 Mar 2017 04:06:47 +0100", "sql": "2017-03-14T04:06:47.000+01", "iso": "2017-03-14T04:06:47+01:00" ], "2016-05-31T14:31:50+02:00": [ "dotnet": "/Date(1464697910000+0200)/", "rss": "Tue, 31 May 2016 14:31:50 +0200", "rss_alt": "31 May 2016 14:31:50 +0200", "sql": "2016-05-31T14:31:50.000+02", "iso": "2016-05-31T14:31:50+02:00" ], "2015-09-22T08:28:16+02:00": [ "dotnet": "/Date(1442903296000+0200)/", "rss": "Tue, 22 Sep 2015 08:28:16 +0200", "rss_alt": "22 Sep 2015 08:28:16 +0200", "sql": "2015-09-22T08:28:16.000+02", "iso": "2015-09-22T08:28:16+02:00" ], "2016-08-11T06:53:56+02:00": [ "dotnet": "/Date(1470891236000+0200)/", "rss": "Thu, 11 Aug 2016 06:53:56 +0200", "rss_alt": "11 Aug 2016 06:53:56 +0200", "sql": "2016-08-11T06:53:56.000+02", "iso": "2016-08-11T06:53:56+02:00" ], "2018-03-27T19:49:32+02:00": [ "dotnet": "/Date(1522172972000+0200)/", "rss": "Tue, 27 Mar 2018 19:49:32 +0200", "rss_alt": "27 Mar 2018 19:49:32 +0200", "sql": "2018-03-27T19:49:32.000+02", "iso": "2018-03-27T19:49:32+02:00" ], "2017-03-04T21:04:51+01:00": [ "dotnet": "/Date(1488657891000+0100)/", "rss": "Sat, 4 Mar 2017 21:04:51 +0100", "rss_alt": "4 Mar 2017 21:04:51 +0100", "sql": "2017-03-04T21:04:51.000+01", "iso": "2017-03-04T21:04:51+01:00" ], "2016-09-19T10:22:17+02:00": [ "dotnet": "/Date(1474273337000+0200)/", "rss": "Mon, 19 Sep 2016 10:22:17 +0200", "rss_alt": "19 Sep 2016 10:22:17 +0200", "sql": "2016-09-19T10:22:17.000+02", "iso": "2016-09-19T10:22:17+02:00" ], "2016-01-09T08:24:32+01:00": [ "dotnet": "/Date(1452324272000+0100)/", "rss": "Sat, 9 Jan 2016 08:24:32 +0100", "rss_alt": "9 Jan 2016 08:24:32 +0100", "sql": "2016-01-09T08:24:32.000+01", "iso": "2016-01-09T08:24:32+01:00" ], "2018-05-16T02:33:15+02:00": [ "dotnet": "/Date(1526430795000+0200)/", "rss": "Wed, 16 May 2018 02:33:15 +0200", "rss_alt": "16 May 2018 02:33:15 +0200", "sql": "2018-05-16T02:33:15.000+02", "iso": "2018-05-16T02:33:15+02:00" ], "2016-11-09T14:42:29+01:00": [ "dotnet": "/Date(1478698949000+0100)/", "rss": "Wed, 9 Nov 2016 14:42:29 +0100", "rss_alt": "9 Nov 2016 14:42:29 +0100", "sql": "2016-11-09T14:42:29.000+01", "iso": "2016-11-09T14:42:29+01:00" ], "2017-09-18T05:07:13+02:00": [ "dotnet": "/Date(1505704033000+0200)/", "rss": "Mon, 18 Sep 2017 05:07:13 +0200", "rss_alt": "18 Sep 2017 05:07:13 +0200", "sql": "2017-09-18T05:07:13.000+02", "iso": "2017-09-18T05:07:13+02:00" ], "2016-11-08T19:03:38+01:00": [ "dotnet": "/Date(1478628218000+0100)/", "rss": "Tue, 8 Nov 2016 19:03:38 +0100", "rss_alt": "8 Nov 2016 19:03:38 +0100", "sql": "2016-11-08T19:03:38.000+01", "iso": "2016-11-08T19:03:38+01:00" ], "2017-08-25T04:33:32+02:00": [ "dotnet": "/Date(1503628412000+0200)/", "rss": "Fri, 25 Aug 2017 04:33:32 +0200", "rss_alt": "25 Aug 2017 04:33:32 +0200", "sql": "2017-08-25T04:33:32.000+02", "iso": "2017-08-25T04:33:32+02:00" ], "2016-06-07T15:10:47+02:00": [ "dotnet": "/Date(1465305047000+0200)/", "rss": "Tue, 7 Jun 2016 15:10:47 +0200", "rss_alt": "7 Jun 2016 15:10:47 +0200", "sql": "2016-06-07T15:10:47.000+02", "iso": "2016-06-07T15:10:47+02:00" ], "2016-12-02T14:54:10+01:00": [ "dotnet": "/Date(1480686850000+0100)/", "rss": "Fri, 2 Dec 2016 14:54:10 +0100", "rss_alt": "2 Dec 2016 14:54:10 +0100", "sql": "2016-12-02T14:54:10.000+01", "iso": "2016-12-02T14:54:10+01:00" ], "2016-02-11T11:48:21+01:00": [ "dotnet": "/Date(1455187701000+0100)/", "rss": "Thu, 11 Feb 2016 11:48:21 +0100", "rss_alt": "11 Feb 2016 11:48:21 +0100", "sql": "2016-02-11T11:48:21.000+01", "iso": "2016-02-11T11:48:21+01:00" ], "2018-01-23T17:44:12+01:00": [ "dotnet": "/Date(1516725852000+0100)/", "rss": "Tue, 23 Jan 2018 17:44:12 +0100", "rss_alt": "23 Jan 2018 17:44:12 +0100", "sql": "2018-01-23T17:44:12.000+01", "iso": "2018-01-23T17:44:12+01:00" ], "2016-05-09T22:11:28+02:00": [ "dotnet": "/Date(1462824688000+0200)/", "rss": "Mon, 9 May 2016 22:11:28 +0200", "rss_alt": "9 May 2016 22:11:28 +0200", "sql": "2016-05-09T22:11:28.000+02", "iso": "2016-05-09T22:11:28+02:00" ], "2015-06-27T04:15:55+02:00": [ "dotnet": "/Date(1435371355000+0200)/", "rss": "Sat, 27 Jun 2015 04:15:55 +0200", "rss_alt": "27 Jun 2015 04:15:55 +0200", "sql": "2015-06-27T04:15:55.000+02", "iso": "2015-06-27T04:15:55+02:00" ], "2017-08-05T16:04:03+02:00": [ "dotnet": "/Date(1501941843000+0200)/", "rss": "Sat, 5 Aug 2017 16:04:03 +0200", "rss_alt": "5 Aug 2017 16:04:03 +0200", "sql": "2017-08-05T16:04:03.000+02", "iso": "2017-08-05T16:04:03+02:00" ], "2015-11-19T22:20:40+01:00": [ "dotnet": "/Date(1447968040000+0100)/", "rss": "Thu, 19 Nov 2015 22:20:40 +0100", "rss_alt": "19 Nov 2015 22:20:40 +0100", "sql": "2015-11-19T22:20:40.000+01", "iso": "2015-11-19T22:20:40+01:00" ], "2017-06-20T14:49:19+02:00": [ "dotnet": "/Date(1497962959000+0200)/", "rss": "Tue, 20 Jun 2017 14:49:19 +0200", "rss_alt": "20 Jun 2017 14:49:19 +0200", "sql": "2017-06-20T14:49:19.000+02", "iso": "2017-06-20T14:49:19+02:00" ] ] } /*public func testGenerateSomeRandomDates() { var total = "" let upperBound = DateInRegion() let lowerBound = (upperBound - 3.years) let randomDates = DateInRegion.randomDates(count: 40, between: lowerBound, and: upperBound) randomDates.forEach { let adjustedDateNoMS = $0.toISO().toISODate()! total += "\"\($0.toISO())\" : [\n\t" total += "\n\t" + "\"dotnet\": \"\(adjustedDateNoMS.toDotNET())\"," total += "\n\t" + "\"rss\": \"\(adjustedDateNoMS.toRSS(alt: false))\"," total += "\n\t" + "\"rss_alt\": \"\(adjustedDateNoMS.toRSS(alt: true))\"," total += "\n\t" + "\"sql\": \"\(adjustedDateNoMS.toSQL())\"," total += "\n\t" + "\"iso\": \"\(adjustedDateNoMS.toISO())\"" total += "\n],\n" } print("\(total)") }*/ public func testDotNETFormatter() { SwiftDate.defaultRegion = Region(calendar: Calendars.gregorian, zone: Zones.europeRome, locale: Locales.english) datesList().forEach { XCTTestFormatterParser(dateStr: $0.key, expected: $0.value["dotnet"]!, type: "dotnet", region: SwiftDate.defaultRegion) } } public func testRSSFormatter() { SwiftDate.defaultRegion = Region(calendar: Calendars.gregorian, zone: Zones.europeRome, locale: Locales.english) datesList().forEach { XCTTestFormatterParser(dateStr: $0.key, expected: $0.value["rss"]!, type: "rss", region: SwiftDate.defaultRegion) } } public func testRSSAltFormatter() { SwiftDate.defaultRegion = Region(calendar: Calendars.gregorian, zone: Zones.europeRome, locale: Locales.english) datesList().forEach { XCTTestFormatterParser(dateStr: $0.key, expected: $0.value["rss_alt"]!, type: "rss_alt", region: SwiftDate.defaultRegion) } } public func testSQLFormatter() { SwiftDate.defaultRegion = Region(calendar: Calendars.gregorian, zone: Zones.europeRome, locale: Locales.english) datesList().forEach { XCTTestFormatterParser(dateStr: $0.key, expected: $0.value["sql"]!, type: "sql", region: SwiftDate.defaultRegion) } } public func testISOFormatter() { SwiftDate.defaultRegion = Region(calendar: Calendars.gregorian, zone: Zones.europeRome, locale: Locales.english) datesList().forEach { XCTTestFormatterParser(dateStr: $0.key, expected: $0.value["iso"]!, type: "iso", region: SwiftDate.defaultRegion) } } func XCTTestFormatterParser(dateStr: String, expected: String, type: String, region: Region = SwiftDate.defaultRegion) { guard let srcDate = dateStr.toDate("yyyy-MM-dd'T'HH:mm:ssZZZZZ", region: SwiftDate.defaultRegion) else { XCTFail("Failed to correctly parse date: '\(dateStr)'") return } let dateAsStr: String = dateToString(date: srcDate, format: type) XCTAssert( (dateAsStr == expected), "Failed to convert date '\(srcDate.description)' to \(type) format. Expected '\(expected)', got '\(dateAsStr)'") guard let decodedSrcDate = stringToDate(string: dateAsStr, format: type, region: region) else { XCTFail("Failed to convert date string to date of type \(type): \(dateAsStr)") return } XCTAssert( (srcDate == decodedSrcDate), "Failed to validate formatter. Got '\(decodedSrcDate)' expecting '\(srcDate)'") } func dateToString(date srcDate: DateInRegion, format: String) -> String { switch format { case "dotnet": return srcDate.toDotNET() case "rss": return srcDate.toRSS(alt: false) case "rss_alt": return srcDate.toRSS(alt: true) case "sql": return srcDate.toSQL() case "iso": return srcDate.toISO() default: XCTFail("Unsupported type: \(format)") return "" } } func stringToDate(string: String, format: String, region: Region) -> DateInRegion? { switch format { case "dotnet": return string.toDotNETDate(region: region) case "rss": return string.toRSSDate(alt: false, region: region) case "rss_alt": return string.toRSSDate(alt: true, region: region) case "sql": return string.toSQLDate(region: region) case "iso": return string.toISODate(nil, region: region) default: XCTFail("Unsupported type: \(format)") return nil } } func testTZInISOParser() { let gmtTimezone = "2017-08-05T16:04:03".toISODate(region: Region.ISO)! // force timezone let timezoneInDate = "2017-08-05T16:04:03+02:00".toISODate()! // parse from iso XCTAssert(gmtTimezone.region.timeZone.secondsFromGMT() == 0, "ISO Date does not contains timezone (is gmt)") XCTAssert(timezoneInDate.region.timeZone.secondsFromGMT() == 7200, "ISO Date does not contains timezone (is gmt)") } func testRSSAltLocale() { let regionAny = Region(calendar: Calendars.buddhist, zone: Zones.indianMayotte, locale: Locales.italian) // region must not use passed locale to perform parsing but only locale as final output let date1 = "Tue, 20 Jun 2017 14:49:19 +0200".toRSSDate(alt: false, region: regionAny) let date2 = "20 Jun 2017 14:49:19 +0200".toRSSDate(alt: true, region: regionAny) XCTAssertNotNil(date1, "Wrong RSS Date region") XCTAssertNotNil(date2, "Wrong RSS Alt Date region") } func testTimeInterval_Clock() { let value = (2.hours + 5.minutes).timeInterval.toClock() XCTAssert(value == "02:05:00", "Failed to format clock") let value2 = (4.minutes + 50.minutes).timeInterval.toClock(zero: DateComponentsFormatter.ZeroFormattingBehavior.dropAll) XCTAssert(value2 == "54", "Failed to format clock") } func testFormatterCustom() { let rome = Region(calendar: Calendars.gregorian, zone: Zones.europeRome, locale: Locales.italian) let date = DateInRegion(year: 2015, month: 1, day: 15, hour: 20, minute: 00, second: 5, nanosecond: 0, region: rome) let fixedFormat = date.toFormat("MMM dd yyyy", locale: Locales.english) let regionFormat = date.toFormat("MMM dd yyyy") XCTAssert( fixedFormat == "Jan 15 2015", "Failed to format with fixed locale") XCTAssert( regionFormat == "gen 15 2015", "Failed to format with standard locale") } func testTimeInterval_FormatterUnits() { // for TimeInterval let values = (36.hours + 2.days + 1.weeks).timeInterval.toUnits([.day, .hour]) XCTAssert(values[.hour] == 12 && values[.day] == 10, "Failed to extract day components") let singleValue = (1.days).timeInterval.toUnit(.minute) XCTAssert(singleValue == 1440, "Failed to extract single date component") } func testTimeInterval_Formatter() { let value1 = (2.hours + 5.minutes + 32.seconds).timeInterval.toString(options: { $0.unitsStyle = .full $0.collapsesLargestUnit = false $0.allowsFractionalUnits = true $0.locale = Locales.english }) let value2 = (5.hours + 2.days).timeInterval.toString(options: { $0.unitsStyle = .abbreviated $0.locale = Locales.english }) XCTAssert(value1 == "2 hours, 5 minutes, 32 seconds", "Failed to format interval to string") XCTAssert(value2 == "2d 5h", "Failed to format interval to string") } /* func testColloquialFormatter() { let ago5Mins = DateInRegion() - 5.minutes let r1 = ago5Mins.toRelative(style: RelativeFormatter.defaultStyle(), locale: Locales.italian) let r2 = ago5Mins.toRelative(style: RelativeFormatter.twitterStyle(), locale: Locales.italian) XCTAssert(r1 == "5 minuti fa", "Failed to use colloquial formatter") XCTAssert(r2 == "5 min fa", "Failed to use colloquial formatter") let justNow = DateInRegion() - 10.seconds let r3 = justNow.toRelative(style: RelativeFormatter.defaultStyle(), locale: Locales.italian) XCTAssert(r3 == "ora", "Failed to use colloquial formatter") let justNow2 = DateInRegion() - 2.hours let r4 = justNow2.toRelative(style: RelativeFormatter.twitterStyle(), locale: Locales.italian) XCTAssert(r4 == "2h fa", "Failed to use colloquial formatter") let justNow3 = DateInRegion() - 1.minutes let r5 = justNow3.toRelative(style: RelativeFormatter.twitterStyle(), locale: Locales.english) XCTAssert(r5 == "1 min. ago", "Failed to use colloquial formatter") let justNow4 = DateInRegion() - 51.seconds let r6 = justNow4.toRelative(style: RelativeFormatter.twitterStyle(), locale: Locales.english) XCTAssert(r6 == "1 min. ago", "Failed to use colloquial formatter") } */ func testISOParser() { func testISO(_ src: String, _ exp: String) { let output = src.toISODate()?.toISO() XCTAssert(output == exp, "Failed to parse ISO '\(src)'") } testISO("20060506", "2006-05-06T00:00:00Z") // YYYYMMDD testISO("2001-12-14", "2001-12-14T00:00:00Z") // YYYY-MM-DD testISO("2001-06", "2001-06-01T00:00:00Z") // YYYY-MM testISO("2015", "2015-01-01T00:00:00Z") // YYYY //testISO("15", "1518-01-01T00:00:00Z") // YY // Implied century: YY is 00-99 testISO("150603", "2015-06-03T00:00:00Z") // YYMMDD testISO("161201", "2016-12-01T00:00:00Z") // YY-MM-DD // Implied year testISO("--1215", "\(Date().year)-12-15T00:00:00Z") // --MMDD testISO("--12-15", "\(Date().year)-12-15T00:00:00Z") // --MM-DD testISO("--12", "\(Date().year)-12-01T00:00:00Z") // --MM // Implied year and month testISO("---15", "\(Date().year)-\(String(format: "%02d", Date().month))-15T00:00:00Z") // ---DD // Ordinal dates: DDD is the number of the day in the year (1-366) testISO("2015010", "2015-01-10T00:00:00Z") // YYYYDDD (DDD is the number of the day of the year) testISO("2015032", "2015-02-01T00:00:00Z") // YYYY-DDD testISO("15-032", "2015-02-01T00:00:00Z") // YY-DDD testISO("15032", "2015-02-01T00:00:00Z") // YYDDD testISO("-032", "\(Date().year)-02-01T00:00:00Z") // -DDD // Week-based dates: ww is the number of the week, and d is the number (1-7) of the day in the week testISO("2018W012", "2018-01-02T00:00:00Z") // yyyyWwwd testISO("2018-W01-2", "2018-01-02T00:00:00Z") // yyyy-Www-d testISO("2018-W01", "2017-12-31T00:00:00Z") // yyyyWww testISO("2018-W01", "2017-12-31T00:00:00Z") // yyyy-Www testISO("18-W01", "2017-12-31T00:00:00Z") // yyWwwd testISO("18-W01-2", "2018-01-02T00:00:00Z") // yy-Www-d testISO("18W01", "2017-12-31T00:00:00Z") // yyWww testISO("18-W01", "2017-12-31T00:00:00Z") // yy-Www // Date testISO("1970-01-01", "1970-01-01T00:00:00Z") testISO("2001", "2001-01-01T00:00:00Z") testISO("2001-02-03", "2001-02-03T00:00:00Z") testISO("2001-02-03T04:05", "2001-02-03T04:05:00Z") testISO("2001-02-03T04:05:06.007-06:30", "2001-02-03T04:05:06-06:30") } } ================================================ FILE: Tests/SwiftDateTests/TestRegion.swift ================================================ // // 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 SwiftDate import XCTest class TestRegion: XCTestCase { func test() { let ago5Mins = DateInRegion() - 5.minutes let x = ago5Mins.toRelative(since: nil, dateTimeStyle: .named, unitsStyle: .short) print(x) } func testRegionInit() { SwiftDate.defaultRegion = Region(calendar: Calendars.gregorian, zone: Zones.gmt, locale: Locales.english) // UTC Region XCTAssert( (Region.UTC.timeZone.identifier == Zones.gmt.toTimezone().identifier), "Failed to create UTC region") XCTAssert( (Region.UTC.calendar.identifier == Calendar.autoupdatingCurrent.identifier), "Failed to inherith the appropriate calendar fromd default region") XCTAssert( (Region.UTC.locale.identifier == Locale.autoupdatingCurrent.identifier), "Failed to inherith the appropriate locale fromd default region") // New Region let region1 = Region(calendar: Calendars.gregorian, zone: Zones.europeRome, locale: Locales.italian) XCTAssert( (region1.timeZone.identifier == "Europe/Rome"), "Failed to set region's zone") XCTAssert( (region1.calendar.identifier == Calendar.Identifier.gregorian), "Failed to set region's calendar") XCTAssert( (region1.locale.identifier == "it"), "Failed to set region's locale") // Current Region let currentRegion = Region.current XCTAssert( (currentRegion.calendar.identifier == Calendar.current.identifier), "Failed to set current's region calendar") XCTAssert( (currentRegion.timeZone.identifier == TimeZone.current.identifier), "Failed to set current's region timezone") XCTAssert( (currentRegion.locale.identifier == Locale.current.identifier), "Failed to set current's region locale") // Default region in another locale and calendar let modifiedDefaultRegion = Region.currentIn(locale: Locales.japanese, calendar: Calendars.japanese) XCTAssert( (modifiedDefaultRegion.locale.identifier == Locale(identifier: "ja").identifier), "Failed to create new region from default with modified locale") XCTAssert( (modifiedDefaultRegion.calendar.identifier == Calendar(identifier: Calendar.Identifier.japanese).identifier), "Failed to create new region from default with modified calendar") // Default region in another locale and calendar, no action let unmodifiedDefaultRegion = Region.currentIn() XCTAssert( (unmodifiedDefaultRegion == SwiftDate.defaultRegion), "currentIn() with no parameters should return unmodified default region") // Default region in another locale and calendar, only calendar let modifiedCalendarDefaultRegion = Region.currentIn(calendar: Calendars.buddhist) XCTAssert( (modifiedCalendarDefaultRegion.calendar.identifier == Calendar.Identifier.buddhist), "currentIn() with modified calendar only does not work") XCTAssert( (modifiedCalendarDefaultRegion.locale == SwiftDate.defaultRegion.locale), "currentIn() with modified calendar also modify the locale") // Default region in another locale and calendar, only locale let modifiedLocaleDefaultRegion = Region.currentIn(locale: Locales.italian) XCTAssert( (modifiedLocaleDefaultRegion.locale.identifier == Locale(identifier: "it").identifier), "currentIn() with modified locale only does not work") XCTAssert( (modifiedLocaleDefaultRegion.calendar.identifier == SwiftDate.defaultRegion.calendar.identifier), "currentIn() with modified locale also modify the calendar") // Init from DateComponents var dComps = DateComponents() dComps.calendar = Calendar(identifier: Calendar.Identifier.coptic) dComps.timeZone = TimeZone(identifier: "Pacific/Truk") let regionFromComponents = Region(fromDateComponents: dComps) XCTAssert( (regionFromComponents.calendar == dComps.calendar!), "Failed to create new region from date components / calendar") XCTAssert( (regionFromComponents.timeZone == dComps.timeZone!), "Failed to create new region from date components / timezone") // Compare two regions let regionA = Region(calendar: Calendars.gregorian, zone: Zones.europeOslo, locale: Locales.english) let regionB = Region(calendar: Calendars.gregorian, zone: Zones.europeOslo, locale: Locales.english) let regionC = Region(calendar: Calendars.buddhist, zone: Zones.europeOslo, locale: Locales.english) let regionD = Region(calendar: Calendars.gregorian, zone: Zones.europeRome, locale: Locales.english) let regionE = Region(calendar: Calendars.buddhist, zone: Zones.europeOslo, locale: Locales.italian) XCTAssert( (regionA == regionB), "Failed to compare two regions") XCTAssert( (regionC != regionD && regionD != regionE), "Failed to compare two regions") // Codable/Decodable for Region do { let encodedJSON_A = try JSONEncoder().encode(regionA) let encodedJSON_B = try JSONEncoder().encode(regionB) XCTAssert( (encodedJSON_A == encodedJSON_B), "Same data regions does not encode the same") let decodedJSON_RegionA = try JSONDecoder().decode(Region.self, from: encodedJSON_A) let decodedJSON_RegionB = try JSONDecoder().decode(Region.self, from: encodedJSON_B) XCTAssert( (decodedJSON_RegionA == decodedJSON_RegionB), "Same data decoded region are not the same") let stringJSON_A = String(data: encodedJSON_A, encoding: .utf8) let compareStringJSON_A = "{\"timezone\":\"Europe\\/Oslo\",\"locale\":\"en\",\"calendar\":\"gregorian\"}" XCTAssert( (stringJSON_A! == compareStringJSON_A), "JSON differ in encodable") } catch let err { XCTFail("Failed to test encodable/decodable on Region: \(err)") } // Description let descriptionRegion_A = regionA.description let expectedDescRegion_A = "{calendar='gregorian', timezone='Europe/Oslo', locale='en'}" XCTAssert( (descriptionRegion_A == expectedDescRegion_A), "Region description differ from expected") // Hash value let hash_regionA = regionA.hashValue let hash_regionB = regionB.hashValue XCTAssert( (hash_regionA == hash_regionB), "Hash value fails") // Current date in this region let regionIT = Region(calendar: Calendars.gregorian, zone: Zones.europeRome, locale: Locales.italian) let nowInIT = regionIT.nowInThisRegion() XCTAssert( (nowInIT.region == regionIT), "Produced date in Region.nowInThisRegion does not have the same origin Region") XCTAssertInTimeIntervalRange(value: nowInIT.date.timeIntervalSinceNow, range: 2, "Produced date in Region.nowInThisRegion does not have the current date") // Init with default region data let regionWithDefault = Region() XCTAssert( (regionWithDefault == SwiftDate.defaultRegion), "Failed to create new Region from default region") // Init with only fixed calendar let defaultRegion_fixedCal = Region(calendar: Calendars.buddhist) XCTAssert( (defaultRegion_fixedCal.calendar.identifier == Calendar.Identifier.buddhist), "Failed to new region from default with fixed only calendar / different calendar") XCTAssert( (defaultRegion_fixedCal.locale.identifier == SwiftDate.defaultRegion.locale.identifier), "Failed to new region from default with fixed only calendar / different locale") XCTAssert( (defaultRegion_fixedCal.timeZone.identifier == SwiftDate.defaultRegion.timeZone.identifier), "Failed to new region from default with fixed only calendar / different timezone") } } func XCTAssertInTimeIntervalRange(value: Double, range: TimeInterval, _ error: String) { guard value >= (value - range) && value <= (value + range) else { XCTFail(error) return } } ================================================ FILE: Tests/SwiftDateTests/TestSwiftDate.swift ================================================ // // 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 SwiftDate import XCTest class TestSwiftDate: XCTestCase { func testAutoFormats() { let builtInAutoFormats = SwiftDate.autoFormats XCTAssert((SwiftDate.autoFormats.isEmpty == false), "No auto formats available") let newFormats = [DateFormats.altRSS, DateFormats.extended, DateFormats.httpHeader] SwiftDate.autoFormats = newFormats XCTAssert( (SwiftDate.autoFormats == newFormats), "Failed to set new auto formats") SwiftDate.resetAutoFormats() XCTAssert( (SwiftDate.autoFormats == builtInAutoFormats), "Failed to reset auto formats") } func testUTCZone() { SwiftDate.defaultRegion = Region(calendar: Calendars.gregorian, zone: Zones.asiaShanghai, locale: Locales.current) // DO NOT recognized the right timezone // The timezone should be UTC let wrongZone = "2020-03-13T05:40:48.000Z" let wrongZoneDate = Date.init(wrongZone) print(wrongZoneDate!.description) XCTAssert("2020-03-13 05:40:48 +0000" == wrongZoneDate!.description) let iso8601Time = "2020-03-13T05:40:48+00:00" let iso8601Date = Date.init(iso8601Time) print(iso8601Date!.description) XCTAssert("2020-03-13 05:40:48 +0000" == iso8601Date!.description) } } ================================================ FILE: generateLinuxTests.sh ================================================ sourcery --sources Tests --templates .sourcery/LinuxMain.stencil --output .sourcery --force-parse generated mv .sourcery/LinuxMain.generated.swift Tests/LinuxMain.swift