{"id":440,"date":"2021-04-03T18:58:14","date_gmt":"2021-04-03T16:58:14","guid":{"rendered":"https:\/\/mic.st\/blog\/?p=440"},"modified":"2021-04-03T18:58:16","modified_gmt":"2021-04-03T16:58:16","slug":"how-to-use-the-relativedatetimeformatter-in-swift","status":"publish","type":"post","link":"https:\/\/mic.st\/blog\/how-to-use-the-relativedatetimeformatter-in-swift\/","title":{"rendered":"How to use the RelativeDateTimeFormatter in Swift"},"content":{"rendered":"\n<p>Working with dates in Swift is a thing I remembered to struggle for a long time as a Junior developer. However, you should get used to objects that have date properties. E.g. you can sort stuff (e.g. see <a href=\"https:\/\/mic.st\/blog\/how-to-sort-an-array-of-objects-by-date-property\/\">https:\/\/mic.st\/blog\/how-to-sort-an-array-of-objects-by-date-property\/<\/a>) easily by date. If you ever tried to develop your own date formatter, you will quickly run into a lot of pitfalls (Even before talking about localization\ud83d\ude04). Here, I will give you an overview on how to use the <strong>RelativeDateTimeFormatter<\/strong> in Swift.<\/p>\n\n\n\n<p>Currently, I am working on a private project that includes local notifications and a small dashboard that shows short information about these notifications. I wanted to make it friendly with something like &#8220;next notification in 18 hours&#8221;, etc. <br>So, how would you do that?<br>My first solution was to just present the notification&#8217;s trigger <code>Date<\/code> using the powerful and well known <code>DateFormatter<\/code> or <code>DateComponentsFormatter<\/code> plus some logic.<br>This is totally fine and might work. However, there is also a nice formatter for just this single use case. \ud83e\udd73 <br>Furthermore, <strong>one of the things I have learnt so far using Swift: If it&#8217;s already built-in, do not reeinvent it because your solution is most probably worse (especially when localization is involved \ud83d\ude04)<\/strong><br><\/p>\n\n\n\n<div class=\"wp-block-image is-style-tw-rounded-corners\"><figure class=\"aligncenter size-full is-resized\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/mic.st\/blog\/wp-content\/uploads\/2021\/04\/pocket-watch-3156771_1280.jpg\" alt=\"An image of a pocket watch laying in sand.\" class=\"wp-image-465\" width=\"640\" height=\"427\" srcset=\"https:\/\/mic.st\/blog\/wp-content\/uploads\/2021\/04\/pocket-watch-3156771_1280.jpg 1280w, https:\/\/mic.st\/blog\/wp-content\/uploads\/2021\/04\/pocket-watch-3156771_1280-300x200.jpg 300w, https:\/\/mic.st\/blog\/wp-content\/uploads\/2021\/04\/pocket-watch-3156771_1280-1024x682.jpg 1024w, https:\/\/mic.st\/blog\/wp-content\/uploads\/2021\/04\/pocket-watch-3156771_1280-768x512.jpg 768w, https:\/\/mic.st\/blog\/wp-content\/uploads\/2021\/04\/pocket-watch-3156771_1280-1200x800.jpg 1200w\" sizes=\"auto, (max-width: 640px) 100vw, 640px\" \/><figcaption>Formatting times in Swift can look as nice as this stock photo!<\/figcaption><\/figure><\/div>\n\n\n\n<p>(I am not sure why Apple did not mention this nice formatter in the Date and Times overview at <a href=\"https:\/\/developer.apple.com\/documentation\/foundation\/dates_and_times\" target=\"_blank\" rel=\"noopener\">https:\/\/developer.apple.com\/documentation\/foundation\/dates_and_times<\/a>. However, it is mentioned on the general overview with all the formatters: <a href=\"https:\/\/developer.apple.com\/documentation\/foundation\/data_formatting \" target=\"_blank\" rel=\"noopener\">https:\/\/developer.apple.com\/documentation\/foundation\/data_formatting <\/a>)<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">The formatter&#8217;s output<\/h2>\n\n\n\n<p>The documentation in the Obj-C header states pretty clear what you can expect from this formatter. It will give you a &#8220;local-aware formatting of a relative date or time&#8221;. E.g. something like I want to have for my notifcations: <em>&#8220;Next: In two weeks&#8221;<\/em>. You pass in two dates (one is your reference dates) and it calculates the difference and produces a nice string for you.<\/p>\n\n\n\n<p>Although you can technically concatenate your formatter&#8217;s results to an existing string, you have to make sure that this will not conflict in a grammatical sense. <\/p>\n\n\n\n<p>The code documentation also says that you should use the formatter&#8217;s results only &#8220;in a standalone manner&#8221;. <\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Using the formatter<\/h2>\n\n\n\n<p>There are four available methods that allow you to format time in different representations to a string:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-1\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript\">func localizedString(<span class=\"hljs-keyword\">for<\/span>: <span class=\"hljs-built_in\">Date<\/span>, <span class=\"hljs-attr\">relativeTo<\/span>: <span class=\"hljs-built_in\">Date<\/span>) -&gt; <span class=\"hljs-built_in\">String<\/span>\nfunc localizedString(<span class=\"hljs-keyword\">from<\/span>: DateComponents) -&gt; <span class=\"hljs-built_in\">String<\/span>\nfunc localizedString(fromTimeInterval: TimeInterval) -&gt; <span class=\"hljs-built_in\">String<\/span>\nfunc string(<span class=\"hljs-keyword\">for<\/span>: Any?) -&gt; <span class=\"hljs-built_in\">String<\/span>?<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-1\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>The one you would use most commonly, might be <code>localizedString(for: Date, relativeTo: Date)<\/code>. Imagine, someone is standing at a certain point in time (your second parameter) reasoning about another date (which is your first parameter). That means, your second parameter is in most cases <code>Date()<\/code>.<\/p>\n\n\n\n<p>What is really cool about the formatter is that it not only handles plurals for you, it also aproximates really nice. So, it does not just format a date that is ten days in the past to <em>&#8220;10 days ago&#8221;<\/em>, it will actually format everything that makes sense to <em>&#8220;last week&#8221;<\/em>, <em>&#8220;next week&#8221;<\/em>, etc.<\/p>\n\n\n\n<p>Let&#8217;s see an easy example of the formatter in action:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-2\" data-shcb-language-name=\"Swift\" data-shcb-language-slug=\"swift\"><span><code class=\"hljs language-swift\"><span class=\"hljs-keyword\">let<\/span> formatter = <span class=\"hljs-type\">RelativeDateTimeFormatter<\/span>()\n<span class=\"hljs-keyword\">let<\/span> calendar = <span class=\"hljs-type\">Calendar<\/span>.current\n<span class=\"hljs-keyword\">let<\/span> today = <span class=\"hljs-type\">Date<\/span>()\n\n<span class=\"hljs-comment\">\/\/ Some other points in time. <\/span>\n<span class=\"hljs-keyword\">if<\/span> <span class=\"hljs-keyword\">let<\/span> lastWeek = calendar.date(byAdding: <span class=\"hljs-type\">DateComponents<\/span>(day: -<span class=\"hljs-number\">10<\/span>),\n\t\t\t\t\t\t\t\tto: <span class=\"hljs-type\">Date<\/span>()),\n   <span class=\"hljs-keyword\">let<\/span> lastWeekAsWell = calendar.date(byAdding: <span class=\"hljs-type\">DateComponents<\/span>(day: -<span class=\"hljs-number\">8<\/span>),\n\t\t\t\t\t\t\t\tto: <span class=\"hljs-type\">Date<\/span>()),\n   <span class=\"hljs-keyword\">let<\/span> twoWeeksAgo = calendar.date(byAdding: <span class=\"hljs-type\">DateComponents<\/span>(day: -<span class=\"hljs-number\">18<\/span>),\n\t\t\t\t\t\t\t\t   to: <span class=\"hljs-type\">Date<\/span>()),\n   <span class=\"hljs-keyword\">let<\/span> lastMonth = calendar.date(byAdding: <span class=\"hljs-type\">DateComponents<\/span>(day: -<span class=\"hljs-number\">40<\/span>),\n\t\t\t\t\t\t\t\t to: <span class=\"hljs-type\">Date<\/span>()) {\n   <span class=\"hljs-keyword\">let<\/span> nextYear = calendar.date(byAdding: <span class=\"hljs-type\">DateComponents<\/span>(month: <span class=\"hljs-number\">16<\/span>),\n\t\t\t\t\t\t\t\tto: today)\n\tformatter.localizedString(<span class=\"hljs-keyword\">for<\/span>: lastWeek, relativeTo: today)\n\tformatter.localizedString(<span class=\"hljs-keyword\">for<\/span>: lastWeekAsWell, relativeTo: today)\n\tformatter.localizedString(<span class=\"hljs-keyword\">for<\/span>: twoWeeksAgo, relativeTo: today)\n\tformatter.localizedString(<span class=\"hljs-keyword\">for<\/span>: lastMonth, relativeTo: today)\n\tformatter.localizedString(<span class=\"hljs-keyword\">for<\/span>: nextYear, relativeTo: today)\n\n\t<span class=\"hljs-comment\">\/\/\tPrints:<\/span>\n\t<span class=\"hljs-comment\">\/\/\t\"1 week ago\"<\/span>\n\t<span class=\"hljs-comment\">\/\/\t\"1 week ago\"<\/span>\n\t<span class=\"hljs-comment\">\/\/\t\"2 weeks ago\"<\/span>\n\t<span class=\"hljs-comment\">\/\/\t\"1 month ago\"<\/span>\n\t<span class=\"hljs-comment\">\/\/\t\"in 1 year\"<\/span>\n}<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-2\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">Swift<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">swift<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>As you can see above, the formatter approximates eight days in the past and ten days in the past to <em>&#8220;1 week ago&#8221;<\/em> which is pretty cool, right?<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Shorthand method for the RelativeDateTimeFormatter<\/h2>\n\n\n\n<p>There is another method that is inherited from <code>Formatter<\/code> which accepts <code>Any<\/code> (<code>func string(for: Any?) -&gt; String?<\/code>) However, it will only return something else than <code>nil<\/code> if you pass in a <code>Date<\/code>. It uses <code>Date()<\/code> as reference date, therefore you can use it as a shorthand for the first presented method. It will give you the same result:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-3\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript\">formatter.string(<span class=\"hljs-keyword\">for<\/span>: lastWeek)\nformatter.string(<span class=\"hljs-keyword\">for<\/span>: lastWeekAsWell)\nformatter.string(<span class=\"hljs-keyword\">for<\/span>: twoWeeksAgo)\nformatter.string(<span class=\"hljs-keyword\">for<\/span>: lastMonth)\nformatter.string(<span class=\"hljs-keyword\">for<\/span>: nextYear)\n\n<span class=\"hljs-comment\">\/\/\tPrints the same as above, given the properties above hold the same values<\/span>\n<span class=\"hljs-comment\">\/\/\t\"1 week ago\"<\/span>\n<span class=\"hljs-comment\">\/\/\t\"1 week ago\"<\/span>\n<span class=\"hljs-comment\">\/\/\t\"2 weeks ago\"<\/span>\n<span class=\"hljs-comment\">\/\/\t\"1 month ago\"<\/span>\n<span class=\"hljs-comment\">\/\/\t\"in 1 year\"<\/span><\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-3\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>What if you want the exact amount of days, weeks, years or whatever? You can use <code>func localizedString(from: DateComponents)<\/code> for that. It will use <code>Date()<\/code> as a default reference and the formatter does not approximate anything (like we saw before). If you use the same quantities we added above to <code>today<\/code> you will get the exact amounts in your string:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-4\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript\">formatter.localizedString(<span class=\"hljs-keyword\">from<\/span>: DateComponents(day: <span class=\"hljs-number\">-10<\/span>))\nformatter.localizedString(<span class=\"hljs-keyword\">from<\/span>: DateComponents(day: <span class=\"hljs-number\">-8<\/span>))\nformatter.localizedString(<span class=\"hljs-keyword\">from<\/span>: DateComponents(day: <span class=\"hljs-number\">-18<\/span>))\nformatter.localizedString(<span class=\"hljs-keyword\">from<\/span>: DateComponents(day: <span class=\"hljs-number\">-40<\/span>))\nformatter.localizedString(<span class=\"hljs-keyword\">from<\/span>: DateComponents(month: <span class=\"hljs-number\">16<\/span>))\n\n<span class=\"hljs-comment\">\/\/\tPrints:<\/span>\n<span class=\"hljs-comment\">\/\/\t\"10 days ago\"<\/span>\n<span class=\"hljs-comment\">\/\/\t\"8 days ago\"<\/span>\n<span class=\"hljs-comment\">\/\/\t\"18 days ago\"<\/span>\n<span class=\"hljs-comment\">\/\/\t\"40 days ago\"<\/span>\n<span class=\"hljs-comment\">\/\/\t\"in 16 months\"<\/span><\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-4\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>The third method <code>localizedString(fromTimeInterval: TimeInterval)<\/code> is a convenience method for creating a string from a <code>TimeInterval<\/code>. It behaves like the method for creating a string from <code>DateComponents<\/code>. However, for longer timespans I recommend not to use it. <code>DateComponents(day: 5)<\/code> reads way better than <code>60 * 60 * 24 * 5<\/code>. In addition to that, you will loose accuracy when using <code>TimeInterval<\/code> for longer periods.<\/p>\n\n\n\n<p>The fourth method is inherited from <code>Formatter<\/code> that is why you can pass in <code>Any<\/code>. However, it will only return something else than <code>nil<\/code> if you pass in a <code>Date<\/code>. It uses <code>Date()<\/code> as reference date, therefore you can use it as a shorthand for the first presented method:<\/p>\n\n\n\n<p>Now that we learnt something about the different methods, let&#8217;s see how we can configure the formatter.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Configuring the RelativeDateTimeFormatter<\/h2>\n\n\n\n<p>You should have now an understanding about how to use the RelativeDateTimeFormatter in Swift. So, it does a pretty great job without configuring anything. Actually, this formatter does not offer a lot of configuring properties but I think it is sufficient in most cases. <\/p>\n\n\n\n<p>In the following listing you can see all the available properties with some documentation by me. You should be familar with some of them if you already worked with <code>DateFormatter<\/code>:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-5\" data-shcb-language-name=\"Swift\" data-shcb-language-slug=\"swift\"><span><code class=\"hljs language-swift shcb-code-table shcb-line-numbers\"><span class='shcb-loc'><span><span class=\"hljs-comment\">\/\/\/ You can set this property to .named or .numeric.<\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-comment\">\/\/\/ .numeric will give you \"1 day ago\", \"1 week ago\", etc.<\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-comment\">\/\/\/ .named will give you \"yesterday\", \"last week\", etc.<\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-keyword\">var<\/span> dateTimeStyle: <span class=\"hljs-type\">RelativeDateTimeFormatter<\/span>.<span class=\"hljs-type\">DateTimeStyle<\/span>\n<\/span><\/span><span class='shcb-loc'><span>\n<\/span><\/span><span class='shcb-loc'><span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-comment\">\/\/\/ Use this to modify how the units and quantaties are formatted.<\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-comment\">\/\/\/ E.g. for a date that is 2 days in the past:<\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-comment\">\/\/\/ .full will result in \"2 days ago\"<\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-comment\">\/\/\/ .abbreviated will result in \"2 d. ago\"<\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-keyword\">var<\/span> unitsStyle: <span class=\"hljs-type\">RelativeDateTimeFormatter<\/span>.<span class=\"hljs-type\">UnitsStyle<\/span>\n<\/span><\/span><span class='shcb-loc'><span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-comment\">\/\/\/ This sets where the formatted string will be used to format correctly.<\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-comment\">\/\/\/ E.g. in a lot of languages the first letter of a sentence is capatalized.<\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-comment\">\/\/\/ You can just use .beginningOfSentence  for that.<\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-comment\">\/\/\/ In other cases you might use .<\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-keyword\">var<\/span> formattingContext: <span class=\"hljs-type\">Formatter<\/span>.<span class=\"hljs-type\">Context<\/span>\n<\/span><\/span><\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-5\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">Swift<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">swift<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>There are two other ones you probably will not need to change because you should respect whatever the users have set on their devices: <\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-6\" data-shcb-language-name=\"PHP\" data-shcb-language-slug=\"php\"><span><code class=\"hljs language-php\"><span class=\"hljs-comment\">\/\/\/ The calendar used by the formatter.<\/span>\n<span class=\"hljs-comment\">\/\/\/ E.g. .chinese, .gregorian, .hebrew and a lot more.<\/span>\n<span class=\"hljs-comment\">\/\/\/ Normally you do not have to set this.<\/span>\n<span class=\"hljs-comment\">\/\/\/ You should let the system apply whatever users picked on their phone.<\/span>\n<span class=\"hljs-comment\">\/\/\/ Defaults to Calendar.autoupdatingCalendar.<\/span>\n<span class=\"hljs-keyword\">var<\/span> calendar: Calendar!\n\n<span class=\"hljs-comment\">\/\/\/ Similar to calendar, normally you do not need to set this.<\/span>\n<span class=\"hljs-comment\">\/\/\/ By default, the calendar's locale is used here.<\/span>\n<span class=\"hljs-keyword\">var<\/span> locale: Locale!<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-6\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">PHP<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">php<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<h2 class=\"wp-block-heading\">More complex time formatting<\/h2>\n\n\n\n<p>This post should have given you a short introduction into how to use the RelativeDateTimeFormatter in Swift. However, there are cases where this formatter might not be enough. If you want to format dates that should be placed in a text or need more complex formatting behavior in general, I would rather use <code>DateComponentsFormatter<\/code> together with <code>NSLocalizedString<\/code>. If you use <code>RelativeDateTimeFormatter<\/code> in non-standalone places you might end up with funny looking texts. You should be aware that it might look totally fine in your preferred language but it might not work in other languages.<\/p>\n\n\n\n<p>Happy time formatting! \u23f0<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Working with dates in Swift is a thing I remembered to struggle for a long time as a Junior developer. However, you should get used to objects that have date properties. E.g. you can sort stuff (e.g. see https:\/\/mic.st\/blog\/how-to-sort-an-array-of-objects-by-date-property\/) easily by date. If you ever tried to develop your own date formatter, you will quickly&hellip;<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"inline_featured_image":false,"footnotes":""},"categories":[3],"tags":[41,4,35],"class_list":["post-440","post","type-post","status-publish","format-standard","hentry","category-ios-development","tag-formatters","tag-ios","tag-swift"],"_links":{"self":[{"href":"https:\/\/mic.st\/blog\/wp-json\/wp\/v2\/posts\/440","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/mic.st\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/mic.st\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/mic.st\/blog\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/mic.st\/blog\/wp-json\/wp\/v2\/comments?post=440"}],"version-history":[{"count":26,"href":"https:\/\/mic.st\/blog\/wp-json\/wp\/v2\/posts\/440\/revisions"}],"predecessor-version":[{"id":471,"href":"https:\/\/mic.st\/blog\/wp-json\/wp\/v2\/posts\/440\/revisions\/471"}],"wp:attachment":[{"href":"https:\/\/mic.st\/blog\/wp-json\/wp\/v2\/media?parent=440"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/mic.st\/blog\/wp-json\/wp\/v2\/categories?post=440"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/mic.st\/blog\/wp-json\/wp\/v2\/tags?post=440"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}