<?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>DEV Community: Ines</title>
    <description>The latest articles on DEV Community by Ines (@inesp).</description>
    <link>https://dev.to/inesp</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%2F502411%2F6801d539-d324-4024-89dc-8c7c8a8269aa.jpeg</url>
      <title>DEV Community: Ines</title>
      <link>https://dev.to/inesp</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/inesp"/>
    <language>en</language>
    <item>
      <title>How do I measure the execution time of Celery tasks?</title>
      <dc:creator>Ines</dc:creator>
      <pubDate>Mon, 13 Jan 2025 12:18:00 +0000</pubDate>
      <link>https://dev.to/inesp/how-do-i-measure-the-execution-time-of-celery-tasks-53ja</link>
      <guid>https://dev.to/inesp/how-do-i-measure-the-execution-time-of-celery-tasks-53ja</guid>
      <description>&lt;p&gt;The next item in my collection of "Aren't we all just constantly re-creating the same bits of code?" &lt;br&gt;
is how to track the execution time of Celery tasks.&lt;/p&gt;

&lt;p&gt;Firstly, it could be argued that there are 2 different "exec" times for every Celery task: &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the &lt;strong&gt;real&lt;/strong&gt; execution time - the time when the code was running&lt;/li&gt;
&lt;li&gt;the &lt;em&gt;"time to done"&lt;/em&gt;-execution time - which includes the time spent in the queue waiting for a free worker&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The reason both are important is: &lt;strong&gt;our real motivation is understanding when &lt;em&gt;the thing&lt;/em&gt; is done.&lt;/strong&gt; &lt;/p&gt;

&lt;p&gt;We trigger a task, we want to know when that job is done and when to expect the results.&lt;/p&gt;

&lt;p&gt;It's like project estimation. The manager truly wants to know when the project will be out of the house and not&lt;br&gt;
really, that it can be done in 1 week, but nobody is free to do it for the next 6 months. 👷‍♀️&lt;/p&gt;
&lt;h2&gt;
  
  
  Signals 🎺
&lt;/h2&gt;

&lt;p&gt;As far as I can see, we just need to time the tasks ourselves. Our weapon of choice are Celery signals.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Hint 1:&lt;/strong&gt; Celery signals get all their arguments as &lt;strong&gt;keyword arguments&lt;/strong&gt;. This means that we can just type out the kwargs we are interested in and pack the rest into &lt;code&gt;**kwargs&lt;/code&gt;.&lt;br&gt; &lt;em&gt;&lt;strong&gt;Also&lt;/strong&gt;: This is the best thing ever. All signals all over the world should adopt this practice!!&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Hint 2:&lt;/strong&gt; We can store the exec start and end times on the task object itself among the other attributes, which are called "headers".&lt;/p&gt;
&lt;h2&gt;
  
  
  Start of queuing
&lt;/h2&gt;

&lt;p&gt;Just as a new Celery task is put into a queue, let's record the current time like so:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;celery&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;signals&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;dateutil.parser&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;isoparse&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;timezone&lt;/span&gt;

&lt;span class="nd"&gt;@signals.before_task_publish.connect&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;before_task_publish&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;kwargs&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;raw_eta&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;eta&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;raw_eta&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="c1"&gt;# the task is scheduled for later, so, let's start the countdown from then
&lt;/span&gt;        &lt;span class="n"&gt;publish_time&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;isoparse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;raw_eta&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; 
    &lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;publish_time&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;now&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tz&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;timezone&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;utc&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;isoformat&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;__publish_time&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;publish_time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;isoformat&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Start of execution
&lt;/h2&gt;

&lt;p&gt;When the task is picked up by a worker, let's record the current time like so:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;celery&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;signals&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;timezone&lt;/span&gt;

&lt;span class="nd"&gt;@signals.task_prerun.connect&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;task_prerun&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;task&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Task&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;kwargs&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="nf"&gt;setattr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;task&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;__prerun_time&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;now&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tz&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;timezone&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;utc&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;isoformat&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  End of execution
&lt;/h2&gt;

&lt;p&gt;When the task is done, we can calculate both exec times and store them .. somewhere. Probably in StatsD or some other monitoring tool.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;StatsD is an industry-standard technology stack for monitoring applications and instrumenting any piece of software to deliver custom metrics.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;small&gt;- Netdata: Introduction to StatsD [1]&lt;/small&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;celery&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;signals&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Task&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;dateutil.parser&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;isoparse&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;datetime&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="n"&gt;timedelta&lt;/span&gt;

&lt;span class="nd"&gt;@signals.task_postrun.connect&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;task_postrun&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;task&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Task&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;kwargs&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;now&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;now&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tz&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;timezone&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;utc&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;publish_time&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;
    &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;publish_time&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;isoparse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;getattr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;task&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;__publish_time&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;""&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="nb"&gt;Exception&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;publish_time&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;

    &lt;span class="n"&gt;prerun_time&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;
    &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;prerun_time&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;isoparse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;getattr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;task&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;__prerun_time&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;""&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="nb"&gt;Exception&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;prerun_time&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;

    &lt;span class="n"&gt;exec_time&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;timedelta&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;now&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;prerun_time&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;prerun_time&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="nf"&gt;timedelta&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="n"&gt;waiting_time&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;timedelta&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;prerun_time&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;publish_time&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;publish_time&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;prerun_time&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="nf"&gt;timedelta&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="n"&gt;waiting_and_exec_time&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;timedelta&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;now&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;publish_time&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;publish_time&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="nf"&gt;timedelta&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="n"&gt;stats&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;exec_time_ms&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;to_milliseconds&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;exec_time&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;waiting_time_ms&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;to_milliseconds&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;waiting_time&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;waiting_and_exec_time_ms&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;to_milliseconds&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;waiting_and_exec_time&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="c1"&gt;# TODO: log somehow to somewhere ¯\_(ツ)_/¯ 
&lt;/span&gt;    &lt;span class="n"&gt;statsd&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;timing&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;celery.task.exec_time_ms&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;...,&lt;/span&gt; &lt;span class="n"&gt;tags&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;task:&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;task&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
    &lt;span class="n"&gt;statsd&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;timing&lt;/span&gt;&lt;span class="p"&gt;(.&lt;/span&gt;&lt;span class="bp"&gt;...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Bonus: And once you are tracking how long tasks take, you can set up alerts for when they take too long. 🛎️
&lt;/h2&gt;

&lt;p&gt;It can be as simple as adding a hard coded threshold to the above function:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;exec_time&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;timedelta&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;hour&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="n"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Task &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;task&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; took too long: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;exec_time&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;. Check it out!&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Or you can have layers of thresholds or thresholds as settings on the task definitions or whatever you can express in code.&lt;/p&gt;

</description>
      <category>celery</category>
      <category>python</category>
    </item>
    <item>
      <title>Isolate code blocks from each other?</title>
      <dc:creator>Ines</dc:creator>
      <pubDate>Thu, 02 Jan 2025 18:01:18 +0000</pubDate>
      <link>https://dev.to/inesp/building-blocks-collection-isolate-code-blocks-from-each-other-11p0</link>
      <guid>https://dev.to/inesp/building-blocks-collection-isolate-code-blocks-from-each-other-11p0</guid>
      <description>&lt;p&gt;Aren't we all just constantly re-creating the same bits of code?&lt;/p&gt;

&lt;p&gt;This goes beyond boilerplate code. We are adding the same bits of code to every project, the same &lt;br&gt;
git shortcuts, the same logs formatters, the same permissions decorators, ... &lt;/p&gt;

&lt;p&gt;Here I've started putting together a personal collection of building blocks. &lt;/p&gt;

&lt;p&gt;And I'm starting with: Code that isolates (insulates) code blocks. &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Hint&lt;/strong&gt;: The full function is &lt;a href="http://www.ines-panker.com/2025/01/01/isolating-code-blocks.html#full-code" rel="noopener noreferrer"&gt;on my blog post&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  Example: I'm sending emails to all team members.
&lt;/h3&gt;

&lt;p&gt;If 1 person's email causes a bug, I want the code to skip this person and continue sending emails to others.&lt;/p&gt;

&lt;p&gt;Naive - no error handling:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;send_all_emails&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;users&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;list&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;User&lt;/span&gt;&lt;span class="p"&gt;]):&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;user&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;users&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="nf"&gt;send_report_email_to_user&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;em&gt;If we have a list of 10 users, but we encounter an unexpected error&lt;br&gt;
with the report for user num 3, then only the first 2 users will get the email, others will not.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;With error handling:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;send_all_emails&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;users&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;list&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;User&lt;/span&gt;&lt;span class="p"&gt;]):&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;user&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;users&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="nf"&gt;suppress_and_log_exc&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
            &lt;span class="c1"&gt;# ↑ Will catch any Exception, log it correctly
&lt;/span&gt;            &lt;span class="c1"&gt;# and then continue with the next user 
&lt;/span&gt;            &lt;span class="nf"&gt;send_report_email_to_user&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  What &lt;code&gt;suppress_and_log_exc&lt;/code&gt; does
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;it catches some &lt;code&gt;Exception&lt;/code&gt; class&lt;/li&gt;
&lt;li&gt;logs it properly&lt;/li&gt;
&lt;li&gt;lets the code continue&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So, something like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="nd"&gt;@contextmanager&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;suppress_and_log_exc&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;action_desc&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="c1"&gt;# ↑ Let's require an identifier. The error msg will be more helpful this way.
&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;yield&lt;/span&gt;
    &lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="nb"&gt;Exception&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;exc&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Error `&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;exc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;__class__&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;__name__&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;` occurred while &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;action_desc&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;exc_info&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;exc&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;h3&gt;
  
  
  Possible use cases
&lt;/h3&gt;

&lt;p&gt;This code comes in handy whenever you have a list of actions that are independent of each other.&lt;/p&gt;

&lt;p&gt;Like for example: we are triggering various side effects after some event.&lt;/p&gt;

&lt;p&gt;Maybe a new user has registered, so, we want to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;send a Slack high-five to the dev team and also &lt;/li&gt;
&lt;li&gt;create a ticket for the customer success team to contact them and also &lt;/li&gt;
&lt;li&gt;... . &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If any of these side effects fail, the others must still be triggered.&lt;/p&gt;

&lt;p&gt;Another example: we have a multi-tenant system and want to trigger one Celery task for each tenant.&lt;/p&gt;

&lt;p&gt;If the code for creating a Celery task for customer number 5 has a problem, we still want to create the tasks for customers 6 to 1.000.000. &lt;/p&gt;

&lt;p&gt;It would be silly, if our code were to fail at say... sending out the monthly bill, with customer number 5 and then not even try to send it to customer number 6 and 7 and so on. &lt;/p&gt;

&lt;h3&gt;
  
  
  Adding more settings to the contextmanager
&lt;/h3&gt;

