├── .DS_Store ├── .gitignore ├── .swift-version ├── LICENSE ├── README.md ├── RelativeFormatter.podspec ├── RelativeFormatter.xcodeproj ├── Info.plist ├── project.pbxproj ├── project.xcworkspace │ └── contents.xcworkspacedata └── xcshareddata │ └── xcschemes │ └── RelativeFormatter.xcscheme ├── Source ├── LocalizationHelper.swift ├── RelativeFormatter.bundle │ ├── en.lproj │ │ └── RelativeFormatter.strings │ ├── es.lproj │ │ └── RelativeFormatter.strings │ ├── fr.lproj │ │ └── RelativeFormatter.strings │ ├── pt.lproj │ │ └── RelativeFormatter.strings │ ├── zh-Hans.lproj │ │ └── RelativeFormatter.strings │ └── zh-Hant.lproj │ │ └── RelativeFormatter.strings └── RelativeFormatter.swift └── Tests ├── Info.plist ├── RelativeFormatterIdiomaticTests.swift └── RelativeFormatterTests.swift /.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bitomule/RelativeFormatter/d15b2d38e1d94d513764c9f6aa9fcb022d76d7db/.DS_Store -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Xcode 2 | # 3 | build/ 4 | *.pbxuser 5 | !default.pbxuser 6 | *.mode1v3 7 | !default.mode1v3 8 | *.mode2v3 9 | !default.mode2v3 10 | *.perspectivev3 11 | !default.perspectivev3 12 | xcuserdata 13 | *.xccheckout 14 | *.moved-aside 15 | DerivedData 16 | *.hmap 17 | *.ipa 18 | *.xcuserstate 19 | 20 | # CocoaPods 21 | # 22 | # We recommend against adding the Pods directory to your .gitignore. However 23 | # you should judge for yourself, the pros and cons are mentioned at: 24 | # http://guides.cocoapods.org/using/using-cocoapods.html#should-i-ignore-the-pods-directory-in-source-control 25 | # 26 | # Pods/ 27 | 28 | # Carthage 29 | # 30 | # Add this line if you want to avoid checking in source code from Carthage dependencies. 31 | # Carthage/Checkouts 32 | 33 | Carthage/Build 34 | -------------------------------------------------------------------------------- /.swift-version: -------------------------------------------------------------------------------- 1 | 3.0 2 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 bitomule 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # RelativeFormatter 2 | Date swift extension to format dates according to current date. 3 | 4 | ## Features 5 | 6 | - [x] Format Date as Time ago 7 | - [x] Format Date as Time ahead 8 | - [x] Format using idiomatic style (today,yesterday,tomorrow) 9 | - [x] Set format precision (years,months,weeks,days,hours,minutes and seconds) 10 | 11 | ## Requirements 12 | 13 | - iOS 8.0+ / Mac OS X 10.9+ 14 | - Xcode 8.0 15 | - Swift 3 16 | 17 | ## Installation 18 | 19 | > **Embedded frameworks require a minimum deployment target of iOS 8 or OS X Mavericks.** 20 | > 21 | 22 | 23 | ### CocoaPods 24 | 25 | [CocoaPods][1] is a dependency manager for Cocoa projects. 26 | 27 | CocoaPods 0.36 adds supports for Swift and embedded frameworks. You can install it with the following command: 28 | 29 | ```bash 30 | $ gem install cocoapods 31 | ``` 32 | 33 | To integrate RelativeFormatter into your Xcode project using CocoaPods, specify it in your `Podfile`: 34 | 35 | ```ruby 36 | source 'https://github.com/CocoaPods/Specs.git' 37 | platform :ios, '8.0' 38 | use_frameworks! 39 | 40 | pod 'RelativeFormatter' 41 | ``` 42 | 43 | Then, run the following command: 44 | 45 | ```bash 46 | $ pod install 47 | ``` 48 | 49 | ### How To Use 50 | 51 | RelativeFormatter is just an NSDate extension, you can use it with any NSDate object: 52 | 53 | There’s only one function to call: 54 | 55 | ```swift 56 | relativeFormatted(idiomatic:Bool=false,precision:Precision=Precision.Second)->String 57 | ``` 58 | 59 | Both parameters aren’t required. 60 | 61 | - idiomatic:Bool 62 | 63 | This parameter is false by default and allows you to use idiomatic date format or just numbers. 64 | 65 | Example: 66 | 67 | ```swift 68 | //oldDate is yesterday date 69 | 70 | oldDate.relativeFormatted() 71 | 72 | //outputs 73 | // “1 day ago” 74 | 75 | oldDate.relativeFormatted(idiomatic:true) 76 | 77 | //outputs 78 | // “yesterday” 79 | ``` 80 | 81 | - precision:Precision 82 | 83 | Precision parameter allows you to define the format precission. Default value is seconds. 84 | 85 | Example: 86 | 87 | 88 | ```swift 89 | todayDate.relativeFormatted(precision:Precision.Year) 90 | 91 | //outputs 92 | // “this year” 93 | 94 | todayDate.relativeFormatted(precision:Precision.Month) 95 | 96 | //outputs 97 | // “this month” 98 | 99 | todayDate.relativeFormatted(precision:Precision.Day) 100 | 101 | //outputs 102 | // “today” 103 | 104 | todayDate.relativeFormatted(precision:Precision.Hour) 105 | 106 | //outputs 107 | // “3 hours ago” 108 | ``` 109 | 110 | You can always use relativeFormatted with default parameters. 111 | If you have an NSDate representing a date 2 months ago just use: 112 | 113 | ```swift 114 | oldDate.relativeFormatted() 115 | ``` 116 | 117 | And you'll get: 118 | 119 | "2 months ago" 120 | 121 | It also works for ahead dates (date in 3 years): 122 | 123 | ```swift 124 | futureDate.relativeFormatted() 125 | ``` 126 | 127 | will return: 128 | 129 | "In 3 years" 130 | 131 | ### Languages 132 | 133 | RelativeFormatter includes localization for: 134 | 135 | - [x] English 136 | - [x] Spanish 137 | - [x] French 138 | 139 | If you can to include a new language please create a pull request 140 | 141 | [1]: http://cocoapods.org -------------------------------------------------------------------------------- /RelativeFormatter.podspec: -------------------------------------------------------------------------------- 1 | # 2 | # Be sure to run `pod spec lint NSDate+RelativeFormatter.podspec' to ensure this is a 3 | # valid spec and to remove all comments including this before submitting the spec. 4 | # 5 | # To learn more about Podspec attributes see http://docs.cocoapods.org/specification.html 6 | # To see working Podspecs in the CocoaPods repo see https://github.com/CocoaPods/Specs/ 7 | # 8 | 9 | Pod::Spec.new do |s| 10 | s.name = "RelativeFormatter" 11 | s.version = "0.8.3" 12 | s.summary = "Date swift extension to format dates relative to current date." 13 | 14 | s.description = <<-DESC 15 | RelativeFormatter adds the relativeFormatted function to Date allowing you to get. 16 | 17 | * Time ago formatted dates "2 days ago" 18 | * Time in the future formatted dates "In 2 days" 19 | DESC 20 | 21 | s.homepage = "https://github.com/bitomule/RelativeFormatter" 22 | s.license = "MIT" 23 | 24 | 25 | s.author = { "David Collado Sela" => "bitomule@gmail.com", "David Santana" => "David@2coders.com" } 26 | s.social_media_url = "http://twitter.com/bitomule" 27 | 28 | s.ios.deployment_target = '8.0' 29 | s.osx.deployment_target = '10.9' 30 | s.watchos.deployment_target = '2.0' 31 | s.tvos.deployment_target = '9.0' 32 | 33 | s.source = { :git => "https://github.com/bitomule/RelativeFormatter.git", :tag => "0.8.3" } 34 | 35 | s.source_files = 'Source/*.swift' 36 | s.resources = 'Source/RelativeFormatter.bundle' 37 | s.requires_arc = true 38 | 39 | end 40 | -------------------------------------------------------------------------------- /RelativeFormatter.xcodeproj/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | FMWK 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | $(CURRENT_PROJECT_VERSION) 23 | NSPrincipalClass 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /RelativeFormatter.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 414121071B03345100DF7EB3 /* RelativeFormatterTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 414121061B03345100DF7EB3 /* RelativeFormatterTests.swift */; }; 11 | 41A56CE61B022F0200347FE2 /* RelativeFormatter.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 41A56CDA1B022F0200347FE2 /* RelativeFormatter.framework */; }; 12 | 41A56D021B02416E00347FE2 /* RelativeFormatter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 41A56CFC1B02416E00347FE2 /* RelativeFormatter.swift */; }; 13 | 41A56D031B02416E00347FE2 /* Info.plist in Resources */ = {isa = PBXBuildFile; fileRef = 41A56CFE1B02416E00347FE2 /* Info.plist */; }; 14 | 41DE8FF41B024F5C00ABAFD1 /* RelativeFormatter.bundle in Resources */ = {isa = PBXBuildFile; fileRef = 41DE8FF31B024F5C00ABAFD1 /* RelativeFormatter.bundle */; }; 15 | 41DE8FF61B02508A00ABAFD1 /* LocalizationHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 41DE8FF51B02508A00ABAFD1 /* LocalizationHelper.swift */; }; 16 | 41DE8FF71B0250BC00ABAFD1 /* RelativeFormatter.bundle in Resources */ = {isa = PBXBuildFile; fileRef = 41DE8FF31B024F5C00ABAFD1 /* RelativeFormatter.bundle */; }; 17 | 41DE8FF81B02533800ABAFD1 /* RelativeFormatterIdiomaticTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 41A56CFF1B02416E00347FE2 /* RelativeFormatterIdiomaticTests.swift */; }; 18 | 41DE8FF91B02536B00ABAFD1 /* RelativeFormatter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 41A56CFC1B02416E00347FE2 /* RelativeFormatter.swift */; }; 19 | 41DE8FFA1B02536F00ABAFD1 /* LocalizationHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 41DE8FF51B02508A00ABAFD1 /* LocalizationHelper.swift */; }; 20 | /* End PBXBuildFile section */ 21 | 22 | /* Begin PBXContainerItemProxy section */ 23 | 41A56CE71B022F0200347FE2 /* PBXContainerItemProxy */ = { 24 | isa = PBXContainerItemProxy; 25 | containerPortal = 41A56CD11B022F0200347FE2 /* Project object */; 26 | proxyType = 1; 27 | remoteGlobalIDString = 41A56CD91B022F0200347FE2; 28 | remoteInfo = "NSDate+RelativeFormatter"; 29 | }; 30 | /* End PBXContainerItemProxy section */ 31 | 32 | /* Begin PBXFileReference section */ 33 | 414121061B03345100DF7EB3 /* RelativeFormatterTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RelativeFormatterTests.swift; sourceTree = ""; }; 34 | 41A56CDA1B022F0200347FE2 /* RelativeFormatter.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = RelativeFormatter.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 35 | 41A56CE51B022F0200347FE2 /* RelativeFormatterTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RelativeFormatterTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 36 | 41A56CFC1B02416E00347FE2 /* RelativeFormatter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RelativeFormatter.swift; sourceTree = ""; }; 37 | 41A56CFE1B02416E00347FE2 /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 38 | 41A56CFF1B02416E00347FE2 /* RelativeFormatterIdiomaticTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RelativeFormatterIdiomaticTests.swift; sourceTree = ""; }; 39 | 41DE8FF31B024F5C00ABAFD1 /* RelativeFormatter.bundle */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.plug-in"; path = RelativeFormatter.bundle; sourceTree = ""; }; 40 | 41DE8FF51B02508A00ABAFD1 /* LocalizationHelper.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LocalizationHelper.swift; sourceTree = ""; }; 41 | /* End PBXFileReference section */ 42 | 43 | /* Begin PBXFrameworksBuildPhase section */ 44 | 41A56CD61B022F0200347FE2 /* Frameworks */ = { 45 | isa = PBXFrameworksBuildPhase; 46 | buildActionMask = 2147483647; 47 | files = ( 48 | ); 49 | runOnlyForDeploymentPostprocessing = 0; 50 | }; 51 | 41A56CE21B022F0200347FE2 /* Frameworks */ = { 52 | isa = PBXFrameworksBuildPhase; 53 | buildActionMask = 2147483647; 54 | files = ( 55 | 41A56CE61B022F0200347FE2 /* RelativeFormatter.framework in Frameworks */, 56 | ); 57 | runOnlyForDeploymentPostprocessing = 0; 58 | }; 59 | /* End PBXFrameworksBuildPhase section */ 60 | 61 | /* Begin PBXGroup section */ 62 | 41A56CD01B022F0200347FE2 = { 63 | isa = PBXGroup; 64 | children = ( 65 | 41A56CF91B02416E00347FE2 /* Source */, 66 | 41A56CFD1B02416E00347FE2 /* Tests */, 67 | 41A56CDB1B022F0200347FE2 /* Products */, 68 | ); 69 | sourceTree = ""; 70 | }; 71 | 41A56CDB1B022F0200347FE2 /* Products */ = { 72 | isa = PBXGroup; 73 | children = ( 74 | 41A56CDA1B022F0200347FE2 /* RelativeFormatter.framework */, 75 | 41A56CE51B022F0200347FE2 /* RelativeFormatterTests.xctest */, 76 | ); 77 | name = Products; 78 | sourceTree = ""; 79 | }; 80 | 41A56CF91B02416E00347FE2 /* Source */ = { 81 | isa = PBXGroup; 82 | children = ( 83 | 41A56CFC1B02416E00347FE2 /* RelativeFormatter.swift */, 84 | 41DE8FF31B024F5C00ABAFD1 /* RelativeFormatter.bundle */, 85 | 41DE8FF51B02508A00ABAFD1 /* LocalizationHelper.swift */, 86 | ); 87 | path = Source; 88 | sourceTree = ""; 89 | }; 90 | 41A56CFD1B02416E00347FE2 /* Tests */ = { 91 | isa = PBXGroup; 92 | children = ( 93 | 41A56CFE1B02416E00347FE2 /* Info.plist */, 94 | 41A56CFF1B02416E00347FE2 /* RelativeFormatterIdiomaticTests.swift */, 95 | 414121061B03345100DF7EB3 /* RelativeFormatterTests.swift */, 96 | ); 97 | path = Tests; 98 | sourceTree = ""; 99 | }; 100 | /* End PBXGroup section */ 101 | 102 | /* Begin PBXHeadersBuildPhase section */ 103 | 41A56CD71B022F0200347FE2 /* Headers */ = { 104 | isa = PBXHeadersBuildPhase; 105 | buildActionMask = 2147483647; 106 | files = ( 107 | ); 108 | runOnlyForDeploymentPostprocessing = 0; 109 | }; 110 | /* End PBXHeadersBuildPhase section */ 111 | 112 | /* Begin PBXNativeTarget section */ 113 | 41A56CD91B022F0200347FE2 /* RelativeFormatter */ = { 114 | isa = PBXNativeTarget; 115 | buildConfigurationList = 41A56CF01B022F0200347FE2 /* Build configuration list for PBXNativeTarget "RelativeFormatter" */; 116 | buildPhases = ( 117 | 41A56CD51B022F0200347FE2 /* Sources */, 118 | 41A56CD61B022F0200347FE2 /* Frameworks */, 119 | 41A56CD71B022F0200347FE2 /* Headers */, 120 | 41A56CD81B022F0200347FE2 /* Resources */, 121 | ); 122 | buildRules = ( 123 | ); 124 | dependencies = ( 125 | ); 126 | name = RelativeFormatter; 127 | productName = "NSDate+RelativeFormatter"; 128 | productReference = 41A56CDA1B022F0200347FE2 /* RelativeFormatter.framework */; 129 | productType = "com.apple.product-type.framework"; 130 | }; 131 | 41A56CE41B022F0200347FE2 /* RelativeFormatterTests */ = { 132 | isa = PBXNativeTarget; 133 | buildConfigurationList = 41A56CF31B022F0200347FE2 /* Build configuration list for PBXNativeTarget "RelativeFormatterTests" */; 134 | buildPhases = ( 135 | 41A56CE11B022F0200347FE2 /* Sources */, 136 | 41A56CE21B022F0200347FE2 /* Frameworks */, 137 | 41A56CE31B022F0200347FE2 /* Resources */, 138 | ); 139 | buildRules = ( 140 | ); 141 | dependencies = ( 142 | 41A56CE81B022F0200347FE2 /* PBXTargetDependency */, 143 | ); 144 | name = RelativeFormatterTests; 145 | productName = "NSDate+RelativeFormatterTests"; 146 | productReference = 41A56CE51B022F0200347FE2 /* RelativeFormatterTests.xctest */; 147 | productType = "com.apple.product-type.bundle.unit-test"; 148 | }; 149 | /* End PBXNativeTarget section */ 150 | 151 | /* Begin PBXProject section */ 152 | 41A56CD11B022F0200347FE2 /* Project object */ = { 153 | isa = PBXProject; 154 | attributes = { 155 | LastSwiftMigration = 0700; 156 | LastSwiftUpdateCheck = 0700; 157 | LastUpgradeCheck = 0800; 158 | ORGANIZATIONNAME = "David Collado Sela"; 159 | TargetAttributes = { 160 | 41A56CD91B022F0200347FE2 = { 161 | CreatedOnToolsVersion = 6.3.1; 162 | LastSwiftMigration = 0800; 163 | }; 164 | 41A56CE41B022F0200347FE2 = { 165 | CreatedOnToolsVersion = 6.3.1; 166 | LastSwiftMigration = 0800; 167 | }; 168 | }; 169 | }; 170 | buildConfigurationList = 41A56CD41B022F0200347FE2 /* Build configuration list for PBXProject "RelativeFormatter" */; 171 | compatibilityVersion = "Xcode 3.2"; 172 | developmentRegion = English; 173 | hasScannedForEncodings = 0; 174 | knownRegions = ( 175 | en, 176 | ); 177 | mainGroup = 41A56CD01B022F0200347FE2; 178 | productRefGroup = 41A56CDB1B022F0200347FE2 /* Products */; 179 | projectDirPath = ""; 180 | projectRoot = ""; 181 | targets = ( 182 | 41A56CD91B022F0200347FE2 /* RelativeFormatter */, 183 | 41A56CE41B022F0200347FE2 /* RelativeFormatterTests */, 184 | ); 185 | }; 186 | /* End PBXProject section */ 187 | 188 | /* Begin PBXResourcesBuildPhase section */ 189 | 41A56CD81B022F0200347FE2 /* Resources */ = { 190 | isa = PBXResourcesBuildPhase; 191 | buildActionMask = 2147483647; 192 | files = ( 193 | 41DE8FF41B024F5C00ABAFD1 /* RelativeFormatter.bundle in Resources */, 194 | 41A56D031B02416E00347FE2 /* Info.plist in Resources */, 195 | ); 196 | runOnlyForDeploymentPostprocessing = 0; 197 | }; 198 | 41A56CE31B022F0200347FE2 /* Resources */ = { 199 | isa = PBXResourcesBuildPhase; 200 | buildActionMask = 2147483647; 201 | files = ( 202 | 41DE8FF71B0250BC00ABAFD1 /* RelativeFormatter.bundle in Resources */, 203 | ); 204 | runOnlyForDeploymentPostprocessing = 0; 205 | }; 206 | /* End PBXResourcesBuildPhase section */ 207 | 208 | /* Begin PBXSourcesBuildPhase section */ 209 | 41A56CD51B022F0200347FE2 /* Sources */ = { 210 | isa = PBXSourcesBuildPhase; 211 | buildActionMask = 2147483647; 212 | files = ( 213 | 41DE8FF61B02508A00ABAFD1 /* LocalizationHelper.swift in Sources */, 214 | 41A56D021B02416E00347FE2 /* RelativeFormatter.swift in Sources */, 215 | ); 216 | runOnlyForDeploymentPostprocessing = 0; 217 | }; 218 | 41A56CE11B022F0200347FE2 /* Sources */ = { 219 | isa = PBXSourcesBuildPhase; 220 | buildActionMask = 2147483647; 221 | files = ( 222 | 41DE8FFA1B02536F00ABAFD1 /* LocalizationHelper.swift in Sources */, 223 | 414121071B03345100DF7EB3 /* RelativeFormatterTests.swift in Sources */, 224 | 41DE8FF91B02536B00ABAFD1 /* RelativeFormatter.swift in Sources */, 225 | 41DE8FF81B02533800ABAFD1 /* RelativeFormatterIdiomaticTests.swift in Sources */, 226 | ); 227 | runOnlyForDeploymentPostprocessing = 0; 228 | }; 229 | /* End PBXSourcesBuildPhase section */ 230 | 231 | /* Begin PBXTargetDependency section */ 232 | 41A56CE81B022F0200347FE2 /* PBXTargetDependency */ = { 233 | isa = PBXTargetDependency; 234 | target = 41A56CD91B022F0200347FE2 /* RelativeFormatter */; 235 | targetProxy = 41A56CE71B022F0200347FE2 /* PBXContainerItemProxy */; 236 | }; 237 | /* End PBXTargetDependency section */ 238 | 239 | /* Begin XCBuildConfiguration section */ 240 | 41A56CEE1B022F0200347FE2 /* Debug */ = { 241 | isa = XCBuildConfiguration; 242 | buildSettings = { 243 | ALWAYS_SEARCH_USER_PATHS = NO; 244 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 245 | CLANG_CXX_LIBRARY = "libc++"; 246 | CLANG_ENABLE_MODULES = YES; 247 | CLANG_ENABLE_OBJC_ARC = YES; 248 | CLANG_WARN_BOOL_CONVERSION = YES; 249 | CLANG_WARN_CONSTANT_CONVERSION = YES; 250 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 251 | CLANG_WARN_EMPTY_BODY = YES; 252 | CLANG_WARN_ENUM_CONVERSION = YES; 253 | CLANG_WARN_INFINITE_RECURSION = YES; 254 | CLANG_WARN_INT_CONVERSION = YES; 255 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 256 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 257 | CLANG_WARN_UNREACHABLE_CODE = YES; 258 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 259 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 260 | COPY_PHASE_STRIP = NO; 261 | CURRENT_PROJECT_VERSION = 1; 262 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 263 | ENABLE_STRICT_OBJC_MSGSEND = YES; 264 | ENABLE_TESTABILITY = YES; 265 | GCC_C_LANGUAGE_STANDARD = gnu99; 266 | GCC_DYNAMIC_NO_PIC = NO; 267 | GCC_NO_COMMON_BLOCKS = YES; 268 | GCC_OPTIMIZATION_LEVEL = 0; 269 | GCC_PREPROCESSOR_DEFINITIONS = ( 270 | "DEBUG=1", 271 | "$(inherited)", 272 | ); 273 | GCC_SYMBOLS_PRIVATE_EXTERN = NO; 274 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 275 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 276 | GCC_WARN_UNDECLARED_SELECTOR = YES; 277 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 278 | GCC_WARN_UNUSED_FUNCTION = YES; 279 | GCC_WARN_UNUSED_VARIABLE = YES; 280 | INFOPLIST_FILE = RelativeFormatter.xcodeproj/info.plist; 281 | IPHONEOS_DEPLOYMENT_TARGET = 8.3; 282 | MTL_ENABLE_DEBUG_INFO = YES; 283 | ONLY_ACTIVE_ARCH = YES; 284 | SDKROOT = iphoneos; 285 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 286 | SWIFT_VERSION = 3.0; 287 | TARGETED_DEVICE_FAMILY = "1,2"; 288 | VERSIONING_SYSTEM = "apple-generic"; 289 | VERSION_INFO_PREFIX = ""; 290 | }; 291 | name = Debug; 292 | }; 293 | 41A56CEF1B022F0200347FE2 /* Release */ = { 294 | isa = XCBuildConfiguration; 295 | buildSettings = { 296 | ALWAYS_SEARCH_USER_PATHS = NO; 297 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 298 | CLANG_CXX_LIBRARY = "libc++"; 299 | CLANG_ENABLE_MODULES = YES; 300 | CLANG_ENABLE_OBJC_ARC = YES; 301 | CLANG_WARN_BOOL_CONVERSION = YES; 302 | CLANG_WARN_CONSTANT_CONVERSION = YES; 303 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 304 | CLANG_WARN_EMPTY_BODY = YES; 305 | CLANG_WARN_ENUM_CONVERSION = YES; 306 | CLANG_WARN_INFINITE_RECURSION = YES; 307 | CLANG_WARN_INT_CONVERSION = YES; 308 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 309 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 310 | CLANG_WARN_UNREACHABLE_CODE = YES; 311 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 312 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 313 | COPY_PHASE_STRIP = NO; 314 | CURRENT_PROJECT_VERSION = 1; 315 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 316 | ENABLE_NS_ASSERTIONS = NO; 317 | ENABLE_STRICT_OBJC_MSGSEND = YES; 318 | GCC_C_LANGUAGE_STANDARD = gnu99; 319 | GCC_NO_COMMON_BLOCKS = YES; 320 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 321 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 322 | GCC_WARN_UNDECLARED_SELECTOR = YES; 323 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 324 | GCC_WARN_UNUSED_FUNCTION = YES; 325 | GCC_WARN_UNUSED_VARIABLE = YES; 326 | INFOPLIST_FILE = RelativeFormatter.xcodeproj/Info.plist; 327 | IPHONEOS_DEPLOYMENT_TARGET = 8.3; 328 | MTL_ENABLE_DEBUG_INFO = NO; 329 | SDKROOT = iphoneos; 330 | SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; 331 | SWIFT_VERSION = 3.0; 332 | TARGETED_DEVICE_FAMILY = "1,2"; 333 | VALIDATE_PRODUCT = YES; 334 | VERSIONING_SYSTEM = "apple-generic"; 335 | VERSION_INFO_PREFIX = ""; 336 | }; 337 | name = Release; 338 | }; 339 | 41A56CF11B022F0200347FE2 /* Debug */ = { 340 | isa = XCBuildConfiguration; 341 | buildSettings = { 342 | CLANG_ENABLE_MODULES = YES; 343 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; 344 | DEFINES_MODULE = YES; 345 | DYLIB_COMPATIBILITY_VERSION = 1; 346 | DYLIB_CURRENT_VERSION = 1; 347 | DYLIB_INSTALL_NAME_BASE = "@rpath"; 348 | INFOPLIST_FILE = RelativeFormatter.xcodeproj/Info.plist; 349 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; 350 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 351 | PRODUCT_BUNDLE_IDENTIFIER = "com.bitomule.$(PRODUCT_NAME:rfc1034identifier)"; 352 | PRODUCT_NAME = "$(TARGET_NAME)"; 353 | SKIP_INSTALL = YES; 354 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 355 | SWIFT_VERSION = 3.0; 356 | }; 357 | name = Debug; 358 | }; 359 | 41A56CF21B022F0200347FE2 /* Release */ = { 360 | isa = XCBuildConfiguration; 361 | buildSettings = { 362 | CLANG_ENABLE_MODULES = YES; 363 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; 364 | DEFINES_MODULE = YES; 365 | DYLIB_COMPATIBILITY_VERSION = 1; 366 | DYLIB_CURRENT_VERSION = 1; 367 | DYLIB_INSTALL_NAME_BASE = "@rpath"; 368 | INFOPLIST_FILE = RelativeFormatter.xcodeproj/Info.plist; 369 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; 370 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 371 | PRODUCT_BUNDLE_IDENTIFIER = "com.bitomule.$(PRODUCT_NAME:rfc1034identifier)"; 372 | PRODUCT_NAME = "$(TARGET_NAME)"; 373 | SKIP_INSTALL = YES; 374 | SWIFT_VERSION = 3.0; 375 | }; 376 | name = Release; 377 | }; 378 | 41A56CF41B022F0200347FE2 /* Debug */ = { 379 | isa = XCBuildConfiguration; 380 | buildSettings = { 381 | FRAMEWORK_SEARCH_PATHS = ( 382 | "$(SDKROOT)/Developer/Library/Frameworks", 383 | "$(inherited)", 384 | ); 385 | GCC_PREPROCESSOR_DEFINITIONS = ( 386 | "DEBUG=1", 387 | "$(inherited)", 388 | ); 389 | INFOPLIST_FILE = RelativeFormatter.xcodeproj/Info.plist; 390 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 391 | PRODUCT_BUNDLE_IDENTIFIER = "com.bitomule.$(PRODUCT_NAME:rfc1034identifier)"; 392 | PRODUCT_NAME = "$(TARGET_NAME)"; 393 | SWIFT_VERSION = 3.0; 394 | }; 395 | name = Debug; 396 | }; 397 | 41A56CF51B022F0200347FE2 /* Release */ = { 398 | isa = XCBuildConfiguration; 399 | buildSettings = { 400 | FRAMEWORK_SEARCH_PATHS = ( 401 | "$(SDKROOT)/Developer/Library/Frameworks", 402 | "$(inherited)", 403 | ); 404 | INFOPLIST_FILE = RelativeFormatter.xcodeproj/Info.plist; 405 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 406 | PRODUCT_BUNDLE_IDENTIFIER = "com.bitomule.$(PRODUCT_NAME:rfc1034identifier)"; 407 | PRODUCT_NAME = "$(TARGET_NAME)"; 408 | SWIFT_VERSION = 3.0; 409 | }; 410 | name = Release; 411 | }; 412 | /* End XCBuildConfiguration section */ 413 | 414 | /* Begin XCConfigurationList section */ 415 | 41A56CD41B022F0200347FE2 /* Build configuration list for PBXProject "RelativeFormatter" */ = { 416 | isa = XCConfigurationList; 417 | buildConfigurations = ( 418 | 41A56CEE1B022F0200347FE2 /* Debug */, 419 | 41A56CEF1B022F0200347FE2 /* Release */, 420 | ); 421 | defaultConfigurationIsVisible = 0; 422 | defaultConfigurationName = Release; 423 | }; 424 | 41A56CF01B022F0200347FE2 /* Build configuration list for PBXNativeTarget "RelativeFormatter" */ = { 425 | isa = XCConfigurationList; 426 | buildConfigurations = ( 427 | 41A56CF11B022F0200347FE2 /* Debug */, 428 | 41A56CF21B022F0200347FE2 /* Release */, 429 | ); 430 | defaultConfigurationIsVisible = 0; 431 | defaultConfigurationName = Release; 432 | }; 433 | 41A56CF31B022F0200347FE2 /* Build configuration list for PBXNativeTarget "RelativeFormatterTests" */ = { 434 | isa = XCConfigurationList; 435 | buildConfigurations = ( 436 | 41A56CF41B022F0200347FE2 /* Debug */, 437 | 41A56CF51B022F0200347FE2 /* Release */, 438 | ); 439 | defaultConfigurationIsVisible = 0; 440 | defaultConfigurationName = Release; 441 | }; 442 | /* End XCConfigurationList section */ 443 | }; 444 | rootObject = 41A56CD11B022F0200347FE2 /* Project object */; 445 | } 446 | -------------------------------------------------------------------------------- /RelativeFormatter.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /RelativeFormatter.xcodeproj/xcshareddata/xcschemes/RelativeFormatter.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 33 | 39 | 40 | 41 | 42 | 43 | 49 | 50 | 51 | 52 | 53 | 54 | 64 | 65 | 71 | 72 | 73 | 74 | 75 | 76 | 82 | 83 | 89 | 90 | 91 | 92 | 94 | 95 | 98 | 99 | 100 | -------------------------------------------------------------------------------- /Source/LocalizationHelper.swift: -------------------------------------------------------------------------------- 1 | // 2 | // String+Local.swift 3 | // RelativeFormatter 4 | // 5 | // Created by David Collado Sela on 12/5/15. 6 | // Copyright (c) 2015 David Collado Sela. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | class LocalizationHelper{ 12 | class func localize(_ key:String,count:Int?=nil)->String{ 13 | let bundlePath = (Bundle(for: LocalizationHelper.self).resourcePath! as NSString).appendingPathComponent("RelativeFormatter.bundle") 14 | 15 | var localizedString = NSLocalizedString(key, tableName: "RelativeFormatter", bundle: Bundle(path: bundlePath)!, value: "", comment: "") 16 | 17 | if let count = count{ 18 | localizedString = String.localizedStringWithFormat(localizedString, count) 19 | } 20 | return localizedString 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /Source/RelativeFormatter.bundle/en.lproj/RelativeFormatter.strings: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bitomule/RelativeFormatter/d15b2d38e1d94d513764c9f6aa9fcb022d76d7db/Source/RelativeFormatter.bundle/en.lproj/RelativeFormatter.strings -------------------------------------------------------------------------------- /Source/RelativeFormatter.bundle/es.lproj/RelativeFormatter.strings: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bitomule/RelativeFormatter/d15b2d38e1d94d513764c9f6aa9fcb022d76d7db/Source/RelativeFormatter.bundle/es.lproj/RelativeFormatter.strings -------------------------------------------------------------------------------- /Source/RelativeFormatter.bundle/fr.lproj/RelativeFormatter.strings: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bitomule/RelativeFormatter/d15b2d38e1d94d513764c9f6aa9fcb022d76d7db/Source/RelativeFormatter.bundle/fr.lproj/RelativeFormatter.strings -------------------------------------------------------------------------------- /Source/RelativeFormatter.bundle/pt.lproj/RelativeFormatter.strings: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bitomule/RelativeFormatter/d15b2d38e1d94d513764c9f6aa9fcb022d76d7db/Source/RelativeFormatter.bundle/pt.lproj/RelativeFormatter.strings -------------------------------------------------------------------------------- /Source/RelativeFormatter.bundle/zh-Hans.lproj/RelativeFormatter.strings: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bitomule/RelativeFormatter/d15b2d38e1d94d513764c9f6aa9fcb022d76d7db/Source/RelativeFormatter.bundle/zh-Hans.lproj/RelativeFormatter.strings -------------------------------------------------------------------------------- /Source/RelativeFormatter.bundle/zh-Hant.lproj/RelativeFormatter.strings: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bitomule/RelativeFormatter/d15b2d38e1d94d513764c9f6aa9fcb022d76d7db/Source/RelativeFormatter.bundle/zh-Hant.lproj/RelativeFormatter.strings -------------------------------------------------------------------------------- /Source/RelativeFormatter.swift: -------------------------------------------------------------------------------- 1 | // 2 | // RelativeFormatter.swift 3 | // RelativeFormatter 4 | // 5 | // Created by David Collado Sela on 12/5/15. 6 | // Copyright (c) 2015 David Collado Sela. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | public enum Precision{ 12 | case year,month,week,day,hour,minute,second 13 | } 14 | 15 | extension Date{ 16 | 17 | public func relativeFormatted(_ idiomatic:Bool=false,precision:Precision=Precision.second)->String{ 18 | let calendar = Calendar.current 19 | let unitFlags: NSCalendar.Unit = [NSCalendar.Unit.minute, NSCalendar.Unit.hour, NSCalendar.Unit.day, NSCalendar.Unit.weekOfYear, NSCalendar.Unit.month, NSCalendar.Unit.year, NSCalendar.Unit.second] 20 | let now = Date().normalized(precision) 21 | let normalized = self.normalized(precision) 22 | let formattedDateData:(key:String,count:Int?) 23 | if(normalized.timeIntervalSince1970 < now.timeIntervalSince1970){ 24 | let components:DateComponents = (calendar as NSCalendar).components(unitFlags, from: normalized, to: now, options: []) 25 | formattedDateData = RelativeFormatter.getPastKeyAndCount(components,idiomatic:idiomatic,precision:precision) 26 | }else{ 27 | let components:DateComponents = (calendar as NSCalendar).components(unitFlags, from: now, to: normalized, options: []) 28 | formattedDateData = RelativeFormatter.getFutureKeyAndCount(components,idiomatic:idiomatic,precision:precision) 29 | } 30 | return LocalizationHelper.localize(formattedDateData.key,count:formattedDateData.count) 31 | } 32 | 33 | func normalized(_ precision:Precision)->Date{ 34 | let unitFlags: NSCalendar.Unit = [NSCalendar.Unit.minute, NSCalendar.Unit.hour, NSCalendar.Unit.day, NSCalendar.Unit.weekOfYear, NSCalendar.Unit.month, NSCalendar.Unit.year, NSCalendar.Unit.second] 35 | var nowDateNewcomponents = DateComponents() 36 | let nowComponets = (Calendar.current as NSCalendar).components(unitFlags, from: self) 37 | switch precision{ 38 | case .year: 39 | nowDateNewcomponents.year = nowComponets.year 40 | case .month: 41 | nowDateNewcomponents.year = nowComponets.year 42 | nowDateNewcomponents.month = nowComponets.month 43 | case .week: 44 | nowDateNewcomponents.year = nowComponets.year 45 | nowDateNewcomponents.month = nowComponets.month 46 | nowDateNewcomponents.weekOfYear = nowComponets.weekOfYear 47 | case .day: 48 | nowDateNewcomponents.year = nowComponets.year 49 | nowDateNewcomponents.month = nowComponets.month 50 | nowDateNewcomponents.weekOfYear = nowComponets.weekOfYear 51 | nowDateNewcomponents.day = nowComponets.day 52 | case .hour: 53 | nowDateNewcomponents.year = nowComponets.year 54 | nowDateNewcomponents.month = nowComponets.month 55 | nowDateNewcomponents.weekOfYear = nowComponets.weekOfYear 56 | nowDateNewcomponents.day = nowComponets.day 57 | nowDateNewcomponents.hour = nowComponets.hour 58 | case .minute: 59 | nowDateNewcomponents.year = nowComponets.year 60 | nowDateNewcomponents.month = nowComponets.month 61 | nowDateNewcomponents.weekOfYear = nowComponets.weekOfYear 62 | nowDateNewcomponents.day = nowComponets.day 63 | nowDateNewcomponents.hour = nowComponets.hour 64 | nowDateNewcomponents.minute = nowComponets.minute 65 | case .second: 66 | nowDateNewcomponents.year = nowComponets.year 67 | nowDateNewcomponents.month = nowComponets.month 68 | nowDateNewcomponents.weekOfYear = nowComponets.weekOfYear 69 | nowDateNewcomponents.day = nowComponets.day 70 | nowDateNewcomponents.hour = nowComponets.hour 71 | nowDateNewcomponents.minute = nowComponets.minute 72 | nowDateNewcomponents.second = nowComponets.second 73 | } 74 | return Calendar.current.date(from: nowDateNewcomponents)! 75 | } 76 | } 77 | 78 | class RelativeFormatter { 79 | 80 | class func getPastKeyAndCount(_ components:DateComponents,idiomatic:Bool,precision:Precision)->(key:String,count:Int?){ 81 | var key = "" 82 | var count:Int? 83 | if(components.year! >= 2){ 84 | count = components.year 85 | key = "yearsago" 86 | } 87 | else if(components.year! >= 1){ 88 | key = "yearago" 89 | } 90 | else if(components.year == 0 && precision == Precision.year){ 91 | return ("thisyear",nil) 92 | } 93 | else if(components.month! >= 2){ 94 | count = components.month 95 | key = "monthsago" 96 | } 97 | else if(components.month! >= 1){ 98 | key = "monthago" 99 | 100 | } 101 | else if(components.month == 0 && precision == Precision.month){ 102 | return ("thismonth",nil) 103 | } 104 | else if(components.weekOfYear! >= 2){ 105 | count = components.weekOfYear 106 | key = "weeksago" 107 | } 108 | else if(components.weekOfYear! >= 1){ 109 | key = "weekago" 110 | } 111 | else if(components.weekOfYear == 0 && precision == Precision.week){ 112 | return ("thisweek",nil) 113 | } 114 | else if(components.day! >= 2){ 115 | count = components.day 116 | key = "daysago" 117 | } 118 | else if(components.day! >= 1){ 119 | key = "dayago" 120 | if(idiomatic){ 121 | key = key + "-idiomatic" 122 | } 123 | } 124 | else if(components.day == 0 && precision == Precision.day){ 125 | return ("today",nil) 126 | } 127 | else if(components.hour! >= 2){ 128 | count = components.hour 129 | key = "hoursago" 130 | } 131 | else if(components.hour! >= 1){ 132 | key = "hourago" 133 | } 134 | else if(components.hour == 0 && precision == Precision.hour){ 135 | return ("thishour",nil) 136 | } 137 | else if(components.minute! >= 2){ 138 | count = components.minute 139 | key = "minutesago" 140 | } 141 | else if(components.minute! >= 1){ 142 | key = "minuteago" 143 | } 144 | else if(components.minute == 0 && precision == Precision.minute){ 145 | return ("thisminute",nil) 146 | } 147 | else if(components.second! >= 2){ 148 | count = components.second 149 | key = "secondsago" 150 | if(idiomatic){ 151 | key = key + "-idiomatic" 152 | } 153 | } 154 | else{ 155 | key = "secondago" 156 | if(idiomatic){ 157 | key = "now" 158 | } 159 | } 160 | 161 | return (key,count) 162 | } 163 | 164 | class func getFutureKeyAndCount(_ components:DateComponents,idiomatic:Bool,precision:Precision)->(key:String,count:Int?){ 165 | var key = "" 166 | var count:Int? 167 | if(components.year! >= 2){ 168 | count = components.year 169 | key = "yearsahead" 170 | } 171 | else if(components.year! >= 1){ 172 | key = "yearahead" 173 | } 174 | else if(components.year == 0 && precision == Precision.year){ 175 | return ("thisyear",nil) 176 | } 177 | else if(components.month! >= 2){ 178 | count = components.month 179 | key = "monthsahead" 180 | } 181 | else if(components.month! >= 1){ 182 | key = "monthahead" 183 | } 184 | else if(components.month == 0 && precision == Precision.month){ 185 | return ("thismonth",nil) 186 | } 187 | else if(components.weekOfYear! >= 2){ 188 | count = components.weekOfYear 189 | key = "weeksahead" 190 | } 191 | else if(components.weekOfYear! >= 1){ 192 | key = "weekahead" 193 | } 194 | else if(components.weekOfYear == 0 && precision == Precision.week){ 195 | return ("thisweek",nil) 196 | } 197 | else if(components.day! >= 2){ 198 | count = components.day 199 | key = "daysahead" 200 | } 201 | else if(components.day! >= 1){ 202 | key = "dayahead" 203 | if(idiomatic){ 204 | key = key + "-idiomatic" 205 | } 206 | } 207 | else if(components.day == 0 && precision == Precision.day){ 208 | return ("today",nil) 209 | } 210 | else if(components.hour! >= 2){ 211 | count = components.hour 212 | key = "hoursahead" 213 | } 214 | else if(components.hour! >= 1){ 215 | key = "hourahead" 216 | } 217 | else if(components.hour == 0 && precision == Precision.hour){ 218 | return ("thishour",nil) 219 | } 220 | else if(components.minute! >= 2){ 221 | count = components.minute 222 | key = "minutesahead" 223 | } 224 | else if(components.minute! >= 1){ 225 | key = "minuteahead" 226 | } 227 | else if(components.minute == 0 && precision == Precision.minute){ 228 | return ("thisminute",nil) 229 | } 230 | else if(components.second! >= 2){ 231 | count = components.second 232 | key = "secondsahead" 233 | if(idiomatic){ 234 | key = key + "-idiomatic" 235 | } 236 | } 237 | else{ 238 | key = "secondahead" 239 | if(idiomatic){ 240 | key = "now" 241 | } 242 | } 243 | 244 | return (key,count) 245 | } 246 | } 247 | -------------------------------------------------------------------------------- /Tests/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | com.bitomule.$(PRODUCT_NAME:rfc1034identifier) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | BNDL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1 23 | 24 | 25 | -------------------------------------------------------------------------------- /Tests/RelativeFormatterIdiomaticTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // RelativeFormatterIdiomaticTests.swift 3 | // RelativeFormatterIdiomaticTests 4 | // 5 | // Created by David Collado Sela on 12/5/15. 6 | // Copyright (c) 2015 David Collado Sela. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import XCTest 11 | 12 | class RelativeFormatterIdiomaticTests: XCTestCase { 13 | 14 | //MARK: - Past 15 | 16 | func testDayAgo(){ 17 | let oneDayAgo = dateByDaysDifference(-1) 18 | let localizedString = oneDayAgo.relativeFormatted(true) 19 | print(localizedString, terminator: "") 20 | XCTAssertEqual(localizedString, "yesterday", "yesterday") 21 | } 22 | 23 | func testSecondsAgo(){ 24 | let twoSecondsAgo = dateBySecondsDifference(-2) 25 | let localizedString = twoSecondsAgo.relativeFormatted(true) 26 | XCTAssertEqual(localizedString, "a few seconds ago", "a few seconds ago") 27 | } 28 | 29 | func testSecondAgo(){ 30 | let twoSecondsAgo = dateBySecondsDifference(-1) 31 | let localizedString = twoSecondsAgo.relativeFormatted(true) 32 | XCTAssertEqual(localizedString, "now", "now") 33 | } 34 | 35 | 36 | //MARK: - Future 37 | 38 | func testDayAhead(){ 39 | var oneDayAhead = dateByDaysDifference(1) 40 | oneDayAhead = oneDayAhead.addingTimeInterval(1) 41 | let localizedString = oneDayAhead.relativeFormatted(true) 42 | XCTAssertEqual(localizedString, "tomorrow", "tomorrow") 43 | } 44 | 45 | func testSecondsAhead(){ 46 | var twoSecondsAhead = dateBySecondsDifference(2) 47 | twoSecondsAhead = twoSecondsAhead.addingTimeInterval(1) 48 | let localizedString = twoSecondsAhead.relativeFormatted(true) 49 | XCTAssertEqual(localizedString, "in a few seconds", "in a few seconds") 50 | } 51 | 52 | func testSecondAhead(){ 53 | let oneSecondAhead = dateBySecondsDifference(1) 54 | let localizedString = oneSecondAhead.relativeFormatted(true) 55 | XCTAssertEqual(localizedString, "now", "now") 56 | } 57 | 58 | //MARK: - Helpers 59 | 60 | func dateByYearsDifference(_ years:Int) -> Date{ 61 | return (Calendar.current as NSCalendar).date(byAdding: NSCalendar.Unit.year, value: years, to: Date(), options: [])! 62 | } 63 | 64 | func dateByMonthsDifference(_ months:Int) -> Date{ 65 | return (Calendar.current as NSCalendar).date(byAdding: NSCalendar.Unit.month, value: months, to: Date(), options: [])! 66 | } 67 | 68 | func dateByWeeksDifference(_ weeks:Int) -> Date{ 69 | return (Calendar.current as NSCalendar).date(byAdding: NSCalendar.Unit.weekOfYear, value: weeks, to: Date(), options: [])! 70 | } 71 | 72 | func dateByDaysDifference(_ days:Int) -> Date{ 73 | return (Calendar.current as NSCalendar).date(byAdding: NSCalendar.Unit.day, value: days, to: Date(), options: [])! 74 | } 75 | 76 | func dateByHoursDifference(_ hours:Int) -> Date{ 77 | return (Calendar.current as NSCalendar).date(byAdding: NSCalendar.Unit.hour, value: hours, to: Date(), options: [])! 78 | } 79 | 80 | func dateByMinutesDifference(_ minutes:Int) -> Date{ 81 | return (Calendar.current as NSCalendar).date(byAdding: NSCalendar.Unit.minute, value: minutes, to: Date(), options: [])! 82 | } 83 | 84 | func dateBySecondsDifference(_ seconds:Int) -> Date{ 85 | return (Calendar.current as NSCalendar).date(byAdding: NSCalendar.Unit.second, value: seconds, to: Date(), options: [])! 86 | } 87 | 88 | 89 | } 90 | -------------------------------------------------------------------------------- /Tests/RelativeFormatterTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // RelativeFormatterTests.swift 3 | // RelativeFormatterTests 4 | // 5 | // Created by David Collado Sela on 12/5/15. 6 | // Copyright (c) 2015 David Collado Sela. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import XCTest 11 | 12 | class RelativeFormatterTests: XCTestCase { 13 | 14 | //MARK: - Past 15 | 16 | func testYearsAgo(){ 17 | let twoYearsAgo = dateByYearsDifference(-2) 18 | let localizedString = twoYearsAgo.relativeFormatted() 19 | XCTAssertEqual(localizedString, "2 years ago", "2 years ago") 20 | } 21 | 22 | func testYearAgo(){ 23 | let oneYearAgo = dateByYearsDifference(-1) 24 | let localizedString = oneYearAgo.relativeFormatted() 25 | XCTAssertEqual(localizedString, "1 year ago", "1 year ago") 26 | } 27 | 28 | func testMonthsAgo(){ 29 | let twoMonthsAgo = dateByMonthsDifference(-2) 30 | let localizedString = twoMonthsAgo.relativeFormatted() 31 | XCTAssertEqual(localizedString, "2 months ago", "2 months ago") 32 | } 33 | 34 | func testMonthAgo(){ 35 | let oneMonthAgo = dateByMonthsDifference(-1) 36 | let localizedString = oneMonthAgo.relativeFormatted() 37 | XCTAssertEqual(localizedString, "1 month ago", "1 month ago") 38 | } 39 | 40 | func testWeeksAgo(){ 41 | let twoWeeksAgo = dateByWeeksDifference(-2) 42 | let localizedString = twoWeeksAgo.relativeFormatted() 43 | XCTAssertEqual(localizedString, "2 weeks ago", "2 weeks ago") 44 | } 45 | 46 | func testWeekAgo(){ 47 | let oneWeekAgo = dateByWeeksDifference(-1) 48 | let localizedString = oneWeekAgo.relativeFormatted() 49 | XCTAssertEqual(localizedString, "1 week ago", "1 week ago") 50 | } 51 | 52 | func testDaysAgo(){ 53 | let twoDaysAgo = dateByDaysDifference(-2) 54 | let localizedString = twoDaysAgo.relativeFormatted() 55 | XCTAssertEqual(localizedString, "2 days ago", "2 days ago") 56 | } 57 | 58 | func testDayAgo(){ 59 | let oneDayAgo = dateByDaysDifference(-1) 60 | let localizedString = oneDayAgo.relativeFormatted() 61 | XCTAssertEqual(localizedString, "1 day ago", "1 day ago") 62 | } 63 | 64 | func testHoursAgo(){ 65 | let twoHoursAgo = dateByHoursDifference(-2) 66 | let localizedString = twoHoursAgo.relativeFormatted() 67 | XCTAssertEqual(localizedString, "2 hours ago", "2 hours ago") 68 | } 69 | 70 | func testHourAgo(){ 71 | let oneHourAgo = dateByHoursDifference(-1) 72 | let localizedString = oneHourAgo.relativeFormatted() 73 | XCTAssertEqual(localizedString, "1 hour ago", "1 hour ago") 74 | } 75 | 76 | func testMinutesAgo(){ 77 | let twoMinutesAgo = dateByMinutesDifference(-2) 78 | let localizedString = twoMinutesAgo.relativeFormatted() 79 | XCTAssertEqual(localizedString, "2 minutes ago", "2 minutes ago") 80 | } 81 | 82 | func testMinuteAgo(){ 83 | let oneMinuteAgo = dateByMinutesDifference(-1) 84 | let localizedString = oneMinuteAgo.relativeFormatted() 85 | XCTAssertEqual(localizedString, "1 minute ago", "1 minute ago") 86 | } 87 | 88 | func testSecondsAgo(){ 89 | let twoSecondsAgo = dateBySecondsDifference(-2) 90 | let localizedString = twoSecondsAgo.relativeFormatted() 91 | XCTAssertEqual(localizedString, "2 seconds ago", "2 seconds ago") 92 | } 93 | 94 | func testSecondAgo(){ 95 | let oneSecondAgo = dateBySecondsDifference(-1) 96 | let localizedString = oneSecondAgo.relativeFormatted() 97 | XCTAssertEqual(localizedString, "1 second ago", "1 second ago") 98 | } 99 | 100 | //MARK: - Future 101 | 102 | func testYearsAhead(){ 103 | var twoYearsAhead = dateByYearsDifference(2) 104 | //We need to add 1 second as 2 exactly years doesn't work 105 | twoYearsAhead = twoYearsAhead.addingTimeInterval(1) 106 | let localizedString = twoYearsAhead.relativeFormatted() 107 | XCTAssertEqual(localizedString, "in 2 years", "in 2 years") 108 | } 109 | 110 | func testYearAhead(){ 111 | var oneYearAhead = dateByYearsDifference(1) 112 | oneYearAhead = oneYearAhead.addingTimeInterval(1) 113 | let localizedString = oneYearAhead.relativeFormatted() 114 | XCTAssertEqual(localizedString, "in 1 year", "in 1 year") 115 | } 116 | 117 | func testMonthsAhead(){ 118 | var twoMonthsAhead = dateByMonthsDifference(2) 119 | twoMonthsAhead = twoMonthsAhead.addingTimeInterval(1) 120 | let localizedString = twoMonthsAhead.relativeFormatted() 121 | XCTAssertEqual(localizedString, "in 2 months", "in 2 months") 122 | } 123 | 124 | func testMonthAhead(){ 125 | var oneMonthAhead = dateByMonthsDifference(1) 126 | oneMonthAhead = oneMonthAhead.addingTimeInterval(1) 127 | let localizedString = oneMonthAhead.relativeFormatted() 128 | XCTAssertEqual(localizedString, "in 1 month", "in 1 month") 129 | } 130 | 131 | func testWeeksAhead(){ 132 | var twoWeeksAhead = dateByWeeksDifference(2) 133 | twoWeeksAhead = twoWeeksAhead.addingTimeInterval(1) 134 | let localizedString = twoWeeksAhead.relativeFormatted() 135 | XCTAssertEqual(localizedString, "in 2 weeks", "in 2 weeks") 136 | } 137 | 138 | func testWeekAhead(){ 139 | var oneWeekAhead = dateByWeeksDifference(1) 140 | oneWeekAhead = oneWeekAhead.addingTimeInterval(1) 141 | let localizedString = oneWeekAhead.relativeFormatted() 142 | XCTAssertEqual(localizedString, "in 1 week", "in 1 week") 143 | } 144 | 145 | func testDaysAhead(){ 146 | var twoDaysAhead = dateByDaysDifference(2) 147 | twoDaysAhead = twoDaysAhead.addingTimeInterval(1) 148 | let localizedString = twoDaysAhead.relativeFormatted() 149 | XCTAssertEqual(localizedString, "in 2 days", "in 2 days") 150 | } 151 | 152 | func testDayAhead(){ 153 | var oneDayAhead = dateByDaysDifference(1) 154 | oneDayAhead = oneDayAhead.addingTimeInterval(1) 155 | let localizedString = oneDayAhead.relativeFormatted() 156 | XCTAssertEqual(localizedString, "in 1 day", "in 1 day") 157 | } 158 | 159 | func testHoursAhead(){ 160 | var twoHoursAhead = dateByHoursDifference(2) 161 | twoHoursAhead = twoHoursAhead.addingTimeInterval(1) 162 | let localizedString = twoHoursAhead.relativeFormatted() 163 | XCTAssertEqual(localizedString, "in 2 hours", "in 2 hours") 164 | } 165 | 166 | func testHourAhead(){ 167 | var oneHourAhead = dateByHoursDifference(1) 168 | oneHourAhead = oneHourAhead.addingTimeInterval(1) 169 | let localizedString = oneHourAhead.relativeFormatted() 170 | XCTAssertEqual(localizedString, "in 1 hour", "in 1 hour") 171 | } 172 | 173 | func testMinutesAhead(){ 174 | var twoMinutesAhead = dateByMinutesDifference(2) 175 | twoMinutesAhead = twoMinutesAhead.addingTimeInterval(1) 176 | let localizedString = twoMinutesAhead.relativeFormatted() 177 | XCTAssertEqual(localizedString, "in 2 minutes", "in 2 minutes") 178 | } 179 | 180 | func testMinuteAhead(){ 181 | var oneMinuteAhead = dateByMinutesDifference(1) 182 | oneMinuteAhead = oneMinuteAhead.addingTimeInterval(1) 183 | let localizedString = oneMinuteAhead.relativeFormatted() 184 | XCTAssertEqual(localizedString, "in 1 minute", "in 1 minute") 185 | } 186 | 187 | func testSecondsAhead(){ 188 | let twoSecondsAhead = dateBySecondsDifference(2) 189 | let localizedString = twoSecondsAhead.relativeFormatted() 190 | XCTAssertEqual(localizedString, "in 2 seconds", "in 2 seconds") 191 | } 192 | 193 | func testSecondAhead(){ 194 | let oneSecondAhead = dateBySecondsDifference(1) 195 | let localizedString = oneSecondAhead.relativeFormatted() 196 | XCTAssertEqual(localizedString, "in 1 second", "in 1 second") 197 | } 198 | 199 | //MARK: - Precision 200 | 201 | func testYearPrecision(){ 202 | let now = Date() 203 | let localizedString = now.relativeFormatted(precision:Precision.year) 204 | XCTAssertEqual(localizedString, "this year", "this year") 205 | } 206 | 207 | func testMonthPrecision(){ 208 | let now = Date() 209 | let localizedString = now.relativeFormatted(precision:Precision.month) 210 | XCTAssertEqual(localizedString, "this month", "this month") 211 | } 212 | 213 | func testWeekPrecision(){ 214 | let now = Date() 215 | let localizedString = now.relativeFormatted(precision:Precision.week) 216 | XCTAssertEqual(localizedString, "this week", "this week") 217 | } 218 | 219 | func testDayPrecision(){ 220 | let now = Date() 221 | let localizedString = now.relativeFormatted(precision:Precision.day) 222 | XCTAssertEqual(localizedString, "today", "today") 223 | } 224 | 225 | func testHourPrecision(){ 226 | let now = Date() 227 | let localizedString = now.relativeFormatted(precision:Precision.hour) 228 | XCTAssertEqual(localizedString, "less than one hour", "less than one hour") 229 | } 230 | 231 | func testMinutePrecision(){ 232 | let now = Date() 233 | let localizedString = now.relativeFormatted(precision:Precision.minute) 234 | XCTAssertEqual(localizedString, "less than one minute", "less than one minute") 235 | } 236 | 237 | //MARK: - Helpers 238 | 239 | func dateByYearsDifference(_ years:Int) -> Date{ 240 | return (Calendar.current as NSCalendar).date(byAdding: NSCalendar.Unit.year, value: years, to: Date(), options: [])! 241 | } 242 | 243 | func dateByMonthsDifference(_ months:Int) -> Date{ 244 | return (Calendar.current as NSCalendar).date(byAdding: NSCalendar.Unit.month, value: months, to: Date(), options: [])! 245 | } 246 | 247 | func dateByWeeksDifference(_ weeks:Int) -> Date{ 248 | return (Calendar.current as NSCalendar).date(byAdding: NSCalendar.Unit.weekOfYear, value: weeks, to: Date(), options: [])! 249 | } 250 | 251 | func dateByDaysDifference(_ days:Int) -> Date{ 252 | return (Calendar.current as NSCalendar).date(byAdding: NSCalendar.Unit.day, value: days, to: Date(), options: [])! 253 | } 254 | 255 | func dateByHoursDifference(_ hours:Int) -> Date{ 256 | return (Calendar.current as NSCalendar).date(byAdding: NSCalendar.Unit.hour, value: hours, to: Date(), options: [])! 257 | } 258 | 259 | func dateByMinutesDifference(_ minutes:Int) -> Date{ 260 | return (Calendar.current as NSCalendar).date(byAdding: NSCalendar.Unit.minute, value: minutes, to: Date(), options: [])! 261 | } 262 | 263 | func dateBySecondsDifference(_ seconds:Int) -> Date{ 264 | return (Calendar.current as NSCalendar).date(byAdding: NSCalendar.Unit.second, value: seconds, to: Date(), options: [])! 265 | } 266 | 267 | 268 | } 269 | --------------------------------------------------------------------------------