<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>Forem: Katharina Gopp</title>
    <description>The latest articles on Forem by Katharina Gopp (@katharinagopp).</description>
    <link>https://forem.com/katharinagopp</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F692044%2Fd730c547-543d-49e4-948f-9bcfb5a011a5.jpg</url>
      <title>Forem: Katharina Gopp</title>
      <link>https://forem.com/katharinagopp</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/katharinagopp"/>
    <language>en</language>
    <item>
      <title>Timezone Shenanigans in Swift</title>
      <dc:creator>Katharina Gopp</dc:creator>
      <pubDate>Tue, 23 Aug 2022 10:11:00 +0000</pubDate>
      <link>https://forem.com/katharinagopp/timezone-shenanigans-in-swift-1654</link>
      <guid>https://forem.com/katharinagopp/timezone-shenanigans-in-swift-1654</guid>
      <description>&lt;h2&gt;
  
  
  And how they can drive you crazy
&lt;/h2&gt;

&lt;p&gt;There comes a time when it is inevitable to work with &lt;code&gt;Calendar&lt;/code&gt;. Sometimes it is easy, but be aware, it can be very tricky 😅&lt;/p&gt;

&lt;p&gt;So let's have some fun with it 👍&lt;/p&gt;

&lt;p&gt;Assume you have to compare two different &lt;code&gt;Date&lt;/code&gt; values.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;date1&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;timeIntervalSince1970&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;// 1970-01-01 00:00:00 &lt;/span&gt;
&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;date2&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;timeIntervalSince1970&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;60&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;60&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;24&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;// 1970-01-02 00:00:00 &lt;/span&gt;