&lt;p&gt;We can make the function more customizable by adding a setting for:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;the log level - some things are in reality just a warning or an info&lt;/li&gt;
&lt;li&gt;a map of log levels - a specific log level per exception class&lt;/li&gt;
&lt;li&gt;the exception class that we want to catch - maybe we just care about EmailSendingException&lt;/li&gt;
&lt;li&gt;more log data - so we can better understand what went wrong when we see this msg in Sentry&lt;/li&gt;
&lt;li&gt;an error callback - a function that is called, when the error happens, which can be used for custom error-cleanup&lt;/li&gt;
&lt;li&gt;.. whatever your heart desires .. 💖&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;So, &lt;a href="http://www.ines-panker.com/2025/01/01/isolating-code-blocks.html#full-code" rel="noopener noreferrer"&gt;here is now the full code.&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Fin: All together now
&lt;/h3&gt;

&lt;p&gt;Our Example code could now look like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;send_all_emails&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;users&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;list&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;User&lt;/span&gt;&lt;span class="p"&gt;]):&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;user&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;users&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="nf"&gt;suppress_and_log_exc&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;action_desc&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Sending my very special report email&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;extra&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;user&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;id&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;):&lt;/span&gt;
            &lt;span class="c1"&gt;# ↑ Will catch any Exception, log it correctly and then. 
&lt;/span&gt;            &lt;span class="nf"&gt;send_report_email_to_user&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Or it could be crazy complicated like so:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;logging&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;send_all_emails&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;users&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;list&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;User&lt;/span&gt;&lt;span class="p"&gt;]):&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;user&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;users&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="nf"&gt;suppress_and_log_exc&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;action_desc&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Sending my very special report email&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;log_level&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;logging&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WARNING&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;# &amp;lt;- default log level
&lt;/span&gt;            &lt;span class="n"&gt;log_level_maps&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;EmailIsInvalidException&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;logging&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;INFO&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
            &lt;span class="n"&gt;exc_types_to_catch&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ReportException&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;EmailException&lt;/span&gt;&lt;span class="p"&gt;,),&lt;/span&gt;
            &lt;span class="n"&gt;extra&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;user&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;id&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;):&lt;/span&gt;
            &lt;span class="c1"&gt;# ↑ Will catch any Exception, log it correctly and then. 
&lt;/span&gt;            &lt;span class="nf"&gt;send_report_email_to_user&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



</description>
    </item>
    <item>
      <title>How to encourage developers to fix Python warnings for deprecated features</title>
      <dc:creator>Ines</dc:creator>
      <pubDate>Wed, 25 Dec 2024 12:50:01 +0000</pubDate>
      <link>https://dev.to/inesp/how-to-encourage-developers-to-fix-python-warnings-for-deprecated-features-42oa</link>
      <guid>https://dev.to/inesp/how-to-encourage-developers-to-fix-python-warnings-for-deprecated-features-42oa</guid>
      <description>&lt;p&gt;Our code is so interdependent! Adding dependencies to projects is just how code is done. &lt;/p&gt;

&lt;p&gt;Every time somebody sneezes a new dependency is added to a project somewhere. &lt;/p&gt;

&lt;p&gt;It is kinda great that we are on this side of the copy-right debate, where the default is building things on top of other people's work, and not shooting at everybody, who dares to even look in the direction of my code. But... who is going to upgrade all these dependencies all the time?&lt;/p&gt;

&lt;p&gt;In the perfect world, we are a community and we all keep the libs upgraded to the latest versions, right? &lt;/p&gt;

&lt;p&gt;Wrong! Devs need to be &lt;em&gt;softly&lt;/em&gt; pushed to do the right thing. Yes, &lt;em&gt;softly&lt;/em&gt;, so they don't notice they are being pushed 😉🚧🚧🚧.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 1: First we need to listen to deprecation warnings
&lt;/h2&gt;

&lt;p&gt;We are using &lt;code&gt;pytest&lt;/code&gt;, so this step is easy-peasy for us.  &lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Starting from version &lt;code&gt;3.1&lt;/code&gt;, pytest now automatically catches warnings during test execution and displays them at the end of the session:&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;small&gt;- &lt;a href="https://docs.pytest.org/en/stable/how-to/capture-warnings.html" rel="noopener noreferrer"&gt;Pytest: How to capture warnings&lt;/a&gt;&lt;/small&gt;&lt;/p&gt;

&lt;p&gt;like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="o"&gt;=============================&lt;/span&gt; warnings summary &lt;span class="o"&gt;=============================&lt;/span&gt;
test_bla_bla_bla.py::test_test
  .../some_file.py:65: PydanticDeprecatedSince20: &lt;span class="sb"&gt;`&lt;/span&gt;pydantic.config.Extra&lt;span class="sb"&gt;`&lt;/span&gt; 
    is deprecated, use literal values instead &lt;span class="o"&gt;(&lt;/span&gt;e.g. &lt;span class="sb"&gt;`&lt;/span&gt;&lt;span class="nv"&gt;extra&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'allow'&lt;/span&gt;&lt;span class="sb"&gt;`&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="nb"&gt;.&lt;/span&gt; 
    Deprecated &lt;span class="k"&gt;in &lt;/span&gt;Pydantic V2.0 to be removed &lt;span class="k"&gt;in &lt;/span&gt;V3.0. See Pydantic V2 Migration Guide 
    at https://errors.pydantic.dev/2.9/migration/
      &lt;span class="nv"&gt;extra&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;Extra.allow,

  .../marshmallow/fields.py:1186: RemovedInMarshammlow4Warning: The &lt;span class="s1"&gt;'default'&lt;/span&gt; argument
    to fields is deprecated. Use &lt;span class="s1"&gt;'dump_default'&lt;/span&gt; instead.
      super&lt;span class="o"&gt;()&lt;/span&gt;.__init__&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="k"&gt;**&lt;/span&gt;kwargs&lt;span class="o"&gt;)&lt;/span&gt;

&lt;span class="o"&gt;=======================&lt;/span&gt; 1 passed, 2 warnings &lt;span class="k"&gt;in &lt;/span&gt;0.12s &lt;span class="o"&gt;=======================&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Step 2: Pick the warnings you want to get rid of
&lt;/h2&gt;

&lt;p&gt;I saw the &lt;code&gt;marshmallow&lt;/code&gt; warning, looked at the code and saw that it was a simple fix. &lt;/p&gt;

&lt;p&gt;So, I'm choosing &lt;code&gt;RemovedInMarshammlow4Warning&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 3: Make deprecation warnings fail the tests
&lt;/h2&gt;

&lt;p&gt;Again, we are using &lt;code&gt;pytest&lt;/code&gt;, so this step is also easy-peasy for us. &lt;/p&gt;

&lt;p&gt;Just add this to the config file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight toml"&gt;&lt;code&gt;&lt;span class="nn"&gt;[tool.pytest.ini_options]&lt;/span&gt;
&lt;span class="err"&gt;...&lt;/span&gt;
&lt;span class="py"&gt;filterwarnings&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="s"&gt;"error::marshmallow.warnings.RemovedInMarshmallow4Warning"&lt;/span&gt;
&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;small&gt;- &lt;a href="https://docs.pytest.org/en/stable/how-to/capture-warnings.html#controlling-warnings" rel="noopener noreferrer"&gt;Pytest: Controlling warnings&lt;/a&gt;&lt;/small&gt;&lt;/p&gt;

&lt;p&gt;Now every test that triggers this deprecation warning will fail.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 4: Fix the code
&lt;/h2&gt;

&lt;p&gt;Unfortunately, you have to take one for the team here. You have to go and fix all the places where this warning is triggered.&lt;/p&gt;

&lt;p&gt;I know.. it's unfair. &lt;/p&gt;

&lt;p&gt;It's a lot of work, it's tedious work and you won't even be able to brag to your boss about it, because it is too technical and generally perceived as irrelevant from their standpoint.&lt;/p&gt;

