Jekyll2024-02-29T14:17:55+00:00https://bootstragram.com/feed/by_tag/ios.xmlBootstragramNotes for myself and fellow programmers, with a focus on iOS, Swift and web frontend development. Since 2009.My Mission Statement as a Programmer2024-01-06T00:00:00+00:002024-01-06T00:00:00+00:00https://bootstragram.com/blog/developer-mission-statement<h2>tl;dr</h2>
<p>As a programmer, I create eco-conscious software with user-friendly design,
embracing IndieWeb principles. I’m cautious with big corporations, upholding
ethics and user rights. My goal is to produce durable and sustainable code,
prioritizing maintainability and developer well-being. I respond swiftly to
evolving challenges for impactful work in a changing tech landscape.</p>
<h2>A longer version</h2>
<p>Let me explain. There is a sense of uneasiness with the state of the world these
days. After having the privilege to spend a fun and peaceful holiday season, I
wanted to do a headcheck on where I stand with my role as a programmer and if I
am aligned with my aspirations and values. So I decided to write my mission
statement. I will develop each of its components with an example of how I think
I apply it.</p>
<h3>🌍 Climate Friendliness</h3>
<p>I need to consider all environmental impacts of the code I write: the hardware
it runs on, the energy it requires, its purpose for the people running it.</p>
<p>Example. I work for <a href="https://memo.bank/">Memo Bank</a>, a bank for small and medium-sized
companies, that <a href="https://memo.bank/nos-engagements/">prioritizes in a no bull-shit way</a> the transition to a low
carbon economy. It has been recognised as such by <a href="https://reclaimfinance.org/site/">Reclaim Finance</a>, and is
one of the recommended banks on their website <a href="https://change-de-banque.org/">Change de banque</a>.</p>
<h3>🤝 Human Friendliness</h3>
<p>I prioritise security and user experience, in that order, to promote the
well-being of end-users. It involves creating software that is intuitive,
inclusive, and positively impacts users’ lives.</p>
<p>Example. I feel that it was my main motivations to start running the <a href="https://www.statium.app/newsletter">Statium
Newsletter</a>, a minimalistic soccer newsletter written in French that focuses
solely on the game. It filters out the noise from meaningless controversies,
<a href="https://www.statium.app/newsletter/posts/newsletter-222">poor journalistic practices</a>, invasive advertisement and poor user
experience of alternative sources. As of today, <a href="https://www.statium.app/">statium.app</a> has a perfect
score of 100 in Performance, Accessibility, Best Practices and SEO with
Lighthouse.</p>
<h3>⚡ Mastery</h3>
<p>I want to continuously get better at my craft. Software is never “done”. A
skillset is never complete. I want to keep engaging on how to be a better
programmer, a better team member, and a better person.</p>
<p>Example. I think a good practice of mastery is balancing being very curious with
just what it needs of focus. I wrote about <a href="https://bootstragram.com/blog/side-projects-2022/">why side-projects are so important
to me</a> in the past: my curiosity is satisfied and the new things I learn have
many positive impacts on my job at Memo Bank.</p>
<h3>🌐 IndieWeb Friendliness</h3>
<p>I support decentralised and people-focused technologies and products that help
individuals to have greater control over their online presence and their data.</p>
<p>Example. I use RSS daily, I <a href="https://indieweb.org/POSSE">POSSE</a>, I run a newsletter using
<a href="https://buttondown.email/">Buttondown</a>, my microblogging platform of choice is <a href="https://dirtyhenry.micro.blog/">micro.blog</a>, I
favor community-run or independant sources when I use links in my blog posts
(examples: <a href="https://www.themoviedb.org/about">TMDB</a>, <a href="https://www.discogs.com/about">Discogs</a>, <a href="https://www.sports-reference.com/">Sports Reference</a>, etc.). I make <a href="https://www.mickf.net/default-apps">my
default apps</a> public.</p>
<h3>🕵️♂️ Big Corporation Cautiousness</h3>
<p>I am curious of the ethical considerations of big companies when engaging with
their products and technologies. I avoid those that compromise user privacy or
promote monopolistic behavior.</p>
<p>Example. I stopped using Gmail, I don’t actively use Meta products or X. I am
wary of AI products and try to use them as sparingly and ethically as possible.</p>
<h3>🌱 Durability & Sustainability</h3>
<p>I focus on the longevity and sustainability of the software I write. I want my
code to run for a long time, I want my mind and body to remain healthy for as
long as possible, and I want my online presence to live long.</p>
<p>Example. I pick dependencies to rely on very carefully and sparingly, I express
<a href="https://dirtyhenry.micro.blog/2023/10/31/144426.html">gratitude towards Open-Source developers</a>, I avoid and fix
<a href="https://dirtyhenry.micro.blog/2023/03/08/every-month-or.html">dead-links</a>, I maintain <a href="https://dirtyhenry.micro.blog/2023/12/26/good-old-fashioned.html">a good work-life balance</a>.</p>
<h2>Conclusion</h2>
<p>This mission statement serves as a guiding compass in my journey as a
programmer, reminding me of the values and principles I hold dear. It is a
commitment to creating software that not only meets the needs of the present but
also contributes positively to the future.</p>Mick FClimate Friendliness, Human Friendliness, Mastery, IndieWeb Friendliness, Big Corporation Cautiousness, Durability & Sustainability serves as a guiding compass in my journey as a programmerFrom block party to ’hood party2023-12-24T00:00:00+00:002023-12-24T00:00:00+00:00https://bootstragram.com/blog/blocks-and-hoods<p>Last year, I wrote about <a href="https://bootstragram.com/blog/side-projects-2022/">the joy sparked by side projects</a>. This year, I
wanted to write another small recap of what I have accomplished in 2023. I kept
iterating on my projects but, more importantly, I kept consolidating the
packages that I use to share code between projects: <a href="https://github.com/dirtyhenry/swift-blocks"><code>swift-blocks</code></a> and
<a href="https://github.com/dirtyhenry/swift-hoods"><code>swift-hoods</code></a>. They are open-sourced and I wanted to formally introduce
them to the world. Here we are.</p>
<h2>🧱 Welcome to my blocks</h2>
<p>For the last 2 years, I have been building <code>swift-blocks</code>, a <strong>dependency-free</strong>
library of tools and patterns I use repeatedly on all my Swift projects.</p>
<p>It is not my first attempt to build such a library but it is the first time I
feel really satisfied about its usefulness. What was different this time? Two
factors:</p>
<ol>
<li>For one, Swift Package Manager provides a much more convenient way to share
code between projects than anything else in the past (Cocoapods, static
libraries, etc.);</li>
<li>And then, I had a much better discipline to be consistent about
using/iterating/adding things to the library over time.</li>
</ol>
<p>Today, I released version <a href="https://github.com/dirtyhenry/swift-blocks/releases/tag/0.2.0">0.2.0</a> of the library. Here are the release notes.
Help yourself.</p>
<ul>
<li><a href="https://swiftpackageindex.com/dirtyhenry/swift-blocks/0.2.0/documentation/blocks/networking"><strong>🌐 Transport.</strong></a> Added a tinier version of objcio’s
<a href="https://github.com/objcio/tiny-networking"><code>tiny-networking</code></a> for concise
endpoint description, combining URL requests and response parsing. Introduced
<a href="https://swiftpackageindex.com/dirtyhenry/swift-blocks/0.2.0/documentation/blocks/urlrequestheaderitem"><code>URLRequestHeaderItem</code></a>
struct mimicking Foundation’s <code>URLQueryItem</code>.</li>
<li><a href="https://swiftpackageindex.com/dirtyhenry/swift-blocks/0.2.0/documentation/blocks/calendar"><strong>📅 Calendar.</strong></a> Improved internal architecture and developer experience
using <em>result builders</em>.</li>
<li><strong>🔐 Security.</strong> Introduced support for PKCE and associated helpers.</li>
<li><strong>🛟 Error Management.</strong> Introduced
<a href="https://swiftpackageindex.com/dirtyhenry/swift-blocks/0.2.0/documentation/blocks/simplemessageerror"><code>SimpleMessageError</code></a>
for convenient error handling, eliminating the need for forced unwrapping.
Ideal for scenarios where a full error domain is not necessary.</li>
<li><strong>🎨 SwiftUI.</strong> Introduced
<a href="https://swiftpackageindex.com/dirtyhenry/swift-blocks/0.2.0/documentation/blocks/taskstatebutton"><code>TaskStateButton</code></a>
interface components, for representing asynchronous
<a href="https://swiftpackageindex.com/dirtyhenry/swift-blocks/0.2.0/documentation/blocks/taskstate">task states</a>.
Supports four states: not started, running, completed (success), or failed.</li>
</ul>
<p>The <a href="https://swiftpackageindex.com/">documentation for this package</a> is now hosted on the Swift Package
Index.</p>
<h2>🏘️ Welcome to my ’hood</h2>
<p>In 2023 though, I have been using
<a href="https://github.com/pointfreeco/swift-composable-architecture">The Composable Architecture</a>
a lot. And I built things that were depending on it. So in addition to
<code>swift-blocks</code>, I started building another library, <code>swift-hoods</code><sup class="footnote-ref"><a href="#fn1" id="fnref1">1</a></sup>, which has
a slightly bigger footprint in a project but is starting to turn out very useful
as well.</p>
<p>Today, it is still in the early stages, but it already provides two useful
items:</p>
<ul>
<li>A keychain TCA dependency for easy testing of code that uses keychain items;</li>
<li>A processor for markdown files that include YAML front-matters.</li>
</ul>
<p>I will detail how these work in the future.</p>
<p>🎄 Have a happy holiday and happy new year 🥂.</p>
<section class="footnotes">
<ol>
<li id="fn1">
<p>Hood standing for neighborhood… ie a larger version of a block. Get it?
Kudos to my lovely wife for helping me find a good name. <a href="#fnref1" class="footnote-backref">↩</a></p>
</li>
</ol>
</section>Mick FIn 2023, I consolidated a dependency-free `swift-blocks` package, and I started working on a bigger `swift-hoods` package that builds upon my favorite dependencies.From the start, get rid of types you don’t control: a lesson (re)learned, Rolling Stones Edition2023-11-09T00:00:00+00:002023-11-09T00:00:00+00:00https://bootstragram.com/blog/keep-control-of-types<p>In recent weeks, I’ve been reminded of a crucial lesson: “Do not use the objects
that are used by an API<sup class="footnote-ref"><a href="#fn1" id="fnref1">1</a></sup> outside of the network layer of your application.”</p>
<p>While this might seem like common sense in a technical job interview, the
real-world scenario brings its own challenges, especially when dealing with (a)
an in-house designed API<sup class="footnote-ref"><a href="#fn2" id="fnref2">2</a></sup>, (b) budget constraints, and (c) the task of
writing boilerplate to convert DTO into model objects. It’s surprisingly easy to
give in to the temptation.</p>
<p>I’m writing this post to emphasize that the effort is always worth it. To drive
this point home, I’ll provide real-life examples to illustrate why.</p>
<h2>Example 1: Future-proofing an API</h2>
<p>Let’s delve into an API where a payload includes the name of a member of the
Rolling Stones. And just for the heck of it, you decide to write an enum to
represent this value. Because… why not?</p>
<p>Here’s the enum to kick things off:</p>
<div class="language-swift highlighter-rouge"><div class="highlight"><pre class="highlight"><code data-lang="swift"><span class="c1">/// 👅 Members of the Rolling Stones</span>
<span class="kd">enum</span> <span class="kt">RollingStone</span><span class="p">:</span> <span class="kt">String</span> <span class="p">{</span>
<span class="k">case</span> <span class="n">mick</span> <span class="o">=</span> <span class="s">"Mick Jagger"</span>
<span class="k">case</span> <span class="n">keith</span> <span class="o">=</span> <span class="s">"Keith Richards"</span>
<span class="k">case</span> <span class="n">bill</span> <span class="o">=</span> <span class="s">"Bill Wyman"</span>
<span class="k">case</span> <span class="n">charlie</span> <span class="o">=</span> <span class="s">"Charlie Watts"</span>
<span class="k">case</span> <span class="n">brian</span> <span class="o">=</span> <span class="s">"Brian Jones"</span>
<span class="p">}</span>
</code></pre></div></div>
<p>Now, imagine using a DTO as a model:</p>
<div class="language-swift highlighter-rouge"><div class="highlight"><pre class="highlight"><code data-lang="swift"><span class="kd">struct</span> <span class="kt">DTOAsModel</span><span class="p">:</span> <span class="kt">Codable</span> <span class="p">{</span>
<span class="k">let</span> <span class="nv">rollingStone</span><span class="p">:</span> <span class="kt">RollingStone</span>
<span class="p">}</span>
<span class="kd">extension</span> <span class="kt">RollingStone</span><span class="p">:</span> <span class="kt">Codable</span> <span class="p">{}</span>
</code></pre></div></div>
<p>And this works fine!</p>
<div class="language-swift highlighter-rouge"><div class="highlight"><pre class="highlight"><code data-lang="swift"><span class="k">do</span> <span class="p">{</span>
<span class="k">let</span> <span class="nv">member1</span> <span class="o">=</span> <span class="k">try</span> <span class="kt">JSONDecoder</span><span class="p">()</span><span class="o">.</span><span class="nf">decode</span><span class="p">(</span>
<span class="kt">DTOAsModel</span><span class="o">.</span><span class="k">self</span><span class="p">,</span>
<span class="nv">from</span><span class="p">:</span> <span class="s">"""
{ "</span><span class="n">rollingStone</span><span class="s">": "</span><span class="kt">Mick</span> <span class="kt">Jagger</span><span class="s">" }
"""</span><span class="o">.</span><span class="nf">data</span><span class="p">(</span><span class="nv">using</span><span class="p">:</span> <span class="o">.</span><span class="n">utf8</span><span class="p">)</span><span class="o">!</span>
<span class="p">)</span>
<span class="p">}</span> <span class="k">catch</span> <span class="p">{</span>
<span class="nf">print</span><span class="p">(</span><span class="n">error</span><span class="p">)</span>
<span class="p">}</span>
</code></pre></div></div>
<p>But what if the backend adds “<a href="https://en.wikipedia.org/wiki/Mick_Taylor">Mick Taylor</a>” as a new possible value that
your Swift code has no idea about?</p>
<div class="language-swift highlighter-rouge"><div class="highlight"><pre class="highlight"><code data-lang="swift"><span class="k">do</span> <span class="p">{</span>
<span class="k">let</span> <span class="nv">member2</span> <span class="o">=</span> <span class="k">try</span> <span class="kt">JSONDecoder</span><span class="p">()</span><span class="o">.</span><span class="nf">decode</span><span class="p">(</span>
<span class="kt">DTOAsModel</span><span class="o">.</span><span class="k">self</span><span class="p">,</span>
<span class="nv">from</span><span class="p">:</span> <span class="s">"""
{ "</span><span class="n">rollingStone</span><span class="s">": "</span><span class="kt">Mick</span> <span class="kt">Taylor</span><span class="s">" }
"""</span><span class="o">.</span><span class="nf">data</span><span class="p">(</span><span class="nv">using</span><span class="p">:</span> <span class="o">.</span><span class="n">utf8</span><span class="p">)</span><span class="o">!</span>
<span class="p">)</span>
<span class="p">}</span> <span class="k">catch</span> <span class="p">{</span>
<span class="nf">print</span><span class="p">(</span><span class="n">error</span><span class="p">)</span>
<span class="p">}</span>
</code></pre></div></div>
<p>Oops, we crash.</p>
<p>There’s a quick and dirty way to fix this, and I’ll admit, I’ve been guilty of
doing it. What if we changed <code>rollingStone</code>:</p>
<div class="language-diff highlighter-rouge"><div class="highlight"><pre class="highlight"><code data-lang="diff"> struct DTOAsModel2: Codable {
<span class="gd">- let rollingStone: RollingStone
</span><span class="gi">+ let rollingStone: String
</span> }
<span class="err">
</span><span class="gi">+ extension DTOAsModel {
+ var typedRollingStone: RollingStone? {
+ RollingStone(rawValue: rollingStone)
+ }
+ }
</span></code></pre></div></div>
<p>But then things get messy: objects are cluttered, autocompletion becomes hard to
read, initializers are not using the enum type, etc.</p>
<p>So what I believe today is the right way to do things is:</p>
<ol>
<li>Limit yourself to JSON-supported types in your DTO;</li>
<li>Create typed model-objects;</li>
<li>Add extensions that can convert from DTO objects into model objects in your
network layer.</li>
</ol>
<div class="language-swift highlighter-rouge"><div class="highlight"><pre class="highlight"><code data-lang="swift"><span class="kd">struct</span> <span class="kt">DTOAsJustDTO</span><span class="p">:</span> <span class="kt">Codable</span> <span class="p">{</span>
<span class="k">let</span> <span class="nv">rollingStone</span><span class="p">:</span> <span class="kt">String</span>
<span class="p">}</span>
<span class="kd">struct</span> <span class="kt">ProperModel</span> <span class="p">{</span>
<span class="k">let</span> <span class="nv">rollingStone</span><span class="p">:</span> <span class="kt">RollingStone</span>
<span class="p">}</span>
<span class="kd">extension</span> <span class="kt">ProperModel</span> <span class="p">{</span>
<span class="nf">init</span><span class="p">?(</span><span class="nv">dto</span><span class="p">:</span> <span class="kt">DTOAsJustDTO</span><span class="p">)</span> <span class="p">{</span>
<span class="k">guard</span> <span class="k">let</span> <span class="nv">rollingStone</span> <span class="o">=</span> <span class="kt">RollingStone</span><span class="p">(</span><span class="nv">rawValue</span><span class="p">:</span> <span class="n">dto</span><span class="o">.</span><span class="n">rollingStone</span><span class="p">)</span> <span class="k">else</span> <span class="p">{</span>
<span class="k">return</span> <span class="kc">nil</span>
<span class="p">}</span>
<span class="k">self</span><span class="o">.</span><span class="n">rollingStone</span> <span class="o">=</span> <span class="n">rollingStone</span>
<span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>
<p>At the cost of a little boilerplate, you have two clean objects that do things
right.</p>
<p>There are many options to support future values of the enum:</p>
<ul>
<li>A <a href="https://docs.swift.org/swift-book/documentation/the-swift-programming-language/initialization/#Failable-Initializers">failable initializer</a>, as in the example,</li>
<li>A throwing initializer,</li>
<li>A prop that can be optional,</li>
<li>Adding an <code>.unknown</code> rolling stone in the enum,</li>
</ul>
<p>It is really up to you and what feels best.</p>
<h2>Example 2: Improving testability of code you don’t control</h2>
<p>Let’s take a look outside of the network layer, with other types of API that you
don’t control. The principles are similar.</p>
<p>So now your app receives a push notification every time the Rolling Stones
release an album.</p>
<div class="language-swift highlighter-rouge"><div class="highlight"><pre class="highlight"><code data-lang="swift"><span class="kd">func</span> <span class="nf">showNotification</span><span class="p">(</span><span class="n">_</span> <span class="nv">notification</span><span class="p">:</span> <span class="kt">UNNotification</span><span class="p">)</span> <span class="p">{</span>
<span class="k">let</span> <span class="nv">userInfo</span> <span class="o">=</span> <span class="n">notification</span><span class="o">.</span><span class="n">request</span><span class="o">.</span><span class="n">content</span><span class="o">.</span><span class="n">userInfo</span>
<span class="k">guard</span> <span class="k">let</span> <span class="nv">albumName</span> <span class="o">=</span> <span class="n">userInfo</span><span class="p">[</span><span class="s">"albumName"</span><span class="p">]</span> <span class="k">as?</span> <span class="kt">String</span> <span class="k">else</span> <span class="p">{</span>
<span class="k">return</span>
<span class="p">}</span>
<span class="nf">print</span><span class="p">(</span><span class="n">albumName</span><span class="p">)</span>
<span class="p">}</span>
</code></pre></div></div>
<p>How can you test this? You cannot create instances of <code>UNNotification</code>.</p>
<p>Instead, get rid of things out of your control early on. Take control of things.
Like so:</p>
<div class="language-swift highlighter-rouge"><div class="highlight"><pre class="highlight"><code data-lang="swift"><span class="kd">enum</span> <span class="kt">NotificationEvent</span> <span class="p">{</span>
<span class="k">case</span> <span class="nf">newAlbum</span><span class="p">(</span><span class="kt">String</span><span class="p">)</span>
<span class="p">}</span>
<span class="kd">extension</span> <span class="kt">NotificationEvent</span> <span class="p">{</span>
<span class="nf">init</span><span class="p">?(</span><span class="n">from</span> <span class="nv">notification</span><span class="p">:</span> <span class="kt">UNNotification</span><span class="p">)</span> <span class="p">{</span>
<span class="k">guard</span> <span class="k">let</span> <span class="nv">albumName</span> <span class="o">=</span> <span class="n">notification</span><span class="o">.</span><span class="n">request</span><span class="o">.</span><span class="n">content</span><span class="o">.</span><span class="n">userInfo</span><span class="p">[</span><span class="s">"albumName"</span><span class="p">]</span> <span class="k">as?</span> <span class="kt">String</span> <span class="k">else</span> <span class="p">{</span>
<span class="k">return</span> <span class="kc">nil</span>
<span class="p">}</span>
<span class="k">self</span> <span class="o">=</span> <span class="o">.</span><span class="nf">newAlbum</span><span class="p">(</span><span class="n">albumName</span><span class="p">)</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="kd">func</span> <span class="nf">showNotification</span><span class="p">(</span><span class="n">_</span> <span class="nv">notification</span><span class="p">:</span> <span class="kt">NotificationEvent</span><span class="p">)</span> <span class="p">{</span>
<span class="k">guard</span> <span class="k">case</span> <span class="k">let</span> <span class="nv">NotificationEvent</span><span class="o">.</span><span class="nf">newAlbum</span><span class="p">(</span><span class="n">albumName</span><span class="p">)</span> <span class="o">=</span> <span class="n">notification</span> <span class="k">else</span> <span class="p">{</span>
<span class="k">return</span>
<span class="p">}</span>
<span class="nf">print</span><span class="p">(</span><span class="n">albumName</span><span class="p">)</span>
<span class="p">}</span>
</code></pre></div></div>
<p>With this code, testing <code>NotificationEvent.init?(from:)</code> will be challenging.
But testing <code>showNotification</code> will be easy-peasy. Mission accomplished.</p>
<h2>More examples to come?</h2>
<p>For sure, I will encounter more examples in the future. And I’ll continue to
update this post as a reference for my future-self on how I want to code future
similar scenarios.</p>
<section class="footnotes">
<ol>
<li id="fn1">
<p>I will call them Data Transfer Objects <em>aka</em> DTO for the rest of this post <a href="#fnref1" class="footnote-backref">↩</a></p>
</li>
<li id="fn2">
<p>So you sort of control it, right? Well, not really. If an API is designed
for more than 1 client, it might include extra-details or complexity. So
while you can think you control it as an organization, your Swift project
does not. <a href="#fnref2" class="footnote-backref">↩</a></p>
</li>
</ol>
</section>Mick FWith my special sauce to future-proof your API and boost testability!The Joy Sparked by Side Projects, 2022 Edition2022-12-13T00:00:00+00:002022-12-13T00:00:00+00:00https://bootstragram.com/blog/side-projects-2022<p>🎄 December is here! Like every year, it is time to watch <em>Home Alone</em> again and
to review the progress I made as a developer over the past 12 months. Side
projects contributed the most.</p>
<h2>Why side projects are necessary for me</h2>
<p>Yes, a paying job is your bread and butter. But having side projects is key to
staying ahead of the younger guns of the tech world, flexing your developer
muscles in new directions and anticipating what’s next for you at work.</p>
<p>Last year, I started writing <a href="https://www.statium.app/">a newsletter about soccer/football</a><sup class="footnote-ref"><a href="#fn1" id="fnref1">1</a></sup> for
people based in France. The content is pretty basic. Still, I learned so much
just setting it up, running it, and trying to automate as many steps as possible
so that each edition would take as little time as possible to write, without
compromising the quality of the content.</p>
<h2>Things I learned</h2>
<p>Here is a non-exhaustive list of the things I trained on last year to run the
newsletter, in an approximative chronological order:</p>
<ul>
<li><a href="https://github.com/apple/swift-argument-parser"><strong>Swift Argument Parser.</strong></a> Writing a CLI in Swift is now my goto solution
when I need to write scripts. Including at work. It is so much easier to
write, document and maintain than Shell scripts.</li>
<li><a href="https://buttondown.email/"><strong>Buttondown.</strong></a> This is an indie tool to bootstrap newsletters. As part
of the side project journey, I believe it is worth exploring alternatives to
big corporations so I skipped Mailchimp, Mailjet and similar options. While I
really like Buttondown, I think the newsletter is now ready to grow out of it,
and finding a better option for my needs is on the roadmap for 2023.</li>
<li><a href="https://www.linode.com/"><strong>Linode.</strong></a> Similarly, I didn’t want to go the AWS route. And Linode’s
interface is much clearer than AWS — though it is not as feature rich. Linode
got recently bought by Akamai, so I am not as attached to it as I used to be.</li>
<li><a href="https://www.terraform.io/"><strong>Terraform.</strong></a> Infrastructure as code. I use it to create server
instances, manage DNS for the domain names, create and manage buckets and
keys.</li>
<li><a href="https://www.rfc-editor.org/rfc/rfc5545"><strong>iCalendar RFC.</strong></a> Not the sexiest subject. But I had to reverse-engineer
how calendar software behaves to get the newsletter to work the way I wanted.
Did you know that the iOS calendar behaves differently if you serve an ICS
file with a 2XX HTTP status code than if you serve it via a 3XX? <a href="https://stackoverflow.com/questions/69631672/how-to-prevent-ios-from-suggesting-to-subscribe-to-an-ics-file-online">I did
not</a>.</li>
<li><a href="https://www.jsonfeed.org/"><strong>JSONFeed.</strong></a> An alternative to RSS written in JSON.</li>
<li><a href="https://micro.blog/statium"><strong>Micro.blog.</strong></a> An alternative to Twitter. Duh.</li>
<li><strong>Modern React web frameworks.</strong> I explored <a href="https://nextjs.org/">Next.js</a> first, then
<a href="https://remix.run/">Remix</a>. Both are pretty amazing.</li>
<li><a href="https://developer.apple.com/documentation/docc"><strong>DocC.</strong></a> The new documentation framework by Apple. I used <a href="https://github.com/realm/jazzy">Jazzy</a>
before, but DocC — albeit sometimes awkward to use — is much more complete. As
illustrated by <a href="/blog/frenchkit-2022/">Simon Støvring’s talk during FrenchKit 2022</a>, you can
create amazing documentation with it.</li>
<li><a href="https://developer.apple.com/xcode-cloud/"><strong>Xcode Cloud.</strong></a> First-party CI and automated distribution of apps. It
seems to cover some common ground with Fastlane, very convenient to distribute
the app I use to run the newsletter on my iPhone and iPad via TestFlight.</li>
<li><a href="https://developer.apple.com/documentation/tabulardata"><strong>TabularData.</strong></a> A new framework provided by Apple to organize and
explore data.</li>
<li><a href="https://developer.apple.com/xcode/swiftui/"><strong>SwiftUI.</strong></a> I am sure you heard of it already 😉.</li>
<li><a href="https://github.com/pointfreeco/swift-composable-architecture"><strong>The Composable Architecture.</strong></a> A 3rd-party architecture library that
solved all the problems that were holding me back when I first got my toes wet
with SwiftUI.</li>
<li><a href="https://developers.google.com/optimization"><strong>OR-Tools library.</strong></a> Linear optimization library available in Python
among others. Picking the right tools to do the right job is essential. I
found this library to act like a Microsoft Excel solver.</li>
</ul>
<p>Each of these tools would deserve their own post. I shouldn’t run out of
inspiration for this blog next year.</p>
<h2>The positive impacts on my paying job</h2>
<h3>🧰 A better set of tools I can use</h3>
<p>Part of my job at Memo Bank is to write the iOS app that serves as the 2FA for
critical operations. The app does not do much. But the things it does are
critical. Our customers depend on it to pay salaries to their employees, pay
taxes on time, and be safe with their transactions.</p>
<p>The app was bootstrapped with compatibility going up to iOS 12. Now that I got
my hands dirty with SwiftUI and <em>The Composable Architecture</em> on my side
projects, I feel comfortable adopting them in the app for upcoming features.
That would not have been the case without the time I spent toying with them
outside of work. I made many mistakes. I rewrote things multiple times. It is
part of learning.</p>
<p>Could this time spent on my free time have been part of the job? Maybe. But I
also like not having to be held accountable for anything when I explore and want
to play around with new things. I do whatever I want, without having to justify
my decisions to anyone else but me. I am in complete control of every decision.
Freedom & Fun.</p>
<h3>🤗 A greater empathy</h3>
<blockquote>
<p>“It should be easy, it is <em>just</em> a button.”</p>
<p>— Quite probably my past self, in a self-indulgent micro-agression towards
some designer from my team. Who the hell did I think I was?</p>
</blockquote>
<p>I wear every possible hat running this newsletter. I am a developer, a product
manager, and a designer. I do customer support, manage the infrastructure, etc.</p>
<p>I get a glimpse of the struggles that every job comes with. I get reminded that
good design is hard to get right, that keeping an infrastructure up takes
effort, that making product decisions is hard. It is a useful humbling reminder
that shit is hard to get right.</p>
<h2>My tips and life lessons</h2>
<p><strong>🧱 Use a monorepo.</strong> Over time, I realized that using a monorepo for my
projects was much more thrilling than having a million bunny projects with 5
commits each. The feeling of progress is much more satisfying.</p>
<p><strong>🏎 Take shortcuts towards the fun.</strong> If you host your code on GitHub, don’t use
an organization and don’t open-source what you are doing until you feel ready.
For instance, at work, you should never leave secrets in your code. On a side
project, why not? If stakes are low, leave the boring required things you write
at work behind and sprint towards the fun parts.</p>
<p><strong>⏱ How to find time for it?</strong> Any opportunity is good to take. My wife agreed
to leave me off kids-duty on Thursday evenings. I try to write a little bit of
code everyday, and when you work remotely, the lunch break is ideal for that.
Over time, things build up and the output you get from a 5-minute slot grows.</p>
<p>Also, I recommend reading <a href="https://www.avanderlee.com/optimization/side-projects-10-tips-for-being-successful/">these tips from Antoine van der Lee</a>. They are
great to act as a guard rail against uncontrollable impulses to try this new
thing. His point is to keep focus, realistic expectations and priorities
straight.</p>
<h2>Conclusion</h2>
<p>Why are you still reading this? Go work on your side project!</p>
<section class="footnotes">
<ol>
<li id="fn1">
<p>I won’t discuss which term is best. I can’t put it better than <a href="https://kottke.org/22/12/fox-sports-us-world-cup-coverage-is-an-unmissable-abomination">Jason Kottke
does</a>: “it’s the sort of debate that 4th graders have on the playground”. <a href="#fnref1" class="footnote-backref">↩</a></p>
</li>
</ol>
</section>Mick FReviewing 2022, the benefits I gained from running a newsletter as a side project are tremendous. Let me explain why.Notes from FrenchKit 20222022-10-07T00:00:00+00:002022-10-07T00:00:00+00:00https://bootstragram.com/blog/frenchkit-2022<p>Previous editions: <a href="/blog/frenchkit-2019/">2019</a> · <a href="/blog/frenchkit-2018/">2018</a> · <a href="/blog/frenchkit-2017/">2017</a></p>
<h2>🌟 The talks I recommend</h2>
<h3>any Idea How to Use some Generics? — Antoine van der Lee</h3>
<p>Dive into the difference between <code>some</code> vs <code>any</code> and how they can replace older
forms of expressing constraints on generics.</p>
<p>Note the choice of case in the title. 🤣</p>
<p>— <a href="https://youtu.be/xT5aaMyyR9c">📺 Watch</a></p>
<h3>Building Custom Property Wrappers for SwiftUI (and how to test them) — Donny Wals</h3>
<p>Implements
<a href="https://github.com/donnywals/SwiftUIPropertyWrapperTalk">a fetching data property wrapper for SwiftUI</a>.</p>
<p>Donny is quite interesting because, year after year, he focuses on predicting
what Apple plans for the future as opposed to investing in 3rd-party libraries
or frameworks.</p>
<p>For instance, he seems genuinely convinced that, for Apple people, data fetchers
belong to views — following Apple’s own APIs — and that Combine and Core Data
are just as relevant in 2022 as they always were.</p>
<p>— <a href="https://youtu.be/x3-GZzjeH9g">📺 Watch</a></p>
<h3>Masterclass: Hacking iOS Apps - and trying to prevent it — Elliot Schrock</h3>
<p>This talk provided a lot of valuable insights. Using a jailbroken iPhone, Elliot
went backwards from the demo to the theory of hacking, highlighting how to
control the app from the CLI. Elliot also mentioned this funny analogy about
security:</p>
<blockquote>
<p>🏃 🐻 If you’re being chased by a bear, you must run faster than the slowest
guy.</p>
</blockquote>
<p>Tools mentioned:</p>
<ul>
<li><a href="http://www.cycript.org"><code>cycript</code></a> (pronounced ssssscript apparently);</li>
<li><a href="https://www.hopperapp.com">Hopper</a> (disassembler);</li>
<li><a href="https://github.com/KJCracks/Clutch">Clutch</a> (executable dumper);</li>
<li><a href="https://wiki.smhuda.com/pentesting/tool-usage/ipainstaller">ipainstaller</a>.</li>
</ul>
<h3>Swift Quiz — Vincent Pradeilles</h3>
<p>Three questions:</p>
<ol>
<li>✅ Retaining cycles. I got this one right.</li>
<li>❌ Mutating function on a <code>struct</code> with a <code>let</code> property. You can reassign
<code>self</code>. I got it wrong.</li>
<li>❌ Empty enums. Does the Swift compiler accept a function that has a
signature that returns something but actually has an empty body, when the
argument is an empty enum (which cannot be instantiated)? It does. I got it
wrong too. But it explains why the signature of <code>fatalError</code> is <code>Never</code>.</li>
</ol>
<p>— <a href="https://youtu.be/mUBBY6JCC0c">📺 Watch</a></p>
<h2>🔥 Hot Takes</h2>
<p><strong>I am so bad at socializing during conferences.</strong> My interactions with other
human beings at FrenchKit consisted primarily of asking questions during Q&A
sessions, but it was hard for me to go beyond that. I tried to attend an event
on the Thursday evening. But a bar packed with 200 people, 95% of which are
nerdy dorks like myself — now quite younger — is not an environment I can thrive
in. I am taller and deafer than the average person so that would translate into
me bending over groups of people, pretending I can hear what is being said. Not
quite fun.</p>
<p><strong>Next time, I will try to attend NSSpain.</strong> It was the last edition of
FrenchKit per say. The hosts are willing to expand to other technologies — which
will be appreciated after the dot conferences disappeared after being bought by
Welcome to the Jungle.</p>
<p><strong>I will build next features at work for iOS 14 users.</strong> Today, we support
iOS 12. Looking at iosref’s
<a href="https://iosref.com/ios">iOS version by device table</a>, I realized that bumping
straight to iOS 14 will leave aside some devices stuck with iOS 12. But no
device is stuck with iOS 13 — or iOS 14 for that matter. So, at the end of the
day, iOS 14 sounds like a sweet spot to be able to benefit both from async/await
and a decent version of SwiftUI.</p>
<p><strong>I should give things a second chance.</strong> I’m talking about you jailbreaking
phones — to put myself in hackers shoes — and UI testing.</p>
<h2>🪣 The rest of my notes</h2>
<p>Here I share raw notes from the other talks cause… why not?</p>
<h3>Beyond Code: Skills for Impactful Development That Makes a Difference — Maxim Cramer</h3>
<p>How to make impact beyond code? Mostly about communication. 🦒 The image of the
giraffe in a park that every can sort of picture vs feature descriptions. We
share very little of our knowledge when we communicate. Shocking fact: 2% of
funding is for female founders.</p>
<p>— <a href="https://youtu.be/epYcQluY954">📺 Watch</a></p>
<h3>I 💕 Swift Concurrency — Tunde Adegoroye</h3>
<p>Concurrency, <code>async let</code>, tasks and tasks groups, actors.</p>
<p>— <a href="https://youtu.be/HptV7UUTToQ">📺 Watch</a></p>
<h3>How not to develop an “Umm” detector using Create ML — Yono Mittlefehldt</h3>
<p>Um detector. Not really about code but about the thought process. Some tools
mentioned worth bookmarking:</p>
<ul>
<li>sox CLI (<a href="https://sox.sourceforge.net">https://sox.sourceforge.net</a>)</li>
<li><a href="https://pytorch.org/">PyTorch</a></li>
<li><a href="https://www.tensorflow.org/">TensorFlow</a></li>
<li><a href="https://developer.apple.com/machine-learning/">Machine Learning - Apple Developer</a></li>
</ul>
<p>— <a href="https://youtu.be/45hxH7-p5IM">📺 Watch</a></p>
<h3>Documenting your project with DocC — Simon Støvring</h3>
<p>Wrap up around DocC by the author of the
<a href="https://apps.apple.com/fr/app/runestone-text-editor/id1548193893?l=en">Runestone</a>
text editor.</p>
<p>I started using DocC with my <a href="https://swiftpackageindex.com/dirtyhenry/swift-blocks/">Blocks</a> project, but I didn’t know about
<a href="https://developer.apple.com/documentation/docc/tutorials">tutorials</a> with DocC.</p>
<p>— <a href="https://youtu.be/paR2INQMc3s">📺 Watch</a></p>
<h3>SwiftUI at Scale — Thomas Ricouard</h3>
<p>The point was that SwiftUI is production ready. And apparently, it’s tied to the
iOS version running the phone so it doesn’t make sense to use SwiftUI before
iOS 14. I guess this means that SwiftUI in iOS 13 is broken.</p>
<p>— <a href="https://youtu.be/tsZBLvdcoTw">📺 Watch</a></p>
<h3>SwiftUI Navigation — Luis Ascorbe</h3>
<p>Should we use SwiftUI or UIKit for navigation? It depends. He recommended the
following:</p>
<ul>
<li>NavigationStack.</li>
<li><a href="https://vimeo.com/751580644">Brandon Williams: SwiftUI Navigation & URL Routing</a>.</li>
<li>Taking a look at
<a href="https://github.com/pointfreeco/swift-composable-architecture">The Composable Architecture</a>
aka <em>TCA</em> which sounds like a good idea.</li>
<li><a href="https://vimeo.com/751173570">Composable Architecture at Scale</a> by Krzysztof
Zablocki</li>
</ul>
<p>— <a href="https://youtu.be/jhO9gfVg6_U">📺 Watch</a></p>
<h3>From source code to executable: a day in the life of a Build System — Samuel Giddinis</h3>
<p>Why do we need build systems and the complexity behind them. Some notes:</p>
<ul>
<li><a href="https://github.com/realm/SwiftLint/blob/main/Makefile">Swiftlint has a Makefile</a>;</li>
<li><a href="https://bazel.build">Bazel</a> is great for large monorepos but require a
significant investment and care</li>
</ul>
<p><img src="https://imgs.xkcd.com/comics/compiling.png" alt="My Code’s Compiling" /></p>
<p><a href="https://github.com/segiddins/Bazel-iOS-Workshop">GitHub - segiddins/Bazel-iOS-Workshop: AppBuilders 2022 workshop on building an iOS app with Bazel</a></p>
<p>— <a href="https://youtu.be/dpxqSjSKJYA">📺 Watch</a></p>
<h3>Migrating your legacy code to local SPM packages — Zouhair Mahieddine</h3>
<p>This title describes exactly the content of the talk. 😆</p>
<p>— <a href="https://youtu.be/bXCuVzsdyDo">📺 Watch</a></p>
<h3>Testing for Accessibility — Robin Kanatzar</h3>
<p>A reminder that the accessibility tool is good. Robin recommended the following
tool:</p>
<ul>
<li><a href="https://www.evinced.com/products/flow-analyzer-for-mobile">Mobile Flow Analyzer - Evinced, Inc.</a></li>
</ul>
<p>— <a href="https://youtu.be/59uLAKVB-d4">📺 Watch</a></p>
<h3>Cloud Functions to the rescue — Zamzam Farzamipooya</h3>
<p>The talk focuses on Firebase/Google. That’s kind of a non starter for my tastes.</p>
<p>It reminded me of stuff I did for Flâneur and why I didn’t like Firebase: data
access boundaries are fragile and require significant effort.</p>
<p>— <a href="https://youtu.be/DdGfFkOz9gQ">📺 Watch</a></p>
<h3>From App to Game Development — Michel-André Chirita</h3>
<p>Interesting. The game is:
<a href="https://apps.apple.com/fr/app/versus-arena/id1542795843?l=en">Versus Arena</a></p>
<p>It made me want to develop a Backgammon game again.</p>
<p>— <a href="https://youtu.be/HLz0Cfkaiew">📺 Watch</a></p>
<h3>The journey of Widgets begins with one step — Audrey Sobgou-Zebaze</h3>
<p>How to start implementing widgets with
<a href="https://developer.apple.com/widgets/">WidgetKit</a>.</p>
<p>— <a href="https://youtu.be/XRMcdUUA0HU">📺 Watch</a></p>Mick FSwift new features, SwiftUI and The Composable Architecture stole the show. As always, conferences help getting your coding juices flowing.Dealing With Dates With No Time in Swift2022-03-02T00:00:00+00:002022-03-02T00:00:00+00:00https://bootstragram.com/blog/date-with-no-time-operations-swift<h2>Where the <del>Streets</del> Dates Have No <del>Name</del> Time</h2>
<p><em>Where the Streets Have No Name</em>, the U2 song, was released on 31 August 1987.
At what time? It is not relevant. Just the date, ie the calendar day, matters.
If I had to translate this in a JSON API, I would write the following:
<code>{"release_date": "1987-08-31"}</code>, ie basic ISO 8601, no time or time zone.</p>
<p>When dealing with such <em>dates with no time</em>, some operations can be necessary.
For instance, what is the 7th day after 31 August 1987? No dependency allowed.
This blog post will answer this question. Easy peasy, right?</p>
<p>But beware, the experienced developer should be humble when coding such
operations. Dealing with calendars is hard and I can easily prove it. Just
answer this simple question: is every minute in a calendar 60 seconds long? If
you answered “yes”, you failed the test.
<a href="https://yourcalendricalfallacyis.com">Go study and have fun</a>.</p>
<p>Hopefully, Swift’s <code>Foundation</code> provides everything we need to avoid obvious
traps. Let’s see what a struct to handle <em>dates with no time</em> could look like.</p>
<h2>The implementation</h2>
<p>This is the end result. A discussion follows.</p>
<div class="language-swift highlighter-rouge"><div class="highlight"><pre class="highlight"><code data-lang="swift"><span class="kd">import</span> <span class="kt">Foundation</span>
<span class="c1">/// A string that represents dates using their ISO 8601 representations.</span>
<span class="c1">///</span>
<span class="c1">/// `PlainDate` is a way to handle dates with no time — such as `2022-03-02` for March 2nd of 2022 — to</span>
<span class="c1">/// perform operations with convenience including adding days, dealing with ranges, etc.</span>
<span class="c1">///</span>
<span class="c1">/// ## Usage Overview</span>
<span class="c1">///</span>
<span class="c1">/// A plain date can be initiated from a string literal, and can be used to create ranges.</span>
<span class="c1">///</span>
<span class="c1">/// let plainDate: PlainDate = "2022-03-01"</span>
<span class="c1">/// let aWeekLater = plainDate.advanced(by: 7)</span>
<span class="c1">/// for day in march1st ..< aWeekLater {</span>
<span class="c1">/// print(day)</span>
<span class="c1">/// }</span>
<span class="kd">public</span> <span class="kd">struct</span> <span class="kt">PlainDate</span> <span class="p">{</span>
<span class="c1">// MARK: - Creating an instance</span>
<span class="c1">/// Returns a date string initialized using their ISO 8601 representation.</span>
<span class="c1">/// - Parameters:</span>
<span class="c1">/// - dateAsString: The ISO 8601 representation of the date. For instance, `2022-03-02`for March 2nd of 2022.</span>
<span class="c1">/// - calendar: The calendar — including the time zone — to use. The default is the current calendar.</span>
<span class="c1">/// - Returns: A date string, or `nil` if a valid date could not be created from `dateAsString`.</span>
<span class="kd">public</span> <span class="nf">init</span><span class="p">?(</span><span class="n">from</span> <span class="nv">dateAsString</span><span class="p">:</span> <span class="kt">String</span><span class="p">,</span> <span class="nv">calendar</span><span class="p">:</span> <span class="kt">Calendar</span> <span class="o">=</span> <span class="o">.</span><span class="n">current</span><span class="p">)</span> <span class="p">{</span>
<span class="k">let</span> <span class="nv">formatter</span> <span class="o">=</span> <span class="k">Self</span><span class="o">.</span><span class="nf">createFormatter</span><span class="p">(</span><span class="nv">timeZone</span><span class="p">:</span> <span class="n">calendar</span><span class="o">.</span><span class="n">timeZone</span><span class="p">)</span>
<span class="k">guard</span> <span class="k">let</span> <span class="nv">date</span> <span class="o">=</span> <span class="n">formatter</span><span class="o">.</span><span class="nf">date</span><span class="p">(</span><span class="nv">from</span><span class="p">:</span> <span class="n">dateAsString</span><span class="p">)</span> <span class="k">else</span> <span class="p">{</span>
<span class="k">return</span> <span class="kc">nil</span>
<span class="p">}</span>
<span class="k">self</span><span class="o">.</span><span class="nf">init</span><span class="p">(</span><span class="nv">date</span><span class="p">:</span> <span class="n">date</span><span class="p">,</span> <span class="nv">calendar</span><span class="p">:</span> <span class="n">calendar</span><span class="p">,</span> <span class="nv">formatter</span><span class="p">:</span> <span class="n">formatter</span><span class="p">)</span>
<span class="p">}</span>
<span class="c1">/// Returns a date string initialized using their ISO 8601 representation.</span>
<span class="c1">/// - Parameters:</span>
<span class="c1">/// - date: The date to represent.</span>
<span class="c1">/// - calendar: The calendar — including the time zone — to use. The default is the current calendar.</span>
<span class="kd">public</span> <span class="nf">init</span><span class="p">(</span><span class="nv">date</span><span class="p">:</span> <span class="kt">Date</span><span class="p">,</span> <span class="nv">calendar</span><span class="p">:</span> <span class="kt">Calendar</span> <span class="o">=</span> <span class="o">.</span><span class="n">current</span><span class="p">)</span> <span class="p">{</span>
<span class="k">self</span><span class="o">.</span><span class="nf">init</span><span class="p">(</span><span class="nv">date</span><span class="p">:</span> <span class="n">date</span><span class="p">,</span> <span class="nv">calendar</span><span class="p">:</span> <span class="n">calendar</span><span class="p">,</span> <span class="nv">formatter</span><span class="p">:</span> <span class="k">Self</span><span class="o">.</span><span class="nf">createFormatter</span><span class="p">(</span><span class="nv">timeZone</span><span class="p">:</span> <span class="n">calendar</span><span class="o">.</span><span class="n">timeZone</span><span class="p">))</span>
<span class="p">}</span>
<span class="kd">private</span> <span class="nf">init</span><span class="p">(</span><span class="nv">date</span><span class="p">:</span> <span class="kt">Date</span><span class="p">,</span> <span class="nv">calendar</span><span class="p">:</span> <span class="kt">Calendar</span> <span class="o">=</span> <span class="o">.</span><span class="n">current</span><span class="p">,</span> <span class="nv">formatter</span><span class="p">:</span> <span class="kt">ISO8601DateFormatter</span><span class="p">)</span> <span class="p">{</span>
<span class="k">self</span><span class="o">.</span><span class="n">formatter</span> <span class="o">=</span> <span class="n">formatter</span>
<span class="k">self</span><span class="o">.</span><span class="n">date</span> <span class="o">=</span> <span class="n">date</span>
<span class="k">self</span><span class="o">.</span><span class="n">calendar</span> <span class="o">=</span> <span class="n">calendar</span>
<span class="p">}</span>
<span class="c1">// MARK: - Properties</span>
<span class="kd">private</span> <span class="k">let</span> <span class="nv">formatter</span><span class="p">:</span> <span class="kt">ISO8601DateFormatter</span>
<span class="kd">private</span> <span class="k">let</span> <span class="nv">date</span><span class="p">:</span> <span class="kt">Date</span>
<span class="kd">private</span> <span class="k">let</span> <span class="nv">calendar</span><span class="p">:</span> <span class="kt">Calendar</span>
<span class="kd">private</span> <span class="kd">static</span> <span class="kd">func</span> <span class="nf">createFormatter</span><span class="p">(</span><span class="nv">timeZone</span><span class="p">:</span> <span class="kt">TimeZone</span><span class="p">)</span> <span class="o">-></span> <span class="kt">ISO8601DateFormatter</span> <span class="p">{</span>
<span class="k">let</span> <span class="nv">formatter</span> <span class="o">=</span> <span class="kt">ISO8601DateFormatter</span><span class="p">()</span>
<span class="n">formatter</span><span class="o">.</span><span class="n">formatOptions</span> <span class="o">=</span> <span class="p">[</span><span class="o">.</span><span class="n">withFullDate</span><span class="p">]</span>
<span class="n">formatter</span><span class="o">.</span><span class="n">timeZone</span> <span class="o">=</span> <span class="n">timeZone</span>
<span class="k">return</span> <span class="n">formatter</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="kd">extension</span> <span class="kt">PlainDate</span><span class="p">:</span> <span class="kt">ExpressibleByStringLiteral</span> <span class="p">{</span>
<span class="kd">public</span> <span class="nf">init</span><span class="p">(</span><span class="n">stringLiteral</span> <span class="nv">value</span><span class="p">:</span> <span class="kt">String</span><span class="p">)</span> <span class="p">{</span>
<span class="k">self</span><span class="o">.</span><span class="nf">init</span><span class="p">(</span><span class="nv">from</span><span class="p">:</span> <span class="n">value</span><span class="p">)</span><span class="o">!</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="kd">extension</span> <span class="kt">PlainDate</span><span class="p">:</span> <span class="kt">Strideable</span> <span class="p">{</span>
<span class="kd">public</span> <span class="kd">func</span> <span class="nf">distance</span><span class="p">(</span><span class="n">to</span> <span class="nv">other</span><span class="p">:</span> <span class="kt">PlainDate</span><span class="p">)</span> <span class="o">-></span> <span class="kt">Int</span> <span class="p">{</span>
<span class="k">let</span> <span class="nv">timeInterval</span> <span class="o">=</span> <span class="n">date</span><span class="o">.</span><span class="nf">distance</span><span class="p">(</span><span class="nv">to</span><span class="p">:</span> <span class="n">other</span><span class="o">.</span><span class="n">date</span><span class="p">)</span>
<span class="k">return</span> <span class="kt">Int</span><span class="p">(</span><span class="nf">round</span><span class="p">(</span><span class="n">timeInterval</span> <span class="o">/</span> <span class="mf">86400.0</span><span class="p">))</span>
<span class="p">}</span>
<span class="kd">public</span> <span class="kd">func</span> <span class="nf">advanced</span><span class="p">(</span><span class="n">by</span> <span class="nv">value</span><span class="p">:</span> <span class="kt">Int</span><span class="p">)</span> <span class="o">-></span> <span class="kt">PlainDate</span> <span class="p">{</span>
<span class="k">let</span> <span class="nv">newDate</span> <span class="o">=</span> <span class="n">calendar</span><span class="o">.</span><span class="nf">date</span><span class="p">(</span><span class="nv">byAdding</span><span class="p">:</span> <span class="o">.</span><span class="n">day</span><span class="p">,</span> <span class="nv">value</span><span class="p">:</span> <span class="n">value</span><span class="p">,</span> <span class="nv">to</span><span class="p">:</span> <span class="n">date</span><span class="p">)</span><span class="o">!</span>
<span class="k">return</span> <span class="kt">PlainDate</span><span class="p">(</span><span class="nv">date</span><span class="p">:</span> <span class="n">newDate</span><span class="p">,</span> <span class="nv">calendar</span><span class="p">:</span> <span class="n">calendar</span><span class="p">,</span> <span class="nv">formatter</span><span class="p">:</span> <span class="n">formatter</span><span class="p">)</span>
<span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>
<h3>Just how bad is <del>DateString</del> for a name?</h3>
<p>Edit: the first name for this class was <code>DateString</code>. I know. Since, I found out
about <a href="https://tc39.es/proposal-temporal/docs/index.html">the <code>Temporal</code> proposal for JavaScript</a> that introduces something very
similar to what I am trying to achieve here as <code>PlainDate</code>. So I renamed this
accordingly.</p>
<h3>Why are time zones involved in this code?</h3>
<p>It’s the result of logical decisions I made from the tools I had at hand:</p>
<ul>
<li>Converting an ISO 8601 string? Use <code>ISO8601DateFormatter</code>!</li>
<li>What type does <code>ISO8601DateFormatter</code> convert the input <code>String</code> into? As a
<code>Date</code>!</li>
<li>So I should use a <code>Date</code> — with time — to store a <em>date with no time</em>? Yes!</li>
<li>How can I output the date back into a <code>String</code>? Use <code>ISO8601DateFormatter</code>
again!</li>
<li>But look at this test, it’s getting buggy around days on which clocks change
to deal with daylight saving time? Use a time zone!</li>
<li>So I have to carry a <code>Calendar</code> and a <code>TimeZone</code> around? No need: a <code>Calendar</code>
does include a <code>TimeZone</code>, just carry the <code>Calendar</code> around!</li>
</ul>
<h3>Implementing ExpressibleByStringLiteral is so easy and convenient!</h3>
<p>Yes, it is. Expressing a <code>PlainDate</code> just as <code>"1987-08-31"</code> is indeed pretty
awesome.</p>
<h3>Wow, and what about implementing Strideable?</h3>
<p>I know, right? On top of answering our original question
(<code>date.advanced(by: offset)</code>), <code>Strideable</code> helps doing things like:</p>
<div class="language-swift highlighter-rouge"><div class="highlight"><pre class="highlight"><code data-lang="swift"><span class="k">let</span> <span class="nv">startDate</span><span class="p">:</span> <span class="kt">PlainDate</span> <span class="o">=</span> <span class="s">"2022-03-01"</span>
<span class="k">let</span> <span class="nv">aWeekLater</span> <span class="o">=</span> <span class="n">startDate</span><span class="o">.</span><span class="nf">advanced</span><span class="p">(</span><span class="nv">by</span><span class="p">:</span> <span class="mi">7</span><span class="p">)</span>
<span class="k">for</span> <span class="n">date</span> <span class="k">in</span> <span class="n">startDate</span> <span class="o">..<</span> <span class="n">aWeekLater</span> <span class="p">{</span>
<span class="c1">// do something.</span>
<span class="p">}</span>
</code></pre></div></div>
<h3>Wait a minute! Is this a hard-coded 86400 in the code? Isn’t that forbidden?</h3>
<p>🕵️ Good eye. 86400 is the number of seconds in a typical day. But not all days
are 86400 seconds long. But the point here is to compute the distance in days
between two dates, and I think this implementation is OK. I’ll explain why:</p>
<ol>
<li><code>Calendar</code> is ignoring leap seconds. I know because a former Apple Foundation
employee <a href="https://stackoverflow.com/a/71312395/455016">told me so</a>.</li>
<li>See the <code>round</code>? This is enough to deal with daylight savings according to my
tests and my understanding of how daylight savings work. Consider a
collection that contains the number of seconds of an arbitrary number of
<strong>consecutive</strong> days. (a) There’s more than a 99% chance that a randomly
picked item is 86400. (b) Considering that the 2 possible outliers values
will alternate: if you find a 23-hour long day (82800 seconds) in the
collection, you will find a 25-hour long day (90000 seconds) before you find
another occurrence of a 23-hour long day. So the average of a set will tend
to 86400 as it gets bigger.</li>
<li>Here is a more intellectually satisfying alternative for this computation. It
might be more solid if you’re working on something really time-sensitive. But
it is about <strong>2 times slower</strong>. So as long as my fast implementation does not
fail me, I’ll stick to it.</li>
</ol>
<div class="language-swift highlighter-rouge"><div class="highlight"><pre class="highlight"><code data-lang="swift"><span class="k">let</span> <span class="nv">start</span> <span class="o">=</span> <span class="n">calendar</span><span class="o">.</span><span class="nf">ordinality</span><span class="p">(</span><span class="nv">of</span><span class="p">:</span> <span class="o">.</span><span class="n">day</span><span class="p">,</span> <span class="nv">in</span><span class="p">:</span> <span class="o">.</span><span class="n">era</span><span class="p">,</span> <span class="nv">for</span><span class="p">:</span> <span class="k">self</span><span class="o">.</span><span class="n">date</span><span class="p">)</span>
<span class="k">let</span> <span class="nv">end</span> <span class="o">=</span> <span class="n">calendar</span><span class="o">.</span><span class="nf">ordinality</span><span class="p">(</span><span class="nv">of</span><span class="p">:</span> <span class="o">.</span><span class="n">day</span><span class="p">,</span> <span class="nv">in</span><span class="p">:</span> <span class="o">.</span><span class="n">era</span><span class="p">,</span> <span class="nv">for</span><span class="p">:</span> <span class="n">other</span><span class="o">.</span><span class="n">date</span><span class="p">)</span>
<span class="k">guard</span> <span class="k">let</span> <span class="nv">start</span> <span class="o">=</span> <span class="n">start</span><span class="p">,</span> <span class="k">let</span> <span class="nv">end</span> <span class="o">=</span> <span class="n">end</span> <span class="k">else</span> <span class="p">{</span>
<span class="nf">fatalError</span><span class="p">(</span><span class="s">"The distance between 2 dates could not be computed."</span><span class="p">)</span>
<span class="p">}</span>
<span class="k">return</span> <span class="n">end</span> <span class="o">-</span> <span class="n">start</span>
</code></pre></div></div>
<h2>Conclusion</h2>
<p>Mission accomplished. Feel free to use this <code>PlainDate</code> class as is. I’ve been
using it for a hobby project — a soccer newsletter — and it works great. If you
can read French and soccer is your thing, you can <a href="https://www.statium.app/newsletter">sign up for the
newsletter</a>.</p>
<h2>Addendum — iOS & macOS Time Zones</h2>
<p>Here is a table of all the known time zones’ identifiers and abbreviations as
run on iOS 15.2 and macOS 12.2.1.</p>
<p>For some reason, <code>UTC</code> is an abbreviation to the <code>UTC</code> identifier, but <code>UTC</code> is
missing in the list of known identifiers. 🤷♂️ I initalized a time zone using
<code>UTC</code>: it succeeded and querying the identifier returned <code>GMT</code>. Double-🤷♂️.</p>
<table>
<thead>
<tr>
<th>Identifier</th>
<th>Abbreviations</th>
</tr>
</thead>
<tbody>
<tr>
<td>Africa/Abidjan</td>
<td></td>
</tr>
<tr>
<td>Africa/Accra</td>
<td></td>
</tr>
<tr>
<td>Africa/Addis_Ababa</td>
<td>EAT</td>
</tr>
<tr>
<td>Africa/Algiers</td>
<td></td>
</tr>
<tr>
<td>Africa/Asmara</td>
<td></td>
</tr>
<tr>
<td>Africa/Bamako</td>
<td></td>
</tr>
<tr>
<td>Africa/Bangui</td>
<td></td>
</tr>
<tr>
<td>Africa/Banjul</td>
<td></td>
</tr>
<tr>
<td>Africa/Bissau</td>
<td></td>
</tr>
<tr>
<td>Africa/Blantyre</td>
<td></td>
</tr>
<tr>
<td>Africa/Brazzaville</td>
<td></td>
</tr>
<tr>
<td>Africa/Bujumbura</td>
<td></td>
</tr>
<tr>
<td>Africa/Cairo</td>
<td></td>
</tr>
<tr>
<td>Africa/Casablanca</td>
<td></td>
</tr>
<tr>
<td>Africa/Ceuta</td>
<td></td>
</tr>
<tr>
<td>Africa/Conakry</td>
<td></td>
</tr>
<tr>
<td>Africa/Dakar</td>
<td></td>
</tr>
<tr>
<td>Africa/Dar_es_Salaam</td>
<td></td>
</tr>
<tr>
<td>Africa/Djibouti</td>
<td></td>
</tr>
<tr>
<td>Africa/Douala</td>
<td></td>
</tr>
<tr>
<td>Africa/El_Aaiun</td>
<td></td>
</tr>
<tr>
<td>Africa/Freetown</td>
<td></td>
</tr>
<tr>
<td>Africa/Gaborone</td>
<td></td>
</tr>
<tr>
<td>Africa/Harare</td>
<td>CAT</td>
</tr>
<tr>
<td>Africa/Johannesburg</td>
<td></td>
</tr>
<tr>
<td>Africa/Juba</td>
<td></td>
</tr>
<tr>
<td>Africa/Kampala</td>
<td></td>
</tr>
<tr>
<td>Africa/Khartoum</td>
<td></td>
</tr>
<tr>
<td>Africa/Kigali</td>
<td></td>
</tr>
<tr>
<td>Africa/Kinshasa</td>
<td></td>
</tr>
<tr>
<td>Africa/Lagos</td>
<td>WAT</td>
</tr>
<tr>
<td>Africa/Libreville</td>
<td></td>
</tr>
<tr>
<td>Africa/Lome</td>
<td></td>
</tr>
<tr>
<td>Africa/Luanda</td>
<td></td>
</tr>
<tr>
<td>Africa/Lubumbashi</td>
<td></td>
</tr>
<tr>
<td>Africa/Lusaka</td>
<td></td>
</tr>
<tr>
<td>Africa/Malabo</td>
<td></td>
</tr>
<tr>
<td>Africa/Maputo</td>
<td></td>
</tr>
<tr>
<td>Africa/Maseru</td>
<td></td>
</tr>
<tr>
<td>Africa/Mbabane</td>
<td></td>
</tr>
<tr>
<td>Africa/Mogadishu</td>
<td></td>
</tr>
<tr>
<td>Africa/Monrovia</td>
<td></td>
</tr>
<tr>
<td>Africa/Nairobi</td>
<td></td>
</tr>
<tr>
<td>Africa/Ndjamena</td>
<td></td>
</tr>
<tr>
<td>Africa/Niamey</td>
<td></td>
</tr>
<tr>
<td>Africa/Nouakchott</td>
<td></td>
</tr>
<tr>
<td>Africa/Ouagadougou</td>
<td></td>
</tr>
<tr>
<td>Africa/Porto-Novo</td>
<td></td>
</tr>
<tr>
<td>Africa/Sao_Tome</td>
<td></td>
</tr>
<tr>
<td>Africa/Tripoli</td>
<td></td>
</tr>
<tr>
<td>Africa/Tunis</td>
<td></td>
</tr>
<tr>
<td>Africa/Windhoek</td>
<td></td>
</tr>
<tr>
<td>America/Adak</td>
<td></td>
</tr>
<tr>
<td>America/Anchorage</td>
<td></td>
</tr>
<tr>
<td>America/Anguilla</td>
<td></td>
</tr>
<tr>
<td>America/Antigua</td>
<td></td>
</tr>
<tr>
<td>America/Araguaina</td>
<td></td>
</tr>
<tr>
<td>America/Argentina/Buenos_Aires</td>
<td>ART</td>
</tr>
<tr>
<td>America/Argentina/Catamarca</td>
<td></td>
</tr>
<tr>
<td>America/Argentina/Cordoba</td>
<td></td>
</tr>
<tr>
<td>America/Argentina/Jujuy</td>
<td></td>
</tr>
<tr>
<td>America/Argentina/La_Rioja</td>
<td></td>
</tr>
<tr>
<td>America/Argentina/Mendoza</td>
<td></td>
</tr>
<tr>
<td>America/Argentina/Rio_Gallegos</td>
<td></td>
</tr>
<tr>
<td>America/Argentina/Salta</td>
<td></td>
</tr>
<tr>
<td>America/Argentina/San_Juan</td>
<td></td>
</tr>
<tr>
<td>America/Argentina/San_Luis</td>
<td></td>
</tr>
<tr>
<td>America/Argentina/Tucuman</td>
<td></td>
</tr>
<tr>
<td>America/Argentina/Ushuaia</td>
<td></td>
</tr>
<tr>
<td>America/Aruba</td>
<td></td>
</tr>
<tr>
<td>America/Asuncion</td>
<td></td>
</tr>
<tr>
<td>America/Atikokan</td>
<td></td>
</tr>
<tr>
<td>America/Bahia</td>
<td></td>
</tr>
<tr>
<td>America/Bahia_Banderas</td>
<td></td>
</tr>
<tr>
<td>America/Barbados</td>
<td></td>
</tr>
<tr>
<td>America/Belem</td>
<td></td>
</tr>
<tr>
<td>America/Belize</td>
<td></td>
</tr>
<tr>
<td>America/Blanc-Sablon</td>
<td></td>
</tr>
<tr>
<td>America/Boa_Vista</td>
<td></td>
</tr>
<tr>
<td>America/Bogota</td>
<td>COT</td>
</tr>
<tr>
<td>America/Boise</td>
<td></td>
</tr>
<tr>
<td>America/Cambridge_Bay</td>
<td></td>
</tr>
<tr>
<td>America/Campo_Grande</td>
<td></td>
</tr>
<tr>
<td>America/Cancun</td>
<td></td>
</tr>
<tr>
<td>America/Caracas</td>
<td></td>
</tr>
<tr>
<td>America/Cayenne</td>
<td></td>
</tr>
<tr>
<td>America/Cayman</td>
<td></td>
</tr>
<tr>
<td>America/Chicago</td>
<td>CDT, CST</td>
</tr>
<tr>
<td>America/Chihuahua</td>
<td></td>
</tr>
<tr>
<td>America/Costa_Rica</td>
<td></td>
</tr>
<tr>
<td>America/Creston</td>
<td></td>
</tr>
<tr>
<td>America/Cuiaba</td>
<td></td>
</tr>
<tr>
<td>America/Curacao</td>
<td></td>
</tr>
<tr>
<td>America/Danmarkshavn</td>
<td></td>
</tr>
<tr>
<td>America/Dawson</td>
<td></td>
</tr>
<tr>
<td>America/Dawson_Creek</td>
<td></td>
</tr>
<tr>
<td>America/Denver</td>
<td>MDT</td>
</tr>
<tr>
<td>America/Detroit</td>
<td></td>
</tr>
<tr>
<td>America/Dominica</td>
<td></td>
</tr>
<tr>
<td>America/Edmonton</td>
<td></td>
</tr>
<tr>
<td>America/Eirunepe</td>
<td></td>
</tr>
<tr>
<td>America/El_Salvador</td>
<td></td>
</tr>
<tr>
<td>America/Fort_Nelson</td>
<td></td>
</tr>
<tr>
<td>America/Fortaleza</td>
<td></td>
</tr>
<tr>
<td>America/Glace_Bay</td>
<td></td>
</tr>
<tr>
<td>America/Godthab</td>
<td></td>
</tr>
<tr>
<td>America/Goose_Bay</td>
<td></td>
</tr>
<tr>
<td>America/Grand_Turk</td>
<td></td>
</tr>
<tr>
<td>America/Grenada</td>
<td></td>
</tr>
<tr>
<td>America/Guadeloupe</td>
<td></td>
</tr>
<tr>
<td>America/Guatemala</td>
<td></td>
</tr>
<tr>
<td>America/Guayaquil</td>
<td></td>
</tr>
<tr>
<td>America/Guyana</td>
<td></td>
</tr>
<tr>
<td>America/Halifax</td>
<td>ADT, AST</td>
</tr>
<tr>
<td>America/Havana</td>
<td></td>
</tr>
<tr>
<td>America/Hermosillo</td>
<td></td>
</tr>
<tr>
<td>America/Indiana/Indianapolis</td>
<td></td>
</tr>
<tr>
<td>America/Indiana/Knox</td>
<td></td>
</tr>
<tr>
<td>America/Indiana/Marengo</td>
<td></td>
</tr>
<tr>
<td>America/Indiana/Petersburg</td>
<td></td>
</tr>
<tr>
<td>America/Indiana/Tell_City</td>
<td></td>
</tr>
<tr>
<td>America/Indiana/Vevay</td>
<td></td>
</tr>
<tr>
<td>America/Indiana/Vincennes</td>
<td></td>
</tr>
<tr>
<td>America/Indiana/Winamac</td>
<td></td>
</tr>
<tr>
<td>America/Inuvik</td>
<td></td>
</tr>
<tr>
<td>America/Iqaluit</td>
<td></td>
</tr>
<tr>
<td>America/Jamaica</td>
<td></td>
</tr>
<tr>
<td>America/Juneau</td>
<td>AKDT, AKST</td>
</tr>
<tr>
<td>America/Kentucky/Louisville</td>
<td></td>
</tr>
<tr>
<td>America/Kentucky/Monticello</td>
<td></td>
</tr>
<tr>
<td>America/Kralendijk</td>
<td></td>
</tr>
<tr>
<td>America/La_Paz</td>
<td></td>
</tr>
<tr>
<td>America/Lima</td>
<td>PET</td>
</tr>
<tr>
<td>America/Los_Angeles</td>
<td>PDT, PST</td>
</tr>
<tr>
<td>America/Lower_Princes</td>
<td></td>
</tr>
<tr>
<td>America/Maceio</td>
<td></td>
</tr>
<tr>
<td>America/Managua</td>
<td></td>
</tr>
<tr>
<td>America/Manaus</td>
<td></td>
</tr>
<tr>
<td>America/Marigot</td>
<td></td>
</tr>
<tr>
<td>America/Martinique</td>
<td></td>
</tr>
<tr>
<td>America/Matamoros</td>
<td></td>
</tr>
<tr>
<td>America/Mazatlan</td>
<td></td>
</tr>
<tr>
<td>America/Menominee</td>
<td></td>
</tr>
<tr>
<td>America/Merida</td>
<td></td>
</tr>
<tr>
<td>America/Metlakatla</td>
<td></td>
</tr>
<tr>
<td>America/Mexico_City</td>
<td></td>
</tr>
<tr>
<td>America/Miquelon</td>
<td></td>
</tr>
<tr>
<td>America/Moncton</td>
<td></td>
</tr>
<tr>
<td>America/Monterrey</td>
<td></td>
</tr>
<tr>
<td>America/Montevideo</td>
<td></td>
</tr>
<tr>
<td>America/Montreal</td>
<td></td>
</tr>
<tr>
<td>America/Montserrat</td>
<td></td>
</tr>
<tr>
<td>America/Nassau</td>
<td></td>
</tr>
<tr>
<td>America/New_York</td>
<td>EDT, EST</td>
</tr>
<tr>
<td>America/Nipigon</td>
<td></td>
</tr>
<tr>
<td>America/Nome</td>
<td></td>
</tr>
<tr>
<td>America/Noronha</td>
<td></td>
</tr>
<tr>
<td>America/North_Dakota/Beulah</td>
<td></td>
</tr>
<tr>
<td>America/North_Dakota/Center</td>
<td></td>
</tr>
<tr>
<td>America/North_Dakota/New_Salem</td>
<td></td>
</tr>
<tr>
<td>America/Nuuk</td>
<td></td>
</tr>
<tr>
<td>America/Ojinaga</td>
<td></td>
</tr>
<tr>
<td>America/Panama</td>
<td></td>
</tr>
<tr>
<td>America/Pangnirtung</td>
<td></td>
</tr>
<tr>
<td>America/Paramaribo</td>
<td></td>
</tr>
<tr>
<td>America/Phoenix</td>
<td>MST</td>
</tr>
<tr>
<td>America/Port-au-Prince</td>
<td></td>
</tr>
<tr>
<td>America/Port_of_Spain</td>
<td></td>
</tr>
<tr>
<td>America/Porto_Velho</td>
<td></td>
</tr>
<tr>
<td>America/Puerto_Rico</td>
<td></td>
</tr>
<tr>
<td>America/Punta_Arenas</td>
<td></td>
</tr>
<tr>
<td>America/Rainy_River</td>
<td></td>
</tr>
<tr>
<td>America/Rankin_Inlet</td>
<td></td>
</tr>
<tr>
<td>America/Recife</td>
<td></td>
</tr>
<tr>
<td>America/Regina</td>
<td></td>
</tr>
<tr>
<td>America/Resolute</td>
<td></td>
</tr>
<tr>
<td>America/Rio_Branco</td>
<td></td>
</tr>
<tr>
<td>America/Santa_Isabel</td>
<td></td>
</tr>
<tr>
<td>America/Santarem</td>
<td></td>
</tr>
<tr>
<td>America/Santiago</td>
<td>CLST, CLT</td>
</tr>
<tr>
<td>America/Santo_Domingo</td>
<td></td>
</tr>
<tr>
<td>America/Sao_Paulo</td>
<td>BRST, BRT</td>
</tr>
<tr>
<td>America/Scoresbysund</td>
<td></td>
</tr>
<tr>
<td>America/Shiprock</td>
<td></td>
</tr>
<tr>
<td>America/Sitka</td>
<td></td>
</tr>
<tr>
<td>America/St_Barthelemy</td>
<td></td>
</tr>
<tr>
<td>America/St_Johns</td>
<td>NDT, NST</td>
</tr>
<tr>
<td>America/St_Kitts</td>
<td></td>
</tr>
<tr>
<td>America/St_Lucia</td>
<td></td>
</tr>
<tr>
<td>America/St_Thomas</td>
<td></td>
</tr>
<tr>
<td>America/St_Vincent</td>
<td></td>
</tr>
<tr>
<td>America/Swift_Current</td>
<td></td>
</tr>
<tr>
<td>America/Tegucigalpa</td>
<td></td>
</tr>
<tr>
<td>America/Thule</td>
<td></td>
</tr>
<tr>
<td>America/Thunder_Bay</td>
<td></td>
</tr>
<tr>
<td>America/Tijuana</td>
<td></td>
</tr>
<tr>
<td>America/Toronto</td>
<td></td>
</tr>
<tr>
<td>America/Tortola</td>
<td></td>
</tr>
<tr>
<td>America/Vancouver</td>
<td></td>
</tr>
<tr>
<td>America/Whitehorse</td>
<td></td>
</tr>
<tr>
<td>America/Winnipeg</td>
<td></td>
</tr>
<tr>
<td>America/Yakutat</td>
<td></td>
</tr>
<tr>
<td>America/Yellowknife</td>
<td></td>
</tr>
<tr>
<td>Antarctica/Casey</td>
<td></td>
</tr>
<tr>
<td>Antarctica/Davis</td>
<td></td>
</tr>
<tr>
<td>Antarctica/DumontDUrville</td>
<td></td>
</tr>
<tr>
<td>Antarctica/Macquarie</td>
<td></td>
</tr>
<tr>
<td>Antarctica/Mawson</td>
<td></td>
</tr>
<tr>
<td>Antarctica/McMurdo</td>
<td></td>
</tr>
<tr>
<td>Antarctica/Palmer</td>
<td></td>
</tr>
<tr>
<td>Antarctica/Rothera</td>
<td></td>
</tr>
<tr>
<td>Antarctica/South_Pole</td>
<td></td>
</tr>
<tr>
<td>Antarctica/Syowa</td>
<td></td>
</tr>
<tr>
<td>Antarctica/Troll</td>
<td></td>
</tr>
<tr>
<td>Antarctica/Vostok</td>
<td></td>
</tr>
<tr>
<td>Arctic/Longyearbyen</td>
<td></td>
</tr>
<tr>
<td>Asia/Aden</td>
<td></td>
</tr>
<tr>
<td>Asia/Almaty</td>
<td></td>
</tr>
<tr>
<td>Asia/Amman</td>
<td></td>
</tr>
<tr>
<td>Asia/Anadyr</td>
<td></td>
</tr>
<tr>
<td>Asia/Aqtau</td>
<td></td>
</tr>
<tr>
<td>Asia/Aqtobe</td>
<td></td>
</tr>
<tr>
<td>Asia/Ashgabat</td>
<td></td>
</tr>
<tr>
<td>Asia/Atyrau</td>
<td></td>
</tr>
<tr>
<td>Asia/Baghdad</td>
<td></td>
</tr>
<tr>
<td>Asia/Bahrain</td>
<td></td>
</tr>
<tr>
<td>Asia/Baku</td>
<td></td>
</tr>
<tr>
<td>Asia/Bangkok</td>
<td>ICT</td>
</tr>
<tr>
<td>Asia/Barnaul</td>
<td></td>
</tr>
<tr>
<td>Asia/Beirut</td>
<td></td>
</tr>
<tr>
<td>Asia/Bishkek</td>
<td></td>
</tr>
<tr>
<td>Asia/Brunei</td>
<td></td>
</tr>
<tr>
<td>Asia/Calcutta</td>
<td></td>
</tr>
<tr>
<td>Asia/Chita</td>
<td></td>
</tr>
<tr>
<td>Asia/Choibalsan</td>
<td></td>
</tr>
<tr>
<td>Asia/Chongqing</td>
<td></td>
</tr>
<tr>
<td>Asia/Colombo</td>
<td></td>
</tr>
<tr>
<td>Asia/Damascus</td>
<td></td>
</tr>
<tr>
<td>Asia/Dhaka</td>
<td>BDT</td>
</tr>
<tr>
<td>Asia/Dili</td>
<td></td>
</tr>
<tr>
<td>Asia/Dubai</td>
<td>GST</td>
</tr>
<tr>
<td>Asia/Dushanbe</td>
<td></td>
</tr>
<tr>
<td>Asia/Famagusta</td>
<td></td>
</tr>
<tr>
<td>Asia/Gaza</td>
<td></td>
</tr>
<tr>
<td>Asia/Harbin</td>
<td></td>
</tr>
<tr>
<td>Asia/Hebron</td>
<td></td>
</tr>
<tr>
<td>Asia/Ho_Chi_Minh</td>
<td></td>
</tr>
<tr>
<td>Asia/Hong_Kong</td>
<td>HKT</td>
</tr>
<tr>
<td>Asia/Hovd</td>
<td></td>
</tr>
<tr>
<td>Asia/Irkutsk</td>
<td></td>
</tr>
<tr>
<td>Asia/Jakarta</td>
<td>WIT</td>
</tr>
<tr>
<td>Asia/Jayapura</td>
<td></td>
</tr>
<tr>
<td>Asia/Jerusalem</td>
<td></td>
</tr>
<tr>
<td>Asia/Kabul</td>
<td></td>
</tr>
<tr>
<td>Asia/Kamchatka</td>
<td></td>
</tr>
<tr>
<td>Asia/Karachi</td>
<td>PKT</td>
</tr>
<tr>
<td>Asia/Kashgar</td>
<td></td>
</tr>
<tr>
<td>Asia/Kathmandu</td>
<td></td>
</tr>
<tr>
<td>Asia/Katmandu</td>
<td></td>
</tr>
<tr>
<td>Asia/Khandyga</td>
<td></td>
</tr>
<tr>
<td>Asia/Krasnoyarsk</td>
<td></td>
</tr>
<tr>
<td>Asia/Kuala_Lumpur</td>
<td></td>
</tr>
<tr>
<td>Asia/Kuching</td>
<td></td>
</tr>
<tr>
<td>Asia/Kuwait</td>
<td></td>
</tr>
<tr>
<td>Asia/Macau</td>
<td></td>
</tr>
<tr>
<td>Asia/Magadan</td>
<td></td>
</tr>
<tr>
<td>Asia/Makassar</td>
<td></td>
</tr>
<tr>
<td>Asia/Manila</td>
<td>PHT</td>
</tr>
<tr>
<td>Asia/Muscat</td>
<td></td>
</tr>
<tr>
<td>Asia/Nicosia</td>
<td></td>
</tr>
<tr>
<td>Asia/Novokuznetsk</td>
<td></td>
</tr>
<tr>
<td>Asia/Novosibirsk</td>
<td></td>
</tr>
<tr>
<td>Asia/Omsk</td>
<td></td>
</tr>
<tr>
<td>Asia/Oral</td>
<td></td>
</tr>
<tr>
<td>Asia/Phnom_Penh</td>
<td></td>
</tr>
<tr>
<td>Asia/Pontianak</td>
<td></td>
</tr>
<tr>
<td>Asia/Pyongyang</td>
<td></td>
</tr>
<tr>
<td>Asia/Qatar</td>
<td></td>
</tr>
<tr>
<td>Asia/Qostanay</td>
<td></td>
</tr>
<tr>
<td>Asia/Qyzylorda</td>
<td></td>
</tr>
<tr>
<td>Asia/Rangoon</td>
<td></td>
</tr>
<tr>
<td>Asia/Riyadh</td>
<td></td>
</tr>
<tr>
<td>Asia/Sakhalin</td>
<td></td>
</tr>
<tr>
<td>Asia/Samarkand</td>
<td></td>
</tr>
<tr>
<td>Asia/Seoul</td>
<td>KST</td>
</tr>
<tr>
<td>Asia/Shanghai</td>
<td></td>
</tr>
<tr>
<td>Asia/Singapore</td>
<td>SGT</td>
</tr>
<tr>
<td>Asia/Srednekolymsk</td>
<td></td>
</tr>
<tr>
<td>Asia/Taipei</td>
<td></td>
</tr>
<tr>
<td>Asia/Tashkent</td>
<td></td>
</tr>
<tr>
<td>Asia/Tbilisi</td>
<td></td>
</tr>
<tr>
<td>Asia/Tehran</td>
<td>IRST</td>
</tr>
<tr>
<td>Asia/Thimphu</td>
<td></td>
</tr>
<tr>
<td>Asia/Tokyo</td>
<td>JST</td>
</tr>
<tr>
<td>Asia/Tomsk</td>
<td></td>
</tr>
<tr>
<td>Asia/Ulaanbaatar</td>
<td></td>
</tr>
<tr>
<td>Asia/Urumqi</td>
<td></td>
</tr>
<tr>
<td>Asia/Ust-Nera</td>
<td></td>
</tr>
<tr>
<td>Asia/Vientiane</td>
<td></td>
</tr>
<tr>
<td>Asia/Vladivostok</td>
<td></td>
</tr>
<tr>
<td>Asia/Yakutsk</td>
<td></td>
</tr>
<tr>
<td>Asia/Yangon</td>
<td></td>
</tr>
<tr>
<td>Asia/Yekaterinburg</td>
<td></td>
</tr>
<tr>
<td>Asia/Yerevan</td>
<td></td>
</tr>
<tr>
<td>Atlantic/Azores</td>
<td></td>
</tr>
<tr>
<td>Atlantic/Bermuda</td>
<td></td>
</tr>
<tr>
<td>Atlantic/Canary</td>
<td></td>
</tr>
<tr>
<td>Atlantic/Cape_Verde</td>
<td></td>
</tr>
<tr>
<td>Atlantic/Faroe</td>
<td></td>
</tr>
<tr>
<td>Atlantic/Madeira</td>
<td></td>
</tr>
<tr>
<td>Atlantic/Reykjavik</td>
<td></td>
</tr>
<tr>
<td>Atlantic/South_Georgia</td>
<td></td>
</tr>
<tr>
<td>Atlantic/St_Helena</td>
<td></td>
</tr>
<tr>
<td>Atlantic/Stanley</td>
<td></td>
</tr>
<tr>
<td>Australia/Adelaide</td>
<td></td>
</tr>
<tr>
<td>Australia/Brisbane</td>
<td></td>
</tr>
<tr>
<td>Australia/Broken_Hill</td>
<td></td>
</tr>
<tr>
<td>Australia/Currie</td>
<td></td>
</tr>
<tr>
<td>Australia/Darwin</td>
<td></td>
</tr>
<tr>
<td>Australia/Eucla</td>
<td></td>
</tr>
<tr>
<td>Australia/Hobart</td>
<td></td>
</tr>
<tr>
<td>Australia/Lindeman</td>
<td></td>
</tr>
<tr>
<td>Australia/Lord_Howe</td>
<td></td>
</tr>
<tr>
<td>Australia/Melbourne</td>
<td></td>
</tr>
<tr>
<td>Australia/Perth</td>
<td></td>
</tr>
<tr>
<td>Australia/Sydney</td>
<td></td>
</tr>
<tr>
<td>Europe/Amsterdam</td>
<td></td>
</tr>
<tr>
<td>Europe/Andorra</td>
<td></td>
</tr>
<tr>
<td>Europe/Astrakhan</td>
<td></td>
</tr>
<tr>
<td>Europe/Athens</td>
<td>EEST, EET</td>
</tr>
<tr>
<td>Europe/Belgrade</td>
<td></td>
</tr>
<tr>
<td>Europe/Berlin</td>
<td></td>
</tr>
<tr>
<td>Europe/Bratislava</td>
<td></td>
</tr>
<tr>
<td>Europe/Brussels</td>
<td></td>
</tr>
<tr>
<td>Europe/Bucharest</td>
<td></td>
</tr>
<tr>
<td>Europe/Budapest</td>
<td></td>
</tr>
<tr>
<td>Europe/Busingen</td>
<td></td>
</tr>
<tr>
<td>Europe/Chisinau</td>
<td></td>
</tr>
<tr>
<td>Europe/Copenhagen</td>
<td></td>
</tr>
<tr>
<td>Europe/Dublin</td>
<td></td>
</tr>
<tr>
<td>Europe/Gibraltar</td>
<td></td>
</tr>
<tr>
<td>Europe/Guernsey</td>
<td></td>
</tr>
<tr>
<td>Europe/Helsinki</td>
<td></td>
</tr>
<tr>
<td>Europe/Isle_of_Man</td>
<td></td>
</tr>
<tr>
<td>Europe/Istanbul</td>
<td>TRT</td>
</tr>
<tr>
<td>Europe/Jersey</td>
<td></td>
</tr>
<tr>
<td>Europe/Kaliningrad</td>
<td></td>
</tr>
<tr>
<td>Europe/Kiev</td>
<td></td>
</tr>
<tr>
<td>Europe/Kirov</td>
<td></td>
</tr>
<tr>
<td>Europe/Lisbon</td>
<td>WEST, WET</td>
</tr>
<tr>
<td>Europe/Ljubljana</td>
<td></td>
</tr>
<tr>
<td>Europe/London</td>
<td>BST</td>
</tr>
<tr>
<td>Europe/Luxembourg</td>
<td></td>
</tr>
<tr>
<td>Europe/Madrid</td>
<td></td>
</tr>
<tr>
<td>Europe/Malta</td>
<td></td>
</tr>
<tr>
<td>Europe/Mariehamn</td>
<td></td>
</tr>
<tr>
<td>Europe/Minsk</td>
<td></td>
</tr>
<tr>
<td>Europe/Monaco</td>
<td></td>
</tr>
<tr>
<td>Europe/Moscow</td>
<td>MSD, MSK</td>
</tr>
<tr>
<td>Europe/Oslo</td>
<td></td>
</tr>
<tr>
<td>Europe/Paris</td>
<td>CEST, CET</td>
</tr>
<tr>
<td>Europe/Podgorica</td>
<td></td>
</tr>
<tr>
<td>Europe/Prague</td>
<td></td>
</tr>
<tr>
<td>Europe/Riga</td>
<td></td>
</tr>
<tr>
<td>Europe/Rome</td>
<td></td>
</tr>
<tr>
<td>Europe/Samara</td>
<td></td>
</tr>
<tr>
<td>Europe/San_Marino</td>
<td></td>
</tr>
<tr>
<td>Europe/Sarajevo</td>
<td></td>
</tr>
<tr>
<td>Europe/Saratov</td>
<td></td>
</tr>
<tr>
<td>Europe/Simferopol</td>
<td></td>
</tr>
<tr>
<td>Europe/Skopje</td>
<td></td>
</tr>
<tr>
<td>Europe/Sofia</td>
<td></td>
</tr>
<tr>
<td>Europe/Stockholm</td>
<td></td>
</tr>
<tr>
<td>Europe/Tallinn</td>
<td></td>
</tr>
<tr>
<td>Europe/Tirane</td>
<td></td>
</tr>
<tr>
<td>Europe/Ulyanovsk</td>
<td></td>
</tr>
<tr>
<td>Europe/Uzhgorod</td>
<td></td>
</tr>
<tr>
<td>Europe/Vaduz</td>
<td></td>
</tr>
<tr>
<td>Europe/Vatican</td>
<td></td>
</tr>
<tr>
<td>Europe/Vienna</td>
<td></td>
</tr>
<tr>
<td>Europe/Vilnius</td>
<td></td>
</tr>
<tr>
<td>Europe/Volgograd</td>
<td></td>
</tr>
<tr>
<td>Europe/Warsaw</td>
<td></td>
</tr>
<tr>
<td>Europe/Zagreb</td>
<td></td>
</tr>
<tr>
<td>Europe/Zaporozhye</td>
<td></td>
</tr>
<tr>
<td>Europe/Zurich</td>
<td></td>
</tr>
<tr>
<td>GMT</td>
<td>GMT</td>
</tr>
<tr>
<td>Indian/Antananarivo</td>
<td></td>
</tr>
<tr>
<td>Indian/Chagos</td>
<td></td>
</tr>
<tr>
<td>Indian/Christmas</td>
<td></td>
</tr>
<tr>
<td>Indian/Cocos</td>
<td></td>
</tr>
<tr>
<td>Indian/Comoro</td>
<td></td>
</tr>
<tr>
<td>Indian/Kerguelen</td>
<td></td>
</tr>
<tr>
<td>Indian/Mahe</td>
<td></td>
</tr>
<tr>
<td>Indian/Maldives</td>
<td></td>
</tr>
<tr>
<td>Indian/Mauritius</td>
<td></td>
</tr>
<tr>
<td>Indian/Mayotte</td>
<td></td>
</tr>
<tr>
<td>Indian/Reunion</td>
<td></td>
</tr>
<tr>
<td>Pacific/Apia</td>
<td></td>
</tr>
<tr>
<td>Pacific/Auckland</td>
<td>NZDT, NZST</td>
</tr>
<tr>
<td>Pacific/Bougainville</td>
<td></td>
</tr>
<tr>
<td>Pacific/Chatham</td>
<td></td>
</tr>
<tr>
<td>Pacific/Chuuk</td>
<td></td>
</tr>
<tr>
<td>Pacific/Easter</td>
<td></td>
</tr>
<tr>
<td>Pacific/Efate</td>
<td></td>
</tr>
<tr>
<td>Pacific/Enderbury</td>
<td></td>
</tr>
<tr>
<td>Pacific/Fakaofo</td>
<td></td>
</tr>
<tr>
<td>Pacific/Fiji</td>
<td></td>
</tr>
<tr>
<td>Pacific/Funafuti</td>
<td></td>
</tr>
<tr>
<td>Pacific/Galapagos</td>
<td></td>
</tr>
<tr>
<td>Pacific/Gambier</td>
<td></td>
</tr>
<tr>
<td>Pacific/Guadalcanal</td>
<td></td>
</tr>
<tr>
<td>Pacific/Guam</td>
<td></td>
</tr>
<tr>
<td>Pacific/Honolulu</td>
<td>HST</td>
</tr>
<tr>
<td>Pacific/Johnston</td>
<td></td>
</tr>
<tr>
<td>Pacific/Kanton</td>
<td></td>
</tr>
<tr>
<td>Pacific/Kiritimati</td>
<td></td>
</tr>
<tr>
<td>Pacific/Kosrae</td>
<td></td>
</tr>
<tr>
<td>Pacific/Kwajalein</td>
<td></td>
</tr>
<tr>
<td>Pacific/Majuro</td>
<td></td>
</tr>
<tr>
<td>Pacific/Marquesas</td>
<td></td>
</tr>
<tr>
<td>Pacific/Midway</td>
<td></td>
</tr>
<tr>
<td>Pacific/Nauru</td>
<td></td>
</tr>
<tr>
<td>Pacific/Niue</td>
<td></td>
</tr>
<tr>
<td>Pacific/Norfolk</td>
<td></td>
</tr>
<tr>
<td>Pacific/Noumea</td>
<td></td>
</tr>
<tr>
<td>Pacific/Pago_Pago</td>
<td></td>
</tr>
<tr>
<td>Pacific/Palau</td>
<td></td>
</tr>
<tr>
<td>Pacific/Pitcairn</td>
<td></td>
</tr>
<tr>
<td>Pacific/Pohnpei</td>
<td></td>
</tr>
<tr>
<td>Pacific/Ponape</td>
<td></td>
</tr>
<tr>
<td>Pacific/Port_Moresby</td>
<td></td>
</tr>
<tr>
<td>Pacific/Rarotonga</td>
<td></td>
</tr>
<tr>
<td>Pacific/Saipan</td>
<td></td>
</tr>
<tr>
<td>Pacific/Tahiti</td>
<td></td>
</tr>
<tr>
<td>Pacific/Tarawa</td>
<td></td>
</tr>
<tr>
<td>Pacific/Tongatapu</td>
<td></td>
</tr>
<tr>
<td>Pacific/Truk</td>
<td></td>
</tr>
<tr>
<td>Pacific/Wake</td>
<td></td>
</tr>
<tr>
<td>Pacific/Wallis</td>
<td></td>
</tr>
</tbody>
</table>
<p>The code to output this table:</p>
<div class="language-swift highlighter-rouge"><div class="highlight"><pre class="highlight"><code data-lang="swift"><span class="kd">import</span> <span class="kt">Foundation</span>
<span class="k">for</span> <span class="n">identifier</span> <span class="k">in</span> <span class="kt">TimeZone</span><span class="o">.</span><span class="n">knownTimeZoneIdentifiers</span><span class="o">.</span><span class="nf">sorted</span><span class="p">()</span> <span class="p">{</span>
<span class="k">let</span> <span class="nv">abbreviations</span> <span class="o">=</span> <span class="kt">TimeZone</span><span class="o">.</span><span class="n">abbreviationDictionary</span><span class="o">.</span><span class="n">filter</span> <span class="p">{</span> <span class="p">(</span><span class="n">k</span><span class="p">,</span> <span class="n">v</span><span class="p">)</span> <span class="k">in</span>
<span class="n">v</span> <span class="o">==</span> <span class="n">identifier</span>
<span class="p">}</span>
<span class="nf">print</span><span class="p">(</span><span class="s">"| </span><span class="se">\(</span><span class="n">identifier</span><span class="se">)</span><span class="s"> | </span><span class="se">\(</span><span class="n">abbreviations</span><span class="o">.</span><span class="n">keys</span><span class="o">.</span><span class="nf">sorted</span><span class="p">()</span><span class="o">.</span><span class="nf">joined</span><span class="p">(</span><span class="nv">separator</span><span class="p">:</span> <span class="s">", "</span><span class="p">)</span><span class="se">)</span><span class="s"> |"</span><span class="p">)</span>
<span class="p">}</span>
</code></pre></div></div>Mick FExploring some code wrappring dates with no time, ie represented as strings in the YYYY‑MM‑DD ISO 8601 format.A Swift Recoverable Precondition That Can Throw2021-12-17T00:00:00+00:002021-12-17T00:00:00+00:00https://bootstragram.com/blog/swift-precondition-that-throws<p>What on Earth is a <em>recoverable</em> precondition? <a href="https://www.swiftbysundell.com/articles/picking-the-right-way-of-failing-in-swift/" title="Picking the right way of failing in Swift, by John Sundell">Isn’t the point of a
precondition to be non-recoverable?</a></p>
<p>Yes. Sorry I couldn’t think of a better name<sup class="footnote-ref"><a href="#fn1" id="fnref1">1</a></sup>.</p>
<h2>The Result</h2>
<div class="language-swift highlighter-rouge"><div class="highlight"><pre class="highlight"><code data-lang="swift"><span class="c1">/// Checks a necessary condition for making forward progress.</span>
<span class="c1">///</span>
<span class="c1">/// The difference with `precondition` is that if an error is _thrown_ when the condition is executed, the error will</span>
<span class="c1">/// be _rethrown_ so that it can be recovered. But this recoverability does not apply if the condition executes</span>
<span class="c1">/// properly and its condition fails.</span>
<span class="c1">///</span>
<span class="c1">/// In other words, this function is a wrapper around `precondition` so that it can be fed a condition closure that can</span>
<span class="c1">/// throw.</span>
<span class="kd">func</span> <span class="nf">recoverablePrecondition</span><span class="p">(</span>
<span class="n">_</span> <span class="nv">condition</span><span class="p">:</span> <span class="kd">@autoclosure</span> <span class="p">()</span> <span class="k">throws</span> <span class="o">-></span> <span class="kt">Bool</span><span class="p">,</span>
<span class="n">_</span> <span class="nv">message</span><span class="p">:</span> <span class="kd">@autoclosure</span> <span class="p">()</span> <span class="o">-></span> <span class="kt">String</span> <span class="o">=</span> <span class="kt">String</span><span class="p">(),</span>
<span class="nv">file</span><span class="p">:</span> <span class="kt">StaticString</span> <span class="o">=</span> <span class="k">#file</span><span class="p">,</span>
<span class="nv">line</span><span class="p">:</span> <span class="kt">UInt</span> <span class="o">=</span> <span class="k">#line</span>
<span class="p">)</span> <span class="k">rethrows</span> <span class="p">{</span>
<span class="k">if</span> <span class="o">!</span><span class="p">(</span><span class="k">try</span> <span class="nf">condition</span><span class="p">())</span> <span class="p">{</span>
<span class="nf">preconditionFailure</span><span class="p">(</span><span class="nf">message</span><span class="p">(),</span> <span class="nv">file</span><span class="p">:</span> <span class="n">file</span><span class="p">,</span> <span class="nv">line</span><span class="p">:</span> <span class="n">line</span><span class="p">)</span>
<span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>
<h2>What is this?</h2>
<p>Let me explain. Here is the code I had:</p>
<div class="language-swift highlighter-rouge"><div class="highlight"><pre class="highlight"><code data-lang="swift"><span class="nf">precondition</span><span class="p">(</span><span class="nf">aLittleTenderness</span><span class="p">())</span>
</code></pre></div></div>
<p>But then, I had to make <code>aLittleTenderness</code> able to throw, and <code>precondition</code>
was yelling at me about it:</p>
<div class="language-markdown highlighter-rouge"><div class="highlight"><pre class="highlight"><code data-lang="markdown">Property access can throw, but it is not marked with 'try' and it is executed in
a non-throwing autoclosure
</code></pre></div></div>
<p>Even when I <em>tried</em> a little tenderness<sup class="footnote-ref"><a href="#fn2" id="fnref2">2</a></sup>:</p>
<div class="language-swift highlighter-rouge"><div class="highlight"><pre class="highlight"><code data-lang="swift"><span class="nf">precondition</span><span class="p">(</span><span class="k">try</span> <span class="nf">aLittleTenderness</span><span class="p">())</span>
</code></pre></div></div>
<p>The compiler would complain:</p>
<div class="language-markdown highlighter-rouge"><div class="highlight"><pre class="highlight"><code data-lang="markdown">Property access can throw, but it is executed in a non-throwing autoclosure
</code></pre></div></div>
<p>And this alternative looked terrible:</p>
<div class="language-swift highlighter-rouge"><div class="highlight"><pre class="highlight"><code data-lang="swift"><span class="nf">precondition</span><span class="p">((</span><span class="k">try</span><span class="p">?</span> <span class="nf">aLittleTenderness</span><span class="p">())</span> <span class="p">??</span> <span class="kc">false</span><span class="p">)</span>
</code></pre></div></div>
<p>So I created a <em>recoverable precondition</em>, which means:</p>
<ul>
<li>if the code throws, then try to recover from it;</li>
<li>if the code does not throw, treat it as a precondition.</li>
</ul>
<p>Don’t hate me. <a href="https://youtu.be/IQ9n2_5mbig" title="Otis Redding - Try A Little Tenderness - Live 1967 (Reelin' In The Years Archives)">Listen to soul music instead</a>.</p>
<h2>Lessons Learned</h2>
<p>Look at this badass signature! Of course I started from the signature of
<a href="https://developer.apple.com/documentation/swift/1540960-precondition" title="precondition(_:_:file:line:) Documentation"><code>precondition</code></a> and I adapted it to my needs. It features some keywords I
don’t use everyday. Each keyword deserves its own extensive explanation. But
wait, other people have already written about them:</p>
<ul>
<li><a href="https://www.swiftbysundell.com/articles/using-autoclosure-when-designing-swift-apis/" title="Using @autoclosure when designing Swift APIs, by John Sundell"><code>@autoclosure</code></a>;</li>
<li><a href="https://www.hackingwithswift.com/example-code/language/how-to-use-the-rethrows-keyword" title="How to use the rethrows keyword, by Paul Hudson"><code>rethrows</code></a>;</li>
<li><a href="https://docs.swift.org/swift-book/ReferenceManual/Expressions.html#ID390" title="Literal Expression section of the Expressions page of the Swift Reference Manual"><code>#file</code> and <code>#line</code></a>.</li>
</ul>
<section class="footnotes">
<ol>
<li id="fn1">
<p>If you can think of one, let me know. <a href="#fnref1" class="footnote-backref">↩</a></p>
</li>
<li id="fn2">
<p>Beware, a terrible music joke is hiding in this statement, can you spot it? <a href="#fnref2" class="footnote-backref">↩</a></p>
</li>
</ol>
</section>Mick FThe what and the why of a Swift precondition helper that accepts statements that can throw recoverable errors.PKCE in Swift: Generating Cryptographically Secure Code Verifiers and Code Challenges2021-05-05T00:00:00+00:002021-05-05T00:00:00+00:00https://bootstragram.com/blog/oauth-pkce-swift-secure-code-verifiers-and-code-challenges<p>I am making an app that uses the Spotify API. The typical first step to
successfully fetch API endpoints is to complete the authorization flow. The
Spotify API uses the <em>Proof Key for Code Exchange</em> (<em>PKCE</em> which is pronounced
“pixy”) extension of OAuth 2.0 to do so. This post presents the code I wrote to
generate the code verifier and the code challenge required to receive an access
token with PKCE.</p>
<h2>Creating a code verifier</h2>
<p>Reading <a href="https://tools.ietf.org/html/rfc7636">the RFC for PKCE</a>, the first step is to create a <em>code verifier</em>, ie
a random string that must meet the following requirements:</p>
<ul>
<li>contains characters in the set: [A-Z] / [a-z] / [0-9] / “-” / “.” / “_” /
“~”;</li>
<li>minimum length of 43 characters and a maximum length of 128 characters;</li>
<li>has enough <em>entropy</em>.</li>
</ul>
<p>Entropy is a term used in thermodynamics to quantify states of disorder,
randomness and uncertainty. The higher the entropy, the more unpredictable the
state becomes. Applied to PKCE, the higher the entropy, the harder it would be
for a potential attacker to learn or guess how code verifiers are created.</p>
<p>With Swift, the <a href="https://developer.apple.com/documentation/security/1399291-secrandomcopybytes"><code>SecRandomCopyBytes</code></a> function in the <a href="https://developer.apple.com/documentation/security">Security
framework</a> will help us comply with this entropy requirement.</p>
<p>We first create an array of 32 zeroed octets<sup class="footnote-ref"><a href="#fn1" id="fnref1">1</a></sup>, that we will feed into
<code>SecRandomCopyBytes</code>:</p>
<div class="language-swift highlighter-rouge"><div class="highlight"><pre class="highlight"><code data-lang="swift"><span class="kd">func</span> <span class="nf">generateCryptographicallySecureRandomOctets</span><span class="p">(</span><span class="nv">count</span><span class="p">:</span> <span class="kt">Int</span><span class="p">)</span> <span class="k">throws</span> <span class="o">-></span> <span class="p">[</span><span class="kt">UInt8</span><span class="p">]</span> <span class="p">{</span>
<span class="k">var</span> <span class="nv">octets</span> <span class="o">=</span> <span class="p">[</span><span class="kt">UInt8</span><span class="p">](</span><span class="nv">repeating</span><span class="p">:</span> <span class="mi">0</span><span class="p">,</span> <span class="nv">count</span><span class="p">:</span> <span class="n">count</span><span class="p">)</span>
<span class="k">let</span> <span class="nv">status</span> <span class="o">=</span> <span class="kt">SecRandomCopyBytes</span><span class="p">(</span><span class="n">kSecRandomDefault</span><span class="p">,</span> <span class="n">octets</span><span class="o">.</span><span class="n">count</span><span class="p">,</span> <span class="o">&</span><span class="n">octets</span><span class="p">)</span>
<span class="k">if</span> <span class="n">status</span> <span class="o">==</span> <span class="n">errSecSuccess</span> <span class="p">{</span> <span class="c1">// Always test the status.</span>
<span class="k">return</span> <span class="n">octets</span>
<span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
<span class="k">throw</span> <span class="kt">PKCEError</span><span class="o">.</span><span class="n">failedToGenerateRandomOctets</span>
<span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>
<p>Calling this function multiple times will return different results that should
be unpredictable.</p>
<p>Next, we need to transform these octets into a Base64-URL encoded string.
Beware, this is different than a Base64 encoded string. Different but close
enough to base an implementation on it, as recommended by the RFC’s Appendix A:</p>
<div class="language-swift highlighter-rouge"><div class="highlight"><pre class="highlight"><code data-lang="swift"><span class="kd">func</span> <span class="nf">base64URLEncode</span><span class="p">(</span><span class="nv">octets</span><span class="p">:</span> <span class="p">[</span><span class="kt">UInt8</span><span class="p">])</span> <span class="o">-></span> <span class="kt">String</span> <span class="p">{</span>
<span class="k">let</span> <span class="nv">data</span> <span class="o">=</span> <span class="kt">Data</span><span class="p">(</span><span class="nv">bytes</span><span class="p">:</span> <span class="n">octets</span><span class="p">,</span> <span class="nv">count</span><span class="p">:</span> <span class="n">octets</span><span class="o">.</span><span class="n">count</span><span class="p">)</span>
<span class="k">return</span> <span class="n">data</span>
<span class="o">.</span><span class="nf">base64EncodedString</span><span class="p">()</span> <span class="c1">// Regular base64 encoder</span>
<span class="o">.</span><span class="nf">replacingOccurrences</span><span class="p">(</span><span class="nv">of</span><span class="p">:</span> <span class="s">"="</span><span class="p">,</span> <span class="nv">with</span><span class="p">:</span> <span class="s">""</span><span class="p">)</span> <span class="c1">// Remove any trailing '='s</span>
<span class="o">.</span><span class="nf">replacingOccurrences</span><span class="p">(</span><span class="nv">of</span><span class="p">:</span> <span class="s">"+"</span><span class="p">,</span> <span class="nv">with</span><span class="p">:</span> <span class="s">"-"</span><span class="p">)</span> <span class="c1">// 62nd char of encoding</span>
<span class="o">.</span><span class="nf">replacingOccurrences</span><span class="p">(</span><span class="nv">of</span><span class="p">:</span> <span class="s">"/"</span><span class="p">,</span> <span class="nv">with</span><span class="p">:</span> <span class="s">"_"</span><span class="p">)</span> <span class="c1">// 63rd char of encoding</span>
<span class="o">.</span><span class="nf">trimmingCharacters</span><span class="p">(</span><span class="nv">in</span><span class="p">:</span> <span class="o">.</span><span class="n">whitespaces</span><span class="p">)</span>
<span class="p">}</span>
</code></pre></div></div>
<p>We have a code verifier:</p>
<div class="language-swift highlighter-rouge"><div class="highlight"><pre class="highlight"><code data-lang="swift"><span class="c1">// This is using a pipe-forward operator to compose functions with ease.</span>
<span class="c1">// Check out the Playground code for the missing code,</span>
<span class="c1">// Or https://www.pointfree.co/episodes/ep1-functions for more details.</span>
<span class="k">let</span> <span class="nv">codeVerifier</span> <span class="o">=</span> <span class="k">try</span> <span class="mi">32</span>
<span class="o">|></span> <span class="n">generateCryptographicallySecureRandomOctets</span>
<span class="o">|></span> <span class="n">base64URLEncode</span>
</code></pre></div></div>
<p>Wait: why did we use 32 octets to generate a string of 43 characters? Since our
resulting string uses an alphabet of 64 letters<sup class="footnote-ref"><a href="#fn2" id="fnref2">2</a></sup>, ie <code>2^6</code>, each character
will code 6 bits. Since 32 octets are 256 bits, it requires 43<sup class="footnote-ref"><a href="#fn3" id="fnref3">3</a></sup> characters to
be represented.</p>
<h2>Creating the code challenge</h2>
<p>Creating the challenge is a matter of transforming the verifier with a series of
operations.</p>
<div class="language-swift highlighter-rouge"><div class="highlight"><pre class="highlight"><code data-lang="swift"><span class="kd">func</span> <span class="nf">challenge</span><span class="p">(</span><span class="k">for</span> <span class="nv">verifier</span><span class="p">:</span> <span class="kt">String</span><span class="p">)</span> <span class="k">throws</span> <span class="o">-></span> <span class="kt">String</span> <span class="p">{</span>
<span class="k">let</span> <span class="nv">challenge</span> <span class="o">=</span> <span class="n">verifier</span>
<span class="o">.</span><span class="nf">data</span><span class="p">(</span><span class="nv">using</span><span class="p">:</span> <span class="o">.</span><span class="n">ascii</span><span class="p">)</span> <span class="c1">// (a)</span>
<span class="o">.</span><span class="n">map</span> <span class="p">{</span> <span class="kt">SHA256</span><span class="o">.</span><span class="nf">hash</span><span class="p">(</span><span class="nv">data</span><span class="p">:</span> <span class="nv">$0</span><span class="p">)</span> <span class="p">}</span> <span class="c1">// (b)</span>
<span class="o">.</span><span class="n">map</span> <span class="p">{</span> <span class="nf">base64URLEncode</span><span class="p">(</span><span class="nv">octets</span><span class="p">:</span> <span class="nv">$0</span><span class="p">)</span> <span class="p">}</span> <span class="c1">// (c)</span>
<span class="k">if</span> <span class="k">let</span> <span class="nv">challenge</span> <span class="o">=</span> <span class="n">challenge</span> <span class="p">{</span>
<span class="k">return</span> <span class="n">challenge</span>
<span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
<span class="k">throw</span> <span class="kt">PKCEError</span><span class="o">.</span><span class="n">failedToCreateChallengeForVerifier</span>
<span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>
<p>The operations are as follow:</p>
<ul>
<li>(a) convert the verifier string back into a collection of octets;</li>
<li>(b) create a SHA-256 hash of that data with <code>SHA256</code>, that is available either
from <a href="https://developer.apple.com/documentation/cryptokit/sha256">Apple CryptoKit</a> on supported platforms or <a href="https://apple.github.io/swift-crypto/docs/current/Crypto/Structs/SHA256.html">Swift Crypto</a> for
others.</li>
<li>(c) transform into a Base64-URL encoded string.</li>
</ul>
<p>As it is, the code won’t compile:</p>
<div class="highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Cannot convert value of type 'SHA256.Digest' (aka 'SHA256Digest') to expected argument type '[UInt8]'
</code></pre></div></div>
<p>This is a good opportunity to transform the signature of our <code>base64URLEncode</code>
function so that it can accept both a <code>[UInt8]</code> or a <code>SHA256.Digest</code><sup class="footnote-ref"><a href="#fn4" id="fnref4">4</a></sup> as an
input:</p>
<div class="language-swift highlighter-rouge"><div class="highlight"><pre class="highlight"><code data-lang="swift"><span class="kd">func</span> <span class="n">base64URLEncode</span><span class="o"><</span><span class="kt">S</span><span class="o">></span><span class="p">(</span><span class="nv">octets</span><span class="p">:</span> <span class="kt">S</span><span class="p">)</span> <span class="o">-></span> <span class="kt">String</span> <span class="k">where</span> <span class="kt">S</span> <span class="p">:</span> <span class="kt">Sequence</span><span class="p">,</span> <span class="kt">UInt8</span> <span class="o">==</span> <span class="kt">S</span><span class="o">.</span><span class="kt">Element</span> <span class="p">{</span>
<span class="k">let</span> <span class="nv">data</span> <span class="o">=</span> <span class="kt">Data</span><span class="p">(</span><span class="n">octets</span><span class="p">)</span>
<span class="k">return</span> <span class="n">data</span>
<span class="o">.</span><span class="nf">base64EncodedString</span><span class="p">()</span> <span class="c1">// Regular base64 encoder</span>
<span class="o">.</span><span class="nf">replacingOccurrences</span><span class="p">(</span><span class="nv">of</span><span class="p">:</span> <span class="s">"="</span><span class="p">,</span> <span class="nv">with</span><span class="p">:</span> <span class="s">""</span><span class="p">)</span> <span class="c1">// Remove any trailing '='s</span>
<span class="o">.</span><span class="nf">replacingOccurrences</span><span class="p">(</span><span class="nv">of</span><span class="p">:</span> <span class="s">"+"</span><span class="p">,</span> <span class="nv">with</span><span class="p">:</span> <span class="s">"-"</span><span class="p">)</span> <span class="c1">// 62nd char of encoding</span>
<span class="o">.</span><span class="nf">replacingOccurrences</span><span class="p">(</span><span class="nv">of</span><span class="p">:</span> <span class="s">"/"</span><span class="p">,</span> <span class="nv">with</span><span class="p">:</span> <span class="s">"_"</span><span class="p">)</span> <span class="c1">// 63rd char of encoding</span>
<span class="o">.</span><span class="nf">trimmingCharacters</span><span class="p">(</span><span class="nv">in</span><span class="p">:</span> <span class="o">.</span><span class="n">whitespaces</span><span class="p">)</span>
<span class="p">}</span>
</code></pre></div></div>
<h2>Testing our code</h2>
<p>The RFC provides testing samples so let’s use this provided data set to validate
this code:</p>
<div class="language-swift highlighter-rouge"><div class="highlight"><pre class="highlight"><code data-lang="swift"><span class="nf">assertEqual</span><span class="p">(</span><span class="nf">base64URLEncode</span><span class="p">(</span><span class="nv">octets</span><span class="p">:</span> <span class="p">[</span><span class="mi">3</span><span class="p">,</span> <span class="mi">236</span><span class="p">,</span> <span class="mi">255</span><span class="p">,</span> <span class="mi">224</span><span class="p">,</span> <span class="mi">193</span><span class="p">]),</span> <span class="s">"A-z_4ME"</span><span class="p">)</span>
<span class="k">let</span> <span class="nv">verifier</span> <span class="o">=</span> <span class="nf">base64URLEncode</span><span class="p">(</span><span class="nv">octets</span><span class="p">:</span> <span class="p">[</span>
<span class="mi">116</span><span class="p">,</span> <span class="mi">24</span><span class="p">,</span> <span class="mi">223</span><span class="p">,</span> <span class="mi">180</span><span class="p">,</span> <span class="mi">151</span><span class="p">,</span> <span class="mi">153</span><span class="p">,</span> <span class="mi">224</span><span class="p">,</span> <span class="mi">37</span><span class="p">,</span> <span class="mi">79</span><span class="p">,</span> <span class="mi">250</span><span class="p">,</span> <span class="mi">96</span><span class="p">,</span> <span class="mi">125</span><span class="p">,</span> <span class="mi">216</span><span class="p">,</span> <span class="mi">173</span><span class="p">,</span>
<span class="mi">187</span><span class="p">,</span> <span class="mi">186</span><span class="p">,</span> <span class="mi">22</span><span class="p">,</span> <span class="mi">212</span><span class="p">,</span> <span class="mi">37</span><span class="p">,</span> <span class="mi">77</span><span class="p">,</span> <span class="mi">105</span><span class="p">,</span> <span class="mi">214</span><span class="p">,</span> <span class="mi">191</span><span class="p">,</span> <span class="mi">240</span><span class="p">,</span> <span class="mi">91</span><span class="p">,</span> <span class="mi">88</span><span class="p">,</span> <span class="mi">5</span><span class="p">,</span> <span class="mi">88</span><span class="p">,</span> <span class="mi">83</span><span class="p">,</span>
<span class="mi">132</span><span class="p">,</span> <span class="mi">141</span><span class="p">,</span> <span class="mi">121</span>
<span class="p">])</span>
<span class="nf">assertEqual</span><span class="p">(</span><span class="n">verifier</span><span class="p">,</span> <span class="s">"dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk"</span><span class="p">)</span>
<span class="nf">assertEqual</span><span class="p">(</span><span class="k">try!</span> <span class="nf">challenge</span><span class="p">(</span><span class="nv">for</span><span class="p">:</span> <span class="n">verifier</span><span class="p">),</span> <span class="s">"E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw-cM"</span><span class="p">)</span>
</code></pre></div></div>
<p>And let’s validate that we can create verifiers of lengths that can cover the
whole range:</p>
<div class="language-swift highlighter-rouge"><div class="highlight"><pre class="highlight"><code data-lang="swift"><span class="k">let</span> <span class="nv">codeVerifier43</span> <span class="o">=</span> <span class="k">try</span> <span class="mi">32</span>
<span class="o">|></span> <span class="n">generateCryptographicallySecureRandomOctets</span>
<span class="o">|></span> <span class="n">base64URLEncode</span>
<span class="nf">assertEqual</span><span class="p">(</span><span class="n">codeVerifier43</span><span class="o">.</span><span class="n">count</span><span class="p">,</span> <span class="mi">43</span><span class="p">)</span>
<span class="k">let</span> <span class="nv">codeVerifier128</span> <span class="o">=</span> <span class="k">try</span> <span class="mi">96</span>
<span class="o">|></span> <span class="n">generateCryptographicallySecureRandomOctets</span>
<span class="o">|></span> <span class="n">base64URLEncode</span>
<span class="nf">assertEqual</span><span class="p">(</span><span class="n">codeVerifier128</span><span class="o">.</span><span class="n">count</span><span class="p">,</span> <span class="mi">128</span><span class="p">)</span>
</code></pre></div></div>
<p>🎉 A lot of green in the output!</p>
<div class="highlighter-rouge"><div class="highlight"><pre class="highlight"><code>✅ A-z_4ME == A-z_4ME
✅ dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk == dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk
✅ E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw-cM == E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw-cM
✅ 43 == 43
✅ 128 == 128
</code></pre></div></div>
<p>Check out <a href="https://github.com/dirtyhenry/xcode-playgrounds/tree/main/PKCE.playground">⛹️ the playground</a> to run the code in Xcode. More to come on this
Spotify API exploration.</p>
<section class="footnotes">
<ol>
<li id="fn1">
<p>Being French, I prefer using the word <code>octets</code> — the same as in French —
than <code>bytes</code>. <a href="#fnref1" class="footnote-backref">↩</a></p>
</li>
<li id="fn2">
<p>Even though the RFC first mentions a set of 66 characters, the <a href="https://tools.ietf.org/html/rfc4648#section-5">Base64-URL
RFC</a> excluded <code>.</code> and <code>~</code> for their special meaning on some file systems. <a href="#fnref2" class="footnote-backref">↩</a></p>
</li>
<li id="fn3">
<p>32 × 8 / 6 = 42.7 ⇒ 43 bits are required. <a href="#fnref3" class="footnote-backref">↩</a></p>
</li>
<li id="fn4">
<p>When digging in the documentation, you will find that <code>SHA256.Digest</code>
conforms to <code>Digest</code> which conforms itself to a <code>Sequence</code> with an element
of <code>UInt8</code>. <a href="#fnref4" class="footnote-backref">↩</a></p>
</li>
</ol>
</section>Mick FI started a side-project using the Spotify API. The first part of the journey was about getting access tokens to reach the API. Here are lessons learned from implementing OAuth 2.0 with the PKCE extension.Notes From Swift Heroes Digital 20212021-04-16T00:00:00+00:002021-04-16T00:00:00+00:00https://bootstragram.com/blog/swift-heroes-digital-2021<p><a href="https://swiftheroes.com/2021/">Swift Heroes</a> went digital because of the Covid pandemic restricting public
gatherings. I am not the best at socializing during conferences and watching
online talks from my comfy chair is a format that I somewhat shamefully enjoyed.
Here are my notes from the talks I have watched.</p>
<p>Talks with 🌟 are the talks I recommend the most.</p>
<h2>Let’s Make That Label With Core Text - <a href="https://twitter.com/krzyzanowskim">Marcin Krzyzanowski</a> 🌟</h2>
<p>Marcin is working on his <a href="https://swiftstudio.app/">Swift Studio</a> project, a Swift IDE for which he had
to dig down deep into the Core Text framework. I enjoyed this talk following <a href="/blog/line-height-with-uikit/">my
own errands on text layout customization</a> and I intend to use the content of
his talk to address another use case: how to balance the lengths of broken lines
of text, to avoid a situation where the last line can be much shorter than its
siblings.</p>
<h2>Future of Swift: A Sneak Peek at <code>async/await</code> - <a href="https://twitter.com/v_pradeilles">Vincent Pradeilles</a> 🌟</h2>
<p>Vincent presented the <a href="https://github.com/apple/swift-evolution/blob/main/proposals/0296-async-await.md"><code>async/await</code> proposal</a> that will be part of Swift
5.5. On the way, he explained how to install the development snapshots of Swift
in Xcode, insisted that <code>async/await</code> would not be competing with Combine and
mentioned exciting <a href="https://forums.swift.org/t/swift-concurrency-roadmap/41611">upcoming features for Swift concurrency</a>.</p>
<h2>Using Core Data in a Modern SwiftUI Application - <a href="https://twitter.com/donnywals">Donny Wals</a> 🌟</h2>
<p>Until 2017, I was using Core Data on a daily basis. But since, I have not used
it at all. I had the feeling it was becoming a dying technology but this talk
convinced me it was still very relevant since Donny makes the case it integrates
really well with SwiftUI since <code>NSManagedObject</code> conforms to <code>ObservableObject</code>.
Donny wrote a book about it, <a href="https://gumroad.com/l/practical-core-data"><em>Practical Core Data</em></a> that digs further on
the topic.</p>
<h2>Evolving Existing Projects With SwiftUI - <a href="https://steipete.me/">Peter Steinberger</a></h2>
<p>This felt like a natural follow-up to Peter’s talk at <a href="/blog/frenchkit-2019/">FrenchKit 2019</a> about
<a href="https://youtu.be/Xo3zGlyxXcI">shipping a Catalyst app</a>. He offered feedback on integrating small doses of
SwiftUI in a large codebase with a long history that is a mix of Objective-C and
Swift.</p>
<p>Here is what I’ll take away from the talk:</p>
<ul>
<li>SwiftUI is unstable and change dramatically at each version;</li>
<li>Previews can time out being <em>de facto</em> unreliable;</li>
<li><code>GeometryReader</code> should be used as high in the hierarchy as possible;</li>
<li>There are straightforward workarounds to SwiftUI limitations — via custom view
modifiers for instance — but they should be considered as hacks with the risks
it implies in terms of maintenance;</li>
<li>Mentioned projects were:
<ul>
<li><a href="https://github.com/siteline/SwiftUI-Introspect">SwiftUI-Introspect</a>: helps to introspect underlying UIKit components
from SwiftUI;</li>
<li><a href="https://swiftuix.com/">SwiftUIX</a>: a project that attempts to fill the gaps that SwiftUI suffers
from when compared to UIKit or AppKit.</li>
</ul>
</li>
</ul>
<h2>Combining SwiftUI and UIKit: AppClips and Widgets - <a href="https://twitter.com/anioutkajarkova">Anna Zharkova</a></h2>
<p>Widgets and AppClips are additions to iOS that come with significant
restrictions compared to what you can achieve in an app. Anna presented
workarounds against the lack of dynamism that she met while developing widgets
and app clips using bike ride maps as an example.</p>
<p>My main takeaway is that, since neither the keychain or local authentication can
be used in these app-derivatives, then if any of the data you would like to
present require authentication, you are out of luck.</p>
<h2>Creating Machine Learning Models With Create ML - Moritz Philip Recke, Tiago Gomes Pereira & Giovanni Monaco</h2>
<p>This talk was presenting how to create machine learning models with convenience
using <a href="https://cloud.annotations.ai/">IBM’s Cloud Annotations tool</a> to create training and testing data
sets, and feeding these into the macOS <a href="https://developer.apple.com/machine-learning/create-ml/">Create ML</a> app. It did sound
accessible and I intend to give it a try one day.</p>
<h2>Mocka: A Mock Server for Developers by Developers - Fabrizio Brancati & Firas Safa</h2>
<p><a href="https://github.com/wise-emotions/mocka">Mocka</a> is an open-source server to mock backend API responses to test
backend edge cases with convenience. The project is promising and I will keep an
eye on it but it is still a young product with many missing features so I am not
sure I will start using it today.</p>
<h2>How to Think Like a SwiftUI View Modifier - <a href="https://twitter.com/joshdholtz">Josh Holtz</a></h2>
<p>Josh presented some view modifiers he programmed and the struggles and pitfalls
he met on the way. A good time saver for people starting learning SwiftUI.</p>
<h2>Full Stack Swift Development - <a href="https://twitter.com/kilo_loco">Kilo Loco</a></h2>
<p>Kilo is a developer advocate for AWS and gave a fair overview of technologies to
use Swift on the backend, even though he obviously highlighted AWS Amplify. It
felt like investing on AWS technologies could result in a lock-in so I was not
convinced I would invest in this direction for hobby projects.</p>Mick FAmong the talks I recommend the most: digging down into Core Text, the future of asynchronous development in Swift with `async/await` and how Core Data is still relevant when using SwiftUI.How I Decode Dates from JSON APIs in Swift2021-03-30T00:00:00+00:002021-03-30T00:00:00+00:00https://bootstragram.com/blog/json-dates-swift<p>This blog post will present why I’m frustrated with the current state of
<code>JSONDecoder</code> and <code>JSONEncoder</code><sup class="footnote-ref"><a href="#fn1" id="fnref1">1</a></sup>, how I think dates should be formatted in a
JSON API, and the code I write to handle JSON date decoding and encoding in my
applications.</p>
<h2>The Problem with <code>Codable</code>’s JSON Support</h2>
<p>Swift has 1st-party support for JavaScript script execution. Let’s create a
simple JSON payload including a date field in Swift:</p>
<div class="language-swift highlighter-rouge"><div class="highlight"><pre class="highlight"><code data-lang="swift"><span class="k">let</span> <span class="nv">javaScriptContext</span> <span class="o">=</span> <span class="kt">JSContext</span><span class="p">()</span><span class="o">!</span>
<span class="k">let</span> <span class="nv">jsonPayload</span> <span class="o">=</span> <span class="n">javaScriptContext</span><span class="o">.</span><span class="nf">evaluateScript</span><span class="p">(</span><span class="s">"""
const payload = { message: '👋', creationDate: new Date() };
JSON.stringify(payload)
"""</span><span class="p">)</span>
<span class="c1">// => {"message":"👋","creationDate":"2021-03-28T13:10:35.656Z"}</span>
</code></pre></div></div>
<p>Now let’s decode this JSON string with Swift:</p>
<div class="language-swift highlighter-rouge"><div class="highlight"><pre class="highlight"><code data-lang="swift"><span class="kd">struct</span> <span class="kt">PayloadStruct</span><span class="p">:</span> <span class="kt">Codable</span> <span class="p">{</span>
<span class="k">let</span> <span class="nv">message</span><span class="p">:</span> <span class="kt">String</span>
<span class="k">let</span> <span class="nv">creationDate</span><span class="p">:</span> <span class="kt">Date</span>
<span class="p">}</span>
<span class="k">do</span> <span class="p">{</span>
<span class="n">_</span> <span class="o">=</span> <span class="k">try</span> <span class="n">jsonPayload</span><span class="p">?</span><span class="o">.</span><span class="nf">toString</span><span class="p">()?</span><span class="o">.</span><span class="nf">data</span><span class="p">(</span><span class="nv">using</span><span class="p">:</span> <span class="o">.</span><span class="n">utf8</span><span class="p">)</span><span class="o">.</span><span class="n">map</span> <span class="p">{</span>
<span class="k">try</span> <span class="kt">JSONDecoder</span><span class="p">()</span><span class="o">.</span><span class="nf">decode</span><span class="p">(</span><span class="kt">PayloadStruct</span><span class="o">.</span><span class="k">self</span><span class="p">,</span> <span class="nv">from</span><span class="p">:</span> <span class="nv">$0</span><span class="p">)</span>
<span class="p">}</span>
<span class="p">}</span> <span class="k">catch</span> <span class="k">let</span> <span class="nv">DecodingError</span><span class="o">.</span><span class="nf">typeMismatch</span><span class="p">(</span><span class="n">type</span><span class="p">,</span> <span class="n">context</span><span class="p">)</span> <span class="p">{</span>
<span class="nf">dump</span><span class="p">(</span><span class="n">type</span><span class="p">)</span>
<span class="nf">dump</span><span class="p">(</span><span class="n">context</span><span class="p">)</span> <span class="c1">// creationDate: Expected to decode Double but found a string/data instead.</span>
<span class="p">}</span>
</code></pre></div></div>
<p>This code throws a <code>DecodingError.typeMismatch</code> error with the following
description: <code>Expected to decode Double but found a string/data instead.</code></p>
<p>This is because, by default, a <code>Date</code> type is expected to be a <code>Double</code>
specifying the number of seconds since 00:00:00 UTC on 1 January 2001. But the
date in our JSON string is formatted as ISO 8601.</p>
<p>Let’s use <code>JSONDecoder</code>’s built-in <code>.iso8601</code> configuration of
<code>DateDecodingStrategy</code>.</p>
<div class="language-swift highlighter-rouge"><div class="highlight"><pre class="highlight"><code data-lang="swift"><span class="k">do</span> <span class="p">{</span>
<span class="k">let</span> <span class="nv">jsonDecoder</span> <span class="o">=</span> <span class="kt">JSONDecoder</span><span class="p">()</span>
<span class="n">jsonDecoder</span><span class="o">.</span><span class="n">dateDecodingStrategy</span> <span class="o">=</span> <span class="o">.</span><span class="n">iso8601</span>
<span class="n">_</span> <span class="o">=</span> <span class="k">try</span> <span class="n">jsonPayload</span><span class="p">?</span><span class="o">.</span><span class="nf">toString</span><span class="p">()?</span><span class="o">.</span><span class="nf">data</span><span class="p">(</span><span class="nv">using</span><span class="p">:</span> <span class="o">.</span><span class="n">utf8</span><span class="p">)</span><span class="o">.</span><span class="n">map</span> <span class="p">{</span>
<span class="k">try</span> <span class="n">jsonDecoder</span><span class="o">.</span><span class="nf">decode</span><span class="p">(</span><span class="kt">PayloadStruct</span><span class="o">.</span><span class="k">self</span><span class="p">,</span> <span class="nv">from</span><span class="p">:</span> <span class="nv">$0</span><span class="p">)</span>
<span class="p">}</span>
<span class="p">}</span> <span class="k">catch</span> <span class="k">let</span> <span class="nv">DecodingError</span><span class="o">.</span><span class="nf">dataCorrupted</span><span class="p">(</span><span class="n">context</span><span class="p">)</span> <span class="p">{</span>
<span class="nf">dump</span><span class="p">(</span><span class="n">context</span><span class="p">)</span>
<span class="p">}</span>
</code></pre></div></div>
<p>Another error? This time, the code throws a <code>DecodingError.dataCorrupted</code> error
with the following description: <code>Expected date string to be ISO8601-formatted</code>.
What is going on? 🤔</p>
<h2>There is no date in JSON, but there is in JS</h2>
<p>True, <a href="https://www.json.org/">JSON</a> does not specify a format for dates. And if projects such as
<a href="https://jsonapi.org/">JSON API</a> do <a href="https://jsonapi.org/recommendations/#date-and-time-fields">recommend</a> using ISO 8601, because the <a href="https://www.w3.org/TR/NOTE-datetime">W3C</a> also thinks
that this format makes the most sense, they are not clear about what exact
flavor of ISO 8601 should be used.</p>
<p>But let’s remember that JSON stands for <em>JavaScript Object Notation</em> and that
<a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/toJSON">JavaScript does specify how to format its date type in JSON</a>.</p>
<p>If you want to refer to March 23rd 2012, at 6:25:43PM UTC timezone, at
millisecond 511, then, whatever your own timezone, you should use:</p>
<div class="highlighter-rouge"><div class="highlight"><pre class="highlight"><code>2012-04-23T18:25:43.511Z
</code></pre></div></div>
<p>Why?<sup class="footnote-ref"><a href="#fn2" id="fnref2">2</a></sup></p>
<ul>
<li>It is readable for humans;</li>
<li>It sorts correctly;</li>
<li>It includes fractional seconds, which can help re-establish chronology.</li>
</ul>
<p>In Swift, this format is achieved by using a <code>ISO8601DateFormatter</code> — beware,
not a <code>DateFormatter</code> — set up with the formatting options
<code>.withInternetDateTime</code> (which is the default), and <code>.withFractionalSeconds</code>.</p>
<h2>A Swift class and convenient extensions to manage dates in JSON API</h2>
<p>As of today, here are the options for the enum
<a href="https://developer.apple.com/documentation/foundation/jsondecoder/datedecodingstrategy"><code>JSONDecoder.DateDecodingStrategy</code></a> that <code>JSONDecoder</code> can use:</p>
<ol>
<li><code>deferredToDate</code>: the default using a <code>TimeInterval</code> (ie an alias to
<code>Double</code>) that is not human-readable;</li>
<li><code>iso8601</code>: this option is using ISO 8601 without fractional seconds, which is
against the usage and JavaScript’s spec;</li>
<li><code>formatted(DateFormatter)</code>: never use this since you might — for instance —
break for users with a 12-hour AM/PM time formatting, which is a lesson I
learned the hard way<sup class="footnote-ref"><a href="#fn3" id="fnref3">3</a></sup>;</li>
<li><code>custom((Decoder) -> Date)</code>: 🎉 this is the option we want and here is the
version I suggest 👇.</li>
</ol>
<div class="language-swift highlighter-rouge"><div class="highlight"><pre class="highlight"><code data-lang="swift"><span class="kd">class</span> <span class="kt">JavaScriptISO8601DateFormatter</span> <span class="p">{</span>
<span class="kd">static</span> <span class="k">let</span> <span class="nv">fractionalSecondsFormatter</span><span class="p">:</span> <span class="kt">ISO8601DateFormatter</span> <span class="o">=</span> <span class="p">{</span>
<span class="k">let</span> <span class="nv">res</span> <span class="o">=</span> <span class="kt">ISO8601DateFormatter</span><span class="p">()</span>
<span class="c1">// The default format options is .withInternetDateTime.</span>
<span class="c1">// We need to add .withFractionalSeconds to parse dates with milliseconds.</span>
<span class="n">res</span><span class="o">.</span><span class="n">formatOptions</span> <span class="o">=</span> <span class="p">[</span><span class="o">.</span><span class="n">withInternetDateTime</span><span class="p">,</span> <span class="o">.</span><span class="n">withFractionalSeconds</span><span class="p">]</span>
<span class="k">return</span> <span class="n">res</span>
<span class="p">}()</span>
<span class="kd">static</span> <span class="k">let</span> <span class="nv">defaultFormatter</span> <span class="o">=</span> <span class="kt">ISO8601DateFormatter</span><span class="p">()</span>
<span class="kd">static</span> <span class="kd">func</span> <span class="nf">decodedDate</span><span class="p">(</span><span class="n">_</span> <span class="nv">decoder</span><span class="p">:</span> <span class="kt">Decoder</span><span class="p">)</span> <span class="k">throws</span> <span class="o">-></span> <span class="kt">Date</span> <span class="p">{</span>
<span class="k">let</span> <span class="nv">container</span> <span class="o">=</span> <span class="k">try</span> <span class="n">decoder</span><span class="o">.</span><span class="nf">singleValueContainer</span><span class="p">()</span>
<span class="k">let</span> <span class="nv">dateAsString</span> <span class="o">=</span> <span class="k">try</span> <span class="n">container</span><span class="o">.</span><span class="nf">decode</span><span class="p">(</span><span class="kt">String</span><span class="o">.</span><span class="k">self</span><span class="p">)</span>
<span class="c1">// See warning below.</span>
<span class="k">for</span> <span class="n">formatter</span> <span class="k">in</span> <span class="p">[</span><span class="n">fractionalSecondsFormatter</span><span class="p">,</span> <span class="n">defaultFormatter</span><span class="p">]</span> <span class="p">{</span>
<span class="k">if</span> <span class="k">let</span> <span class="nv">res</span> <span class="o">=</span> <span class="n">formatter</span><span class="o">.</span><span class="nf">date</span><span class="p">(</span><span class="nv">from</span><span class="p">:</span> <span class="n">dateAsString</span><span class="p">)</span> <span class="p">{</span>
<span class="k">return</span> <span class="n">res</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="k">throw</span> <span class="kt">DecodingError</span><span class="o">.</span><span class="nf">dataCorrupted</span><span class="p">(</span><span class="kt">DecodingError</span><span class="o">.</span><span class="kt">Context</span><span class="p">(</span>
<span class="nv">codingPath</span><span class="p">:</span> <span class="n">decoder</span><span class="o">.</span><span class="n">codingPath</span><span class="p">,</span>
<span class="nv">debugDescription</span><span class="p">:</span> <span class="s">"Expected date string to be JavaScript-ISO8601-formatted."</span>
<span class="p">))</span>
<span class="p">}</span>
<span class="kd">static</span> <span class="kd">func</span> <span class="nf">encodeDate</span><span class="p">(</span><span class="nv">date</span><span class="p">:</span> <span class="kt">Date</span><span class="p">,</span> <span class="nv">encoder</span><span class="p">:</span> <span class="kt">Encoder</span><span class="p">)</span> <span class="k">throws</span> <span class="p">{</span>
<span class="k">var</span> <span class="nv">container</span> <span class="o">=</span> <span class="n">encoder</span><span class="o">.</span><span class="nf">singleValueContainer</span><span class="p">()</span>
<span class="k">try</span> <span class="n">container</span><span class="o">.</span><span class="nf">encode</span><span class="p">(</span><span class="n">fractionalSecondsFormatter</span><span class="o">.</span><span class="nf">string</span><span class="p">(</span><span class="nv">from</span><span class="p">:</span> <span class="n">date</span><span class="p">))</span>
<span class="p">}</span>
<span class="kd">private</span> <span class="nf">init</span><span class="p">()</span> <span class="p">{}</span>
<span class="p">}</span>
<span class="kd">extension</span> <span class="kt">JSONDecoder</span><span class="o">.</span><span class="kt">DateDecodingStrategy</span> <span class="p">{</span>
<span class="kd">static</span> <span class="kd">func</span> <span class="nf">javaScriptISO8601</span><span class="p">()</span> <span class="o">-></span> <span class="kt">JSONDecoder</span><span class="o">.</span><span class="kt">DateDecodingStrategy</span> <span class="p">{</span>
<span class="o">.</span><span class="nf">custom</span><span class="p">(</span><span class="kt">JavaScriptISO8601DateFormatter</span><span class="o">.</span><span class="n">decodedDate</span><span class="p">)</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="kd">extension</span> <span class="kt">JSONDecoder</span> <span class="p">{</span>
<span class="kd">static</span> <span class="kd">func</span> <span class="nf">javaScriptISO8601</span><span class="p">()</span> <span class="o">-></span> <span class="kt">JSONDecoder</span> <span class="p">{</span>
<span class="k">let</span> <span class="nv">res</span> <span class="o">=</span> <span class="kt">JSONDecoder</span><span class="p">()</span>
<span class="n">res</span><span class="o">.</span><span class="n">dateDecodingStrategy</span> <span class="o">=</span> <span class="o">.</span><span class="nf">javaScriptISO8601</span><span class="p">()</span>
<span class="k">return</span> <span class="n">res</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="kd">extension</span> <span class="kt">JSONEncoder</span><span class="o">.</span><span class="kt">DateEncodingStrategy</span> <span class="p">{</span>
<span class="kd">static</span> <span class="kd">func</span> <span class="nf">javaScriptISO8601</span><span class="p">()</span> <span class="o">-></span> <span class="kt">JSONEncoder</span><span class="o">.</span><span class="kt">DateEncodingStrategy</span> <span class="p">{</span>
<span class="o">.</span><span class="nf">custom</span><span class="p">(</span><span class="kt">JavaScriptISO8601DateFormatter</span><span class="o">.</span><span class="n">encodeDate</span><span class="p">)</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="kd">extension</span> <span class="kt">JSONEncoder</span> <span class="p">{</span>
<span class="kd">static</span> <span class="kd">func</span> <span class="nf">javaScriptISO8601</span><span class="p">()</span> <span class="o">-></span> <span class="kt">JSONEncoder</span> <span class="p">{</span>
<span class="k">let</span> <span class="nv">res</span> <span class="o">=</span> <span class="kt">JSONEncoder</span><span class="p">()</span>
<span class="n">res</span><span class="o">.</span><span class="n">dateEncodingStrategy</span> <span class="o">=</span> <span class="o">.</span><span class="nf">javaScriptISO8601</span><span class="p">()</span>
<span class="k">return</span> <span class="n">res</span>
<span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>
<p>⚠️ In this code, I attempt decoding dates with 2 formatters: one that supports
fractional seconds and one that does not. The reason is that the JSON of the API
my app was consuming was coming from a Kotlin backend that used Java’s
<a href="https://docs.oracle.com/javase/8/docs/api/java/time/format/DateTimeFormatter.html"><code>DateTimeFormatter</code></a> that stipulates:</p>
<blockquote>
<p>If the nano-of-second is zero or not available then the format is complete.</p>
</blockquote>
<p>Statistically speaking, 99,9% of the API would be formatted with fractional
seconds — that’s why we attempt decoding dates with this formatter first —, and
0,1% without. 🤷</p>
<h2>Usage</h2>
<div class="language-swift highlighter-rouge"><div class="highlight"><pre class="highlight"><code data-lang="swift"><span class="k">let</span> <span class="nv">payload</span> <span class="o">=</span> <span class="k">try</span> <span class="n">jsonPayload</span><span class="p">?</span><span class="o">.</span><span class="nf">toString</span><span class="p">()?</span><span class="o">.</span><span class="nf">data</span><span class="p">(</span><span class="nv">using</span><span class="p">:</span> <span class="o">.</span><span class="n">utf8</span><span class="p">)</span><span class="o">.</span><span class="n">map</span> <span class="p">{</span>
<span class="k">try</span> <span class="kt">JSONDecoder</span><span class="o">.</span><span class="nf">javaScriptISO8601</span><span class="p">()</span><span class="o">.</span><span class="nf">decode</span><span class="p">(</span><span class="kt">PayloadStruct</span><span class="o">.</span><span class="k">self</span><span class="p">,</span> <span class="nv">from</span><span class="p">:</span> <span class="nv">$0</span><span class="p">)</span>
<span class="p">}</span>
<span class="k">let</span> <span class="nv">reencodedJSONPayload</span> <span class="o">=</span> <span class="p">(</span><span class="k">try</span><span class="p">?</span> <span class="kt">JSONEncoder</span><span class="o">.</span><span class="nf">javaScriptISO8601</span><span class="p">()</span><span class="o">.</span><span class="nf">encode</span><span class="p">(</span><span class="n">payload</span><span class="p">))</span><span class="o">.</span><span class="n">flatMap</span> <span class="p">{</span>
<span class="kt">String</span><span class="p">(</span><span class="nv">data</span><span class="p">:</span> <span class="nv">$0</span><span class="p">,</span> <span class="nv">encoding</span><span class="p">:</span> <span class="o">.</span><span class="n">utf8</span><span class="p">)</span>
<span class="p">}</span>
</code></pre></div></div>
<p>Check out <a href="https://github.com/dirtyhenry/xcode-playgrounds/tree/main/JSON-date.playground">⛹️ the playground</a> to run the code in Xcode.</p>
<section class="footnotes">
<ol>
<li id="fn1">
<p>The good news being that the community is <a href="https://forums.swift.org/t/serialization-in-swift/46641">currently collecting
feedbacks</a> to iterate over Swift’s serialization features. <a href="#fnref1" class="footnote-backref">↩</a></p>
</li>
<li id="fn2">
<p>Please refer to <a href="https://stackoverflow.com/questions/10286204/what-is-the-right-json-date-format">this StackOverflow thread</a> for a more elaborate
conversation about this issue. <a href="#fnref2" class="footnote-backref">↩</a></p>
</li>
<li id="fn3">
<p>And knowing that <a href="https://github.com/Ranchero-Software/NetNewsWire/commit/afbe25a26c291dc5d006dfda2eb4650bcaa9f9f7">I am not the only one</a> does make me feel better. <a href="#fnref3" class="footnote-backref">↩</a></p>
</li>
</ol>
</section>Mick FIn my experience, the default strategies to decode and encode dates of JSON API with Swift never matched my needs. Here is why and how I fix it.