&lt;span class="kd"&gt;func&lt;/span&gt; &lt;span class="nf"&gt;compareDates&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;date1&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;date2&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;){&lt;/span&gt;
    &lt;span class="k"&gt;switch&lt;/span&gt; &lt;span class="n"&gt;date1&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="nv"&gt;date2&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"date1 and date2 represent the same point in time"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="o"&gt;...&lt;/span&gt;&lt;span class="nv"&gt;date2&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"date1 is earlier in time than date2"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="n"&gt;date2&lt;/span&gt;&lt;span class="o"&gt;...&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"date1 is later in time than date2"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;default&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="nf"&gt;compareDates&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;date1&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;date1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;date2&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;date2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;As we would assume, we get as result &lt;code&gt;"date1 is earlier in time than date2"&lt;/code&gt;. Nice! 😃&lt;/p&gt;

&lt;p&gt;So what happens, if we use the same day but different &lt;code&gt;TimeZone&lt;/code&gt; values and want to compare the start of the day? (Only you can answer why you would need that, but just assume you do 😅)&lt;/p&gt;

&lt;p&gt;Let's just say, we want to know if Berlin or New York City celebrated New Year's Day in 1971 first.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="o"&gt;...&lt;/span&gt;
&lt;span class="kd"&gt;func&lt;/span&gt; &lt;span class="nf"&gt;startOfDayIn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;date&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;timeZone&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;TimeZone&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="kt"&gt;Date&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="nv"&gt;calendar&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;Calendar&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;current&lt;/span&gt;
    &lt;span class="n"&gt;calendar&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;timeZone&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;timeZone&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;calendar&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;startOfDay&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;for&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;date&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;date&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;timeIntervalSince1970&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;60&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;60&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;24&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;365&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;// 1971-01-01 00:00:00&lt;/span&gt;
&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;timeZone1&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;TimeZone&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;secondsFromGMT&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;60&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;60&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt; &lt;span class="c1"&gt;// Berlin&lt;/span&gt;
&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;start1&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;startOfDayIn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;date&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;date&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;timeZone&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;timeZone1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;timeZone2&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;TimeZone&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;secondsFromGMT&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;60&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;60&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt; &lt;span class="c1"&gt;// New York City&lt;/span&gt;
&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;start2&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;startOfDayIn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;date&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;date&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;timeZone&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;timeZone2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="nf"&gt;compareDates&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;date1&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;start1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;date2&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;start2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Well that was easy 😁 We get as a result..&lt;/p&gt;

&lt;p&gt;Wait, what?? &lt;code&gt;"date1 is later in time than date2"&lt;/code&gt; 😳🤯😱 How is that even possible? The day in Berlin starts earlier than the day in New York, it should have been &lt;code&gt;"date1 is earlier in time than date2"&lt;/code&gt; !!!&lt;/p&gt;

&lt;p&gt;So let's investigate this. 🧐&lt;/p&gt;

&lt;p&gt;First, let's print a few startTimes to compare them. Since we used January 1st, we can be sure that it's not a problem because the date is before year 1970.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;12&lt;/span&gt;&lt;span class="o"&gt;...&lt;/span&gt;&lt;span class="mi"&gt;12&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;reversed&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;forEach&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;deviation&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;timeZone&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;TimeZone&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;secondsFromGMT&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;60&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;60&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;deviation&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;start&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;startOfDayIn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;date&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;date&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;timeZone&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;timeZone&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;start&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"| UTC&lt;/span&gt;&lt;span class="se"&gt;\(&lt;/span&gt;&lt;span class="n"&gt;deviation&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="s"&gt;"+"&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;""&lt;/span&gt;&lt;span class="se"&gt;)\(&lt;/span&gt;&lt;span class="n"&gt;deviation&lt;/span&gt;&lt;span class="se"&gt;)&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This gives us the following list:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Date&lt;/th&gt;
&lt;th&gt;Timezone&lt;/th&gt;
&lt;th&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1970-12-31 12:00:00 +0000&lt;/td&gt;
&lt;td&gt;UTC+12&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;1970-12-31 13:00:00 +0000&lt;/td&gt;
&lt;td&gt;UTC+11&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;1970-12-31 14:00:00 +0000&lt;/td&gt;
&lt;td&gt;UTC+10&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;1970-12-31 15:00:00 +0000&lt;/td&gt;
&lt;td&gt;UTC+9&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;1970-12-31 16:00:00 +0000&lt;/td&gt;
&lt;td&gt;UTC+8&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;1970-12-31 17:00:00 +0000&lt;/td&gt;
&lt;td&gt;UTC+7&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;1970-12-31 18:00:00 +0000&lt;/td&gt;
&lt;td&gt;UTC+6&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;1970-12-31 19:00:00 +0000&lt;/td&gt;
&lt;td&gt;UTC+5&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;1970-12-31 20:00:00 +0000&lt;/td&gt;
&lt;td&gt;UTC+4&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;1970-12-31 21:00:00 +0000&lt;/td&gt;
&lt;td&gt;UTC+3&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;1970-12-31 22:00:00 +0000&lt;/td&gt;
&lt;td&gt;UTC+2&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;1970-12-31 23:00:00 +0000&lt;/td&gt;
&lt;td&gt;UTC+1&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;1971-01-01 00:00:00 +0000&lt;/td&gt;
&lt;td&gt;UTC+0&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;1970-12-31 01:00:00 +0000&lt;/td&gt;
&lt;td&gt;UTC-1&lt;/td&gt;
&lt;td&gt;Why are we back in 1970 again??&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;1970-12-31 02:00:00 +0000&lt;/td&gt;
&lt;td&gt;UTC-2&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;1970-12-31 03:00:00 +0000&lt;/td&gt;
&lt;td&gt;UTC-3&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;1970-12-31 04:00:00 +0000&lt;/td&gt;
&lt;td&gt;UTC-4&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;1970-12-31 05:00:00 +0000&lt;/td&gt;
&lt;td&gt;UTC-5&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;1970-12-31 06:00:00 +0000&lt;/td&gt;
&lt;td&gt;UTC-6&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;1970-12-31 07:00:00 +0000&lt;/td&gt;
&lt;td&gt;UTC-7&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;1970-12-31 08:00:00 +0000&lt;/td&gt;
&lt;td&gt;UTC-8&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;1970-12-31 09:00:00 +0000&lt;/td&gt;
&lt;td&gt;UTC-9&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;1970-12-31 10:00:00 +0000&lt;/td&gt;
&lt;td&gt;UTC-10&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;1970-12-31 11:00:00 +0000&lt;/td&gt;
&lt;td&gt;UTC-11&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;1970-12-31 12:00:00 +0000&lt;/td&gt;
&lt;td&gt;UTC-12&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Ok, the first lines until &lt;code&gt;UTC+0&lt;/code&gt; look as expected. But why is the rest wrong?&lt;/p&gt;

&lt;p&gt;The solution is rather simple. There were too many (or rather too few) &lt;code&gt;TimeZone&lt;/code&gt; conversions!&lt;/p&gt;

&lt;p&gt;When we used the timezone for New York &lt;code&gt;UTC-8&lt;/code&gt;, we used &lt;code&gt;"1971-01-01 00:00:00"&lt;/code&gt; as date value. But this is in &lt;code&gt;UTC+0&lt;/code&gt;! The date in &lt;code&gt;UTC-8&lt;/code&gt; is actually &lt;code&gt;"1970-12-31 18:00:00 -0800"&lt;/code&gt;. When we call &lt;code&gt;startOfDay&lt;/code&gt;, we get &lt;code&gt;"1970-12-31 00:00:00 -0800"&lt;/code&gt; which is in &lt;code&gt;UTC+0&lt;/code&gt; &lt;code&gt;"1970-12-31 08:00:00 +0000"&lt;/code&gt;. &lt;br&gt;
This means, the result is actually correct, but our function is not 🙈&lt;/p&gt;

&lt;p&gt;So let's fix it 💪&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="kd"&gt;func&lt;/span&gt; &lt;span class="nf"&gt;adjustedStartOfDayIn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;date&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;timeZone&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;TimeZone&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="kt"&gt;Date&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="nv"&gt;calendar&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;Calendar&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;current&lt;/span&gt;
    &lt;span class="n"&gt;calendar&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;timeZone&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;timeZone&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;correctDay&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;date&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addingTimeInterval&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;TimeInterval&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;calendar&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;timeZone&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;secondsFromGMT&lt;/span&gt;&lt;span class="p"&gt;()))&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;calendar&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;startOfDay&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;for&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;correctDay&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;correctStart1&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;adjustedStartOfDayIn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;date&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;date&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;timeZone&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;timeZone1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;correctStart2&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;adjustedStartOfDayIn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;date&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;date&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;timeZone&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;timeZone2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="nf"&gt;compareDates&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;date1&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;correctStart1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;date2&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;correctStart1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now we get &lt;code&gt;"date1 is earlier in time than date2"&lt;/code&gt; as result, just as expected. 😁&lt;br&gt;