&lt;p&gt;And everything this does is help the next person (who won't be you), who will upgrade marshmallow and nothing in code will break. So, they won't thank you either, because they won't even notice anything has been done. &lt;/p&gt;

&lt;p&gt;So, ... what I'm saying is: sometimes you need to do the right thing, and nobody will thank you for it. &lt;/p&gt;

&lt;p&gt;But I will know. And you will know. 🤝&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 5: Lean back and listen to Slack convos and GitHub comments
&lt;/h2&gt;

&lt;p&gt;Eventually, there should be one message somewhere of somebody being confused: they wrote the code the same as last time, but this time, the tests fail, what's going on? &lt;/p&gt;

&lt;p&gt;And you can say: "oh, hard to say, but I do see this is a deprecated feature, that must be the reason". 🍹&lt;/p&gt;

&lt;h2&gt;
  
  
  External links
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://docs.pytest.org/en/stable/how-to/capture-warnings.html" rel="noopener noreferrer"&gt;Pytest: How to capture warnings&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.python.org/3/library/warnings.html#the-warnings-filter" rel="noopener noreferrer"&gt;Python: The warnings filter&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>python</category>
      <category>pytest</category>
    </item>
    <item>
      <title>Why is sooooo.. much documentation a pain to read?</title>
      <dc:creator>Ines</dc:creator>
      <pubDate>Tue, 24 Dec 2024 09:10:04 +0000</pubDate>
      <link>https://dev.to/inesp/why-is-sooooo-much-documentation-a-pain-to-read-1jgn</link>
      <guid>https://dev.to/inesp/why-is-sooooo-much-documentation-a-pain-to-read-1jgn</guid>
      <description>&lt;p&gt;Because it is written in a boring, counterproductive style. But why is that?&lt;/p&gt;

&lt;p&gt;Because it copied the style of academic papers. But why are academic papers dry and boring? &lt;/p&gt;

&lt;p&gt;Because most schools have since forever talked to their pupils in a dry and boring manner. But why is that? &lt;/p&gt;

&lt;p&gt;Because until recently human society has been managed in an authoritarian manner. Everybody knew exactly who was above them and who was below them, who they have to obey and who they can give orders to. For the most part, &lt;em&gt;fun&lt;/em&gt; was reserved for the afterlife and the rich. But come-on! we can do better, here are a few pointers.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;(I utterly love this 5-whys interrogation technique! I recommend trying it out.)&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Learning is easiest through experience, thus let's start with a practical exercise. We will write an email.&lt;/p&gt;

&lt;h2&gt;
  
  
  Imagine the following scenario:
&lt;/h2&gt;

&lt;p&gt;It is Thursday, the 4th day of your 1-week sprint. You have just finished a ticket and have opened up a pull request. &lt;/p&gt;

&lt;p&gt;The ticket was about developing a new way to create CLI commands. The existing commands were all built on a now-defunct library.&lt;/p&gt;

&lt;p&gt;You are happy with your work, it took a lot of coordination between departments. However, one thing is still left to do: &lt;strong&gt;you have to tell everybody about this new way of writing commands.&lt;/strong&gt; &lt;/p&gt;

&lt;p&gt;How do you do it?&lt;/p&gt;

&lt;h2&gt;
  
  
  Attempt 1: Write a boring, old email
&lt;/h2&gt;

&lt;p&gt;Let's say you decide to &lt;strong&gt;write an email&lt;/strong&gt;. &lt;/p&gt;

&lt;p&gt;How better to make everybody understand this new approach, then to tell them everything, the whole story, from start to finish:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Everybody,&lt;/p&gt;

&lt;p&gt;I’ve been consulting with DevOps a lot lately because I was writing a new command. And it turns out DevOps has a lot of problems running our existing commands, they can’t do … and … and they are frustrated with … . Another problem is the fact that our library …. is not maintained anymore, so we shouldn’t use it anymore. It is not super dangerous to use it, but we should switch to something more stable soon. Together we’ve come to the conclusion that the best way forward is to use approach …. . I've written documentation about how to use the new system for CLI commands. So the old way is now deprecated, please do not use the old way anymore. If you need any help with the new way, you can ping me. &lt;/p&gt;

&lt;p&gt;Thank you,&lt;/p&gt;

&lt;p&gt;Me&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Great, done! Let's send it out. &lt;/p&gt;

&lt;p&gt;You have done everything and more. You've finished the ticket, you've communicated with everybody, you've even written docs and an email and everything. You are one great developer 😌 !&lt;/p&gt;

&lt;p&gt;But, are you really done? &lt;/p&gt;

&lt;p&gt;The crucial part of email sending comes after the email has been sent. Will anybody read it? And will the recipient act on it?&lt;/p&gt;

&lt;h2&gt;
  
  
  Why no one is reading your emails
&lt;/h2&gt;

&lt;p&gt;Let's switch perspectives.&lt;/p&gt;

&lt;p&gt;You are a different developer now. &lt;/p&gt;

&lt;p&gt;It is still Thursday, the 4th day of your 1-week sprint. You are down to 1 un-finished ticket. &lt;/p&gt;

&lt;p&gt;Truth be told, you have solved this last ticket as well, but the pesky end-to-end tests are failing. You've been staring at the failing tests for 1 hour now, trying to figure out how could your change have caused this, maybe the tests are just flaky. &lt;/p&gt;

&lt;p&gt;Should you take some time and make them stable? Do you even have the time? There is this important meeting tomorrow, you should start preparing for. &lt;/p&gt;

&lt;p&gt;Hm... oh, look an email has arrived.&lt;/p&gt;

&lt;p&gt;What will happen now? Will this second developer read our email? Will she read it thoroughly? &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Of course not.&lt;/strong&gt; &lt;/p&gt;

&lt;p&gt;The second developer has her own stuff to worry about. She'll skip over the text and forget it.&lt;/p&gt;

&lt;p&gt;What did we expect to happen? &lt;/p&gt;

&lt;p&gt;People are not machines, we cannot insert a new if-statement into their decision mechanisms via one email. &lt;/p&gt;

&lt;p&gt;People remember through repetition, not by reading emails. So... what exactly was our goal with the email? We never specified it. &lt;/p&gt;

&lt;h2&gt;
  
  
  Attempt 2: A fun, short email
&lt;/h2&gt;

&lt;p&gt;We should have started with instructions. &lt;strong&gt;First the TL;DR,&lt;/strong&gt; then the explanation:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Everybody,&lt;/p&gt;

&lt;p&gt;Please, read the following docs: http://....&lt;/p&gt;

&lt;p&gt;We have defined a new procedure for creating &lt;strong&gt;CLI commands&lt;/strong&gt;. We must all adhere to it.&lt;/p&gt;

&lt;p&gt;Thank you,&lt;br&gt;
Me&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This way of writing feels unnatural at the beginning. &lt;/p&gt;

&lt;p&gt;Intuitively, we would first explain the situation and then ask people to act. But we are all flooded with messages daily. &lt;/p&gt;

&lt;p&gt;I have recently realized, that I am being paid to read. I read emails, blogs, docs, specs, Slack, Confluence, JIRA, ... &lt;/p&gt;

&lt;p&gt;There is always more to read. The logical consequence is skimming all text. &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;We no longer read, we skim, searching for a glimpse of relevance.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Know your audience.&lt;/strong&gt; Because they will only skim, be relevant and concise. &lt;/p&gt;

&lt;p&gt;What would have been the reaction to the above, shortened email? I guess that 70% of its recipients would skim it and 20% would read the docs.&lt;/p&gt;

&lt;p&gt;Is this it? Have we given it our best? Is this the best possible outcome?&lt;/p&gt;

&lt;p&gt;Of course not. We went at this from the wrong angle. &lt;strong&gt;Sending out an email would always have been futile.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Email has its limitations, but this does not mean we cannot still achieve our goal.&lt;/p&gt;

&lt;h2&gt;
  
  
  Attempt 3: Secret messages, that trigger when somebody moves a lever
&lt;/h2&gt;

&lt;p&gt;First, what do we really want? &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;We want that all future commands are written in the new way.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;We must switch perspectives. We must think like "the other". It's like a marketing ploy: How do I make everybody buy my product?&lt;/p&gt;

&lt;p&gt;Who will need to know about the new way of writing commands?&lt;/p&gt;

&lt;p&gt;Definitely a developer. &lt;/p&gt;

&lt;p&gt;She might be a senior developer, who has been at the company for 10 years, or she might be a junior developer in her 2nd week. It might be somebody from another team or another department, it could be a DevOps or it could be a QA person.&lt;/p&gt;

&lt;p&gt;Maybe you'll know them, maybe you won't. Thus all hints and instructions need to be written down and must be very visible.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What will this developer want?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;They will want to quickly and efficiently write a command. &lt;/p&gt;

&lt;p&gt;They will absolutely NOT want to know the history of your meetings, what who said, what who thought, what day of the week it was, what anybody had for breakfast, none of these things. They will want to quickly and efficiently write a command. &lt;/p&gt;

&lt;p&gt;Their first question might be: are there any existing commands and can I just do the same. &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The will either search the docs or the source. So... you need to fix both paths.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;If you search for "command" in docs, do you find docs about the old approach? Change this. &lt;/p&gt;

&lt;p&gt;The docs need to reference the new approach, delete old docs, if possible, or add big bold "Deprecated" signs.&lt;/p&gt;

&lt;p&gt;If you search for "command" in source, what do you find? &lt;/p&gt;

&lt;p&gt;Is there a folder called &lt;code&gt;commands&lt;/code&gt; in your app, but all the commands inside are done in the deprecated way? Rename the folder. Add "Deprecation" notices to all the old commands' classes. There are tools in many languages, with which you mark deprecated-code. &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Leave breadcrumbs everywhere!&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Archetype
&lt;/h2&gt;

&lt;p&gt;The best documentation follows the same principle. Think about your audience and state the things that will be important to them.&lt;/p&gt;

&lt;p&gt;A while ago I was searching for a Git-history drawing tool. I stumbled upon GitGraphJS and its utterly awesome docs:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2Fl1re6r8e9lcbpfwuyeuk.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Fl1re6r8e9lcbpfwuyeuk.png" alt="Git Graph's amazing docs" width="800" height="422"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The docs are concise, short and cover all the important first questions I had. Once I had set up the library with a simple example, I could look into any details.&lt;/p&gt;

&lt;h2&gt;
  
  
  Infamous Webpack
&lt;/h2&gt;

&lt;p&gt;Webpack is infamously difficult to set up and infamously difficult to understand. What problem is it even solving? &lt;/p&gt;

&lt;p&gt;Here is the top of their current home page. After reading this, I still don't know what Webpack even is, I don't know why I should use it, how it will help me and, honestly, I had to stare at those 4 file examples for a very long time before I understood what they are trying to tell me. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2Fy2b2gpe8gpb2e0cvtgwl.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Fy2b2gpe8gpb2e0cvtgwl.png" alt="Webpack Home Page" width="800" height="1295"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Lots of start-up products lack a clear description of what they do. Their pages talk about improving performance, collaboration, bundling or some other aloft subject. But read as I may, I still can't find a clear answer to: "What does this product actually do?".&lt;/p&gt;

&lt;p&gt;I would need to invest weeks into understanding Webpack, who has that time?! &lt;/p&gt;

&lt;p&gt;Several wrapper-libraries have been created around Webpack. Libraries, because nobody has time for the official docs.&lt;/p&gt;

&lt;p&gt;And if these libraries have found a way to make Webpack easy to set up, why wasn't Webpack able to do the same?&lt;/p&gt;

&lt;p&gt;&lt;a href="https://laravel-mix.com/" rel="noopener noreferrer"&gt;&lt;img src="https://media2.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%2F4zzmxgbqi9hlge72pxv2.png" alt="Laravel Mix" width="800" height="352"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;One of these wrapper libraries is Laravel Mix. To set up and use Laravel Mix, it can be enough to read its 350 words long quick guide. I've tried it, it works! There is more documentation than this, you can go into depth on many topics, but you do not need to and that is the point. &lt;/p&gt;

&lt;p&gt;If Webpack were meant for CS researchers to study it, then its documentation would be just perfect. If it was meant for us, however, then it missed the target. But it did provoke a bunch of memes.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2Fpdnmlw25ke9abvlwrg7t.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Fpdnmlw25ke9abvlwrg7t.png" alt="Webpack meme" width="500" height="415"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Why is Stack Overflow so immensely popular?
&lt;/h2&gt;

&lt;p&gt;Because most documentation is just not good at answering questions.&lt;/p&gt;

&lt;p&gt;When you have a problem, how often do you go check the official documentation and how often do you check random blogs or Stack Overflow? I should trust the official docs of any product more than a random blog post from a random person/bot(?) on the internet. But I can't, because docs so rarely explain the most common use cases. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2F6gk44xl0f95sf5zu0724.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2F6gk44xl0f95sf5zu0724.png" alt="Stack Overflow, FAQ" width="800" height="779"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I would love it if most docs would include a FAQ section. If you are writing some docs right now, please, add a FAQ section. This will be a chance for you to see us: what will we do with your library, how will we use your code, what will be most confusing about your approach, ... .&lt;/p&gt;

&lt;h2&gt;
  
  
  To all future docs authors
&lt;/h2&gt;

&lt;p&gt;To all you, future authors of documentations. &lt;/p&gt;

&lt;p&gt;Please, &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;write a short and a long version of your docs (a tl;dr section is greatly appreciated)&lt;/li&gt;
&lt;li&gt;consider the user experience of your readers&lt;/li&gt;
&lt;li&gt;include a FAQ section and expand it with time.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Thank you! We will repay you with likes and shares and subscribes and also by cloning your approach. &lt;/p&gt;

&lt;p&gt;External sources&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://en.wikipedia.org/wiki/Five_whys" rel="noopener noreferrer"&gt;5 Whys&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://what-problem-does-it-solve.com/webpack/index.html" rel="noopener noreferrer"&gt;Webpack from Nothing&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>programming</category>
      <category>discuss</category>
    </item>
    <item>
      <title>Software Estimation Is A Delusion</title>
      <dc:creator>Ines</dc:creator>
      <pubDate>Thu, 31 Dec 2020 16:25:46 +0000</pubDate>
      <link>https://dev.to/inesp/software-estimation-is-a-delusion-j1p</link>
      <guid>https://dev.to/inesp/software-estimation-is-a-delusion-j1p</guid>
      <description>&lt;p&gt;You will never be able to accurately estimate the time and effort it will take you to build a piece of software .. for as long as you keep doing new things. As long as your projects don't resemble each other as your signatures do, you will not know how much effort it will take to finish a new project. A lot has been said and written about software estimation, but one thing is clear: predictions of future effort are always based on the amounts of past effort.&lt;/p&gt;

&lt;p&gt;2 distinct approaches exist for measuring the future time and effort of software development:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;asking a human expert for her opinion or&lt;/li&gt;
&lt;li&gt;building a mathematical model and condensing the requirements of the software project to only the relevant parameters&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The approaches might seem radically different, but the 2nd one is just trying to create a formal model of the 1st one. It is trying to cut out the human component of software estimation. It is trying to reduce an expert's decision process into a relatively simple and highly portable mathematical formula. And while it is doing this it is also often pretending to be searching for a law of nature, for the natural relationship between software building and time, which is independent of the organization or the customer that the project is built for or by.&lt;/p&gt;

&lt;p&gt;However, the best mathematical models for software estimations are those that are highly calibrated to the team they are measuring. By "calibrated" I mean that the function for calculating the required effort is fitted to data produced by this team in past projects. And so we are back to experiences. If you have done this project before, then you will be able to estimate accurately how much effort it will take to do it again.&lt;/p&gt;

&lt;p&gt;But of course, life is a moving target. As you do the same thing again and again, you get better and faster at it. As the world around you changes: new versions of OSs, new browsers, new programming-language versions, ... you have to change too, which makes you slower again. As you lose or gain members on your project, the effort and time needed are also changed. And I haven't even begun listing any big changes: like having a harassing customer, getting a disruptive team member, having to comply with a new law, ... .&lt;/p&gt;

&lt;p&gt;It all boils down to whether or not you see the world as ever-unpredictable or ever-predictable. If it is ever predictable, then software estimation is perfectly possible and also works perfectly brilliantly. If, however, you see the world as ever-changing or you yourself are inclined to take on ever-changing projects, then any time or effort estimations are useless and deceiving. Because: nobody has yet come up with a prediction system that is not based on existing past data.&lt;/p&gt;

&lt;h2&gt;
  
  
  Is it even important?
&lt;/h2&gt;

&lt;p&gt;Why are people even asking this question: "How long will it take to create this piece of software"? Because they don't have the resources to work on this forever. And this is understandable.&lt;/p&gt;

&lt;p&gt;But there is a secret message in that question as well. It is something that we understand so very, very well that we don't even think about it. The proposed piece of software doesn't need to be so complicated. It could be much simpler and still do its job. And simpler software will take fewer resources to produce. But people don't want the simple software, they want the most that they can get for their budget.&lt;/p&gt;

&lt;p&gt;The true question isn't: &lt;em&gt;How long will this take?&lt;/em&gt;, but &lt;em&gt;What can you build for this much money?&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;2 types of people ask this question: managers and customers. These 2 groups are pretty different, we communicate with them differently. But the core answer to say to both of them is something along the lines of: &lt;em&gt;"Let's build it iteratively"&lt;/em&gt;. Let's first organize the requirements by priority: what is core functionality and what is optional. Let's then build first the foundations and then the core functionality. And if we will then still have time and money left, let's continue. And if we will then still have time and money left, let's continue. And if we will then still have time and money left, ...&lt;/p&gt;

&lt;p&gt;For instance, let's say I am a freelancer creating simple web pages. A customer asks me to create their first web page, a simple presentation of who they are and what they do. They might come with a well-defined idea of what they like, with all kinds of details and requirements and a 100-page description document. And they want to know how much this will cost and how long it will take. For me, this is nearly impossible to estimate. But let's say I still give them a number, which might be too big for them. So they might continue: "What about if we take these 20 pages out. How much does it cost then?". This doesn't make the job of estimating any easier. Even if I know exactly how much time it saves me to ignore those 20 pages, my estimation of the full 100 pages was wonky. So.. &lt;code&gt;wonky - 20 = still wonky&lt;/code&gt;. It will be much easier to start with a simpler page that can stand by itself, preferably one similar to something I've already built. After that, we can add more fancy functionalities if we will still want to.&lt;/p&gt;

&lt;p&gt;And what if the task cannot be split up? Because such things exist too: we either switch to a new system or we don't.&lt;/p&gt;

&lt;p&gt;The answer is simple but unpopular: We don't know how long something big that has not been done before will take. We have no experience doing it. But here is a list of things we will gain and here is a list of risks we will take. And one of the risks is also that all this time and effort will go to waste. If this project is important enough, let's allocate a few resources to it so that somebody can start working on it. If their work goes well, then let's continue, if it will still go well, then let's continue, if it will still go well, then let's continue gradually increasing that somebody's resources. But it could also not go well, in which case, we have to be prepared to stop work on this project and forget about it. Just because we've already invested 20 person-months into this project, doesn't necessarily mean it makes sense to finish it. Maybe it will be better to drop it. And maybe it will all go very well and we'll soon reap the rewards of starting this project.&lt;/p&gt;

&lt;p&gt;The importance of this question is questionable. If you are asking your developers how long something will take because you will secretly send this number over to the customer as a binding offer, then stop doing it. Your developers are not freelancers, they don't need to haggle with your customers over how much they pay you. If you are asking your developers for numbers to report to your superiors, then only ask for estimates of projects that are similar to existing projects they did. If you ask for estimates for projects they have no experience with, you will get made-up numbers, that you will have trouble defending later on. If you are asked this question by a customer and you are the one, who has to come up with answers to it, then try to split up the project into sub-projects that are similar to your experience.&lt;/p&gt;

&lt;p&gt;There is no point in giving estimations that you can't stick to with a reasonable error margin.&lt;/p&gt;

&lt;p&gt;But what if your projects are similar and you want to give estimation a go, what are your options?&lt;/p&gt;

&lt;h2&gt;
  
  
  What are your options for estimation?
&lt;/h2&gt;

&lt;p&gt;All estimates usually take 2 things into account:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the size and complexity of the project&lt;/li&gt;
&lt;li&gt;the resources provided to the project - the team&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Unfortunately, none of these attributes is easy to assess. It would be great if I could say: "I have 4 developers and a project of size 75200. How long will it take to build the project?". But, what is &lt;em&gt;4&lt;/em&gt; and what is &lt;em&gt;75200&lt;/em&gt;? Which leaves you with 2 choices: either you spend a looong time putting the &lt;em&gt;4&lt;/em&gt; and the &lt;em&gt;75200&lt;/em&gt; into some kind of perspective, some kind of mathematical function, a prediction machine or you ask a human to feel it, to look at it and then say a number, from the gut, "What is your gut telling you?". Yes, these are your options. You can rely on a human or on a machine, each comes with a set of advantages and disadvantages.&lt;/p&gt;

&lt;p&gt;There have been studies comparing which is better: human or machine. The results are ... depends on who you ask. Sometimes it is the models that are better other times it is the humans.&lt;/p&gt;

&lt;h3&gt;
  
  
  Human Expert
&lt;/h3&gt;

&lt;p&gt;An expert would be someone, who knows how to build your project and has done it before and also knows the team. It is the complexity of the project in relation to the capabilities of the team that define the effort needed to finish the project. This is a difficult person to find.&lt;/p&gt;

&lt;p&gt;It has been known since the 70s that developers tend to give very optimistic estimations. Nothing has changed since then.&lt;/p&gt;

&lt;p&gt;One of the reasons is that developers have to give lots of estimates, but then rarely track the actual effort that went into the tasks. And if they don't track, then they can't review the initial estimate after the project is done.&lt;/p&gt;

&lt;p&gt;Another is, that developers often estimate by breaking up the task into smaller tasks, estimate the sub-tasks and then just sum up the numbers. But this way inevitably misses some tasks, while giving the estimator a really good feeling of having thought of everything.&lt;/p&gt;

&lt;p&gt;It is nearly impossible to come up with a complete list of every sub-task that will need to be done. We also tend to forget to include any unexpected problems. Problems we will only understand once we are staring them down.&lt;/p&gt;

&lt;p&gt;The researchers M. Jørgensen, K. Teigen, K. J. Moløkken-Østvold did a marvelous study of how developers badly under-estimate the required effort titled &lt;em&gt;Better Sure Than Safe? Overconfidence in Judgment Based Software Development Effort Prediction Intervals&lt;/em&gt;. They observed how estimates are asked for, but then rarely reviewed after the project:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;We investigated more than 100 projects from 6 development organizations and found that about 30% of the projects had applied effort prediction intervals on the activity level. However, only 3 of these projects had logged the actual effort applying the same work break-down structure as used when estimating the effort. In fact, even these three projects had to split and combine some of the logged effort data to enable an analysis of the accuracy of the effort of PIs (tn: PI = prediction interval, a minimum - maximum effort).&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;They also created 5 teams of developers and asked them to estimate the effort needed for a project. The project was an actual past project of the company they worked for. The original estimation for the project was 1240 hours, but the project took 2400 hours to be finished. The teams produced the following values as the Estimate of the most likely effort: 1100, 1500, 1550, 1339, 2251. Just one of the teams came close to the actual number of hours spent, the other 4 had incredibly optimistic numbers.&lt;/p&gt;

&lt;p&gt;Another interesting part of the experiment was that &lt;strong&gt;the worse estimations were done by developers and project managers&lt;/strong&gt;. (So, please, project managers, stop pressuring the developers with statements like: "How can this take more than 1 day to create?". Such statements are just pure ignorance 🙄 )&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The lowest effort estimates were provided by the developers and the project managers, whereas the user interaction designers and the engagement managers gave generally higher estimates.&lt;br&gt;&lt;br&gt;
...&lt;br&gt;&lt;br&gt;
As a result, technical background did not lead to better effort PIs (tn: PI = prediction interval, a minimum - maximum effort), only to more confidence in the estimates. This is in line with the distinction between an "inside" versus an "outside" view in predictions (Kahneman and Lovallo 1993).&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;So.. what did Kahneman and Lovallo say about inside and outside predictions? Here is an excerpt of their paper &lt;em&gt;Timid Choices and Bold Forecasts: A Cognitive Perspective on Risk Taking&lt;/em&gt;:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;An inside view forecast is generated by focusing on the case at hand, by considering the plan and the obstacles to its completion, by constructing scenarios of future progress, and by extrapolating current trends. The outside view is the one that the curriculum expert was encouraged to adopt. It essentially ignores the details of the case at hand, and involves no attempt at detailed forecasting of the future  history of the project. Instead, it focuses on the statistics of a class of cases chosen to be similar in relevant respects to the present one.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Another powerful reason for over-optimistic developer-estimates I would like to emphasize is the social component of estimation. When a developer is asked to give an estimate, 2 social constraints guide her estimate. Firstly, the estimate cannot be huge, because that effectively stops the project immediately. And secondly, she cannot come up with a very wide estimate. An estimate is wide when the expected min and max values are far apart. Providing a wide estimate or an unexpectedly big one is considered the same as providing a useless estimate.&lt;/p&gt;

&lt;p&gt;I still very vividly remember an event in my past. I was the head of a delicate effort to replace a patched-up version of something that was very basic to our app with a new, cleaner and more complete implementation. It was a tricky job, we didn't know what we were getting into simply because this project wasn't similar to any other project we did. But it was important. We couldn't live with the old implementation anymore and we had finally convinced the management to give us resources to replace it. I didn't give any estimate of the project at the beginning, but at some point during the project, I was convinced that I do have to provide some estimate. "It doesn't have to be correct", I was told, but I do have to give a number. And so I sat down, opened the form and picked a date 6 months into the future. My manager was sitting next to me and said something like: "What?! No, we can't put that in.". But I knew that 6 months was a perfectly optimistic number. Our app was huge and a lot of work will need to be done. And so, I and my manager looked at each other. We knew that this project needs to be done, the engineering department is convinced of it, but the management is difficult to persuade. We've already come very far, the finish line is very close. The +6-months number will stir up a lot of anxiety, but the project still needs to be done, it will be great, once it is done and anxiety is not helping anyone. And so we said: "Could we do it in 4 months?", "If we skip a few things and smash up a few others, maybe..probably..", "Ok, let's put +3 months then".&lt;/p&gt;

&lt;p&gt;M. Jørgensen, K. Teigen and K. J. Moløkken-Østvold saw something similar in their research. Developers admitted that they believe their managers will see them as less competent if they provide estimates with huge margins. To test this thinking they created an experiment in which they asked 37 software professionals to evaluate some hypothetical project estimations. All 37 participants had some experience with project management. They were presented with estimates for 5 hypothetical tasks given by 2 hypothetical developers: D1 and D2. Both developers gave the same "Most likely effort", but different "Estimated min" and "Estimated max". D1 would give a narrow min-max interval, while D2 would give a much wider min-max interval. The participants were also provided the actual effort needed for each task.&lt;/p&gt;

&lt;p&gt;Mathematically speaking providing a wider min-max interval means you will be right more often. D2's estimates were correct for 4 tasks out of 5, whereas D1 was right only 3 times.&lt;/p&gt;

&lt;p&gt;The participants were then asked 3 questions. Here is how they answered the first 2:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;em&gt;"As a project manager, would you prefer the estimates of D1 or D2?"&lt;/em&gt; - 35 out of 37 participants prefer D1, even though D1's estimates are objectively less accurate than D2's. The other 2 choose D2.&lt;/li&gt;
&lt;li&gt;
&lt;em&gt;"Do you believe D1 or D2 knew more about how to solve the development tasks?"&lt;/em&gt; - 29 participants choose D1, 1 participant choose D2.&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;The preference for D1 in the (a)-question means that nearly all the software professionals preferred the much too narrow intervals of D1 to the statistically more appropriate intervals of D2!&lt;br&gt;&lt;br&gt;
...&lt;br&gt;&lt;br&gt;
Answers to question (b) revealed that wider intervals were believed to indicate less task knowledge.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;From all this, we can say that a lot goes into producing an estimate. It is not just the number of resources that need to be reserved. And for some reason, we prefer to have exact numbers, even if that means they are wrong most of the time. We want to have certainty, the feeling that somebody knows, what is going on, somebody else has things under control. Or at least somebody should pretend to have things under control and if they slip out of control, this somebody should take care of taking back control over them.&lt;/p&gt;

&lt;p&gt;We are definitely not rational beings, but emotional ones :)&lt;/p&gt;

