Jekyll2023-12-15T09:03:35-03:00https://hkotsubo.github.io/feed.xmlhkotsubo_programming_blogMeus pitacos sobre programação.Hugo KotsuboComo gerar números aleatórios sem repetição2023-04-12T08:00:00-03:002023-04-12T08:00:00-03:00https://hkotsubo.github.io/blog/2023-04-12/como-gerar-numeros-aleatorios-sem-repeticao<p>Vamos supor que preciso gerar um conjunto qualquer de números aleatórios, mas que não hajam valores repetidos. Por exemplo, para gerar um jogo da mega-sena (6 números distintos entre 1 e 60).</p>
<p>Uma ideia inicial - mas que tem seus problemas (já vamos entender melhor mais abaixo) - é ir guardando os números em uma lista/array, e para cada número gerado, verificar se ele já está na lista. Em JavaScript seria assim:</p>
<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">const</span> <span class="nx">quantidade</span> <span class="o">=</span> <span class="mi">6</span><span class="p">,</span> <span class="nx">minimo</span> <span class="o">=</span> <span class="mi">1</span><span class="p">,</span> <span class="nx">maximo</span> <span class="o">=</span> <span class="mi">60</span><span class="p">;</span>
<span class="kd">const</span> <span class="nx">numeros</span> <span class="o">=</span> <span class="p">[];</span>
<span class="k">while</span> <span class="p">(</span><span class="nx">numeros</span><span class="p">.</span><span class="nx">length</span> <span class="o"><</span> <span class="mi">6</span><span class="p">)</span> <span class="p">{</span> <span class="c1">// enquanto não tem 6 números</span>
<span class="kd">const</span> <span class="nx">n</span> <span class="o">=</span> <span class="nb">Math</span><span class="p">.</span><span class="nx">floor</span><span class="p">(</span><span class="nb">Math</span><span class="p">.</span><span class="nx">random</span><span class="p">()</span> <span class="o">*</span> <span class="p">(</span><span class="nx">maximo</span> <span class="o">-</span> <span class="nx">minimo</span> <span class="o">+</span> <span class="mi">1</span><span class="p">))</span> <span class="o">+</span> <span class="nx">minimo</span><span class="p">;</span> <span class="c1">// gera número entre 1 e 60</span>
<span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="nx">numeros</span><span class="p">.</span><span class="nx">includes</span><span class="p">(</span><span class="nx">n</span><span class="p">))</span> <span class="p">{</span> <span class="c1">// se o número não está no array, adiciona</span>
<span class="nx">numeros</span><span class="p">.</span><span class="nx">push</span><span class="p">(</span><span class="nx">n</span><span class="p">);</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="nx">numeros</span><span class="p">);</span>
</code></pre></div></div>
<p>A princípio funciona, mas essa solução não escala tão bem se a quantidade de números gerados for muito próxima da quantidade total. Vamos alterar o código acima para mostrar quando um número repetido é gerado:</p>
<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">while</span> <span class="p">(</span><span class="nx">numeros</span><span class="p">.</span><span class="nx">length</span> <span class="o"><</span> <span class="nx">quantidade</span><span class="p">)</span> <span class="p">{</span>
<span class="kd">const</span> <span class="nx">n</span> <span class="o">=</span> <span class="nb">Math</span><span class="p">.</span><span class="nx">floor</span><span class="p">(</span><span class="nb">Math</span><span class="p">.</span><span class="nx">random</span><span class="p">()</span> <span class="o">*</span> <span class="p">(</span><span class="nx">maximo</span> <span class="o">-</span> <span class="nx">minimo</span> <span class="o">+</span> <span class="mi">1</span><span class="p">))</span> <span class="o">+</span> <span class="nx">minimo</span><span class="p">;</span>
<span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="nx">numeros</span><span class="p">.</span><span class="nx">includes</span><span class="p">(</span><span class="nx">n</span><span class="p">))</span> <span class="p">{</span>
<span class="nx">numeros</span><span class="p">.</span><span class="nx">push</span><span class="p">(</span><span class="nx">n</span><span class="p">);</span>
<span class="p">}</span> <span class="k">else</span> <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="s2">`array com </span><span class="p">${</span><span class="nx">numeros</span><span class="p">.</span><span class="nx">length</span><span class="p">}</span><span class="s2"> elementos, </span><span class="p">${</span><span class="nx">n</span><span class="p">}</span><span class="s2"> repetido`</span><span class="p">);</span>
<span class="p">}</span>
</code></pre></div></div>
<p>Quando a <code class="language-plaintext highlighter-rouge">quantidade</code> é <code class="language-plaintext highlighter-rouge">6</code>, não há tantas repetições (na maioria das vezes tem uma ou nenhuma). Mas se aumentarmos para <code class="language-plaintext highlighter-rouge">quantidade = 40</code>, por exemplo, aí já muda bastante:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>array com 4 elementos, 8 repetido
array com 16 elementos, 32 repetido
array com 23 elementos, 1 repetido
array com 23 elementos, 31 repetido
array com 24 elementos, 44 repetido
array com 24 elementos, 50 repetido
array com 28 elementos, 30 repetido
array com 29 elementos, 30 repetido
array com 29 elementos, 15 repetido
array com 32 elementos, 15 repetido
array com 32 elementos, 4 repetido
array com 33 elementos, 8 repetido
array com 33 elementos, 21 repetido
array com 34 elementos, 24 repetido
array com 34 elementos, 39 repetido
array com 35 elementos, 47 repetido
array com 36 elementos, 4 repetido
array com 36 elementos, 12 repetido
array com 36 elementos, 3 repetido
array com 37 elementos, 8 repetido
array com 37 elementos, 49 repetido
array com 37 elementos, 43 repetido
array com 37 elementos, 53 repetido
array com 38 elementos, 28 repetido
array com 38 elementos, 58 repetido
array com 38 elementos, 35 repetido
array com 38 elementos, 3 repetido
array com 38 elementos, 8 repetido
array com 38 elementos, 34 repetido
array com 38 elementos, 6 repetido
array com 38 elementos, 53 repetido
array com 38 elementos, 12 repetido
array com 38 elementos, 15 repetido
</code></pre></div></div>
<p>No começo ainda não há tantas repetições, mas conforme o array cresce e o tamanho se aproxima da quantidade que queremos, a probabilidade de sortear um número já existente aumenta. Ou seja, o loop se repete várias e várias vezes até encontrar um número que ainda não foi gerado. E ainda vale lembrar que o método <code class="language-plaintext highlighter-rouge">includes</code> precisa percorrer todo o array até encontrar o elemento em questão, para saber se ele está no array. Ou seja, o array <code class="language-plaintext highlighter-rouge">numeros</code> é percorrido várias vezes durante este processo.</p>
<p>Fiz um teste rodando este algoritmo mil vezes e computando a quantidade de repetições, além do mínimo e máximo:</p>
<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">const</span> <span class="nx">quantidade</span> <span class="o">=</span> <span class="mi">40</span><span class="p">,</span> <span class="nx">minimo</span> <span class="o">=</span> <span class="mi">1</span><span class="p">,</span> <span class="nx">maximo</span> <span class="o">=</span> <span class="mi">60</span><span class="p">;</span>
<span class="kd">let</span> <span class="nx">sum</span> <span class="o">=</span> <span class="mi">0</span><span class="p">,</span> <span class="nx">qtd</span> <span class="o">=</span> <span class="mi">1000</span><span class="p">,</span> <span class="nx">min</span> <span class="o">=</span> <span class="nb">Number</span><span class="p">.</span><span class="nx">MAX_SAFE_INTEGER</span><span class="p">,</span> <span class="nx">max</span> <span class="o">=</span> <span class="nb">Number</span><span class="p">.</span><span class="nx">MIN_SAFE_INTEGER</span><span class="p">;</span>
<span class="k">for</span> <span class="p">(</span><span class="kd">let</span> <span class="nx">c</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="nx">c</span> <span class="o"><</span> <span class="nx">qtd</span><span class="p">;</span> <span class="nx">c</span><span class="o">++</span><span class="p">)</span> <span class="p">{</span>
<span class="kd">const</span> <span class="nx">numeros</span> <span class="o">=</span> <span class="p">[];</span>
<span class="kd">let</span> <span class="nx">rep</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>
<span class="k">while</span> <span class="p">(</span><span class="nx">numeros</span><span class="p">.</span><span class="nx">length</span> <span class="o"><</span> <span class="nx">quantidade</span><span class="p">)</span> <span class="p">{</span>
<span class="kd">const</span> <span class="nx">n</span> <span class="o">=</span> <span class="nb">Math</span><span class="p">.</span><span class="nx">floor</span><span class="p">(</span><span class="nb">Math</span><span class="p">.</span><span class="nx">random</span><span class="p">()</span> <span class="o">*</span> <span class="p">(</span><span class="nx">maximo</span> <span class="o">-</span> <span class="nx">minimo</span> <span class="o">+</span> <span class="mi">1</span><span class="p">))</span> <span class="o">+</span> <span class="nx">minimo</span><span class="p">;</span>
<span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="nx">numeros</span><span class="p">.</span><span class="nx">includes</span><span class="p">(</span><span class="nx">n</span><span class="p">))</span> <span class="p">{</span>
<span class="nx">numeros</span><span class="p">.</span><span class="nx">push</span><span class="p">(</span><span class="nx">n</span><span class="p">);</span>
<span class="p">}</span> <span class="k">else</span> <span class="nx">rep</span><span class="o">++</span><span class="p">;</span>
<span class="p">}</span>
<span class="nx">sum</span> <span class="o">+=</span> <span class="nx">rep</span><span class="p">;</span>
<span class="k">if</span> <span class="p">(</span><span class="nx">rep</span> <span class="o">></span> <span class="nx">max</span><span class="p">)</span> <span class="nx">max</span> <span class="o">=</span> <span class="nx">rep</span><span class="p">;</span>
<span class="k">if</span> <span class="p">(</span><span class="nx">rep</span> <span class="o"><</span> <span class="nx">min</span><span class="p">)</span> <span class="nx">min</span> <span class="o">=</span> <span class="nx">rep</span><span class="p">;</span>
<span class="p">}</span>
<span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="nx">sum</span> <span class="o">/</span> <span class="nx">qtd</span><span class="p">,</span> <span class="nx">min</span><span class="p">,</span> <span class="nx">max</span><span class="p">);</span>
</code></pre></div></div>
<p>Rodei este código várias vezes, e a média variou em torno de 25 repetições. O máximo variou entre 45 e 60, o mínimo entre 6 e 10. Ou seja, em todas as vezes, sempre teve números repetidos.</p>
<hr />
<h2 id="a-solução-fisher-yates">A solução: Fisher-Yates</h2>
<p>Originalmente, o <a href="https://en.wikipedia.org/wiki/Fisher%E2%80%93Yates_shuffle">algoritmo Fisher-Yates</a> serve para embaralhar um array. Mas podemos adaptá-lo para o nosso problema: no caso da mega-sena (6 números entre 1 e 60), basta gerar um array com todos os 60 números, embaralhá-lo e pegar os 6 primeiros. Porém, não precisamos embaralhar tudo, apenas as 6 primeiras posições:</p>
<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">const</span> <span class="nx">quantidade</span> <span class="o">=</span> <span class="mi">6</span><span class="p">,</span> <span class="nx">minimo</span> <span class="o">=</span> <span class="mi">1</span><span class="p">,</span> <span class="nx">maximo</span> <span class="o">=</span> <span class="mi">60</span><span class="p">;</span>
<span class="kd">const</span> <span class="nx">todos</span> <span class="o">=</span> <span class="p">[];</span>
<span class="k">for</span> <span class="p">(</span><span class="kd">let</span> <span class="nx">i</span> <span class="o">=</span> <span class="nx">minimo</span><span class="p">;</span> <span class="nx">i</span> <span class="o"><=</span> <span class="nx">maximo</span><span class="p">;</span> <span class="nx">i</span><span class="o">++</span><span class="p">)</span> <span class="p">{</span> <span class="c1">// gera um array com todos os números</span>
<span class="nx">todos</span><span class="p">.</span><span class="nx">push</span><span class="p">(</span><span class="nx">i</span><span class="p">);</span>
<span class="p">}</span>
<span class="c1">// embaralha as 6 primeiras posições</span>
<span class="k">for</span> <span class="p">(</span><span class="kd">let</span> <span class="nx">i</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="nx">i</span> <span class="o"><</span> <span class="nx">quantidade</span><span class="p">;</span> <span class="nx">i</span><span class="o">++</span><span class="p">)</span> <span class="p">{</span>
<span class="c1">// pega uma posição aleatória do array e troca com a posição atual</span>
<span class="kd">const</span> <span class="nx">j</span> <span class="o">=</span> <span class="nb">Math</span><span class="p">.</span><span class="nx">floor</span><span class="p">(</span><span class="nb">Math</span><span class="p">.</span><span class="nx">random</span><span class="p">()</span> <span class="o">*</span> <span class="nx">todos</span><span class="p">.</span><span class="nx">length</span><span class="p">);</span>
<span class="kd">const</span> <span class="nx">tmp</span> <span class="o">=</span> <span class="nx">todos</span><span class="p">[</span><span class="nx">j</span><span class="p">];</span>
<span class="nx">todos</span><span class="p">[</span><span class="nx">j</span><span class="p">]</span> <span class="o">=</span> <span class="nx">todos</span><span class="p">[</span><span class="nx">i</span><span class="p">];</span>
<span class="nx">todos</span><span class="p">[</span><span class="nx">i</span><span class="p">]</span> <span class="o">=</span> <span class="nx">tmp</span><span class="p">;</span>
<span class="p">}</span>
<span class="c1">// pega os 6 primeiros elementos</span>
<span class="kd">const</span> <span class="nx">result</span> <span class="o">=</span> <span class="nx">todos</span><span class="p">.</span><span class="nx">slice</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span> <span class="nx">quantidade</span><span class="p">);</span>
</code></pre></div></div>
<p>Este algoritmo evita tanto as repetições quanto a necessidade de verificar se o número gerado está no array, tornando-se muito mais eficiente.</p>
<p><strong>É claro</strong> que para poucos arrays pequenos, a diferença será insignificante. Afinal, <a href="https://blog.codinghorror.com/everything-is-fast-for-small-n/">para poucos dados, tudo é rápido</a>. Mas se tiver que gerar muitos números várias vezes, começa a fazer diferença.</p>
<p>Fiz um teste usando o <a href="https://benchmarkjs.com/">Benchmark.js</a> para medir cada algoritmo. O código ficou assim:</p>
<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">var</span> <span class="nx">Benchmark</span> <span class="o">=</span> <span class="nx">require</span><span class="p">(</span><span class="dl">'</span><span class="s1">benchmark</span><span class="dl">'</span><span class="p">);</span>
<span class="kd">var</span> <span class="nx">suite</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">Benchmark</span><span class="p">.</span><span class="nx">Suite</span><span class="p">;</span>
<span class="kd">const</span> <span class="nx">quantidade</span> <span class="o">=</span> <span class="mi">6</span><span class="p">,</span> <span class="nx">minimo</span> <span class="o">=</span> <span class="mi">1</span><span class="p">,</span> <span class="nx">maximo</span> <span class="o">=</span> <span class="mi">60</span><span class="p">;</span>
<span class="c1">// só precisa criar todos os números uma vez, pois nas vezes seguintes o array será sempre re-embaralhado</span>
<span class="kd">const</span> <span class="nx">todos</span> <span class="o">=</span> <span class="p">[];</span>
<span class="k">for</span> <span class="p">(</span><span class="kd">let</span> <span class="nx">i</span> <span class="o">=</span> <span class="nx">minimo</span><span class="p">;</span> <span class="nx">i</span> <span class="o"><=</span> <span class="nx">maximo</span><span class="p">;</span> <span class="nx">i</span><span class="o">++</span><span class="p">)</span> <span class="p">{</span>
<span class="nx">todos</span><span class="p">.</span><span class="nx">push</span><span class="p">(</span><span class="nx">i</span><span class="p">);</span>
<span class="p">}</span>
<span class="nx">suite</span>
<span class="p">.</span><span class="nx">add</span><span class="p">(</span><span class="dl">'</span><span class="s1">loop</span><span class="dl">'</span><span class="p">,</span> <span class="kd">function</span> <span class="p">()</span> <span class="p">{</span>
<span class="kd">const</span> <span class="nx">numeros</span> <span class="o">=</span> <span class="p">[];</span>
<span class="k">while</span> <span class="p">(</span><span class="nx">numeros</span><span class="p">.</span><span class="nx">length</span> <span class="o"><</span> <span class="nx">quantidade</span><span class="p">)</span> <span class="p">{</span>
<span class="kd">const</span> <span class="nx">n</span> <span class="o">=</span> <span class="nb">Math</span><span class="p">.</span><span class="nx">floor</span><span class="p">(</span><span class="nb">Math</span><span class="p">.</span><span class="nx">random</span><span class="p">()</span> <span class="o">*</span> <span class="p">(</span><span class="nx">maximo</span> <span class="o">-</span> <span class="nx">minimo</span> <span class="o">+</span> <span class="mi">1</span><span class="p">))</span> <span class="o">+</span> <span class="nx">minimo</span><span class="p">;</span>
<span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="nx">numeros</span><span class="p">.</span><span class="nx">includes</span><span class="p">(</span><span class="nx">n</span><span class="p">))</span> <span class="p">{</span>
<span class="nx">numeros</span><span class="p">.</span><span class="nx">push</span><span class="p">(</span><span class="nx">n</span><span class="p">);</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="p">})</span>
<span class="p">.</span><span class="nx">add</span><span class="p">(</span><span class="dl">'</span><span class="s1">fisher yates</span><span class="dl">'</span><span class="p">,</span> <span class="kd">function</span> <span class="p">()</span> <span class="p">{</span>
<span class="k">for</span> <span class="p">(</span><span class="kd">let</span> <span class="nx">i</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="nx">i</span> <span class="o"><</span> <span class="nx">quantidade</span><span class="p">;</span> <span class="nx">i</span><span class="o">++</span><span class="p">)</span> <span class="p">{</span>
<span class="kd">const</span> <span class="nx">j</span> <span class="o">=</span> <span class="nb">Math</span><span class="p">.</span><span class="nx">floor</span><span class="p">(</span><span class="nb">Math</span><span class="p">.</span><span class="nx">random</span><span class="p">()</span> <span class="o">*</span> <span class="nx">todos</span><span class="p">.</span><span class="nx">length</span><span class="p">);</span>
<span class="kd">const</span> <span class="nx">tmp</span> <span class="o">=</span> <span class="nx">todos</span><span class="p">[</span><span class="nx">j</span><span class="p">];</span>
<span class="nx">todos</span><span class="p">[</span><span class="nx">j</span><span class="p">]</span> <span class="o">=</span> <span class="nx">todos</span><span class="p">[</span><span class="nx">i</span><span class="p">];</span>
<span class="nx">todos</span><span class="p">[</span><span class="nx">i</span><span class="p">]</span> <span class="o">=</span> <span class="nx">tmp</span><span class="p">;</span>
<span class="p">}</span>
<span class="kd">const</span> <span class="nx">result</span> <span class="o">=</span> <span class="nx">todos</span><span class="p">.</span><span class="nx">slice</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span> <span class="nx">quantidade</span><span class="p">);</span>
<span class="p">})</span>
<span class="p">.</span><span class="nx">on</span><span class="p">(</span><span class="dl">'</span><span class="s1">complete</span><span class="dl">'</span><span class="p">,</span> <span class="kd">function</span> <span class="p">()</span> <span class="p">{</span>
<span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="dl">'</span><span class="s1">Fastest is </span><span class="dl">'</span> <span class="o">+</span> <span class="k">this</span><span class="p">.</span><span class="nx">filter</span><span class="p">(</span><span class="dl">'</span><span class="s1">fastest</span><span class="dl">'</span><span class="p">).</span><span class="nx">map</span><span class="p">(</span><span class="dl">'</span><span class="s1">name</span><span class="dl">'</span><span class="p">));</span>
<span class="p">})</span>
<span class="p">.</span><span class="nx">on</span><span class="p">(</span><span class="dl">'</span><span class="s1">cycle</span><span class="dl">'</span><span class="p">,</span> <span class="kd">function</span> <span class="p">(</span><span class="nx">event</span><span class="p">)</span> <span class="p">{</span>
<span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="nb">String</span><span class="p">(</span><span class="nx">event</span><span class="p">.</span><span class="nx">target</span><span class="p">));</span>
<span class="p">})</span>
<span class="p">.</span><span class="nx">run</span><span class="p">({</span> <span class="dl">'</span><span class="s1">async</span><span class="dl">'</span><span class="p">:</span> <span class="kc">true</span> <span class="p">});</span>
</code></pre></div></div>
<p>O resultado pode variar de máquina para outra, mas em geral o Fisher-Yates foi mais rápido:</p>
<pre><code class="language-none">loop x 5,815,553 ops/sec ±1.96% (81 runs sampled)
fisher yates x 7,341,581 ops/sec ±3.01% (79 runs sampled)
Fastest is fisher yates
</code></pre>
<p>Os números acima são as operações por segundo (quanto mais, melhor - e note que estão em notação americana, com vírgulas separando os milhares). Ou seja, o loop conseguiu mais de 5 milhões de operações por segundo, enquanto o Fisher-Yates conseguiu mais de 7 milhões (cerca de 1,26 vezes o número de operações do loop).</p>
<p>Mudando para <code class="language-plaintext highlighter-rouge">quantidade = 40</code>, a diferença se torna ainda mais gritante:</p>
<pre><code class="language-none">loop x 280,240 ops/sec ±1.14% (88 runs sampled)
fisher yates x 1,470,527 ops/sec ±1.79% (85 runs sampled)
Fastest is fisher yates
</code></pre>
<p>Agora o Fisher-Yates conseguiu cerca de 5,24 vezes o número de operações do loop. Isso porque, como já vimos, para quantidade igual a 40 o loop começa a ter muitas repetições, tendo que gerar outro número (e para cada número gerado, precisa percorrer o array novamente para verificar se ele já existe).</p>
<hr />
<h2 id="considerações-finais">Considerações Finais</h2>
<p>Quando alguém te diz para estudar os fundamentos (algoritmos, estruturas de dados, etc), é disso que estamos falando. A maioria dos problemas comuns já foi resolvida, com soluções exaustivamente testadas - e comprovadas - no mundo real. Claro que como exercício (para fins puramente didáticos) é interessante você mesmo tentar resolver - e provavelmente a ideia inicial que muitos têm é usar a primeira solução indicada acima. Mas saiba que para muita coisa já existem um ou mais algoritmos prontos. A maioria, inclusive, foi criada há décadas e aperfeiçoada ao longo do tempo. Em código sério que vai para a produção, geralmente você não precisa reinventar a roda.</p>
<p>Aliás, em algumas linguagens já existem bibliotecas que te trazem isso pronto. Em Python, por exemplo, existe a <a href="https://docs.python.org/3/library/random.html#random.sample">função <code class="language-plaintext highlighter-rouge">random.sample</code></a>:</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">from</span> <span class="nn">random</span> <span class="kn">import</span> <span class="n">sample</span>
<span class="c1"># números entre 1 e 60
</span><span class="n">todos</span> <span class="o">=</span> <span class="nb">range</span><span class="p">(</span><span class="mi">1</span><span class="p">,</span> <span class="mi">61</span><span class="p">)</span> <span class="c1"># em um range o valor final não é incluso, por isso é 61
# pega 6 números, sem repetição
</span><span class="n">numeros</span> <span class="o">=</span> <span class="n">sample</span><span class="p">(</span><span class="n">todos</span><span class="p">,</span> <span class="n">k</span><span class="o">=</span><span class="mi">6</span><span class="p">)</span>
<span class="k">print</span><span class="p">(</span><span class="n">numeros</span><span class="p">)</span>
</code></pre></div></div>
<p>Por fim, este algoritmo não vale apenas para números, e sim para <strong>qualquer array/lista/coleção de dados</strong>. Podemos, por exemplo, ter um array contendo objetos. Como o Fisher-Yates está trabalhando com os índices e em nenhum momento é preciso comparar os elementos em si, para ele tanto faz o que são esses elementos.</p>Hugo KotsuboVamos supor que preciso gerar um conjunto qualquer de números aleatórios, mas que não hajam valores repetidos. Por exemplo, para gerar um jogo da mega-sena (6 números distintos entre 1 e 60). Uma ideia inicial - mas que tem seus problemas (já vamos entender melhor mais abaixo) - é ir guardando os números em uma lista/array, e para cada número gerado, verificar se ele já está na lista. Em JavaScript seria assim: const quantidade = 6, minimo = 1, maximo = 60; const numeros = []; while (numeros.length < 6) { // enquanto não tem 6 números const n = Math.floor(Math.random() * (maximo - minimo + 1)) + minimo; // gera número entre 1 e 60 if (!numeros.includes(n)) { // se o número não está no array, adiciona numeros.push(n); } } console.log(numeros); A princípio funciona, mas essa solução não escala tão bem se a quantidade de números gerados for muito próxima da quantidade total. Vamos alterar o código acima para mostrar quando um número repetido é gerado: while (numeros.length < quantidade) { const n = Math.floor(Math.random() * (maximo - minimo + 1)) + minimo; if (!numeros.includes(n)) { numeros.push(n); } else console.log(`array com ${numeros.length} elementos, ${n} repetido`); } Quando a quantidade é 6, não há tantas repetições (na maioria das vezes tem uma ou nenhuma). Mas se aumentarmos para quantidade = 40, por exemplo, aí já muda bastante: array com 4 elementos, 8 repetido array com 16 elementos, 32 repetido array com 23 elementos, 1 repetido array com 23 elementos, 31 repetido array com 24 elementos, 44 repetido array com 24 elementos, 50 repetido array com 28 elementos, 30 repetido array com 29 elementos, 30 repetido array com 29 elementos, 15 repetido array com 32 elementos, 15 repetido array com 32 elementos, 4 repetido array com 33 elementos, 8 repetido array com 33 elementos, 21 repetido array com 34 elementos, 24 repetido array com 34 elementos, 39 repetido array com 35 elementos, 47 repetido array com 36 elementos, 4 repetido array com 36 elementos, 12 repetido array com 36 elementos, 3 repetido array com 37 elementos, 8 repetido array com 37 elementos, 49 repetido array com 37 elementos, 43 repetido array com 37 elementos, 53 repetido array com 38 elementos, 28 repetido array com 38 elementos, 58 repetido array com 38 elementos, 35 repetido array com 38 elementos, 3 repetido array com 38 elementos, 8 repetido array com 38 elementos, 34 repetido array com 38 elementos, 6 repetido array com 38 elementos, 53 repetido array com 38 elementos, 12 repetido array com 38 elementos, 15 repetido No começo ainda não há tantas repetições, mas conforme o array cresce e o tamanho se aproxima da quantidade que queremos, a probabilidade de sortear um número já existente aumenta. Ou seja, o loop se repete várias e várias vezes até encontrar um número que ainda não foi gerado. E ainda vale lembrar que o método includes precisa percorrer todo o array até encontrar o elemento em questão, para saber se ele está no array. Ou seja, o array numeros é percorrido várias vezes durante este processo. Fiz um teste rodando este algoritmo mil vezes e computando a quantidade de repetições, além do mínimo e máximo: const quantidade = 40, minimo = 1, maximo = 60; let sum = 0, qtd = 1000, min = Number.MAX_SAFE_INTEGER, max = Number.MIN_SAFE_INTEGER; for (let c = 0; c < qtd; c++) { const numeros = []; let rep = 0; while (numeros.length < quantidade) { const n = Math.floor(Math.random() * (maximo - minimo + 1)) + minimo; if (!numeros.includes(n)) { numeros.push(n); } else rep++; } sum += rep; if (rep > max) max = rep; if (rep < min) min = rep; } console.log(sum / qtd, min, max); Rodei este código várias vezes, e a média variou em torno de 25 repetições. O máximo variou entre 45 e 60, o mínimo entre 6 e 10. Ou seja, em todas as vezes, sempre teve números repetidos. A solução: Fisher-Yates Originalmente, o algoritmo Fisher-Yates serve para embaralhar um array. Mas podemos adaptá-lo para o nosso problema: no caso da mega-sena (6 números entre 1 e 60), basta gerar um array com todos os 60 números, embaralhá-lo e pegar os 6 primeiros. Porém, não precisamos embaralhar tudo, apenas as 6 primeiras posições: const quantidade = 6, minimo = 1, maximo = 60; const todos = []; for (let i = minimo; i <= maximo; i++) { // gera um array com todos os números todos.push(i); } // embaralha as 6 primeiras posições for (let i = 0; i < quantidade; i++) { // pega uma posição aleatória do array e troca com a posição atual const j = Math.floor(Math.random() * todos.length); const tmp = todos[j]; todos[j] = todos[i]; todos[i] = tmp; } // pega os 6 primeiros elementos const result = todos.slice(0, quantidade); Este algoritmo evita tanto as repetições quanto a necessidade de verificar se o número gerado está no array, tornando-se muito mais eficiente. É claro que para poucos arrays pequenos, a diferença será insignificante. Afinal, para poucos dados, tudo é rápido. Mas se tiver que gerar muitos números várias vezes, começa a fazer diferença. Fiz um teste usando o Benchmark.js para medir cada algoritmo. O código ficou assim: var Benchmark = require('benchmark'); var suite = new Benchmark.Suite; const quantidade = 6, minimo = 1, maximo = 60; // só precisa criar todos os números uma vez, pois nas vezes seguintes o array será sempre re-embaralhado const todos = []; for (let i = minimo; i <= maximo; i++) { todos.push(i); } suite .add('loop', function () { const numeros = []; while (numeros.length < quantidade) { const n = Math.floor(Math.random() * (maximo - minimo + 1)) + minimo; if (!numeros.includes(n)) { numeros.push(n); } } }) .add('fisher yates', function () { for (let i = 0; i < quantidade; i++) { const j = Math.floor(Math.random() * todos.length); const tmp = todos[j]; todos[j] = todos[i]; todos[i] = tmp; } const result = todos.slice(0, quantidade); }) .on('complete', function () { console.log('Fastest is ' + this.filter('fastest').map('name')); }) .on('cycle', function (event) { console.log(String(event.target)); }) .run({ 'async': true }); O resultado pode variar de máquina para outra, mas em geral o Fisher-Yates foi mais rápido: loop x 5,815,553 ops/sec ±1.96% (81 runs sampled) fisher yates x 7,341,581 ops/sec ±3.01% (79 runs sampled) Fastest is fisher yates Os números acima são as operações por segundo (quanto mais, melhor - e note que estão em notação americana, com vírgulas separando os milhares). Ou seja, o loop conseguiu mais de 5 milhões de operações por segundo, enquanto o Fisher-Yates conseguiu mais de 7 milhões (cerca de 1,26 vezes o número de operações do loop). Mudando para quantidade = 40, a diferença se torna ainda mais gritante: loop x 280,240 ops/sec ±1.14% (88 runs sampled) fisher yates x 1,470,527 ops/sec ±1.79% (85 runs sampled) Fastest is fisher yates Agora o Fisher-Yates conseguiu cerca de 5,24 vezes o número de operações do loop. Isso porque, como já vimos, para quantidade igual a 40 o loop começa a ter muitas repetições, tendo que gerar outro número (e para cada número gerado, precisa percorrer o array novamente para verificar se ele já existe). Considerações Finais Quando alguém te diz para estudar os fundamentos (algoritmos, estruturas de dados, etc), é disso que estamos falando. A maioria dos problemas comuns já foi resolvida, com soluções exaustivamente testadas - e comprovadas - no mundo real. Claro que como exercício (para fins puramente didáticos) é interessante você mesmo tentar resolver - e provavelmente a ideia inicial que muitos têm é usar a primeira solução indicada acima. Mas saiba que para muita coisa já existem um ou mais algoritmos prontos. A maioria, inclusive, foi criada há décadas e aperfeiçoada ao longo do tempo. Em código sério que vai para a produção, geralmente você não precisa reinventar a roda. Aliás, em algumas linguagens já existem bibliotecas que te trazem isso pronto. Em Python, por exemplo, existe a função random.sample: from random import sample # números entre 1 e 60 todos = range(1, 61) # em um range o valor final não é incluso, por isso é 61 # pega 6 números, sem repetição numeros = sample(todos, k=6) print(numeros) Por fim, este algoritmo não vale apenas para números, e sim para qualquer array/lista/coleção de dados. Podemos, por exemplo, ter um array contendo objetos. Como o Fisher-Yates está trabalhando com os índices e em nenhum momento é preciso comparar os elementos em si, para ele tanto faz o que são esses elementos.Não leve o Índice TIOBE tão a sério2023-04-11T08:00:00-03:002023-04-11T08:00:00-03:00https://hkotsubo.github.io/blog/2023-04-11/nao-leve-o-indice-tiobe-a-serio<p>Provavelmente você já ouviu falar do <a href="https://www.tiobe.com/tiobe-index/">Índice TIOBE</a>, que mede a “popularidade” das linguagens de programação. Vira e mexe sai alguma notícia dizendo que linguagem X entrou no top 50, ou linguagem Y passou a linguagem Z e agora é a primeira, etc. E aí aparecem os inevitáveis comentários de gente comemorando que sua linguagem favorita subiu (ou que a linguagem que não gosta caiu) N posições. E o pior, usam isso como argumento para defender que uma linguagem é “melhor” ou “pior” que a outra.</p>
<p>Bem, discutir qual linguagem é melhor, e/ou ser torcedor/<em>fanboy</em>/<em>cheerleader</em> de linguagem, já é algo que eu acho - pra não dizer outra coisa - ridículo. Além de ser uma discussão muitas vezes inútil e improdutiva. E usar o Índice TIOBE como argumento nessa discussão é pior ainda, e nos parágrafos seguintes explicarei os motivos (<em>disclaimer: este post é baseado <a href="https://www.tabnews.com.br/kht/4606e326-d3c9-4a68-b97e-231a927ee66f">neste comentário que fiz no TabNews</a></em>).</p>
<hr />
<h1 id="como-o-índice-tiobe-é-calculado">Como o Índice TIOBE é calculado</h1>
<p>No <a href="https://www.tiobe.com/tiobe-index/programminglanguages_definition/">próprio site do índice</a> tem a explicação completa. Aqui eu traduzirei e comentarei apenas alguns trechos que são pertinentes ao ponto que quero discutir.</p>
<p>Em resumo, é feita uma busca pelo termo <code class="language-plaintext highlighter-rouge">+"<language> programming"</code>, para várias linguagens. Por exemplo: <code class="language-plaintext highlighter-rouge">+"JavaScript programming"</code>, <code class="language-plaintext highlighter-rouge">+"Python programming"</code>, etc. O sinal de mais e as aspas são uma “sintaxe comum” em muitos mecanismos de busca, para indicar que deve obrigatoriamente buscar por esses dois termos exatamente nesta ordem. Esta busca é feita em vários sites diferentes, e a quantidade de <em>hits</em> (resultados) é usada para calcular o valor daquela linguagem no índice. Basicamente, quanto mais resultados, maior é o valor (e melhor a classificação da linguagem).</p>
<p>A lista de linguagens, os sites onde a busca é feita, e a fórmula para calcular o valor estão bem explicados no <a href="https://www.tiobe.com/tiobe-index/programminglanguages_definition/">link já citado</a>. Lá também tem os critérios para escolher as linguagens e sites. Mas vamos focar em um ponto específico, e que na minha opinião mostra como o índice não deve ser levado tão a sério.</p>
<h2 id="escolha-dos-sites">Escolha dos sites</h2>
<p>São escolhidos os 25 melhores sites do ranking da <a href="https://www.similarweb.com/">Similarweb</a> (empresa que fornece serviços de <em>Web Analytics</em>), que satisfaçam os seguintes critérios:</p>
<ul>
<li>a página inicial deve ter um campo de busca</li>
<li>a página de busca contém uma indicação da quantidade de resultados encontrados</li>
<li>os resultados estão disponíveis no HTML (ou seja, nada daquele JS que coloca o valor dinamicamente em algum lugar)</li>
<li>idiomas com caracteres especiais são corretamente codificados</li>
<li>a busca deve retornar pelo menos um resultado</li>
<li>os resultados não devem ter muitas discrepâncias</li>
<li>sites pornográficos são desconsiderados</li>
</ul>
<p>A partir disto temos uma lista de 25 sites, que pelos critérios acima podem variar dependendo da época. Olhando hoje (11 de abril de 2023), a lista contém alguns sites “óbvios” como o Google, Bing e Wikipedia. <strong>Mas tem dois que me chamaram a atenção: o <a href="https://www.walmart.com/">Walmart.com</a> (??) e o <a href="https://www.etsy.com/">Etsy.com</a> (que é um site que vende roupas, calçados, artigos para a casa, cozinha, etc).</strong></p>
<h3 id="sério-por-que-buscas-feitas-nesses-dois-sites-são-consideradas-em-um-ranking-de-popularidade-de-linguagens-de-programação">Sério, por que buscas feitas nesses dois sites são consideradas em um ranking de popularidade de <strong>linguagens de programação</strong>?</h3>
<p>Só por curiosidade, fiz uma busca por <code class="language-plaintext highlighter-rouge">+"Java programming"</code> em ambos.</p>
<p>No Etsy, a busca retornou <a href="https://www.etsy.com/search?q=%2B%22Java%20programming%22&ref=search_bar">adesivos, canecas e camisetas</a>:</p>
<p><img src="/assets/img/etsy_adesivos_canecas.png" title="Adesivos e canecas" alt="Adesivos e canecas" />
<br /><br />
<img src="/assets/img/etsy_camisetas_canecas.png" title="Camisetas e canecas" alt="Camisetas e canecas" /></p>
<p>Já no site do Walmart, <a href="https://www.walmart.com/search?q=%2B%22Java+programming%22">retornou livros</a>:</p>
<p><img src="/assets/img/walmart_livros.png" title="Livros" alt="Livros" /></p>
<p>Só o fato disso ser considerado no índice já deveria servir para desqualificá-lo, na minha opinião.</p>
<p>Então da próxima vez que você pensar em usar o Índice TIOBE para argumentar que uma linguagem é “melhor” ou “pior”, pense nisso.</p>Hugo KotsuboProvavelmente você já ouviu falar do Índice TIOBE, que mede a “popularidade” das linguagens de programação. Vira e mexe sai alguma notícia dizendo que linguagem X entrou no top 50, ou linguagem Y passou a linguagem Z e agora é a primeira, etc. E aí aparecem os inevitáveis comentários de gente comemorando que sua linguagem favorita subiu (ou que a linguagem que não gosta caiu) N posições. E o pior, usam isso como argumento para defender que uma linguagem é “melhor” ou “pior” que a outra. Bem, discutir qual linguagem é melhor, e/ou ser torcedor/fanboy/cheerleader de linguagem, já é algo que eu acho - pra não dizer outra coisa - ridículo. Além de ser uma discussão muitas vezes inútil e improdutiva. E usar o Índice TIOBE como argumento nessa discussão é pior ainda, e nos parágrafos seguintes explicarei os motivos (disclaimer: este post é baseado neste comentário que fiz no TabNews). Como o Índice TIOBE é calculado No próprio site do índice tem a explicação completa. Aqui eu traduzirei e comentarei apenas alguns trechos que são pertinentes ao ponto que quero discutir. Em resumo, é feita uma busca pelo termo +"<language> programming", para várias linguagens. Por exemplo: +"JavaScript programming", +"Python programming", etc. O sinal de mais e as aspas são uma “sintaxe comum” em muitos mecanismos de busca, para indicar que deve obrigatoriamente buscar por esses dois termos exatamente nesta ordem. Esta busca é feita em vários sites diferentes, e a quantidade de hits (resultados) é usada para calcular o valor daquela linguagem no índice. Basicamente, quanto mais resultados, maior é o valor (e melhor a classificação da linguagem). A lista de linguagens, os sites onde a busca é feita, e a fórmula para calcular o valor estão bem explicados no link já citado. Lá também tem os critérios para escolher as linguagens e sites. Mas vamos focar em um ponto específico, e que na minha opinião mostra como o índice não deve ser levado tão a sério. Escolha dos sites São escolhidos os 25 melhores sites do ranking da Similarweb (empresa que fornece serviços de Web Analytics), que satisfaçam os seguintes critérios: a página inicial deve ter um campo de busca a página de busca contém uma indicação da quantidade de resultados encontrados os resultados estão disponíveis no HTML (ou seja, nada daquele JS que coloca o valor dinamicamente em algum lugar) idiomas com caracteres especiais são corretamente codificados a busca deve retornar pelo menos um resultado os resultados não devem ter muitas discrepâncias sites pornográficos são desconsiderados A partir disto temos uma lista de 25 sites, que pelos critérios acima podem variar dependendo da época. Olhando hoje (11 de abril de 2023), a lista contém alguns sites “óbvios” como o Google, Bing e Wikipedia. Mas tem dois que me chamaram a atenção: o Walmart.com (??) e o Etsy.com (que é um site que vende roupas, calçados, artigos para a casa, cozinha, etc). Sério, por que buscas feitas nesses dois sites são consideradas em um ranking de popularidade de linguagens de programação? Só por curiosidade, fiz uma busca por +"Java programming" em ambos. No Etsy, a busca retornou adesivos, canecas e camisetas: Já no site do Walmart, retornou livros: Só o fato disso ser considerado no índice já deveria servir para desqualificá-lo, na minha opinião. Então da próxima vez que você pensar em usar o Índice TIOBE para argumentar que uma linguagem é “melhor” ou “pior”, pense nisso.Lendo dados do teclado em Java: usando a classe java.util.Scanner2022-06-15T08:00:00-03:002022-06-15T08:00:00-03:00https://hkotsubo.github.io/blog/2022-06-15/lendo-dados-teclado-em-java-usando-a-classe-scanner<p>Um problema muito frequente que vejo acontecer ao se usar a classe <code class="language-plaintext highlighter-rouge">java.util.Scanner</code> é quando tem algo assim:</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nc">Scanner</span> <span class="n">sc</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">Scanner</span><span class="o">(</span><span class="nc">System</span><span class="o">.</span><span class="na">in</span><span class="o">);</span>
<span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="s">"Digite sua idade:"</span><span class="o">);</span>
<span class="kt">int</span> <span class="n">idade</span> <span class="o">=</span> <span class="n">sc</span><span class="o">.</span><span class="na">nextInt</span><span class="o">();</span>
<span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="s">"Digite seu nome:"</span><span class="o">);</span>
<span class="nc">String</span> <span class="n">nome</span> <span class="o">=</span> <span class="n">sc</span><span class="o">.</span><span class="na">nextLine</span><span class="o">();</span>
<span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">printf</span><span class="o">(</span><span class="s">"Nome: %s, idade: %d\n"</span><span class="o">,</span> <span class="n">nome</span><span class="o">,</span> <span class="n">idade</span><span class="o">);</span>
</code></pre></div></div>
<p>Então quando o programa pede para digitar a idade, você digita <code class="language-plaintext highlighter-rouge">42</code> e dá <kbd>ENTER</kbd>. Mas aí o programa nem espera você digitar o nome, e já imprime:</p>
<pre><code class="language-none">Nome: , idade: 42
</code></pre>
<p>Ou seja, o nome ficou vazio, e ele nem sequer deixou você digitá-lo.</p>
<hr />
<h2 id="o-que-aconteceu">O que aconteceu?</h2>
<p>Basicamente, quando você digita <kbd>ENTER</kbd>, o <code class="language-plaintext highlighter-rouge">System.in</code> recebe um <a href="https://www.fileformat.info/info/unicode/char/0a/index.htm">caractere de quebra de linha</a> (o famoso <code class="language-plaintext highlighter-rouge">\n</code>, ou <em>line break</em>, ou <em>newline</em>, ou <em>line feed</em>). Só que este caractere nem sempre é consumido pelo <code class="language-plaintext highlighter-rouge">Scanner</code>.</p>
<p>A ideia é que cada método do <code class="language-plaintext highlighter-rouge">Scanner</code> só consuma o mínimo de caracteres necessários para retornar o valor que foi requisitado. No caso de <code class="language-plaintext highlighter-rouge">nextInt</code>, ele verifica que <code class="language-plaintext highlighter-rouge">4</code> e <code class="language-plaintext highlighter-rouge">2</code> são parte de um número, mas a quebra de linha não. Por isso ele só consome os caracteres <code class="language-plaintext highlighter-rouge">4</code> e <code class="language-plaintext highlighter-rouge">2</code>, retornando o número <code class="language-plaintext highlighter-rouge">42</code>. Mas a quebra de linha permanece lá, para ser consumida pelo próximo método do <code class="language-plaintext highlighter-rouge">Scanner</code>.</p>
<p>Depois, quando <code class="language-plaintext highlighter-rouge">nextLine</code> é chamado, ele consome tudo até a quebra de linha. E segundo a <a href="https://docs.oracle.com/en/java/javase/18/docs/api/java.base/java/util/Scanner.html#nextLine()">documentação</a>, ele não inclui a quebra de linha no resultado (“<em>returns the rest of the current line, excluding any line separator at the end</em>”). Ou seja, ele vai lendo tudo até encontrar uma quebra de linha, e devolve tudo que foi lido (exceto a própria quebra de linha). Mas como neste caso o próximo caractere já era a própria quebra de linha, o resultado foi uma string vazia.</p>
<h2 id="mas-por-que-é-assim">Mas por que é assim?</h2>
<p>Para ser mais exato, o <code class="language-plaintext highlighter-rouge">Scanner</code> quebra a entrada (no caso do <code class="language-plaintext highlighter-rouge">System.in</code>, os caracteres digitados) em <strong><em>tokens</em></strong>, que são basicamente sequências de caracteres separadas por delimitadores. Por padrão um delimitador é qualquer caractere que retorne <code class="language-plaintext highlighter-rouge">true</code> para o <a href="https://docs.oracle.com/en/java/javase/18/docs/api/java.base/java/lang/Character.html#isWhitespace(char)">método <code class="language-plaintext highlighter-rouge">Character.isWhitespace</code></a> (que no caso são espaços, quebras de linha, entre outros)<sup id="fnref:delim" role="doc-noteref"><a href="#fn:delim" class="footnote" rel="footnote">1</a></sup>.</p>
<p>Ou seja, qualquer sequência de caracteres que não sejam espaços nem quebras de linha será considerada um <em>token</em>. E os métodos do <code class="language-plaintext highlighter-rouge">Scanner</code>, em geral, processam um <em>token</em> de cada vez. Mas há exceções, como <code class="language-plaintext highlighter-rouge">nextLine()</code>, que lê tudo até o final da linha, ou <code class="language-plaintext highlighter-rouge">findInLine</code>, que ignora os delimitadores, entre outros - e a melhor forma de saber se um método considera ou não os <em>tokens</em> é <strong>lendo a <a href="https://docs.oracle.com/en/java/javase/18/docs/api/java.base/java/util/Scanner.html">documentação</a></strong>.</p>
<p>No caso de <code class="language-plaintext highlighter-rouge">nextInt()</code>, a <a href="https://docs.oracle.com/en/java/javase/18/docs/api/java.base/java/util/Scanner.html#nextInt()">documentação</a> diz: “<em>Scans the <strong>next token</strong> of the input as an int</em>”. Ou seja, ele vai pegar o próximo <em>token</em> e converter para um número inteiro. Por exemplo, suponha que eu tenho um código que chama <code class="language-plaintext highlighter-rouge">nextInt()</code> três vezes seguidas:</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nc">Scanner</span> <span class="n">sc</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">Scanner</span><span class="o">(</span><span class="nc">System</span><span class="o">.</span><span class="na">in</span><span class="o">)</span>
<span class="kt">int</span> <span class="n">a</span> <span class="o">=</span> <span class="n">sc</span><span class="o">.</span><span class="na">nextInt</span><span class="o">();</span>
<span class="kt">int</span> <span class="n">b</span> <span class="o">=</span> <span class="n">sc</span><span class="o">.</span><span class="na">nextInt</span><span class="o">();</span>
<span class="kt">int</span> <span class="n">c</span> <span class="o">=</span> <span class="n">sc</span><span class="o">.</span><span class="na">nextInt</span><span class="o">();</span>
</code></pre></div></div>
<p>Se eu digitar <code class="language-plaintext highlighter-rouge">10 20 30</code> (assim mesmo, tudo na mesma linha) e em seguida <kbd>ENTER</kbd>, ele vai ler os números <code class="language-plaintext highlighter-rouge">10</code>, <code class="language-plaintext highlighter-rouge">20</code> e <code class="language-plaintext highlighter-rouge">30</code> separadamente, mesmo que todos estejam na mesma linha (e os valores de <code class="language-plaintext highlighter-rouge">a</code>, <code class="language-plaintext highlighter-rouge">b</code> e <code class="language-plaintext highlighter-rouge">c</code> serão respectivamente 10, 20 e 30). Afinal, o espaço é um caractere delimitador, então há três <em>tokens</em> (<code class="language-plaintext highlighter-rouge">10</code>, <code class="language-plaintext highlighter-rouge">20</code> e <code class="language-plaintext highlighter-rouge">30</code>), e portanto três chamadas seguidas de <code class="language-plaintext highlighter-rouge">nextInt()</code> retornarão os respectivos números.</p>
<p>E se eu digitar <code class="language-plaintext highlighter-rouge">10</code>, <kbd>ENTER</kbd>, <code class="language-plaintext highlighter-rouge">20</code>, <kbd>ENTER</kbd>, <code class="language-plaintext highlighter-rouge">30</code>, <kbd>ENTER</kbd>, também serão três <em>tokens</em> (pois o <kbd>ENTER</kbd> gera o caractere de quebra de linha, que é um delimitador), e os valores de <code class="language-plaintext highlighter-rouge">a</code>, <code class="language-plaintext highlighter-rouge">b</code> e <code class="language-plaintext highlighter-rouge">c</code> também serão 10, 20 e 30. Vale notar que em ambos os casos os delimitadores (espaços e quebras de linha) <strong>não</strong> fazem parte dos <em>tokens</em>. Por isso que no primeiro exemplo acima a quebra de linha não é consumida.</p>
<p>Ao usar <code class="language-plaintext highlighter-rouge">nextInt()</code>, estou basicamente dizendo “<em>me dê o próximo número</em>”, sem me importar se ele está na mesma linha, na próxima, depois de 20000 espaços, etc. O que importa é que o próximo <em>token</em> possa ser convertido para número.</p>
<p>O problema acontece quando você mistura métodos que pegam o próximo <em>token</em> (ou seja, que levam em conta os delimitadores) com métodos que não trabalham com <em>tokens</em> (como <code class="language-plaintext highlighter-rouge">nextLine()</code>, que sempre lê tudo até o final da linha, independente de quais sejam os delimitadores).</p>
<p>Então você precisa saber exatamente o que quer fazer. Você quer um número, independente de ter mais informações na mesma linha, ou quer que a linha toda contenha apenas um número e nada mais? Se for o segundo caso, então não use <code class="language-plaintext highlighter-rouge">nextInt()</code>, prefira ler a linha toda com <code class="language-plaintext highlighter-rouge">nextLine()</code> e em seguida converta-a para <code class="language-plaintext highlighter-rouge">int</code>. Poderia ter um método que faz essa validação, por exemplo:</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">public</span> <span class="kd">static</span> <span class="kt">int</span> <span class="nf">lerInt</span><span class="o">(</span><span class="nc">String</span> <span class="n">mensagem</span><span class="o">,</span> <span class="nc">Scanner</span> <span class="n">sc</span><span class="o">)</span> <span class="o">{</span>
<span class="k">while</span> <span class="o">(</span><span class="kc">true</span><span class="o">)</span> <span class="o">{</span>
<span class="k">try</span> <span class="o">{</span>
<span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="n">mensagem</span><span class="o">);</span>
<span class="k">return</span> <span class="nc">Integer</span><span class="o">.</span><span class="na">parseInt</span><span class="o">(</span><span class="n">sc</span><span class="o">.</span><span class="na">nextLine</span><span class="o">());</span>
<span class="o">}</span> <span class="k">catch</span> <span class="o">(</span><span class="nc">NumberFormatException</span> <span class="n">e</span><span class="o">)</span> <span class="o">{</span>
<span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="s">"Você não digitou um número"</span><span class="o">);</span>
<span class="o">}</span>
<span class="o">}</span>
<span class="o">}</span>
</code></pre></div></div>
<p>Assim, ele pede que o número seja digitado, e enquanto não digitar corretamente, ele mostra a mensagem de erro e pede para digitar novamente. Para usar seria algo como:</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nc">Scanner</span> <span class="n">sc</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">Scanner</span><span class="o">(</span><span class="nc">System</span><span class="o">.</span><span class="na">in</span><span class="o">);</span>
<span class="kt">int</span> <span class="n">idade</span> <span class="o">=</span> <span class="n">lerInt</span><span class="o">(</span><span class="s">"Digite sua idade:"</span><span class="o">,</span> <span class="n">sc</span><span class="o">);</span>
<span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="s">"Digite seu nome:"</span><span class="o">);</span>
<span class="c1">// para ler String, continue usando nextLine()</span>
<span class="nc">String</span> <span class="n">nome</span> <span class="o">=</span> <span class="n">sc</span><span class="o">.</span><span class="na">nextLine</span><span class="o">();</span>
<span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">printf</span><span class="o">(</span><span class="s">"Nome: %s, idade: %d\n"</span><span class="o">,</span> <span class="n">nome</span><span class="o">,</span> <span class="n">idade</span><span class="o">);</span>
</code></pre></div></div>
<p>Desta forma, não ocorre o problema da quebra de linha não ser consumida, já que <code class="language-plaintext highlighter-rouge">nextLine()</code> pegará a linha toda, e depois <code class="language-plaintext highlighter-rouge">Integer.parseInt</code> tentará converter para <code class="language-plaintext highlighter-rouge">int</code>.</p>
<p>Uma alternativa que muitos sugerem é chamar <code class="language-plaintext highlighter-rouge">nextLine()</code> logo depois de <code class="language-plaintext highlighter-rouge">nextInt()</code>, para garantir que a quebra de linha seja consumida:</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kt">int</span> <span class="n">idade</span> <span class="o">=</span> <span class="n">sc</span><span class="o">.</span><span class="na">nextInt</span><span class="o">();</span>
<span class="n">sc</span><span class="o">.</span><span class="na">nextLine</span><span class="o">();</span>
</code></pre></div></div>
<p>Mas já que é para consumir toda a linha, por que não usar <code class="language-plaintext highlighter-rouge">nextLine()</code> de uma vez?</p>
<p><strong>Lembrando ainda que há uma diferença</strong>: suponha que foi digitado, por exemplo, <code class="language-plaintext highlighter-rouge">42 abc</code> e <kbd>ENTER</kbd>. Se eu usar o método <code class="language-plaintext highlighter-rouge">lerInt</code>, então <code class="language-plaintext highlighter-rouge">Integer.parseInt(sc.nextLine())</code> vai ler a linha inteira (<code class="language-plaintext highlighter-rouge">42 abc</code>) e tentar converter tudo isso para número, e não será possível (portanto mostrará a mensagem de erro e pedirá que digite novamente). Mas se eu usar <code class="language-plaintext highlighter-rouge">nextInt()</code>, ele vai ler o <code class="language-plaintext highlighter-rouge">42</code> e converter corretamente para <code class="language-plaintext highlighter-rouge">int</code>, e em seguida o <code class="language-plaintext highlighter-rouge">nextLine()</code> irá ler o restante da linha (<code class="language-plaintext highlighter-rouge"> abc</code> - e repare que o espaço antes do “a” também faz parte da string). Isso pode fazer diferença dependendo do que você precisa.</p>
<p>Por exemplo, se a sua intenção é que toda a linha contenha apenas o número e nada mais, então o método <code class="language-plaintext highlighter-rouge">lerInt</code> é o mais adequado. Mas se quer que o próximo <em>token</em> seja um número, e depois quer ignorar o restante da linha, usar <code class="language-plaintext highlighter-rouge">nextInt()</code> seguido de <code class="language-plaintext highlighter-rouge">nextLine()</code> seria melhor.</p>
<blockquote>
<p><strong>Atenção</strong>: dependendo do caso, <code class="language-plaintext highlighter-rouge">sc.nextInt()</code> não é 100% equivalente a <code class="language-plaintext highlighter-rouge">Integer.parseInt(sc.nextLine())</code>. Veja mais abaixo na <a href="#integerparseint-vs-nextint">seção “<em><code class="language-plaintext highlighter-rouge">Integer.parseInt</code> vs <code class="language-plaintext highlighter-rouge">nextInt()</code></em>”</a>.</p>
</blockquote>
<hr />
<h2 id="mas-e-o-método-hasnextint">Mas e o método hasNextInt()?</h2>
<p>Dependendo de como você fizer, pode não funcionar da forma esperada no caso de alguém digitar algo que não é um número (por exemplo, <code class="language-plaintext highlighter-rouge">xyz</code>), porque <a href="https://docs.oracle.com/en/java/javase/18/docs/api/java.base/java/util/Scanner.html#hasNextInt()">segundo a documentação</a>, <strong>os caracteres digitados não são consumidos</strong> por este método (“<em>The scanner does not advance past any input</em>”).</p>
<p>Ou seja, se o código fosse assim:</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// ERRADO, se não digitar um número válido (por exemplo, "xyz"), ele entra em loop</span>
<span class="kd">public</span> <span class="kd">static</span> <span class="kt">int</span> <span class="nf">lerInt</span><span class="o">(</span><span class="nc">String</span> <span class="n">mensagem</span><span class="o">,</span> <span class="nc">Scanner</span> <span class="n">sc</span><span class="o">)</span> <span class="o">{</span>
<span class="k">while</span> <span class="o">(</span><span class="kc">true</span><span class="o">)</span> <span class="o">{</span>
<span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="n">mensagem</span><span class="o">);</span>
<span class="k">if</span> <span class="o">(</span><span class="n">sc</span><span class="o">.</span><span class="na">hasNextInt</span><span class="o">())</span> <span class="o">{</span>
<span class="k">return</span> <span class="n">sc</span><span class="o">.</span><span class="na">nextInt</span><span class="o">();</span>
<span class="o">}</span> <span class="k">else</span> <span class="o">{</span>
<span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="s">"Você não digitou um número"</span><span class="o">);</span>
<span class="o">}</span>
<span class="o">}</span>
<span class="o">}</span>
</code></pre></div></div>
<p>O que acontece se digitar <code class="language-plaintext highlighter-rouge">xyz</code>? O método <code class="language-plaintext highlighter-rouge">hasNextInt()</code> detecta que o próximo <em>token</em> não é um número e cai no <code class="language-plaintext highlighter-rouge">else</code>, mostrando a mensagem de erro (“<em>Você não digitou um número</em>”). Depois o <code class="language-plaintext highlighter-rouge">while</code> continua, mas como os caracteres não são consumidos pelo <code class="language-plaintext highlighter-rouge">Scanner</code>, a próxima chamada de <code class="language-plaintext highlighter-rouge">hasNextInt()</code> ainda estará olhando para <code class="language-plaintext highlighter-rouge">xyz</code>, então vai continuar dando <code class="language-plaintext highlighter-rouge">false</code>, e o código entra em <em>loop</em> (com o detalhe que o programa nem sequer espera você digitar algo, ele simplesmente imprime a mensagem e já chama <code class="language-plaintext highlighter-rouge">hasNextInt()</code> novamente).</p>
<p>Claro que seria possível usar a outra ideia mencionada acima (colocar um <code class="language-plaintext highlighter-rouge">nextLine()</code> ou <code class="language-plaintext highlighter-rouge">next()</code> dentro do <code class="language-plaintext highlighter-rouge">else</code>, para forçar que a linha ou o <em>token</em> seja consumido), mas deve-se levar em conta as diferenças já explicadas.</p>
<hr />
<h2 id="não-vale-somente-para-int-ou-a-diferença-entre-next-e-nextline">Não vale somente para <code class="language-plaintext highlighter-rouge">int</code> (ou: A diferença entre <code class="language-plaintext highlighter-rouge">next()</code> e <code class="language-plaintext highlighter-rouge">nextLine()</code>)</h2>
<p>Tudo que foi dito acima sobre <code class="language-plaintext highlighter-rouge">nextInt()</code> também vale para os demais métodos que trabalham com <em>tokens</em>, como <code class="language-plaintext highlighter-rouge">nextDouble()</code>, <code class="language-plaintext highlighter-rouge">nextLong()</code>, etc. Ou seja, se você digitar a informação que o método espera (por exemplo, um número no formato correto), a quebra de linha não é consumida.</p>
<p>A mesma atenção deve ser tomada ao usar o método <code class="language-plaintext highlighter-rouge">next()</code>, que você pode pensar como sendo um “nextString”, já que ele retorna o próximo <em>token</em> como uma <code class="language-plaintext highlighter-rouge">String</code>. E como este método trabalha com <em>tokens</em>, ele também pode gerar comportamentos inesperados. Por exemplo, se tivermos este código:</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nc">Scanner</span> <span class="n">sc</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">Scanner</span><span class="o">(</span><span class="nc">System</span><span class="o">.</span><span class="na">in</span><span class="o">);</span>
<span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="s">"Digite seu nome:"</span><span class="o">);</span>
<span class="nc">String</span> <span class="n">nome</span> <span class="o">=</span> <span class="n">sc</span><span class="o">.</span><span class="na">next</span><span class="o">();</span>
<span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="s">"Digite seu endereço:"</span><span class="o">);</span>
<span class="nc">String</span> <span class="n">endereco</span> <span class="o">=</span> <span class="n">sc</span><span class="o">.</span><span class="na">next</span><span class="o">();</span>
<span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">printf</span><span class="o">(</span><span class="s">"Nome: %s, endereço: %s\n"</span><span class="o">,</span> <span class="n">nome</span><span class="o">,</span> <span class="n">endereco</span><span class="o">);</span>
</code></pre></div></div>
<p>Se ao pedir o nome eu digitar <code class="language-plaintext highlighter-rouge">Fulano de Tal</code> e <kbd>ENTER</kbd>, ele nem espera eu digitar o endereço, e já imprime:</p>
<pre><code class="language-none">Nome: Fulano, endereço: de
</code></pre>
<p>Isso acontece porque <code class="language-plaintext highlighter-rouge">next()</code> pega o próximo <em>token</em>, e como já dito, por padrão o <code class="language-plaintext highlighter-rouge">Scanner</code> usa espaços e quebras de linha como delimitadores. Por isso, em <code class="language-plaintext highlighter-rouge">Fulano de Tal</code> temos três <em>tokens</em> (“Fulano”, “de” e “Tal”), e a primeira chamada de <code class="language-plaintext highlighter-rouge">next()</code> pega somente o primeiro <em>token</em>, que é “Fulano”. Na segunda vez que <code class="language-plaintext highlighter-rouge">next()</code> é chamado, ele pega o segundo <em>token</em> (que é “de”), por isso ele nem espera você digitar nada, porque não precisa (ele verifica que já existe um <em>token</em> disponível e o retorna).</p>
<p>Novamente, você precisa saber o que exatamente quer fazer: você quer o próximo <em>token</em> ou tudo que estiver na linha? Se for a segunda opção, use <code class="language-plaintext highlighter-rouge">nextLine()</code>.</p>
<hr />
<h3 id="e-se-eu-mudar-o-delimitador">E se eu mudar o delimitador?</h3>
<p>É possível mudar o delimitador, usando o <a href="https://docs.oracle.com/en/java/javase/18/docs/api/java.base/java/util/Scanner.html#useDelimiter(java.lang.String)">método <code class="language-plaintext highlighter-rouge">useDelimiter</code></a>. Por exemplo, você pode definir que apenas a quebra de linha separa os <em>tokens</em>, mas o espaço não:</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// apenas a quebra de linha é o delimitador</span>
<span class="nc">Scanner</span> <span class="n">sc</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">Scanner</span><span class="o">(</span><span class="nc">System</span><span class="o">.</span><span class="na">in</span><span class="o">).</span><span class="na">useDelimiter</span><span class="o">(</span><span class="s">"\n"</span><span class="o">);</span>
<span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="s">"Digite seu nome:"</span><span class="o">);</span>
<span class="nc">String</span> <span class="n">nome</span> <span class="o">=</span> <span class="n">sc</span><span class="o">.</span><span class="na">next</span><span class="o">();</span> <span class="c1">// assim, posso usar next() mesmo se eu digitar espaços</span>
<span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="s">"Digite seu endereço:"</span><span class="o">);</span>
<span class="nc">String</span> <span class="n">endereco</span> <span class="o">=</span> <span class="n">sc</span><span class="o">.</span><span class="na">next</span><span class="o">();</span>
<span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">printf</span><span class="o">(</span><span class="s">"Nome: %s, endereço: %s\n"</span><span class="o">,</span> <span class="n">nome</span><span class="o">,</span> <span class="n">endereco</span><span class="o">);</span>
</code></pre></div></div>
<p>Assim, somente a quebra de linha irá separar os <em>tokens</em>, e <code class="language-plaintext highlighter-rouge">next()</code> lerá a linha inteira quando você digitar algo que tenha espaços.</p>
<hr />
<h2 id="integerparseint-vs-nextint"><code class="language-plaintext highlighter-rouge">Integer.parseInt</code> vs <code class="language-plaintext highlighter-rouge">nextInt()</code></h2>
<p>Apesar de eu ter dito acima que <code class="language-plaintext highlighter-rouge">Integer.parseInt(sc.nextLine())</code> é uma alternativa para <code class="language-plaintext highlighter-rouge">sc.nextInt()</code>, temos que lembrar de alguns detalhes importantes.</p>
<p>Quando você cria um <code class="language-plaintext highlighter-rouge">Scanner</code>, por padrão ele usa o <em>locale default</em> retornado por <code class="language-plaintext highlighter-rouge">Locale.getDefault(Locale.Category.FORMAT)</code>. De forma <strong>bem</strong> resumida, um <code class="language-plaintext highlighter-rouge">Locale</code> define várias configurações relativas a um idioma, como o formato de números (“mil e duzentos” pode ser escrito como <code class="language-plaintext highlighter-rouge">1.200</code> no Brasil ou <code class="language-plaintext highlighter-rouge">1,200</code> nos EUA, por exemplo), entre várias outras coisas (como sempre, <a href="https://docs.oracle.com/en/java/javase/18/docs/api/java.base/java/util/Locale.html">leia a documentação para mais detalhes</a>).</p>
<p>E o <em>locale</em> interfere diretamente na forma como o <code class="language-plaintext highlighter-rouge">Scanner</code> reconhece números. Por exemplo, na minha máquina o <em>locale default</em> é <code class="language-plaintext highlighter-rouge">pt_BR</code> (código para o português do Brasil), que usa o ponto como separador de milhares. Se eu digitar <code class="language-plaintext highlighter-rouge">+1.234</code>, então <code class="language-plaintext highlighter-rouge">Integer.parseInt(sc.nextLine())</code> dará erro, já que este método não aceita os separadores no número. Já <code class="language-plaintext highlighter-rouge">nextInt()</code> retornará o número <code class="language-plaintext highlighter-rouge">1234</code> (mil duzentos e trinta e quatro).</p>
<p>Mas se eu mudar o <em>locale</em> (por exemplo, <code class="language-plaintext highlighter-rouge">Scanner sc = new Scanner(System.in).useLocale(Locale.US)</code> para usar o <em>locale</em> correspondente ao inglês americano), agora <code class="language-plaintext highlighter-rouge">+1.234</code> dá erro, pois neste <em>locale</em> o separador de milhares é a vírgula - então o <code class="language-plaintext highlighter-rouge">Scanner</code> só reconhece o número se eu digitar <code class="language-plaintext highlighter-rouge">+1,234</code>.</p>
<p>O mesmo vale para o separador decimal quando eu uso <code class="language-plaintext highlighter-rouge">nextDouble()</code>: em alguns <em>locales</em> (como o português) usa-se a vírgula, enquanto em outros (como o inglês), usa-se o ponto. Ou seja, dependendo do <em>locale</em>, o número “um e meio” deve ser digitado como <code class="language-plaintext highlighter-rouge">1,5</code> ou <code class="language-plaintext highlighter-rouge">1.5</code>, para que <code class="language-plaintext highlighter-rouge">nextDouble()</code> o reconheça corretamente.</p>
<p>Isso faz com que o <code class="language-plaintext highlighter-rouge">Scanner</code> seja mais flexível e poderoso (já que <code class="language-plaintext highlighter-rouge">Integer.parseInt</code> e <code class="language-plaintext highlighter-rouge">Double.parseDouble</code> não aceitam separadores de milhares, além de não ser possível configurá-los para <em>locales</em> diferentes), mas ainda sim é preciso se atentar a tudo que já foi dito: o fato desses métodos trabalharem com <em>tokens</em>, e os problemas que isso causa ao misturá-los com métodos que ignoram os delimitadores, como <code class="language-plaintext highlighter-rouge">nextLine()</code>. Não existe solução universal, e tudo depende do que você precisa fazer: você quer que toda a linha tenha apenas o número e nada mais, ou quer que o próximo <em>token</em> seja um número, independente de estar ou não na mesma linha?</p>
<p>Por fim, uma alternativa seria usar <code class="language-plaintext highlighter-rouge">nextLine()</code> juntamente com <a href="https://docs.oracle.com/en/java/javase/18/docs/api/java.base/java/text/DecimalFormat.html"><code class="language-plaintext highlighter-rouge">DecimalFormat</code></a>, pois com esta classe é possível configurar o <em>locale</em>, os separadores de milhares e casas decimais, etc.</p>
<hr />
<h2 id="conclusão">Conclusão</h2>
<p>Como tudo em programação, não existe <em>A Solução Ideal Para Todos Os Casos</em><sup>®</sup>, pois tudo depende do que você quer fazer. Se quer que a linha toda contenha apenas uma informação específica (como por exemplo, um número inteiro), então prefira ler a linha toda com <code class="language-plaintext highlighter-rouge">nextLine()</code> e depois processá-la de acordo (convertendo-a para número, ou fazendo qualquer outra validação, etc). Mas se quer que o próximo <em>token</em> seja um número, independente de ter ou não mais informações na mesma linha, então use o respectivo método <code class="language-plaintext highlighter-rouge">nextAlgumaCoisa</code>.</p>
<p>E sempre veja na documentação se o método que você usou trabalha com <em>tokens</em> ou se ignora os delimitadores. E claro, teste bastante, até entender o que está acontecendo.</p>
<div class="footnotes" role="doc-endnotes">
<ol>
<li id="fn:delim" role="doc-endnote">
<p>É possível mudar o delimitador, usando o método <code class="language-plaintext highlighter-rouge">useDelimiter</code>. <a href="#fnref:delim" class="reversefootnote" role="doc-backlink">↩</a></p>
</li>
</ol>
</div>Hugo KotsuboUm problema muito frequente que vejo acontecer ao se usar a classe java.util.Scanner é quando tem algo assim: Scanner sc = new Scanner(System.in); System.out.println("Digite sua idade:"); int idade = sc.nextInt(); System.out.println("Digite seu nome:"); String nome = sc.nextLine(); System.out.printf("Nome: %s, idade: %d\n", nome, idade); Então quando o programa pede para digitar a idade, você digita 42 e dá ENTER. Mas aí o programa nem espera você digitar o nome, e já imprime: Nome: , idade: 42 Ou seja, o nome ficou vazio, e ele nem sequer deixou você digitá-lo. O que aconteceu? Basicamente, quando você digita ENTER, o System.in recebe um caractere de quebra de linha (o famoso \n, ou line break, ou newline, ou line feed). Só que este caractere nem sempre é consumido pelo Scanner. A ideia é que cada método do Scanner só consuma o mínimo de caracteres necessários para retornar o valor que foi requisitado. No caso de nextInt, ele verifica que 4 e 2 são parte de um número, mas a quebra de linha não. Por isso ele só consome os caracteres 4 e 2, retornando o número 42. Mas a quebra de linha permanece lá, para ser consumida pelo próximo método do Scanner. Depois, quando nextLine é chamado, ele consome tudo até a quebra de linha. E segundo a documentação, ele não inclui a quebra de linha no resultado (“returns the rest of the current line, excluding any line separator at the end”). Ou seja, ele vai lendo tudo até encontrar uma quebra de linha, e devolve tudo que foi lido (exceto a própria quebra de linha). Mas como neste caso o próximo caractere já era a própria quebra de linha, o resultado foi uma string vazia. Mas por que é assim? Para ser mais exato, o Scanner quebra a entrada (no caso do System.in, os caracteres digitados) em tokens, que são basicamente sequências de caracteres separadas por delimitadores. Por padrão um delimitador é qualquer caractere que retorne true para o método Character.isWhitespace (que no caso são espaços, quebras de linha, entre outros)1. Ou seja, qualquer sequência de caracteres que não sejam espaços nem quebras de linha será considerada um token. E os métodos do Scanner, em geral, processam um token de cada vez. Mas há exceções, como nextLine(), que lê tudo até o final da linha, ou findInLine, que ignora os delimitadores, entre outros - e a melhor forma de saber se um método considera ou não os tokens é lendo a documentação. No caso de nextInt(), a documentação diz: “Scans the next token of the input as an int”. Ou seja, ele vai pegar o próximo token e converter para um número inteiro. Por exemplo, suponha que eu tenho um código que chama nextInt() três vezes seguidas: Scanner sc = new Scanner(System.in) int a = sc.nextInt(); int b = sc.nextInt(); int c = sc.nextInt(); Se eu digitar 10 20 30 (assim mesmo, tudo na mesma linha) e em seguida ENTER, ele vai ler os números 10, 20 e 30 separadamente, mesmo que todos estejam na mesma linha (e os valores de a, b e c serão respectivamente 10, 20 e 30). Afinal, o espaço é um caractere delimitador, então há três tokens (10, 20 e 30), e portanto três chamadas seguidas de nextInt() retornarão os respectivos números. E se eu digitar 10, ENTER, 20, ENTER, 30, ENTER, também serão três tokens (pois o ENTER gera o caractere de quebra de linha, que é um delimitador), e os valores de a, b e c também serão 10, 20 e 30. Vale notar que em ambos os casos os delimitadores (espaços e quebras de linha) não fazem parte dos tokens. Por isso que no primeiro exemplo acima a quebra de linha não é consumida. Ao usar nextInt(), estou basicamente dizendo “me dê o próximo número”, sem me importar se ele está na mesma linha, na próxima, depois de 20000 espaços, etc. O que importa é que o próximo token possa ser convertido para número. O problema acontece quando você mistura métodos que pegam o próximo token (ou seja, que levam em conta os delimitadores) com métodos que não trabalham com tokens (como nextLine(), que sempre lê tudo até o final da linha, independente de quais sejam os delimitadores). Então você precisa saber exatamente o que quer fazer. Você quer um número, independente de ter mais informações na mesma linha, ou quer que a linha toda contenha apenas um número e nada mais? Se for o segundo caso, então não use nextInt(), prefira ler a linha toda com nextLine() e em seguida converta-a para int. Poderia ter um método que faz essa validação, por exemplo: public static int lerInt(String mensagem, Scanner sc) { while (true) { try { System.out.println(mensagem); return Integer.parseInt(sc.nextLine()); } catch (NumberFormatException e) { System.out.println("Você não digitou um número"); } } } Assim, ele pede que o número seja digitado, e enquanto não digitar corretamente, ele mostra a mensagem de erro e pede para digitar novamente. Para usar seria algo como: Scanner sc = new Scanner(System.in); int idade = lerInt("Digite sua idade:", sc); System.out.println("Digite seu nome:"); // para ler String, continue usando nextLine() String nome = sc.nextLine(); System.out.printf("Nome: %s, idade: %d\n", nome, idade); Desta forma, não ocorre o problema da quebra de linha não ser consumida, já que nextLine() pegará a linha toda, e depois Integer.parseInt tentará converter para int. Uma alternativa que muitos sugerem é chamar nextLine() logo depois de nextInt(), para garantir que a quebra de linha seja consumida: int idade = sc.nextInt(); sc.nextLine(); Mas já que é para consumir toda a linha, por que não usar nextLine() de uma vez? Lembrando ainda que há uma diferença: suponha que foi digitado, por exemplo, 42 abc e ENTER. Se eu usar o método lerInt, então Integer.parseInt(sc.nextLine()) vai ler a linha inteira (42 abc) e tentar converter tudo isso para número, e não será possível (portanto mostrará a mensagem de erro e pedirá que digite novamente). Mas se eu usar nextInt(), ele vai ler o 42 e converter corretamente para int, e em seguida o nextLine() irá ler o restante da linha ( abc - e repare que o espaço antes do “a” também faz parte da string). Isso pode fazer diferença dependendo do que você precisa. Por exemplo, se a sua intenção é que toda a linha contenha apenas o número e nada mais, então o método lerInt é o mais adequado. Mas se quer que o próximo token seja um número, e depois quer ignorar o restante da linha, usar nextInt() seguido de nextLine() seria melhor. Atenção: dependendo do caso, sc.nextInt() não é 100% equivalente a Integer.parseInt(sc.nextLine()). Veja mais abaixo na seção “Integer.parseInt vs nextInt()”. Mas e o método hasNextInt()? Dependendo de como você fizer, pode não funcionar da forma esperada no caso de alguém digitar algo que não é um número (por exemplo, xyz), porque segundo a documentação, os caracteres digitados não são consumidos por este método (“The scanner does not advance past any input”). Ou seja, se o código fosse assim: // ERRADO, se não digitar um número válido (por exemplo, "xyz"), ele entra em loop public static int lerInt(String mensagem, Scanner sc) { while (true) { System.out.println(mensagem); if (sc.hasNextInt()) { return sc.nextInt(); } else { System.out.println("Você não digitou um número"); } } } O que acontece se digitar xyz? O método hasNextInt() detecta que o próximo token não é um número e cai no else, mostrando a mensagem de erro (“Você não digitou um número”). Depois o while continua, mas como os caracteres não são consumidos pelo Scanner, a próxima chamada de hasNextInt() ainda estará olhando para xyz, então vai continuar dando false, e o código entra em loop (com o detalhe que o programa nem sequer espera você digitar algo, ele simplesmente imprime a mensagem e já chama hasNextInt() novamente). Claro que seria possível usar a outra ideia mencionada acima (colocar um nextLine() ou next() dentro do else, para forçar que a linha ou o token seja consumido), mas deve-se levar em conta as diferenças já explicadas. Não vale somente para int (ou: A diferença entre next() e nextLine()) Tudo que foi dito acima sobre nextInt() também vale para os demais métodos que trabalham com tokens, como nextDouble(), nextLong(), etc. Ou seja, se você digitar a informação que o método espera (por exemplo, um número no formato correto), a quebra de linha não é consumida. A mesma atenção deve ser tomada ao usar o método next(), que você pode pensar como sendo um “nextString”, já que ele retorna o próximo token como uma String. E como este método trabalha com tokens, ele também pode gerar comportamentos inesperados. Por exemplo, se tivermos este código: Scanner sc = new Scanner(System.in); System.out.println("Digite seu nome:"); String nome = sc.next(); System.out.println("Digite seu endereço:"); String endereco = sc.next(); System.out.printf("Nome: %s, endereço: %s\n", nome, endereco); Se ao pedir o nome eu digitar Fulano de Tal e ENTER, ele nem espera eu digitar o endereço, e já imprime: Nome: Fulano, endereço: de Isso acontece porque next() pega o próximo token, e como já dito, por padrão o Scanner usa espaços e quebras de linha como delimitadores. Por isso, em Fulano de Tal temos três tokens (“Fulano”, “de” e “Tal”), e a primeira chamada de next() pega somente o primeiro token, que é “Fulano”. Na segunda vez que next() é chamado, ele pega o segundo token (que é “de”), por isso ele nem espera você digitar nada, porque não precisa (ele verifica que já existe um token disponível e o retorna). Novamente, você precisa saber o que exatamente quer fazer: você quer o próximo token ou tudo que estiver na linha? Se for a segunda opção, use nextLine(). E se eu mudar o delimitador? É possível mudar o delimitador, usando o método useDelimiter. Por exemplo, você pode definir que apenas a quebra de linha separa os tokens, mas o espaço não: // apenas a quebra de linha é o delimitador Scanner sc = new Scanner(System.in).useDelimiter("\n"); System.out.println("Digite seu nome:"); String nome = sc.next(); // assim, posso usar next() mesmo se eu digitar espaços System.out.println("Digite seu endereço:"); String endereco = sc.next(); System.out.printf("Nome: %s, endereço: %s\n", nome, endereco); Assim, somente a quebra de linha irá separar os tokens, e next() lerá a linha inteira quando você digitar algo que tenha espaços. Integer.parseInt vs nextInt() Apesar de eu ter dito acima que Integer.parseInt(sc.nextLine()) é uma alternativa para sc.nextInt(), temos que lembrar de alguns detalhes importantes. Quando você cria um Scanner, por padrão ele usa o locale default retornado por Locale.getDefault(Locale.Category.FORMAT). De forma bem resumida, um Locale define várias configurações relativas a um idioma, como o formato de números (“mil e duzentos” pode ser escrito como 1.200 no Brasil ou 1,200 nos EUA, por exemplo), entre várias outras coisas (como sempre, leia a documentação para mais detalhes). E o locale interfere diretamente na forma como o Scanner reconhece números. Por exemplo, na minha máquina o locale default é pt_BR (código para o português do Brasil), que usa o ponto como separador de milhares. Se eu digitar +1.234, então Integer.parseInt(sc.nextLine()) dará erro, já que este método não aceita os separadores no número. Já nextInt() retornará o número 1234 (mil duzentos e trinta e quatro). Mas se eu mudar o locale (por exemplo, Scanner sc = new Scanner(System.in).useLocale(Locale.US) para usar o locale correspondente ao inglês americano), agora +1.234 dá erro, pois neste locale o separador de milhares é a vírgula - então o Scanner só reconhece o número se eu digitar +1,234. O mesmo vale para o separador decimal quando eu uso nextDouble(): em alguns locales (como o português) usa-se a vírgula, enquanto em outros (como o inglês), usa-se o ponto. Ou seja, dependendo do locale, o número “um e meio” deve ser digitado como 1,5 ou 1.5, para que nextDouble() o reconheça corretamente. Isso faz com que o Scanner seja mais flexível e poderoso (já que Integer.parseInt e Double.parseDouble não aceitam separadores de milhares, além de não ser possível configurá-los para locales diferentes), mas ainda sim é preciso se atentar a tudo que já foi dito: o fato desses métodos trabalharem com tokens, e os problemas que isso causa ao misturá-los com métodos que ignoram os delimitadores, como nextLine(). Não existe solução universal, e tudo depende do que você precisa fazer: você quer que toda a linha tenha apenas o número e nada mais, ou quer que o próximo token seja um número, independente de estar ou não na mesma linha? Por fim, uma alternativa seria usar nextLine() juntamente com DecimalFormat, pois com esta classe é possível configurar o locale, os separadores de milhares e casas decimais, etc. Conclusão Como tudo em programação, não existe A Solução Ideal Para Todos Os Casos®, pois tudo depende do que você quer fazer. Se quer que a linha toda contenha apenas uma informação específica (como por exemplo, um número inteiro), então prefira ler a linha toda com nextLine() e depois processá-la de acordo (convertendo-a para número, ou fazendo qualquer outra validação, etc). Mas se quer que o próximo token seja um número, independente de ter ou não mais informações na mesma linha, então use o respectivo método nextAlgumaCoisa. E sempre veja na documentação se o método que você usou trabalha com tokens ou se ignora os delimitadores. E claro, teste bastante, até entender o que está acontecendo. É possível mudar o delimitador, usando o método useDelimiter. ↩Entendendo o Date do JavaScript (ou: criei um Date mas ao printar/formatar ele fica com dia/hora errado)2021-11-29T08:00:00-03:002021-11-29T08:00:00-03:00https://hkotsubo.github.io/blog/2021-11-29/entendendo-o-date-do-javascript<p>Eu poderia simplesmente jogar um monte de código e pronto (os famosos posts “Tente isso”, que só deixam código e não explicam nada), mas acho que é muito mais importante que você entenda como funciona o <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date"><code class="language-plaintext highlighter-rouge">Date</code> do JavaScript</a>, para só depois entender os problemas mais comuns que acontecem ao usá-lo e formatá-lo.</p>
<h1 id="o-que-é-o-date-do-javascript">O que é o <code class="language-plaintext highlighter-rouge">Date</code> do JavaScript</h1>
<p>Apesar do nome, o <code class="language-plaintext highlighter-rouge">Date</code> não é exatamente uma data. Pelo menos não no sentido de ter um único valor de dia, mês, ano, hora, minuto e segundo.</p>
<p>Segundo a <a href="https://tc39.es/ecma262/#sec-date-objects">especificação da linguagem</a>, o único valor que um <code class="language-plaintext highlighter-rouge">Date</code> tem é a quantidade de milissegundos que se passaram desde <code class="language-plaintext highlighter-rouge">1970-01-01T00:00Z</code> (1 de janeiro de 1970, à meia-noite, em <a href="https://en.wikipedia.org/wiki/UTC">UTC</a>). Esse valor também é chamado de timestamp, e já foi explicado em detalhes <a href="/blog/2019-05-02/o-que-e-timestamp" class="new-window">em outro post</a> (leitura sugerida para entender melhor).</p>
<p>Mas apenas para dar um exemplo: eu rodei <code class="language-plaintext highlighter-rouge">new Date().valueOf()</code> agora há pouco e o resultado foi o timestamp <code class="language-plaintext highlighter-rouge">1638186750973</code>. Esse valor é o mesmo no mundo todo (qualquer um que tivesse rodado o mesmo código no mesmo instante teria o mesmo valor). Só que este timestamp corresponde às seguintes datas e horas:</p>
<table>
<thead>
<tr>
<th>Data e hora</th>
<th>Fuso horário</th>
</tr>
</thead>
<tbody>
<tr>
<td>29/11/2021, às 08:52:30</td>
<td>São Paulo</td>
</tr>
<tr>
<td>29/11/2021, às 03:52:30</td>
<td>Los Angeles</td>
</tr>
<tr>
<td><strong>30</strong>/11/2021, às 01:52:30</td>
<td>Samoa</td>
</tr>
<tr>
<td>29/11/2021, às 11:52:30</td>
<td>UTC</td>
</tr>
</tbody>
</table>
<p>Todas as datas e horas acima correspondem ao mesmo timestamp (<code class="language-plaintext highlighter-rouge">1638186750973</code>): o instante (o ponto na linha do tempo) é o mesmo, o que muda é a data/hora correspondente, que varia conforme o timezone (fuso horário). Em cada parte do mundo, o mesmo timestamp corresponderá a uma data e/ou hora diferente, e esse ponto é crucial para entendermos os problemas que ocorrem ao manipular e formatar datas no JavaScript.</p>
<h2 id="obtendo-os-campos-da-data">Obtendo os campos da data</h2>
<p>Quando você usa métodos como <code class="language-plaintext highlighter-rouge">getDate()</code> ou <code class="language-plaintext highlighter-rouge">getHours()</code>, os valores retornados levam em conta o <strong>timezone que está configurando no ambiente no qual o código roda</strong> (seja o browser, o Node.js, <a href="https://deno.land/">Deno</a>, etc). O mesmo acontece se você imprime a data (com <code class="language-plaintext highlighter-rouge">console.log</code> ou <code class="language-plaintext highlighter-rouge">alert</code>, por exemplo) ou usa métodos como <code class="language-plaintext highlighter-rouge">toString()</code> ou <code class="language-plaintext highlighter-rouge">toLocaleString()</code>. Os valores de data e hora sempre estarão no timezone que estiver configurado no browser (<em>a partir de agora vou dizer apenas “browser”, mas entenda que estou me referindo ao ambiente no qual o código roda, podendo ser também o Node.js, <a href="https://deno.land/">Deno</a>, ou qualquer outro runtime</em>).</p>
<p>Porém, existem métodos que retornam os valores em UTC. Alguns são mais óbvios, como <code class="language-plaintext highlighter-rouge">getUTCDate()</code> e <code class="language-plaintext highlighter-rouge">getUTCHours()</code>, mas outros nem tanto, como <code class="language-plaintext highlighter-rouge">toISOString()</code> por exemplo. E é a partir daí que surgem os clássicos problemas de “<em>criei uma data mas ela fica com um dia a menos</em>”. Exemplo:</p>
<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// Atenção! Os resultados abaixo foram obtidos em um browser configurado</span>
<span class="c1">// com o timezone "Horário de Brasília"</span>
<span class="c1">// 29 de novembro de 2021, às 23:30</span>
<span class="c1">// Sim, novembro é 10 (porque janeiro é 0, fevereiro é 1, etc)</span>
<span class="kd">let</span> <span class="nx">data</span> <span class="o">=</span> <span class="k">new</span> <span class="nb">Date</span><span class="p">(</span><span class="mi">2021</span><span class="p">,</span> <span class="mi">10</span><span class="p">,</span> <span class="mi">29</span><span class="p">,</span> <span class="mi">23</span><span class="p">,</span> <span class="mi">30</span><span class="p">);</span>
<span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="nx">data</span><span class="p">.</span><span class="nx">toLocaleString</span><span class="p">(</span><span class="dl">'</span><span class="s1">pt-BR</span><span class="dl">'</span><span class="p">));</span> <span class="c1">// 29/11/2021 23:30:00</span>
<span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="nx">data</span><span class="p">.</span><span class="nx">toISOString</span><span class="p">());</span> <span class="c1">// 2021-11-30T02:30:00.000Z</span>
</code></pre></div></div>
<p>Primeiro eu crio uma data referente a 29 de novembro de 2021, às 23:30 (lembrando do <em>irritante</em> detalhe de que <a href="https://pt.stackoverflow.com/a/405998/112052">no <code class="language-plaintext highlighter-rouge">Date</code> do JavaScript, janeiro é zero</a>, por isso que novembro é <code class="language-plaintext highlighter-rouge">10</code>).</p>
<p>Mas internamente o <code class="language-plaintext highlighter-rouge">Date</code> só tem o valor do timestamp, e se você leu <a href="/blog/2019-05-02/o-que-e-timestamp" class="new-window">o respectivo post já indicado acima</a>, já sabe que para uma data e hora ser convertida para um timestamp, você precisa de um timezone. E neste caso o JavaScript usará o timezone que estiver configurado no browser. No meu ambiente, o browser está usando o “Horário de Brasília” (geralmente o browser usa o que está configurado no Sistema Operacional), então <strong>se o seu ambiente está com uma configuração de fuso horário diferente da minha, os resultados não necessariamente serão os mesmos</strong>.</p>
<p>Enfim, a data criada acima refere-se a 29 de novembro de 2021, às 23:30 no Horário de Brasília (pois este é o timezone configurado no meu browser). Ao imprimir a data com <code class="language-plaintext highlighter-rouge">toLocaleString()</code>, os valores de data e hora seguem o timezone do browser. Mas <code class="language-plaintext highlighter-rouge">toISOString()</code> retorna os valores em UTC (repare como o dia e a hora mudaram).</p>
<p>É daí que surgem aqueles problemas de “<em>criei uma data mas ela aparece com um dia/algumas horas a mais (ou a menos)</em>”. Graças à forma como o <code class="language-plaintext highlighter-rouge">Date</code> funciona (somado ao fato de alguns métodos usarem UTC e outros não), esse tipo de problema infelizmente ainda é muito comum.</p>
<h1 id="tá-e-como-eu-resolvo">Tá, e como eu resolvo?</h1>
<p>Depende do que você quer fazer.</p>
<p>Se quer que os valores de data e hora sigam o timezone do browser, use os métodos que retornam tais valores (como os <em>getters</em> e <code class="language-plaintext highlighter-rouge">toLocaleString</code>). Se quer os valores em UTC, use os métodos <code class="language-plaintext highlighter-rouge">getUTCXXX</code> e <code class="language-plaintext highlighter-rouge">toISOString</code>.</p>
<p>Se quer os valores em outro timezone, uma alternativa é passá-lo para <code class="language-plaintext highlighter-rouge">toLocaleString</code>. Exemplo:</p>
<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// criar Date correspondente ao timestamp 1638186750973</span>
<span class="kd">const</span> <span class="nx">d</span> <span class="o">=</span> <span class="k">new</span> <span class="nb">Date</span><span class="p">(</span><span class="mi">1638186750973</span><span class="p">);</span>
<span class="c1">// formatar usando timezones diferentes</span>
<span class="k">for</span> <span class="p">(</span><span class="kd">const</span> <span class="nx">tz</span> <span class="k">of</span> <span class="p">[</span><span class="dl">'</span><span class="s1">America/Sao_Paulo</span><span class="dl">'</span><span class="p">,</span> <span class="dl">'</span><span class="s1">America/Los_Angeles</span><span class="dl">'</span><span class="p">,</span> <span class="dl">'</span><span class="s1">Pacific/Apia</span><span class="dl">'</span><span class="p">,</span> <span class="dl">'</span><span class="s1">UTC</span><span class="dl">'</span><span class="p">])</span> <span class="p">{</span>
<span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="nx">d</span><span class="p">.</span><span class="nx">toLocaleString</span><span class="p">(</span><span class="dl">'</span><span class="s1">pt-BR</span><span class="dl">'</span><span class="p">,</span> <span class="p">{</span> <span class="na">timeZone</span><span class="p">:</span> <span class="nx">tz</span> <span class="p">}));</span>
<span class="p">}</span>
</code></pre></div></div>
<p>O primeiro parâmetro é o <em>locale</em>, que é explicado em mais detalhes na <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl#locale_identification_and_negotiation">documentação</a>. No exemplo acima, usei <code class="language-plaintext highlighter-rouge">pt-BR</code>, que corresponde ao português do Brasil (ou seja, o formato da data - no caso, “dd/mm/aaaa hh:mm:ss” - usa a configuração deste idioma), e eu imprimo a mesma data usando timezones diferentes. A saída é:</p>
<pre><code class="language-none">29/11/2021 08:52:30
29/11/2021 03:52:30
30/11/2021 01:52:30
29/11/2021 11:52:30
</code></pre>
<p>Repare que o <strong>formato</strong> é o mesmo (“dd/mm/aaaa hh:mm:ss” - pois é o que está configurado para o locale pt-BR), mas os valores de data e hora podem variar de acordo com o timezone.</p>
<h3 id="e-se-eu-quiser-outro-formato">E se eu quiser outro formato?</h3>
<p>Infelizmente o JavaScript não nos dá muita alternativa. O máximo que dá para fazer é mudar o locale passado para <code class="language-plaintext highlighter-rouge">toLocaleString</code>, mas ainda sim você está limitado aos formatos que já estão configurados para cada um (sem contar que o sistema pode não ter determinado(s) locale(s) instalado(s) - por exemplo, o <a href="https://github.com/nodejs/node/issues/8500#issuecomment-556520467">Node, antes da versão 13 não vinha com os locales instalados</a>). Há ainda a - um pouco mais rara, mas ainda sim possível - possibilidade do formato associado a um locale mudar. Por fim, há também o fato de <a href="https://stackoverflow.com/q/25574963">o formato retornado por <code class="language-plaintext highlighter-rouge">toLocaleString</code> não ser garantidamente o mesmo em todos os browsers</a>.</p>
<p>Sendo assim, se quiser um formato customizado, o jeito é usar os <em>getters</em> e construí-lo manualmente. Mas com isso você está limitado a usar o timezone do browser ou UTC, já que não há <em>getters</em> que obtém os valores de acordo com um timezone específico. Para formatar a data em um formato customizado (que não dependa do locale) <strong>e</strong> com os valores de data e hora referentes a um timezone que não seja o do browser e nem UTC, o jeito é recorrer a bibliotecas externas. Algumas opções são:</p>
<ul>
<li><a href="https://momentjs.com/">Moment.js</a> (neste caso precisa também do <a href="https://momentjs.com/timezone/">Moment Timezone</a>)</li>
<li><a href="https://date-fns.org/">date-fns</a></li>
<li><a href="https://moment.github.io/luxon/#/">Luxon</a></li>
</ul>
<h3 id="gambiarra">Gambiarra</h3>
<p>Se pesquisar um pouco, provavelmente você vai encontrar alguém sugerindo para mudar o valor do timestamp. Algo do tipo:</p>
<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// GAMBIARRA, NÃO FAÇA ISSO!</span>
<span class="kd">let</span> <span class="nx">data</span> <span class="o">=</span> <span class="k">new</span> <span class="nb">Date</span><span class="p">();</span> <span class="c1">// data atual</span>
<span class="c1">// subtrai a diferença em relação a UTC</span>
<span class="kd">let</span> <span class="nx">data2</span> <span class="o">=</span> <span class="k">new</span> <span class="nb">Date</span><span class="p">(</span><span class="nx">data</span><span class="p">.</span><span class="nx">valueOf</span><span class="p">()</span> <span class="o">-</span> <span class="nx">data</span><span class="p">.</span><span class="nx">getTimezoneOffset</span><span class="p">()</span> <span class="o">*</span> <span class="mi">60000</span><span class="p">);</span>
<span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="nx">data2</span><span class="p">.</span><span class="nx">toISOString</span><span class="p">());</span>
</code></pre></div></div>
<p>Como <code class="language-plaintext highlighter-rouge">toISOString()</code> retorna os valores de data e hora em UTC, então a ideia da “solução” acima é ajustar o timestamp, subtraindo a diferença em relação a UTC (que é o retorno de <code class="language-plaintext highlighter-rouge">getTimezoneOffset()</code>). Apesar de “funcionar” (mostra o valor “correto”), mudar o timestamp tem um problema, pois na verdade você está mudando o instante que a data representa.</p>
<p>Fazendo uma analogia, suponha que estou em São Paulo, e hoje é dia 29 de novembro de 2021, às 13:00. Neste mesmo instante, em Londres, já são 16:00. Mas vamos supor que meu computador está configurado com o fuso de Londres, e portanto ele mostra “16:00”, mas eu gostaria que ele mostrasse o Horário de Brasília. Eu posso arrumar isso de duas maneiras:</p>
<ol>
<li>mudando a configuração de fuso horário do computador, setando para o Horário de Brasília</li>
<li>mantendo a configuração de fuso horário, e atrasando o relógio em 3 horas</li>
</ol>
<p>Em ambos os casos, vai passar a mostrar o horário correto (13:00). Mas a segunda opção, apesar de “funcionar”, não está exatamente correta, pois na verdade o que eu fiz foi mudar o relógio para um instante diferente (13:00 em Londres, um instante que ocorreu três horas no passado). É isso que acontece quando você muda o timestamp, como no código-gambiarra acima: a data passa a corresponder a um instante completamente diferente (e se em algum lugar você estava contando que ela tivesse “a data atual”, bem, boa sorte tentando achar este bug).</p>
<p>Sem contar que as regras dos timezones <a href="https://www.timeanddate.com/news/time/">mudam o tempo todo</a>: países vivem adotando e cancelando o horário de verão (só para ficar no exemplo mais comum), e por isso não dá para contar que as diferenças com relação a UTC sempre serão fixas. Por isso qualquer código que mude o valor para uma quantidade arbitrária de horas está sujeito a falhar mais cedo ou mais tarde (a opção 2 acima, por exemplo, falha quando um dos países está em horário de verão). Somente mantendo o sistema atualizado (e sem gambiarras como o código acima) você garante que não será pego de surpresa.</p>
<h1 id="criando-uma-data-específica">Criando uma data específica</h1>
<p>Outros problemas similares ocorrem quando se quer criar uma data específica, ou quando você recebe uma string e quer convertê-la para data.</p>
<p>Um exempo clássico (outro caso de “<em>criei uma data mas ela fica com um dia a menos</em>”):</p>
<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// código rodando em um browser configurado com o Horário de Brasília</span>
<span class="c1">// configurações diferentes não necessariamente darão o mesmo resultado</span>
<span class="c1">// 29 de novembro de 2021</span>
<span class="kd">let</span> <span class="nx">data</span> <span class="o">=</span> <span class="k">new</span> <span class="nb">Date</span><span class="p">(</span><span class="dl">'</span><span class="s1">2021-11-29</span><span class="dl">'</span><span class="p">);</span>
<span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="nx">data</span><span class="p">.</span><span class="nx">toLocaleDateString</span><span class="p">(</span><span class="dl">'</span><span class="s1">pt-BR</span><span class="dl">'</span><span class="p">));</span> <span class="c1">// 28/11/2021</span>
</code></pre></div></div>
<p>Repare que quando você cria uma data com uma string, não tem mais aquela regra <em>irritante</em> de janeiro ser zero, fevereiro ser 1, etc. Aqui usa-se os valores corretos (por isso novembro é <code class="language-plaintext highlighter-rouge">11</code>). Mas ao imprimir a data, ela ficou com “um dia a menos” (repare que o dia é 28, e não 29).</p>
<p>Isso acontece porque, segundo a <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date#Timestamp_string">documentação</a>, quando passamos uma string no formato “AAAA-MM-DD” (sem as horas), a data é tratada como UTC. Além disso, <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date#Individual_date_and_time_component_values">também é dito</a> que se os campos de horário forem omitidos, seus valores são setados para zero.</p>
<p>Ou seja, <code class="language-plaintext highlighter-rouge">new Date('2021-11-29')</code> cria uma data referente à 29 de novembro de 2021, <strong>à meia-noite em UTC</strong>. Mas <code class="language-plaintext highlighter-rouge">toLocaleDateString</code> retorna a data no timezone do browser, e no caso o meu está configurado com o Horário de Brasília. E como “meia-noite em UTC” corresponde à 21:00 do dia anterior no Horário de Brasília (ou 22:00 quando está em horário de verão), dá essa diferença de “um dia”. Na verdade, se imprimirmos o horário dá para ver melhor o que aconteceu:</p>
<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// código rodando em um browser configurado com o Horário de Brasília</span>
<span class="c1">// configurações diferentes não necessariamente darão o mesmo resultado</span>
<span class="c1">// 29 de novembro de 2021</span>
<span class="kd">let</span> <span class="nx">data</span> <span class="o">=</span> <span class="k">new</span> <span class="nb">Date</span><span class="p">(</span><span class="dl">'</span><span class="s1">2021-11-29</span><span class="dl">'</span><span class="p">);</span>
<span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="nx">data</span><span class="p">.</span><span class="nx">toLocaleString</span><span class="p">(</span><span class="dl">'</span><span class="s1">pt-BR</span><span class="dl">'</span><span class="p">));</span> <span class="c1">// 28/11/2021 21:00:00</span>
</code></pre></div></div>
<p>Uma solução é passar os campos de horário, pois assim ele passa a considerar o timezone do browser:</p>
<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">let</span> <span class="nx">data</span> <span class="o">=</span> <span class="k">new</span> <span class="nb">Date</span><span class="p">(</span><span class="dl">'</span><span class="s1">2021-11-29T00:00</span><span class="dl">'</span><span class="p">);</span>
<span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="nx">data</span><span class="p">.</span><span class="nx">toLocaleString</span><span class="p">(</span><span class="dl">'</span><span class="s1">pt-BR</span><span class="dl">'</span><span class="p">));</span> <span class="c1">// 29/11/2021 00:00:00</span>
</code></pre></div></div>
<p>Sim, o simples fato de adicionar o horário faz com que a data e hora não use mais UTC, e passe a usar o timezone do browser. Não vou entrar no mérito de discutir se isso é “bom” ou se “faz sentido”, só vou repetir o que um professor de inglês que tive costumava dizer: “<em>Eu não inventei as regras, eu só as ensino</em>”.</p>
<p>Por fim, se você usar os valores numéricos, sempre é usado o timezone do browser, mesmo que você não passe os campos de horário (e claro, tem o detalhe de ter que subtrair 1 do mês):</p>
<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// 29 de novembro (que tem que ser 10, não 11) de 2021</span>
<span class="c1">// horário omitido, então ele usa "meia-noite no timezone do browser"</span>
<span class="kd">let</span> <span class="nx">data</span> <span class="o">=</span> <span class="k">new</span> <span class="nb">Date</span><span class="p">(</span><span class="mi">2021</span><span class="p">,</span> <span class="mi">10</span><span class="p">,</span> <span class="mi">29</span><span class="p">);</span>
<span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="nx">data</span><span class="p">.</span><span class="nx">toLocaleString</span><span class="p">(</span><span class="dl">'</span><span class="s1">pt-BR</span><span class="dl">'</span><span class="p">));</span> <span class="c1">// 29/11/2021 00:00:00</span>
</code></pre></div></div>
<p>Resumindo:</p>
<table>
<thead>
<tr>
<th style="text-align: left">Argumentos passados ao construtor</th>
<th style="text-align: left">Sem horário</th>
<th style="text-align: left">Com horário</th>
</tr>
</thead>
<tbody>
<tr>
<td style="text-align: left">string</td>
<td style="text-align: left">UTC</td>
<td style="text-align: left">timezone do browser</td>
</tr>
<tr>
<td style="text-align: left">valores numéricos</td>
<td style="text-align: left">timezone do browser</td>
<td style="text-align: left">timezone do browser</td>
</tr>
</tbody>
</table>
<h3 id="não-use-qualquer-formato-de-string">Não use qualquer formato de string</h3>
<p>Ao se passar uma string para o construtor de <code class="language-plaintext highlighter-rouge">Date</code>, o <strong>único</strong> formato <a href="https://262.ecma-international.org/5.1/#sec-15.9.1.15">garantido pela especificação da linguagem</a> que funciona em qualquer ambiente é o definido pela <a href="https://en.wikipedia.org/wiki/ISO_8601">norma ISO 8601</a>. No caso, é o que foi usado nos exemplos acima: “AAAA-MM-DD” ou “AAAA-MM-DDTHH:MM” (sim, tem uma letra “T” maiúscula entre a data e a hora). <strong>Qualquer outro formato é dependente de implementação e não é garantido que funcione em todos os ambientes</strong>.</p>
<blockquote>
<p>“<em>Ah, mas eu sempre usei o formato XYZ e funcionou</em>”</p>
</blockquote>
<p><a href="https://blog.codinghorror.com/the-works-on-my-machine-certification-program/">Parabéns!</a> 🙂</p>
<p>Tudo bem que muitos formatos “funcionam” em vários browsers diferentes, mas se não quer depender da sorte, eu sugiro que qualquer string que você receber seja devidamente quebrada em valores numéricos ou convertida para ISO 8601, e só depois passe esses valores para o construtor.</p>
<p>Por exemplo, testando o formato “dd/mm/aaaa” no Node e Chrome:</p>
<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// 11 de setembro (e não 9 de novembro)</span>
<span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="k">new</span> <span class="nb">Date</span><span class="p">(</span><span class="dl">'</span><span class="s1">09/11/2021</span><span class="dl">'</span><span class="p">).</span><span class="nx">toLocaleString</span><span class="p">(</span><span class="dl">'</span><span class="s1">pt-BR</span><span class="dl">'</span><span class="p">));</span> <span class="c1">// 11/09/2021 00:00:00</span>
<span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="k">new</span> <span class="nb">Date</span><span class="p">(</span><span class="dl">'</span><span class="s1">29/11/2021</span><span class="dl">'</span><span class="p">).</span><span class="nx">toLocaleString</span><span class="p">(</span><span class="dl">'</span><span class="s1">pt-BR</span><span class="dl">'</span><span class="p">));</span> <span class="c1">// Invalid Date</span>
</code></pre></div></div>
<p>Este formato é interpretado como “mês/dia/ano”, e por isso o segundo caso sequer resulta em uma data válida (pode ser que funcione em algum outro browser, mas não tenho certeza). Neste caso, temos que tratar a string manualmente para extrair os valores corretos dela. E aí surgem códigos “bonitos” como esse:</p>
<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// obter os valores numéricos da string</span>
<span class="kd">let</span> <span class="p">[</span><span class="nx">dia</span><span class="p">,</span> <span class="nx">mes</span><span class="p">,</span> <span class="nx">ano</span><span class="p">]</span> <span class="o">=</span> <span class="dl">'</span><span class="s1">29/11/2021</span><span class="dl">'</span><span class="p">.</span><span class="nx">split</span><span class="p">(</span><span class="dl">'</span><span class="s1">/</span><span class="dl">'</span><span class="p">).</span><span class="nx">map</span><span class="p">(</span><span class="nx">n</span> <span class="o">=></span> <span class="nb">parseInt</span><span class="p">(</span><span class="nx">n</span><span class="p">));</span>
<span class="kd">let</span> <span class="nx">data</span> <span class="o">=</span> <span class="k">new</span> <span class="nb">Date</span><span class="p">(</span><span class="nx">ano</span><span class="p">,</span> <span class="nx">mes</span> <span class="o">-</span> <span class="mi">1</span><span class="p">,</span> <span class="nx">dia</span><span class="p">);</span> <span class="c1">// lembrar de subtrair 1 do mês</span>
<span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="nx">data</span><span class="p">.</span><span class="nx">toLocaleString</span><span class="p">(</span><span class="dl">'</span><span class="s1">pt-BR</span><span class="dl">'</span><span class="p">));</span> <span class="c1">// 29/11/2021 00:00:00</span>
</code></pre></div></div>
<p>Outra opção é usar alguma biblioteca externa, como já sugerido acima, que possuem opções de <em>parsing</em> mais flexíveis, na qual é possível indicar o formato. Por exemplo, no Moment.js seria algo como:</p>
<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// 29 de novembro de 2021, à meia-noite no timezone do browser</span>
<span class="kd">let</span> <span class="nx">data</span> <span class="o">=</span> <span class="nx">moment</span><span class="p">(</span><span class="dl">'</span><span class="s1">29/11/2021</span><span class="dl">'</span><span class="p">,</span> <span class="dl">'</span><span class="s1">DD/MM/YYYY</span><span class="dl">'</span><span class="p">);</span>
<span class="c1">// se quiser converter para Date</span>
<span class="kd">let</span> <span class="nx">jsDate</span> <span class="o">=</span> <span class="nx">date</span><span class="p">.</span><span class="nx">toDate</span><span class="p">();</span>
</code></pre></div></div>
<p>E todas as libs já mencionadas possuem formas parecidas de obter a data a partir de uma string, bastando especificar o formato correto.</p>
<hr />
<p>Para mais detalhes sobre <code class="language-plaintext highlighter-rouge">Date</code>, e para se aprofundar nos assuntos abordados neste post, leia <a href="https://pt.stackoverflow.com/q/456089/112052">aqui</a>, <a href="https://pt.stackoverflow.com/q/455437/112052">aqui</a>, <a href="https://pt.stackoverflow.com/q/408160/112052">aqui</a>, <a href="https://pt.stackoverflow.com/a/416172/112052">aqui</a>, <a href="https://pt.stackoverflow.com/a/344030/112052">aqui</a>, <a href="https://pt.stackoverflow.com/q/498025/112052">aqui</a> e <a href="https://pt.stackoverflow.com/a/494302/112052">aqui</a>.</p>Hugo KotsuboEu poderia simplesmente jogar um monte de código e pronto (os famosos posts “Tente isso”, que só deixam código e não explicam nada), mas acho que é muito mais importante que você entenda como funciona o Date do JavaScript, para só depois entender os problemas mais comuns que acontecem ao usá-lo e formatá-lo. O que é o Date do JavaScript Apesar do nome, o Date não é exatamente uma data. Pelo menos não no sentido de ter um único valor de dia, mês, ano, hora, minuto e segundo. Segundo a especificação da linguagem, o único valor que um Date tem é a quantidade de milissegundos que se passaram desde 1970-01-01T00:00Z (1 de janeiro de 1970, à meia-noite, em UTC). Esse valor também é chamado de timestamp, e já foi explicado em detalhes em outro post (leitura sugerida para entender melhor). Mas apenas para dar um exemplo: eu rodei new Date().valueOf() agora há pouco e o resultado foi o timestamp 1638186750973. Esse valor é o mesmo no mundo todo (qualquer um que tivesse rodado o mesmo código no mesmo instante teria o mesmo valor). Só que este timestamp corresponde às seguintes datas e horas: Data e hora Fuso horário 29/11/2021, às 08:52:30 São Paulo 29/11/2021, às 03:52:30 Los Angeles 30/11/2021, às 01:52:30 Samoa 29/11/2021, às 11:52:30 UTC Todas as datas e horas acima correspondem ao mesmo timestamp (1638186750973): o instante (o ponto na linha do tempo) é o mesmo, o que muda é a data/hora correspondente, que varia conforme o timezone (fuso horário). Em cada parte do mundo, o mesmo timestamp corresponderá a uma data e/ou hora diferente, e esse ponto é crucial para entendermos os problemas que ocorrem ao manipular e formatar datas no JavaScript. Obtendo os campos da data Quando você usa métodos como getDate() ou getHours(), os valores retornados levam em conta o timezone que está configurando no ambiente no qual o código roda (seja o browser, o Node.js, Deno, etc). O mesmo acontece se você imprime a data (com console.log ou alert, por exemplo) ou usa métodos como toString() ou toLocaleString(). Os valores de data e hora sempre estarão no timezone que estiver configurado no browser (a partir de agora vou dizer apenas “browser”, mas entenda que estou me referindo ao ambiente no qual o código roda, podendo ser também o Node.js, Deno, ou qualquer outro runtime). Porém, existem métodos que retornam os valores em UTC. Alguns são mais óbvios, como getUTCDate() e getUTCHours(), mas outros nem tanto, como toISOString() por exemplo. E é a partir daí que surgem os clássicos problemas de “criei uma data mas ela fica com um dia a menos”. Exemplo: // Atenção! Os resultados abaixo foram obtidos em um browser configurado // com o timezone "Horário de Brasília" // 29 de novembro de 2021, às 23:30 // Sim, novembro é 10 (porque janeiro é 0, fevereiro é 1, etc) let data = new Date(2021, 10, 29, 23, 30); console.log(data.toLocaleString('pt-BR')); // 29/11/2021 23:30:00 console.log(data.toISOString()); // 2021-11-30T02:30:00.000Z Primeiro eu crio uma data referente a 29 de novembro de 2021, às 23:30 (lembrando do irritante detalhe de que no Date do JavaScript, janeiro é zero, por isso que novembro é 10). Mas internamente o Date só tem o valor do timestamp, e se você leu o respectivo post já indicado acima, já sabe que para uma data e hora ser convertida para um timestamp, você precisa de um timezone. E neste caso o JavaScript usará o timezone que estiver configurado no browser. No meu ambiente, o browser está usando o “Horário de Brasília” (geralmente o browser usa o que está configurado no Sistema Operacional), então se o seu ambiente está com uma configuração de fuso horário diferente da minha, os resultados não necessariamente serão os mesmos. Enfim, a data criada acima refere-se a 29 de novembro de 2021, às 23:30 no Horário de Brasília (pois este é o timezone configurado no meu browser). Ao imprimir a data com toLocaleString(), os valores de data e hora seguem o timezone do browser. Mas toISOString() retorna os valores em UTC (repare como o dia e a hora mudaram). É daí que surgem aqueles problemas de “criei uma data mas ela aparece com um dia/algumas horas a mais (ou a menos)”. Graças à forma como o Date funciona (somado ao fato de alguns métodos usarem UTC e outros não), esse tipo de problema infelizmente ainda é muito comum. Tá, e como eu resolvo? Depende do que você quer fazer. Se quer que os valores de data e hora sigam o timezone do browser, use os métodos que retornam tais valores (como os getters e toLocaleString). Se quer os valores em UTC, use os métodos getUTCXXX e toISOString. Se quer os valores em outro timezone, uma alternativa é passá-lo para toLocaleString. Exemplo: // criar Date correspondente ao timestamp 1638186750973 const d = new Date(1638186750973); // formatar usando timezones diferentes for (const tz of ['America/Sao_Paulo', 'America/Los_Angeles', 'Pacific/Apia', 'UTC']) { console.log(d.toLocaleString('pt-BR', { timeZone: tz })); } O primeiro parâmetro é o locale, que é explicado em mais detalhes na documentação. No exemplo acima, usei pt-BR, que corresponde ao português do Brasil (ou seja, o formato da data - no caso, “dd/mm/aaaa hh:mm:ss” - usa a configuração deste idioma), e eu imprimo a mesma data usando timezones diferentes. A saída é: 29/11/2021 08:52:30 29/11/2021 03:52:30 30/11/2021 01:52:30 29/11/2021 11:52:30 Repare que o formato é o mesmo (“dd/mm/aaaa hh:mm:ss” - pois é o que está configurado para o locale pt-BR), mas os valores de data e hora podem variar de acordo com o timezone. E se eu quiser outro formato? Infelizmente o JavaScript não nos dá muita alternativa. O máximo que dá para fazer é mudar o locale passado para toLocaleString, mas ainda sim você está limitado aos formatos que já estão configurados para cada um (sem contar que o sistema pode não ter determinado(s) locale(s) instalado(s) - por exemplo, o Node, antes da versão 13 não vinha com os locales instalados). Há ainda a - um pouco mais rara, mas ainda sim possível - possibilidade do formato associado a um locale mudar. Por fim, há também o fato de o formato retornado por toLocaleString não ser garantidamente o mesmo em todos os browsers. Sendo assim, se quiser um formato customizado, o jeito é usar os getters e construí-lo manualmente. Mas com isso você está limitado a usar o timezone do browser ou UTC, já que não há getters que obtém os valores de acordo com um timezone específico. Para formatar a data em um formato customizado (que não dependa do locale) e com os valores de data e hora referentes a um timezone que não seja o do browser e nem UTC, o jeito é recorrer a bibliotecas externas. Algumas opções são: Moment.js (neste caso precisa também do Moment Timezone) date-fns Luxon Gambiarra Se pesquisar um pouco, provavelmente você vai encontrar alguém sugerindo para mudar o valor do timestamp. Algo do tipo: // GAMBIARRA, NÃO FAÇA ISSO! let data = new Date(); // data atual // subtrai a diferença em relação a UTC let data2 = new Date(data.valueOf() - data.getTimezoneOffset() * 60000); console.log(data2.toISOString()); Como toISOString() retorna os valores de data e hora em UTC, então a ideia da “solução” acima é ajustar o timestamp, subtraindo a diferença em relação a UTC (que é o retorno de getTimezoneOffset()). Apesar de “funcionar” (mostra o valor “correto”), mudar o timestamp tem um problema, pois na verdade você está mudando o instante que a data representa. Fazendo uma analogia, suponha que estou em São Paulo, e hoje é dia 29 de novembro de 2021, às 13:00. Neste mesmo instante, em Londres, já são 16:00. Mas vamos supor que meu computador está configurado com o fuso de Londres, e portanto ele mostra “16:00”, mas eu gostaria que ele mostrasse o Horário de Brasília. Eu posso arrumar isso de duas maneiras: mudando a configuração de fuso horário do computador, setando para o Horário de Brasília mantendo a configuração de fuso horário, e atrasando o relógio em 3 horas Em ambos os casos, vai passar a mostrar o horário correto (13:00). Mas a segunda opção, apesar de “funcionar”, não está exatamente correta, pois na verdade o que eu fiz foi mudar o relógio para um instante diferente (13:00 em Londres, um instante que ocorreu três horas no passado). É isso que acontece quando você muda o timestamp, como no código-gambiarra acima: a data passa a corresponder a um instante completamente diferente (e se em algum lugar você estava contando que ela tivesse “a data atual”, bem, boa sorte tentando achar este bug). Sem contar que as regras dos timezones mudam o tempo todo: países vivem adotando e cancelando o horário de verão (só para ficar no exemplo mais comum), e por isso não dá para contar que as diferenças com relação a UTC sempre serão fixas. Por isso qualquer código que mude o valor para uma quantidade arbitrária de horas está sujeito a falhar mais cedo ou mais tarde (a opção 2 acima, por exemplo, falha quando um dos países está em horário de verão). Somente mantendo o sistema atualizado (e sem gambiarras como o código acima) você garante que não será pego de surpresa. Criando uma data específica Outros problemas similares ocorrem quando se quer criar uma data específica, ou quando você recebe uma string e quer convertê-la para data. Um exempo clássico (outro caso de “criei uma data mas ela fica com um dia a menos”): // código rodando em um browser configurado com o Horário de Brasília // configurações diferentes não necessariamente darão o mesmo resultado // 29 de novembro de 2021 let data = new Date('2021-11-29'); console.log(data.toLocaleDateString('pt-BR')); // 28/11/2021 Repare que quando você cria uma data com uma string, não tem mais aquela regra irritante de janeiro ser zero, fevereiro ser 1, etc. Aqui usa-se os valores corretos (por isso novembro é 11). Mas ao imprimir a data, ela ficou com “um dia a menos” (repare que o dia é 28, e não 29). Isso acontece porque, segundo a documentação, quando passamos uma string no formato “AAAA-MM-DD” (sem as horas), a data é tratada como UTC. Além disso, também é dito que se os campos de horário forem omitidos, seus valores são setados para zero. Ou seja, new Date('2021-11-29') cria uma data referente à 29 de novembro de 2021, à meia-noite em UTC. Mas toLocaleDateString retorna a data no timezone do browser, e no caso o meu está configurado com o Horário de Brasília. E como “meia-noite em UTC” corresponde à 21:00 do dia anterior no Horário de Brasília (ou 22:00 quando está em horário de verão), dá essa diferença de “um dia”. Na verdade, se imprimirmos o horário dá para ver melhor o que aconteceu: // código rodando em um browser configurado com o Horário de Brasília // configurações diferentes não necessariamente darão o mesmo resultado // 29 de novembro de 2021 let data = new Date('2021-11-29'); console.log(data.toLocaleString('pt-BR')); // 28/11/2021 21:00:00 Uma solução é passar os campos de horário, pois assim ele passa a considerar o timezone do browser: let data = new Date('2021-11-29T00:00'); console.log(data.toLocaleString('pt-BR')); // 29/11/2021 00:00:00 Sim, o simples fato de adicionar o horário faz com que a data e hora não use mais UTC, e passe a usar o timezone do browser. Não vou entrar no mérito de discutir se isso é “bom” ou se “faz sentido”, só vou repetir o que um professor de inglês que tive costumava dizer: “Eu não inventei as regras, eu só as ensino”. Por fim, se você usar os valores numéricos, sempre é usado o timezone do browser, mesmo que você não passe os campos de horário (e claro, tem o detalhe de ter que subtrair 1 do mês): // 29 de novembro (que tem que ser 10, não 11) de 2021 // horário omitido, então ele usa "meia-noite no timezone do browser" let data = new Date(2021, 10, 29); console.log(data.toLocaleString('pt-BR')); // 29/11/2021 00:00:00 Resumindo: Argumentos passados ao construtor Sem horário Com horário string UTC timezone do browser valores numéricos timezone do browser timezone do browser Não use qualquer formato de string Ao se passar uma string para o construtor de Date, o único formato garantido pela especificação da linguagem que funciona em qualquer ambiente é o definido pela norma ISO 8601. No caso, é o que foi usado nos exemplos acima: “AAAA-MM-DD” ou “AAAA-MM-DDTHH:MM” (sim, tem uma letra “T” maiúscula entre a data e a hora). Qualquer outro formato é dependente de implementação e não é garantido que funcione em todos os ambientes. “Ah, mas eu sempre usei o formato XYZ e funcionou” Parabéns! 🙂 Tudo bem que muitos formatos “funcionam” em vários browsers diferentes, mas se não quer depender da sorte, eu sugiro que qualquer string que você receber seja devidamente quebrada em valores numéricos ou convertida para ISO 8601, e só depois passe esses valores para o construtor. Por exemplo, testando o formato “dd/mm/aaaa” no Node e Chrome: // 11 de setembro (e não 9 de novembro) console.log(new Date('09/11/2021').toLocaleString('pt-BR')); // 11/09/2021 00:00:00 console.log(new Date('29/11/2021').toLocaleString('pt-BR')); // Invalid Date Este formato é interpretado como “mês/dia/ano”, e por isso o segundo caso sequer resulta em uma data válida (pode ser que funcione em algum outro browser, mas não tenho certeza). Neste caso, temos que tratar a string manualmente para extrair os valores corretos dela. E aí surgem códigos “bonitos” como esse: // obter os valores numéricos da string let [dia, mes, ano] = '29/11/2021'.split('/').map(n => parseInt(n)); let data = new Date(ano, mes - 1, dia); // lembrar de subtrair 1 do mês console.log(data.toLocaleString('pt-BR')); // 29/11/2021 00:00:00 Outra opção é usar alguma biblioteca externa, como já sugerido acima, que possuem opções de parsing mais flexíveis, na qual é possível indicar o formato. Por exemplo, no Moment.js seria algo como: // 29 de novembro de 2021, à meia-noite no timezone do browser let data = moment('29/11/2021', 'DD/MM/YYYY'); // se quiser converter para Date let jsDate = date.toDate(); E todas as libs já mencionadas possuem formas parecidas de obter a data a partir de uma string, bastando especificar o formato correto. Para mais detalhes sobre Date, e para se aprofundar nos assuntos abordados neste post, leia aqui, aqui, aqui, aqui, aqui, aqui e aqui.Datas/Horas vs Durações2021-07-05T19:00:00-03:002021-07-05T19:00:00-03:00https://hkotsubo.github.io/blog/2021-07-05/datas-vs-duracoes<p>Considere as duas frases abaixo:</p>
<ul>
<li>A filme passou às duas horas da tarde.</li>
<li>A filme tem duração de duas horas.</li>
</ul>
<p>Apesar de ambas usarem “<em>duas horas</em>”, em cada uma o significado é diferente. Na primeira frase, “duas horas” indica um <strong>horário</strong>: um momento específico do dia. Na segunda frase, “duas horas” indica uma <strong>duração</strong>: uma quantidade de tempo.</p>
<p>Apesar de ambos usarem as mesmas palavras - embora no primeiro caso tenha sido necessário usar “da tarde” para desambiguar - eles são conceitos diferentes. Uma data e/ou um horário <strong>não</strong> é o mesmo que uma duração. Datas representam pontos específicos no calendário e horários representam momentos específicos do dia, e durações são apenas quantidades de tempo, sem necessariamente estarem relacionadas a uma data e hora específicas (por exemplo, na segunda frase, não é dito que horas o filme começou; na verdade nem diz se ele foi exibido de fato, só se sabe quanto tempo ele dura).</p>
<p>O que confunde é que ambos usam os mesmos termos (anos, meses, dias, horas, minutos e segundos), e até mesmo a representação pode ser igual: um relógio mostra <code class="language-plaintext highlighter-rouge">10:00:00</code> para indicar que são 10 horas da manhã, um cronômetro mostra <code class="language-plaintext highlighter-rouge">10:00:00</code> para mostrar que já se passaram 10 horas.</p>
<p>Outro detalhe é que esses conceitos, embora não sejam a mesma coisa, estão relacionados: se eu tenho uma data e somo uma duração, o resultado é outra data (ex: dia 10 de janeiro somado com uma duração de 3 dias resulta em 13 de janeiro), e se eu calculo a diferença entre duas datas, o resultado é uma duração (entre 10 e 13 de janeiro, a duração é de 3 dias).</p>
<p>E como são coisas diferentes, tratar um como se fosse o outro nem sempre trará os resultados esperados. Junte a isso o suporte nem sempre adequado das linguagens, e temos códigos confusos que muitas vezes só funcionam por coincidência (quando funcionam).</p>
<hr />
<p>Um exemplo comum é, dada uma tabela com horários de entrada e saída de um funcionário, calcular quantas horas foram trabalhadas no total.</p>
<table>
<thead>
<tr>
<th>Entrada</th>
<th>Saída</th>
</tr>
</thead>
<tbody>
<tr>
<td>08:00</td>
<td>17:00</td>
</tr>
<tr>
<td>08:15</td>
<td>18:30</td>
</tr>
<tr>
<td>09:30</td>
<td>18:30</td>
</tr>
<tr>
<td>etc</td>
<td>…</td>
</tr>
</tbody>
</table>
<p>Os dados da tabela são horários (momentos específicos do dia), mas eu quero calcular a duração (o total de horas). Um erro comum é pegar a diferença entre a entrada e saída, e tratá-la como um horário. Por exemplo, na primeira linha, o total de horas é 9 (foram trabalhadas 9 horas naquele dia), mas o programador guarda essa informação como se fosse um horário (<code class="language-plaintext highlighter-rouge">09:00</code> - que na verdade é 9 da manhã, e não uma duração de 9 horas). Já na segunda linha, a duração é de 10 horas e 15 minutos, e na terceira, de 9 horas.</p>
<p>Se eu guardar esses valores como horários (usando algum tipo específico, caso a linguagem suporte), geralmente não vai ser possível obter o total (que é de 28 horas e 15 minutos), já que os tipos de data/hora não permitem valores acima de 24 para as horas - e nem vou entrar no mérito de que “somar horas” é uma operação que sequer faz sentido: quanto é “9 da manhã” mais “10 e 15 da manhã”?</p>
<p>Agora se eu tratar esses valores como durações, aí sim faz sentido: uma duração de 9 horas mais outra duração de 10 horas e 15 minutos é igual a uma duração de 19 horas e 15 minutos. E não há o limite de 24 horas, como há para os horários. Para as linguagens que suportam um tipo específico para durações, isto é perfeitamente possível. Mas quando não há um tipo disponível, você pode criar o seu próprio, ou então trabalhar com um número mesmo (que guarda, por exemplo, o tempo total em minutos, ou segundos, ou qualquer unidade que você precisar).</p>
<p>O importante é não confundir os conceitos e não usar um quando na verdade se deve usar outro. Um erro <strong>muito</strong> comum é tentar usar uma data de qualquer maneira (com gambiarras que às vezes podem “funcionar”, mas geralmente é por coincidência), quando uma duração seria bem mais apropriado e simples (e o mais importante, dando resultados corretos, sem depender de coincidências). É só ver o que aconteceu <a href="https://pt.stackoverflow.com/a/370220/112052">aqui</a>, por exemplo (apesar de todos os votos positivos, está completamente errado, e na <a href="https://pt.stackoverflow.com/a/370227/112052">minha resposta</a> eu explico os motivos)<sup id="fnref:rant" role="doc-noteref"><a href="#fn:rant" class="footnote" rel="footnote">1</a></sup>.</p>
<hr />
<p>Vamos ver então como seria uma solução para este problema. Lembrando que <strong>os códigos abaixo focam neste caso específico</strong>, em que temos apenas o horário (horas e minutos). Se tivesse que calcular considerando os dias, meses e/ou anos, iria precisar de modificações e adaptações para cada caso. Mas o importante é ter em mente a ideia principal: nunca confundir datas/horas com durações, e nunca tratar um como se fosse o outro.</p>
<p>Abaixo tem soluções em algumas linguagens (você pode usar os links abaixo para ir direto para uma delas):</p>
<ul>
<li><a href="#java">Java</a></li>
<li><a href="#c">C#</a></li>
<li><a href="#python">Python</a></li>
<li><a href="#php">PHP</a></li>
<li><a href="#javascript">JavaScript</a></li>
</ul>
<h3 id="java">Java</h3>
<p>Se você estiver usando o Java >= 8, use a <a href="https://docs.oracle.com/javase/8/docs/api/java/time/package-summary.html">API <code class="language-plaintext highlighter-rouge">java.time</code></a>. Para representar um horário (<strong>somente</strong> hora, minuto, segundo e frações de segundo), você pode usar a classe <a href="https://docs.oracle.com/javase/8/docs/api/java/time/LocalTime.html"><code class="language-plaintext highlighter-rouge">java.time.LocalTime</code></a>. E para calcular a diferença, você pode usar um <a href="https://docs.oracle.com/javase/8/docs/api/java/time/Duration.html"><code class="language-plaintext highlighter-rouge">Duration</code></a>, algo assim:</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nc">LocalTime</span> <span class="n">entrada</span> <span class="o">=</span> <span class="nc">LocalTime</span><span class="o">.</span><span class="na">parse</span><span class="o">(</span><span class="s">"08:00"</span><span class="o">);</span>
<span class="nc">LocalTime</span> <span class="n">saida</span> <span class="o">=</span> <span class="nc">LocalTime</span><span class="o">.</span><span class="na">parse</span><span class="o">(</span><span class="s">"17:00"</span><span class="o">);</span>
<span class="nc">Duration</span> <span class="n">duracao</span> <span class="o">=</span> <span class="nc">Duration</span><span class="o">.</span><span class="na">between</span><span class="o">(</span><span class="n">entrada</span><span class="o">,</span> <span class="n">saida</span><span class="o">);</span>
</code></pre></div></div>
<p>Para calcular o total, você poderia usar um <em>loop</em>:</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nc">Duration</span> <span class="n">total</span> <span class="o">=</span> <span class="nc">Duration</span><span class="o">.</span><span class="na">ZERO</span><span class="o">;</span>
<span class="k">for</span> <span class="o">(</span><span class="nc">Horarios</span> <span class="nl">h:</span> <span class="n">listaHorarios</span><span class="o">)</span> <span class="o">{</span>
<span class="nc">LocalTime</span> <span class="n">entrada</span> <span class="o">=</span> <span class="nc">LocalTime</span><span class="o">.</span><span class="na">parse</span><span class="o">(</span><span class="n">h</span><span class="o">.</span><span class="na">getHorarioEntrada</span><span class="o">());</span>
<span class="nc">LocalTime</span> <span class="n">saida</span> <span class="o">=</span> <span class="nc">LocalTime</span><span class="o">.</span><span class="na">parse</span><span class="o">(</span><span class="n">h</span><span class="o">.</span><span class="na">getHorarioSaida</span><span class="o">());</span>
<span class="n">total</span> <span class="o">=</span> <span class="n">total</span><span class="o">.</span><span class="na">plus</span><span class="o">(</span><span class="nc">Duration</span><span class="o">.</span><span class="na">between</span><span class="o">(</span><span class="n">entrada</span><span class="o">,</span> <span class="n">saida</span><span class="o">));</span> <span class="c1">// plus() retorna outro Duration com o resultado da soma</span>
<span class="o">}</span>
</code></pre></div></div>
<p>Já para obter o resultado, infelizmente não há uma forma “limpa” e direta de formatar a saída, e você terá que fazer manualmente. A partir do Java 9 você pode usar métodos como <code class="language-plaintext highlighter-rouge">toHoursPart()</code> e <code class="language-plaintext highlighter-rouge">toMinutesPart()</code>:</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kt">long</span> <span class="n">horas</span> <span class="o">=</span> <span class="n">total</span><span class="o">.</span><span class="na">toHoursPart</span><span class="o">();</span>
<span class="kt">long</span> <span class="n">minutos</span> <span class="o">=</span> <span class="n">total</span><span class="o">.</span><span class="na">toMinutesPart</span><span class="o">();</span>
</code></pre></div></div>
<p>Mas no Java 8, a conta tem que ser feita manualmente:</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kt">long</span> <span class="n">minutos</span> <span class="o">=</span> <span class="n">total</span><span class="o">.</span><span class="na">toMinutes</span><span class="o">();</span>
<span class="kt">long</span> <span class="n">horas</span> <span class="o">=</span> <span class="n">minutos</span> <span class="o">/</span> <span class="mi">60</span><span class="o">;</span>
<span class="n">minutos</span> <span class="o">%=</span> <span class="mi">60</span><span class="o">;</span>
</code></pre></div></div>
<p>Isso porque <code class="language-plaintext highlighter-rouge">toMinutes()</code> retorna o total de minutos correspondente à duração (por exemplo, se a duração for de 28 horas e 15 minutos, <code class="language-plaintext highlighter-rouge">toMinutes()</code> retorna <code class="language-plaintext highlighter-rouge">1695</code>). Já <code class="language-plaintext highlighter-rouge">toMinutesPart()</code> retorna <code class="language-plaintext highlighter-rouge">15</code>.</p>
<p>Outra forma é usar um <code class="language-plaintext highlighter-rouge">java.time.temporal.ChronoUnit</code> e obter o total em minutos, por exemplo:</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kt">long</span> <span class="n">totalMinutos</span> <span class="o">=</span> <span class="mi">0</span><span class="o">;</span>
<span class="k">for</span> <span class="o">(</span><span class="nc">Horarios</span> <span class="nl">h:</span> <span class="n">listaHorarios</span><span class="o">)</span> <span class="o">{</span>
<span class="nc">LocalTime</span> <span class="n">entrada</span> <span class="o">=</span> <span class="nc">LocalTime</span><span class="o">.</span><span class="na">parse</span><span class="o">(</span><span class="n">h</span><span class="o">.</span><span class="na">getHorarioEntrada</span><span class="o">());</span>
<span class="nc">LocalTime</span> <span class="n">saida</span> <span class="o">=</span> <span class="nc">LocalTime</span><span class="o">.</span><span class="na">parse</span><span class="o">(</span><span class="n">h</span><span class="o">.</span><span class="na">getHorarioSaida</span><span class="o">());</span>
<span class="n">totalMinutos</span> <span class="o">+=</span> <span class="nc">ChronoUnit</span><span class="o">.</span><span class="na">MINUTES</span><span class="o">.</span><span class="na">between</span><span class="o">(</span><span class="n">entrada</span><span class="o">,</span> <span class="n">entrada</span><span class="o">);</span>
<span class="o">}</span>
<span class="c1">// depois, "quebre" o valor</span>
<span class="kt">long</span> <span class="n">horas</span> <span class="o">=</span> <span class="n">totalMinutos</span> <span class="o">/</span> <span class="mi">60</span><span class="o">;</span>
<span class="kt">long</span> <span class="n">minutos</span> <span class="o">=</span> <span class="n">totalMinutos</span> <span class="o">%</span> <span class="mi">60</span><span class="o">;</span>
</code></pre></div></div>
<p>Lembrando que neste caso específico eu só tenho as horas e minutos, por isso ter o total em minutos é adequado. Mas se tivesse também os segundos e/ou frações de segundos, aí você teria que considerá-los (e aí acho que usar um <code class="language-plaintext highlighter-rouge">Duration</code> simplifica as coisas, pois internamente ele já trata desses detalhes).</p>
<p>Para Java <= 7, não há uma classe específica para durações, e o único jeito é fazer o cálculo manualmente (similar o que foi feito abaixo com <a href="#php">PHP</a>, por exemplo).</p>
<h3 id="c">C#</h3>
<p>Em C#, o horário pode ser representado por um <a href="https://docs.microsoft.com/en-us/dotnet/api/system.datetime?view=netframework-4.7.2"><code class="language-plaintext highlighter-rouge">DateTime</code></a>, e uma duração, por um <a href="https://docs.microsoft.com/en-us/dotnet/api/system.timespan?view=net-5.0"><code class="language-plaintext highlighter-rouge">TimeSpan</code></a>. A ideia geral é bem similar ao código anterior:</p>
<div class="language-c# highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">TimeSpan</span> <span class="n">total</span> <span class="p">=</span> <span class="n">TimeSpan</span><span class="p">.</span><span class="n">Zero</span><span class="p">;</span>
<span class="k">foreach</span> <span class="p">(</span><span class="n">Horarios</span> <span class="n">h</span> <span class="k">in</span> <span class="n">listaHorarios</span><span class="p">)</span>
<span class="p">{</span>
<span class="k">if</span> <span class="p">(</span><span class="n">DateTime</span><span class="p">.</span><span class="nf">TryParseExact</span><span class="p">(</span><span class="n">h</span><span class="p">.</span><span class="n">HorarioEntrada</span><span class="p">,</span> <span class="s">"HH:mm"</span><span class="p">,</span> <span class="n">CultureInfo</span><span class="p">.</span><span class="n">InvariantCulture</span><span class="p">,</span> <span class="n">DateTimeStyles</span><span class="p">.</span><span class="n">None</span><span class="p">,</span> <span class="k">out</span> <span class="kt">var</span> <span class="n">entrada</span><span class="p">)</span>
<span class="p">&&</span> <span class="n">DateTime</span><span class="p">.</span><span class="nf">TryParseExact</span><span class="p">(</span><span class="n">h</span><span class="p">.</span><span class="n">HorarioSaida</span><span class="p">,</span> <span class="s">"HH:mm"</span><span class="p">,</span> <span class="n">CultureInfo</span><span class="p">.</span><span class="n">InvariantCulture</span><span class="p">,</span> <span class="n">DateTimeStyles</span><span class="p">.</span><span class="n">None</span><span class="p">,</span> <span class="k">out</span> <span class="kt">var</span> <span class="n">saida</span><span class="p">))</span>
<span class="p">{</span>
<span class="n">total</span> <span class="p">=</span> <span class="n">total</span><span class="p">.</span><span class="nf">Add</span><span class="p">(</span><span class="n">saida</span> <span class="p">-</span> <span class="n">entrada</span><span class="p">);</span>
<span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>
<p>Repare que um <code class="language-plaintext highlighter-rouge">DateTime</code> sobrecarrega a operação de subtração, por isso que <code class="language-plaintext highlighter-rouge">saida - entrada</code> já retorna um <code class="language-plaintext highlighter-rouge">TimeSpan</code> corretamente.</p>
<h3 id="python">Python</h3>
<p>Em Python você pode usar o <a href="https://docs.python.org/3/library/datetime.html">módulo <code class="language-plaintext highlighter-rouge">datetime</code></a>. Neste caso, temos que fazer o <em>parsing</em> de uma string, e para isso precisamos de <a href="https://docs.python.org/3/library/datetime.html#datetime.datetime.strptime"><code class="language-plaintext highlighter-rouge">strptime</code></a> (que retorna um <a href="https://docs.python.org/3/library/datetime.html#datetime-objects"><code class="language-plaintext highlighter-rouge">datetime</code></a>). Já para representar uma duração, basta usar um <a href="https://docs.python.org/3/library/datetime.html#timedelta-objects"><code class="language-plaintext highlighter-rouge">timedelta</code></a>:</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">from</span> <span class="nn">datetime</span> <span class="kn">import</span> <span class="n">datetime</span><span class="p">,</span> <span class="n">timedelta</span>
<span class="n">horarios</span> <span class="o">=</span> <span class="p">[</span> <span class="p">(</span><span class="s">"08:00"</span><span class="p">,</span> <span class="s">"17:00"</span><span class="p">),</span> <span class="p">(</span><span class="s">"08:15"</span><span class="p">,</span> <span class="s">"18:30"</span><span class="p">),</span> <span class="p">(</span><span class="s">"09:30"</span><span class="p">,</span> <span class="s">"18:30"</span><span class="p">)</span> <span class="p">]</span>
<span class="n">total</span> <span class="o">=</span> <span class="n">timedelta</span><span class="p">(</span><span class="n">hours</span><span class="o">=</span><span class="mi">0</span><span class="p">)</span>
<span class="k">for</span> <span class="n">ini</span><span class="p">,</span> <span class="n">fim</span> <span class="ow">in</span> <span class="n">horarios</span><span class="p">:</span>
<span class="n">entrada</span> <span class="o">=</span> <span class="n">datetime</span><span class="p">.</span><span class="n">strptime</span><span class="p">(</span><span class="n">ini</span><span class="p">,</span> <span class="s">'%H:%M'</span><span class="p">)</span>
<span class="n">saida</span> <span class="o">=</span> <span class="n">datetime</span><span class="p">.</span><span class="n">strptime</span><span class="p">(</span><span class="n">fim</span><span class="p">,</span> <span class="s">'%H:%M'</span><span class="p">)</span>
<span class="n">total</span> <span class="o">+=</span> <span class="n">saida</span> <span class="o">-</span> <span class="n">entrada</span>
<span class="n">segundos</span> <span class="o">=</span> <span class="nb">int</span><span class="p">(</span><span class="n">total</span><span class="p">.</span><span class="n">total_seconds</span><span class="p">())</span>
<span class="n">horas</span><span class="p">,</span> <span class="n">segundos</span> <span class="o">=</span> <span class="nb">divmod</span><span class="p">(</span><span class="n">segundos</span><span class="p">,</span> <span class="mi">3600</span><span class="p">)</span>
<span class="n">minutos</span><span class="p">,</span> <span class="n">segundos</span> <span class="o">=</span> <span class="nb">divmod</span><span class="p">(</span><span class="n">segundos</span><span class="p">,</span> <span class="mi">60</span><span class="p">)</span>
<span class="k">print</span><span class="p">(</span><span class="sa">f</span><span class="s">'</span><span class="si">{</span><span class="n">horas</span><span class="p">:</span><span class="mi">02</span><span class="si">}</span><span class="s">:</span><span class="si">{</span><span class="n">minutos</span><span class="p">:</span><span class="mi">02</span><span class="si">}</span><span class="s">:</span><span class="si">{</span><span class="n">segundos</span><span class="p">:</span><span class="mi">02</span><span class="si">}</span><span class="s">'</span><span class="p">)</span> <span class="c1"># 28:15:00
</span></code></pre></div></div>
<p>Repare que podemos subtrair duas datas com o operador <code class="language-plaintext highlighter-rouge">-</code>, que o resultado é um <code class="language-plaintext highlighter-rouge">timedelta</code> (que por sua vez, pode ser somado diretamente a outro <code class="language-plaintext highlighter-rouge">timedelta</code>). Para obter o total, porém, temos que fazer manualmente, obtendo o total em segundos com <code class="language-plaintext highlighter-rouge">total_seconds()</code> (e neste caso eu converti para <code class="language-plaintext highlighter-rouge">int</code> porque sei que não há frações de segundo, mas caso tenha, este arredondamento não poderia ser feito).</p>
<h3 id="php">PHP</h3>
<p>Em PHP acredito que o mais simples é converter cada horário em um “total de minutos” e calcular a diferença manualmente:</p>
<div class="language-php highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">function</span> <span class="n">getTotalMinutes</span><span class="p">(</span><span class="nv">$hm</span><span class="p">)</span> <span class="p">{</span>
<span class="k">list</span><span class="p">(</span><span class="nv">$hora</span><span class="p">,</span> <span class="nv">$minuto</span><span class="p">)</span> <span class="o">=</span> <span class="nb">explode</span><span class="p">(</span><span class="s1">':'</span><span class="p">,</span> <span class="nv">$hm</span><span class="p">);</span>
<span class="k">return</span> <span class="nv">$hora</span> <span class="o">*</span> <span class="mi">60</span> <span class="o">+</span> <span class="nv">$minuto</span><span class="p">;</span>
<span class="p">}</span>
<span class="nv">$horarios</span> <span class="o">=</span> <span class="p">[</span> <span class="p">[</span><span class="s2">"08:00"</span><span class="p">,</span> <span class="s2">"17:00"</span><span class="p">],</span> <span class="p">[</span><span class="s2">"08:15"</span><span class="p">,</span> <span class="s2">"18:30"</span><span class="p">],</span> <span class="p">[</span><span class="s2">"09:30"</span><span class="p">,</span> <span class="s2">"18:30"</span><span class="p">]</span> <span class="p">];</span>
<span class="nv">$totalMins</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>
<span class="k">foreach</span><span class="p">(</span><span class="nv">$horarios</span> <span class="k">as</span> <span class="k">list</span><span class="p">(</span><span class="nv">$entrada</span><span class="p">,</span> <span class="nv">$saida</span><span class="p">))</span> <span class="p">{</span>
<span class="nv">$totalMins</span> <span class="o">+=</span> <span class="nf">getTotalMinutes</span><span class="p">(</span><span class="nv">$saida</span><span class="p">)</span> <span class="o">-</span> <span class="nf">getTotalMinutes</span><span class="p">(</span><span class="nv">$entrada</span><span class="p">);</span>
<span class="p">}</span>
<span class="nv">$horas</span> <span class="o">=</span> <span class="nv">$totalMins</span> <span class="o">/</span> <span class="mi">60</span><span class="p">;</span>
<span class="nv">$minutos</span> <span class="o">=</span> <span class="nv">$totalMins</span> <span class="o">%</span> <span class="mi">60</span><span class="p">;</span>
<span class="k">echo</span> <span class="nb">sprintf</span><span class="p">(</span><span class="s2">"%02d:%02d"</span><span class="p">,</span> <span class="nv">$horas</span><span class="p">,</span> <span class="nv">$minutos</span><span class="p">);</span> <span class="c1">// 28:15</span>
</code></pre></div></div>
<p>Claro que você até poderia usar <a href="https://www.php.net/manual/pt_BR/class.datetime.php"><code class="language-plaintext highlighter-rouge">DateTime</code></a> para as datas e <a href="https://www.php.net/manual/pt_BR/dateinterval.construct.php"><code class="language-plaintext highlighter-rouge">DateInterval</code></a> para as durações, porém não é possível somar um <code class="language-plaintext highlighter-rouge">DateInterval</code> com outro, o que torna o seu uso bem limitado para o nosso caso.</p>
<h3 id="javascript">JavaScript</h3>
<p>Em JavaScript, até o presente momento, não existe uma classe específica para representar uma duração. Atualmente existe uma <a href="https://tc39.es/proposal-temporal/docs/index.html">proposta para a API <code class="language-plaintext highlighter-rouge">Temporal</code></a>, que possuirá suporte a durações, mas ela ainda não é suportada por todos os <em>browsers</em>. Ou seja, por enquanto, o jeito é recorrer a bibliotecas externas, ou então fazer o cálculo manualmente, similar ao que foi feito acima:</p>
<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">function</span> <span class="nx">getTotalMinutes</span><span class="p">(</span><span class="nx">hm</span><span class="p">)</span> <span class="p">{</span>
<span class="kd">var</span> <span class="p">[</span><span class="nx">hora</span><span class="p">,</span> <span class="nx">minuto</span><span class="p">]</span> <span class="o">=</span> <span class="nx">hm</span><span class="p">.</span><span class="nx">split</span><span class="p">(</span><span class="dl">'</span><span class="s1">:</span><span class="dl">'</span><span class="p">).</span><span class="nx">map</span><span class="p">(</span><span class="nx">v</span> <span class="o">=></span> <span class="nb">parseInt</span><span class="p">(</span><span class="nx">v</span><span class="p">));</span>
<span class="k">return</span> <span class="nx">hora</span> <span class="o">*</span> <span class="mi">60</span> <span class="o">+</span> <span class="nx">minuto</span><span class="p">;</span>
<span class="p">}</span>
<span class="kd">var</span> <span class="nx">horarios</span> <span class="o">=</span> <span class="p">[</span> <span class="p">[</span><span class="dl">"</span><span class="s2">08:00</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">17:00</span><span class="dl">"</span><span class="p">],</span> <span class="p">[</span><span class="dl">"</span><span class="s2">08:15</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">18:30</span><span class="dl">"</span><span class="p">],</span> <span class="p">[</span><span class="dl">"</span><span class="s2">09:30</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">18:30</span><span class="dl">"</span><span class="p">]</span> <span class="p">];</span>
<span class="kd">var</span> <span class="nx">totalMins</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>
<span class="k">for</span> <span class="p">(</span><span class="kd">var</span> <span class="p">[</span><span class="nx">ini</span><span class="p">,</span> <span class="nx">fim</span><span class="p">]</span> <span class="k">of</span> <span class="nx">horarios</span><span class="p">)</span> <span class="p">{</span>
<span class="nx">totalMins</span> <span class="o">+=</span> <span class="nx">getTotalMinutes</span><span class="p">(</span><span class="nx">fim</span><span class="p">)</span> <span class="o">-</span> <span class="nx">getTotalMinutes</span><span class="p">(</span><span class="nx">ini</span><span class="p">);</span>
<span class="p">}</span>
<span class="kd">var</span> <span class="nx">horas</span> <span class="o">=</span> <span class="nb">Math</span><span class="p">.</span><span class="nx">floor</span><span class="p">(</span><span class="nx">totalMins</span> <span class="o">/</span> <span class="mi">60</span><span class="p">);</span>
<span class="kd">var</span> <span class="nx">minutos</span> <span class="o">=</span> <span class="nx">totalMins</span> <span class="o">%</span> <span class="mi">60</span><span class="p">;</span>
<span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="s2">`</span><span class="p">${</span><span class="nx">horas</span><span class="p">.</span><span class="nx">toString</span><span class="p">().</span><span class="nx">padStart</span><span class="p">(</span><span class="mi">2</span><span class="p">,</span> <span class="dl">'</span><span class="s1">0</span><span class="dl">'</span><span class="p">)}</span><span class="s2">:</span><span class="p">${</span><span class="nx">minutos</span><span class="p">.</span><span class="nx">toString</span><span class="p">().</span><span class="nx">padStart</span><span class="p">(</span><span class="mi">2</span><span class="p">,</span> <span class="dl">'</span><span class="s1">0</span><span class="dl">'</span><span class="p">)}</span><span class="s2">`</span><span class="p">);</span> <span class="c1">// 28:15</span>
</code></pre></div></div>
<hr />
<h3 id="considerações-finais">Considerações finais</h3>
<p>Use sempre o tipo mais adequado para cada situação. Nem sempre a linguagem que estamos usando possui suporte adequado à durações, mas sempre é possível criar um ou então usar um número mesmo (e aí ele pode representar o total em minutos, segundos, ou qualquer outra unidade que você precisar). Às vezes usar o tipo errado “funciona” por coincidência (como usar o <code class="language-plaintext highlighter-rouge">LocalTime</code> do Java para representar uma duração em horas, que “funciona” para durações de até 24 horas, mas ainda sim é um uso torto e sujeito a erros).</p>
<p>Mas programar baseado em coincidências não é o ideal. O melhor é tratar cada dado de acordo com o que ele representa, usando o tipo mais adequado e com a semântica correta. Se datas e durações são coisas diferentes, não trate-as como se fossem a mesma.</p>
<div class="footnotes" role="doc-endnotes">
<ol>
<li id="fn:rant" role="doc-endnote">
<p>Acho até que os votos positivos sejam consequência de tanta gente não entender a diferença entre datas e durações, e por isso acham que aquele código está certo e faz sentido. Mas divago… <a href="#fnref:rant" class="reversefootnote" role="doc-backlink">↩</a></p>
</li>
</ol>
</div>Hugo KotsuboConsidere as duas frases abaixo: A filme passou às duas horas da tarde. A filme tem duração de duas horas. Apesar de ambas usarem “duas horas”, em cada uma o significado é diferente. Na primeira frase, “duas horas” indica um horário: um momento específico do dia. Na segunda frase, “duas horas” indica uma duração: uma quantidade de tempo. Apesar de ambos usarem as mesmas palavras - embora no primeiro caso tenha sido necessário usar “da tarde” para desambiguar - eles são conceitos diferentes. Uma data e/ou um horário não é o mesmo que uma duração. Datas representam pontos específicos no calendário e horários representam momentos específicos do dia, e durações são apenas quantidades de tempo, sem necessariamente estarem relacionadas a uma data e hora específicas (por exemplo, na segunda frase, não é dito que horas o filme começou; na verdade nem diz se ele foi exibido de fato, só se sabe quanto tempo ele dura). O que confunde é que ambos usam os mesmos termos (anos, meses, dias, horas, minutos e segundos), e até mesmo a representação pode ser igual: um relógio mostra 10:00:00 para indicar que são 10 horas da manhã, um cronômetro mostra 10:00:00 para mostrar que já se passaram 10 horas. Outro detalhe é que esses conceitos, embora não sejam a mesma coisa, estão relacionados: se eu tenho uma data e somo uma duração, o resultado é outra data (ex: dia 10 de janeiro somado com uma duração de 3 dias resulta em 13 de janeiro), e se eu calculo a diferença entre duas datas, o resultado é uma duração (entre 10 e 13 de janeiro, a duração é de 3 dias). E como são coisas diferentes, tratar um como se fosse o outro nem sempre trará os resultados esperados. Junte a isso o suporte nem sempre adequado das linguagens, e temos códigos confusos que muitas vezes só funcionam por coincidência (quando funcionam). Um exemplo comum é, dada uma tabela com horários de entrada e saída de um funcionário, calcular quantas horas foram trabalhadas no total. Entrada Saída 08:00 17:00 08:15 18:30 09:30 18:30 etc … Os dados da tabela são horários (momentos específicos do dia), mas eu quero calcular a duração (o total de horas). Um erro comum é pegar a diferença entre a entrada e saída, e tratá-la como um horário. Por exemplo, na primeira linha, o total de horas é 9 (foram trabalhadas 9 horas naquele dia), mas o programador guarda essa informação como se fosse um horário (09:00 - que na verdade é 9 da manhã, e não uma duração de 9 horas). Já na segunda linha, a duração é de 10 horas e 15 minutos, e na terceira, de 9 horas. Se eu guardar esses valores como horários (usando algum tipo específico, caso a linguagem suporte), geralmente não vai ser possível obter o total (que é de 28 horas e 15 minutos), já que os tipos de data/hora não permitem valores acima de 24 para as horas - e nem vou entrar no mérito de que “somar horas” é uma operação que sequer faz sentido: quanto é “9 da manhã” mais “10 e 15 da manhã”? Agora se eu tratar esses valores como durações, aí sim faz sentido: uma duração de 9 horas mais outra duração de 10 horas e 15 minutos é igual a uma duração de 19 horas e 15 minutos. E não há o limite de 24 horas, como há para os horários. Para as linguagens que suportam um tipo específico para durações, isto é perfeitamente possível. Mas quando não há um tipo disponível, você pode criar o seu próprio, ou então trabalhar com um número mesmo (que guarda, por exemplo, o tempo total em minutos, ou segundos, ou qualquer unidade que você precisar). O importante é não confundir os conceitos e não usar um quando na verdade se deve usar outro. Um erro muito comum é tentar usar uma data de qualquer maneira (com gambiarras que às vezes podem “funcionar”, mas geralmente é por coincidência), quando uma duração seria bem mais apropriado e simples (e o mais importante, dando resultados corretos, sem depender de coincidências). É só ver o que aconteceu aqui, por exemplo (apesar de todos os votos positivos, está completamente errado, e na minha resposta eu explico os motivos)1. Vamos ver então como seria uma solução para este problema. Lembrando que os códigos abaixo focam neste caso específico, em que temos apenas o horário (horas e minutos). Se tivesse que calcular considerando os dias, meses e/ou anos, iria precisar de modificações e adaptações para cada caso. Mas o importante é ter em mente a ideia principal: nunca confundir datas/horas com durações, e nunca tratar um como se fosse o outro. Abaixo tem soluções em algumas linguagens (você pode usar os links abaixo para ir direto para uma delas): Java C# Python PHP JavaScript Java Se você estiver usando o Java >= 8, use a API java.time. Para representar um horário (somente hora, minuto, segundo e frações de segundo), você pode usar a classe java.time.LocalTime. E para calcular a diferença, você pode usar um Duration, algo assim: LocalTime entrada = LocalTime.parse("08:00"); LocalTime saida = LocalTime.parse("17:00"); Duration duracao = Duration.between(entrada, saida); Para calcular o total, você poderia usar um loop: Duration total = Duration.ZERO; for (Horarios h: listaHorarios) { LocalTime entrada = LocalTime.parse(h.getHorarioEntrada()); LocalTime saida = LocalTime.parse(h.getHorarioSaida()); total = total.plus(Duration.between(entrada, saida)); // plus() retorna outro Duration com o resultado da soma } Já para obter o resultado, infelizmente não há uma forma “limpa” e direta de formatar a saída, e você terá que fazer manualmente. A partir do Java 9 você pode usar métodos como toHoursPart() e toMinutesPart(): long horas = total.toHoursPart(); long minutos = total.toMinutesPart(); Mas no Java 8, a conta tem que ser feita manualmente: long minutos = total.toMinutes(); long horas = minutos / 60; minutos %= 60; Isso porque toMinutes() retorna o total de minutos correspondente à duração (por exemplo, se a duração for de 28 horas e 15 minutos, toMinutes() retorna 1695). Já toMinutesPart() retorna 15. Outra forma é usar um java.time.temporal.ChronoUnit e obter o total em minutos, por exemplo: long totalMinutos = 0; for (Horarios h: listaHorarios) { LocalTime entrada = LocalTime.parse(h.getHorarioEntrada()); LocalTime saida = LocalTime.parse(h.getHorarioSaida()); totalMinutos += ChronoUnit.MINUTES.between(entrada, entrada); } // depois, "quebre" o valor long horas = totalMinutos / 60; long minutos = totalMinutos % 60; Lembrando que neste caso específico eu só tenho as horas e minutos, por isso ter o total em minutos é adequado. Mas se tivesse também os segundos e/ou frações de segundos, aí você teria que considerá-los (e aí acho que usar um Duration simplifica as coisas, pois internamente ele já trata desses detalhes). Para Java <= 7, não há uma classe específica para durações, e o único jeito é fazer o cálculo manualmente (similar o que foi feito abaixo com PHP, por exemplo). C# Em C#, o horário pode ser representado por um DateTime, e uma duração, por um TimeSpan. A ideia geral é bem similar ao código anterior: TimeSpan total = TimeSpan.Zero; foreach (Horarios h in listaHorarios) { if (DateTime.TryParseExact(h.HorarioEntrada, "HH:mm", CultureInfo.InvariantCulture, DateTimeStyles.None, out var entrada) && DateTime.TryParseExact(h.HorarioSaida, "HH:mm", CultureInfo.InvariantCulture, DateTimeStyles.None, out var saida)) { total = total.Add(saida - entrada); } } Repare que um DateTime sobrecarrega a operação de subtração, por isso que saida - entrada já retorna um TimeSpan corretamente. Python Em Python você pode usar o módulo datetime. Neste caso, temos que fazer o parsing de uma string, e para isso precisamos de strptime (que retorna um datetime). Já para representar uma duração, basta usar um timedelta: from datetime import datetime, timedelta horarios = [ ("08:00", "17:00"), ("08:15", "18:30"), ("09:30", "18:30") ] total = timedelta(hours=0) for ini, fim in horarios: entrada = datetime.strptime(ini, '%H:%M') saida = datetime.strptime(fim, '%H:%M') total += saida - entrada segundos = int(total.total_seconds()) horas, segundos = divmod(segundos, 3600) minutos, segundos = divmod(segundos, 60) print(f'{horas:02}:{minutos:02}:{segundos:02}') # 28:15:00 Repare que podemos subtrair duas datas com o operador -, que o resultado é um timedelta (que por sua vez, pode ser somado diretamente a outro timedelta). Para obter o total, porém, temos que fazer manualmente, obtendo o total em segundos com total_seconds() (e neste caso eu converti para int porque sei que não há frações de segundo, mas caso tenha, este arredondamento não poderia ser feito). PHP Em PHP acredito que o mais simples é converter cada horário em um “total de minutos” e calcular a diferença manualmente: function getTotalMinutes($hm) { list($hora, $minuto) = explode(':', $hm); return $hora * 60 + $minuto; } $horarios = [ ["08:00", "17:00"], ["08:15", "18:30"], ["09:30", "18:30"] ]; $totalMins = 0; foreach($horarios as list($entrada, $saida)) { $totalMins += getTotalMinutes($saida) - getTotalMinutes($entrada); } $horas = $totalMins / 60; $minutos = $totalMins % 60; echo sprintf("%02d:%02d", $horas, $minutos); // 28:15 Claro que você até poderia usar DateTime para as datas e DateInterval para as durações, porém não é possível somar um DateInterval com outro, o que torna o seu uso bem limitado para o nosso caso. JavaScript Em JavaScript, até o presente momento, não existe uma classe específica para representar uma duração. Atualmente existe uma proposta para a API Temporal, que possuirá suporte a durações, mas ela ainda não é suportada por todos os browsers. Ou seja, por enquanto, o jeito é recorrer a bibliotecas externas, ou então fazer o cálculo manualmente, similar ao que foi feito acima: function getTotalMinutes(hm) { var [hora, minuto] = hm.split(':').map(v => parseInt(v)); return hora * 60 + minuto; } var horarios = [ ["08:00", "17:00"], ["08:15", "18:30"], ["09:30", "18:30"] ]; var totalMins = 0; for (var [ini, fim] of horarios) { totalMins += getTotalMinutes(fim) - getTotalMinutes(ini); } var horas = Math.floor(totalMins / 60); var minutos = totalMins % 60; console.log(`${horas.toString().padStart(2, '0')}:${minutos.toString().padStart(2, '0')}`); // 28:15 Considerações finais Use sempre o tipo mais adequado para cada situação. Nem sempre a linguagem que estamos usando possui suporte adequado à durações, mas sempre é possível criar um ou então usar um número mesmo (e aí ele pode representar o total em minutos, segundos, ou qualquer outra unidade que você precisar). Às vezes usar o tipo errado “funciona” por coincidência (como usar o LocalTime do Java para representar uma duração em horas, que “funciona” para durações de até 24 horas, mas ainda sim é um uso torto e sujeito a erros). Mas programar baseado em coincidências não é o ideal. O melhor é tratar cada dado de acordo com o que ele representa, usando o tipo mais adequado e com a semântica correta. Se datas e durações são coisas diferentes, não trate-as como se fossem a mesma. Acho até que os votos positivos sejam consequência de tanta gente não entender a diferença entre datas e durações, e por isso acham que aquele código está certo e faz sentido. Mas divago… ↩Somar 1 dia a uma data é o mesmo que somar 24 horas?2020-05-18T13:00:00-03:002020-05-18T13:00:00-03:00https://hkotsubo.github.io/blog/2020-05-18/somar-1-dia-a-uma-data-e-o-mesmo-que-somar-24-horas<p>Em um <a href="/blog/2019-04-06/nao-reinvente-a-roda-como-somar-dias-a-uma-data" class="new-window">artigo anterior</a>, já vimos como somar dias a uma data é mais complicado do que parece. E agora vamos ver como um dia nem sempre tem 24 horas, e por isso somar 1 dia a uma data nem sempre terá o mesmo resultado de somar 24 horas.</p>
<p>E não estou falando do fato de que a <a href="https://en.wikipedia.org/wiki/Earth%27s_rotation">rotação da Terra está cada vez mais lenta</a>, e sim de algo bem mais complicado: fusos horários.</p>
<p>Neste exato momento, a data e hora atual, em cada lugar do mundo, pode ser diferente. Enquanto em São Paulo são duas da tarde de um domingo, no Japão já são duas da manhã de segunda-feira. Mas dependendo da época do ano, pode ser que duas da tarde em São Paulo seja equivalente a uma da manhã no Japão. Tudo por causa do horário de verão.</p>
<p>Neste exemplo vamos considerar o Horário de Brasília, mas o problema ocorrerá em qualquer lugar que adote o horário de verão, ou cujo fuso horário tenha sido mudado por qualquer motivo que seja. Então para entender porque somar 1 dia nem sempre é o mesmo que somar 24 horas, primeiro precisamos entender como funciona o horário de verão.</p>
<hr />
<p>De forma resumida, o horário de verão consiste em atrasar ou adiantar o relógio uma determinada quantidade de tempo (na maioria dos lugares, essa quantidade é uma hora), em determinada data e horário. No caso do Horário de Brasília, por exemplo, em 4 de novembro de 2018, à meia-noite, os relógios foram adiantados em uma hora. Isso quer dizer que na prática, o relógio pulou de 23:59:59.999 do dia 3 direto para 01:00 do dia 4. Todos os minutos entre 00:00 e 00:59 <strong>não existiram</strong> no dia 4, para este fuso horário. Ou seja, o dia 4 começou na verdade às 01:00, e portanto neste fuso horário, este dia teve 23 horas.</p>
<blockquote>
<p>Isso é chamado de <em>DST Gap</em>: “DST” é a sigla para <em>Daylight Saving Time</em> (o nome em inglês para Horário de Verão, que muitos também chamam de “Summer Time”) e <em>gap</em> significa “vão”, ou seja, há um “vazio” ali porque uma hora foi “pulada”.</p>
</blockquote>
<p>Para exemplificar, vou usar a API <code class="language-plaintext highlighter-rouge">java.time</code>, disponível a partir do Java 8 (porque é a que tenho mais familiaridade, e também porque ela possui mecanismos adequados para lidar com fusos horários, o que facilitará as explicações). Para o exemplo do parágrafo anterior, vou usar a classe <a href="https://docs.oracle.com/javase/8/docs/api/java/time/ZonedDateTime.html"><code class="language-plaintext highlighter-rouge">java.time.ZonedDateTime</code></a>, que representa uma data e hora em um fuso horário específico:</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">import</span> <span class="nn">java.time.ZoneId</span><span class="o">;</span>
<span class="kn">import</span> <span class="nn">java.time.ZonedDateTime</span><span class="o">;</span>
<span class="c1">// 3 de novembro de 2018, às 23:59:59.999999999 no Horário de Brasília</span>
<span class="nc">ZonedDateTime</span> <span class="n">zdt</span> <span class="o">=</span> <span class="nc">ZonedDateTime</span><span class="o">.</span><span class="na">of</span><span class="o">(</span><span class="mi">2018</span><span class="o">,</span> <span class="mi">11</span><span class="o">,</span> <span class="mi">3</span><span class="o">,</span> <span class="mi">23</span><span class="o">,</span> <span class="mi">59</span><span class="o">,</span> <span class="mi">59</span><span class="o">,</span> <span class="mi">999999999</span><span class="o">,</span> <span class="nc">ZoneId</span><span class="o">.</span><span class="na">of</span><span class="o">(</span><span class="s">"America/Sao_Paulo"</span><span class="o">));</span>
<span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="n">zdt</span><span class="o">);</span> <span class="c1">// 2018-11-03T23:59:59.999999999-03:00[America/Sao_Paulo]</span>
<span class="c1">// somando 1 nanossegundo</span>
<span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="n">zdt</span><span class="o">.</span><span class="na">plusNanos</span><span class="o">(</span><span class="mi">1</span><span class="o">));</span> <span class="c1">// 2018-11-04T01:00-02:00[America/Sao_Paulo]</span>
</code></pre></div></div>
<p>Repare como, ao somar 1 nanossegundo à data, o resultado pula direto de 23:59:59 do dia 3 para 1 da manhã do dia 4. Isso acontece porque à meia-noite ocorreu a transição do horário de verão e o relógio foi adiantado em uma hora.</p>
<p>Mas apesar desse “pulo”, os instantes continuam sendo contínuos. Se você calcular a diferença entre essas datas, ela será de um 1 nanossegundo:</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">import</span> <span class="nn">java.time.Duration</span><span class="o">;</span>
<span class="kn">import</span> <span class="nn">java.time.temporal.ChronoUnit</span><span class="o">;</span>
<span class="c1">// 3 de novembro de 2018, às 23:59:59.999999999 no Horário de Brasília</span>
<span class="nc">ZonedDateTime</span> <span class="n">zdt</span> <span class="o">=</span> <span class="nc">ZonedDateTime</span><span class="o">.</span><span class="na">of</span><span class="o">(</span><span class="mi">2018</span><span class="o">,</span> <span class="mi">11</span><span class="o">,</span> <span class="mi">3</span><span class="o">,</span> <span class="mi">23</span><span class="o">,</span> <span class="mi">59</span><span class="o">,</span> <span class="mi">59</span><span class="o">,</span> <span class="mi">999999999</span><span class="o">,</span> <span class="nc">ZoneId</span><span class="o">.</span><span class="na">of</span><span class="o">(</span><span class="s">"America/Sao_Paulo"</span><span class="o">));</span>
<span class="c1">// somando 1 nanossegundo</span>
<span class="nc">ZonedDateTime</span> <span class="n">zdtDepois</span> <span class="o">=</span> <span class="n">zdt</span><span class="o">.</span><span class="na">plusNanos</span><span class="o">(</span><span class="mi">1</span><span class="o">);</span>
<span class="c1">// diferença entre as datas</span>
<span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="nc">ChronoUnit</span><span class="o">.</span><span class="na">NANOS</span><span class="o">.</span><span class="na">between</span><span class="o">(</span><span class="n">zdt</span><span class="o">,</span> <span class="n">zdtDepois</span><span class="o">));</span> <span class="c1">// 1</span>
<span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="nc">Duration</span><span class="o">.</span><span class="na">between</span><span class="o">(</span><span class="n">zdt</span><span class="o">,</span> <span class="n">zdtDepois</span><span class="o">));</span> <span class="c1">// PT0.000000001S</span>
</code></pre></div></div>
<p>E se convertê-las para UTC, veremos que de fato os instantes são contínuos:</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="n">zdt</span><span class="o">.</span><span class="na">toInstant</span><span class="o">());</span> <span class="c1">// 2018-11-04T02:59:59.999999999Z</span>
<span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="n">zdtDepois</span><span class="o">.</span><span class="na">toInstant</span><span class="o">());</span> <span class="c1">// 2018-11-04T03:00:00Z</span>
</code></pre></div></div>
<p>Essa “mágica” acontece porque houve uma mudança de <em>offset</em> (a diferença com relação a UTC). Se você reparar na saída do primeiro código, verá que o primeiro caso mostra <code class="language-plaintext highlighter-rouge">-03:00</code> (3 horas antes de UTC) e o segundo mostra <code class="language-plaintext highlighter-rouge">-02:00</code> (2 horas antes de UTC). É essa mudança de <em>offset</em> que torna possível adiantar o relógio do ponto de vista local, sem quebrar a continuidade dos instantes referentes a antes e depois da mudança.</p>
<hr />
<h3 id="e-o-que-isso-tem-a-ver-com-somar-1-dia-ou-24-horas-a-uma-data">E o que isso tem a ver com somar 1 dia ou 24 horas a uma data?</h3>
<p>Tem tudo a ver. Quando somamos 1 dia a uma data/hora, espera-se que o resultado seja o <strong>mesmo horário do dia seguinte</strong>. E na grande maioria dos casos, isso será o mesmo que somar 24 horas. Mas graças ao horário de verão, nem sempre isso é verdade.</p>
<p>Por exemplo, vamos considerar 3 de novembro de 2018, às 21h no Horário de Brasília:</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// 3 de novembro de 2018, às 21:00 no Horário de Brasília</span>
<span class="nc">ZonedDateTime</span> <span class="n">zdt</span> <span class="o">=</span> <span class="nc">ZonedDateTime</span><span class="o">.</span><span class="na">of</span><span class="o">(</span><span class="mi">2018</span><span class="o">,</span> <span class="mi">11</span><span class="o">,</span> <span class="mi">3</span><span class="o">,</span> <span class="mi">21</span><span class="o">,</span> <span class="mi">0</span><span class="o">,</span> <span class="mi">0</span><span class="o">,</span> <span class="mi">0</span><span class="o">,</span> <span class="nc">ZoneId</span><span class="o">.</span><span class="na">of</span><span class="o">(</span><span class="s">"America/Sao_Paulo"</span><span class="o">));</span>
<span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="n">zdt</span><span class="o">);</span> <span class="c1">// 2018-11-03T21:00-03:00[America/Sao_Paulo]</span>
<span class="c1">// somar 1 dia</span>
<span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="n">zdt</span><span class="o">.</span><span class="na">plusDays</span><span class="o">(</span><span class="mi">1</span><span class="o">));</span> <span class="c1">// 2018-11-04T21:00-02:00[America/Sao_Paulo]</span>
<span class="c1">// somar 24 horas</span>
<span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="n">zdt</span><span class="o">.</span><span class="na">plusHours</span><span class="o">(</span><span class="mi">24</span><span class="o">));</span> <span class="c1">// 2018-11-04T22:00-02:00[America/Sao_Paulo]</span>
</code></pre></div></div>
<p>Repare que ao somar 1 dia, o resultado é o mesmo horário do dia seguinte (dia 4 às 21h). Mas ao somar 24 horas, o resultado é <strong>22h</strong> do dia 4.</p>
<p>Isso acontece por causa do horário de verão. Imagine que às 21h do dia 3 eu inicio a contagem em um cronômetro. Quando der meia-noite, o cronômetro estará marcando 3 horas, mas por causa do horário de verão, na verdade não será meia-noite e sim 1 da manhã. Só que o cronômetro não dá esse salto na sua contagem (ele não vai pular de 2 horas e 59 minutos para 4 horas, ele vai continuar marcando que se passaram 3 horas). E ele só vai indicar que se passaram 24 horas às 22h do dia 4.</p>
<p>Já quando eu somo 1 dia, a classe <code class="language-plaintext highlighter-rouge">ZonedDateTime</code> considera que eu quero o mesmo horário do dia seguinte, então somente os campos relativos à data são alterados, e depois ela faz os devidos ajustes no <em>offset</em> (ela verifica que agora a data está em horário de verão e muda o <em>offset</em> para <code class="language-plaintext highlighter-rouge">-02:00</code>).</p>
<hr />
<p>Quando acaba o horário de verão, acontece algo tão ou mais estranho. Ainda usando como exemplo o Horário de Brasília, quando acaba o horário de verão, o relógio é atrasado em uma hora. Em 2019, por exemplo, o horário de verão acabou no dia 17 de fevereiro: à meia-noite, os relógios foram atrasados em uma hora e voltaram para 23:00 do dia 16.</p>
<p>Ou seja, os relógios pularam de 23:59:59.999 do dia 16 para 23:00 do dia 16. Isso quer dizer que todos os minutos entre 23:00 e 23:59 ocorreram <strong>duas vezes</strong>: uma horário de verão e outra no horário “normal”.</p>
<blockquote>
<p>Isso é chamado de <em>DST Overlap</em>: já vimos acima que “DST” é a sigla em inglês para o horário de verão, e <em>overlap</em> é “sobreposição”, para indicar que um mesmo intervalo de tempo ocorreu duas vezes.</p>
</blockquote>
<p>Usando <code class="language-plaintext highlighter-rouge">ZonedDateTime</code> para exemplificar:</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// 16 de fevereiro de 2019, às 23:59:59.999999999 no Horário de Brasília</span>
<span class="nc">ZonedDateTime</span> <span class="n">zdt</span> <span class="o">=</span> <span class="nc">ZonedDateTime</span><span class="o">.</span><span class="na">of</span><span class="o">(</span><span class="mi">2019</span><span class="o">,</span> <span class="mi">2</span><span class="o">,</span> <span class="mi">16</span><span class="o">,</span> <span class="mi">23</span><span class="o">,</span> <span class="mi">59</span><span class="o">,</span> <span class="mi">59</span><span class="o">,</span> <span class="mi">999999999</span><span class="o">,</span> <span class="nc">ZoneId</span><span class="o">.</span><span class="na">of</span><span class="o">(</span><span class="s">"America/Sao_Paulo"</span><span class="o">));</span>
<span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="n">zdt</span><span class="o">);</span> <span class="c1">// 2019-02-16T23:59:59.999999999-02:00[America/Sao_Paulo]</span>
<span class="c1">// somar 1 nanossegundo</span>
<span class="nc">ZonedDateTime</span> <span class="n">zdtDepois</span> <span class="o">=</span> <span class="n">zdt</span><span class="o">.</span><span class="na">plusNanos</span><span class="o">(</span><span class="mi">1</span><span class="o">);</span>
<span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="n">zdtDepois</span><span class="o">);</span> <span class="c1">// 2019-02-16T23:00-03:00[America/Sao_Paulo]</span>
<span class="c1">// diferença entre as datas é de 1 nanossegundo</span>
<span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="nc">ChronoUnit</span><span class="o">.</span><span class="na">NANOS</span><span class="o">.</span><span class="na">between</span><span class="o">(</span><span class="n">zdt</span><span class="o">,</span> <span class="n">zdtDepois</span><span class="o">));</span> <span class="c1">// 1</span>
<span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="nc">Duration</span><span class="o">.</span><span class="na">between</span><span class="o">(</span><span class="n">zdt</span><span class="o">,</span> <span class="n">zdtDepois</span><span class="o">));</span> <span class="c1">// PT0.000000001S</span>
<span class="c1">// converter para UTC, para mostrar que os instantes são contínuos</span>
<span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="n">zdt</span><span class="o">.</span><span class="na">toInstant</span><span class="o">());</span> <span class="c1">// 2019-02-17T01:59:59.999999999Z</span>
<span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="n">zdtDepois</span><span class="o">.</span><span class="na">toInstant</span><span class="o">());</span> <span class="c1">// 2019-02-17T02:00:00Z</span>
</code></pre></div></div>
<p>Repare que o <em>offset</em> mudou de <code class="language-plaintext highlighter-rouge">-02:00</code> de volta para <code class="language-plaintext highlighter-rouge">-03:00</code>. Importante reparar também que, como o intervalo entre 23:00 e 23:59 ocorreu duas vezes, o dia 16 teve 25 horas de duração.</p>
<p>E como agora temos uma hora a mais, somar 1 dia não terá o mesmo resultado de somar 24 horas:</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// 16 de fevereiro de 2019, às 21:00 no Horário de Brasília</span>
<span class="nc">ZonedDateTime</span> <span class="n">zdt</span> <span class="o">=</span> <span class="nc">ZonedDateTime</span><span class="o">.</span><span class="na">of</span><span class="o">(</span><span class="mi">2019</span><span class="o">,</span> <span class="mi">2</span><span class="o">,</span> <span class="mi">16</span><span class="o">,</span> <span class="mi">21</span><span class="o">,</span> <span class="mi">0</span><span class="o">,</span> <span class="mi">0</span><span class="o">,</span> <span class="mi">0</span><span class="o">,</span> <span class="nc">ZoneId</span><span class="o">.</span><span class="na">of</span><span class="o">(</span><span class="s">"America/Sao_Paulo"</span><span class="o">));</span>
<span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="n">zdt</span><span class="o">);</span> <span class="c1">// 2019-02-16T21:00-02:00[America/Sao_Paulo]</span>
<span class="c1">// somar 1 dia</span>
<span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="n">zdt</span><span class="o">.</span><span class="na">plusDays</span><span class="o">(</span><span class="mi">1</span><span class="o">));</span> <span class="c1">// 2019-02-17T21:00-03:00[America/Sao_Paulo]</span>
<span class="c1">// somar 24 horas</span>
<span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="n">zdt</span><span class="o">.</span><span class="na">plusHours</span><span class="o">(</span><span class="mi">24</span><span class="o">));</span> <span class="c1">// 2019-02-17T20:00-03:00[America/Sao_Paulo]</span>
</code></pre></div></div>
<p>Aqui vale a mesma analogia do cronômetro: se eu ligá-lo às 21h do dia 16, quando chegar meia-noite ele estará marcando 3 horas. Mas o relógio será atrasado em uma hora, e todos os minutos entre 23:00 e 23:59 serão contados novamente (quando chegar meia-noite de novo, o cronômetro estará marcando 4 horas). E quando chegar as 20h do dia 17, ele já estará marcando 24 horas.</p>
<hr />
<p>Vale lembrar que <em>gaps</em> e <em>overlaps</em> nem sempre são de uma hora (há regiões da Austrália que durante o horário de verão <a href="https://www.timeanddate.com/time/zone/australia/lord-howe-island?year=2019">adiantam o relógio em meia hora</a>), e nem sempre é por causa do horário de verão (em 2018 a Coreia do Norte <a href="https://www.timeanddate.com/news/time/north-korea-aligns-with-south.html">adiantou seu fuso em meia-hora</a> para alinhá-lo com o horário da Coreia do Sul).</p>
<p>Lembre-se que quem define as regras de qualquer fuso horário (se vai ou não ter horário de verão, quando começa e termina, qual <em>offset</em> será usado, etc) é o governo local de cada região, muitas vezes sem justificativa técnica (é comum dizer que “o povo terá mais horas de sol” e coisas do tipo), então mesmo se hoje não tiver horário de verão na sua região, nada garante que amanhã não haverá. Ao somar datas ou calcular a diferença entre elas, esses fatores sempre devem ser levados em consideração, pois pode dar diferença dependendo da forma como você calcula.</p>
<p>Neste <em>post</em> eu foquei mais em Java, apenas para ilustrar a diferença. Outras linguagens podem ter comportamento diferente ou não tratar corretamente todos os casos (JavaScript, por exemplo, dá o mesmo resultado se somar 1 dia ou 24 horas, ignorando os efeitos do horário de verão).</p>Hugo KotsuboEm um artigo anterior, já vimos como somar dias a uma data é mais complicado do que parece. E agora vamos ver como um dia nem sempre tem 24 horas, e por isso somar 1 dia a uma data nem sempre terá o mesmo resultado de somar 24 horas. E não estou falando do fato de que a rotação da Terra está cada vez mais lenta, e sim de algo bem mais complicado: fusos horários. Neste exato momento, a data e hora atual, em cada lugar do mundo, pode ser diferente. Enquanto em São Paulo são duas da tarde de um domingo, no Japão já são duas da manhã de segunda-feira. Mas dependendo da época do ano, pode ser que duas da tarde em São Paulo seja equivalente a uma da manhã no Japão. Tudo por causa do horário de verão. Neste exemplo vamos considerar o Horário de Brasília, mas o problema ocorrerá em qualquer lugar que adote o horário de verão, ou cujo fuso horário tenha sido mudado por qualquer motivo que seja. Então para entender porque somar 1 dia nem sempre é o mesmo que somar 24 horas, primeiro precisamos entender como funciona o horário de verão. De forma resumida, o horário de verão consiste em atrasar ou adiantar o relógio uma determinada quantidade de tempo (na maioria dos lugares, essa quantidade é uma hora), em determinada data e horário. No caso do Horário de Brasília, por exemplo, em 4 de novembro de 2018, à meia-noite, os relógios foram adiantados em uma hora. Isso quer dizer que na prática, o relógio pulou de 23:59:59.999 do dia 3 direto para 01:00 do dia 4. Todos os minutos entre 00:00 e 00:59 não existiram no dia 4, para este fuso horário. Ou seja, o dia 4 começou na verdade às 01:00, e portanto neste fuso horário, este dia teve 23 horas. Isso é chamado de DST Gap: “DST” é a sigla para Daylight Saving Time (o nome em inglês para Horário de Verão, que muitos também chamam de “Summer Time”) e gap significa “vão”, ou seja, há um “vazio” ali porque uma hora foi “pulada”. Para exemplificar, vou usar a API java.time, disponível a partir do Java 8 (porque é a que tenho mais familiaridade, e também porque ela possui mecanismos adequados para lidar com fusos horários, o que facilitará as explicações). Para o exemplo do parágrafo anterior, vou usar a classe java.time.ZonedDateTime, que representa uma data e hora em um fuso horário específico: import java.time.ZoneId; import java.time.ZonedDateTime; // 3 de novembro de 2018, às 23:59:59.999999999 no Horário de Brasília ZonedDateTime zdt = ZonedDateTime.of(2018, 11, 3, 23, 59, 59, 999999999, ZoneId.of("America/Sao_Paulo")); System.out.println(zdt); // 2018-11-03T23:59:59.999999999-03:00[America/Sao_Paulo] // somando 1 nanossegundo System.out.println(zdt.plusNanos(1)); // 2018-11-04T01:00-02:00[America/Sao_Paulo] Repare como, ao somar 1 nanossegundo à data, o resultado pula direto de 23:59:59 do dia 3 para 1 da manhã do dia 4. Isso acontece porque à meia-noite ocorreu a transição do horário de verão e o relógio foi adiantado em uma hora. Mas apesar desse “pulo”, os instantes continuam sendo contínuos. Se você calcular a diferença entre essas datas, ela será de um 1 nanossegundo: import java.time.Duration; import java.time.temporal.ChronoUnit; // 3 de novembro de 2018, às 23:59:59.999999999 no Horário de Brasília ZonedDateTime zdt = ZonedDateTime.of(2018, 11, 3, 23, 59, 59, 999999999, ZoneId.of("America/Sao_Paulo")); // somando 1 nanossegundo ZonedDateTime zdtDepois = zdt.plusNanos(1); // diferença entre as datas System.out.println(ChronoUnit.NANOS.between(zdt, zdtDepois)); // 1 System.out.println(Duration.between(zdt, zdtDepois)); // PT0.000000001S E se convertê-las para UTC, veremos que de fato os instantes são contínuos: System.out.println(zdt.toInstant()); // 2018-11-04T02:59:59.999999999Z System.out.println(zdtDepois.toInstant()); // 2018-11-04T03:00:00Z Essa “mágica” acontece porque houve uma mudança de offset (a diferença com relação a UTC). Se você reparar na saída do primeiro código, verá que o primeiro caso mostra -03:00 (3 horas antes de UTC) e o segundo mostra -02:00 (2 horas antes de UTC). É essa mudança de offset que torna possível adiantar o relógio do ponto de vista local, sem quebrar a continuidade dos instantes referentes a antes e depois da mudança. E o que isso tem a ver com somar 1 dia ou 24 horas a uma data? Tem tudo a ver. Quando somamos 1 dia a uma data/hora, espera-se que o resultado seja o mesmo horário do dia seguinte. E na grande maioria dos casos, isso será o mesmo que somar 24 horas. Mas graças ao horário de verão, nem sempre isso é verdade. Por exemplo, vamos considerar 3 de novembro de 2018, às 21h no Horário de Brasília: // 3 de novembro de 2018, às 21:00 no Horário de Brasília ZonedDateTime zdt = ZonedDateTime.of(2018, 11, 3, 21, 0, 0, 0, ZoneId.of("America/Sao_Paulo")); System.out.println(zdt); // 2018-11-03T21:00-03:00[America/Sao_Paulo] // somar 1 dia System.out.println(zdt.plusDays(1)); // 2018-11-04T21:00-02:00[America/Sao_Paulo] // somar 24 horas System.out.println(zdt.plusHours(24)); // 2018-11-04T22:00-02:00[America/Sao_Paulo] Repare que ao somar 1 dia, o resultado é o mesmo horário do dia seguinte (dia 4 às 21h). Mas ao somar 24 horas, o resultado é 22h do dia 4. Isso acontece por causa do horário de verão. Imagine que às 21h do dia 3 eu inicio a contagem em um cronômetro. Quando der meia-noite, o cronômetro estará marcando 3 horas, mas por causa do horário de verão, na verdade não será meia-noite e sim 1 da manhã. Só que o cronômetro não dá esse salto na sua contagem (ele não vai pular de 2 horas e 59 minutos para 4 horas, ele vai continuar marcando que se passaram 3 horas). E ele só vai indicar que se passaram 24 horas às 22h do dia 4. Já quando eu somo 1 dia, a classe ZonedDateTime considera que eu quero o mesmo horário do dia seguinte, então somente os campos relativos à data são alterados, e depois ela faz os devidos ajustes no offset (ela verifica que agora a data está em horário de verão e muda o offset para -02:00). Quando acaba o horário de verão, acontece algo tão ou mais estranho. Ainda usando como exemplo o Horário de Brasília, quando acaba o horário de verão, o relógio é atrasado em uma hora. Em 2019, por exemplo, o horário de verão acabou no dia 17 de fevereiro: à meia-noite, os relógios foram atrasados em uma hora e voltaram para 23:00 do dia 16. Ou seja, os relógios pularam de 23:59:59.999 do dia 16 para 23:00 do dia 16. Isso quer dizer que todos os minutos entre 23:00 e 23:59 ocorreram duas vezes: uma horário de verão e outra no horário “normal”. Isso é chamado de DST Overlap: já vimos acima que “DST” é a sigla em inglês para o horário de verão, e overlap é “sobreposição”, para indicar que um mesmo intervalo de tempo ocorreu duas vezes. Usando ZonedDateTime para exemplificar: // 16 de fevereiro de 2019, às 23:59:59.999999999 no Horário de Brasília ZonedDateTime zdt = ZonedDateTime.of(2019, 2, 16, 23, 59, 59, 999999999, ZoneId.of("America/Sao_Paulo")); System.out.println(zdt); // 2019-02-16T23:59:59.999999999-02:00[America/Sao_Paulo] // somar 1 nanossegundo ZonedDateTime zdtDepois = zdt.plusNanos(1); System.out.println(zdtDepois); // 2019-02-16T23:00-03:00[America/Sao_Paulo] // diferença entre as datas é de 1 nanossegundo System.out.println(ChronoUnit.NANOS.between(zdt, zdtDepois)); // 1 System.out.println(Duration.between(zdt, zdtDepois)); // PT0.000000001S // converter para UTC, para mostrar que os instantes são contínuos System.out.println(zdt.toInstant()); // 2019-02-17T01:59:59.999999999Z System.out.println(zdtDepois.toInstant()); // 2019-02-17T02:00:00Z Repare que o offset mudou de -02:00 de volta para -03:00. Importante reparar também que, como o intervalo entre 23:00 e 23:59 ocorreu duas vezes, o dia 16 teve 25 horas de duração. E como agora temos uma hora a mais, somar 1 dia não terá o mesmo resultado de somar 24 horas: // 16 de fevereiro de 2019, às 21:00 no Horário de Brasília ZonedDateTime zdt = ZonedDateTime.of(2019, 2, 16, 21, 0, 0, 0, ZoneId.of("America/Sao_Paulo")); System.out.println(zdt); // 2019-02-16T21:00-02:00[America/Sao_Paulo] // somar 1 dia System.out.println(zdt.plusDays(1)); // 2019-02-17T21:00-03:00[America/Sao_Paulo] // somar 24 horas System.out.println(zdt.plusHours(24)); // 2019-02-17T20:00-03:00[America/Sao_Paulo] Aqui vale a mesma analogia do cronômetro: se eu ligá-lo às 21h do dia 16, quando chegar meia-noite ele estará marcando 3 horas. Mas o relógio será atrasado em uma hora, e todos os minutos entre 23:00 e 23:59 serão contados novamente (quando chegar meia-noite de novo, o cronômetro estará marcando 4 horas). E quando chegar as 20h do dia 17, ele já estará marcando 24 horas. Vale lembrar que gaps e overlaps nem sempre são de uma hora (há regiões da Austrália que durante o horário de verão adiantam o relógio em meia hora), e nem sempre é por causa do horário de verão (em 2018 a Coreia do Norte adiantou seu fuso em meia-hora para alinhá-lo com o horário da Coreia do Sul). Lembre-se que quem define as regras de qualquer fuso horário (se vai ou não ter horário de verão, quando começa e termina, qual offset será usado, etc) é o governo local de cada região, muitas vezes sem justificativa técnica (é comum dizer que “o povo terá mais horas de sol” e coisas do tipo), então mesmo se hoje não tiver horário de verão na sua região, nada garante que amanhã não haverá. Ao somar datas ou calcular a diferença entre elas, esses fatores sempre devem ser levados em consideração, pois pode dar diferença dependendo da forma como você calcula. Neste post eu foquei mais em Java, apenas para ilustrar a diferença. Outras linguagens podem ter comportamento diferente ou não tratar corretamente todos os casos (JavaScript, por exemplo, dá o mesmo resultado se somar 1 dia ou 24 horas, ignorando os efeitos do horário de verão).O programador foi ao mercado2020-04-17T15:00:00-03:002020-04-17T15:00:00-03:00https://hkotsubo.github.io/blog/2020-04-17/o-programador-foi-ao-mercado<p>Você provavelmente já ouviu alguma variação dessa piada. A esposa do programador diz para ele ir ao mercado e dá instruções bem claras:</p>
<blockquote>
<p>-Se tiver batata, traga duas. Se tiver cebola, traga três.</p>
</blockquote>
<p>E então ele volta com 3 batatas.</p>
<hr />
<p>Apesar de besta, dá para tirar uma “lição de moral” da piada.</p>
<p>O problema aconteceu porque o programador entendeu isso (exemplo em Python):</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">tem_batata</span> <span class="o">=</span> <span class="n">tem_cebola</span> <span class="o">=</span> <span class="bp">True</span>
<span class="k">if</span> <span class="n">tem_batata</span><span class="p">:</span>
<span class="n">qtd_batatas</span> <span class="o">=</span> <span class="mi">2</span>
<span class="k">if</span> <span class="n">tem_cebola</span><span class="p">:</span>
<span class="n">qtd_batatas</span> <span class="o">=</span> <span class="mi">3</span>
<span class="k">print</span><span class="p">(</span><span class="sa">f</span><span class="s">'Querida, cheguei! Trouxe as </span><span class="si">{</span><span class="n">qtd_batatas</span><span class="si">}</span><span class="s"> batatas.'</span><span class="p">)</span>
</code></pre></div></div>
<p>Como no mercado tinha batata e cebola, o resultado é:</p>
<pre><code class="language-none">Querida, cheguei! Trouxe as 3 batatas.
</code></pre>
<p>Mas na verdade o que a esposa quis dizer é:</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">if</span> <span class="n">tem_batata</span><span class="p">:</span>
<span class="n">qtd_batatas</span> <span class="o">=</span> <span class="mi">2</span>
<span class="k">if</span> <span class="n">tem_cebola</span><span class="p">:</span>
<span class="n">qtd_cebolas</span> <span class="o">=</span> <span class="mi">3</span> <span class="c1"># "Foi isso que eu quis dizer!"
</span></code></pre></div></div>
<p>Dadas as devidas proporções, é mais ou menos isso que acontece praticamente todos os dias na área de desenvolvimento. Alguém dá uma “especificação”, que na verdade é só uma ou duas frases, porque a pessoa acha aquilo tão óbvio que não precisa detalhar. Não precisa de contexto, não há a menor chance de alguém interpretar errado, não tem ambiguidade nenhuma na frase. Não é mesmo?</p>
<p>Aí a equipe de desenvolvimento entende outra coisa e sai fazendo, mesmo achando a regra estranha (“<em>O que a cebola tem a ver com a quantidade de batatas?</em>”).</p>
<p>Tudo bem que esse caso é anedótico e até meio exagerado, e esmagadora maioria vai achar “óbvio” que a esposa quis dizer “3 cebolas”, mas pense em quantas vezes já aconteceu alguma falha de comunicação parecida em projetos nos quais você já trabalhou. Aquela mensagem com duas frases sem pontuação, uma resposta “sim” para um email com várias perguntas, um comentário desconexo, a lista é longa.</p>
<p>E muitas vezes isso poderia ser evitado com uma simples confirmação (“<em>É isso mesmo? São 3 batatas ou 3 cebolas?</em>”). Por mais “óbvio” ou “besta” que possa parecer, nunca é demais confirmar algo que pode dar margem para ambiguidade.</p>
<hr />
<p><strong>PS</strong>: claro que isso também se resolve com uma especificação detalhada e clara, sem ambiguidades. Mas em caso de dúvida, não custa nada perguntar, por mais óbvio que pareça…</p>Hugo KotsuboVocê provavelmente já ouviu alguma variação dessa piada. A esposa do programador diz para ele ir ao mercado e dá instruções bem claras: -Se tiver batata, traga duas. Se tiver cebola, traga três. E então ele volta com 3 batatas. Apesar de besta, dá para tirar uma “lição de moral” da piada. O problema aconteceu porque o programador entendeu isso (exemplo em Python): tem_batata = tem_cebola = True if tem_batata: qtd_batatas = 2 if tem_cebola: qtd_batatas = 3 print(f'Querida, cheguei! Trouxe as {qtd_batatas} batatas.') Como no mercado tinha batata e cebola, o resultado é: Querida, cheguei! Trouxe as 3 batatas. Mas na verdade o que a esposa quis dizer é: if tem_batata: qtd_batatas = 2 if tem_cebola: qtd_cebolas = 3 # "Foi isso que eu quis dizer!" Dadas as devidas proporções, é mais ou menos isso que acontece praticamente todos os dias na área de desenvolvimento. Alguém dá uma “especificação”, que na verdade é só uma ou duas frases, porque a pessoa acha aquilo tão óbvio que não precisa detalhar. Não precisa de contexto, não há a menor chance de alguém interpretar errado, não tem ambiguidade nenhuma na frase. Não é mesmo? Aí a equipe de desenvolvimento entende outra coisa e sai fazendo, mesmo achando a regra estranha (“O que a cebola tem a ver com a quantidade de batatas?”). Tudo bem que esse caso é anedótico e até meio exagerado, e esmagadora maioria vai achar “óbvio” que a esposa quis dizer “3 cebolas”, mas pense em quantas vezes já aconteceu alguma falha de comunicação parecida em projetos nos quais você já trabalhou. Aquela mensagem com duas frases sem pontuação, uma resposta “sim” para um email com várias perguntas, um comentário desconexo, a lista é longa. E muitas vezes isso poderia ser evitado com uma simples confirmação (“É isso mesmo? São 3 batatas ou 3 cebolas?”). Por mais “óbvio” ou “besta” que possa parecer, nunca é demais confirmar algo que pode dar margem para ambiguidade. PS: claro que isso também se resolve com uma especificação detalhada e clara, sem ambiguidades. Mas em caso de dúvida, não custa nada perguntar, por mais óbvio que pareça…Meu JSON retornou a data /Date(1555375534143-0300)/, o que eu faço?2020-01-18T15:00:00-03:002020-01-18T15:00:00-03:00https://hkotsubo.github.io/blog/2020-01-18/meu-json-retornou-a-data-date-1555375534143-0300-o-que-eu-faco<p>Este formato é conhecido como <a href="https://davidsekar.com/javascript/converting-json-date-string-date-to-date-object">Microsoft JSON Date</a>, já que algumas versões mais antigas do Framework .NET serializavam datas neste formato (embora não pareça ser um “nome oficial”, mas muitos o conhecem assim).</p>
<p>É importante salientar que ele <strong>não</strong> faz parte da <a href="http://json.org/">especificação oficial do formato JSON</a>. Conforme já explicado <a href="/blog/2019-04-13/como-ler-e-manipular-um-json" class="new-window">neste outro post</a>, um JSON possui vários tipos definidos, como números e strings, mas não possui nenhum tipo específico para datas. Portanto, o valor em questão (<code class="language-plaintext highlighter-rouge">/Date(1555375534143-0300)/</code>) <strong>é na verdade uma string</strong>. Cabe a você transformar esta string em uma data.</p>
<h1 id="como-interpretar-este-formato">Como interpretar este formato</h1>
<p>Este valor pode vir como <code class="language-plaintext highlighter-rouge">/Date(1555375534143-0300)/</code> ou <code class="language-plaintext highlighter-rouge">/Date(1555375534143)/</code>. As barras, parênteses e a palavra “Date” são sempre fixas, e o que importa de fato é o que está dentro dos parênteses.</p>
<p>O número gigante (<code class="language-plaintext highlighter-rouge">1555375534143</code>) é um <a href="/blog/2019-05-02/o-que-e-timestamp" class="new-window">timestamp</a> <strong>←</strong> Neste link há uma descrição bem detalhada (recomendo a leitura caso você não saiba o que é um timestamp), mas apenas para resumir:</p>
<ul>
<li>o timestamp representa um único instante, um ponto específico na linha do tempo (a quantidade de tempo decorrida desde o “instante zero”, também conhecido como <em>Unix Epoch</em> - que corresponde a 1 de janeiro de 1970, à meia-noite, em <a href="https://en.wikipedia.org/wiki/Coordinated_Universal_Time">UTC</a>)</li>
<li>ele pode corresponder a uma data e hora diferente em cada parte do mundo (ou seja, depende do fuso horário)</li>
<li>o timestamp é tradicionalmente representado em segundos ou milissegundos, e no caso deste post, o valor está em milissegundos</li>
</ul>
<p>O segundo ponto é importante, pois para converter o timestamp para uma data, você precisa saber qual timezone (fuso horário) será usado, já que em cada timezone o resultado será uma data e hora diferente (você leu <a href="/blog/2019-05-02/o-que-e-timestamp" class="new-window">o link que eu sugeri</a>, né? Lá explica isso em detalhes). Por exemplo, o timestamp <code class="language-plaintext highlighter-rouge">1555375534143</code> corresponde às seguintes datas e horas:</p>
<table>
<thead>
<tr>
<th>Data e hora</th>
<th>Fuso horário</th>
</tr>
</thead>
<tbody>
<tr>
<td>15/04/2019, às 21:45:34.143</td>
<td>São Paulo</td>
</tr>
<tr>
<td>15/04/2019, às 17:45:34.143</td>
<td>Los Angeles</td>
</tr>
<tr>
<td><strong>16</strong>/04/2019, às 09:45:34.143</td>
<td>Tóquio</td>
</tr>
<tr>
<td><strong>16</strong>/04/2019, às 00:45:34.143</td>
<td>UTC</td>
</tr>
</tbody>
</table>
<p>Todas as datas e horas acima correspondem ao mesmo timestamp (<code class="language-plaintext highlighter-rouge">1555375534143</code>), portanto, ao fazer a conversão é necessário saber qual o timezone sendo usado.</p>
<p>O segundo valor (<code class="language-plaintext highlighter-rouge">-0300</code>) é um <em>offset</em>, ou seja, a diferença em relação a <a href="https://en.wikipedia.org/wiki/Coordinated_Universal_Time">UTC</a>. No caso, <code class="language-plaintext highlighter-rouge">-0300</code> significa 3 horas a menos que o UTC, que é o offset usado em São Paulo naquele instante.</p>
<p>Dito isso, como interpretar valores como <code class="language-plaintext highlighter-rouge">/Date(1555375534143-0300)/</code> ou <code class="language-plaintext highlighter-rouge">/Date(1555375534143)/</code>? Depende.</p>
<p>Você pode pegar somente o timestamp e ignorar o <em>offset</em>, e em seguida converter o timestamp para uma data e hora, usando algum timezone qualquer. Ou você pode usar o <em>offset</em> que foi passado para fazer esta conversão (e usar UTC, ou algum timezone <em>default</em>, caso não haja um <em>offset</em>). Ou você pode simplesmente usar o valor do timestamp (<code class="language-plaintext highlighter-rouge">1555375534143</code>) da forma que está. Tudo depende do que você quer ou precisa fazer.</p>
<p>Para extrair os valores, você pode usar tanto <code class="language-plaintext highlighter-rouge">substring</code> quanto expressões regulares (<em>regex</em>), e dependendo da linguagem, é possível fazer o <em>parsing</em> diretamente, usando uma API de datas. Abaixo tem exemplos em algumas das linguagens que conheço (com uma ênfase maior em Java, pois é a API que tenho mais familiaridade). Se quiser, pode usar os links abaixo para ir direto para a linguagem de sua preferência:</p>
<ul>
<li><a href="#java">Java</a></li>
<li><a href="#c">C#</a></li>
<li><a href="#python">Python</a></li>
<li><a href="#php">PHP</a></li>
<li><a href="#javascript">JavaScript</a></li>
</ul>
<h3 id="java">Java</h3>
<p>Se você estiver usando o Java >= 8, use a <a href="https://docs.oracle.com/javase/8/docs/api/java/time/package-summary.html">API <code class="language-plaintext highlighter-rouge">java.time</code></a>. Com um <a href="https://docs.oracle.com/javase/8/docs/api/java/time/format/DateTimeFormatterBuilder.html"><code class="language-plaintext highlighter-rouge">java.time.format.DateTimeFormatterBuilder</code></a>, é possível construir um <a href="https://docs.oracle.com/javase/8/docs/api/java/time/format/DateTimeFormatter.html"><code class="language-plaintext highlighter-rouge">java.time.format.DateTimeFormatter</code></a> que faz o <em>parsing</em> deste formato.</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nc">DateTimeFormatter</span> <span class="n">parser</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">DateTimeFormatterBuilder</span><span class="o">()</span>
<span class="c1">// parte inicial</span>
<span class="o">.</span><span class="na">appendLiteral</span><span class="o">(</span><span class="s">"/Date("</span><span class="o">)</span>
<span class="c1">// para o timestamp, usa-se o InstantSeconds e os milissegundos</span>
<span class="o">.</span><span class="na">appendValue</span><span class="o">(</span><span class="nc">ChronoField</span><span class="o">.</span><span class="na">INSTANT_SECONDS</span><span class="o">)</span>
<span class="o">.</span><span class="na">appendValue</span><span class="o">(</span><span class="nc">ChronoField</span><span class="o">.</span><span class="na">MILLI_OF_SECOND</span><span class="o">,</span> <span class="mi">3</span><span class="o">)</span>
<span class="c1">// offset opcional (colchetes indicam que o campo é opcional)</span>
<span class="o">.</span><span class="na">appendPattern</span><span class="o">(</span><span class="s">"[XX]"</span><span class="o">)</span>
<span class="c1">// se não tiver offset, assume-se que é zero (UTC)</span>
<span class="o">.</span><span class="na">parseDefaulting</span><span class="o">(</span><span class="nc">ChronoField</span><span class="o">.</span><span class="na">OFFSET_SECONDS</span><span class="o">,</span> <span class="mi">0</span><span class="o">)</span>
<span class="c1">// parte final, e cria o DateTimeFormatter</span>
<span class="o">.</span><span class="na">appendLiteral</span><span class="o">(</span><span class="s">")/"</span><span class="o">).</span><span class="na">toFormatter</span><span class="o">();</span>
<span class="nc">OffsetDateTime</span> <span class="n">odt1</span> <span class="o">=</span> <span class="nc">OffsetDateTime</span><span class="o">.</span><span class="na">parse</span><span class="o">(</span><span class="s">"/Date(1555375534143)/"</span><span class="o">,</span> <span class="n">parser</span><span class="o">);</span>
<span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="n">odt1</span><span class="o">);</span> <span class="c1">// 2019-04-16T00:45:34.143Z</span>
<span class="nc">OffsetDateTime</span> <span class="n">odt2</span> <span class="o">=</span> <span class="nc">OffsetDateTime</span><span class="o">.</span><span class="na">parse</span><span class="o">(</span><span class="s">"/Date(1555375534143-0300)/"</span><span class="o">,</span> <span class="n">parser</span><span class="o">);</span>
<span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="n">odt2</span><span class="o">);</span> <span class="c1">// 2019-04-15T21:45:34.143-03:00</span>
</code></pre></div></div>
<p>No exemplo acima eu uso <a href="https://docs.oracle.com/javase/8/docs/api/java/time/temporal/ChronoField.html"><code class="language-plaintext highlighter-rouge">java.time.temporal.ChronoField</code></a> para definir o trecho que obtém o timestamp. São usados <a href="https://docs.oracle.com/javase/8/docs/api/java/time/temporal/ChronoField.html#INSTANT_SECONDS"><code class="language-plaintext highlighter-rouge">INSTANT_SECONDS</code></a>, que captura o trecho <code class="language-plaintext highlighter-rouge">1555375534</code>, e <a href="https://docs.oracle.com/javase/8/docs/api/java/time/temporal/ChronoField.html#MILLI_OF_SECOND"><code class="language-plaintext highlighter-rouge">MILLI_OF_SECONDS</code></a>, que captura o trecho <code class="language-plaintext highlighter-rouge">143</code>.</p>
<p>Depois, é feito o <em>parsing</em> para um <a href="https://docs.oracle.com/javase/8/docs/api/java/time/OffsetDateTime.html"><code class="language-plaintext highlighter-rouge">java.time.OffsetDateTime</code></a>, que possui a data, hora e <em>offset</em>. Quando a entrada possui um <em>offset</em>, eu uso o respectivo valor, mas quando não tem <em>offset</em>, por padrão eu uso zero (que corresponde a UTC).</p>
<p>Com isso, eu tenho o valor do timestamp e o <em>offset</em>, e internamente o método <code class="language-plaintext highlighter-rouge">parse</code> faz as devidas conversões para gerar a data e hora correspondentes. Repare que isso faz com que os valores de data e hora mudem no resultado final, por isso é importante definir qual <em>offset</em> ou timezone será usado.</p>
<p>Se quiser o valor numérico do timestamp, use <code class="language-plaintext highlighter-rouge">odt1.toInstant().toEpochMilli()</code> (também funciona com <code class="language-plaintext highlighter-rouge">odt2</code>, pois ambos correspondem ao mesmo instante - e portanto, ao mesmo timestamp). Já para obter o <em>offset</em>, você pode usar:</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nc">ZoneOffset</span> <span class="n">offset</span> <span class="o">=</span> <span class="n">odt2</span><span class="o">.</span><span class="na">getOffset</span><span class="o">();</span>
<span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="n">offset</span><span class="o">.</span><span class="na">getTotalSeconds</span><span class="o">());</span> <span class="c1">// -10800</span>
<span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="nc">Duration</span><span class="o">.</span><span class="na">ofSeconds</span><span class="o">(</span><span class="n">offset</span><span class="o">.</span><span class="na">getTotalSeconds</span><span class="o">()).</span><span class="na">toHours</span><span class="o">());</span> <span class="c1">// -3</span>
</code></pre></div></div>
<p>Neste caso, há diferença em usar <code class="language-plaintext highlighter-rouge">odt1</code> ou <code class="language-plaintext highlighter-rouge">odt2</code>, pois o primeiro está em UTC (offset zero) e o segundo está no <em>offset</em> <code class="language-plaintext highlighter-rouge">-03:00</code> (3 horas a menos que UTC). O método <a href="https://docs.oracle.com/javase/8/docs/api/java/time/ZoneOffset.html#getTotalSeconds--"><code class="language-plaintext highlighter-rouge">getTotalSeconds()</code></a> retorna o valor total do <em>offset</em> em segundos (no caso acima, <code class="language-plaintext highlighter-rouge">-10800</code>, que corresponde a “menos 3 horas”).</p>
<p>Também é usado um <a href="https://docs.oracle.com/javase/8/docs/api/java/time/Duration.html"><code class="language-plaintext highlighter-rouge">java.time.Duration</code></a> para converter o valor dos segundos para horas, resultando em <code class="language-plaintext highlighter-rouge">-3</code>. Mas atenção, o método <a href="https://docs.oracle.com/javase/8/docs/api/java/time/Duration.html#toHours--"><code class="language-plaintext highlighter-rouge">toHours()</code></a> arredonda o valor e isso pode fazer diferença para casos em que o <em>offset</em> não é de horas inteiras (como ocorre por exemplo na Índia, que atualmente usa o <em>offset</em> <code class="language-plaintext highlighter-rouge">+05:30</code> - 5 horas e meia à frente do UTC). Para obter o valor exato do <em>offset</em>, melhor usar <code class="language-plaintext highlighter-rouge">getTotalSeconds()</code>.</p>
<p>Lembrando que, como o <em>offset</em> não é o mesmo, <code class="language-plaintext highlighter-rouge">odt1.getOffset().getTotalSeconds()</code> retornará zero, já que ele foi obtido da string <code class="language-plaintext highlighter-rouge">/Date(1555375534143)/</code>, que não possui <em>offset</em>, e no nosso <code class="language-plaintext highlighter-rouge">DateTimeFormatter</code> foi definido que neste caso usa-se o <em>offset</em> zero.</p>
<p>Se você estiver usando Java 6 e 7, pode usar o <a href="https://www.threeten.org/threetenbp/">Threeten Backport</a>, um <em>backport</em> do <code class="language-plaintext highlighter-rouge">java.time</code>. Ele basicamente possui as mesmas classes e métodos do <code class="language-plaintext highlighter-rouge">java.time</code>, a diferença é que o nome do pacote é <code class="language-plaintext highlighter-rouge">org.threeten.bp</code>. Ou seja, com exceção dos <code class="language-plaintext highlighter-rouge">import</code>’s, o código ficará igual ao do exemplo acima.</p>
<hr />
<p>Obviamente, você também pode usar a API legada (<a href="https://docs.oracle.com/javase/8/docs/api/java/util/Date.html"><code class="language-plaintext highlighter-rouge">java.util.Date</code></a> e <a href="https://docs.oracle.com/javase/8/docs/api/java/text/SimpleDateFormat.html"><code class="language-plaintext highlighter-rouge">java.text.SimpleDateFormat</code></a>). Infelizmente, com esta API não é possível fazer algo tão direto quanto o exemplo acima com <code class="language-plaintext highlighter-rouge">DateTimeFormatter</code>, então o jeito é extrair as informações diretamente da <code class="language-plaintext highlighter-rouge">String</code>. Uma das opções é usar regex, através do pacote <code class="language-plaintext highlighter-rouge">java.util.regex</code>:</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// Matcher e Pattern fazem parte do pacote java.util.regex</span>
<span class="nc">Matcher</span> <span class="n">matcher</span> <span class="o">=</span> <span class="nc">Pattern</span><span class="o">.</span><span class="na">compile</span><span class="o">(</span><span class="s">"/Date\\((\\d+)([-+]\\d{4})?\\)/"</span><span class="o">)</span>
<span class="o">.</span><span class="na">matcher</span><span class="o">(</span><span class="s">"/Date(1555375534143)/"</span><span class="o">);</span>
<span class="k">if</span> <span class="o">(</span><span class="n">matcher</span><span class="o">.</span><span class="na">find</span><span class="o">())</span> <span class="o">{</span>
<span class="kt">long</span> <span class="n">timestamp</span> <span class="o">=</span> <span class="nc">Long</span><span class="o">.</span><span class="na">parseLong</span><span class="o">(</span><span class="n">matcher</span><span class="o">.</span><span class="na">group</span><span class="o">(</span><span class="mi">1</span><span class="o">));</span>
<span class="nc">String</span> <span class="n">offset</span> <span class="o">=</span> <span class="n">matcher</span><span class="o">.</span><span class="na">group</span><span class="o">(</span><span class="mi">2</span><span class="o">);</span>
<span class="k">if</span> <span class="o">(</span><span class="n">offset</span> <span class="o">==</span> <span class="kc">null</span><span class="o">)</span> <span class="o">{</span> <span class="c1">// se não tem offset, usa zero</span>
<span class="n">offset</span> <span class="o">=</span> <span class="s">"+0000"</span><span class="o">;</span>
<span class="o">}</span>
<span class="c1">// se quiser um java.util.Date, pode parar por aqui</span>
<span class="nc">Date</span> <span class="n">date</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">Date</span><span class="o">(</span><span class="n">timestamp</span><span class="o">);</span>
<span class="c1">// se quiser obter uma String com a data e hora correspondente no offset em questão</span>
<span class="nc">SimpleDateFormat</span> <span class="n">sdf</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">SimpleDateFormat</span><span class="o">(</span><span class="s">"dd/MM/yyyy HH:mm:ss XXX"</span><span class="o">);</span>
<span class="c1">// usar o offset para converter o Date em uma data e hora</span>
<span class="n">sdf</span><span class="o">.</span><span class="na">setTimeZone</span><span class="o">(</span><span class="nc">TimeZone</span><span class="o">.</span><span class="na">getTimeZone</span><span class="o">(</span><span class="s">"GMT"</span> <span class="o">+</span> <span class="n">offset</span><span class="o">));</span>
<span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="n">sdf</span><span class="o">.</span><span class="na">format</span><span class="o">(</span><span class="n">date</span><span class="o">));</span> <span class="c1">// 16/04/2019 00:45:34 Z</span>
<span class="o">}</span> <span class="k">else</span> <span class="o">{</span>
<span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="s">"String não está no formato correto"</span><span class="o">);</span>
<span class="o">}</span>
</code></pre></div></div>
<p>A lógica geral é a mesma: tenta-se obter o timestamp e o <em>offset</em> da <code class="language-plaintext highlighter-rouge">String</code>. Caso o <em>offset</em> não esteja presente, algum valor <em>default</em> é usado (no caso, estou usando zero). Eu uso <code class="language-plaintext highlighter-rouge">\\d+</code> para capturar o timestamp (um ou mais dígitos), e para o <em>offset</em> eu uso <code class="language-plaintext highlighter-rouge">[-+]</code> (o sinal de menos ou de mais) seguido de <code class="language-plaintext highlighter-rouge">\\d{4}</code> (quatro dígitos), e uso o <code class="language-plaintext highlighter-rouge">?</code> para dizer que o <em>offset</em> é <a href="https://www.regular-expressions.info/optional.html">opcional</a>. Cada um dos campos está entre parênteses para formar <a href="https://www.regular-expressions.info/brackets.html">grupos de captura</a>, e com isso eu posso obter os respectivos valores usando o método <code class="language-plaintext highlighter-rouge">group</code> (no caso, o grupo 1 é o timestamp e o grupo 2 é o offset).</p>
<p>A seguir, eu uso o timestamp para criar um <code class="language-plaintext highlighter-rouge">java.util.Date</code>. E um <code class="language-plaintext highlighter-rouge">Date</code>, apesar do nome, não representa uma data, e sim um timestamp. Esse é um ponto meio confuso, pois ao imprimir o <code class="language-plaintext highlighter-rouge">Date</code>, ele usa o timezone <em>default</em> da JVM para obter os valores de data e hora. Exemplo:</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nc">Date</span> <span class="n">data</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">Date</span><span class="o">(</span><span class="mi">1555375534143L</span><span class="o">);</span>
<span class="nc">TimeZone</span><span class="o">.</span><span class="na">setDefault</span><span class="o">(</span><span class="nc">TimeZone</span><span class="o">.</span><span class="na">getTimeZone</span><span class="o">(</span><span class="s">"America/Sao_Paulo"</span><span class="o">));</span>
<span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="n">data</span><span class="o">.</span><span class="na">getTime</span><span class="o">()</span> <span class="o">+</span> <span class="s">"="</span> <span class="o">+</span> <span class="n">data</span><span class="o">);</span>
<span class="nc">TimeZone</span><span class="o">.</span><span class="na">setDefault</span><span class="o">(</span><span class="nc">TimeZone</span><span class="o">.</span><span class="na">getTimeZone</span><span class="o">(</span><span class="s">"Asia/Tokyo"</span><span class="o">));</span>
<span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="n">data</span><span class="o">.</span><span class="na">getTime</span><span class="o">()</span> <span class="o">+</span> <span class="s">"="</span> <span class="o">+</span> <span class="n">data</span><span class="o">);</span>
</code></pre></div></div>
<p>No código acima estou imprimindo o timestamp (através do método <a href="https://docs.oracle.com/javase/8/docs/api/java/util/Date.html#getTime--"><code class="language-plaintext highlighter-rouge">getTime()</code></a>), e em seguida imprimo a própria data, que internamente chama o método <a href="https://docs.oracle.com/javase/8/docs/api/java/util/Date.html#toString--"><code class="language-plaintext highlighter-rouge">toString()</code></a>. A saída é:</p>
<pre><code class="language-none">1555375534143=Mon Apr 15 21:45:34 BRT 2019
1555375534143=Tue Apr 16 09:45:34 JST 2019
</code></pre>
<p>Repare que o valor do timestamp é o mesmo, mas o valor retornado por <code class="language-plaintext highlighter-rouge">toString()</code> não, pois este método usa o timezone <em>default</em> que está setado no momento, para converter o timestamp para uma data e hora específicas. Mas o <code class="language-plaintext highlighter-rouge">Date</code> não possui esses valores de data e hora: internamente, esta classe só possui o valor do timestamp. <strong>Por isso que o construtor de <code class="language-plaintext highlighter-rouge">Date</code> só precisa do timestamp, e se quisermos apenas uma instância de <code class="language-plaintext highlighter-rouge">Date</code>, não precisamos do <em>offset</em></strong>.</p>
<p>Mas caso você queira uma <code class="language-plaintext highlighter-rouge">String</code> contendo a data, hora correspondentes ao timestamp, no offset indicado, basta usar um <code class="language-plaintext highlighter-rouge">SimpleDateFormat</code>, como o código acima mostra. Ele também usa um <a href="https://docs.oracle.com/javase/8/docs/api/java/util/TimeZone.html"><code class="language-plaintext highlighter-rouge">java.util.TimeZone</code></a> com o valor do <em>offset</em> que foi obtido pela regex. Com isso, a saída é uma <code class="language-plaintext highlighter-rouge">String</code> que corresponde ao timestamp e ao offset que estavam na entrada.</p>
<p>No exemplo acima, a saída é <code class="language-plaintext highlighter-rouge">16/04/2019 00:45:34 Z</code> (este “Z” no final <a href="https://en.wikipedia.org/wiki/ISO_8601#Coordinated_Universal_Time_(UTC)">indica que está em UTC</a> - ou seja, que o <em>offset</em> é zero). Se eu testar com <code class="language-plaintext highlighter-rouge">"/Date(1555375534143-0300)/"</code>, a saída é <code class="language-plaintext highlighter-rouge">15/04/2019 21:45:34 -03:00</code>.</p>
<p>Também daria para obter o timestamp e <em>offset</em> usando <code class="language-plaintext highlighter-rouge">substring</code>, e fazendo alguns <code class="language-plaintext highlighter-rouge">if</code>’s para saber se o <em>offset</em> existe, mas eu acho que a solução com regex é mais direta nesse caso.</p>
<h3 id="c">C#</h3>
<p>Em C# você pode usar o <a href="https://www.newtonsoft.com/json">Json.NET</a>, que possui a classe <a href="https://www.newtonsoft.com/json/help/html/SerializingJSON.htm#JsonConvert"><code class="language-plaintext highlighter-rouge">JsonConvert</code></a>. Com isso podemos obter um <a href="https://docs.microsoft.com/en-us/dotnet/api/system.datetime?view=netframework-4.8"><code class="language-plaintext highlighter-rouge">DateTime</code></a>:</p>
<div class="language-c# highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kt">string</span> <span class="n">sa</span> <span class="p">=</span> <span class="s">@""""</span> <span class="p">+</span> <span class="s">"/Date(1555375534143-0300)/"</span> <span class="p">+</span> <span class="s">@""""</span><span class="p">;</span>
<span class="n">DateTime</span> <span class="n">dt</span> <span class="p">=</span> <span class="n">JsonConvert</span><span class="p">.</span><span class="n">DeserializeObject</span><span class="p"><</span><span class="n">DateTime</span><span class="p">>(</span><span class="n">sa</span><span class="p">);</span>
<span class="n">Console</span><span class="p">.</span><span class="nf">WriteLine</span><span class="p">(</span><span class="n">dt</span><span class="p">);</span> <span class="c1">// 4/15/19 9:45:34 PM</span>
</code></pre></div></div>
<p>De forma similar ao que foi feito acima em Java, também podemos obter o timestamp e o <em>offset</em> usando regex, e em seguida usamos um <a href="https://docs.microsoft.com/en-us/dotnet/api/system.datetimeoffset?view=netframework-4.8"><code class="language-plaintext highlighter-rouge">DateTimeOffset</code></a> para obter a data e hora correspondente:</p>
<div class="language-c# highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">Regex</span> <span class="n">r</span> <span class="p">=</span> <span class="k">new</span> <span class="nf">Regex</span><span class="p">(</span><span class="s">@"/Date\((\d+)([-+]\d{4})?\)/"</span><span class="p">);</span>
<span class="n">Match</span> <span class="n">match</span> <span class="p">=</span> <span class="n">r</span><span class="p">.</span><span class="nf">Match</span><span class="p">(</span><span class="s">"/Date(1555375534143-0300)/"</span><span class="p">);</span>
<span class="k">if</span> <span class="p">(</span><span class="n">match</span><span class="p">.</span><span class="n">Success</span><span class="p">)</span>
<span class="p">{</span>
<span class="kt">long</span> <span class="n">timestamp</span> <span class="p">=</span> <span class="kt">long</span><span class="p">.</span><span class="nf">Parse</span><span class="p">(</span><span class="n">match</span><span class="p">.</span><span class="n">Groups</span><span class="p">[</span><span class="m">1</span><span class="p">].</span><span class="n">Value</span><span class="p">);</span>
<span class="n">TimeSpan</span> <span class="n">offset</span> <span class="p">=</span> <span class="n">TimeSpan</span><span class="p">.</span><span class="n">Zero</span><span class="p">;</span> <span class="c1">// offset por padrão é zero</span>
<span class="k">if</span><span class="p">(</span><span class="n">match</span><span class="p">.</span><span class="n">Groups</span><span class="p">[</span><span class="m">2</span><span class="p">].</span><span class="n">Success</span><span class="p">)</span>
<span class="p">{</span> <span class="c1">// se foi encontrado offset</span>
<span class="kt">string</span> <span class="k">value</span> <span class="p">=</span> <span class="n">match</span><span class="p">.</span><span class="n">Groups</span><span class="p">[</span><span class="m">2</span><span class="p">].</span><span class="n">Value</span><span class="p">;</span>
<span class="kt">int</span> <span class="n">hours</span> <span class="p">=</span> <span class="kt">int</span><span class="p">.</span><span class="nf">Parse</span><span class="p">(</span><span class="k">value</span><span class="p">.</span><span class="nf">Substring</span><span class="p">(</span><span class="m">1</span><span class="p">,</span> <span class="m">2</span><span class="p">));</span>
<span class="k">if</span> <span class="p">(</span><span class="k">value</span><span class="p">.</span><span class="nf">Substring</span><span class="p">(</span><span class="m">0</span><span class="p">,</span> <span class="m">1</span><span class="p">).</span><span class="nf">Equals</span><span class="p">(</span><span class="s">"-"</span><span class="p">))</span>
<span class="p">{</span>
<span class="n">hours</span> <span class="p">=</span> <span class="p">-</span><span class="n">hours</span><span class="p">;</span>
<span class="p">}</span>
<span class="kt">int</span> <span class="n">minutes</span> <span class="p">=</span> <span class="kt">int</span><span class="p">.</span><span class="nf">Parse</span><span class="p">(</span><span class="k">value</span><span class="p">.</span><span class="nf">Substring</span><span class="p">(</span><span class="m">3</span><span class="p">));</span>
<span class="n">offset</span> <span class="p">=</span> <span class="k">new</span> <span class="nf">TimeSpan</span><span class="p">(</span><span class="n">hours</span><span class="p">,</span> <span class="n">minutes</span><span class="p">,</span> <span class="m">0</span><span class="p">);</span>
<span class="p">}</span>
<span class="n">DateTimeOffset</span> <span class="n">dt</span> <span class="p">=</span> <span class="n">DateTimeOffset</span><span class="p">.</span><span class="nf">FromUnixTimeMilliseconds</span><span class="p">(</span><span class="n">timestamp</span><span class="p">).</span><span class="nf">ToOffset</span><span class="p">(</span><span class="n">offset</span><span class="p">);</span>
<span class="n">Console</span><span class="p">.</span><span class="nf">WriteLine</span><span class="p">(</span><span class="n">dt</span><span class="p">);</span> <span class="c1">// 4/15/19 9:45:34 PM -03:00</span>
<span class="p">}</span>
</code></pre></div></div>
<p>Foi feito um pequeno trabalho com substrings para obter o valor correto do <em>offset</em> como um <a href="https://docs.microsoft.com/en-us/dotnet/api/system.timespan?view=netframework-4.8"><code class="language-plaintext highlighter-rouge">TimeSpan</code></a>. Em seguida, usamos o método <a href="https://docs.microsoft.com/en-us/dotnet/api/system.datetimeoffset.fromunixtimemilliseconds?view=netframework-4.8"><code class="language-plaintext highlighter-rouge">FromUnixTimeMilliseconds</code></a>, passando o timestamp, e o método <a href="https://docs.microsoft.com/en-us/dotnet/api/system.datetimeoffset.tooffset?view=netframework-4.8"><code class="language-plaintext highlighter-rouge">ToOffset</code></a>, que converte para o <em>offset</em> indicado.</p>
<h3 id="python">Python</h3>
<p>A ideia é a mesma: use regex para obter o timestamp e o <em>offset</em> (ou use UTC quando este não for encontrado), e em seguida crie uma data. No caso, estou usando o <a href="https://docs.python.org/3.6/library/datetime.html">módulo <code class="language-plaintext highlighter-rouge">datetime</code></a> para criar as datas, e o <a href="https://docs.python.org/3.6/library/re.html">módulo <code class="language-plaintext highlighter-rouge">re</code></a> para expressões regulares:</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">from</span> <span class="nn">datetime</span> <span class="kn">import</span> <span class="n">datetime</span><span class="p">,</span> <span class="n">timezone</span><span class="p">,</span> <span class="n">timedelta</span>
<span class="kn">import</span> <span class="nn">re</span>
<span class="n">m</span> <span class="o">=</span> <span class="n">re</span><span class="p">.</span><span class="n">match</span><span class="p">(</span><span class="sa">r</span><span class="s">'/Date\((\d+)([-+]\d{4})?\)/'</span><span class="p">,</span> <span class="s">'/Date(1555375534143-0300)/'</span><span class="p">)</span>
<span class="k">if</span> <span class="n">m</span><span class="p">:</span>
<span class="n">timestamp</span> <span class="o">=</span> <span class="nb">int</span><span class="p">(</span><span class="n">m</span><span class="p">.</span><span class="n">group</span><span class="p">(</span><span class="mi">1</span><span class="p">))</span>
<span class="n">offset</span> <span class="o">=</span> <span class="n">m</span><span class="p">.</span><span class="n">group</span><span class="p">(</span><span class="mi">2</span><span class="p">)</span>
<span class="k">if</span> <span class="n">offset</span><span class="p">:</span>
<span class="n">total</span> <span class="o">=</span> <span class="n">timedelta</span><span class="p">(</span><span class="n">hours</span> <span class="o">=</span> <span class="nb">int</span><span class="p">(</span><span class="n">offset</span><span class="p">[</span><span class="mi">1</span><span class="p">:</span><span class="mi">3</span><span class="p">]),</span> <span class="n">minutes</span> <span class="o">=</span> <span class="nb">int</span><span class="p">(</span><span class="n">offset</span><span class="p">[</span><span class="mi">3</span><span class="p">:]))</span>
<span class="k">if</span> <span class="n">offset</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span> <span class="o">==</span> <span class="s">'-'</span><span class="p">:</span>
<span class="n">total</span> <span class="o">=</span> <span class="o">-</span><span class="n">total</span>
<span class="n">offset</span> <span class="o">=</span> <span class="n">timezone</span><span class="p">(</span><span class="n">total</span><span class="p">)</span>
<span class="k">else</span><span class="p">:</span> <span class="c1"># não tem o offset, usar UTC
</span> <span class="n">offset</span> <span class="o">=</span> <span class="n">timezone</span><span class="p">.</span><span class="n">utc</span>
<span class="c1"># o timestamp está em milissegundos, mas fromtimestamp recebe o valor em segundos
</span> <span class="n">data</span> <span class="o">=</span> <span class="n">datetime</span><span class="p">.</span><span class="n">fromtimestamp</span><span class="p">(</span><span class="n">timestamp</span> <span class="o">/</span> <span class="mi">1000</span><span class="p">).</span><span class="n">astimezone</span><span class="p">(</span><span class="n">offset</span><span class="p">)</span>
<span class="k">print</span><span class="p">(</span><span class="n">data</span><span class="p">)</span> <span class="c1"># 2019-04-15 21:45:34.143000-03:00
</span></code></pre></div></div>
<h3 id="php">PHP</h3>
<p>Similar aos demais, basta usar uma expressão regular para obter o timestamp e o <em>offset</em>:</p>
<div class="language-php highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">if</span> <span class="p">(</span><span class="nb">preg_match</span><span class="p">(</span><span class="s1">'/Date\((\d+)([-+]\d{4})?\)/'</span><span class="p">,</span> <span class="s1">'/Date(1555375534143-0300)/'</span><span class="p">,</span> <span class="nv">$matches</span><span class="p">))</span> <span class="p">{</span>
<span class="nv">$timestamp</span> <span class="o">=</span> <span class="nb">number_format</span><span class="p">(</span><span class="nv">$matches</span><span class="p">[</span><span class="mi">1</span><span class="p">]</span> <span class="o">/</span> <span class="mi">1000</span><span class="p">,</span> <span class="mi">3</span><span class="p">,</span> <span class="s1">'.'</span><span class="p">,</span> <span class="s1">''</span><span class="p">);</span>
<span class="c1">// $matches pode ter até 3 elementos: o match total e os dois grupos de captura</span>
<span class="k">if</span> <span class="p">(</span><span class="nb">count</span><span class="p">(</span><span class="nv">$matches</span><span class="p">)</span> <span class="o">===</span> <span class="mi">3</span><span class="p">)</span> <span class="p">{</span>
<span class="nv">$offset</span> <span class="o">=</span> <span class="nv">$matches</span><span class="p">[</span><span class="mi">2</span><span class="p">];</span>
<span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
<span class="nv">$offset</span> <span class="o">=</span> <span class="s1">'+0000'</span><span class="p">;</span>
<span class="p">}</span>
<span class="nv">$data</span> <span class="o">=</span> <span class="nc">DateTime</span><span class="o">::</span><span class="nf">createFromFormat</span><span class="p">(</span><span class="s1">'U.u'</span><span class="p">,</span> <span class="nv">$timestamp</span><span class="p">);</span>
<span class="nv">$data</span><span class="o">-></span><span class="nf">setTimeZone</span><span class="p">(</span><span class="k">new</span> <span class="nc">DateTimeZone</span><span class="p">(</span><span class="nv">$offset</span><span class="p">));</span>
<span class="p">}</span>
</code></pre></div></div>
<p>Aqui tomamos o cuidado de dividir o timestamp por 1000, pois o formato <code class="language-plaintext highlighter-rouge">U.u</code> aceita os segundos, seguido de um ponto, seguido das frações de segundo.</p>
<p>Para saber se o <em>offset</em> está presente, eu vejo se o array de <em>matches</em> tem 3 posições. Isso porque a primeira posição sempre contém todo o trecho que corresponde à expressão, e as posições subsequentes correspondem aos grupos de captura.</p>
<h3 id="javascript">JavaScript</h3>
<p>Por fim, em JavaScript, a ideia é a mesma: usar uma regex para extrair o timestamp e offset, e criar a data:</p>
<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">let</span> <span class="nx">regex</span> <span class="o">=</span> <span class="sr">/Date</span><span class="se">\((\d</span><span class="sr">+</span><span class="se">)([</span><span class="sr">-+</span><span class="se">]\d{4})?\)</span><span class="sr">/</span><span class="p">;</span>
<span class="kd">let</span> <span class="nx">match</span> <span class="o">=</span> <span class="nx">regex</span><span class="p">.</span><span class="nx">exec</span><span class="p">(</span><span class="dl">'</span><span class="s1">/Date(1555375534143-0300)/</span><span class="dl">'</span><span class="p">).</span><span class="nx">slice</span><span class="p">(</span><span class="mi">1</span><span class="p">,</span> <span class="mi">3</span><span class="p">);</span>
<span class="kd">let</span> <span class="nx">data</span> <span class="o">=</span> <span class="k">new</span> <span class="nb">Date</span><span class="p">(</span><span class="nb">parseInt</span><span class="p">(</span><span class="nx">match</span><span class="p">[</span><span class="mi">0</span><span class="p">]));</span>
</code></pre></div></div>
<p>Ao executar a regex, eu pego somente um trecho do array retornado (contendo os dois grupos de captura), já que <code class="language-plaintext highlighter-rouge">exec</code> retorna um array com mais informações que não me interessam neste caso.</p>
<p>Um detalhe é que o <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date"><code class="language-plaintext highlighter-rouge">Date</code> do JavaScript</a> (similar ao <code class="language-plaintext highlighter-rouge">java.util.Date</code> do Java) representa um <a href="/blog/2019-05-02/o-que-e-timestamp" class="new-window">timestamp</a>, e por isso ele não precisa do offset.</p>
<p>Caso queira a data no offset que está na string, uma alternativa é usar a biblioteca <a href="https://momentjs.com">Moment.js</a>:</p>
<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">let</span> <span class="nx">regex</span> <span class="o">=</span> <span class="sr">/Date</span><span class="se">\((\d</span><span class="sr">+</span><span class="se">)([</span><span class="sr">-+</span><span class="se">]\d{4})?\)</span><span class="sr">/</span><span class="p">;</span>
<span class="kd">let</span> <span class="nx">match</span> <span class="o">=</span> <span class="nx">regex</span><span class="p">.</span><span class="nx">exec</span><span class="p">(</span><span class="dl">'</span><span class="s1">/Date(1555375534143-0300)/</span><span class="dl">'</span><span class="p">).</span><span class="nx">slice</span><span class="p">(</span><span class="mi">1</span><span class="p">,</span> <span class="mi">3</span><span class="p">);</span>
<span class="kd">let</span> <span class="nx">offset</span> <span class="o">=</span> <span class="nx">match</span><span class="p">[</span><span class="mi">1</span><span class="p">]</span> <span class="o">||</span> <span class="dl">'</span><span class="s1">+0000</span><span class="dl">'</span><span class="p">;</span>
<span class="kd">let</span> <span class="nx">data</span> <span class="o">=</span> <span class="nx">moment</span><span class="p">(</span><span class="nb">parseInt</span><span class="p">(</span><span class="nx">match</span><span class="p">[</span><span class="mi">0</span><span class="p">])).</span><span class="nx">utcOffset</span><span class="p">(</span><span class="nx">offset</span><span class="p">);</span>
<span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="nx">data</span><span class="p">.</span><span class="nx">format</span><span class="p">());</span> <span class="c1">// 2019-04-15T21:45:34-03:00</span>
</code></pre></div></div>
<p>Outra opção é usar <a href="https://momentjs.com//docs/#/parsing/parse-zone/"><code class="language-plaintext highlighter-rouge">parseZone</code></a>, que suporta este formato e consegue preservar o <em>offset</em>:</p>
<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="nx">moment</span><span class="p">.</span><span class="nx">parseZone</span><span class="p">(</span><span class="dl">"</span><span class="s2">/Date(1555375534143-0300)/</span><span class="dl">"</span><span class="p">).</span><span class="nx">format</span><span class="p">());</span> <span class="c1">// 2019-04-15T21:45:34-03:00</span>
</code></pre></div></div>
<hr />
<p>Resumindo, o formato <code class="language-plaintext highlighter-rouge">/Date(1555375534143-0300)/</code> pode assustar à primeira vista, mas uma vez que entendemos a sua - falta de - lógica, não é difícil extrair dele os dados que precisamos.</p>Hugo KotsuboEste formato é conhecido como Microsoft JSON Date, já que algumas versões mais antigas do Framework .NET serializavam datas neste formato (embora não pareça ser um “nome oficial”, mas muitos o conhecem assim). É importante salientar que ele não faz parte da especificação oficial do formato JSON. Conforme já explicado neste outro post, um JSON possui vários tipos definidos, como números e strings, mas não possui nenhum tipo específico para datas. Portanto, o valor em questão (/Date(1555375534143-0300)/) é na verdade uma string. Cabe a você transformar esta string em uma data. Como interpretar este formato Este valor pode vir como /Date(1555375534143-0300)/ ou /Date(1555375534143)/. As barras, parênteses e a palavra “Date” são sempre fixas, e o que importa de fato é o que está dentro dos parênteses. O número gigante (1555375534143) é um timestamp ← Neste link há uma descrição bem detalhada (recomendo a leitura caso você não saiba o que é um timestamp), mas apenas para resumir: o timestamp representa um único instante, um ponto específico na linha do tempo (a quantidade de tempo decorrida desde o “instante zero”, também conhecido como Unix Epoch - que corresponde a 1 de janeiro de 1970, à meia-noite, em UTC) ele pode corresponder a uma data e hora diferente em cada parte do mundo (ou seja, depende do fuso horário) o timestamp é tradicionalmente representado em segundos ou milissegundos, e no caso deste post, o valor está em milissegundos O segundo ponto é importante, pois para converter o timestamp para uma data, você precisa saber qual timezone (fuso horário) será usado, já que em cada timezone o resultado será uma data e hora diferente (você leu o link que eu sugeri, né? Lá explica isso em detalhes). Por exemplo, o timestamp 1555375534143 corresponde às seguintes datas e horas: Data e hora Fuso horário 15/04/2019, às 21:45:34.143 São Paulo 15/04/2019, às 17:45:34.143 Los Angeles 16/04/2019, às 09:45:34.143 Tóquio 16/04/2019, às 00:45:34.143 UTC Todas as datas e horas acima correspondem ao mesmo timestamp (1555375534143), portanto, ao fazer a conversão é necessário saber qual o timezone sendo usado. O segundo valor (-0300) é um offset, ou seja, a diferença em relação a UTC. No caso, -0300 significa 3 horas a menos que o UTC, que é o offset usado em São Paulo naquele instante. Dito isso, como interpretar valores como /Date(1555375534143-0300)/ ou /Date(1555375534143)/? Depende. Você pode pegar somente o timestamp e ignorar o offset, e em seguida converter o timestamp para uma data e hora, usando algum timezone qualquer. Ou você pode usar o offset que foi passado para fazer esta conversão (e usar UTC, ou algum timezone default, caso não haja um offset). Ou você pode simplesmente usar o valor do timestamp (1555375534143) da forma que está. Tudo depende do que você quer ou precisa fazer. Para extrair os valores, você pode usar tanto substring quanto expressões regulares (regex), e dependendo da linguagem, é possível fazer o parsing diretamente, usando uma API de datas. Abaixo tem exemplos em algumas das linguagens que conheço (com uma ênfase maior em Java, pois é a API que tenho mais familiaridade). Se quiser, pode usar os links abaixo para ir direto para a linguagem de sua preferência: Java C# Python PHP JavaScript Java Se você estiver usando o Java >= 8, use a API java.time. Com um java.time.format.DateTimeFormatterBuilder, é possível construir um java.time.format.DateTimeFormatter que faz o parsing deste formato. DateTimeFormatter parser = new DateTimeFormatterBuilder() // parte inicial .appendLiteral("/Date(") // para o timestamp, usa-se o InstantSeconds e os milissegundos .appendValue(ChronoField.INSTANT_SECONDS) .appendValue(ChronoField.MILLI_OF_SECOND, 3) // offset opcional (colchetes indicam que o campo é opcional) .appendPattern("[XX]") // se não tiver offset, assume-se que é zero (UTC) .parseDefaulting(ChronoField.OFFSET_SECONDS, 0) // parte final, e cria o DateTimeFormatter .appendLiteral(")/").toFormatter(); OffsetDateTime odt1 = OffsetDateTime.parse("/Date(1555375534143)/", parser); System.out.println(odt1); // 2019-04-16T00:45:34.143Z OffsetDateTime odt2 = OffsetDateTime.parse("/Date(1555375534143-0300)/", parser); System.out.println(odt2); // 2019-04-15T21:45:34.143-03:00 No exemplo acima eu uso java.time.temporal.ChronoField para definir o trecho que obtém o timestamp. São usados INSTANT_SECONDS, que captura o trecho 1555375534, e MILLI_OF_SECONDS, que captura o trecho 143. Depois, é feito o parsing para um java.time.OffsetDateTime, que possui a data, hora e offset. Quando a entrada possui um offset, eu uso o respectivo valor, mas quando não tem offset, por padrão eu uso zero (que corresponde a UTC). Com isso, eu tenho o valor do timestamp e o offset, e internamente o método parse faz as devidas conversões para gerar a data e hora correspondentes. Repare que isso faz com que os valores de data e hora mudem no resultado final, por isso é importante definir qual offset ou timezone será usado. Se quiser o valor numérico do timestamp, use odt1.toInstant().toEpochMilli() (também funciona com odt2, pois ambos correspondem ao mesmo instante - e portanto, ao mesmo timestamp). Já para obter o offset, você pode usar: ZoneOffset offset = odt2.getOffset(); System.out.println(offset.getTotalSeconds()); // -10800 System.out.println(Duration.ofSeconds(offset.getTotalSeconds()).toHours()); // -3 Neste caso, há diferença em usar odt1 ou odt2, pois o primeiro está em UTC (offset zero) e o segundo está no offset -03:00 (3 horas a menos que UTC). O método getTotalSeconds() retorna o valor total do offset em segundos (no caso acima, -10800, que corresponde a “menos 3 horas”). Também é usado um java.time.Duration para converter o valor dos segundos para horas, resultando em -3. Mas atenção, o método toHours() arredonda o valor e isso pode fazer diferença para casos em que o offset não é de horas inteiras (como ocorre por exemplo na Índia, que atualmente usa o offset +05:30 - 5 horas e meia à frente do UTC). Para obter o valor exato do offset, melhor usar getTotalSeconds(). Lembrando que, como o offset não é o mesmo, odt1.getOffset().getTotalSeconds() retornará zero, já que ele foi obtido da string /Date(1555375534143)/, que não possui offset, e no nosso DateTimeFormatter foi definido que neste caso usa-se o offset zero. Se você estiver usando Java 6 e 7, pode usar o Threeten Backport, um backport do java.time. Ele basicamente possui as mesmas classes e métodos do java.time, a diferença é que o nome do pacote é org.threeten.bp. Ou seja, com exceção dos import’s, o código ficará igual ao do exemplo acima. Obviamente, você também pode usar a API legada (java.util.Date e java.text.SimpleDateFormat). Infelizmente, com esta API não é possível fazer algo tão direto quanto o exemplo acima com DateTimeFormatter, então o jeito é extrair as informações diretamente da String. Uma das opções é usar regex, através do pacote java.util.regex: // Matcher e Pattern fazem parte do pacote java.util.regex Matcher matcher = Pattern.compile("/Date\\((\\d+)([-+]\\d{4})?\\)/") .matcher("/Date(1555375534143)/"); if (matcher.find()) { long timestamp = Long.parseLong(matcher.group(1)); String offset = matcher.group(2); if (offset == null) { // se não tem offset, usa zero offset = "+0000"; } // se quiser um java.util.Date, pode parar por aqui Date date = new Date(timestamp); // se quiser obter uma String com a data e hora correspondente no offset em questão SimpleDateFormat sdf = new SimpleDateFormat("dd/MM/yyyy HH:mm:ss XXX"); // usar o offset para converter o Date em uma data e hora sdf.setTimeZone(TimeZone.getTimeZone("GMT" + offset)); System.out.println(sdf.format(date)); // 16/04/2019 00:45:34 Z } else { System.out.println("String não está no formato correto"); } A lógica geral é a mesma: tenta-se obter o timestamp e o offset da String. Caso o offset não esteja presente, algum valor default é usado (no caso, estou usando zero). Eu uso \\d+ para capturar o timestamp (um ou mais dígitos), e para o offset eu uso [-+] (o sinal de menos ou de mais) seguido de \\d{4} (quatro dígitos), e uso o ? para dizer que o offset é opcional. Cada um dos campos está entre parênteses para formar grupos de captura, e com isso eu posso obter os respectivos valores usando o método group (no caso, o grupo 1 é o timestamp e o grupo 2 é o offset). A seguir, eu uso o timestamp para criar um java.util.Date. E um Date, apesar do nome, não representa uma data, e sim um timestamp. Esse é um ponto meio confuso, pois ao imprimir o Date, ele usa o timezone default da JVM para obter os valores de data e hora. Exemplo: Date data = new Date(1555375534143L); TimeZone.setDefault(TimeZone.getTimeZone("America/Sao_Paulo")); System.out.println(data.getTime() + "=" + data); TimeZone.setDefault(TimeZone.getTimeZone("Asia/Tokyo")); System.out.println(data.getTime() + "=" + data); No código acima estou imprimindo o timestamp (através do método getTime()), e em seguida imprimo a própria data, que internamente chama o método toString(). A saída é: 1555375534143=Mon Apr 15 21:45:34 BRT 2019 1555375534143=Tue Apr 16 09:45:34 JST 2019 Repare que o valor do timestamp é o mesmo, mas o valor retornado por toString() não, pois este método usa o timezone default que está setado no momento, para converter o timestamp para uma data e hora específicas. Mas o Date não possui esses valores de data e hora: internamente, esta classe só possui o valor do timestamp. Por isso que o construtor de Date só precisa do timestamp, e se quisermos apenas uma instância de Date, não precisamos do offset. Mas caso você queira uma String contendo a data, hora correspondentes ao timestamp, no offset indicado, basta usar um SimpleDateFormat, como o código acima mostra. Ele também usa um java.util.TimeZone com o valor do offset que foi obtido pela regex. Com isso, a saída é uma String que corresponde ao timestamp e ao offset que estavam na entrada. No exemplo acima, a saída é 16/04/2019 00:45:34 Z (este “Z” no final indica que está em UTC - ou seja, que o offset é zero). Se eu testar com "/Date(1555375534143-0300)/", a saída é 15/04/2019 21:45:34 -03:00. Também daria para obter o timestamp e offset usando substring, e fazendo alguns if’s para saber se o offset existe, mas eu acho que a solução com regex é mais direta nesse caso. C# Em C# você pode usar o Json.NET, que possui a classe JsonConvert. Com isso podemos obter um DateTime: string sa = @"""" + "/Date(1555375534143-0300)/" + @""""; DateTime dt = JsonConvert.DeserializeObject<DateTime>(sa); Console.WriteLine(dt); // 4/15/19 9:45:34 PM De forma similar ao que foi feito acima em Java, também podemos obter o timestamp e o offset usando regex, e em seguida usamos um DateTimeOffset para obter a data e hora correspondente: Regex r = new Regex(@"/Date\((\d+)([-+]\d{4})?\)/"); Match match = r.Match("/Date(1555375534143-0300)/"); if (match.Success) { long timestamp = long.Parse(match.Groups[1].Value); TimeSpan offset = TimeSpan.Zero; // offset por padrão é zero if(match.Groups[2].Success) { // se foi encontrado offset string value = match.Groups[2].Value; int hours = int.Parse(value.Substring(1, 2)); if (value.Substring(0, 1).Equals("-")) { hours = -hours; } int minutes = int.Parse(value.Substring(3)); offset = new TimeSpan(hours, minutes, 0); } DateTimeOffset dt = DateTimeOffset.FromUnixTimeMilliseconds(timestamp).ToOffset(offset); Console.WriteLine(dt); // 4/15/19 9:45:34 PM -03:00 } Foi feito um pequeno trabalho com substrings para obter o valor correto do offset como um TimeSpan. Em seguida, usamos o método FromUnixTimeMilliseconds, passando o timestamp, e o método ToOffset, que converte para o offset indicado. Python A ideia é a mesma: use regex para obter o timestamp e o offset (ou use UTC quando este não for encontrado), e em seguida crie uma data. No caso, estou usando o módulo datetime para criar as datas, e o módulo re para expressões regulares: from datetime import datetime, timezone, timedelta import re m = re.match(r'/Date\((\d+)([-+]\d{4})?\)/', '/Date(1555375534143-0300)/') if m: timestamp = int(m.group(1)) offset = m.group(2) if offset: total = timedelta(hours = int(offset[1:3]), minutes = int(offset[3:])) if offset[0] == '-': total = -total offset = timezone(total) else: # não tem o offset, usar UTC offset = timezone.utc # o timestamp está em milissegundos, mas fromtimestamp recebe o valor em segundos data = datetime.fromtimestamp(timestamp / 1000).astimezone(offset) print(data) # 2019-04-15 21:45:34.143000-03:00 PHP Similar aos demais, basta usar uma expressão regular para obter o timestamp e o offset: if (preg_match('/Date\((\d+)([-+]\d{4})?\)/', '/Date(1555375534143-0300)/', $matches)) { $timestamp = number_format($matches[1] / 1000, 3, '.', ''); // $matches pode ter até 3 elementos: o match total e os dois grupos de captura if (count($matches) === 3) { $offset = $matches[2]; } else { $offset = '+0000'; } $data = DateTime::createFromFormat('U.u', $timestamp); $data->setTimeZone(new DateTimeZone($offset)); } Aqui tomamos o cuidado de dividir o timestamp por 1000, pois o formato U.u aceita os segundos, seguido de um ponto, seguido das frações de segundo. Para saber se o offset está presente, eu vejo se o array de matches tem 3 posições. Isso porque a primeira posição sempre contém todo o trecho que corresponde à expressão, e as posições subsequentes correspondem aos grupos de captura. JavaScript Por fim, em JavaScript, a ideia é a mesma: usar uma regex para extrair o timestamp e offset, e criar a data: let regex = /Date\((\d+)([-+]\d{4})?\)/; let match = regex.exec('/Date(1555375534143-0300)/').slice(1, 3); let data = new Date(parseInt(match[0])); Ao executar a regex, eu pego somente um trecho do array retornado (contendo os dois grupos de captura), já que exec retorna um array com mais informações que não me interessam neste caso. Um detalhe é que o Date do JavaScript (similar ao java.util.Date do Java) representa um timestamp, e por isso ele não precisa do offset. Caso queira a data no offset que está na string, uma alternativa é usar a biblioteca Moment.js: let regex = /Date\((\d+)([-+]\d{4})?\)/; let match = regex.exec('/Date(1555375534143-0300)/').slice(1, 3); let offset = match[1] || '+0000'; let data = moment(parseInt(match[0])).utcOffset(offset); console.log(data.format()); // 2019-04-15T21:45:34-03:00 Outra opção é usar parseZone, que suporta este formato e consegue preservar o offset: console.log(moment.parseZone("/Date(1555375534143-0300)/").format()); // 2019-04-15T21:45:34-03:00 Resumindo, o formato /Date(1555375534143-0300)/ pode assustar à primeira vista, mas uma vez que entendemos a sua - falta de - lógica, não é difícil extrair dele os dados que precisamos.O que é o timestamp?2019-05-02T21:30:00-03:002019-05-02T21:30:00-03:00https://hkotsubo.github.io/blog/2019-05-02/o-que-e-timestamp<p>Provavelmente você já teve que lidar com datas e esbarrou em valores como <code class="language-plaintext highlighter-rouge">1556322834</code> ou <code class="language-plaintext highlighter-rouge">1556322834401</code>. Talvez tenham te falado que guardar esses valores é melhor que guardar a data, e você meio que acreditou, afinal, “o código funciona”. Mas o que são esses números?</p>
<p>Esses números <a href="https://codeofmatt.com/please-dont-call-it-epoch-time/">possuem vários nomes</a>: <em>Unix timestamps</em>, <em>Unix Time</em>, ou simplesmente <em>timestamps</em> (que é o nome que usaremos a partir de agora). Um timestamp basicamente representa um instante único, um ponto específico na linha do tempo, e seu valor corresponde a uma determinada quantidade de tempo decorrida a partir de um instante inicial.</p>
<p>Esse instante inicial (o “instante zero”) é chamado de <a href="https://en.wikipedia.org/wiki/Unix_time"><em>Unix Epoch</em></a>, cujo valor é <code class="language-plaintext highlighter-rouge">1970-01-01T00:00Z</code> (1 de janeiro de 1970, à meia-noite, em <a href="https://en.wikipedia.org/wiki/Coordinated_Universal_Time">UTC</a> <sup id="fnref:utc" role="doc-noteref"><a href="#fn:utc" class="footnote" rel="footnote">1</a></sup>). E o timestamp geralmente tem seu valor em segundos ou milissegundos, podendo ser um número positivo (para instantes que ocorrem depois do <em>Unix Epoch</em>) ou negativo (para instantes anteriores ao <em>Unix Epoch</em>).</p>
<p>O timestamp <code class="language-plaintext highlighter-rouge">1556322834</code>, por exemplo, representa um instante ocorrido 1.556.322.834 segundos depois do <em>Unix Epoch</em>, que corresponde a <code class="language-plaintext highlighter-rouge">2019-04-26T23:53:54Z</code> (26 de abril de 2019, às 23:53:54 <strong>em UTC</strong>).</p>
<p>Um detalhe importante é que o timestamp representa um único instante, <strong>que é o mesmo no mundo todo</strong>. Muitas linguagens e APIs possuem funções para retornar “a data atual”, mas retornam o valor do timestamp. E qualquer computador do mundo que rodasse uma dessas funções, naquele exato instante, obteria o mesmo valor (<code class="language-plaintext highlighter-rouge">1556322834</code>)<sup id="fnref:secs" role="doc-noteref"><a href="#fn:secs" class="footnote" rel="footnote">2</a></sup>.</p>
<p>O detalhe é que um mesmo valor de timestamp corresponde a uma data e hora diferente, dependendo do fuso-horário. O timestamp <code class="language-plaintext highlighter-rouge">1556322834</code>, por exemplo, corresponde às seguintes datas e horários:</p>
<table>
<thead>
<tr>
<th>Data e hora</th>
<th>Fuso horário</th>
</tr>
</thead>
<tbody>
<tr>
<td>26/04/2019, às 20:53:54</td>
<td>São Paulo</td>
</tr>
<tr>
<td>26/04/2019, às 16:53:54</td>
<td>Los Angeles</td>
</tr>
<tr>
<td><strong>27</strong>/04/2019, às 08:53:54</td>
<td>Tóquio</td>
</tr>
</tbody>
</table>
<p>Este é um conceito importantíssimo: o timestamp <code class="language-plaintext highlighter-rouge">1556322834</code> corresponde a <strong>todas</strong> as datas e horas acima. O instante é o mesmo (1.556.322.834 segundos depois do <em>Unix Epoch</em>), o que muda é apenas a data e hora local, de acordo com o fuso-horário que você usa como referência.</p>
<p>Por isso, só faz sentido converter um timestamp para uma data e hora (e vice-versa) se você estiver usando um fuso-horário específico. Muitas linguagens possuem funções que fazem essas conversões sem pedir por um fuso-horário, mas no fundo elas usam algum predefinido (geralmente o <em>default</em> que está configurado no ambiente em que o código roda). Algumas permitem que você mude ou configure o fuso-horário, mas nem sempre isso é possível.</p>
<p>A seguir seguem alguns exemplos básicos de manipulação do timestamp em algumas linguagens. Se quiser, pode usar os links abaixo para ir direto para a linguagem de sua preferência:</p>
<ul>
<li><a href="#java">Java</a></li>
<li><a href="#c">C#</a></li>
<li><a href="#python">Python</a></li>
<li><a href="#php">PHP</a></li>
<li><a href="#javascript">JavaScript</a></li>
</ul>
<h3 id="java">Java</h3>
<p>Se você estiver usando o Java >= 8, use a <a href="https://docs.oracle.com/javase/8/docs/api/java/time/package-summary.html">API <code class="language-plaintext highlighter-rouge">java.time</code></a>. Para representar um timestamp, você pode usar a classe <a href="https://docs.oracle.com/javase/8/docs/api/java/time/Instant.html"><code class="language-plaintext highlighter-rouge">java.time.Instant</code></a>:</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// timestamp correspondente ao instante atual</span>
<span class="nc">Instant</span> <span class="n">agora</span> <span class="o">=</span> <span class="nc">Instant</span><span class="o">.</span><span class="na">now</span><span class="o">();</span>
<span class="c1">// timestamp em segundos - 2019-04-26T23:53:54Z</span>
<span class="nc">Instant</span> <span class="n">instant</span> <span class="o">=</span> <span class="nc">Instant</span><span class="o">.</span><span class="na">ofEpochSecond</span><span class="o">(</span><span class="mi">1556322834</span><span class="o">);</span>
<span class="c1">// timestamp em milissegundos - 2019-04-26T23:53:54.483Z</span>
<span class="nc">Instant</span> <span class="n">instant</span> <span class="o">=</span> <span class="nc">Instant</span><span class="o">.</span><span class="na">ofEpochMilli</span><span class="o">(</span><span class="mi">1556322834483L</span><span class="o">);</span>
<span class="c1">// timestamp em segundos + nanossegundos - 2019-04-26T23:53:54.123456789Z</span>
<span class="nc">Instant</span> <span class="n">instant</span> <span class="o">=</span> <span class="nc">Instant</span><span class="o">.</span><span class="na">ofEpochSecond</span><span class="o">(</span><span class="mi">1556322834</span><span class="o">,</span> <span class="mi">123456789</span><span class="o">);</span>
</code></pre></div></div>
<p>O método <a href="https://docs.oracle.com/javase/8/docs/api/java/time/Instant.html#now--"><code class="language-plaintext highlighter-rouge">now()</code></a> retorna um <code class="language-plaintext highlighter-rouge">Instant</code> contendo o timestamp atual. As classes da API <code class="language-plaintext highlighter-rouge">java.time</code> possuem precisão de nanossegundos (9 casas decimais na fração de segundos), mas o método <code class="language-plaintext highlighter-rouge">now()</code> usa o “melhor relógio disponível no sistema”. No Java 8, o “melhor relógio disponível” é o método <a href="https://docs.oracle.com/javase/8/docs/api/java/lang/System.html#currentTimeMillis--"><code class="language-plaintext highlighter-rouge">System.currentTimeMillis()</code></a>, portanto <code class="language-plaintext highlighter-rouge">Instant.now()</code> retornará um timestamp com precisão de milissegundos (mesmo que a JVM esteja rodando em uma máquina cujo relógio tenha precisão maior). A partir do Java 9 este método de fato usa a mesma precisão que o relógio do sistema possui.</p>
<p>Mas caso você precise <strong>somente</strong> do valor numérico do timestamp atual e nada mais, basta usar <code class="language-plaintext highlighter-rouge">System.currentTimeMillis()</code>, que retorna esse valor em milissegundos.</p>
<p>Repare que também há métodos para criar um <code class="language-plaintext highlighter-rouge">Instant</code> a partir de um timestamp em segundos ou milissegundos, além de uma opção para setar também o valor dos nanossegundos (mas independente da forma usada, internamente são mantidos dois valores: a quantidade de segundos e os nanossegundos). Ao imprimir um <code class="language-plaintext highlighter-rouge">Instant</code> (com <code class="language-plaintext highlighter-rouge">System.out.println</code> ou com sua API de log favorita), sempre é mostrado a data e hora correspondente em UTC, no formato <a href="https://en.wikipedia.org/wiki/ISO_8601">ISO 8601</a> (ex: <code class="language-plaintext highlighter-rouge">2019-04-26T23:53:54.483Z</code>).</p>
<p>Para converter um <code class="language-plaintext highlighter-rouge">Instant</code> para um fuso-horário específico, basta usar um <a href="https://docs.oracle.com/javase/8/docs/api/java/time/ZoneId.html"><code class="language-plaintext highlighter-rouge">java.time.ZoneId</code></a>, passando um nome de <em>timezone</em> válido:</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nc">Instant</span> <span class="n">instant</span> <span class="o">=</span> <span class="nc">Instant</span><span class="o">.</span><span class="na">ofEpochMilli</span><span class="o">(</span><span class="mi">1556322834483L</span><span class="o">);</span>
<span class="c1">// 2019-04-26T20:53:54.483-03:00[America/Sao_Paulo]</span>
<span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="n">instant</span><span class="o">.</span><span class="na">atZone</span><span class="o">(</span><span class="nc">ZoneId</span><span class="o">.</span><span class="na">of</span><span class="o">(</span><span class="s">"America/Sao_Paulo"</span><span class="o">)));</span>
<span class="c1">// 2019-04-27T08:53:54.483+09:00[Asia/Tokyo]</span>
<span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="n">instant</span><span class="o">.</span><span class="na">atZone</span><span class="o">(</span><span class="nc">ZoneId</span><span class="o">.</span><span class="na">of</span><span class="o">(</span><span class="s">"Asia/Tokyo"</span><span class="o">)));</span>
</code></pre></div></div>
<p>Os nomes <code class="language-plaintext highlighter-rouge">America/Sao_Paulo</code> e <code class="language-plaintext highlighter-rouge">Asia/Tokyo</code> são definidos pela <a href="https://www.iana.org/time-zones">IANA</a> (órgão responsável pelo banco de informações de fusos horários que o Java e muitos outros sistemas, APIs e linguagens usam). Para saber todos os <em>timezones</em> disponíveis, use o método <a href="https://docs.oracle.com/javase/8/docs/api/java/time/ZoneId.html#getAvailableZoneIds--"><code class="language-plaintext highlighter-rouge">ZoneId.getAvailableZoneIds()</code></a>.</p>
<p>O método <a href="https://docs.oracle.com/javase/8/docs/api/java/time/Instant.html#atZone-java.time.ZoneId-"><code class="language-plaintext highlighter-rouge">atZone</code></a> retorna um <a href="https://docs.oracle.com/javase/8/docs/api/java/time/ZonedDateTime.html"><code class="language-plaintext highlighter-rouge">java.time.ZonedDateTime</code></a>, que possui uma data, hora e timezone (portanto, representa uma data e hora em um fuso horário específico).</p>
<hr />
<p>Para converter uma data (somente o dia, mês e ano) para um timestamp, é preciso definir um horário e um timezone. Lembre-se que o timestamp representa um instante único, um ponto na linha do tempo. Tendo somente o dia, não é possível obter um único valor de timestamp, já que um dia possui várias horas e portanto corresponde a vários instantes diferentes. Tendo o dia e horário, também não é possível obter o timestamp, já que uma data e hora pode corresponder a um instante diferente em cada parte do mundo (por exemplo, 26 de abril de 2019 às 22h ocorreu em um instante diferente em cada parte do mundo).</p>
<p>A API <code class="language-plaintext highlighter-rouge">java.time</code> é feita de modo que te “obriga” a fornecer estas informações. Ou seja, se eu tenho um <a href="https://docs.oracle.com/javase/8/docs/api/java/time/LocalDate.html"><code class="language-plaintext highlighter-rouge">java.time.LocalDate</code></a> (uma classe que possui somente o dia, mês e ano) e quero transformá-la em um <code class="language-plaintext highlighter-rouge">Instant</code>, eu devo fornecer o horário e o timezone:</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nc">LocalDate</span> <span class="n">data</span> <span class="o">=</span> <span class="nc">LocalDate</span><span class="o">.</span><span class="na">of</span><span class="o">(</span><span class="mi">2019</span><span class="o">,</span> <span class="mi">4</span><span class="o">,</span> <span class="mi">26</span><span class="o">);</span> <span class="c1">// 26 de abril de 2019</span>
<span class="nc">Instant</span> <span class="n">instant</span> <span class="o">=</span> <span class="n">data</span>
<span class="o">.</span><span class="na">atTime</span><span class="o">(</span><span class="mi">10</span><span class="o">,</span> <span class="mi">30</span><span class="o">)</span> <span class="c1">// 10:30 da manhã</span>
<span class="o">.</span><span class="na">atZone</span><span class="o">(</span><span class="nc">ZoneId</span><span class="o">.</span><span class="na">of</span><span class="o">(</span><span class="s">"America/Sao_Paulo"</span><span class="o">))</span> <span class="c1">// setar o timezone</span>
<span class="o">.</span><span class="na">toInstant</span><span class="o">();</span> <span class="c1">// obter o Instant</span>
</code></pre></div></div>
<p>Dependendo do horário e timezone escolhido, o valor do <code class="language-plaintext highlighter-rouge">Instant</code> será diferente. Por um lado, é um pouco trabalhoso setar o horário e timezone, mas por outro lado a API te força a pensar da maneira correta com relação aos conceitos de datas, horas e timestamps, além de permitir que você tenha um controle maior sobre os valores usados (algo que muitas APIs não permitem, por exemplo, pois usam valores predefinidos e nem sempre permitem que você mude).</p>
<hr />
<p>Para Java <= 7, existe a API legada: <a href="https://docs.oracle.com/javase/8/docs/api/java/util/Date.html"><code class="language-plaintext highlighter-rouge">java.util.Date</code></a> e <a href="https://docs.oracle.com/javase/8/docs/api/java/util/Calendar.html"><code class="language-plaintext highlighter-rouge">java.util.Calendar</code></a>.</p>
<p><code class="language-plaintext highlighter-rouge">Date</code>, apesar do nome, não é uma data (um dia, mês e ano específicos). Na verdade, esta classe representa um timestamp. O que pode confundir é que, ao imprimir um <code class="language-plaintext highlighter-rouge">Date</code>, ele converte o timestamp para o timezone <em>default</em> da JVM e mostra a data e hora correspondente. Exemplo:</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nc">Date</span> <span class="n">data</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">Date</span><span class="o">(</span><span class="mi">1556322834483L</span><span class="o">);</span>
<span class="nc">TimeZone</span><span class="o">.</span><span class="na">setDefault</span><span class="o">(</span><span class="nc">TimeZone</span><span class="o">.</span><span class="na">getTimeZone</span><span class="o">(</span><span class="s">"America/Sao_Paulo"</span><span class="o">));</span>
<span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="n">data</span><span class="o">.</span><span class="na">getTime</span><span class="o">()</span> <span class="o">+</span> <span class="s">"="</span> <span class="o">+</span> <span class="n">data</span><span class="o">);</span>
<span class="nc">TimeZone</span><span class="o">.</span><span class="na">setDefault</span><span class="o">(</span><span class="nc">TimeZone</span><span class="o">.</span><span class="na">getTimeZone</span><span class="o">(</span><span class="s">"Asia/Tokyo"</span><span class="o">));</span>
<span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="n">data</span><span class="o">.</span><span class="na">getTime</span><span class="o">()</span> <span class="o">+</span> <span class="s">"="</span> <span class="o">+</span> <span class="n">data</span><span class="o">);</span>
</code></pre></div></div>
<p>Eu uso <a href="https://docs.oracle.com/javase/8/docs/api/java/util/TimeZone.html#setDefault-java.util.TimeZone-"><code class="language-plaintext highlighter-rouge">TimeZone.setDefault</code></a> para mudar o timezone default da JVM, e em seguida imprimo o valor de <a href="https://docs.oracle.com/javase/8/docs/api/java/util/Date.html#getTime--"><code class="language-plaintext highlighter-rouge">getTime()</code></a> (que retorna o timestamp) e o próprio <code class="language-plaintext highlighter-rouge">Date</code> (que usará o timestamp default para converter o timestamp para uma data e hora). O resultado é:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>1556322834483=Fri Apr 26 20:53:54 BRT 2019
1556322834483=Sat Apr 27 08:53:54 JST 2019
</code></pre></div></div>
<p>Repare que o valor do timestamp é o mesmo, mas a data e hora não. O valor do <code class="language-plaintext highlighter-rouge">Date</code> (o timestamp) não é alterado, mas quando este é impresso, o timestamp é convertido para o timezone default que está setado no momento. Isso é algo que confunde <strong>muita</strong> gente: o <code class="language-plaintext highlighter-rouge">Date</code> só possui o valor do timestamp, nada mais. Qualquer outro valor (dia, mês, ano , hora, minuto, segundo) é derivado do timestamp, levando em conta o timezone default da JVM. Mas o <code class="language-plaintext highlighter-rouge">Date</code> em si não possui tais valores.</p>
<hr />
<p>Para converter uma data específica (dia, mês e ano) em um timestamp, basta usar um <code class="language-plaintext highlighter-rouge">Calendar</code>:</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// 26 de abril de 2019, 10:30</span>
<span class="nc">Calendar</span> <span class="n">cal</span> <span class="o">=</span> <span class="nc">Calendar</span><span class="o">.</span><span class="na">getInstance</span><span class="o">();</span>
<span class="n">cal</span><span class="o">.</span><span class="na">set</span><span class="o">(</span><span class="mi">2019</span><span class="o">,</span> <span class="nc">Calendar</span><span class="o">.</span><span class="na">APRIL</span><span class="o">,</span> <span class="mi">26</span><span class="o">,</span> <span class="mi">10</span><span class="o">,</span> <span class="mi">30</span><span class="o">,</span> <span class="mi">0</span><span class="o">);</span>
<span class="n">cal</span><span class="o">.</span><span class="na">set</span><span class="o">(</span><span class="nc">Calendar</span><span class="o">.</span><span class="na">MILLISECOND</span><span class="o">,</span> <span class="mi">0</span><span class="o">);</span>
<span class="n">cal</span><span class="o">.</span><span class="na">setTimeZone</span><span class="o">(</span><span class="nc">TimeZone</span><span class="o">.</span><span class="na">getTimeZone</span><span class="o">(</span><span class="s">"America/Sao_Paulo"</span><span class="o">));</span>
<span class="kt">long</span> <span class="n">timestamp</span> <span class="o">=</span> <span class="n">cal</span><span class="o">.</span><span class="na">getTimeInMillis</span><span class="o">();</span> <span class="c1">// 1556285400000</span>
</code></pre></div></div>
<p>No exemplo acima foram setados o horário e o timezone, mas a API não nos obriga a fazer isso. <code class="language-plaintext highlighter-rouge">Calendar.getInstance()</code> cria uma instância contendo a data e hora atual no timezone <em>default</em> da JVM, e se você só mudar o dia, mês e ano, os outros campos permanecem os mesmos, e isso faz com que o valor do timestamp retornado seja diferente.</p>
<h3 id="c">C#</h3>
<p>Em C# existe o <a href="https://docs.microsoft.com/en-us/dotnet/api/system.datetime?view=netframework-4.8#main"><em>struct</em> <code class="language-plaintext highlighter-rouge">DateTime</code></a>, mas ele não usa <em>Unix timestamps</em>. Na verdade ele usa um <em>epoch</em> diferente, ou seja, o instante zero não é o <em>Unix Epoch</em>, e sim <code class="language-plaintext highlighter-rouge">0001-01-01T00:00Z</code> (1 de janeiro do <strong>ano 1</strong>, à meia-noite em UTC). E a unidade de medida usada é chamada de <em>tick</em>, que equivale a 100 nanossegundos (ou 0,0000001 segundos). Por isso há <a href="https://docs.microsoft.com/en-us/dotnet/api/system.datetime.-ctor?view=netframework-4.8">vários construtores</a> que recebem a quantidade de ticks e criam o <code class="language-plaintext highlighter-rouge">DateTime</code> correspondente.</p>
<p>Para trabalhar com <em>Unix timestamps</em>, existe o <em>struct</em> <a href="https://docs.microsoft.com/en-us/dotnet/api/system.datetimeoffset?view=netframework-4.8"><code class="language-plaintext highlighter-rouge">DateTimeOffset</code></a>, que possui o método <a href="https://docs.microsoft.com/en-us/dotnet/api/system.datetimeoffset.fromunixtimeseconds?redirectedfrom=MSDN&view=netframework-4.8#System_DateTimeOffset_FromUnixTimeSeconds_System_Int64_"><code class="language-plaintext highlighter-rouge">FromUnixTimeSeconds</code></a>, que recebe o timestamp em segundos. Também existe o método <a href="https://docs.microsoft.com/en-us/dotnet/api/system.datetimeoffset.fromunixtimemilliseconds?view=netframework-4.8"><code class="language-plaintext highlighter-rouge">FromUnixTimeMilliseconds</code></a> que recebe o timestamp em milissegundos:</p>
<div class="language-c# highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">DateTimeOffset</span> <span class="n">d</span> <span class="p">=</span> <span class="n">DateTimeOffset</span><span class="p">.</span><span class="nf">FromUnixTimeMilliseconds</span><span class="p">(</span><span class="m">1556322834483</span><span class="p">);</span>
<span class="n">Console</span><span class="p">.</span><span class="nf">WriteLine</span><span class="p">(</span><span class="n">d</span><span class="p">);</span> <span class="c1">// 4/26/19 11:53:54 PM +00:00</span>
</code></pre></div></div>
<p>Se quiser, também pode usar <code class="language-plaintext highlighter-rouge">d.DateTime</code> para obter o <code class="language-plaintext highlighter-rouge">DateTime</code> correspondente. E caso queira transformar uma data específica em um timestamp, basta fazer o processo inverso, criando um <code class="language-plaintext highlighter-rouge">DateTimeOffset</code> a partir de um <code class="language-plaintext highlighter-rouge">DateTime</code>:</p>
<div class="language-c# highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">DateTime</span> <span class="n">dt</span> <span class="p">=</span> <span class="k">new</span> <span class="nf">DateTime</span><span class="p">(</span><span class="m">2019</span><span class="p">,</span> <span class="m">4</span><span class="p">,</span> <span class="m">26</span><span class="p">,</span> <span class="m">10</span><span class="p">,</span> <span class="m">30</span><span class="p">,</span> <span class="m">0</span><span class="p">);</span>
<span class="n">DateTimeOffset</span> <span class="n">d</span> <span class="p">=</span> <span class="k">new</span> <span class="nf">DateTimeOffset</span><span class="p">(</span><span class="n">dt</span><span class="p">);</span>
<span class="n">Console</span><span class="p">.</span><span class="nf">WriteLine</span><span class="p">(</span><span class="n">d</span><span class="p">.</span><span class="nf">ToUnixTimeSeconds</span><span class="p">());</span> <span class="c1">// 1556285400</span>
</code></pre></div></div>
<p>No exemplo acima o retorno foi <code class="language-plaintext highlighter-rouge">1556285400</code> (que corresponde a 26/04/2019 às 10:30 no Horário de Brasília), pois o valor de <code class="language-plaintext highlighter-rouge">dt.Kind</code> acima é <code class="language-plaintext highlighter-rouge">Unspecified</code>, e neste caso o <code class="language-plaintext highlighter-rouge">DateTimeOffset</code> usa o timezone configurado no sistema (e o meu sistema está usando o Horário de Brasília). Para mais detalhes e opções, <a href="https://stackoverflow.com/q/249760">veja este link</a>.</p>
<p>E para obter o timestamp atual (somente o valor numérico e nada mais):</p>
<div class="language-c# highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">Console</span><span class="p">.</span><span class="nf">WriteLine</span><span class="p">(</span><span class="n">DateTimeOffset</span><span class="p">.</span><span class="n">UtcNow</span><span class="p">.</span><span class="nf">ToUnixTimeSeconds</span><span class="p">());</span>
<span class="n">Console</span><span class="p">.</span><span class="nf">WriteLine</span><span class="p">(((</span><span class="n">DateTimeOffset</span><span class="p">)</span> <span class="n">DateTime</span><span class="p">.</span><span class="n">UtcNow</span><span class="p">).</span><span class="nf">ToUnixTimeSeconds</span><span class="p">());</span>
</code></pre></div></div>
<h3 id="python">Python</h3>
<p>Em Python você pode usar o módulo <a href="https://docs.python.org/3/library/datetime.html"><code class="language-plaintext highlighter-rouge">datetime</code></a>, que possui o método <a href="https://docs.python.org/3/library/datetime.html#datetime.datetime.fromtimestamp"><code class="language-plaintext highlighter-rouge">fromtimestamp</code></a> para criar uma data a partir de um timestamp:</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">from</span> <span class="nn">datetime</span> <span class="kn">import</span> <span class="n">datetime</span>
<span class="n">d</span> <span class="o">=</span> <span class="n">datetime</span><span class="p">.</span><span class="n">fromtimestamp</span><span class="p">(</span><span class="mf">1556322834.483</span><span class="p">)</span>
<span class="k">print</span><span class="p">(</span><span class="n">d</span><span class="p">)</span> <span class="c1"># 2019-04-26 20:53:54.483000
</span></code></pre></div></div>
<p>O valor do timestamp deve estar em segundos, mas o método <code class="language-plaintext highlighter-rouge">fromtimestamp</code> aceita valores com frações de segundos (no exemplo acima, este valor é <code class="language-plaintext highlighter-rouge">.483</code>, ou seja, 483 milissegundos). O limite da API é microssegundos (6 casas decimais), e valores com mais que 6 casas decimais são arredondados (ex: <code class="language-plaintext highlighter-rouge">datetime.fromtimestamp(1556322834.483199999)</code> resulta em <code class="language-plaintext highlighter-rouge">2019-04-26 20:53:54.483200</code>).</p>
<p>O detalhe é que por <em>default</em> é usado o timezone do sistema para converter o timestamp para uma data e hora. No meu caso, o timezone da minha máquina é <code class="language-plaintext highlighter-rouge">America/Sao_Paulo</code>, mas <a href="https://ideone.com/y7bWxp">rodando esse mesmo código no Ideone.com</a>, o resultado foi <code class="language-plaintext highlighter-rouge">2019-04-26 23:53:54.483000</code> (pois o timezone do servidor deles é UTC).</p>
<p>Ou seja, dependendo da configuração do ambiente no qual o código está rodando, você pode obter uma data e hora diferentes. Para não depender desta configuração, você precisa usar um timezone específico.</p>
<p>Até o Python 3.8 uma alternativa é usar o <a href="https://pypi.org/project/pytz/">módulo <code class="language-plaintext highlighter-rouge">pytz</code> (disponível no PyPI)</a>:</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1"># Até Python 3.8
</span><span class="kn">from</span> <span class="nn">pytz</span> <span class="kn">import</span> <span class="n">timezone</span> <span class="c1"># pip install pytz
</span><span class="kn">from</span> <span class="nn">datetime</span> <span class="kn">import</span> <span class="n">datetime</span>
<span class="c1"># obter a data e hora em um timezone específico
</span><span class="n">d</span> <span class="o">=</span> <span class="n">datetime</span><span class="p">.</span><span class="n">fromtimestamp</span><span class="p">(</span><span class="mf">1556322834.483199999</span><span class="p">,</span> <span class="n">tz</span><span class="o">=</span><span class="n">timezone</span><span class="p">(</span><span class="s">'America/Sao_Paulo'</span><span class="p">))</span>
<span class="k">print</span><span class="p">(</span><span class="n">d</span><span class="p">)</span> <span class="c1"># 2019-04-26 20:53:54.483200-03:00
</span>
<span class="c1"># obter o timestamp a partir de uma data/hora e timezone
</span><span class="n">d</span> <span class="o">=</span> <span class="n">timezone</span><span class="p">(</span><span class="s">'America/Sao_Paulo'</span><span class="p">).</span><span class="n">localize</span><span class="p">(</span><span class="n">datetime</span><span class="p">(</span><span class="mi">2019</span><span class="p">,</span> <span class="mi">4</span><span class="p">,</span> <span class="mi">26</span><span class="p">,</span> <span class="mi">10</span><span class="p">,</span> <span class="mi">30</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">))</span>
<span class="k">print</span><span class="p">(</span><span class="n">d</span><span class="p">.</span><span class="n">timestamp</span><span class="p">())</span> <span class="c1"># 1556285400.0
</span></code></pre></div></div>
<p>E a partir do Python 3.9 você pode usar o <a href="https://docs.python.org/3/library/zoneinfo.html">módulo nativo <code class="language-plaintext highlighter-rouge">zoneinfo</code></a>:</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1"># A partir do Python 3.9, não precisa mais do pytz
</span><span class="kn">from</span> <span class="nn">zoneinfo</span> <span class="kn">import</span> <span class="n">ZoneInfo</span>
<span class="kn">from</span> <span class="nn">datetime</span> <span class="kn">import</span> <span class="n">datetime</span>
<span class="c1"># obter a data e hora em um timezone específico
</span><span class="n">d</span> <span class="o">=</span> <span class="n">datetime</span><span class="p">.</span><span class="n">fromtimestamp</span><span class="p">(</span><span class="mf">1556322834.483199999</span><span class="p">,</span> <span class="n">tz</span><span class="o">=</span><span class="n">ZoneInfo</span><span class="p">(</span><span class="s">'America/Sao_Paulo'</span><span class="p">))</span>
<span class="k">print</span><span class="p">(</span><span class="n">d</span><span class="p">)</span> <span class="c1"># 2019-04-26 20:53:54.483200-03:00
</span>
<span class="c1"># obter o timestamp a partir de uma data/hora e timezone
</span><span class="n">d</span> <span class="o">=</span> <span class="n">datetime</span><span class="p">(</span><span class="mi">2019</span><span class="p">,</span> <span class="mi">4</span><span class="p">,</span> <span class="mi">26</span><span class="p">,</span> <span class="mi">10</span><span class="p">,</span> <span class="mi">30</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="n">tzinfo</span><span class="o">=</span><span class="n">ZoneInfo</span><span class="p">(</span><span class="s">'America/Sao_Paulo'</span><span class="p">))</span>
<span class="k">print</span><span class="p">(</span><span class="n">d</span><span class="p">.</span><span class="n">timestamp</span><span class="p">())</span> <span class="c1"># 1556285400.0
</span></code></pre></div></div>
<p>Repare que agora, ao imprimir o <code class="language-plaintext highlighter-rouge">datetime</code>, também é mostrado o offset <code class="language-plaintext highlighter-rouge">-03:00</code> (que é a diferença em relação a UTC, que o timezone <code class="language-plaintext highlighter-rouge">America/Sao_Paulo</code> usa naquele instante específico). No código acima também há um exemplo para converter uma data e hora específicas em um timestamp (lembrando que o timezone utilizado faz com que o valor do timestamp seja diferente, já que a mesma data e hora acontece em instantes diferentes em cada parte do mundo). E se você usar apenas o construtor de <code class="language-plaintext highlighter-rouge">datetime</code> (sem usar nenhum timezone), o retorno de <code class="language-plaintext highlighter-rouge">timestamp()</code> usará o timezone do ambiente no qual o código está rodando (e portanto pode variar).</p>
<p>Usar um timezone é importante para que o código não fique dependente da configuração de timezone do ambiente no qual o código roda - a menos que este seja o comportamento desejado, claro. Mas se quiser que o timestamp corresponda a uma data e hora em um timezone específico, é melhor usar o <code class="language-plaintext highlighter-rouge">pytz</code> (ou <code class="language-plaintext highlighter-rouge">zoneinfo</code>, se estiver usando Python >= 3.9) As informações de timezones mudam o tempo todo (há épocas em que há horário de verão e o offset muda, <a href="https://pt.stackoverflow.com/a/349822/112052">entre outros detalhes explicados neste link</a>), e tentar manter isso manualmente é inviável. O <code class="language-plaintext highlighter-rouge">pytz</code> é atualizado de acordo com as versões do <a href="https://www.iana.org/time-zones">TZDB da IANA</a> (o banco de informações de fusos-horários que várias linguagens e sistemas usam) e basta você usar o timezone correto (como <code class="language-plaintext highlighter-rouge">America/Sao_Paulo</code>, <code class="language-plaintext highlighter-rouge">Europe/London</code>, etc), que o <code class="language-plaintext highlighter-rouge">pytz</code> se encarrega de verificar qual a data e hora correspondentes. Já o módulo <code class="language-plaintext highlighter-rouge">zoneinfo</code> puxa essas informações do sistema operacional, e caso estas não estejam disponíveis, ele pega do <a href="https://tzdata.readthedocs.io/en/latest/">pacote <code class="language-plaintext highlighter-rouge">tzdata</code></a>. Na <a href="https://docs.python.org/3/library/zoneinfo.html#data-sources">documentação</a> há mais detalhes sobre isso.</p>
<p>E caso você precise somente do timestamp atual (o valor numérico e nada mais), pode usar o <a href="https://docs.python.org/3/library/time.html#time.time">método <code class="language-plaintext highlighter-rouge">time</code></a>:</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">import</span> <span class="nn">time</span>
<span class="c1"># valor do timestamp atual em segundos (e contendo as frações de segundos)
</span><span class="k">print</span><span class="p">(</span><span class="n">time</span><span class="p">.</span><span class="n">time</span><span class="p">())</span>
</code></pre></div></div>
<h3 id="php">PHP</h3>
<p>Em PHP você pode usar a <a href="https://www.php.net/manual/en/class.datetime.php">classe <code class="language-plaintext highlighter-rouge">DateTime</code></a>. Para obter uma instância a partir de um timestamp, você pode fazer de duas maneiras:</p>
<div class="language-php highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// criar um DateTime correspondente ao timestamp 1556322834</span>
<span class="nv">$d</span> <span class="o">=</span> <span class="nc">DateTime</span><span class="o">::</span><span class="nf">createFromFormat</span><span class="p">(</span><span class="s1">'U'</span><span class="p">,</span> <span class="mi">1556322834</span><span class="p">);</span>
<span class="c1">// ou</span>
<span class="nv">$d</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">DateTime</span><span class="p">(</span><span class="s1">'@'</span><span class="mf">.</span> <span class="mi">1556322834</span><span class="p">);</span>
</code></pre></div></div>
<p>Em ambos os casos, o timestamp 1556322834 está em segundos, e a data e hora resultante estará em UTC (e neste caso, não adianta mudar o timezone <em>default</em> com <a href="https://www.php.net/manual/en/function.date-default-timezone-set.php"><code class="language-plaintext highlighter-rouge">date_default_timezone_set</code></a>, pois este é ignorado quando um timestamp é usado). Se você fizer um <code class="language-plaintext highlighter-rouge">var_dump($d)</code>, a saída será:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>object(DateTime)#10 (3) {
["date"]=>
string(26) "2019-04-26 23:53:54.000000"
["timezone_type"]=>
int(1)
["timezone"]=>
string(6) "+00:00"
}
</code></pre></div></div>
<p>O offset <code class="language-plaintext highlighter-rouge">+00:00</code> indica que de fato o <code class="language-plaintext highlighter-rouge">DateTime</code> está em UTC. Se quiser que ela esteja em um timezone específico, basta passar um <a href="https://www.php.net/manual/en/class.datetimezone.php"><code class="language-plaintext highlighter-rouge">DateTimeZone</code></a> como parâmetro:</p>
<div class="language-php highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$d</span> <span class="o">=</span> <span class="nc">DateTime</span><span class="o">::</span><span class="nf">createFromFormat</span><span class="p">(</span><span class="s1">'U'</span><span class="p">,</span> <span class="mi">1556322834</span><span class="p">);</span>
<span class="nv">$d</span><span class="o">-></span><span class="nf">setTimezone</span><span class="p">(</span><span class="k">new</span> <span class="nc">DateTimeZone</span><span class="p">(</span><span class="s1">'America/Sao_Paulo'</span><span class="p">));</span>
<span class="c1">// ou</span>
<span class="nv">$d</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">DateTime</span><span class="p">(</span><span class="s1">'@'</span><span class="mf">.</span> <span class="mi">1556322834</span><span class="p">);</span>
<span class="nv">$d</span><span class="o">-></span><span class="nf">setTimezone</span><span class="p">(</span><span class="k">new</span> <span class="nc">DateTimeZone</span><span class="p">(</span><span class="s1">'America/Sao_Paulo'</span><span class="p">));</span>
</code></pre></div></div>
<p>Um detalhe chato é que no construtor, o timestamp exige que se tenha o <code class="language-plaintext highlighter-rouge">@</code> antes (veja o campo “Unix Timestamp” na <a href="https://www.php.net/manual/en/datetime.formats.compound.php">documentação</a>) Em ambos os casos, a saída será:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>object(DateTime)#10 (3) {
["date"]=>
string(26) "2019-04-26 20:53:54.000000"
["timezone_type"]=>
int(3)
["timezone"]=>
string(17) "America/Sao_Paulo"
}
</code></pre></div></div>
<p>Ao ajustar o timezone, os valores de data e hora também são alterados de acordo com as regras do timezone utilizado. Um detalhe interessante é que usando <a href="https://www.php.net/manual/en/datetime.createfromformat.php"><code class="language-plaintext highlighter-rouge">createFromFormat</code></a> também é possível usar valores com frações de segundos (limitado a 6 casas decimais):</p>
<div class="language-php highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1"># timestamp correspondente a 1556322834 segundos e 123456 microssegundos</span>
<span class="nv">$d</span> <span class="o">=</span> <span class="nc">DateTime</span><span class="o">::</span><span class="nf">createFromFormat</span><span class="p">(</span><span class="s1">'U.u'</span><span class="p">,</span> <span class="s1">'1556322834.123456'</span><span class="p">);</span>
</code></pre></div></div>
<hr />
<p>Para obter o valor do timestamp a partir de uma data, hora e timezone específicos, também é simples:</p>
<div class="language-php highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$d</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">DateTime</span><span class="p">();</span>
<span class="nv">$d</span><span class="o">-></span><span class="nf">setTimezone</span><span class="p">(</span><span class="k">new</span> <span class="nc">DateTimeZone</span><span class="p">(</span><span class="s1">'America/Sao_Paulo'</span><span class="p">));</span>
<span class="nv">$d</span><span class="o">-></span><span class="nf">setDate</span><span class="p">(</span><span class="mi">2019</span><span class="p">,</span> <span class="mi">4</span><span class="p">,</span> <span class="mi">26</span><span class="p">);</span> <span class="c1">// 26 de abril de 2019</span>
<span class="nv">$d</span><span class="o">-></span><span class="nf">setTime</span><span class="p">(</span><span class="mi">10</span><span class="p">,</span> <span class="mi">30</span><span class="p">);</span> <span class="c1">// 10:30 da manhã </span>
<span class="k">echo</span> <span class="nv">$d</span><span class="o">-></span><span class="nf">getTimestamp</span><span class="p">();</span> <span class="c1">// 1556285400</span>
<span class="c1">// ou</span>
<span class="nv">$d</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">DateTime</span><span class="p">(</span><span class="s1">'2019-04-26T10:30'</span><span class="p">,</span> <span class="k">new</span> <span class="nc">DateTimeZone</span><span class="p">(</span><span class="s1">'America/Sao_Paulo'</span><span class="p">));</span>
<span class="k">echo</span> <span class="nv">$d</span><span class="o">-></span><span class="nf">getTimestamp</span><span class="p">();</span> <span class="c1">// 1556285400</span>
<span class="c1">// ou ainda</span>
<span class="k">echo</span> <span class="nb">strtotime</span><span class="p">(</span><span class="s1">'2019-04-26T10:30 America/Sao_Paulo'</span><span class="p">);</span> <span class="c1">// 1556285400</span>
</code></pre></div></div>
<p>Atenção para chamar <code class="language-plaintext highlighter-rouge">setTimezone</code> antes de <code class="language-plaintext highlighter-rouge">setDate</code> e <code class="language-plaintext highlighter-rouge">setTime</code>. Se você mudar a data e hora primeiro, as alterações serão feitas levando em conta o timezone <em>default</em>. Depois, ao mudar o timezone, a data e hora serão ajustadas de acordo com o novo timezone, que não necessariamente serão os mesmos que você setou.</p>
<p>Por fim, se você precisa <strong>apenas</strong> do valor numérico do timestamp atual, basta usar a função <a href="https://php.net/manual/en/function.time.php"><code class="language-plaintext highlighter-rouge">time()</code></a>, que retorna o timestamp em segundos.</p>
<hr />
<p>E a função <a href="https://php.net/manual/en/function.strtotime.php"><code class="language-plaintext highlighter-rouge">strtotime</code></a>? Bem, ela também funciona (<code class="language-plaintext highlighter-rouge">strtotime('now')</code> retorna o mesmo que <code class="language-plaintext highlighter-rouge">time()</code>), e muitos costumam usar em conjunto com <a href="https://php.net/manual/en/function.date.php"><code class="language-plaintext highlighter-rouge">date</code></a>:</p>
<div class="language-php highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">date_default_timezone_set</span><span class="p">(</span><span class="s1">'America/Sao_Paulo'</span><span class="p">);</span>
<span class="k">echo</span><span class="p">(</span><span class="nb">date</span><span class="p">(</span><span class="s1">'d/m/Y H:i:s'</span><span class="p">,</span> <span class="nb">strtotime</span><span class="p">(</span><span class="s1">'now'</span><span class="p">)));</span>
</code></pre></div></div>
<p>No caso, <code class="language-plaintext highlighter-rouge">strtotime</code> retorna o timestamp, e <code class="language-plaintext highlighter-rouge">date</code> converte este timestamp para uma data e hora específicas, retornando uma string no formato indicado. No exemplo acima, o retorno é a data e hora atual no formato “dia/mês/ano hora:minuto:segundo” (atenção para o fato de que <code class="language-plaintext highlighter-rouge">date</code>, apesar do nome, retorna uma <strong>string</strong> - se quiser um objeto que represente uma data, use <code class="language-plaintext highlighter-rouge">DateTime</code>).</p>
<p>O detalhe é que <code class="language-plaintext highlighter-rouge">date</code> usa o timezone <em>default</em> que está setado no momento. Se eu quiser que ela use um timezone específico, devo mudá-lo com <code class="language-plaintext highlighter-rouge">date_default_timezone_set</code>. Só que isso afeta todas as demais funções que usam o timezone <em>default</em>. Já usando a solução anterior com <code class="language-plaintext highlighter-rouge">DateTime</code>, eu mudo somente o timezone das instâncias com as quais estou trabalhando, sem mudar a configuração do PHP.</p>
<h3 id="javascript">JavaScript</h3>
<p>Em JavaScript você pode usar <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date"><code class="language-plaintext highlighter-rouge">Date</code></a>, passando o timestamp para o construtor. O detalhe é que o valor deve estar em milissegundos:</p>
<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">let</span> <span class="nx">d</span> <span class="o">=</span> <span class="k">new</span> <span class="nb">Date</span><span class="p">(</span><span class="mi">1556322834000</span><span class="p">);</span>
<span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="nx">d</span><span class="p">);</span>
</code></pre></div></div>
<p>A data e hora que será mostrada depende do timezone que está configurado no browser (que geralmente é obtido do sistema operacional) e não há muito mais o que fazer para controlar isso. O melhor que dá para fazer é gerar uma string contendo a data e hora correspondentes em um timezone específico:</p>
<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="nx">d</span><span class="p">.</span><span class="nx">toLocaleString</span><span class="p">(</span><span class="dl">'</span><span class="s1">pt-BR</span><span class="dl">'</span><span class="p">,</span> <span class="p">{</span> <span class="na">timeZone</span><span class="p">:</span> <span class="dl">'</span><span class="s1">America/Los_Angeles</span><span class="dl">'</span> <span class="p">}));</span>
<span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="nx">d</span><span class="p">.</span><span class="nx">toLocaleString</span><span class="p">(</span><span class="dl">'</span><span class="s1">pt-BR</span><span class="dl">'</span><span class="p">,</span> <span class="p">{</span> <span class="na">timeZone</span><span class="p">:</span> <span class="dl">'</span><span class="s1">Asia/Tokyo</span><span class="dl">'</span> <span class="p">}));</span>
</code></pre></div></div>
<p>Com isso, é usado o formato correspondente ao locale <code class="language-plaintext highlighter-rouge">pt-BR</code> (que deve estar instalado no sistema, caso contrário será usado o locale <em>default</em> que estiver configurado no browser), e os valores de data e hora são obtidos a partir do timezone indicado. A saída é:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>26/04/2019 16:53:54
27/04/2019 08:53:54
</code></pre></div></div>
<p>Para obter o valor do timestamp a partir de uma data e hora específica, você pode passar os valores diretamente para o construtor, e em seguida usar o método <code class="language-plaintext highlighter-rouge">getTime()</code>, que retorna o timestamp em milissegundos:</p>
<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">let</span> <span class="nx">d</span> <span class="o">=</span> <span class="k">new</span> <span class="nb">Date</span><span class="p">(</span><span class="mi">2019</span><span class="p">,</span> <span class="mi">3</span><span class="p">,</span> <span class="mi">26</span><span class="p">,</span> <span class="mi">10</span><span class="p">,</span> <span class="mi">30</span><span class="p">);</span>
<span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="nx">d</span><span class="p">.</span><span class="nx">getTime</span><span class="p">());</span>
</code></pre></div></div>
<p>O detalhe é que é usado o timezone do browser, e não há como controlar isso.</p>
<hr />
<p>Uma opção para controlar o timezone é usar o <a href="https://momentjs.com/">Moment.js</a>, juntamente como o <a href="https://momentjs.com/timezone/">Moment Timezone</a>. Com isso, você pode escolher o timezone para o qual o timestamp será convertido:</p>
<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nx">moment</span><span class="p">.</span><span class="nx">tz</span><span class="p">(</span><span class="mi">1556322834000</span><span class="p">,</span> <span class="dl">"</span><span class="s2">America/Los_Angeles</span><span class="dl">"</span><span class="p">).</span><span class="nx">format</span><span class="p">()</span> <span class="c1">// 2019-04-26T16:53:54-07:00</span>
<span class="nx">moment</span><span class="p">.</span><span class="nx">tz</span><span class="p">(</span><span class="mi">1556322834000</span><span class="p">,</span> <span class="dl">"</span><span class="s2">America/Sao_Paulo</span><span class="dl">"</span><span class="p">).</span><span class="nx">format</span><span class="p">()</span> <span class="c1">// 2019-04-26T20:53:54-03:00</span>
</code></pre></div></div>
<p>Repare que o valor do timestamp está em milissegundos. Também é possível obter o timestamp corresponde a uma data e hora, em um timezone específico:</p>
<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nx">moment</span><span class="p">.</span><span class="nx">tz</span><span class="p">(</span><span class="dl">"</span><span class="s2">2019-04-26 10:30</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">America/Los_Angeles</span><span class="dl">"</span><span class="p">).</span><span class="nx">valueOf</span><span class="p">()</span> <span class="c1">// 1556299800000</span>
<span class="nx">moment</span><span class="p">.</span><span class="nx">tz</span><span class="p">(</span><span class="dl">"</span><span class="s2">2019-04-26 10:30</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">America/Sao_Paulo</span><span class="dl">"</span><span class="p">).</span><span class="nx">valueOf</span><span class="p">()</span> <span class="c1">// 1556285400000</span>
</code></pre></div></div>
<p>O método <a href="https://momentjs.com/docs/#/displaying/unix-timestamp-milliseconds/"><code class="language-plaintext highlighter-rouge">valueOf()</code></a> retorna o valor do timestamp em milissegundos. Os valores são diferentes porque as 10:30 do dia 26/04/2019 ocorreram em instantes diferentes em Los Angeles e São Paulo.</p>
<p>Se quiser este mesmo valor em segundos, use o método <a href="https://momentjs.com/docs/#/displaying/unix-timestamp/"><code class="language-plaintext highlighter-rouge">unix()</code></a>. E se quiser o valor numérico do timestamp atual, basta fazer <code class="language-plaintext highlighter-rouge">new Date().getTime()</code>.</p>
<p>Para entender melhor o <code class="language-plaintext highlighter-rouge">Date</code> do JavaScript, fiz <a href="/blog/2021-11-29/entendendo-o-date-do-javascript" class="new-window">este post mais detalhado</a>.</p>
<hr />
<p>Enfim, todas as linguagens possuem algum suporte a datas, horas, timezones e timestamps (algumas melhores, outras piores). Sempre que for converter uma data e hora de/para um timestamp, é importante saber qual o timezone que está sendo utilizado. Como você pôde perceber, esta informação nem sempre está clara: muitas vezes é usado um timezone <em>default</em> (que nem sempre dá para saber facilmente qual é) e algum valor sempre é retornado - e como não dá erro, muitos assumem que “funcionou”, sem sequer conferir se os <strong>valores</strong> estão corretos.</p>
<p>Não há mágica: um timestamp corresponde a uma data e hora específicas em cada timezone. Se foi retornado um timestamp, é porque as 3 informações (data, hora e timezone) estavam presentes. Se você não forneceu alguma(s) dela(s), então algum valor <em>default</em> foi usado. É comum, por exemplo, APIs que só recebem dia, mês e ano e retornam um timestamp. Nesse caso, algum valor foi usado para o horário (a maioria usa meia-noite) e para o timezone (o <em>default</em> que está configurado “no sistema”). Em alguns casos este comportamento é documentado, e dependendo da API, isso pode ser configurado ou alterado programaticamente. Sempre leia a documentação e confira os valores retornados. Não aceite qualquer data só porque “funcionou”.</p>
<div class="footnotes" role="doc-endnotes">
<ol>
<li id="fn:utc" role="doc-endnote">
<p>Lembrando que “meia-noite em UTC” não significa “meia-noite” em todos os lugares. Cada fuso-horário do mundo possui um horário diferente com relação a UTC (o Horário de Brasília, por exemplo, é 3 horas a menos em relação a UTC). <a href="#fnref:utc" class="reversefootnote" role="doc-backlink">↩</a></p>
</li>
<li id="fn:secs" role="doc-endnote">
<p>Algumas linguagens e APIs retornam o valor em segundos, enquanto outras retornam o valor em milissegundos (ou em microssegundos, ou até mesmo nanossegundos - sempre leia a documentação para saber qual a precisão máxima suportada). Mas a ideia geral é a mesma: quaisquer computadores do mundo - assumindo que estão configurados corretamente - rodando ao mesmo tempo, obtêm o mesmo valor do timestamp para a “data atual”. <a href="#fnref:secs" class="reversefootnote" role="doc-backlink">↩</a></p>
</li>
</ol>
</div>Hugo KotsuboProvavelmente você já teve que lidar com datas e esbarrou em valores como 1556322834 ou 1556322834401. Talvez tenham te falado que guardar esses valores é melhor que guardar a data, e você meio que acreditou, afinal, “o código funciona”. Mas o que são esses números? Esses números possuem vários nomes: Unix timestamps, Unix Time, ou simplesmente timestamps (que é o nome que usaremos a partir de agora). Um timestamp basicamente representa um instante único, um ponto específico na linha do tempo, e seu valor corresponde a uma determinada quantidade de tempo decorrida a partir de um instante inicial. Esse instante inicial (o “instante zero”) é chamado de Unix Epoch, cujo valor é 1970-01-01T00:00Z (1 de janeiro de 1970, à meia-noite, em UTC 1). E o timestamp geralmente tem seu valor em segundos ou milissegundos, podendo ser um número positivo (para instantes que ocorrem depois do Unix Epoch) ou negativo (para instantes anteriores ao Unix Epoch). O timestamp 1556322834, por exemplo, representa um instante ocorrido 1.556.322.834 segundos depois do Unix Epoch, que corresponde a 2019-04-26T23:53:54Z (26 de abril de 2019, às 23:53:54 em UTC). Um detalhe importante é que o timestamp representa um único instante, que é o mesmo no mundo todo. Muitas linguagens e APIs possuem funções para retornar “a data atual”, mas retornam o valor do timestamp. E qualquer computador do mundo que rodasse uma dessas funções, naquele exato instante, obteria o mesmo valor (1556322834)2. O detalhe é que um mesmo valor de timestamp corresponde a uma data e hora diferente, dependendo do fuso-horário. O timestamp 1556322834, por exemplo, corresponde às seguintes datas e horários: Data e hora Fuso horário 26/04/2019, às 20:53:54 São Paulo 26/04/2019, às 16:53:54 Los Angeles 27/04/2019, às 08:53:54 Tóquio Este é um conceito importantíssimo: o timestamp 1556322834 corresponde a todas as datas e horas acima. O instante é o mesmo (1.556.322.834 segundos depois do Unix Epoch), o que muda é apenas a data e hora local, de acordo com o fuso-horário que você usa como referência. Por isso, só faz sentido converter um timestamp para uma data e hora (e vice-versa) se você estiver usando um fuso-horário específico. Muitas linguagens possuem funções que fazem essas conversões sem pedir por um fuso-horário, mas no fundo elas usam algum predefinido (geralmente o default que está configurado no ambiente em que o código roda). Algumas permitem que você mude ou configure o fuso-horário, mas nem sempre isso é possível. A seguir seguem alguns exemplos básicos de manipulação do timestamp em algumas linguagens. Se quiser, pode usar os links abaixo para ir direto para a linguagem de sua preferência: Java C# Python PHP JavaScript Java Se você estiver usando o Java >= 8, use a API java.time. Para representar um timestamp, você pode usar a classe java.time.Instant: // timestamp correspondente ao instante atual Instant agora = Instant.now(); // timestamp em segundos - 2019-04-26T23:53:54Z Instant instant = Instant.ofEpochSecond(1556322834); // timestamp em milissegundos - 2019-04-26T23:53:54.483Z Instant instant = Instant.ofEpochMilli(1556322834483L); // timestamp em segundos + nanossegundos - 2019-04-26T23:53:54.123456789Z Instant instant = Instant.ofEpochSecond(1556322834, 123456789); O método now() retorna um Instant contendo o timestamp atual. As classes da API java.time possuem precisão de nanossegundos (9 casas decimais na fração de segundos), mas o método now() usa o “melhor relógio disponível no sistema”. No Java 8, o “melhor relógio disponível” é o método System.currentTimeMillis(), portanto Instant.now() retornará um timestamp com precisão de milissegundos (mesmo que a JVM esteja rodando em uma máquina cujo relógio tenha precisão maior). A partir do Java 9 este método de fato usa a mesma precisão que o relógio do sistema possui. Mas caso você precise somente do valor numérico do timestamp atual e nada mais, basta usar System.currentTimeMillis(), que retorna esse valor em milissegundos. Repare que também há métodos para criar um Instant a partir de um timestamp em segundos ou milissegundos, além de uma opção para setar também o valor dos nanossegundos (mas independente da forma usada, internamente são mantidos dois valores: a quantidade de segundos e os nanossegundos). Ao imprimir um Instant (com System.out.println ou com sua API de log favorita), sempre é mostrado a data e hora correspondente em UTC, no formato ISO 8601 (ex: 2019-04-26T23:53:54.483Z). Para converter um Instant para um fuso-horário específico, basta usar um java.time.ZoneId, passando um nome de timezone válido: Instant instant = Instant.ofEpochMilli(1556322834483L); // 2019-04-26T20:53:54.483-03:00[America/Sao_Paulo] System.out.println(instant.atZone(ZoneId.of("America/Sao_Paulo"))); // 2019-04-27T08:53:54.483+09:00[Asia/Tokyo] System.out.println(instant.atZone(ZoneId.of("Asia/Tokyo"))); Os nomes America/Sao_Paulo e Asia/Tokyo são definidos pela IANA (órgão responsável pelo banco de informações de fusos horários que o Java e muitos outros sistemas, APIs e linguagens usam). Para saber todos os timezones disponíveis, use o método ZoneId.getAvailableZoneIds(). O método atZone retorna um java.time.ZonedDateTime, que possui uma data, hora e timezone (portanto, representa uma data e hora em um fuso horário específico). Para converter uma data (somente o dia, mês e ano) para um timestamp, é preciso definir um horário e um timezone. Lembre-se que o timestamp representa um instante único, um ponto na linha do tempo. Tendo somente o dia, não é possível obter um único valor de timestamp, já que um dia possui várias horas e portanto corresponde a vários instantes diferentes. Tendo o dia e horário, também não é possível obter o timestamp, já que uma data e hora pode corresponder a um instante diferente em cada parte do mundo (por exemplo, 26 de abril de 2019 às 22h ocorreu em um instante diferente em cada parte do mundo). A API java.time é feita de modo que te “obriga” a fornecer estas informações. Ou seja, se eu tenho um java.time.LocalDate (uma classe que possui somente o dia, mês e ano) e quero transformá-la em um Instant, eu devo fornecer o horário e o timezone: LocalDate data = LocalDate.of(2019, 4, 26); // 26 de abril de 2019 Instant instant = data .atTime(10, 30) // 10:30 da manhã .atZone(ZoneId.of("America/Sao_Paulo")) // setar o timezone .toInstant(); // obter o Instant Dependendo do horário e timezone escolhido, o valor do Instant será diferente. Por um lado, é um pouco trabalhoso setar o horário e timezone, mas por outro lado a API te força a pensar da maneira correta com relação aos conceitos de datas, horas e timestamps, além de permitir que você tenha um controle maior sobre os valores usados (algo que muitas APIs não permitem, por exemplo, pois usam valores predefinidos e nem sempre permitem que você mude). Para Java <= 7, existe a API legada: java.util.Date e java.util.Calendar. Date, apesar do nome, não é uma data (um dia, mês e ano específicos). Na verdade, esta classe representa um timestamp. O que pode confundir é que, ao imprimir um Date, ele converte o timestamp para o timezone default da JVM e mostra a data e hora correspondente. Exemplo: Date data = new Date(1556322834483L); TimeZone.setDefault(TimeZone.getTimeZone("America/Sao_Paulo")); System.out.println(data.getTime() + "=" + data); TimeZone.setDefault(TimeZone.getTimeZone("Asia/Tokyo")); System.out.println(data.getTime() + "=" + data); Eu uso TimeZone.setDefault para mudar o timezone default da JVM, e em seguida imprimo o valor de getTime() (que retorna o timestamp) e o próprio Date (que usará o timestamp default para converter o timestamp para uma data e hora). O resultado é: 1556322834483=Fri Apr 26 20:53:54 BRT 2019 1556322834483=Sat Apr 27 08:53:54 JST 2019 Repare que o valor do timestamp é o mesmo, mas a data e hora não. O valor do Date (o timestamp) não é alterado, mas quando este é impresso, o timestamp é convertido para o timezone default que está setado no momento. Isso é algo que confunde muita gente: o Date só possui o valor do timestamp, nada mais. Qualquer outro valor (dia, mês, ano , hora, minuto, segundo) é derivado do timestamp, levando em conta o timezone default da JVM. Mas o Date em si não possui tais valores. Para converter uma data específica (dia, mês e ano) em um timestamp, basta usar um Calendar: // 26 de abril de 2019, 10:30 Calendar cal = Calendar.getInstance(); cal.set(2019, Calendar.APRIL, 26, 10, 30, 0); cal.set(Calendar.MILLISECOND, 0); cal.setTimeZone(TimeZone.getTimeZone("America/Sao_Paulo")); long timestamp = cal.getTimeInMillis(); // 1556285400000 No exemplo acima foram setados o horário e o timezone, mas a API não nos obriga a fazer isso. Calendar.getInstance() cria uma instância contendo a data e hora atual no timezone default da JVM, e se você só mudar o dia, mês e ano, os outros campos permanecem os mesmos, e isso faz com que o valor do timestamp retornado seja diferente. C# Em C# existe o struct DateTime, mas ele não usa Unix timestamps. Na verdade ele usa um epoch diferente, ou seja, o instante zero não é o Unix Epoch, e sim 0001-01-01T00:00Z (1 de janeiro do ano 1, à meia-noite em UTC). E a unidade de medida usada é chamada de tick, que equivale a 100 nanossegundos (ou 0,0000001 segundos). Por isso há vários construtores que recebem a quantidade de ticks e criam o DateTime correspondente. Para trabalhar com Unix timestamps, existe o struct DateTimeOffset, que possui o método FromUnixTimeSeconds, que recebe o timestamp em segundos. Também existe o método FromUnixTimeMilliseconds que recebe o timestamp em milissegundos: DateTimeOffset d = DateTimeOffset.FromUnixTimeMilliseconds(1556322834483); Console.WriteLine(d); // 4/26/19 11:53:54 PM +00:00 Se quiser, também pode usar d.DateTime para obter o DateTime correspondente. E caso queira transformar uma data específica em um timestamp, basta fazer o processo inverso, criando um DateTimeOffset a partir de um DateTime: DateTime dt = new DateTime(2019, 4, 26, 10, 30, 0); DateTimeOffset d = new DateTimeOffset(dt); Console.WriteLine(d.ToUnixTimeSeconds()); // 1556285400 No exemplo acima o retorno foi 1556285400 (que corresponde a 26/04/2019 às 10:30 no Horário de Brasília), pois o valor de dt.Kind acima é Unspecified, e neste caso o DateTimeOffset usa o timezone configurado no sistema (e o meu sistema está usando o Horário de Brasília). Para mais detalhes e opções, veja este link. E para obter o timestamp atual (somente o valor numérico e nada mais): Console.WriteLine(DateTimeOffset.UtcNow.ToUnixTimeSeconds()); Console.WriteLine(((DateTimeOffset) DateTime.UtcNow).ToUnixTimeSeconds()); Python Em Python você pode usar o módulo datetime, que possui o método fromtimestamp para criar uma data a partir de um timestamp: from datetime import datetime d = datetime.fromtimestamp(1556322834.483) print(d) # 2019-04-26 20:53:54.483000 O valor do timestamp deve estar em segundos, mas o método fromtimestamp aceita valores com frações de segundos (no exemplo acima, este valor é .483, ou seja, 483 milissegundos). O limite da API é microssegundos (6 casas decimais), e valores com mais que 6 casas decimais são arredondados (ex: datetime.fromtimestamp(1556322834.483199999) resulta em 2019-04-26 20:53:54.483200). O detalhe é que por default é usado o timezone do sistema para converter o timestamp para uma data e hora. No meu caso, o timezone da minha máquina é America/Sao_Paulo, mas rodando esse mesmo código no Ideone.com, o resultado foi 2019-04-26 23:53:54.483000 (pois o timezone do servidor deles é UTC). Ou seja, dependendo da configuração do ambiente no qual o código está rodando, você pode obter uma data e hora diferentes. Para não depender desta configuração, você precisa usar um timezone específico. Até o Python 3.8 uma alternativa é usar o módulo pytz (disponível no PyPI): # Até Python 3.8 from pytz import timezone # pip install pytz from datetime import datetime # obter a data e hora em um timezone específico d = datetime.fromtimestamp(1556322834.483199999, tz=timezone('America/Sao_Paulo')) print(d) # 2019-04-26 20:53:54.483200-03:00 # obter o timestamp a partir de uma data/hora e timezone d = timezone('America/Sao_Paulo').localize(datetime(2019, 4, 26, 10, 30, 0, 0)) print(d.timestamp()) # 1556285400.0 E a partir do Python 3.9 você pode usar o módulo nativo zoneinfo: # A partir do Python 3.9, não precisa mais do pytz from zoneinfo import ZoneInfo from datetime import datetime # obter a data e hora em um timezone específico d = datetime.fromtimestamp(1556322834.483199999, tz=ZoneInfo('America/Sao_Paulo')) print(d) # 2019-04-26 20:53:54.483200-03:00 # obter o timestamp a partir de uma data/hora e timezone d = datetime(2019, 4, 26, 10, 30, 0, 0, tzinfo=ZoneInfo('America/Sao_Paulo')) print(d.timestamp()) # 1556285400.0 Repare que agora, ao imprimir o datetime, também é mostrado o offset -03:00 (que é a diferença em relação a UTC, que o timezone America/Sao_Paulo usa naquele instante específico). No código acima também há um exemplo para converter uma data e hora específicas em um timestamp (lembrando que o timezone utilizado faz com que o valor do timestamp seja diferente, já que a mesma data e hora acontece em instantes diferentes em cada parte do mundo). E se você usar apenas o construtor de datetime (sem usar nenhum timezone), o retorno de timestamp() usará o timezone do ambiente no qual o código está rodando (e portanto pode variar). Usar um timezone é importante para que o código não fique dependente da configuração de timezone do ambiente no qual o código roda - a menos que este seja o comportamento desejado, claro. Mas se quiser que o timestamp corresponda a uma data e hora em um timezone específico, é melhor usar o pytz (ou zoneinfo, se estiver usando Python >= 3.9) As informações de timezones mudam o tempo todo (há épocas em que há horário de verão e o offset muda, entre outros detalhes explicados neste link), e tentar manter isso manualmente é inviável. O pytz é atualizado de acordo com as versões do TZDB da IANA (o banco de informações de fusos-horários que várias linguagens e sistemas usam) e basta você usar o timezone correto (como America/Sao_Paulo, Europe/London, etc), que o pytz se encarrega de verificar qual a data e hora correspondentes. Já o módulo zoneinfo puxa essas informações do sistema operacional, e caso estas não estejam disponíveis, ele pega do pacote tzdata. Na documentação há mais detalhes sobre isso. E caso você precise somente do timestamp atual (o valor numérico e nada mais), pode usar o método time: import time # valor do timestamp atual em segundos (e contendo as frações de segundos) print(time.time()) PHP Em PHP você pode usar a classe DateTime. Para obter uma instância a partir de um timestamp, você pode fazer de duas maneiras: // criar um DateTime correspondente ao timestamp 1556322834 $d = DateTime::createFromFormat('U', 1556322834); // ou $d = new DateTime('@'. 1556322834); Em ambos os casos, o timestamp 1556322834 está em segundos, e a data e hora resultante estará em UTC (e neste caso, não adianta mudar o timezone default com date_default_timezone_set, pois este é ignorado quando um timestamp é usado). Se você fizer um var_dump($d), a saída será: object(DateTime)#10 (3) { ["date"]=> string(26) "2019-04-26 23:53:54.000000" ["timezone_type"]=> int(1) ["timezone"]=> string(6) "+00:00" } O offset +00:00 indica que de fato o DateTime está em UTC. Se quiser que ela esteja em um timezone específico, basta passar um DateTimeZone como parâmetro: $d = DateTime::createFromFormat('U', 1556322834); $d->setTimezone(new DateTimeZone('America/Sao_Paulo')); // ou $d = new DateTime('@'. 1556322834); $d->setTimezone(new DateTimeZone('America/Sao_Paulo')); Um detalhe chato é que no construtor, o timestamp exige que se tenha o @ antes (veja o campo “Unix Timestamp” na documentação) Em ambos os casos, a saída será: object(DateTime)#10 (3) { ["date"]=> string(26) "2019-04-26 20:53:54.000000" ["timezone_type"]=> int(3) ["timezone"]=> string(17) "America/Sao_Paulo" } Ao ajustar o timezone, os valores de data e hora também são alterados de acordo com as regras do timezone utilizado. Um detalhe interessante é que usando createFromFormat também é possível usar valores com frações de segundos (limitado a 6 casas decimais): # timestamp correspondente a 1556322834 segundos e 123456 microssegundos $d = DateTime::createFromFormat('U.u', '1556322834.123456'); Para obter o valor do timestamp a partir de uma data, hora e timezone específicos, também é simples: $d = new DateTime(); $d->setTimezone(new DateTimeZone('America/Sao_Paulo')); $d->setDate(2019, 4, 26); // 26 de abril de 2019 $d->setTime(10, 30); // 10:30 da manhã echo $d->getTimestamp(); // 1556285400 // ou $d = new DateTime('2019-04-26T10:30', new DateTimeZone('America/Sao_Paulo')); echo $d->getTimestamp(); // 1556285400 // ou ainda echo strtotime('2019-04-26T10:30 America/Sao_Paulo'); // 1556285400 Atenção para chamar setTimezone antes de setDate e setTime. Se você mudar a data e hora primeiro, as alterações serão feitas levando em conta o timezone default. Depois, ao mudar o timezone, a data e hora serão ajustadas de acordo com o novo timezone, que não necessariamente serão os mesmos que você setou. Por fim, se você precisa apenas do valor numérico do timestamp atual, basta usar a função time(), que retorna o timestamp em segundos. E a função strtotime? Bem, ela também funciona (strtotime('now') retorna o mesmo que time()), e muitos costumam usar em conjunto com date: date_default_timezone_set('America/Sao_Paulo'); echo(date('d/m/Y H:i:s', strtotime('now'))); No caso, strtotime retorna o timestamp, e date converte este timestamp para uma data e hora específicas, retornando uma string no formato indicado. No exemplo acima, o retorno é a data e hora atual no formato “dia/mês/ano hora:minuto:segundo” (atenção para o fato de que date, apesar do nome, retorna uma string - se quiser um objeto que represente uma data, use DateTime). O detalhe é que date usa o timezone default que está setado no momento. Se eu quiser que ela use um timezone específico, devo mudá-lo com date_default_timezone_set. Só que isso afeta todas as demais funções que usam o timezone default. Já usando a solução anterior com DateTime, eu mudo somente o timezone das instâncias com as quais estou trabalhando, sem mudar a configuração do PHP. JavaScript Em JavaScript você pode usar Date, passando o timestamp para o construtor. O detalhe é que o valor deve estar em milissegundos: let d = new Date(1556322834000); console.log(d); A data e hora que será mostrada depende do timezone que está configurado no browser (que geralmente é obtido do sistema operacional) e não há muito mais o que fazer para controlar isso. O melhor que dá para fazer é gerar uma string contendo a data e hora correspondentes em um timezone específico: console.log(d.toLocaleString('pt-BR', { timeZone: 'America/Los_Angeles' })); console.log(d.toLocaleString('pt-BR', { timeZone: 'Asia/Tokyo' })); Com isso, é usado o formato correspondente ao locale pt-BR (que deve estar instalado no sistema, caso contrário será usado o locale default que estiver configurado no browser), e os valores de data e hora são obtidos a partir do timezone indicado. A saída é: 26/04/2019 16:53:54 27/04/2019 08:53:54 Para obter o valor do timestamp a partir de uma data e hora específica, você pode passar os valores diretamente para o construtor, e em seguida usar o método getTime(), que retorna o timestamp em milissegundos: let d = new Date(2019, 3, 26, 10, 30); console.log(d.getTime()); O detalhe é que é usado o timezone do browser, e não há como controlar isso. Uma opção para controlar o timezone é usar o Moment.js, juntamente como o Moment Timezone. Com isso, você pode escolher o timezone para o qual o timestamp será convertido: moment.tz(1556322834000, "America/Los_Angeles").format() // 2019-04-26T16:53:54-07:00 moment.tz(1556322834000, "America/Sao_Paulo").format() // 2019-04-26T20:53:54-03:00 Repare que o valor do timestamp está em milissegundos. Também é possível obter o timestamp corresponde a uma data e hora, em um timezone específico: moment.tz("2019-04-26 10:30", "America/Los_Angeles").valueOf() // 1556299800000 moment.tz("2019-04-26 10:30", "America/Sao_Paulo").valueOf() // 1556285400000 O método valueOf() retorna o valor do timestamp em milissegundos. Os valores são diferentes porque as 10:30 do dia 26/04/2019 ocorreram em instantes diferentes em Los Angeles e São Paulo. Se quiser este mesmo valor em segundos, use o método unix(). E se quiser o valor numérico do timestamp atual, basta fazer new Date().getTime(). Para entender melhor o Date do JavaScript, fiz este post mais detalhado. Enfim, todas as linguagens possuem algum suporte a datas, horas, timezones e timestamps (algumas melhores, outras piores). Sempre que for converter uma data e hora de/para um timestamp, é importante saber qual o timezone que está sendo utilizado. Como você pôde perceber, esta informação nem sempre está clara: muitas vezes é usado um timezone default (que nem sempre dá para saber facilmente qual é) e algum valor sempre é retornado - e como não dá erro, muitos assumem que “funcionou”, sem sequer conferir se os valores estão corretos. Não há mágica: um timestamp corresponde a uma data e hora específicas em cada timezone. Se foi retornado um timestamp, é porque as 3 informações (data, hora e timezone) estavam presentes. Se você não forneceu alguma(s) dela(s), então algum valor default foi usado. É comum, por exemplo, APIs que só recebem dia, mês e ano e retornam um timestamp. Nesse caso, algum valor foi usado para o horário (a maioria usa meia-noite) e para o timezone (o default que está configurado “no sistema”). Em alguns casos este comportamento é documentado, e dependendo da API, isso pode ser configurado ou alterado programaticamente. Sempre leia a documentação e confira os valores retornados. Não aceite qualquer data só porque “funcionou”. Lembrando que “meia-noite em UTC” não significa “meia-noite” em todos os lugares. Cada fuso-horário do mundo possui um horário diferente com relação a UTC (o Horário de Brasília, por exemplo, é 3 horas a menos em relação a UTC). ↩ Algumas linguagens e APIs retornam o valor em segundos, enquanto outras retornam o valor em milissegundos (ou em microssegundos, ou até mesmo nanossegundos - sempre leia a documentação para saber qual a precisão máxima suportada). Mas a ideia geral é a mesma: quaisquer computadores do mundo - assumindo que estão configurados corretamente - rodando ao mesmo tempo, obtêm o mesmo valor do timestamp para a “data atual”. ↩Como ler e manipular um JSON2019-04-13T15:07:00-03:002019-04-13T15:07:00-03:00https://hkotsubo.github.io/blog/2019-04-13/como-ler-e-manipular-um-json<p>JSON significa “JavaScript Object Notation” (Notação de Objetos JavaScript). Apesar de ter “JavaScript” no nome, ele pode ser usado em outras linguagens, pois o JSON é na verdade um <a href="http://json.org/"><strong>formato</strong> para representar dados</a>. A sua sintaxe foi inspirada no JavaScript - daí o nome - mas hoje em dia várias linguagens possuem algum suporte para ele, seja nativo ou via bibliotecas.</p>
<p>Dentro de um JSON podemos ter vários tipos de dados diferentes. Para começar, temos os tipos “básicos”:</p>
<ul>
<li>strings: quaisquer caracteres entre aspas duplas. Ex: <code class="language-plaintext highlighter-rouge">"sou uma string"</code>. Também são aceitas aquelas sequências de escape comuns em várias linguagens, como <code class="language-plaintext highlighter-rouge">\n</code> para representar a quebra de linha, <code class="language-plaintext highlighter-rouge">\t</code> para <kbd>TAB</kbd>, etc.</li>
<li>números: pode ser um valor inteiro positivo (<code class="language-plaintext highlighter-rouge">42</code>), negativo (<code class="language-plaintext highlighter-rouge">-10</code>), com casas decimais (<code class="language-plaintext highlighter-rouge">12.34</code> - e repare que o ponto - e não a vírgula - é o separador das casas decimais) ou em notação científica (<code class="language-plaintext highlighter-rouge">2.53962e15</code>)</li>
<li>valores <em>booleanos</em> (<code class="language-plaintext highlighter-rouge">true</code> e <code class="language-plaintext highlighter-rouge">false</code>) e o valor nulo (<code class="language-plaintext highlighter-rouge">null</code>)</li>
</ul>
<p>Além destes, podemos ter também <strong>arrays</strong> e <strong>objetos</strong>.</p>
<h3 id="array">Array</h3>
<p>Um array é uma lista ordenada de valores, dentro de colchetes (<code class="language-plaintext highlighter-rouge">[</code> e <code class="language-plaintext highlighter-rouge">]</code>) e separados por vírgulas. Exemplo:</p>
<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">[</span> <span class="mi">1</span><span class="p">,</span> <span class="dl">"</span><span class="s2">segundo elemento</span><span class="dl">"</span><span class="p">,</span> <span class="mf">3.4</span> <span class="p">]</span>
</code></pre></div></div>
<p>Este array tem 3 elementos: o primeiro é o número <code class="language-plaintext highlighter-rouge">1</code>, o segundo é a string <code class="language-plaintext highlighter-rouge">"segundo elemento"</code> e o terceiro é o número <code class="language-plaintext highlighter-rouge">3.4</code>. Veja que ele começa com <code class="language-plaintext highlighter-rouge">[</code> e termina com <code class="language-plaintext highlighter-rouge">]</code>, e os elementos são separados por vírgulas.</p>
<p>Um detalhe importante é que um array não precisa ter necessariamente todos os elementos do mesmo tipo<sup id="fnref:array" role="doc-noteref"><a href="#fn:array" class="footnote" rel="footnote">1</a></sup>.</p>
<p>Além disso, em um array, a ordem é importante. Então <code class="language-plaintext highlighter-rouge">[ 1, "segundo elemento", 3.4 ]</code> <strong>não</strong> é o mesmo que <code class="language-plaintext highlighter-rouge">[ 1, 3.4, "segundo elemento" ]</code>.</p>
<h3 id="objeto">Objeto</h3>
<p>Um objeto é um conjunto de pares “chave: valor”, dentro de chaves (<code class="language-plaintext highlighter-rouge">{</code> e <code class="language-plaintext highlighter-rouge">}</code>) e separados por vírgulas. A chave sempre é uma string, e o valor pode ser qualquer tipo válido. Exemplo:</p>
<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">{</span> <span class="dl">"</span><span class="s2">nome</span><span class="dl">"</span><span class="p">:</span> <span class="dl">"</span><span class="s2">Fulano</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">idade</span><span class="dl">"</span><span class="p">:</span> <span class="mi">90</span> <span class="p">}</span>
</code></pre></div></div>
<p>Este objeto possui duas chaves:</p>
<ul>
<li><code class="language-plaintext highlighter-rouge">"nome"</code>, cujo valor é a string <code class="language-plaintext highlighter-rouge">"Fulano"</code></li>
<li><code class="language-plaintext highlighter-rouge">"idade"</code>, cujo valor é o número <code class="language-plaintext highlighter-rouge">90</code></li>
</ul>
<p>Repare que ele começa com <code class="language-plaintext highlighter-rouge">{</code> e termina com <code class="language-plaintext highlighter-rouge">}</code>. Entre uma chave e seu respectivo valor, sempre tem o caractere <code class="language-plaintext highlighter-rouge">:</code>, e entre cada par “chave: valor” há uma vírgula.</p>
<p>Um detalhe importante é que em um objeto a ordem dos pares “chave: valor” <strong>não importa</strong>. Ou seja, <code class="language-plaintext highlighter-rouge">{ "nome": "Fulano", "idade": 90 }</code> e <code class="language-plaintext highlighter-rouge">{ "idade": 90, "nome": "Fulano" }</code> são exatamente o mesmo objeto.</p>
<hr />
<p>Dentro de um array ou objeto, os valores podem ser qualquer um dos tipos acima. Ou seja, podemos ter não só os tipos básicos, mas também outros arrays e objetos. Então podemos ter coisas como:</p>
<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">{</span>
<span class="dl">"</span><span class="s2">nome</span><span class="dl">"</span><span class="p">:</span> <span class="dl">"</span><span class="s2">Fulano</span><span class="dl">"</span><span class="p">,</span>
<span class="dl">"</span><span class="s2">idade</span><span class="dl">"</span><span class="p">:</span> <span class="mi">90</span><span class="p">,</span>
<span class="dl">"</span><span class="s2">filmes_preferidos</span><span class="dl">"</span><span class="p">:</span> <span class="p">[</span> <span class="dl">"</span><span class="s2">Pulp Fiction</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">Clube da Luta</span><span class="dl">"</span> <span class="p">],</span>
<span class="dl">"</span><span class="s2">contatos</span><span class="dl">"</span><span class="p">:</span> <span class="p">{</span>
<span class="dl">"</span><span class="s2">telefone</span><span class="dl">"</span><span class="p">:</span> <span class="dl">"</span><span class="s2">(11) 91111-2222</span><span class="dl">"</span><span class="p">,</span>
<span class="dl">"</span><span class="s2">emails</span><span class="dl">"</span><span class="p">:</span> <span class="p">[</span> <span class="dl">"</span><span class="s2">fulano@gmail.com</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">fulano@yahoo.com</span><span class="dl">"</span> <span class="p">]</span>
<span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>
<p>O JSON acima é um objeto, pois está delimitado por <code class="language-plaintext highlighter-rouge">{</code> e <code class="language-plaintext highlighter-rouge">}</code>. Dentro dele temos 4 chaves:</p>
<ul>
<li><code class="language-plaintext highlighter-rouge">"nome"</code>, cujo valor é a string <code class="language-plaintext highlighter-rouge">"Fulano"</code></li>
<li><code class="language-plaintext highlighter-rouge">"idade"</code>, cujo valor é o número <code class="language-plaintext highlighter-rouge">90</code></li>
<li><code class="language-plaintext highlighter-rouge">"filmes_preferidos"</code>, cujo valor é um array (pois está delimitado por colchetes: <code class="language-plaintext highlighter-rouge">[</code> e <code class="language-plaintext highlighter-rouge">]</code>)
<ul>
<li>este array, por sua vez, possui dois elementos: as strings <code class="language-plaintext highlighter-rouge">"Pulp Fiction"</code> e <code class="language-plaintext highlighter-rouge">"Clube da Luta"</code></li>
</ul>
</li>
<li><code class="language-plaintext highlighter-rouge">"contatos"</code>, cujo valor é <strong>outro</strong> objeto (pois este também está delimitado por <code class="language-plaintext highlighter-rouge">{</code> e <code class="language-plaintext highlighter-rouge">}</code>)
<ul>
<li>este objeto, por sua vez, possui duas chaves:
<ul>
<li><code class="language-plaintext highlighter-rouge">"telefone"</code>: cujo valor é a string <code class="language-plaintext highlighter-rouge">"(11) 91111-2222"</code></li>
<li><code class="language-plaintext highlighter-rouge">"emails"</code>: cujo valor é um array (pois está delimitado por colchetes)
<ul>
<li>este array, por sua vez, possui dois elementos: as strings <code class="language-plaintext highlighter-rouge">"fulano@gmail.com"</code> e <code class="language-plaintext highlighter-rouge">"fulano@yahoo.com"</code></li>
</ul>
</li>
</ul>
</li>
</ul>
</li>
</ul>
<p>Outro exemplo:</p>
<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">[</span>
<span class="p">{</span>
<span class="dl">"</span><span class="s2">nome</span><span class="dl">"</span><span class="p">:</span> <span class="dl">"</span><span class="s2">Fulano</span><span class="dl">"</span><span class="p">,</span>
<span class="dl">"</span><span class="s2">idade</span><span class="dl">"</span><span class="p">:</span> <span class="mi">90</span>
<span class="p">},</span>
<span class="p">{</span>
<span class="dl">"</span><span class="s2">nome</span><span class="dl">"</span><span class="p">:</span> <span class="dl">"</span><span class="s2">Ciclano</span><span class="dl">"</span><span class="p">,</span>
<span class="dl">"</span><span class="s2">idade</span><span class="dl">"</span><span class="p">:</span> <span class="mi">10</span><span class="p">,</span>
<span class="dl">"</span><span class="s2">email</span><span class="dl">"</span><span class="p">:</span> <span class="dl">"</span><span class="s2">ciclano@gmail.com</span><span class="dl">"</span>
<span class="p">}</span>
<span class="p">]</span>
</code></pre></div></div>
<p>O JSON acima é um array, pois está delimitado por <code class="language-plaintext highlighter-rouge">[</code> e <code class="language-plaintext highlighter-rouge">]</code>. Ele contém 2 elementos:</p>
<ul>
<li>o primeiro elemento é um objeto (pois está delimitado por <code class="language-plaintext highlighter-rouge">{</code> e <code class="language-plaintext highlighter-rouge">}</code>), que por sua vez possui duas chaves:
<ul>
<li><code class="language-plaintext highlighter-rouge">"nome"</code>, cujo valor é a string <code class="language-plaintext highlighter-rouge">"Fulano"</code></li>
<li><code class="language-plaintext highlighter-rouge">"idade"</code>, cujo valor é o número <code class="language-plaintext highlighter-rouge">90</code></li>
</ul>
</li>
<li>o segundo elemento também é um objeto (pois está delimitado por <code class="language-plaintext highlighter-rouge">{</code> e <code class="language-plaintext highlighter-rouge">}</code>), que por sua vez possui três chaves:
<ul>
<li><code class="language-plaintext highlighter-rouge">"nome"</code>, cujo valor é a string <code class="language-plaintext highlighter-rouge">"Ciclano"</code></li>
<li><code class="language-plaintext highlighter-rouge">"idade"</code>, cujo valor é o número <code class="language-plaintext highlighter-rouge">10</code></li>
<li><code class="language-plaintext highlighter-rouge">"email"</code>, cujo valor é a string <code class="language-plaintext highlighter-rouge">"ciclano@gmail.com"</code></li>
</ul>
</li>
</ul>
<p>Repare que depois do fechamento do primeiro objeto (ou seja, depois do <code class="language-plaintext highlighter-rouge">}</code>) há uma vírgula, que é o separador de elementos de um array. Da mesma forma que um array de números teria a vírgula entre cada número (como por exemplo <code class="language-plaintext highlighter-rouge">[ 1, 2, 3 ]</code>), um array de objetos também deve ter a vírgula separando-os.</p>
<h2 id="regra-básica-para-ler-um-json">Regra básica para ler um JSON</h2>
<p>A regra básica é: antes de sair escrevendo código, <strong>pare e olhe</strong> para o JSON. Veja se ele é um objeto ou array (que é o que a grande maioria das APIs REST retorna hoje em dia): se começa com <code class="language-plaintext highlighter-rouge">{</code> é um objeto, se começa com <code class="language-plaintext highlighter-rouge">[</code> é um array.</p>
<p>Depois veja o que tem dentro desse objeto ou array. Se for um array, procure pelas vírgulas que separam os elementos e verifique os seus tipos, e preocupe-se com a ordem em que eles estão, para saber como obter cada um. Se for um objeto, veja quais são as chaves e os respectivos valores.</p>
<p>Se o JSON é muito grande e/ou com muitas estruturas aninhadas (objetos dentro de arrays, dentro outros arrays, dentro de outros objetos, etc), formatá-lo ajuda a visualizar a sua estrutura. Há vários sites como <a href="https://jsonlint.com/">esse</a>, que validam e formatam um JSON. Além disso, as linguagens/APIs que trabalham com JSON geralmente possuem uma opção para formatá-lo (veremos alguns exemplos mais abaixo).</p>
<p>Outro ponto importante é verificar se o JSON que você está tentando ler é de fato um JSON válido. Um exemplo clássico é:</p>
<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">{</span>
<span class="dl">"</span><span class="s2">nome</span><span class="dl">"</span><span class="p">:</span> <span class="dl">"</span><span class="s2">Fulano</span><span class="dl">"</span><span class="p">,</span>
<span class="dl">"</span><span class="s2">idade</span><span class="dl">"</span><span class="p">:</span> <span class="mi">90</span>
<span class="p">},</span>
<span class="p">{</span>
<span class="dl">"</span><span class="s2">nome</span><span class="dl">"</span><span class="p">:</span> <span class="dl">"</span><span class="s2">Ciclano</span><span class="dl">"</span><span class="p">,</span>
<span class="dl">"</span><span class="s2">idade</span><span class="dl">"</span><span class="p">:</span> <span class="mi">10</span>
<span class="p">}</span>
</code></pre></div></div>
<p>Inicialmente você pode pensar que, como começa com <code class="language-plaintext highlighter-rouge">{</code>, então é um objeto. Mas repare bem: o primeiro objeto (o que tem <code class="language-plaintext highlighter-rouge">"nome": "Fulano"</code>) termina na quarta linha (onde há o <code class="language-plaintext highlighter-rouge">}</code>), e logo em seguida há uma vírgula. Logo depois, temos <strong>outro</strong> objeto (o que tem <code class="language-plaintext highlighter-rouge">"nome": "Ciclano"</code>).</p>
<p><strong>Este não é um JSON válido</strong>. Há dois objetos “soltos”, separados por vírgula. <strong>Cada um deles individualmente</strong> é um JSON válido, mas ter ambos separados por vírgula não é.</p>
<p>O que poderia deixar esta estrutura válida é colocá-la entre <code class="language-plaintext highlighter-rouge">[</code> e <code class="language-plaintext highlighter-rouge">]</code>:</p>
<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">[</span>
<span class="p">{</span>
<span class="dl">"</span><span class="s2">nome</span><span class="dl">"</span><span class="p">:</span> <span class="dl">"</span><span class="s2">Fulano</span><span class="dl">"</span><span class="p">,</span>
<span class="dl">"</span><span class="s2">idade</span><span class="dl">"</span><span class="p">:</span> <span class="mi">90</span>
<span class="p">},</span>
<span class="p">{</span>
<span class="dl">"</span><span class="s2">nome</span><span class="dl">"</span><span class="p">:</span> <span class="dl">"</span><span class="s2">Ciclano</span><span class="dl">"</span><span class="p">,</span>
<span class="dl">"</span><span class="s2">idade</span><span class="dl">"</span><span class="p">:</span> <span class="mi">10</span>
<span class="p">}</span>
<span class="p">]</span>
</code></pre></div></div>
<p>Pois agora ela se tornou um array com dois objetos. Mas da forma que estava (sem o <code class="language-plaintext highlighter-rouge">[</code> e <code class="language-plaintext highlighter-rouge">]</code>), a estrutura é inválida, e muitas APIs dão erro ao tentar processá-la (ou ignoram o segundo objeto, varia conforme a implementação).</p>
<p>Depois de ler o JSON e verificar se ele é um array ou objeto, você pode acessar seus elementos. Cada linguagem fornece seu mecanismo para tal, e abaixo coloquei exemplos em algumas linguagens.</p>
<hr />
<p>Antes de prosseguir, um detalhe importante: JSON é um formato para troca de dados, ou seja, para informações que serão mandadas “para lá e para cá”. E no fim, tudo isso vira texto (um monte de caracteres que você vai ter que interpretar). O que quer dizer que todos os exemplos de JSON que vimos até agora são na verdade strings (ou o resultado retornado por uma API, ou um arquivo).</p>
<p>Se eu consulto uma API e ela retorna <code class="language-plaintext highlighter-rouge">{ "nome": "Fulano", "idade": 90 }</code>, o que eu terei é uma <strong>string</strong> que contém o caractere <code class="language-plaintext highlighter-rouge">{</code>, depois o espaço, depois a aspas, depois a letra <code class="language-plaintext highlighter-rouge">n</code> e assim por diante. O que as APIs de JSON fazem é transformar esta string em alguma estrutura similar na linguagem em questão (geralmente arrays/listas e maps/dicionários/objects, além dos tipos numéricos e strings da própria linguagem). Sempre veja a documentação para saber como é feito o mapeamento entre tipos.</p>
<p>Agora sim, vamos ao código. Para todos os exemplos abaixo, usarei este JSON:</p>
<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">{</span>
<span class="dl">"</span><span class="s2">nome</span><span class="dl">"</span><span class="p">:</span> <span class="dl">"</span><span class="s2">Fulano</span><span class="dl">"</span><span class="p">,</span>
<span class="dl">"</span><span class="s2">idade</span><span class="dl">"</span><span class="p">:</span> <span class="mi">90</span><span class="p">,</span>
<span class="dl">"</span><span class="s2">filmes_preferidos</span><span class="dl">"</span><span class="p">:</span> <span class="p">[</span> <span class="dl">"</span><span class="s2">Pulp Fiction</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">Clube da Luta</span><span class="dl">"</span> <span class="p">],</span>
<span class="dl">"</span><span class="s2">contatos</span><span class="dl">"</span><span class="p">:</span> <span class="p">{</span>
<span class="dl">"</span><span class="s2">telefone</span><span class="dl">"</span><span class="p">:</span> <span class="dl">"</span><span class="s2">(11) 91111-2222</span><span class="dl">"</span><span class="p">,</span>
<span class="dl">"</span><span class="s2">emails</span><span class="dl">"</span><span class="p">:</span> <span class="p">[</span> <span class="dl">"</span><span class="s2">fulano@gmail.com</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">fulano@yahoo.com</span><span class="dl">"</span> <span class="p">]</span>
<span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>
<p>Se quiser, pode usar os links abaixo para ir direto para a sua linguagem de sua preferência:</p>
<ul>
<li><a href="#java">Java</a></li>
<li><a href="#c">C#</a></li>
<li><a href="#python">Python</a></li>
<li><a href="#php">PHP</a></li>
<li><a href="#javascript">JavaScript</a></li>
<li><a href="#regex">Regex</a></li>
</ul>
<p><sup>Mas já adianto que o exemplo em Java é um <em>pouquinho</em> mais completo que os demais, por ser a linguagem com a qual tenho mais familiaridade.</sup></p>
<h3 id="java">Java</h3>
<p>No Java existem várias APIs disponíveis, como o pacote <a href="https://mvnrepository.com/artifact/org.json/json/"><code class="language-plaintext highlighter-rouge">org.json</code></a> e a biblioteca <a href="https://github.com/google/gson">Gson do Google</a>. Todas são bem parecidas, possuindo classes para representar objetos e arrays. Um exemplo com <code class="language-plaintext highlighter-rouge">org.json</code>:</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">import</span> <span class="nn">org.json.JSONArray</span><span class="o">;</span>
<span class="kn">import</span> <span class="nn">org.json.JSONObject</span><span class="o">;</span>
<span class="nc">String</span> <span class="n">texto</span> <span class="o">=</span> <span class="s">"{ \"nome\": \"Fulano\", \"idade\": 90, \"filmes_preferidos\": [ \"Pulp Fiction\", \"Clube da Luta\" ],"</span>
<span class="o">+</span> <span class="s">" \"contatos\": { \"telefone\": \"(11) 91111-2222\", \"emails\": [ \"fulano@gmail.com\", \"fulano@yahoo.com\" ] } }"</span><span class="o">;</span>
<span class="c1">// cria o JSONObject</span>
<span class="nc">JSONObject</span> <span class="n">obj</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">JSONObject</span><span class="o">(</span><span class="n">texto</span><span class="o">);</span>
<span class="c1">// verifica se possui a chave "nome"</span>
<span class="k">if</span> <span class="o">(</span><span class="n">obj</span><span class="o">.</span><span class="na">has</span><span class="o">(</span><span class="s">"nome"</span><span class="o">))</span> <span class="o">{</span>
<span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="s">"possui nome="</span> <span class="o">+</span> <span class="n">obj</span><span class="o">.</span><span class="na">getString</span><span class="o">(</span><span class="s">"nome"</span><span class="o">));</span> <span class="c1">// nome é uma String</span>
<span class="o">}</span> <span class="k">else</span> <span class="o">{</span>
<span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="s">"não possui nome"</span><span class="o">);</span>
<span class="o">}</span>
<span class="c1">// obtém o array de filmes</span>
<span class="nc">JSONArray</span> <span class="n">filmes</span> <span class="o">=</span> <span class="n">obj</span><span class="o">.</span><span class="na">getJSONArray</span><span class="o">(</span><span class="s">"filmes_preferidos"</span><span class="o">);</span>
<span class="n">filmes</span><span class="o">.</span><span class="na">forEach</span><span class="o">(</span><span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">::</span><span class="n">println</span><span class="o">);</span> <span class="c1">// imprime todos os filmes</span>
<span class="c1">// obtém o objeto com os contatos</span>
<span class="nc">JSONObject</span> <span class="n">contatos</span> <span class="o">=</span> <span class="n">obj</span><span class="o">.</span><span class="na">getJSONObject</span><span class="o">(</span><span class="s">"contatos"</span><span class="o">);</span>
<span class="k">for</span> <span class="o">(</span><span class="nc">String</span> <span class="n">chave</span> <span class="o">:</span> <span class="n">contatos</span><span class="o">.</span><span class="na">keySet</span><span class="o">())</span> <span class="o">{</span>
<span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="n">chave</span> <span class="o">+</span> <span class="s">"="</span> <span class="o">+</span> <span class="n">contatos</span><span class="o">.</span><span class="na">get</span><span class="o">(</span><span class="n">chave</span><span class="o">));</span>
<span class="o">}</span>
<span class="c1">//----------------------------------------------------------</span>
<span class="c1">// adicionar mais um contato</span>
<span class="n">contatos</span><span class="o">.</span><span class="na">put</span><span class="o">(</span><span class="s">"twitter"</span><span class="o">,</span> <span class="s">"@fulano"</span><span class="o">);</span>
<span class="c1">// imprime o JSON formatado (indentando com 2 espaços)</span>
<span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="n">obj</span><span class="o">.</span><span class="na">toString</span><span class="o">(</span><span class="mi">2</span><span class="o">));</span>
</code></pre></div></div>
<p>A primeira parte pega uma <code class="language-plaintext highlighter-rouge">String</code> contendo todo o JSON e transforma em um <code class="language-plaintext highlighter-rouge">JSONObject</code>. Em seguida eu uso o método <code class="language-plaintext highlighter-rouge">has</code> para verificar se o objeto possui determinada chave e o método <code class="language-plaintext highlighter-rouge">getString</code> para obter o valor de uma chave como uma <code class="language-plaintext highlighter-rouge">String</code>.</p>
<p>Depois eu uso <code class="language-plaintext highlighter-rouge">getJSONArray</code> para obter o array correspondente à chave <code class="language-plaintext highlighter-rouge">"filmes_preferidos"</code> e percorro o mesmo, imprimindo cada um dos seus valores. Também faço algo similar com a chave <code class="language-plaintext highlighter-rouge">"contatos"</code>, obtendo o objeto correspondente (com <code class="language-plaintext highlighter-rouge">getJSONObject</code>) e percorrendo todas suas chaves e valores. Até este ponto, a saída é:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>possui nome=Fulano
Pulp Fiction
Clube da Luta
emails=["fulano@gmail.com","fulano@yahoo.com"]
telefone=(11) 91111-2222
</code></pre></div></div>
<p>Repare como os elementos do array (<code class="language-plaintext highlighter-rouge">"Pulp Fiction"</code> e <code class="language-plaintext highlighter-rouge">"Clube da Luta"</code>) mantêm a sua ordem, já que em um array a ordem dos elementos é importante. Já as chaves do objeto “contatos” aparecem fora de ordem (na <code class="language-plaintext highlighter-rouge">String</code> original, <code class="language-plaintext highlighter-rouge">"telefone"</code> estava antes de <code class="language-plaintext highlighter-rouge">"emails"</code>, mas ao percorrer o objeto, elas são retornadas em outra ordem), pois em um objeto não importa a ordem das chaves - e a ordem mostrada vai depender dos detalhes internos de implementação da API. Mas note que a chave <code class="language-plaintext highlighter-rouge">"emails"</code> possui como valor um array com os endereços de email, e dentro deste array, a ordem dos elementos é preservada.</p>
<p>Por fim, eu uso o método <code class="language-plaintext highlighter-rouge">put</code> para adicionar mais um par “chave: valor” ao objeto <code class="language-plaintext highlighter-rouge">"contatos"</code>, e uso o método <code class="language-plaintext highlighter-rouge">toString()</code>, passando como parâmetro a quantidade de espaços usada para formatar o JSON (no caso, usei 2 espaços). A saída é:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>{
"idade": 90,
"filmes_preferidos": [
"Pulp Fiction",
"Clube da Luta"
],
"contatos": {
"emails": [
"fulano@gmail.com",
"fulano@yahoo.com"
],
"telefone": "(11) 91111-2222",
"twitter": "@fulano"
},
"nome": "Fulano"
}
</code></pre></div></div>
<p>Repare novamente que nos arrays a ordem dos elementos é mantida, mas nas chaves dos objetos, não necessariamente é a mesma. <strong>Lembre-se que em um objeto JSON, a ordem das chaves não importa, e portanto nenhuma implementação é obrigada a manter a mesma ordem</strong>.</p>
<p>Se a variável <code class="language-plaintext highlighter-rouge">texto</code> tivesse um array em vez de um objeto, bastaria você usar <code class="language-plaintext highlighter-rouge">new JSONArray(texto)</code>.</p>
<p>Caso o JSON seja inválido, os construtores de <code class="language-plaintext highlighter-rouge">JSONObject</code> e <code class="language-plaintext highlighter-rouge">JSONArray</code> lançam um <code class="language-plaintext highlighter-rouge">org.json.JSONException</code>.</p>
<hr />
<p>Com a API Gson, podemos ir além e mapear o JSON para um objeto específico. Para o nosso JSON, podemos criar uma classe <code class="language-plaintext highlighter-rouge">Pessoa</code>, por exemplo:</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">import</span> <span class="nn">java.util.List</span><span class="o">;</span>
<span class="kn">import</span> <span class="nn">java.util.Map</span><span class="o">;</span>
<span class="kn">import</span> <span class="nn">com.google.gson.annotations.SerializedName</span><span class="o">;</span>
<span class="kd">public</span> <span class="kd">class</span> <span class="nc">Pessoa</span> <span class="o">{</span>
<span class="kd">private</span> <span class="nc">String</span> <span class="n">nome</span><span class="o">;</span>
<span class="kd">private</span> <span class="kt">int</span> <span class="n">idade</span><span class="o">;</span>
<span class="nd">@SerializedName</span><span class="o">(</span><span class="s">"filmes_preferidos"</span><span class="o">)</span>
<span class="kd">private</span> <span class="nc">List</span><span class="o"><</span><span class="nc">String</span><span class="o">></span> <span class="n">filmesPreferidos</span><span class="o">;</span>
<span class="kd">private</span> <span class="nc">Map</span><span class="o"><</span><span class="nc">String</span><span class="o">,</span> <span class="nc">Object</span><span class="o">></span> <span class="n">contatos</span><span class="o">;</span>
<span class="c1">// construtores, getters, setters, etc...</span>
<span class="o">}</span>
</code></pre></div></div>
<p>Com isso, podemos ler o JSON e criar uma instância de <code class="language-plaintext highlighter-rouge">Pessoa</code> com os campos já preenchidos:</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">import</span> <span class="nn">com.google.gson.Gson</span><span class="o">;</span>
<span class="kn">import</span> <span class="nn">com.google.gson.GsonBuilder</span><span class="o">;</span>
<span class="nc">String</span> <span class="n">texto</span> <span class="o">=</span> <span class="s">"{ \"nome\": \"Fulano\", \"idade\": 90, \"filmes_preferidos\": [ \"Pulp Fiction\", \"Clube da Luta\" ],"</span>
<span class="o">+</span> <span class="s">" \"contatos\": { \"telefone\": \"(11) 91111-2222\", \"emails\": [ \"fulano@gmail.com\", \"fulano@yahoo.com\" ] } }"</span><span class="o">;</span>
<span class="nc">Gson</span> <span class="n">gson</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">GsonBuilder</span><span class="o">().</span><span class="na">setPrettyPrinting</span><span class="o">().</span><span class="na">create</span><span class="o">();</span>
<span class="c1">// lê o JSON e cria uma Pessoa com os campos preenchidos</span>
<span class="nc">Pessoa</span> <span class="n">pessoa</span> <span class="o">=</span> <span class="n">gson</span><span class="o">.</span><span class="na">fromJson</span><span class="o">(</span><span class="n">texto</span><span class="o">,</span> <span class="nc">Pessoa</span><span class="o">.</span><span class="na">class</span><span class="o">);</span>
<span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="s">"nome="</span> <span class="o">+</span> <span class="n">pessoa</span><span class="o">.</span><span class="na">getNome</span><span class="o">());</span>
<span class="k">for</span> <span class="o">(</span><span class="nc">String</span> <span class="n">filme</span> <span class="o">:</span> <span class="n">pessoa</span><span class="o">.</span><span class="na">getFilmesPreferidos</span><span class="o">())</span> <span class="o">{</span>
<span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="n">filme</span><span class="o">);</span>
<span class="o">}</span>
<span class="k">for</span> <span class="o">(</span><span class="nc">Map</span><span class="o">.</span><span class="na">Entry</span><span class="o"><</span><span class="nc">String</span><span class="o">,</span> <span class="nc">Object</span><span class="o">></span> <span class="n">contato</span> <span class="o">:</span> <span class="n">pessoa</span><span class="o">.</span><span class="na">getContatos</span><span class="o">().</span><span class="na">entrySet</span><span class="o">())</span> <span class="o">{</span>
<span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="n">contato</span><span class="o">.</span><span class="na">getKey</span><span class="o">()</span> <span class="o">+</span> <span class="s">"="</span> <span class="o">+</span> <span class="n">contato</span><span class="o">.</span><span class="na">getValue</span><span class="o">());</span>
<span class="o">}</span>
<span class="c1">// adicionar um novo contato</span>
<span class="n">pessoa</span><span class="o">.</span><span class="na">getContatos</span><span class="o">().</span><span class="na">put</span><span class="o">(</span><span class="s">"twitter"</span><span class="o">,</span> <span class="s">"@fulano"</span><span class="o">);</span>
<span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="n">gson</span><span class="o">.</span><span class="na">toJson</span><span class="o">(</span><span class="n">pessoa</span><span class="o">));</span>
</code></pre></div></div>
<p>Com isso fica bem mais fácil obter os campos do JSON e adicionar novas informações ao mesmo. A saída é:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>nome=Fulano
Pulp Fiction
Clube da Luta
telefone=(11) 91111-2222
emails=[fulano@gmail.com, fulano@yahoo.com]
{
"nome": "Fulano",
"idade": 90,
"filmes_preferidos": [
"Pulp Fiction",
"Clube da Luta"
],
"contatos": {
"telefone": "(11) 91111-2222",
"emails": [
"fulano@gmail.com",
"fulano@yahoo.com"
],
"twitter": "@fulano"
}
}
</code></pre></div></div>
<p>Claro que, dependendo da situação, nem sempre é desejável mapear o JSON para uma classe. Pode ser que sejam muitos dados e você não precise de todos, então não vale a pena mapear tudo, por exemplo. Neste caso, você pode usar um <code class="language-plaintext highlighter-rouge">java.util.Map</code>:</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nc">Map</span> <span class="n">json</span> <span class="o">=</span> <span class="n">gson</span><span class="o">.</span><span class="na">fromJson</span><span class="o">(</span><span class="n">texto</span><span class="o">,</span> <span class="nc">Map</span><span class="o">.</span><span class="na">class</span><span class="o">);</span>
</code></pre></div></div>
<p>Assim, o JSON vira um <code class="language-plaintext highlighter-rouge">Map</code>, sendo que as chaves são <code class="language-plaintext highlighter-rouge">Strings</code> e os valores podem ser <code class="language-plaintext highlighter-rouge">List</code>, <code class="language-plaintext highlighter-rouge">Map</code>, <code class="language-plaintext highlighter-rouge">String</code>, <code class="language-plaintext highlighter-rouge">Double</code>, etc, dependendo do que eles eram no JSON original. Para obter o telefone, por exemplo, você poderia fazer <code class="language-plaintext highlighter-rouge">((Map) json.get("contatos")).get("telefone")</code>. Não é muito “bonito”, mas se você só precisa deste campo, e o JSON é muito grande e complexo, talvez não valha a pena mapeá-lo para uma classe só para obter um único campo.</p>
<p>Além disso, a classe <code class="language-plaintext highlighter-rouge">Gson</code> possui métodos que trabalham com <code class="language-plaintext highlighter-rouge">java.io.Reader</code> e <code class="language-plaintext highlighter-rouge">java.io.Writer</code>, permitindo que o JSON seja lido e escrito de/para arquivos e quaisquer outros <em>streams</em> de dados diretamente.</p>
<p>Se o JSON for inválido, o método <code class="language-plaintext highlighter-rouge">fromJSON</code> lança um <code class="language-plaintext highlighter-rouge">com.google.gson.JsonSyntaxException</code>.</p>
<h3 id="c">C#</h3>
<p>Em C# existem várias APIs diferentes para manipular JSON, como você pode ver <a href="http://sagistech.blogspot.com/2010/03/parsing-twitter-json-comparing-c.html">neste comparativo</a>. Pelo que vi, são todas bem parecidas em termos de funcionamento básico. Segue abaixo um exemplo com o <a href="https://www.newtonsoft.com/json">Json.NET</a>:</p>
<div class="language-c# highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">using</span> <span class="nn">Newtonsoft.Json</span><span class="p">;</span>
<span class="k">using</span> <span class="nn">Newtonsoft.Json.Linq</span><span class="p">;</span>
<span class="kt">string</span> <span class="n">texto</span> <span class="p">=</span> <span class="s">"{ \"nome\": \"Fulano\", \"idade\": 90, \"filmes_preferidos\": [ \"Pulp Fiction\", \"Clube da Luta\" ],"</span>
<span class="p">+</span> <span class="s">" \"contatos\": { \"telefone\": \"(11) 91111-2222\", \"emails\": [ \"fulano@gmail.com\", \"fulano@yahoo.com\" ] } }"</span><span class="p">;</span>
<span class="kt">dynamic</span> <span class="n">dobj</span> <span class="p">=</span> <span class="n">JsonConvert</span><span class="p">.</span><span class="n">DeserializeObject</span><span class="p"><</span><span class="kt">dynamic</span><span class="p">>(</span><span class="n">texto</span><span class="p">);</span>
<span class="c1">// obter nome</span>
<span class="n">Console</span><span class="p">.</span><span class="nf">WriteLine</span><span class="p">(</span><span class="n">dobj</span><span class="p">[</span><span class="s">"nome"</span><span class="p">].</span><span class="nf">ToString</span><span class="p">());</span>
<span class="c1">// percorrer array de filmes</span>
<span class="k">foreach</span> <span class="p">(</span><span class="kt">string</span> <span class="n">filme</span> <span class="k">in</span> <span class="n">dobj</span><span class="p">[</span><span class="s">"filmes_preferidos"</span><span class="p">])</span>
<span class="p">{</span>
<span class="n">Console</span><span class="p">.</span><span class="nf">WriteLine</span><span class="p">(</span><span class="n">filme</span><span class="p">);</span>
<span class="p">}</span>
<span class="c1">// percorrer contatos</span>
<span class="k">foreach</span> <span class="p">(</span><span class="n">JProperty</span> <span class="n">c</span> <span class="k">in</span> <span class="n">dobj</span><span class="p">[</span><span class="s">"contatos"</span><span class="p">])</span>
<span class="p">{</span>
<span class="n">Console</span><span class="p">.</span><span class="nf">WriteLine</span><span class="p">(</span><span class="s">$"</span><span class="p">{</span><span class="n">c</span><span class="p">.</span><span class="n">Name</span><span class="p">}</span><span class="s"> = </span><span class="p">{</span><span class="n">c</span><span class="p">.</span><span class="n">Value</span><span class="p">}</span><span class="s">"</span><span class="p">);</span>
<span class="p">}</span>
<span class="c1">// incluir novo contato</span>
<span class="n">dobj</span><span class="p">[</span><span class="s">"contatos"</span><span class="p">][</span><span class="s">"twitter"</span><span class="p">]</span> <span class="p">=</span> <span class="s">"@fulano"</span><span class="p">;</span>
<span class="n">Console</span><span class="p">.</span><span class="nf">WriteLine</span><span class="p">(</span><span class="n">JsonConvert</span><span class="p">.</span><span class="nf">SerializeObject</span><span class="p">(</span><span class="n">dobj</span><span class="p">,</span> <span class="n">Formatting</span><span class="p">.</span><span class="n">Indented</span><span class="p">));</span>
</code></pre></div></div>
<p>Fazemos algo parecido com o código anterior em <a href="#java">Java</a>: imprimimos o nome, depois percorremos o array de filmes e por fim os contatos. Depois criamos um novo contato. A saída é:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Fulano
Pulp Fiction
Clube da Luta
telefone = (11) 91111-2222
emails = [
"fulano@gmail.com",
"fulano@yahoo.com"
]
{
"nome": "Fulano",
"idade": 90,
"filmes_preferidos": [
"Pulp Fiction",
"Clube da Luta"
],
"contatos": {
"telefone": "(11) 91111-2222",
"emails": [
"fulano@gmail.com",
"fulano@yahoo.com"
],
"twitter": "@fulano"
}
}
</code></pre></div></div>
<p>Se o JSON for inválido, é lançado um <code class="language-plaintext highlighter-rouge">Newtonsoft.Json.JsonReaderException</code>. Veja mais exemplos com outras bibliotecas <a href="https://pt.stackoverflow.com/q/706/112052">aqui</a>.</p>
<p>E a partir do .NET Core 3.0 é possível usar uma nova API, disponível no namespace <a href="https://learn.microsoft.com/en-us/dotnet/api/system.text.json?view=net-8.0&WT.mc_id=DOP-MVP-5002397"><code class="language-plaintext highlighter-rouge">System.Text.Json</code></a>.</p>
<h3 id="python">Python</h3>
<p>Em Python, você pode usar o <a href="https://docs.python.org/3.6/library/json.html">módulo <code class="language-plaintext highlighter-rouge">json</code></a>. A conversão de tipos do JSON de/para o Python é feita com base <a href="https://docs.python.org/3.6/library/json.html#json-to-py-table">nesta tabela</a>:</p>
<table>
<thead>
<tr>
<th style="text-align: left">JSON</th>
<th style="text-align: left">Python</th>
</tr>
</thead>
<tbody>
<tr>
<td style="text-align: left">object</td>
<td style="text-align: left">dict</td>
</tr>
<tr>
<td style="text-align: left">array</td>
<td style="text-align: left">list</td>
</tr>
<tr>
<td style="text-align: left">string</td>
<td style="text-align: left">str</td>
</tr>
<tr>
<td style="text-align: left">number (int)</td>
<td style="text-align: left">int</td>
</tr>
<tr>
<td style="text-align: left">number (real)</td>
<td style="text-align: left">float</td>
</tr>
<tr>
<td style="text-align: left">true</td>
<td style="text-align: left">True</td>
</tr>
<tr>
<td style="text-align: left">false</td>
<td style="text-align: left">False</td>
</tr>
<tr>
<td style="text-align: left">null</td>
<td style="text-align: left">None</td>
</tr>
</tbody>
</table>
<p>Portanto, objetos JSON serão transformados em <a href="https://docs.python.org/3.6/library/stdtypes.html#dict">dicionários</a>, e arrays serão transformados em <a href="https://docs.python.org/3.6/library/stdtypes.html#list">listas</a>. Com isso fica fácil manipular o JSON:</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">import</span> <span class="nn">json</span>
<span class="n">texto</span> <span class="o">=</span> <span class="s">"""
{
"nome": "Fulano",
"idade": 90,
"filmes_preferidos": [ "Pulp Fiction", "Clube da Luta" ],
"contatos": {
"telefone": "(11) 91111-2222",
"emails": [ "fulano@gmail.com", "fulano@yahoo.com" ]
}
}
"""</span>
<span class="n">obj</span> <span class="o">=</span> <span class="n">json</span><span class="p">.</span><span class="n">loads</span><span class="p">(</span><span class="n">texto</span><span class="p">)</span>
<span class="c1"># obter o nome
</span><span class="k">print</span><span class="p">(</span><span class="n">obj</span><span class="p">[</span><span class="s">'nome'</span><span class="p">])</span>
<span class="c1"># percorrer o array de filmes
</span><span class="k">for</span> <span class="n">filme</span> <span class="ow">in</span> <span class="n">obj</span><span class="p">[</span><span class="s">'filmes_preferidos'</span><span class="p">]:</span>
<span class="k">print</span><span class="p">(</span><span class="n">filme</span><span class="p">)</span>
<span class="c1"># percorrer as chaves e valores do objeto "contatos"
</span><span class="k">for</span> <span class="n">tipo</span><span class="p">,</span> <span class="n">contato</span> <span class="ow">in</span> <span class="n">obj</span><span class="p">[</span><span class="s">'contatos'</span><span class="p">].</span><span class="n">items</span><span class="p">():</span>
<span class="k">print</span><span class="p">(</span><span class="s">'{}={}'</span><span class="p">.</span><span class="nb">format</span><span class="p">(</span><span class="n">tipo</span><span class="p">,</span> <span class="n">contato</span><span class="p">))</span>
<span class="c1"># incluir um novo contato
</span><span class="n">obj</span><span class="p">[</span><span class="s">'contatos'</span><span class="p">][</span><span class="s">'twitter'</span><span class="p">]</span> <span class="o">=</span> <span class="s">'@fulano'</span>
<span class="c1"># imprimir o json (indentando com 2 espaços)
</span><span class="k">print</span><span class="p">(</span><span class="n">json</span><span class="p">.</span><span class="n">dumps</span><span class="p">(</span><span class="n">obj</span><span class="p">,</span> <span class="n">indent</span><span class="o">=</span><span class="mi">2</span><span class="p">))</span>
</code></pre></div></div>
<p>Para transformar uma string que contém um JSON nos objetos correspondentes do Python, basta usar o método <a href="https://docs.python.org/3/library/json.html#json.loads"><code class="language-plaintext highlighter-rouge">loads</code></a>. Como o JSON é um objeto, o retorno de <code class="language-plaintext highlighter-rouge">loads</code> é um dicionário, que podemos manipular normalmente, como qualquer outro dicionário do Python. O mesmo vale para os seus valores. A chave <code class="language-plaintext highlighter-rouge">"filmes_preferidos"</code>, por exemplo, contém um array, que o método <code class="language-plaintext highlighter-rouge">loads</code> transforma em uma lista. Já a chave <code class="language-plaintext highlighter-rouge">"contatos"</code> possui um objeto, e portanto ela se torna um dicionário. Para percorrê-los, usamos as formas tradicionais da linguagem, como faríamos com qualquer outra lista ou dicionário.</p>
<p>Para modificar o JSON, é a mesma coisa: basta manipular as listas e dicionários normalmente, adicionando, removendo ou mudando seus elementos. Por fim, podemos gerar uma string contendo todo o JSON, usando o método <a href="https://docs.python.org/3/library/json.html#json.dumps"><code class="language-plaintext highlighter-rouge">dumps</code></a>. Usei o parâmetro <code class="language-plaintext highlighter-rouge">indent=2</code> para que o JSON fique bem formatado, usando 2 espaços para indentação. A saída do código acima é:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Fulano
Pulp Fiction
Clube da Luta
telefone=(11) 91111-2222
emails=['fulano@gmail.com', 'fulano@yahoo.com']
{
"nome": "Fulano",
"idade": 90,
"filmes_preferidos": [
"Pulp Fiction",
"Clube da Luta"
],
"contatos": {
"telefone": "(11) 91111-2222",
"emails": [
"fulano@gmail.com",
"fulano@yahoo.com"
],
"twitter": "@fulano"
}
}
</code></pre></div></div>
<p>Existem ainda os métodos <a href="https://docs.python.org/3/library/json.html#json.load"><code class="language-plaintext highlighter-rouge">load</code></a> e <a href="https://docs.python.org/3/library/json.html#json.dump"><code class="language-plaintext highlighter-rouge">dump</code></a>, que em vez de ler e escrever o JSON de/para strings, faz a leitura e escrita em arquivos:</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1"># ler de um arquivo
</span><span class="k">with</span> <span class="nb">open</span><span class="p">(</span><span class="s">'entrada.json'</span><span class="p">,</span> <span class="s">'r'</span><span class="p">)</span> <span class="k">as</span> <span class="n">arq</span><span class="p">:</span>
<span class="n">obj</span> <span class="o">=</span> <span class="n">json</span><span class="p">.</span><span class="n">load</span><span class="p">(</span><span class="n">arq</span><span class="p">)</span>
<span class="c1"># manipular o obj ...
</span>
<span class="c1"># gravar obj em outro arquivo
</span><span class="k">with</span> <span class="nb">open</span><span class="p">(</span><span class="s">'saida.json'</span><span class="p">,</span> <span class="s">'w'</span><span class="p">)</span> <span class="k">as</span> <span class="n">arq</span><span class="p">:</span>
<span class="n">json</span><span class="p">.</span><span class="n">dump</span><span class="p">(</span><span class="n">obj</span><span class="p">,</span> <span class="n">arq</span><span class="p">,</span> <span class="n">indent</span><span class="o">=</span><span class="mi">2</span><span class="p">)</span>
</code></pre></div></div>
<p>Se o JSON for inválido, os métodos <code class="language-plaintext highlighter-rouge">loads</code> e <code class="language-plaintext highlighter-rouge">load</code> lançam um <a href="https://docs.python.org/3/library/json.html#json.JSONDecodeError"><code class="language-plaintext highlighter-rouge">JSONDecodeError</code></a>.</p>
<h3 id="php">PHP</h3>
<p>Em PHP, você pode usar a função <a href="https://www.php.net/manual/en/function.json-decode.php"><code class="language-plaintext highlighter-rouge">json_decode</code></a> para transformar uma string contendo um JSON no objeto correspondente. O primeiro parâmetro é a string contendo o JSON, e o segundo parâmetro (opcional) controla o tipo que será retornado. No exemplo abaixo eu passo <code class="language-plaintext highlighter-rouge">true</code>, indicando que o retorno será um array associativo:</p>
<div class="language-php highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$texto</span> <span class="o">=</span> <span class="sh"><<<JSON
{
"nome": "Fulano",
"idade": 90,
"filmes_preferidos": [ "Pulp Fiction", "Clube da Luta" ],
"contatos": {
"telefone": "(11) 91111-2222",
"emails": [ "fulano@gmail.com", "fulano@yahoo.com" ]
}
}
JSON;</span>
<span class="c1">// converte o texto para um array</span>
<span class="nv">$json</span> <span class="o">=</span> <span class="nb">json_decode</span><span class="p">(</span><span class="nv">$texto</span><span class="p">,</span> <span class="kc">true</span><span class="p">);</span>
<span class="c1">// obter o nome</span>
<span class="k">echo</span> <span class="nv">$json</span><span class="p">[</span><span class="s1">'nome'</span><span class="p">],</span> <span class="kc">PHP_EOL</span><span class="p">;</span>
<span class="c1">// percorrer o array de filmes</span>
<span class="k">foreach</span> <span class="p">(</span><span class="nv">$json</span><span class="p">[</span><span class="s1">'filmes_preferidos'</span><span class="p">]</span> <span class="k">as</span> <span class="nv">$filme</span><span class="p">)</span> <span class="p">{</span>
<span class="k">echo</span> <span class="nv">$filme</span><span class="p">,</span> <span class="kc">PHP_EOL</span><span class="p">;</span>
<span class="p">}</span>
<span class="c1"># percorrer os contatos</span>
<span class="k">foreach</span> <span class="p">(</span><span class="nv">$json</span><span class="p">[</span><span class="s1">'contatos'</span><span class="p">]</span> <span class="k">as</span> <span class="nv">$tipo</span> <span class="o">=></span> <span class="nv">$contato</span><span class="p">)</span> <span class="p">{</span>
<span class="k">echo</span> <span class="s2">"</span><span class="si">{</span><span class="nv">$tipo</span><span class="si">}</span><span class="s2"> = "</span><span class="mf">.</span> <span class="p">(</span><span class="nb">is_array</span><span class="p">(</span><span class="nv">$contato</span><span class="p">)</span> <span class="o">?</span> <span class="nb">implode</span><span class="p">(</span><span class="s2">", "</span><span class="p">,</span> <span class="nv">$contato</span><span class="p">)</span> <span class="o">:</span> <span class="nv">$contato</span><span class="p">),</span> <span class="kc">PHP_EOL</span><span class="p">;</span>
<span class="p">}</span>
<span class="c1">// adicionar novo contato</span>
<span class="nv">$json</span><span class="p">[</span><span class="s1">'contatos'</span><span class="p">][</span><span class="s1">'twitter'</span><span class="p">]</span> <span class="o">=</span> <span class="s1">'@fulano'</span><span class="p">;</span>
<span class="c1">// imprimir o JSON formatado</span>
<span class="k">echo</span> <span class="nb">json_encode</span><span class="p">(</span><span class="nv">$json</span><span class="p">,</span> <span class="no">JSON_PRETTY_PRINT</span><span class="p">);</span>
</code></pre></div></div>
<p>Com isso, cada chave do objeto JSON se torna uma chave no array associativo, e seus respectivos valores podem ser números, strings ou outros arrays. E para transformar esse array em uma string contendo todo o JSON, eu uso a função <a href="https://www.php.net/manual/en/function.json-encode.php"><code class="language-plaintext highlighter-rouge">json_encode</code></a>. E também uso a opção <code class="language-plaintext highlighter-rouge">JSON_PRETTY_PRINT</code> para que ele fique formatado. A saída é:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Fulano
Pulp Fiction
Clube da Luta
telefone = (11) 91111-2222
emails = fulano@gmail.com, fulano@yahoo.com
{
"nome": "Fulano",
"idade": 90,
"filmes_preferidos": [
"Pulp Fiction",
"Clube da Luta"
],
"contatos": {
"telefone": "(11) 91111-2222",
"emails": [
"fulano@gmail.com",
"fulano@yahoo.com"
],
"twitter": "@fulano"
}
}
</code></pre></div></div>
<p>Se eu fizer apenas <code class="language-plaintext highlighter-rouge">json_decode($texto)</code> (ou <code class="language-plaintext highlighter-rouge">json_decode($texto, false)</code>), o retorno será um objeto em vez de um array. Com isso, o modo de acessar os elementos muda um pouco:</p>
<div class="language-php highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// converte o texto para um objeto (e não mais um array)</span>
<span class="nv">$json</span> <span class="o">=</span> <span class="nb">json_decode</span><span class="p">(</span><span class="nv">$texto</span><span class="p">,</span> <span class="kc">false</span><span class="p">);</span>
<span class="c1">// obter o nome</span>
<span class="k">echo</span> <span class="nv">$json</span><span class="o">-></span><span class="n">nome</span><span class="p">,</span> <span class="kc">PHP_EOL</span><span class="p">;</span>
<span class="c1">// percorrer o array de filmes</span>
<span class="k">foreach</span> <span class="p">(</span><span class="nv">$json</span><span class="o">-></span><span class="n">filmes_preferidos</span> <span class="k">as</span> <span class="nv">$filme</span><span class="p">)</span> <span class="p">{</span>
<span class="k">echo</span> <span class="nv">$filme</span><span class="p">,</span> <span class="kc">PHP_EOL</span><span class="p">;</span>
<span class="p">}</span>
<span class="c1"># percorrer os contatos</span>
<span class="k">foreach</span> <span class="p">(</span><span class="nv">$json</span><span class="o">-></span><span class="n">contatos</span> <span class="k">as</span> <span class="nv">$tipo</span> <span class="o">=></span> <span class="nv">$contato</span><span class="p">)</span> <span class="p">{</span>
<span class="k">echo</span> <span class="s2">"</span><span class="si">{</span><span class="nv">$tipo</span><span class="si">}</span><span class="s2"> = "</span><span class="mf">.</span> <span class="p">(</span><span class="nb">is_array</span><span class="p">(</span><span class="nv">$contato</span><span class="p">)</span> <span class="o">?</span> <span class="nb">implode</span><span class="p">(</span><span class="s2">", "</span><span class="p">,</span> <span class="nv">$contato</span><span class="p">)</span> <span class="o">:</span> <span class="nv">$contato</span><span class="p">),</span> <span class="kc">PHP_EOL</span><span class="p">;</span>
<span class="p">}</span>
<span class="c1">// adicionar novo contato</span>
<span class="nv">$json</span><span class="o">-></span><span class="n">contatos</span><span class="o">-></span><span class="n">twitter</span> <span class="o">=</span> <span class="s1">'@fulano'</span><span class="p">;</span>
<span class="c1">// imprimir o JSON formatado</span>
<span class="k">echo</span> <span class="nb">json_encode</span><span class="p">(</span><span class="nv">$json</span><span class="p">,</span> <span class="no">JSON_PRETTY_PRINT</span><span class="p">);</span>
</code></pre></div></div>
<p>Se <code class="language-plaintext highlighter-rouge">$texto</code> contém um JSON inválido, <code class="language-plaintext highlighter-rouge">json_decode</code> retorna <code class="language-plaintext highlighter-rouge">NULL</code>.</p>
<h3 id="javascript">JavaScript</h3>
<p>Em JavaScript, existe o <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON">objeto <code class="language-plaintext highlighter-rouge">JSON</code></a>, que possui os métodos <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON/parse"><code class="language-plaintext highlighter-rouge">parse</code></a> (para transformar uma string contendo todo o JSON na estrutura correspondente do JavaScript - ou seja, um array, objeto, string, número, etc), e <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON/stringify"><code class="language-plaintext highlighter-rouge">stringify</code></a>, que faz o oposto (transforma uma variável do JavaScript em uma string no formato JSON).</p>
<p>No código abaixo estou usando <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Template_literals"><em>Template Strings</em></a>, por facilitar a criação de uma string com várias linhas. <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Template_literals#Browser_compatibility">Veja se seu browser é compatível com este recurso</a> (caso não seja, há uma alternativa abaixo, usando uma string tradicional).</p>
<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">let</span> <span class="nx">texto</span> <span class="o">=</span> <span class="s2">`{
"nome": "Fulano",
"idade": 90,
"filmes_preferidos": [ "Pulp Fiction", "Clube da Luta" ],
"contatos": {
"telefone": "(11) 91111-2222",
"emails": [ "fulano@gmail.com", "fulano@yahoo.com" ]
}
}`</span><span class="p">;</span>
<span class="c1">// se o seu browser não suporta a template string acima, use a linha abaixo</span>
<span class="c1">//let texto = '{ "nome": "Fulano", "idade": 90, "filmes_preferidos": [ "Pulp Fiction", "Clube da Luta" ], "contatos": { "telefone": "(11) 91111-2222", "emails": [ "fulano@gmail.com", "fulano@yahoo.com" ] } }';</span>
<span class="kd">let</span> <span class="nx">json</span> <span class="o">=</span> <span class="nx">JSON</span><span class="p">.</span><span class="nx">parse</span><span class="p">(</span><span class="nx">texto</span><span class="p">);</span>
<span class="c1">// imprimir nome</span>
<span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="nx">json</span><span class="p">.</span><span class="nx">nome</span><span class="p">);</span>
<span class="c1">// imprimir filmes</span>
<span class="nx">json</span><span class="p">.</span><span class="nx">filmes_preferidos</span><span class="p">.</span><span class="nx">forEach</span><span class="p">(</span><span class="nx">filme</span> <span class="o">=></span> <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="nx">filme</span><span class="p">));</span>
<span class="c1">// imprimir contatos</span>
<span class="k">for</span> <span class="p">(</span><span class="kd">let</span> <span class="p">[</span><span class="nx">tipo</span><span class="p">,</span> <span class="nx">contato</span><span class="p">]</span> <span class="k">of</span> <span class="nb">Object</span><span class="p">.</span><span class="nx">entries</span><span class="p">(</span><span class="nx">json</span><span class="p">.</span><span class="nx">contatos</span><span class="p">))</span> <span class="p">{</span>
<span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="nx">tipo</span><span class="p">,</span> <span class="dl">'</span><span class="s1">=</span><span class="dl">'</span><span class="p">,</span> <span class="nb">Array</span><span class="p">.</span><span class="nx">isArray</span><span class="p">(</span><span class="nx">contato</span><span class="p">)</span> <span class="p">?</span> <span class="nx">contato</span><span class="p">.</span><span class="nx">join</span><span class="p">()</span> <span class="p">:</span> <span class="nx">contato</span><span class="p">);</span>
<span class="p">}</span>
<span class="c1">// adicionar novo contato</span>
<span class="nx">json</span><span class="p">.</span><span class="nx">contatos</span><span class="p">.</span><span class="nx">twitter</span> <span class="o">=</span> <span class="dl">'</span><span class="s1">@fulano</span><span class="dl">'</span><span class="p">;</span>
<span class="c1">// transformar em string (indentado com 2 espaços)</span>
<span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="nx">JSON</span><span class="p">.</span><span class="nx">stringify</span><span class="p">(</span><span class="nx">json</span><span class="p">,</span> <span class="kc">null</span><span class="p">,</span> <span class="mi">2</span><span class="p">));</span>
</code></pre></div></div>
<p>Um objeto JSON se torna um objeto do JavaScript, e podemos acessar suas chaves diretamente (como fizemos com <code class="language-plaintext highlighter-rouge">json.nome</code> e <code class="language-plaintext highlighter-rouge">json.contatos</code>, por exemplo). Já um array JSON se torna um array do JavaScript, com todos os métodos que este possui, como o <code class="language-plaintext highlighter-rouge">forEach</code> utilizado acima.</p>
<p>Por fim, <code class="language-plaintext highlighter-rouge">JSON.stringify</code> possui uma opção para formatar a saída, bastando passar a quantidade de espaços usados para indentação. A saída é:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Fulano
Pulp Fiction
Clube da Luta
telefone = (11) 91111-2222
emails = fulano@gmail.com,fulano@yahoo.com
{
"nome": "Fulano",
"idade": 90,
"filmes_preferidos": [
"Pulp Fiction",
"Clube da Luta"
],
"contatos": {
"telefone": "(11) 91111-2222",
"emails": [
"fulano@gmail.com",
"fulano@yahoo.com"
],
"twitter": "@fulano"
}
}
</code></pre></div></div>
<p>Se o JSON for inválido, <code class="language-plaintext highlighter-rouge">JSON.parse</code> lança um <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/SyntaxError"><code class="language-plaintext highlighter-rouge">SyntaxError</code></a>.</p>
<h3 id="regex">Regex</h3>
<p>Sendo bem sincero: <a href="https://pt.stackoverflow.com/a/357086/112052">não use expressões regulares (regex) para ler e manipular um JSON</a>. Não que seja impossível, mas antes avalie se você vai querer usar uma regex <a href="https://stackoverflow.com/a/3845829">como essa</a>:</p>
<div class="language-php highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$pcre_regex</span> <span class="o">=</span> <span class="s1">'
/
(?(DEFINE)
(?<number> -? (?= [1-9]|0(?!\d) ) \d+ (\.\d+)? ([eE] [+-]? \d+)? )
(?<boolean> true | false | null )
(?<string> " ([^"\\\\]* | \\\\ ["\\\\bfnrt\/] | \\\\ u [0-9a-f]{4} )* " )
(?<array> \[ (?: (?&json) (?: , (?&json) )* )? \s* \] )
(?<pair> \s* (?&string) \s* : (?&json) )
(?<object> \{ (?: (?&pair) (?: , (?&pair) )* )? \s* \} )
(?<json> \s* (?: (?&number) | (?&boolean) | (?&string) | (?&array) | (?&object) ) \s* )
)
\A (?&json) \Z
/six
'</span><span class="p">;</span>
</code></pre></div></div>
<p>Esta regex funciona com PHP. A opção <code class="language-plaintext highlighter-rouge">x</code> habilita o modo <em>extended</em>, no qual espaços e quebras de linha são ignorados pela regex, assim ela pode ser escrita de uma maneira mais “amigável” (não que ela seja fácil, mas imagine se estivesse tudo na mesma linha e sem espaços).</p>
<p>Em outras linguagens não é garantido que funcione, já que nem todas as <em>engines</em> possuem todos os recursos, e até mesmo a sintaxe de algumas estruturas pode ser diferente. Em Python é possível usá-la com o <a href="https://pypi.org/project/regex/">módulo <code class="language-plaintext highlighter-rouge">regex</code> (disponível no PyPI)</a>, mas o <a href="https://docs.python.org/3/library/re.html">módulo <code class="language-plaintext highlighter-rouge">re</code></a> nativo não suporta esta regex. Java e JavaScript também não possuem suporte a todos os recursos usados nesta expressão.</p>
<p>O ponto crucial desta regex são as <a href="https://www.rexegg.com/regex-disambiguation.html#define">sub-rotinas</a>, que nem todas as linguagens possuem (são os trechos com <code class="language-plaintext highlighter-rouge">(?&</code>). Outra limitação é que <strong>a regex acima só verifica se o JSON é válido, mas não extrai os seus valores</strong>.</p>
<p>Para obter uma estrutura que possui os valores devidamente separados e organizados (seja em um map, array, dicionário ou objeto), você terá que usar um <em>parser</em> específico, que a maioria das linguagens possuem. E os <em>parsers</em> que vimos acima já verificam se o JSON é válido, então para que usar a regex?</p>
<hr />
<p>Com isso, acredito que agora você já está mais preparado para ler e manipular um JSON. Não é tão difícil: o grande problema - que pelo menos eu vejo - é que as pessoas não param para analisar a estrutura e já saem escrevendo código sem pensar. Se você olha a estrutura antes, consegue ver como percorrê-la, um nível de cada vez, até chegar na informação que você precisa.</p>
<div class="footnotes" role="doc-endnotes">
<ol>
<li id="fn:array" role="doc-endnote">
<p>Se a linguagem que você está usando para ler o JSON não permite que existam elementos de tipos diferentes em um mesmo array, aí é outra história… <a href="#fnref:array" class="reversefootnote" role="doc-backlink">↩</a></p>
</li>
</ol>
</div>Hugo KotsuboJSON significa “JavaScript Object Notation” (Notação de Objetos JavaScript). Apesar de ter “JavaScript” no nome, ele pode ser usado em outras linguagens, pois o JSON é na verdade um formato para representar dados. A sua sintaxe foi inspirada no JavaScript - daí o nome - mas hoje em dia várias linguagens possuem algum suporte para ele, seja nativo ou via bibliotecas. Dentro de um JSON podemos ter vários tipos de dados diferentes. Para começar, temos os tipos “básicos”: strings: quaisquer caracteres entre aspas duplas. Ex: "sou uma string". Também são aceitas aquelas sequências de escape comuns em várias linguagens, como \n para representar a quebra de linha, \t para TAB, etc. números: pode ser um valor inteiro positivo (42), negativo (-10), com casas decimais (12.34 - e repare que o ponto - e não a vírgula - é o separador das casas decimais) ou em notação científica (2.53962e15) valores booleanos (true e false) e o valor nulo (null) Além destes, podemos ter também arrays e objetos. Array Um array é uma lista ordenada de valores, dentro de colchetes ([ e ]) e separados por vírgulas. Exemplo: [ 1, "segundo elemento", 3.4 ] Este array tem 3 elementos: o primeiro é o número 1, o segundo é a string "segundo elemento" e o terceiro é o número 3.4. Veja que ele começa com [ e termina com ], e os elementos são separados por vírgulas. Um detalhe importante é que um array não precisa ter necessariamente todos os elementos do mesmo tipo1. Além disso, em um array, a ordem é importante. Então [ 1, "segundo elemento", 3.4 ] não é o mesmo que [ 1, 3.4, "segundo elemento" ]. Objeto Um objeto é um conjunto de pares “chave: valor”, dentro de chaves ({ e }) e separados por vírgulas. A chave sempre é uma string, e o valor pode ser qualquer tipo válido. Exemplo: { "nome": "Fulano", "idade": 90 } Este objeto possui duas chaves: "nome", cujo valor é a string "Fulano" "idade", cujo valor é o número 90 Repare que ele começa com { e termina com }. Entre uma chave e seu respectivo valor, sempre tem o caractere :, e entre cada par “chave: valor” há uma vírgula. Um detalhe importante é que em um objeto a ordem dos pares “chave: valor” não importa. Ou seja, { "nome": "Fulano", "idade": 90 } e { "idade": 90, "nome": "Fulano" } são exatamente o mesmo objeto. Dentro de um array ou objeto, os valores podem ser qualquer um dos tipos acima. Ou seja, podemos ter não só os tipos básicos, mas também outros arrays e objetos. Então podemos ter coisas como: { "nome": "Fulano", "idade": 90, "filmes_preferidos": [ "Pulp Fiction", "Clube da Luta" ], "contatos": { "telefone": "(11) 91111-2222", "emails": [ "fulano@gmail.com", "fulano@yahoo.com" ] } } O JSON acima é um objeto, pois está delimitado por { e }. Dentro dele temos 4 chaves: "nome", cujo valor é a string "Fulano" "idade", cujo valor é o número 90 "filmes_preferidos", cujo valor é um array (pois está delimitado por colchetes: [ e ]) este array, por sua vez, possui dois elementos: as strings "Pulp Fiction" e "Clube da Luta" "contatos", cujo valor é outro objeto (pois este também está delimitado por { e }) este objeto, por sua vez, possui duas chaves: "telefone": cujo valor é a string "(11) 91111-2222" "emails": cujo valor é um array (pois está delimitado por colchetes) este array, por sua vez, possui dois elementos: as strings "fulano@gmail.com" e "fulano@yahoo.com" Outro exemplo: [ { "nome": "Fulano", "idade": 90 }, { "nome": "Ciclano", "idade": 10, "email": "ciclano@gmail.com" } ] O JSON acima é um array, pois está delimitado por [ e ]. Ele contém 2 elementos: o primeiro elemento é um objeto (pois está delimitado por { e }), que por sua vez possui duas chaves: "nome", cujo valor é a string "Fulano" "idade", cujo valor é o número 90 o segundo elemento também é um objeto (pois está delimitado por { e }), que por sua vez possui três chaves: "nome", cujo valor é a string "Ciclano" "idade", cujo valor é o número 10 "email", cujo valor é a string "ciclano@gmail.com" Repare que depois do fechamento do primeiro objeto (ou seja, depois do }) há uma vírgula, que é o separador de elementos de um array. Da mesma forma que um array de números teria a vírgula entre cada número (como por exemplo [ 1, 2, 3 ]), um array de objetos também deve ter a vírgula separando-os. Regra básica para ler um JSON A regra básica é: antes de sair escrevendo código, pare e olhe para o JSON. Veja se ele é um objeto ou array (que é o que a grande maioria das APIs REST retorna hoje em dia): se começa com { é um objeto, se começa com [ é um array. Depois veja o que tem dentro desse objeto ou array. Se for um array, procure pelas vírgulas que separam os elementos e verifique os seus tipos, e preocupe-se com a ordem em que eles estão, para saber como obter cada um. Se for um objeto, veja quais são as chaves e os respectivos valores. Se o JSON é muito grande e/ou com muitas estruturas aninhadas (objetos dentro de arrays, dentro outros arrays, dentro de outros objetos, etc), formatá-lo ajuda a visualizar a sua estrutura. Há vários sites como esse, que validam e formatam um JSON. Além disso, as linguagens/APIs que trabalham com JSON geralmente possuem uma opção para formatá-lo (veremos alguns exemplos mais abaixo). Outro ponto importante é verificar se o JSON que você está tentando ler é de fato um JSON válido. Um exemplo clássico é: { "nome": "Fulano", "idade": 90 }, { "nome": "Ciclano", "idade": 10 } Inicialmente você pode pensar que, como começa com {, então é um objeto. Mas repare bem: o primeiro objeto (o que tem "nome": "Fulano") termina na quarta linha (onde há o }), e logo em seguida há uma vírgula. Logo depois, temos outro objeto (o que tem "nome": "Ciclano"). Este não é um JSON válido. Há dois objetos “soltos”, separados por vírgula. Cada um deles individualmente é um JSON válido, mas ter ambos separados por vírgula não é. O que poderia deixar esta estrutura válida é colocá-la entre [ e ]: [ { "nome": "Fulano", "idade": 90 }, { "nome": "Ciclano", "idade": 10 } ] Pois agora ela se tornou um array com dois objetos. Mas da forma que estava (sem o [ e ]), a estrutura é inválida, e muitas APIs dão erro ao tentar processá-la (ou ignoram o segundo objeto, varia conforme a implementação). Depois de ler o JSON e verificar se ele é um array ou objeto, você pode acessar seus elementos. Cada linguagem fornece seu mecanismo para tal, e abaixo coloquei exemplos em algumas linguagens. Antes de prosseguir, um detalhe importante: JSON é um formato para troca de dados, ou seja, para informações que serão mandadas “para lá e para cá”. E no fim, tudo isso vira texto (um monte de caracteres que você vai ter que interpretar). O que quer dizer que todos os exemplos de JSON que vimos até agora são na verdade strings (ou o resultado retornado por uma API, ou um arquivo). Se eu consulto uma API e ela retorna { "nome": "Fulano", "idade": 90 }, o que eu terei é uma string que contém o caractere {, depois o espaço, depois a aspas, depois a letra n e assim por diante. O que as APIs de JSON fazem é transformar esta string em alguma estrutura similar na linguagem em questão (geralmente arrays/listas e maps/dicionários/objects, além dos tipos numéricos e strings da própria linguagem). Sempre veja a documentação para saber como é feito o mapeamento entre tipos. Agora sim, vamos ao código. Para todos os exemplos abaixo, usarei este JSON: { "nome": "Fulano", "idade": 90, "filmes_preferidos": [ "Pulp Fiction", "Clube da Luta" ], "contatos": { "telefone": "(11) 91111-2222", "emails": [ "fulano@gmail.com", "fulano@yahoo.com" ] } } Se quiser, pode usar os links abaixo para ir direto para a sua linguagem de sua preferência: Java C# Python PHP JavaScript Regex Mas já adianto que o exemplo em Java é um pouquinho mais completo que os demais, por ser a linguagem com a qual tenho mais familiaridade. Java No Java existem várias APIs disponíveis, como o pacote org.json e a biblioteca Gson do Google. Todas são bem parecidas, possuindo classes para representar objetos e arrays. Um exemplo com org.json: import org.json.JSONArray; import org.json.JSONObject; String texto = "{ \"nome\": \"Fulano\", \"idade\": 90, \"filmes_preferidos\": [ \"Pulp Fiction\", \"Clube da Luta\" ]," + " \"contatos\": { \"telefone\": \"(11) 91111-2222\", \"emails\": [ \"fulano@gmail.com\", \"fulano@yahoo.com\" ] } }"; // cria o JSONObject JSONObject obj = new JSONObject(texto); // verifica se possui a chave "nome" if (obj.has("nome")) { System.out.println("possui nome=" + obj.getString("nome")); // nome é uma String } else { System.out.println("não possui nome"); } // obtém o array de filmes JSONArray filmes = obj.getJSONArray("filmes_preferidos"); filmes.forEach(System.out::println); // imprime todos os filmes // obtém o objeto com os contatos JSONObject contatos = obj.getJSONObject("contatos"); for (String chave : contatos.keySet()) { System.out.println(chave + "=" + contatos.get(chave)); } //---------------------------------------------------------- // adicionar mais um contato contatos.put("twitter", "@fulano"); // imprime o JSON formatado (indentando com 2 espaços) System.out.println(obj.toString(2)); A primeira parte pega uma String contendo todo o JSON e transforma em um JSONObject. Em seguida eu uso o método has para verificar se o objeto possui determinada chave e o método getString para obter o valor de uma chave como uma String. Depois eu uso getJSONArray para obter o array correspondente à chave "filmes_preferidos" e percorro o mesmo, imprimindo cada um dos seus valores. Também faço algo similar com a chave "contatos", obtendo o objeto correspondente (com getJSONObject) e percorrendo todas suas chaves e valores. Até este ponto, a saída é: possui nome=Fulano Pulp Fiction Clube da Luta emails=["fulano@gmail.com","fulano@yahoo.com"] telefone=(11) 91111-2222 Repare como os elementos do array ("Pulp Fiction" e "Clube da Luta") mantêm a sua ordem, já que em um array a ordem dos elementos é importante. Já as chaves do objeto “contatos” aparecem fora de ordem (na String original, "telefone" estava antes de "emails", mas ao percorrer o objeto, elas são retornadas em outra ordem), pois em um objeto não importa a ordem das chaves - e a ordem mostrada vai depender dos detalhes internos de implementação da API. Mas note que a chave "emails" possui como valor um array com os endereços de email, e dentro deste array, a ordem dos elementos é preservada. Por fim, eu uso o método put para adicionar mais um par “chave: valor” ao objeto "contatos", e uso o método toString(), passando como parâmetro a quantidade de espaços usada para formatar o JSON (no caso, usei 2 espaços). A saída é: { "idade": 90, "filmes_preferidos": [ "Pulp Fiction", "Clube da Luta" ], "contatos": { "emails": [ "fulano@gmail.com", "fulano@yahoo.com" ], "telefone": "(11) 91111-2222", "twitter": "@fulano" }, "nome": "Fulano" } Repare novamente que nos arrays a ordem dos elementos é mantida, mas nas chaves dos objetos, não necessariamente é a mesma. Lembre-se que em um objeto JSON, a ordem das chaves não importa, e portanto nenhuma implementação é obrigada a manter a mesma ordem. Se a variável texto tivesse um array em vez de um objeto, bastaria você usar new JSONArray(texto). Caso o JSON seja inválido, os construtores de JSONObject e JSONArray lançam um org.json.JSONException. Com a API Gson, podemos ir além e mapear o JSON para um objeto específico. Para o nosso JSON, podemos criar uma classe Pessoa, por exemplo: import java.util.List; import java.util.Map; import com.google.gson.annotations.SerializedName; public class Pessoa { private String nome; private int idade; @SerializedName("filmes_preferidos") private List<String> filmesPreferidos; private Map<String, Object> contatos; // construtores, getters, setters, etc... } Com isso, podemos ler o JSON e criar uma instância de Pessoa com os campos já preenchidos: import com.google.gson.Gson; import com.google.gson.GsonBuilder; String texto = "{ \"nome\": \"Fulano\", \"idade\": 90, \"filmes_preferidos\": [ \"Pulp Fiction\", \"Clube da Luta\" ]," + " \"contatos\": { \"telefone\": \"(11) 91111-2222\", \"emails\": [ \"fulano@gmail.com\", \"fulano@yahoo.com\" ] } }"; Gson gson = new GsonBuilder().setPrettyPrinting().create(); // lê o JSON e cria uma Pessoa com os campos preenchidos Pessoa pessoa = gson.fromJson(texto, Pessoa.class); System.out.println("nome=" + pessoa.getNome()); for (String filme : pessoa.getFilmesPreferidos()) { System.out.println(filme); } for (Map.Entry<String, Object> contato : pessoa.getContatos().entrySet()) { System.out.println(contato.getKey() + "=" + contato.getValue()); } // adicionar um novo contato pessoa.getContatos().put("twitter", "@fulano"); System.out.println(gson.toJson(pessoa)); Com isso fica bem mais fácil obter os campos do JSON e adicionar novas informações ao mesmo. A saída é: nome=Fulano Pulp Fiction Clube da Luta telefone=(11) 91111-2222 emails=[fulano@gmail.com, fulano@yahoo.com] { "nome": "Fulano", "idade": 90, "filmes_preferidos": [ "Pulp Fiction", "Clube da Luta" ], "contatos": { "telefone": "(11) 91111-2222", "emails": [ "fulano@gmail.com", "fulano@yahoo.com" ], "twitter": "@fulano" } } Claro que, dependendo da situação, nem sempre é desejável mapear o JSON para uma classe. Pode ser que sejam muitos dados e você não precise de todos, então não vale a pena mapear tudo, por exemplo. Neste caso, você pode usar um java.util.Map: Map json = gson.fromJson(texto, Map.class); Assim, o JSON vira um Map, sendo que as chaves são Strings e os valores podem ser List, Map, String, Double, etc, dependendo do que eles eram no JSON original. Para obter o telefone, por exemplo, você poderia fazer ((Map) json.get("contatos")).get("telefone"). Não é muito “bonito”, mas se você só precisa deste campo, e o JSON é muito grande e complexo, talvez não valha a pena mapeá-lo para uma classe só para obter um único campo. Além disso, a classe Gson possui métodos que trabalham com java.io.Reader e java.io.Writer, permitindo que o JSON seja lido e escrito de/para arquivos e quaisquer outros streams de dados diretamente. Se o JSON for inválido, o método fromJSON lança um com.google.gson.JsonSyntaxException. C# Em C# existem várias APIs diferentes para manipular JSON, como você pode ver neste comparativo. Pelo que vi, são todas bem parecidas em termos de funcionamento básico. Segue abaixo um exemplo com o Json.NET: using Newtonsoft.Json; using Newtonsoft.Json.Linq; string texto = "{ \"nome\": \"Fulano\", \"idade\": 90, \"filmes_preferidos\": [ \"Pulp Fiction\", \"Clube da Luta\" ]," + " \"contatos\": { \"telefone\": \"(11) 91111-2222\", \"emails\": [ \"fulano@gmail.com\", \"fulano@yahoo.com\" ] } }"; dynamic dobj = JsonConvert.DeserializeObject<dynamic>(texto); // obter nome Console.WriteLine(dobj["nome"].ToString()); // percorrer array de filmes foreach (string filme in dobj["filmes_preferidos"]) { Console.WriteLine(filme); } // percorrer contatos foreach (JProperty c in dobj["contatos"]) { Console.WriteLine($"{c.Name} = {c.Value}"); } // incluir novo contato dobj["contatos"]["twitter"] = "@fulano"; Console.WriteLine(JsonConvert.SerializeObject(dobj, Formatting.Indented)); Fazemos algo parecido com o código anterior em Java: imprimimos o nome, depois percorremos o array de filmes e por fim os contatos. Depois criamos um novo contato. A saída é: Fulano Pulp Fiction Clube da Luta telefone = (11) 91111-2222 emails = [ "fulano@gmail.com", "fulano@yahoo.com" ] { "nome": "Fulano", "idade": 90, "filmes_preferidos": [ "Pulp Fiction", "Clube da Luta" ], "contatos": { "telefone": "(11) 91111-2222", "emails": [ "fulano@gmail.com", "fulano@yahoo.com" ], "twitter": "@fulano" } } Se o JSON for inválido, é lançado um Newtonsoft.Json.JsonReaderException. Veja mais exemplos com outras bibliotecas aqui. E a partir do .NET Core 3.0 é possível usar uma nova API, disponível no namespace System.Text.Json. Python Em Python, você pode usar o módulo json. A conversão de tipos do JSON de/para o Python é feita com base nesta tabela: JSON Python object dict array list string str number (int) int number (real) float true True false False null None Portanto, objetos JSON serão transformados em dicionários, e arrays serão transformados em listas. Com isso fica fácil manipular o JSON: import json texto = """ { "nome": "Fulano", "idade": 90, "filmes_preferidos": [ "Pulp Fiction", "Clube da Luta" ], "contatos": { "telefone": "(11) 91111-2222", "emails": [ "fulano@gmail.com", "fulano@yahoo.com" ] } } """ obj = json.loads(texto) # obter o nome print(obj['nome']) # percorrer o array de filmes for filme in obj['filmes_preferidos']: print(filme) # percorrer as chaves e valores do objeto "contatos" for tipo, contato in obj['contatos'].items(): print('{}={}'.format(tipo, contato)) # incluir um novo contato obj['contatos']['twitter'] = '@fulano' # imprimir o json (indentando com 2 espaços) print(json.dumps(obj, indent=2)) Para transformar uma string que contém um JSON nos objetos correspondentes do Python, basta usar o método loads. Como o JSON é um objeto, o retorno de loads é um dicionário, que podemos manipular normalmente, como qualquer outro dicionário do Python. O mesmo vale para os seus valores. A chave "filmes_preferidos", por exemplo, contém um array, que o método loads transforma em uma lista. Já a chave "contatos" possui um objeto, e portanto ela se torna um dicionário. Para percorrê-los, usamos as formas tradicionais da linguagem, como faríamos com qualquer outra lista ou dicionário. Para modificar o JSON, é a mesma coisa: basta manipular as listas e dicionários normalmente, adicionando, removendo ou mudando seus elementos. Por fim, podemos gerar uma string contendo todo o JSON, usando o método dumps. Usei o parâmetro indent=2 para que o JSON fique bem formatado, usando 2 espaços para indentação. A saída do código acima é: Fulano Pulp Fiction Clube da Luta telefone=(11) 91111-2222 emails=['fulano@gmail.com', 'fulano@yahoo.com'] { "nome": "Fulano", "idade": 90, "filmes_preferidos": [ "Pulp Fiction", "Clube da Luta" ], "contatos": { "telefone": "(11) 91111-2222", "emails": [ "fulano@gmail.com", "fulano@yahoo.com" ], "twitter": "@fulano" } } Existem ainda os métodos load e dump, que em vez de ler e escrever o JSON de/para strings, faz a leitura e escrita em arquivos: # ler de um arquivo with open('entrada.json', 'r') as arq: obj = json.load(arq) # manipular o obj ... # gravar obj em outro arquivo with open('saida.json', 'w') as arq: json.dump(obj, arq, indent=2) Se o JSON for inválido, os métodos loads e load lançam um JSONDecodeError. PHP Em PHP, você pode usar a função json_decode para transformar uma string contendo um JSON no objeto correspondente. O primeiro parâmetro é a string contendo o JSON, e o segundo parâmetro (opcional) controla o tipo que será retornado. No exemplo abaixo eu passo true, indicando que o retorno será um array associativo: $texto = <<<JSON { "nome": "Fulano", "idade": 90, "filmes_preferidos": [ "Pulp Fiction", "Clube da Luta" ], "contatos": { "telefone": "(11) 91111-2222", "emails": [ "fulano@gmail.com", "fulano@yahoo.com" ] } } JSON; // converte o texto para um array $json = json_decode($texto, true); // obter o nome echo $json['nome'], PHP_EOL; // percorrer o array de filmes foreach ($json['filmes_preferidos'] as $filme) { echo $filme, PHP_EOL; } # percorrer os contatos foreach ($json['contatos'] as $tipo => $contato) { echo "{$tipo} = ". (is_array($contato) ? implode(", ", $contato) : $contato), PHP_EOL; } // adicionar novo contato $json['contatos']['twitter'] = '@fulano'; // imprimir o JSON formatado echo json_encode($json, JSON_PRETTY_PRINT); Com isso, cada chave do objeto JSON se torna uma chave no array associativo, e seus respectivos valores podem ser números, strings ou outros arrays. E para transformar esse array em uma string contendo todo o JSON, eu uso a função json_encode. E também uso a opção JSON_PRETTY_PRINT para que ele fique formatado. A saída é: Fulano Pulp Fiction Clube da Luta telefone = (11) 91111-2222 emails = fulano@gmail.com, fulano@yahoo.com { "nome": "Fulano", "idade": 90, "filmes_preferidos": [ "Pulp Fiction", "Clube da Luta" ], "contatos": { "telefone": "(11) 91111-2222", "emails": [ "fulano@gmail.com", "fulano@yahoo.com" ], "twitter": "@fulano" } } Se eu fizer apenas json_decode($texto) (ou json_decode($texto, false)), o retorno será um objeto em vez de um array. Com isso, o modo de acessar os elementos muda um pouco: // converte o texto para um objeto (e não mais um array) $json = json_decode($texto, false); // obter o nome echo $json->nome, PHP_EOL; // percorrer o array de filmes foreach ($json->filmes_preferidos as $filme) { echo $filme, PHP_EOL; } # percorrer os contatos foreach ($json->contatos as $tipo => $contato) { echo "{$tipo} = ". (is_array($contato) ? implode(", ", $contato) : $contato), PHP_EOL; } // adicionar novo contato $json->contatos->twitter = '@fulano'; // imprimir o JSON formatado echo json_encode($json, JSON_PRETTY_PRINT); Se $texto contém um JSON inválido, json_decode retorna NULL. JavaScript Em JavaScript, existe o objeto JSON, que possui os métodos parse (para transformar uma string contendo todo o JSON na estrutura correspondente do JavaScript - ou seja, um array, objeto, string, número, etc), e stringify, que faz o oposto (transforma uma variável do JavaScript em uma string no formato JSON). No código abaixo estou usando Template Strings, por facilitar a criação de uma string com várias linhas. Veja se seu browser é compatível com este recurso (caso não seja, há uma alternativa abaixo, usando uma string tradicional). let texto = `{ "nome": "Fulano", "idade": 90, "filmes_preferidos": [ "Pulp Fiction", "Clube da Luta" ], "contatos": { "telefone": "(11) 91111-2222", "emails": [ "fulano@gmail.com", "fulano@yahoo.com" ] } }`; // se o seu browser não suporta a template string acima, use a linha abaixo //let texto = '{ "nome": "Fulano", "idade": 90, "filmes_preferidos": [ "Pulp Fiction", "Clube da Luta" ], "contatos": { "telefone": "(11) 91111-2222", "emails": [ "fulano@gmail.com", "fulano@yahoo.com" ] } }'; let json = JSON.parse(texto); // imprimir nome console.log(json.nome); // imprimir filmes json.filmes_preferidos.forEach(filme => console.log(filme)); // imprimir contatos for (let [tipo, contato] of Object.entries(json.contatos)) { console.log(tipo, '=', Array.isArray(contato) ? contato.join() : contato); } // adicionar novo contato json.contatos.twitter = '@fulano'; // transformar em string (indentado com 2 espaços) console.log(JSON.stringify(json, null, 2)); Um objeto JSON se torna um objeto do JavaScript, e podemos acessar suas chaves diretamente (como fizemos com json.nome e json.contatos, por exemplo). Já um array JSON se torna um array do JavaScript, com todos os métodos que este possui, como o forEach utilizado acima. Por fim, JSON.stringify possui uma opção para formatar a saída, bastando passar a quantidade de espaços usados para indentação. A saída é: Fulano Pulp Fiction Clube da Luta telefone = (11) 91111-2222 emails = fulano@gmail.com,fulano@yahoo.com { "nome": "Fulano", "idade": 90, "filmes_preferidos": [ "Pulp Fiction", "Clube da Luta" ], "contatos": { "telefone": "(11) 91111-2222", "emails": [ "fulano@gmail.com", "fulano@yahoo.com" ], "twitter": "@fulano" } } Se o JSON for inválido, JSON.parse lança um SyntaxError. Regex Sendo bem sincero: não use expressões regulares (regex) para ler e manipular um JSON. Não que seja impossível, mas antes avalie se você vai querer usar uma regex como essa: $pcre_regex = ' / (?(DEFINE) (?<number> -? (?= [1-9]|0(?!\d) ) \d+ (\.\d+)? ([eE] [+-]? \d+)? ) (?<boolean> true | false | null ) (?<string> " ([^"\\\\]* | \\\\ ["\\\\bfnrt\/] | \\\\ u [0-9a-f]{4} )* " ) (?<array> \[ (?: (?&json) (?: , (?&json) )* )? \s* \] ) (?<pair> \s* (?&string) \s* : (?&json) ) (?<object> \{ (?: (?&pair) (?: , (?&pair) )* )? \s* \} ) (?<json> \s* (?: (?&number) | (?&boolean) | (?&string) | (?&array) | (?&object) ) \s* ) ) \A (?&json) \Z /six '; Esta regex funciona com PHP. A opção x habilita o modo extended, no qual espaços e quebras de linha são ignorados pela regex, assim ela pode ser escrita de uma maneira mais “amigável” (não que ela seja fácil, mas imagine se estivesse tudo na mesma linha e sem espaços). Em outras linguagens não é garantido que funcione, já que nem todas as engines possuem todos os recursos, e até mesmo a sintaxe de algumas estruturas pode ser diferente. Em Python é possível usá-la com o módulo regex (disponível no PyPI), mas o módulo re nativo não suporta esta regex. Java e JavaScript também não possuem suporte a todos os recursos usados nesta expressão. O ponto crucial desta regex são as sub-rotinas, que nem todas as linguagens possuem (são os trechos com (?&). Outra limitação é que a regex acima só verifica se o JSON é válido, mas não extrai os seus valores. Para obter uma estrutura que possui os valores devidamente separados e organizados (seja em um map, array, dicionário ou objeto), você terá que usar um parser específico, que a maioria das linguagens possuem. E os parsers que vimos acima já verificam se o JSON é válido, então para que usar a regex? Com isso, acredito que agora você já está mais preparado para ler e manipular um JSON. Não é tão difícil: o grande problema - que pelo menos eu vejo - é que as pessoas não param para analisar a estrutura e já saem escrevendo código sem pensar. Se você olha a estrutura antes, consegue ver como percorrê-la, um nível de cada vez, até chegar na informação que você precisa. Se a linguagem que você está usando para ler o JSON não permite que existam elementos de tipos diferentes em um mesmo array, aí é outra história… ↩