The key for the solution here is that we didn't use &lt;code&gt;date&lt;/code&gt; directly but shifted it by the offset of the specific timezone and UTC, so we get the &lt;code&gt;startOfDay&lt;/code&gt; in the timezone we want to use.&lt;/p&gt;

&lt;p&gt;Conclusion: Don't mess with timezones!&lt;/p&gt;

&lt;p&gt;You can find the complete code &lt;a href="https://gist.github.com/KatharinaGopp/7dac4c660860b9b4130631c032bb8268/b69386341ed3a33f4fd3bfeaa9e500b56a681f7d#file-timezone-shenanigans-swift"&gt;here&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>swift</category>
      <category>beginners</category>
      <category>ios</category>
    </item>
    <item>
      <title>How to Write a Distance Converter in Swift/SwiftUI</title>
      <dc:creator>Katharina Gopp</dc:creator>
      <pubDate>Tue, 05 Oct 2021 11:25:10 +0000</pubDate>
      <link>https://forem.com/katharinagopp/how-to-write-a-distance-converter-in-swiftui-2463</link>
      <guid>https://forem.com/katharinagopp/how-to-write-a-distance-converter-in-swiftui-2463</guid>
      <description>&lt;p&gt;In this tutorial, we will build a simple distance converter for iOS with SwiftUI. We will take a look at how to read user input as a &lt;code&gt;Double&lt;/code&gt; and how to prevent the user from entering faulty values.&lt;/p&gt;

&lt;p&gt;Once completed, we will be able to enter a distance with a start unit and select the unit we want to convert the distance into. Also, we will add a conversion history of the current session, which will be deleted after closing the app.&lt;/p&gt;

&lt;p&gt;So let's get started 😀&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 1: Modeling the Different Units
&lt;/h2&gt;