&lt;h2&gt;
  
  
  Mathematical models
&lt;/h2&gt;

&lt;p&gt;There is an incredible variety of mathematical models available:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;regression-based approaches: That is when you want to create a formula (ie. $ Y = ? * X ^ ? + ? $) that expresses the relationship between a dependent variable ($Y$), also called the "outcome variable" and one or more independent variables ($X$).&lt;/li&gt;
&lt;li&gt;classification and regression trees: That is when you want to create a decision tree where each node represents a question and the node's branches represent possible answers to the question. Traversing through the tree eventually leads us to a leaf, which represents the outcome, our prediction. If the leaves represent a class (a discreet value), then the tree is called a classification tree. If the leaves can take any number in a continuum, then the tree is called a regression tree.&lt;/li&gt;
&lt;li&gt;neural networks: That is when you create a huge, directed and weighted graph, and teach it to return a specific output given a specific input. And then feed it new input data and use its output as predictions of the future. The internal workings of this system become completely unknown to you, they become a black box of decision making.&lt;/li&gt;
&lt;li&gt;Bayesian statistics: That is when you calculate the probability of an event happening given some prior knowledge of other conditions that might be relevant. $ P(A|B) = \frac{P(B|A) P(A)}{P(B)} $&lt;/li&gt;
&lt;li&gt;lexical analyses of requirement specifications: That is when you analyze the words used in the documents describing the new software.&lt;/li&gt;
&lt;li&gt;case-based reasoning&lt;/li&gt;
&lt;li&gt;fuzzy logic modeling&lt;/li&gt;
&lt;li&gt;simulation based probability&lt;/li&gt;
&lt;li&gt;...&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;All of them have been studied by researchers, but I could find no data about how often a mathematical approach is used in software companies. Even worse, I did find this 2007 review of existing studies(A Systematic Review of Software Development Cost Estimation Studies by M.Jørgensen and M.J.Shepperd), which found very few case studies of how estimation is actually performed in companies:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The proportion of estimation studies where estimation methods are studied or evaluated in real-life situations is low. We could not, for example, find a single study on how software companies actually use formal estimation models. Our knowledge of the performance of formal estimation models is, therefore, limited to laboratory settings&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;It has been shown that a model that is better fitted to the organization using it will produce better estimates. (By C.S.Murali and C.Sankar in "Issues in estimating real-time data communications software projects" in 1997 and R.Jeffery, M.Ruhe and I.Wieczorek in "Comparative study of two software development cost modeling techniques using multi-organizational and company-specific data" in 2000.) But this again means that your organization must be producing similar projects with similar people, that you have to first meticulously track the requirements and the effort invested into several projects and that you must be careful to not create a model that is over-fitted to your data.&lt;/p&gt;

