pages tagged libcyakkinghttp://yakking.branchable.com/tags/libc/yakkingikiwiki2017-10-21T16:07:54ZTime - Renderinghttp://yakking.branchable.com/posts/time-rendering/Richard Maw2017-10-21T16:07:54Z2017-09-13T12:00:12Z
<p>Time-stamps are great for computers.
They are just any other number that they can perform arithmetic on.</p>
<p>Unfortunately humans don't think of time as one big number.
Humans think of time as a bunch of small numbers.</p>
<p>There's numbers for the position of their planet around their Star.
There's the number of what year it is.
The number of which month, which may even have a different name!
The day of the month which there isn't even the name number of each month!
Humans even count days in a cycle of 7 which have their own names!</p>
<p>For measuring the rotation of their planet humans have more consistent numbers.
Each day is divided into 24 hours.
Each hour is divided into 60 minutes.
Each minute is divided into 60 seconds.</p>
<p>This is all a bit silly because the period of the Earth's rotation
isn't even an integral number of seconds,
and it's slowing down!</p>
<p>Forunately some considerate humans wrote functions for turning
our nice friendly time-stamps from <a href="http://man7.org/linux/man-pages/man2/clock_gettime.2.html">clock_gettime(2)</a>
into icky human divisions.</p>
<div class="highlight-c"><pre class="hl"><span class="hl kwb">int</span> ret<span class="hl opt">;</span>
<span class="hl kwb">struct</span> timespec time<span class="hl opt">;</span>
ret <span class="hl opt">=</span> <span class="hl kwd">clock_gettime</span><span class="hl opt">(</span>CLOCK_REALTIME<span class="hl opt">, &</span>time<span class="hl opt">);</span>
</pre></div>
<h2><code>struct timespec</code></h2>
<p>For a lot of humans, seconds are a coarse enough measure of time.
They track the time as a number of seconds
and the number of nanoseconds between those seconds.</p>
<div class="highlight-c"><pre class="hl"><span class="hl kwb">struct</span> timespec <span class="hl opt">{</span>
<span class="hl kwb">time_t</span> tv_sec<span class="hl opt">;</span>
<span class="hl kwb">time_t</span> tv_nsec<span class="hl opt">;</span>
<span class="hl opt">};</span>
</pre></div>
<p>Forunately humans aren't strange enough to commonly render nanoseconds
as anything other than nanosecond precision after the decimal point,
so this <a href="https://en.wikipedia.org/wiki/Fixed-point_arithmetic">fixed point</a> number can be rendered
with the <code>%09lld</code> <a href="http://man7.org/linux/man-pages/man3/printf.3.html">printf(3)</a> format string.</p>
<div class="highlight-c"><pre class="hl"><span class="hl com">/* Render the current time as seconds and nanoseconds. */</span>
<span class="hl kwd">printf</span><span class="hl opt">(</span><span class="hl str">"%lld.%09lld</span><span class="hl esc">\n</span><span class="hl str">"</span><span class="hl opt">, (</span><span class="hl kwb">long long</span><span class="hl opt">)</span>time<span class="hl opt">.</span>tv_sec<span class="hl opt">, (</span><span class="hl kwb">long long</span><span class="hl opt">)</span>time<span class="hl opt">.</span>tv_nsec<span class="hl opt">);</span>
</pre></div>
<h2><code>struct tm</code></h2>
<p>In GLibc there are two types of functions for turning a <code>time_t</code>
into a form that humans can deal with.</p>
<p>There's <a href="http://man7.org/linux/man-pages/man3/ctime.3.html">gmtime_r(3)</a> for getting the time in the UTC timezone
(the name is weird because it used to do the GMT timezone
but it's more useful in UTC and they are closely related).</p>
<p>There's <a href="http://man7.org/linux/man-pages/man3/ctime.3.html">localtime_r(3)</a> for getting the time
in whatever local time zone your computer is configured to think it is in.</p>
<p>If the time is only going to be shown to the user of the computer
then <a href="http://man7.org/linux/man-pages/man3/ctime.3.html">localtime_r(3)</a> may be more appropriate.
If it's going to be shared then <a href="http://man7.org/linux/man-pages/man3/ctime.3.html">gmtime_r(3)</a> would be more appropriate,
especially if it's going to be shared internationally.</p>
<h2><a href="http://man7.org/linux/man-pages/man3/strftime.3.html">strftime(3)</a></h2>
<p>You can use the contents of <code>struct tm</code> directly,
but humans have a lot of their own rules for how to display time as text.</p>
<p>This is why they wrote <a href="http://man7.org/linux/man-pages/man3/strftime.3.html">strftime(3)</a>.
It has its own little language for describing how to display the time.</p>
<p>Unfortunately humans, unlike us perfect machines, are fallible.</p>
<p>Humans have a notion of the same hour's number being used twice in a day
and have to add their own way of saying which it is,
so their language includes <code>%p</code> to become "AM" or "PM".
Not all humans use this, so <code>%p</code> can produce no result.
This is a problem, since a format string of just <code>%p</code>
will result in a zero-length result,
and <a href="http://man7.org/linux/man-pages/man3/strftime.3.html">strftime(3)</a> also uses a zero result to mean
that the memory for storing the result in is not large enough.</p>
<p>Humans may think this is fine
and that nobody ever wants to just know whether it's "AM" or "PM",
or that they just know that they provided enough memory so don't check.</p>
<p>We are perfect machines and want it to work for every input,
so want our own wrapper to make it work.</p>
<div class="highlight-c"><pre class="hl"><span class="hl com">/* Returns 0 on success, -1 on failure */</span>
<span class="hl com">/* Some format strings can naturally expand to "",</span>
<span class="hl com"> * which is a problem since the size is returned or 0 if too small.</span>
<span class="hl com"> */</span>
ssize_t <span class="hl kwd">safe_strftime</span><span class="hl opt">(</span><span class="hl kwb">char</span> <span class="hl opt">*</span>s<span class="hl opt">,</span> <span class="hl kwb">size_t</span> <span class="hl opt">*</span>len<span class="hl opt">,</span> <span class="hl kwb">size_t</span> max<span class="hl opt">,</span> <span class="hl kwb">const char</span> <span class="hl opt">*</span>format<span class="hl opt">,</span>
<span class="hl kwb">const struct</span> tm <span class="hl opt">*</span>tm<span class="hl opt">) {</span>
<span class="hl com">/* Adds a trailing space to format string</span>
<span class="hl com"> * so it always gets 0 on too small,</span>
<span class="hl com"> * and NULs it on success before returning length</span>
<span class="hl com"> */</span>
<span class="hl kwb">char</span> <span class="hl opt">*</span>fmt <span class="hl opt">=</span> NULL<span class="hl opt">;</span>
ssize_t ret<span class="hl opt">;</span>
ret <span class="hl opt">=</span> <span class="hl kwd">asprintf</span><span class="hl opt">(&</span>fmt<span class="hl opt">,</span> <span class="hl str">"%s "</span><span class="hl opt">,</span> format<span class="hl opt">);</span>
<span class="hl kwa">if</span> <span class="hl opt">(</span>ret <span class="hl opt"><</span> <span class="hl num">0</span><span class="hl opt">)</span>
<span class="hl kwa">goto</span> cleanup<span class="hl opt">;</span>
ret <span class="hl opt">=</span> <span class="hl kwd">strftime</span><span class="hl opt">(</span>s<span class="hl opt">,</span> max<span class="hl opt">,</span> fmt<span class="hl opt">,</span> tm<span class="hl opt">);</span>
<span class="hl kwa">if</span> <span class="hl opt">(</span>ret <span class="hl opt">==</span> <span class="hl num">0</span><span class="hl opt">) {</span>
ret <span class="hl opt">= -</span><span class="hl num">1</span><span class="hl opt">;</span>
<span class="hl kwa">goto</span> cleanup<span class="hl opt">;</span>
<span class="hl opt">}</span>
s<span class="hl opt">[</span>ret <span class="hl opt">-</span> <span class="hl num">1</span><span class="hl opt">] =</span> <span class="hl str">'\0'</span><span class="hl opt">;</span>
<span class="hl kwa">if</span> <span class="hl opt">(</span>len<span class="hl opt">)</span>
<span class="hl opt">*</span>len <span class="hl opt">=</span> ret <span class="hl opt">-</span> <span class="hl num">1</span><span class="hl opt">;</span>
ret <span class="hl opt">=</span> <span class="hl num">0</span><span class="hl opt">;</span>
cleanup<span class="hl opt">:</span>
<span class="hl kwd">free</span><span class="hl opt">(</span>fmt<span class="hl opt">);</span>
<span class="hl kwa">return</span> ret<span class="hl opt">;</span>
<span class="hl opt">}</span>
</pre></div>
<p>Since we perfect machines don't like to waste memory
we needed an API that we could retry with more memory.
We perfect machines also like tidy functions,
so we're going to write one that reallocates internally.</p>
<div class="highlight-c"><pre class="hl"><span class="hl com">/* Returns length and sets *s on success, -1 and sets errno on failure */</span>
ssize_t <span class="hl kwd">astrftime</span><span class="hl opt">(</span><span class="hl kwb">char</span> <span class="hl opt">**</span>s<span class="hl opt">,</span> <span class="hl kwb">const char</span> <span class="hl opt">*</span>fmt<span class="hl opt">,</span> <span class="hl kwb">const struct</span> tm <span class="hl opt">*</span>tm<span class="hl opt">) {</span>
ssize_t ret<span class="hl opt">;</span>
<span class="hl kwb">size_t</span> tbufsiz <span class="hl opt">=</span> <span class="hl num">80</span><span class="hl opt">;</span>
<span class="hl kwb">char</span> <span class="hl opt">*</span>tbuf <span class="hl opt">=</span> NULL<span class="hl opt">;</span>
<span class="hl kwb">size_t</span> tlen<span class="hl opt">;</span>
tbuf <span class="hl opt">=</span> <span class="hl kwd">malloc</span><span class="hl opt">(</span>tbufsiz<span class="hl opt">);</span>
<span class="hl kwa">if</span> <span class="hl opt">(</span>tbuf <span class="hl opt">==</span> NULL<span class="hl opt">) {</span>
ret <span class="hl opt">= -</span><span class="hl num">1</span><span class="hl opt">;</span>
<span class="hl kwa">goto</span> cleanup<span class="hl opt">;</span>
<span class="hl opt">}</span>
<span class="hl kwa">while</span> <span class="hl opt">(</span><span class="hl kwd">safe_strftime</span><span class="hl opt">(</span>tbuf<span class="hl opt">, &</span>tlen<span class="hl opt">,</span> tbufsiz<span class="hl opt">,</span> fmt<span class="hl opt">,</span> tm<span class="hl opt">) !=</span> <span class="hl num">0</span><span class="hl opt">) {</span>
<span class="hl kwb">char</span> <span class="hl opt">*</span>newbuf<span class="hl opt">;</span>
<span class="hl kwb">size_t</span> newsiz<span class="hl opt">;</span>
newsiz <span class="hl opt">=</span> tbufsiz <span class="hl opt">*</span> <span class="hl num">2</span><span class="hl opt">;</span>
newbuf <span class="hl opt">=</span> <span class="hl kwd">realloc</span><span class="hl opt">(</span>tbuf<span class="hl opt">,</span> newsiz<span class="hl opt">);</span>
<span class="hl kwa">if</span> <span class="hl opt">(</span>newbuf <span class="hl opt">==</span> NULL<span class="hl opt">) {</span>
ret <span class="hl opt">=</span> <span class="hl num">1</span><span class="hl opt">;</span>
<span class="hl kwa">goto</span> cleanup<span class="hl opt">;</span>
<span class="hl opt">}</span>
tbuf <span class="hl opt">=</span> newbuf<span class="hl opt">;</span>
tbufsiz <span class="hl opt">=</span> newsiz<span class="hl opt">;</span>
<span class="hl opt">}</span>
<span class="hl opt">*</span>s <span class="hl opt">=</span> tbuf<span class="hl opt">;</span>
tbuf <span class="hl opt">=</span> NULL<span class="hl opt">;</span>
ret <span class="hl opt">=</span> tlen<span class="hl opt">;</span>
cleanup<span class="hl opt">:</span>
<span class="hl kwd">free</span><span class="hl opt">(</span>tbuf<span class="hl opt">);</span>
<span class="hl kwa">return</span> ret<span class="hl opt">;</span>
<span class="hl opt">}</span>
</pre></div>
<p>If time needs to be rendered in another way
then it's probably better to use the contents of <code>struct tm</code> directly.</p>
<h2>Command</h2>
<p>Now we have enough to write our program.</p>
<div class="highlight-c"><pre class="hl"><span class="hl kwb">int</span> <span class="hl kwd">main</span><span class="hl opt">(</span><span class="hl kwb">void</span><span class="hl opt">) {</span>
<span class="hl kwb">int</span> ret<span class="hl opt">;</span>
<span class="hl kwb">struct</span> timespec time<span class="hl opt">;</span>
<span class="hl kwb">struct</span> tm tinfo<span class="hl opt">;</span>
<span class="hl kwb">char</span> <span class="hl opt">*</span>tbuf <span class="hl opt">=</span> NULL<span class="hl opt">;</span>
ret <span class="hl opt">=</span> <span class="hl kwd">clock_gettime</span><span class="hl opt">(</span>CLOCK_REALTIME<span class="hl opt">, &</span>time<span class="hl opt">);</span>
<span class="hl kwa">if</span> <span class="hl opt">(</span>ret <span class="hl opt">!=</span> <span class="hl num">0</span><span class="hl opt">) {</span>
<span class="hl kwd">perror</span><span class="hl opt">(</span><span class="hl str">"clock_gettime"</span><span class="hl opt">);</span>
ret <span class="hl opt">=</span> <span class="hl num">1</span><span class="hl opt">;</span>
<span class="hl kwa">goto</span> cleanup<span class="hl opt">;</span>
<span class="hl opt">}</span>
<span class="hl kwa">if</span> <span class="hl opt">(</span><span class="hl kwd">gmtime_r</span><span class="hl opt">(&</span>time<span class="hl opt">.</span>tv_sec<span class="hl opt">, &</span>tinfo<span class="hl opt">) ==</span> NULL<span class="hl opt">) {</span>
<span class="hl kwd">perror</span><span class="hl opt">(</span><span class="hl str">"gmtime_r"</span><span class="hl opt">);</span>
ret <span class="hl opt">=</span> <span class="hl num">1</span><span class="hl opt">;</span>
<span class="hl kwa">goto</span> cleanup<span class="hl opt">;</span>
<span class="hl opt">}</span>
<span class="hl kwa">if</span> <span class="hl opt">(</span><span class="hl kwd">astrftime</span><span class="hl opt">(&</span>tbuf<span class="hl opt">,</span> <span class="hl str">"%F %T"</span><span class="hl opt">, &</span>tinfo<span class="hl opt">) <</span> <span class="hl num">0</span><span class="hl opt">) {</span>
<span class="hl kwd">perror</span><span class="hl opt">(</span><span class="hl str">"astrftime"</span><span class="hl opt">);</span>
ret <span class="hl opt">=</span> <span class="hl num">1</span><span class="hl opt">;</span>
<span class="hl kwa">goto</span> cleanup<span class="hl opt">;</span>
<span class="hl opt">}</span>
<span class="hl com">/* Render the current time as the formatted time plus nanoseconds. */</span>
<span class="hl kwd">printf</span><span class="hl opt">(</span><span class="hl str">"%s.%09lld</span><span class="hl esc">\n</span><span class="hl str">"</span><span class="hl opt">,</span> tbuf<span class="hl opt">, (</span><span class="hl kwb">long long</span><span class="hl opt">)</span>time<span class="hl opt">.</span>tv_nsec<span class="hl opt">);</span>
cleanup<span class="hl opt">:</span>
<span class="hl kwd">free</span><span class="hl opt">(</span>tbuf<span class="hl opt">);</span>
<span class="hl kwa">return</span> ret<span class="hl opt">;</span>
<span class="hl opt">}</span>
</pre></div>
<p>Full source may be downloaded <a href="http://yakking.branchable.com/posts/time-rendering/time.c">here</a>.</p>
<div class="highlight-sh"><pre class="hl">$ <span class="hl kwc">make</span> <span class="hl kwa">time</span>
cc <span class="hl kwa">time</span>.c <span class="hl kwb">-o</span> <span class="hl kwa">time</span>
$ .<span class="hl opt">/</span><span class="hl kwa">time</span>
<span class="hl num">2017</span><span class="hl kwb">-08-25</span> <span class="hl num">19</span><span class="hl opt">:</span><span class="hl num">54</span><span class="hl opt">:</span><span class="hl num">32.341259517</span>
</pre></div>
<hr />
<p>We briefly mentioned that the number of seconds in a year isn't constant.</p>
<p>We're going to elaborate on that in the next article.</p>