&lt;p&gt;We start by creating an &lt;code&gt;enum&lt;/code&gt; for the different units. For readability, we use the full name for the &lt;code&gt;cases&lt;/code&gt; and add an abbreviation, which we assign as a corresponding &lt;code&gt;String&lt;/code&gt;. Additionaly, we need our &lt;code&gt;enum&lt;/code&gt; to conform to the &lt;code&gt;CaseIterable&lt;/code&gt; protocol, so that we can later use &lt;code&gt;DistanceUnit.allCases&lt;/code&gt; in our &lt;code&gt;Picker&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;enum DistanceUnit: String, CaseIterable {
    case inch = "in"
    case foot = "ft"
    case yard = "yd"
    case meter = "m"
    case kilometer = "km"
    case mile = "mi"
    case nauticMile = "nmi"
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Step 2: Add a Conversion Struct
&lt;/h2&gt;

&lt;p&gt;To record a conversion history, we create first a new &lt;code&gt;struct&lt;/code&gt; called &lt;code&gt;Conversion&lt;/code&gt; so we can use then an &lt;code&gt;Array&lt;/code&gt; of &lt;code&gt;Conversion&lt;/code&gt; for our conversion history. In &lt;code&gt;Conversion&lt;/code&gt;, we save the start value and unit as well as the end value and unit. Since we later want to use the &lt;code&gt;Array&lt;/code&gt; of &lt;code&gt;Conversion&lt;/code&gt; in a list, &lt;code&gt;Conversion&lt;/code&gt; has to conform to the &lt;code&gt;Identifiable&lt;/code&gt; protocol, so we have to add an &lt;code&gt;id&lt;/code&gt; as well.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;struct Conversion: Identifiable {
    var id: UUID = UUID()

    var startValue: Double = 0.0
    var startDistanceUnit: DistanceUnit = .kilometer
    var endValue: Double = 0.0
    var endDistanceUnit: DistanceUnit = .mile
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Step 3: Build a UI for the App
&lt;/h2&gt;

&lt;p&gt;In the &lt;code&gt;body&lt;/code&gt; of our &lt;code&gt;ContentView&lt;/code&gt;, we build our UI. It contains a headline, the conversion input, a button and the history of conversions as a list.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;struct ContentView: View {
    @State var startDistanceUnit: DistanceUnit = .kilometer
    @State var endDistanceUnit: DistanceUnit = .mile
    @State var startValueString = ""

    @State var conversionHistory: [Conversion] = []

    var body: some View {
        VStack {
            // Headline
            Text("Distance Conversion")
                .font(.title)
                .bold()
                .padding()

            // Conversion Input
            HStack {
                Text("Distance:")
                TextField("e.g. 5.3", text: $startValueString)
                Picker("\(startDistanceUnit.rawValue)", selection: $startUnit) {
                    ForEach(DistanceUnit.allCases, id: \.self) { unit in
                        Text(unit.rawValue)
                    }
                }
                .pickerStyle(MenuPickerStyle())
            }
            .padding(.horizontal, 25)

            HStack {
                Spacer()

                Text("Convert to: ")
                Picker("\(endDistanceUnit.rawValue)", selection: $endUnit) {
                    ForEach(DistanceUnit.allCases, id: \.self) { distanceUnit in
                        Text(distanceUnit.rawValue)
                    }
                }
                .pickerStyle(MenuPickerStyle())
            }

            // Convert Button
            HStack {
                Spacer()

                Button(action: {
                    // TODO: add conversion
                }) {
                    Text("Convert")
                }
                .padding()
                .background(Color.green)
                .foregroundColor(.black)
                .cornerRadius(15)

                Spacer()
            }

            // Conversion History
            List {
                ForEach(conversionHistory.reversed()) { currentConversion in
                    HStack {
                        Spacer()

                        Text("\(currentConversion.startValue) \(currentConversion.startDistanceUnit.rawValue) -&amp;gt; \(currentConversion.endValue) \(currentConversion.endDistanceUnit.rawValue)")

                        Spacer()
                    }
                }
            }
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It should look like this:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4r2bil4reuzo542769zj.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4r2bil4reuzo542769zj.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 4: Implement Button Action
&lt;/h2&gt;

&lt;p&gt;Next, we need a function to convert our start value and save it as a &lt;code&gt;Conversion&lt;/code&gt; in our &lt;code&gt;conversionHistory&lt;/code&gt; array. Therefore, we add the functions &lt;code&gt;convertUnit&lt;/code&gt; and &lt;code&gt;saveConversion&lt;/code&gt; and call &lt;code&gt;saveConversion()&lt;/code&gt; in our &lt;code&gt;Button&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;One way of calculating the conversion is to use a nested &lt;code&gt;switch&lt;/code&gt; statement. But then we have in each &lt;code&gt;switch&lt;/code&gt; case again all other &lt;code&gt;switch&lt;/code&gt; cases. That would lead to n&lt;sup&gt;2&lt;/sup&gt; statements for n distance units. And the more units we use, the longer and more unpleasent the statement will get. &lt;/p&gt;

&lt;p&gt;Another way to calculate it is the use of a conversion factor for each &lt;code&gt;case&lt;/code&gt; of our &lt;code&gt;enum&lt;/code&gt;. When multiplying a distance with this conversion factor, we get the distance in meters. To use this factor, we add a variable to &lt;code&gt;UnitSystem&lt;/code&gt; called &lt;code&gt;conversionFactorToMeters&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;extension DistanceUnit {
    var conversionFactorToMeter: Double {
        switch self {
        case .inch:
            return 0.0254
        case .foot:
            return 0.3048
        case .yard:
            return 0.9144
        case .meter:
            return 1
        case .kilometer:
            return 1000
        case .mile:
            return 1609.344
        case .nauticMile:
            return 1852
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With this, we can simply convert our value to meters by multiplying the conversion factor of the &lt;code&gt;startUnit&lt;/code&gt; and then to the specified unit by dividing with the conversion factor of the &lt;code&gt;endUnit&lt;/code&gt;. This gives us to the following functions:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;struct ContentView: View {
    // ...

    var body: some View {
        // ...

        Button(action: {
            saveConversion()
        }) {
            Text("Convert")
        }
        // ...
    }

    // Functions
    func convertUnit(valueToConvert: Double, fromUnit: DistanceUnit, toUnit: DistanceUnit) -&amp;gt; Double {
        return valueToConvert * fromUnit.conversionFactorToMeter / toUnit.conversionFactorToMeter
    }

    func saveConversion() {
        var conversion = Conversion()

        conversion.startUnit = startUnit
        conversion.endUnit = endUnit
        conversion.startValue = Double(startValueString) ?? 0.0
        conversion.endValue = convertUnit(valueToConvert: conversion.startValue, fromUnit: startUnit, toUnit: endUnit)

        conversions.append(conversion)
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Congratulations, you have a fully functioning Distance Converter App!&lt;/p&gt;

&lt;p&gt;But...&lt;/p&gt;

&lt;p&gt;If you try it, you will see that it isn't really user-friendly. So let's change that! &lt;/p&gt;

&lt;p&gt;Since we only put &lt;code&gt;Double&lt;/code&gt; values into our &lt;code&gt;TextField&lt;/code&gt;, we can set the &lt;code&gt;keyboardType&lt;/code&gt; to &lt;code&gt;.decimalPad&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;struct ContentView: View {
    // ...
    var body: some View {
        // ...

        TextField("e.g. 5.3", text: $startValueString)
            .keyboardType(.decimalPad)

        // ...
    }
    // ...
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;While this modifier prevents us from typing anything other than numbers and decimal separators, we can still paste other text. Furthermore, we can type multiple decimal separators. This means that we don't have a decimal input which results in using the default value as &lt;code&gt;startValue&lt;/code&gt;, which is 0.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 5: Prevent Faulty Input
&lt;/h2&gt;

&lt;p&gt;While searching for a solution for this problem, I came across &lt;a href="https://programmingwithswift.com/numbers-only-textfield-with-swiftui/" rel="noopener noreferrer"&gt;this&lt;/a&gt; blog post. In this blog post is a solution how to use a &lt;code&gt;TextField&lt;/code&gt; for numbers only. Since it should work not only for &lt;code&gt;Integer&lt;/code&gt; but also for &lt;code&gt;Double&lt;/code&gt; values, I made some adjustments:&lt;/p&gt;

&lt;p&gt;First we add a new &lt;code&gt;class&lt;/code&gt; called &lt;code&gt;ValidatedDecimal&lt;/code&gt;, which has a variable &lt;code&gt;valueString&lt;/code&gt;. When this variable is set, we check each char whether it is a number or the first decimal separator. All other characters will be filtered. Additionally, if the first input is a decimal separator, we add a 0 upfront.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;class ValidatedDecimal: ObservableObject {
    @Published var valueString = "" {
        didSet {
            var hasDecimalSeparator = false
            var filteredString = ""

            for char in valueString {
                if char.isNumber {
                    filteredString.append(char)
                } else if String(char) == Locale.current.decimalSeparator &amp;amp;&amp;amp; !hasDecimalSeparator {
                    if filteredString.count == 0 {
                        filteredString = "0"
                    }
                    filteredString.append(char)
                    hasDecimalSeparator = true
                }
            }

            if valueString != filteredString {
                valueString = filteredString
            }
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now we have to change a few things in our &lt;code&gt;ContentView&lt;/code&gt;: Instead of the &lt;code&gt;startValueString&lt;/code&gt; we use an instance of &lt;code&gt;ValidatedDecimal&lt;/code&gt; called &lt;code&gt;startValue&lt;/code&gt; and bind the input from the &lt;code&gt;TextField&lt;/code&gt; to the variable &lt;code&gt;valueString&lt;/code&gt; from &lt;code&gt;startValue&lt;/code&gt;. In the function &lt;code&gt;saveConversion()&lt;/code&gt; we also have to replace &lt;code&gt;startValueString&lt;/code&gt; with &lt;code&gt;startValue.valueString&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;struct ContentView: View {
    // ...

    // @State var startValueString = ""
    @ObservedObject var startValue = ValidatedDecimal()

    // ...

    var body: some View {
        // ...

        TextField("e.g. 5.3", text: $startValue.valueString)

        // ...
    }
    // ...

    func saveConversion() {
        // ...

        conversion.startValue = Double(startValue.valueString) ?? 0.0

        // ...
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This already looks quite nice 😃 So we could stop here.&lt;/p&gt;

&lt;p&gt;Or...&lt;/p&gt;

&lt;p&gt;We take another look into the different regional settings. While this code works with a dot as decimal separator, it does not work in regions, where the decimal separator is a comma. So let's fix that.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 6: Taking Regional Settings into Account
&lt;/h2&gt;

&lt;p&gt;If your region is using a decimal comma, we run into a problem:&lt;/p&gt;

&lt;p&gt;Since we use the &lt;code&gt;KeyboardType&lt;/code&gt; &lt;code&gt;.decimalPad&lt;/code&gt;, the comma is shown as decimal separator, which is what we want. But when casting the string into a double, we need a decimal dot instead. Otherwise the string will not be recognized as double and we get our default value of 0.0 😕&lt;/p&gt;

&lt;p&gt;To fix this, we add a computed property to &lt;code&gt;ValidatedDecimal&lt;/code&gt;, which we simply call &lt;code&gt;decimalValue&lt;/code&gt;. In this variable we take the &lt;code&gt;valueString&lt;/code&gt;, replace the decimal separator with a &lt;code&gt;"."&lt;/code&gt; and return the resulting value as &lt;code&gt;Double&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;class ValidatedDecimal: ObservableObject {
    // ...

    var decimalValue: Double {
        let replacedString = valueString.replacingOccurrences(of: Locale.current.decimalSeparator!, with: ".")

        return Double(replacedString) ?? 0
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now we only have to use &lt;code&gt;decimalValue&lt;/code&gt; as &lt;code&gt;startValue&lt;/code&gt; for the &lt;code&gt;Conversion&lt;/code&gt; in the &lt;code&gt;ContentView&lt;/code&gt;. Also, we change the decimal point of the text in the &lt;code&gt;TextField&lt;/code&gt; to &lt;code&gt;Locale.current.decimalSeparator&lt;/code&gt; to be consistent.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;struct ContentView: View {
    // ...

    var body: some View {
        // ...

        TextField("e.g. 5\(Locale.current.decimalSeparator!)3", text: $startValue.valueString)

        // ...
    }
    // ...

    func saveConversion() {
        // ...

        // conversion.startValue = Double(startValue.valueString) ?? 0.0
        conversion.startValue = startValue.decimalValue

        // ...
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Wow, this looks like we're finished 😃 &lt;/p&gt;

&lt;p&gt;On the other hand...&lt;/p&gt;

&lt;p&gt;By making some minor adjustments we can make it more readable and user-friendly.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 7: Final Adjustments
&lt;/h2&gt;

&lt;p&gt;First of all, we don't want to always delete the previous input. So we add a &lt;code&gt;reset()&lt;/code&gt; function to our code.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;struct ContentView: View {
    // ...

    var body: some View {
        // ...

        Button(action: {
            saveConversion()
            resetInput()
        }) {
            Text("Convert")
        }
        // ...
    }

    // Functions
    // ...

    func resetInput() {
        startValue.valueString = ""
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Also, we don't want to see trailing zeros in our history, so let's add a &lt;code&gt;NumberFormatter&lt;/code&gt;. In this formatter, we can set the maximum  accuracy of the conversion by setting a value to the property &lt;code&gt;.maximumFractionDigits&lt;/code&gt;. Please note, however, that we should not set this too high, as the conversion and the floating point arithmetic of our double value can lead to inaccuracy.&lt;/p&gt;

&lt;p&gt;For readability, we put the corresponding code into functions:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;struct ContentView: View {
    // ...
    var body: some View {
        // ...

        List {
                ForEach(conversions.reversed()) { currentConversion in
                    HStack {
                        Spacer()

                        Text(printConversion(conversion: currentConversion))

                        Spacer()
                    }
                }
            }

        // ...
    }
    // Functions
    // ...

    func printConversion(conversion: Conversion) -&amp;gt; String {
        return printDistanceWithUnit(distance: conversion.startValue, unit: conversion.startUnit) + " -&amp;gt; " + printDistanceWithUnit(distance: conversion.endValue, unit: conversion.endUnit)
    }

    func printDistanceWithUnit(distance: Double, unit: DistanceUnit) -&amp;gt; String {
        let formatter = NumberFormatter()
        formatter.numberStyle = .decimal
        formatter.maximumFractionDigits = 6 // Change this value for accuracy

        var returnString = ""

        let value = NSNumber(value: distance)
        if let distanceString = formatter.string(from: value) {
            returnString = distanceString + " " + unit.rawValue
        }

        return returnString
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Finally, to prevent tapping the &lt;code&gt;Button&lt;/code&gt; and adding lot of zero to zero conversions, we disable the convert &lt;code&gt;Button&lt;/code&gt; if the &lt;code&gt;TextField&lt;/code&gt; is blank and change it's color to make the deactivation visible.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;struct ContentView: View {
    // ...
    var body: some View {
        // ...

        Button(action: {
                    saveConversion()
                    resetInput()
                }) {
                    Text("Convert")
                }
                .disabled(startValue.valueString == "")
                .padding()
                .background(startValue.valueString == "" ? Color.gray : Color.green)
                .foregroundColor(startValue.valueString == "" ? .black.opacity(0.2) : .black)
                .cornerRadius(15)        
        // ...
    }
    // ...
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;So that's it, we made a distance converter app 👍🏻 &lt;/p&gt;

&lt;p&gt;You can find the full source code &lt;a href="https://github.com/KatharinaGopp/DistanceConverter" rel="noopener noreferrer"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;I hope this post helped you to implement a conversion app and how to use a &lt;code&gt;TextField&lt;/code&gt; for &lt;code&gt;Double&lt;/code&gt; input. And while there is no built in option to prevent faulty input in a &lt;code&gt;TextField&lt;/code&gt;, we can always create our own input validator 😉.&lt;/p&gt;

</description>
      <category>tutorial</category>
      <category>beginners</category>
      <category>swift</category>
      <category>ios</category>
    </item>
  </channel>
</rss>