&lt;h3&gt;
  
  
  Estimation variables
&lt;/h3&gt;

&lt;p&gt;Producing a good model for your organization may legitimately be too complex or too time-consuming. The biggest issue is how to get all the contextual information that is currently stored in the heads of your team  members into the model. Which information is even relative?&lt;/p&gt;

&lt;p&gt;Given that there is no one estimation method that had been proven best, it seems safe to say that it is not the method itself that produces accurate results, but what information you feed it with. Figuring out which information influences produces the most accurate result is a difficult job and it is made even worse by the fact that all your numbers are fabricated. You don't know how big a project will be once it is finished: how many lines of code you will create, how many tests, how many files. Even if you have finished a very similar project, you still don't know how much more time you will need to add that 1 "tiny" change that is in the requirements.&lt;/p&gt;

&lt;p&gt;In 1994 N. Fenton published an elegant paper titled &lt;em&gt;"Software Measurement: A Necessary Scientific Basis"&lt;/em&gt;, in which he emphasized that we have to define metrics in an empirical way. A metric has to be defined together with the procedure for determining the value. It is not enough to say that a project's size can be small, medium or big. We must also define how to measure the project's size. Do we look for the number of files, the number of lines, is 1000 lines a small project, is it medium? This is especially important for predictions. Some mathematical models still exist that need the LOC (=lines of code) number to predict the time and effort for a software project. But it is impossible to know the LOC of a future project, so we are replacing one guess with another:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;..  size is defined as the number of delivered source statements (tn: LOC), which is an attribute of the final implemented system. Since the prediction system is used at the specification phase, we have to predict the product attribute size in order to plug it into the model. This means that we are replacing one difficult prediction problem (effort prediction) with another prediction problem which may be no easier (size prediction).&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;A way to estimate the size and complexity of a project is via function points (=FPs), which express the amount of functionality a program provides to its users. Once 1 FP is defined, it is theoretically easy to count all FPs a project will create by reading through the requirements. The time it takes to deliver 1 FP is calculated from previous projects. The theory has been developed into a few ISO standards, but it has also seen a lot of criticism and quite a few improvement attempts. Whether it will work for you or not is ... unknown 😛&lt;/p&gt;

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

&lt;p&gt;It is easy to get pulled into the details of &lt;em&gt;how&lt;/em&gt; these approaches work and completely forget that we haven't yet proven that they even &lt;em&gt;will&lt;/em&gt; work, that they will give you an accurate number. The research does not favor one approach over the others.&lt;/p&gt;

&lt;p&gt;B.Boehm and C. Abts say in &lt;em&gt;Software Development Cost Estimation Approaches – A Survey&lt;/em&gt;:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The important lesson to take from this paper is that no one method or model should be preferred over all others. The key to arriving at sound estimates is to use a variety of methods and tools and then investigating the reasons why the estimates provided by one might differ significantly from those provided by another.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;But as we've seen, maybe it isn't really accuracy that businesses and people are looking for. Maybe estimates are needed for the sole purpose of risk aversion. People what to know what the risks of starting this project are. But risk can be measured in other ways.&lt;/p&gt;

&lt;p&gt;At my current stage in life, I find that everybody involved in a project is much happier if we go through the project in iterations. We don't have to decide everything at the beginning of a project. We should first concentrate on the essential bits. Things that can be done in a few days, a week at most and review the result. It is much less risky to see somebody spend a few days on a project and then review the results than it is to wait for a few months. An amusing consequence of this approach is that the developer will then also first concentrate on the essential bits. Maybe the first goal will be a proof of concept, something ugly and hacky, but something that works. Or maybe the first goal will be bits and pieces of code and in-depth research of the requirements and limitations and then comes the proof of concept. Either way, we will be kept in the loop on the progress. And being kept in the loop is what makes the risk lesser.&lt;/p&gt;

&lt;p&gt;Maybe it is time we stop estimating tasks left and right and instead start managing the project's risk and customer's expectations.&lt;/p&gt;

&lt;h3&gt;
  
  
  External sources:
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.ipd.kit.edu/mitarbeiter/padberg/lehre/sqs07/FentonTSE1994.pdf"&gt;N. Fenton: Software measurement: a necessary scientific basis&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.simula.no/publications/estimation-software-development-work-effortevidence-expert-judgment-and-formal-models"&gt;M. Jørgensen: Estimation of Software Development Work Effort:Evidence on Expert Judgment and Formal Models&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.simula.no/publications/impact-irrelevant-and-misleading-information-software-development-effort-estimates"&gt;M. Jørgensen, S. Grimstad: The Impact of Irrelevant and Misleading Information on Software Development Effort Estimates: a Randomized Controlled Field Experiment&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="http://shape-of-code.coding-guidelines.com/2016/05/19/cocomo-how-not-to-fit-a-model-to-data/"&gt;COCOMO: Not worth serious attention&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.simula.no/publications/systematic-review-software-development-cost-estimation-studies"&gt;M. Jørgensen, M. Shepperd: Systematic Review of Software Development Cost Estimation Studies&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a&gt;M. Jørgensen, K. Teigen, K. J. Moløkken-Østvold: Better Sure Than Safe? Overconfidence in Judgment Based Software Development Effort Prediction&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a&gt;S. Basha, P.Dhavachelvan: Analysis of Empirical Software Effort Estimation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.tutorialspoint.com/estimation_techniques/estimation_techniques_function_points.htm"&gt;Estimation Techniques - Function Points&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.researchgate.net/publication/330960812_Timid_Choices_and_Bold_Forecasts_A_Cognitive_Perspective_on_Risk_Taking"&gt;Timid Choices and Bold Forecasts: A Cognitive Perspective on Risk Taking&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www2.seas.gwu.edu/~mlancast/cs254/usccse2000-505.pdf"&gt;B.Boehm, C.Abts: Software Development Cost Estimation Approaches – A Survey&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://en.wikipedia.org/wiki/Regression_analysis"&gt;Wikipedia: Regression analysis&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://en.wikipedia.org/wiki/Decision_tree_learning"&gt;Wikipedia: Decision tree learning&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://en.wikipedia.org/wiki/Artificial_neural_network"&gt;Wikipedia: Artificial neural network&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://en.wikipedia.org/wiki/Bayes%27_theorem"&gt;Wikipedia: Bayes' theorem&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>programming</category>
      <category>productivity</category>
      <category>leadership</category>
    </item>
    <item>
      <title>Celery: A few gotchas explained</title>
      <dc:creator>Ines</dc:creator>
      <pubDate>Mon, 02 Nov 2020 08:22:13 +0000</pubDate>
      <link>https://dev.to/inesp/celery-a-few-gotchas-explained-408k</link>
      <guid>https://dev.to/inesp/celery-a-few-gotchas-explained-408k</guid>
      <description>&lt;p&gt;Have you ever heard of the continuum of theory-before-practice VS. practice-before-theory? Probably not, since I created the name just now 😏. But, though the name is new, the continuum is old. The question is simple: should I first study, study, study the documentation and then only after I presumably fully understand the library and its logic start using it in my code, or should I first dive into it, use it and abuse it before going back and reading the documentation of it.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--zWp_3VhM--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/4kxxc84j56b9xyvd161p.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--zWp_3VhM--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/4kxxc84j56b9xyvd161p.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;


&lt;center&gt;&lt;small&gt;&lt;a href="https://www.freepik.com/vectors/children"&gt;Children vector created by freepik - www.freepik.com&lt;/a&gt;&lt;/small&gt;&lt;/center&gt;

&lt;p&gt;We all float in the continuum, none of us is stationary. Life-events nudge us to the left and to the right and sometimes fiercely sling us into one of the extremes as if we were pink-pong balls. Often we only want to study as much as is absolutely needed, because we equate Practice with joy and Theory with tediousness. And we are right to a degree: how much of a foreign language can you remember if you don't use it regularly. But then, sometimes, it turns out that we badly underestimate how much theory is "absolutely needed". And we have to go back, just like I had to go back to figure out Celery. My strategy of broadly getting it was only broadly enough. Now I had to go back and read all the Theory.&lt;/p&gt;

&lt;p&gt;Celery is actually full of gotcha-s. Partly because we are dealing with processes, concurrencies, threads, .. and most of the time such details are abstracted away and a developer doesn't need to think about them and thus has little experience with them. And partly because Celery does here and there perform in unexpected ways. Some documentation-reading is definitely required.&lt;/p&gt;

&lt;p&gt;So, here it is, all kinds of basic and advanced concepts around Celery.&lt;/p&gt;
&lt;h2&gt;
  
  
  Workers and brokers
&lt;/h2&gt;

&lt;p&gt;First, let me explain some basic concepts, under which Celery operates.&lt;/p&gt;

&lt;p&gt;Celery is a &lt;strong&gt;"Task Queue"&lt;/strong&gt;. Yeah, I also didn't know this was an actual term, I just thought it was a description of what it is: a queue of tasks that will eventually be executed (Maybe our Slovene universities should start using English terms after all. I don't want us to sacrifice regional languages, but doctors also have to understand the Latin terminology. And English has become our de facto Latin, for better or for worse. It is just embarrassing to not know basic English terms after 5 years of study and 10 years of work...). So, Celery is essentially a program that &lt;strong&gt;keeps track of tasks&lt;/strong&gt; that need to be run and &lt;strong&gt;keeps a group of workers&lt;/strong&gt;, which will execute the tasks. Its main points are that it can execute several tasks &lt;strong&gt;in parallel&lt;/strong&gt; and that it is &lt;strong&gt;not blocking&lt;/strong&gt; the independent applications(=&lt;strong&gt;Producers&lt;/strong&gt;), which are giving it tasks.&lt;/p&gt;

&lt;p&gt;But, Celery doesn't actually store the queue of tasks in its memory. It needs somebody else to store the tasks, it needs a &lt;strong&gt;Message Broker&lt;/strong&gt; (or Broker for short), which is a fancy term for a program that can store and handle a queue 🙃. These are usually either Redis or RabbitMQ. So, Celery understands and controls the queue, but the queue is stored inside Redis/RabbitMQ.&lt;/p&gt;

&lt;p&gt;On to the workers..&lt;/p&gt;

&lt;p&gt;When you start Celery (&lt;code&gt;celery -A tasks worker&lt;/code&gt;) 1 worker is created. This worker is actually a supervisor process that will spawn child-processes or threads which will execute the tasks. By default, the worker will create child-processes, not threads, and it will create as many concurrent child-processes as there are CPUs on the machine. The supervisor process will keep tabs on what is going on with the tasks and the processes/threads, but it will not run the tasks itself. This group of child-processes or threads, which is waiting for tasks to execute, is called &lt;strong&gt;an execution pool&lt;/strong&gt; or &lt;strong&gt;a thread pool&lt;/strong&gt;.&lt;/p&gt;
&lt;h2&gt;
  
  
  Queues
&lt;/h2&gt;

&lt;p&gt;Yes, I deliberately used the plural for queues, because there is more than one type of queue 🧙🏽‍⚗️.&lt;/p&gt;

&lt;p&gt;First, there is the main queue, which accepts tasks from the producers as they come in and passes them on to workers as the workers ask for them. By default, you have only 1 such queue. All workers take tasks from the same queue. But you can also specify a few such queues and limit specific workers to specific queues. The default queue is called &lt;a href="https://docs.celeryproject.org/en/stable/userguide/configuration.html#task-default-queue"&gt;&lt;code&gt;celery&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;To see the first 100 tasks in the queue in Redis, run:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;redis-cli lrange celery 0 100
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;These queues are more or less, but not precisely FIFO (if the priority of all tasks is the same). The tasks that are put into the queue first, get taken out of the queue first, BUT they are not necessarily executed first. When workers fetch new tasks from the queue, they usually (and by default) do not fetch only as many tasks as they have processes, they fetch more. By default, they fetch &lt;a href="https://docs.celeryproject.org/en/stable/userguide/configuration.html#std-setting-worker_prefetch_multiplier"&gt;4 times as many as they have processed&lt;/a&gt;. They do this because it saves them time. Communicating with the broker takes some time and if the tasks that need to be run are quick to execute, then the workers will ask for more tasks again and again and again in very quick successions. To avoid this, they ask for X-times as many tasks as they have processes (=&lt;code&gt;worker_prefetch_multiplier&lt;/code&gt;).&lt;/p&gt;

&lt;p&gt;But there are tasks that never make it into the queue and still get executed by the workers. How is that possible, you ask me? I was asking myself and google the very same question. And let me tell you, Google had very little to say about it. I found just scraps of information. But taking Celery and Redis apart for a few hours (or was it days??), here is what I discovered.&lt;/p&gt;

&lt;p&gt;Tasks with an ETA are never put into the main queue. They are put directly into the half-queue-half-list of "unacknowledged tasks", which they named &lt;code&gt;unacked&lt;/code&gt;. And I do agree that "unacknowledged" is a very long word with a good amount of silenced letters sprinkled in, but it is very easy to miss something named &lt;code&gt;unacked&lt;/code&gt; when you are trying to understand how some tasks have just disappeared. So, a note for next time I or you need to name something: all user-facing names should be spelled out completely.&lt;/p&gt;

&lt;p&gt;So what are ETA tasks? They are scheduled tasks. ETA stands for "estimated time of arrival". All tasks that have ETA or Countdown specified (ie. &lt;code&gt;my_task.apply_async((1, 2), countdown=3)&lt;/code&gt;, &lt;code&gt;my_task.apply_async((1, 2), eta=tomorrow_datetime)&lt;/code&gt;) are kept in this other type of queue-list. This also includes &lt;strong&gt;all task retries&lt;/strong&gt;, because when a task is retried, it is retried after a specific number of seconds, which means it has an ETA.&lt;/p&gt;

&lt;p&gt;To see which tasks are in the ETA-queue in Redis, run:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;redis-cli HGETAL unacked
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You will get a list of keys and their values alternating, like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;1) "46165d9f-cf45-4a75-ace1-44443337e000"
2) "[{\"body\": \"W1swXSwge30sIHsiY2FsbGJhY2tzIjogbnVsbCwgImVycmJhY2tzIj\", \"content-encoding\": \"utf-8\", \"content-type\": \"application/json\", \"priority\": 0, \"body_encoding\": ...
3) "d91e8c77-25c0-497f-9969-0ccce000c6667"
4) "[{\"body\": \"W1s0XSwge30sIHsiY2FsbGJhY2tzIjogbnVsbCwgI\", \"content-encoding\": \"utf-8\", ...
...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Tasks
&lt;/h2&gt;

&lt;p&gt;Tasks are sometimes also called messages. At its core, the message broker is just something that passes messages from one system to another. In our case, the message is a description of tasks: the task: the task name (a unique identifier), the input parameters, the ETA, the number of retries, ... .&lt;/p&gt;

&lt;p&gt;In Celery the task is actually a class. So every time you decorate a function to make it a task, a class is created in the background. This means that each task has a &lt;code&gt;self&lt;/code&gt;, unto which a lot of things are appended (i.e. &lt;code&gt;name&lt;/code&gt;, &lt;code&gt;request&lt;/code&gt;, &lt;code&gt;status&lt;/code&gt;, &lt;code&gt;priority&lt;/code&gt;, &lt;code&gt;retries&lt;/code&gt;, &lt;a href="https://github.com/celery/celery/blob/8c5e9888ae10288ae1b2113bdce6a4a41c47354b/celery/events/state.py#L247-L264"&gt;and more&lt;/a&gt;). Sometimes we need access to these properties. In those cases we use &lt;code&gt;bind=True&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="o"&gt;@&lt;/span&gt;&lt;span class="n"&gt;shared_tas&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;bind&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,...)&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;_send_one_email&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;email_type&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;user_id&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="p"&gt;...&lt;/span&gt;
    &lt;span class="n"&gt;num_of_retries&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;retries&lt;/span&gt;
    &lt;span class="p"&gt;...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Task acknowledgment
&lt;/h2&gt;

&lt;p&gt;Previously we said that when workers are free, they go and fetch some more tasks from the broker. But it is a bit more nuanced. When a worker "takes" a task, the task moved from the main queue to the &lt;code&gt;unacked&lt;/code&gt; queue-list. The task is completely removed from the broker only once the worker acknowledges it. &lt;strong&gt;This means that when the worker "prefetches" a number of tasks, what really happens is that those tasks are only marked as his (reserved). They are put into the unacked queue, so other workers won't take them.&lt;/strong&gt; If the worker dies, then those tasks are made available to other workers.&lt;/p&gt;

&lt;p&gt;So, when does a worker acknowledge a task? By default Celery assumes that it is dangerous to run tasks more than once, consequently it acknowledges tasks just before they are executed. You can change this by setting the famous &lt;a href="https://docs.celeryproject.org/en/stable/userguide/tasks.html#Task.acks_late"&gt;&lt;code&gt;acks_late&lt;/code&gt;&lt;/a&gt;. In this case, a task has the slight possibility of being run more than once, if the worker running it dies in the middle of the execution. And with "dies", I literally mean die. A Python exception in the task code will not kill the worker. Such a task will still be acknowledged, but its state will be set to &lt;code&gt;FAILURE&lt;/code&gt;. Something has to happen so that the worker never reaches the code &lt;a href="https://github.com/celery/celery/blob/8c5e9888ae10288ae1b2113bdce6a4a41c47354b/celery/worker/request.py#L373"&gt;&lt;code&gt;self.acknowledge()&lt;/code&gt;&lt;/a&gt;. And this is rare. For this reason, I suspect that setting &lt;code&gt;acks_late&lt;/code&gt; or not setting it has little bearing.&lt;/p&gt;

&lt;h2&gt;
  
  
  ETA
&lt;/h2&gt;

&lt;p&gt;As I already mentioned ETA tasks are ... hard to find. They never make it to the main queue. They are immediately assigned to a worker and put into the &lt;code&gt;unacked&lt;/code&gt; queue. I suspect that it was not intentional that the ETA tasks immediately get &lt;strong&gt;assigned to a specific worker&lt;/strong&gt;. I suspect this was just a consequence of the existing code. An ETA task can't go into a general queue, which works almost as FIFO. The only other place is among the unacknowledged tasks in which case it needs to be reserved by one worker.&lt;/p&gt;

&lt;p&gt;Interestingly, the ETA time is not the exact time this task will run. &lt;strong&gt;Instead, this is the earliest time this task will run.&lt;/strong&gt; Once the ETA time comes around, the task must wait for the worker to be free.&lt;/p&gt;

&lt;h2&gt;
  
  
  Retry Tasks
&lt;/h2&gt;

&lt;p&gt;Celery doesn't perform any retry logic by default. Mostly because it assumes that tasks are not idempotent, that it is not safe to run more than once. Retrying a task does, however, have full support in Celery, but it has to be set up explicitly and separately for every task.&lt;/p&gt;

&lt;p&gt;One way of triggering a retry is by calling &lt;code&gt;self.retry()&lt;/code&gt; in a task. What happens after this is triggered? An ETA time is calculated, some new metadata is put together and then the task is sent to the broker, where it falls into the &lt;code&gt;unacked&lt;/code&gt; queue and is assigned to the same worker that already ran this task. This is how retry-tasks become ETA tasks and are therefore never seen in the main broker queue. It is a very sleek, but unexpected system. And again, Google has very little to say about this.&lt;/p&gt;

&lt;p&gt;Learn more about retries in my &lt;a href="http://www.ines-panker.com/2020/10/29/retry-celery-tasks.html"&gt;Celery task retry guide&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  CPU bound or I/O bound and processes vs threads
&lt;/h2&gt;

&lt;p&gt;As we already said, by default Celery executes tasks in separate processes, not threads. But you can make it switch to threads, by starting the workers with either &lt;code&gt;--poll eventlet&lt;/code&gt; or &lt;code&gt;--pool gevent&lt;/code&gt;. Both eventlet and gevent actually create greenlets, not threads. Greenlets (or green threads) are thread-like, but they are not threads, because by definition threads are controlled by the OS. Greenlets do not rely on the OS to provide thread support, instead, they emulate multithreading. They are managed in application space and not in OS space. There is no pre-emptive switching between the threads at any given moment. Instead, the greenlets voluntarily or explicitly give up control to one another at specified points in your code.&lt;/p&gt;

&lt;p&gt;If your tasks are heavy on CPU usage: if they do a lot of computing (=are CPU bound), then you should keep using processes. If, on the other hand, your tasks are mostly doing HTTP requests (=I/O bound), then you can profit from using threads. The reason for this is that while your task is waiting for the HTTP request to return a result, it is not doing anything, it is not using the CPU and would thus not mind if another thread would make use of it.&lt;/p&gt;

&lt;h2&gt;
  
  
  There is a lot more to Celery
&lt;/h2&gt;

&lt;p&gt;and the documentation is not perfect. Many features have their description split up and dotted around the web page. It is difficult to find details of the implementation. But it is also a complicated subject matter. I don't know how Celery will behave outside of the few scenarios I have literally created and experimented on. Sure, after a few years of intensive work I might have a good understanding of how it works, but Celery lives on the fringes of my day to day. I set it up, but then it disappears into async-land. It behaves radically different when on the server and when on my computer. I can see which tasks were done, but I can't see how well they were done. Transparency is very difficult with something that runs in parallel, possibly in threads and semi-independent of the application. I don't trust it, I don't trust that I understand its settings correctly or I don't trust that I know how to set them correctly. Celery is like a spirit, it comes and goes, sometimes it breaks, but most of the time, it just works. Hopefully, it works on the tasks we assigned it, but if that is not the case, it will be equally silent.&lt;/p&gt;

&lt;h4&gt;
  
  
  External Sources:
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://docs.celeryproject.org/en/stable/index.html"&gt;Celery docs&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.celeryproject.org/en/stable/faq.html#faq-acks-late-vs-retry"&gt;Celery docs: Should I use retry or acks_late?&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.distributedpython.com/2018/10/26/celery-execution-pool/"&gt;Celery Execution Pools: What is it all about?&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://fixes.co.za/python/task-queues/"&gt;Task Queues&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://en.wikipedia.org/wiki/Thread_pool"&gt;Wikipedia: Thread pool&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://eventlet.net/"&gt;Eventlet&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="http://www.gevent.org/"&gt;Gevent&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://learn-gevent-socketio.readthedocs.io/en/latest/greenlets.html"&gt;What are greenlets?&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>python</category>
      <category>celery</category>
    </item>
    <item>
      <title>What are UTC, ISO time format and UNIX time and how does Python handle them</title>
      <dc:creator>Ines</dc:creator>
      <pubDate>Fri, 30 Oct 2020 13:23:22 +0000</pubDate>
      <link>https://dev.to/inesp/what-are-utc-iso-time-format-and-unix-time-and-how-does-python-handle-them-22bg</link>
      <guid>https://dev.to/inesp/what-are-utc-iso-time-format-and-unix-time-and-how-does-python-handle-them-22bg</guid>
      <description>&lt;p&gt;&lt;em&gt;"If my time is 16:44, what is that in UTC time?"&lt;/em&gt; is what I asked myself last week. &lt;/p&gt;

&lt;p&gt;&lt;em&gt;"Do we use UTC dates or do we store timezones into the database? And by the way, if I call &lt;code&gt;datetime.now()&lt;/code&gt;, am I getting the correct time or should I adjust it with a timezone suffix?"&lt;/em&gt;. &lt;/p&gt;

&lt;p&gt;This time-business is so delicate. I never had much trouble with date-times, until last week, when I had to create a humble Celery task that needs to be a master of time, that needs to understand how all the dates on all these objects relate to one another. And suddenly I find myself appalled by all datetimes with no timezone info. What a grievous mistake it was to allow programmers to forget about timezones, to just call &lt;code&gt;now()&lt;/code&gt; and hope for the best. How is my super-duper, time-lord of a Celery task supposed to lord the time if a single measly timezone-lacking datetime knocks it out of its balance?&lt;/p&gt;

&lt;p&gt;So I had to read a few docs and a few standards to really, truly understand what to do with missing timezones and whether the datetime I am creating is really and truly the exact datetime I want and not one that is 1h before or ahead (my timezone is +01:00, which is very easy to mistake for +00:00).&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;small&gt;What is&lt;/small&gt; UTC time
&lt;/h2&gt;

&lt;p&gt;UTC is a global time standard. It defined how time will be measured and coordinated between everybody. But UTC is also used as a &lt;strong&gt;successor of GMT&lt;/strong&gt; (=Greenwich Mean Time) to describe the time at the 0° meridian.&lt;/p&gt;

&lt;p&gt;In code, we usually denote it with &lt;strong&gt;+00:00&lt;/strong&gt; or with a &lt;strong&gt;Z&lt;/strong&gt;. The letter Z is used for historical reasons because Z was the name of the timezone using this time. This time is also sometimes called the &lt;strong&gt;Zulu time&lt;/strong&gt; since NATO's phonetic alphabet for Z is "Zulu".&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The daylight saving time (=DST) does not affect UTC.&lt;/strong&gt; DST simply modifies the ±[hh]:[mm]-part of the date-time: Europe's CTE (=Central European Time) is usually described as &lt;code&gt;UTC+1&lt;/code&gt;, but during the summer, when DST kicks in, it simply becomes &lt;code&gt;UTC+2&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;small&gt;What is&lt;/small&gt; the ISO format / ISO 8601 format
&lt;/h2&gt;

&lt;p&gt;This ISO standard defines how to format dates and date-times without confusion and misunderstandings.&lt;/p&gt;

&lt;p&gt;Date format:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;2020-10-26&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Date and time formats:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;2020-10-26&lt;b&gt;T&lt;/b&gt;08:15:30&lt;b&gt;+02:00&lt;/b&gt;
&lt;/li&gt;
&lt;li&gt;2020-10-26&lt;b&gt;T&lt;/b&gt;08:15:30&lt;b&gt;Z&lt;/b&gt;
&lt;/li&gt;
&lt;li&gt;20201026&lt;b&gt;T&lt;/b&gt;081530&lt;b&gt;Z&lt;/b&gt;
&lt;/li&gt;
&lt;li&gt;2020-10-26&lt;b&gt;T&lt;/b&gt;08:15:30&lt;b&gt;.456+02:00&lt;/b&gt; &amp;lt;- with microseconds&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;If the timezone is omitted, the datetime is usually interpreted to be recording the locale time.&lt;/strong&gt; But of course, this is up to every programming team to define for themselves.&lt;/p&gt;

&lt;p&gt;The &lt;strong&gt;T&lt;/strong&gt; is also often omitted. The ISO standard does allow for this, but only if the 2 parties communicating with such dates have agreed to allow it. However, there is no mention of substituting T with a space. But I think we all see that our community has decided that it can correctly understand a datetime with a space and is actively using it everywhere.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;small&gt;What is&lt;/small&gt; UNIX time / Epoch time / POSIX time
&lt;/h2&gt;

&lt;p&gt;Unix time is an &lt;strong&gt;integer&lt;/strong&gt;, it's the number of seconds passed since 1st January 1970 UTC - a capriciously chosen date. This date is called the Epoch.&lt;/p&gt;

&lt;p&gt;UNIX time &lt;strong&gt;excludes all leap seconds.&lt;/strong&gt; It pretends that every day has exactly 24 * 60 * 60 (=86 400) seconds.&lt;/p&gt;

&lt;h2&gt;
  
  
  Timezones and UTC offsets
&lt;/h2&gt;

&lt;p&gt;UTC offsets are the ±[hh]:[mm] parts of date-time formats. They are a multiple of 15 minutes and they describe the difference between UTC and the locale time.&lt;/p&gt;

&lt;h2&gt;
  
  
  Python in the world of datetime
&lt;/h2&gt;

&lt;p&gt;Python differentiates between &lt;em&gt;naive&lt;/em&gt; and &lt;em&gt;aware&lt;/em&gt; datetimes. The first one has no timezone, while the second does have a timezone. Each &lt;code&gt;datetime&lt;/code&gt; object has an optional attribute &lt;code&gt;tzinfo&lt;/code&gt;, which can hold timezone information.&lt;/p&gt;

&lt;h4&gt;
  
  
  How to get the current time
&lt;/h4&gt;

&lt;p&gt;My current time is &lt;code&gt;2020-10-26T12:30:10Z&lt;/code&gt;, my timezone is +1. Here I get my locale time:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;In&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;datetime&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt;

&lt;span class="n"&gt;In&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;now&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;  &lt;span class="c1"&gt;# produces a naive datetime
&lt;/span&gt;&lt;span class="n"&gt;Out&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;datetime&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2020&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;26&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;13&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;933315&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;


&lt;span class="n"&gt;In&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;utcnow&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;  &lt;span class="c1"&gt;# produces a naive datetime
&lt;/span&gt;&lt;span class="n"&gt;Out&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;datetime&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2020&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;26&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;12&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;933315&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Both above functions create a &lt;strong&gt;naive&lt;/strong&gt; datetime.&lt;/p&gt;

&lt;h4&gt;
  
  
  How to get the current time with timezone
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;In&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;datetime&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;timezone&lt;/span&gt;

&lt;span class="n"&gt;In&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;now&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tz&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;timezone&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;utc&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;Out&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;datetime&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2020&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;26&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;12&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;933315&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;tzinfo&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;datetime&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="n"&gt;utc&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  How to create an exact datetime
&lt;/h4&gt;

&lt;p&gt;Here I create the 31st Dec 2020, at midnight UTC time:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;In&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;datetime&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;timezone&lt;/span&gt;

&lt;span class="n"&gt;In&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2020&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;12&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;31&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="mi"&gt;0&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="n"&gt;tzinfo&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;timezone&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;utc&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;Out&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;datetime&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2020&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;12&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;31&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="mi"&gt;0&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="n"&gt;tzinfo&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;datetime&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="n"&gt;utc&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And here I create the 31st Dec 2020, at midnight in the timezone +03:00:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;In&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;datetime&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;timedelta&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;timezone&lt;/span&gt;

&lt;span class="n"&gt;In&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2020&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;12&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;31&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="mi"&gt;0&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="n"&gt;tzinfo&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;timezone&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;timedelta&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;minutes&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;60&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt;
&lt;span class="n"&gt;Out&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;datetime&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2020&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;12&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;31&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="mi"&gt;0&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="n"&gt;tzinfo&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;datetime&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="n"&gt;datetime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;timedelta&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;seconds&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;3600&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Timezones are set with &lt;code&gt;timedelta&lt;/code&gt; to describe the UTC offset.&lt;/p&gt;

&lt;h4&gt;
  
  
  How to get the UNIX time of any date / how to turn UNIX time to date
&lt;/h4&gt;

&lt;p&gt;In Python, this format is called "timestamp". And it is not an integer, but a float, so that microseconds can be represented.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;In&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;datetime&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;timezone&lt;/span&gt;

&lt;span class="c1"&gt;# turn a date into a timestamp:
&lt;/span&gt;&lt;span class="n"&gt;In&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;d&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2020&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;...)&lt;/span&gt;
&lt;span class="n"&gt;In&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;timestamp&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="n"&gt;Out&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;1603711810.933315&lt;/span&gt;  &lt;span class="c1"&gt;# the num of seconds since Epoch
&lt;/span&gt;
&lt;span class="c1"&gt;# turn the timestamp into a naive date
&lt;/span&gt;&lt;span class="n"&gt;In&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;fromtimestamp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;1603711810.933315&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;Out&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;datetime&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2020&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;26&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;11&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;933315&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="c1"&gt;# turn the timestamp into an aware date
&lt;/span&gt;&lt;span class="n"&gt;In&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;fromtimestamp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;1603711810.933315&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;tz&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;timezone&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;utc&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;Out&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;datetime&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2020&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;26&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;11&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;933315&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;tzinfo&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;datetime&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="n"&gt;utc&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="c1"&gt;# turn the timestamp into an aware date at UTC+3
&lt;/span&gt;&lt;span class="n"&gt;In&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;fromtimestamp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;1603711810.933315&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;tz&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;timezone&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;timedelta&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;minutes&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;60&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt;
&lt;span class="n"&gt;Out&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;datetime&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2020&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;26&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;14&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;933315&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;tzinfo&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;datetime&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="n"&gt;datetime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;timedelta&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;seconds&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;10800&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The POSIX time is always the time at UTC+0, which means that when we call &lt;code&gt;fromtimestamp&lt;/code&gt; with the timezone parameter we define only what &lt;strong&gt;the timezone of the result will be, not the timezone of the input value&lt;/strong&gt;. When we passed in &lt;code&gt;UTC+0&lt;/code&gt;, it produced the time &lt;code&gt;11:30&lt;/code&gt;, when we passed in &lt;code&gt;UTC+3&lt;/code&gt; it produced &lt;code&gt;14:30&lt;/code&gt;.&lt;/p&gt;

&lt;h4&gt;
  
  
  How to parse a string into a &lt;code&gt;datetime&lt;/code&gt;
&lt;/h4&gt;

&lt;p&gt;Use the &lt;code&gt;datetime.fromisoformat&lt;/code&gt; for this, BUT: not all ISO 8601 strings can be parsed. Only strings of this format can be parsed:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;YYYY-MM-DD[*HH[:MM[:SS[.fff[fff]]]][+HH:MM[:SS[.ffffff]]]]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Using &lt;code&gt;Z&lt;/code&gt; as the timezone is not supported.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;In&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;fromisoformat&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"2020-10-26T12:30+02:00"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;Out&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;datetime&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2020&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;26&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;12&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;tzinfo&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;datetime&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="n"&gt;datetime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;timedelta&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;seconds&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;7200&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt;

&lt;span class="n"&gt;In&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;fromisoformat&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"2020-10-26 12:30:10.998"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;Out&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;datetime&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2020&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;26&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;12&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;998000&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  How to add or remove days/months/years to a datetime
&lt;/h4&gt;

&lt;p&gt;One way is to use &lt;code&gt;timedelta&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;In&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;d&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;fromisoformat&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"2020-10-26 12:30:10.555+01:00"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;Out&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;datetime&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2020&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;26&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;12&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;555000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;tzinfo&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;datetime&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="n"&gt;datetime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;timedelta&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;seconds&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;3600&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt;

&lt;span class="c1"&gt;# add 1 day
&lt;/span&gt;&lt;span class="n"&gt;In&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;d&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;timedelta&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;days&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="n"&gt;Out&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;datetime&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2020&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;27&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;12&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;555000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;tzinfo&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;datetime&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="n"&gt;datetime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;timedelta&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;seconds&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;3600&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt;

&lt;span class="c1"&gt;# add 4 hours
&lt;/span&gt;&lt;span class="n"&gt;In&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;d&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;timedelta&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;minutes&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;60&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;Out&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;datetime&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2020&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;26&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;16&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;555000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;tzinfo&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;datetime&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="n"&gt;datetime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;timedelta&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;seconds&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;3600&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt;

&lt;span class="c1"&gt;# subtract a year (given that this is not a leap year)
&lt;/span&gt;&lt;span class="n"&gt;In&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;d&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;timedelta&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;minutes&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="n"&gt;Out&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;datetime&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2019&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;27&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;12&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;555000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;tzinfo&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;datetime&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="n"&gt;datetime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;timedelta&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;seconds&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;3600&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The other is to replace parts of the date, but this can trigger &lt;code&gt;ValueError&lt;/code&gt;s:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;In&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;d&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;fromisoformat&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"2020-02-29 00:00:00+00:00"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;Out&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;datetime&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2020&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;29&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="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;tzinfo&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;datetime&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="n"&gt;utc&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# this date 20 years ago:
&lt;/span&gt;&lt;span class="n"&gt;In&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;year&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;2000&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;Out&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;datetime&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;29&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="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;tzinfo&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;datetime&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="n"&gt;utc&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# change year to 2019, which didn't have Feb 29th:
&lt;/span&gt;&lt;span class="n"&gt;In&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;year&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;2019&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="o"&gt;---------------------------------------------------------------------------&lt;/span&gt;
&lt;span class="nb"&gt;ValueError&lt;/span&gt;
&lt;span class="o"&gt;----&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;year&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;2019&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="nb"&gt;ValueError&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;day&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="n"&gt;out&lt;/span&gt; &lt;span class="n"&gt;of&lt;/span&gt; &lt;span class="nb"&gt;range&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;month&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;replace&lt;/code&gt; function can be used to replace any part of the datetime: minutes, hours, days, timezone, ...:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;In&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;d&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;fromisoformat&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"2020-02-29 00:00:00+00:00"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;Out&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;datetime&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2020&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;29&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="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;tzinfo&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;datetime&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="n"&gt;utc&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;In&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;year&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;2019&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;day&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;28&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;tzinfo&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;timezone&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;timedelta&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;minutes&lt;/span&gt;&lt;span class="o"&gt;=-&lt;/span&gt;&lt;span class="mi"&gt;8&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;60&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt;
&lt;span class="n"&gt;Out&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;datetime&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2019&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;28&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="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;tzinfo&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;datetime&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="n"&gt;datetime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;timedelta&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;days&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="n"&gt;seconds&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;57600&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h6&gt;
  
  
  External Sources:
&lt;/h6&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://en.wikipedia.org/wiki/Unix_time"&gt;Wikipedia: Unix time&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://en.wikipedia.org/wiki/Coordinated_Universal_Time"&gt;Wikipedia: UTC&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://en.wikipedia.org/wiki/UTC_offset"&gt;Wikipedia: UTC offset&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://en.wikipedia.org/wiki/NATO_phonetic_alphabet"&gt;Wikipedia: NATO phonetic alphabet&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://en.wikipedia.org/wiki/ISO_8601"&gt;Wikipedia: ISO 8601&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.python.org/3/library/datetime.html"&gt;Python Docs: datetime&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://stackoverflow.com/questions/9531524/in-an-iso-8601-date-is-the-t-character-mandatory"&gt;StackOverflow: In an ISO 8601 date, is the T character mandatory?&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="http://www.ines-panker.com/2020/10/26/all-date-formats.html"&gt;What are UTC time, ISO time format and UNIX time&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>python</category>
      <category>utc</category>
      <category>datetime</category>
    </item>
  </channel>
</rss>
