<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: FUG</title>
    <description>The latest articles on DEV Community by FUG (@neulketing).</description>
    <link>https://dev.to/neulketing</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4169781%2F972e6bb8-2aad-4cb2-a0d0-19625a6d055e.png</url>
      <title>DEV Community: FUG</title>
      <link>https://dev.to/neulketing</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/neulketing"/>
    <language>en</language>
    <item>
      <title>February 29 Needs a Policy Parameter, Not a Hidden Exception</title>
      <dc:creator>FUG</dc:creator>
      <pubDate>Sat, 17 Oct 2026 15:37:04 +0000</pubDate>
      <link>https://dev.to/neulketing/february-29-needs-a-policy-parameter-not-a-hidden-exception-24o5</link>
      <guid>https://dev.to/neulketing/february-29-needs-a-policy-parameter-not-a-hidden-exception-24o5</guid>
      <description>&lt;p&gt;As of 2026-10-15&lt;/p&gt;

&lt;p&gt;A birthday reminder fails on February 29, so a developer catches the exception and substitutes February 28. The program now runs. But the patch has also chosen a policy about when somebody's anniversary occurs in a non-leap year. That choice belongs in the feature's contract, where users and tests can see it.&lt;/p&gt;

&lt;p&gt;We build the free age calculator at agecalc.fyi. Its English FAQ explicitly says that it uses March 1 for February 29 birthdays in non-leap years. That visible rule is the starting point here. We will implement two possible reminder conventions, without treating either one as a universal legal rule.&lt;/p&gt;

&lt;h2&gt;
  
  
  Keep the birthday unchanged
&lt;/h2&gt;

&lt;p&gt;Someone born on February 29 should retain February 29 in the source record. Do not “repair” their date of birth to make a yearly scheduler simpler. A date of birth describes an event that happened; an anniversary date is a value derived for a particular target year.&lt;/p&gt;

&lt;p&gt;The Gregorian leap-year rule makes years divisible by four leap years, except century years unless divisible by 400. Thus 2000 is a leap year, while 2100 is not. For application code, Python already supplies &lt;code&gt;calendar.isleap&lt;/code&gt;; &lt;code&gt;datetime.date&lt;/code&gt; validates calendar dates. Their behavior is documented in the &lt;a href="https://docs.python.org/3/library/calendar.html" rel="noopener noreferrer"&gt;Python calendar module&lt;/a&gt; and &lt;a href="https://docs.python.org/3/library/datetime.html#date-objects" rel="noopener noreferrer"&gt;date API&lt;/a&gt;.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Source birthday&lt;/th&gt;
&lt;th&gt;Target year&lt;/th&gt;
&lt;th&gt;February 28 policy&lt;/th&gt;
&lt;th&gt;March 1 policy&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;2000-02-29&lt;/td&gt;
&lt;td&gt;2027&lt;/td&gt;
&lt;td&gt;2027-02-28&lt;/td&gt;
&lt;td&gt;2027-03-01&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2000-02-29&lt;/td&gt;
&lt;td&gt;2028&lt;/td&gt;
&lt;td&gt;2028-02-29&lt;/td&gt;
&lt;td&gt;2028-02-29&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2000-02-29&lt;/td&gt;
&lt;td&gt;2100&lt;/td&gt;
&lt;td&gt;2100-02-28&lt;/td&gt;
&lt;td&gt;2100-03-01&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2000-05-12&lt;/td&gt;
&lt;td&gt;2027&lt;/td&gt;
&lt;td&gt;2027-05-12&lt;/td&gt;
&lt;td&gt;2027-05-12&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The policy changes only the missing-date case. A normal birthday should not move, and a leap-year February 29 should remain February 29. Those are good invariants because they prevent a narrowly intended workaround from becoming a general month-end adjustment.&lt;/p&gt;

&lt;h2&gt;
  
  
  An executable policy, with boundary tests
&lt;/h2&gt;

&lt;p&gt;Save this as &lt;code&gt;birthdays.py&lt;/code&gt; and run &lt;code&gt;python3 birthdays.py&lt;/code&gt;. It uses only the standard library. &lt;code&gt;anniversary&lt;/code&gt; returns a date; &lt;code&gt;completed_years&lt;/code&gt; computes a whole-year age under the same explicit convention. The latter is a calendar model, not an eligibility determination for a contract or public service.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;calendar&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;isleap&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;date&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;unittest&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;anniversary&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;born&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;year&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;policy&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;policy&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;feb28&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;mar1&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="nc"&gt;ValueError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Unknown leap-day policy&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;born&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;month&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;born&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;day&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;29&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="nf"&gt;isleap&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;year&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;month&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;day&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;28&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;policy&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;feb28&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="nf"&gt;else &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;date&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;year&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;month&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;day&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;date&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;year&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;born&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;month&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;born&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;day&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;completed_years&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;born&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;as_of&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;policy&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;as_of&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;born&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="nc"&gt;ValueError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Reference date precedes birth&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;boundary&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;anniversary&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;born&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;as_of&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;year&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;policy&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;as_of&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;year&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;born&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;year&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nf"&gt;int&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;as_of&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;boundary&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;


&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;BirthdayTests&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;unittest&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;TestCase&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;test_non_leap_boundaries&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;born&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;date&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;29&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;cases&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
            &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;date&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2027&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;27&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;feb28&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;26&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
            &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;date&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2027&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;28&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;feb28&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;27&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
            &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;date&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2027&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;28&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;mar1&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;26&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
            &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;date&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2027&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;mar1&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;27&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="p"&gt;]&lt;/span&gt;
        &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;as_of&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;policy&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;expected&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;cases&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;subTest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;as_of&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;as_of&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;policy&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;policy&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
                &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;assertEqual&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;completed_years&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;born&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;as_of&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;policy&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
                                 &lt;span class="n"&gt;expected&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;test_centuries_and_ordinary_dates&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;born&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;date&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;29&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;assertTrue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;isleap&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2000&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;assertFalse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;isleap&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2100&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
        &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;policy&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;feb28&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;mar1&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
            &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;assertEqual&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;anniversary&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;born&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2028&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;policy&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
                             &lt;span class="nf"&gt;date&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2028&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;29&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
            &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;assertEqual&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;anniversary&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;date&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;12&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="mi"&gt;2027&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;policy&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
                             &lt;span class="nf"&gt;date&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2027&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;12&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;assertEqual&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;anniversary&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;born&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2100&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;mar1&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
                         &lt;span class="nf"&gt;date&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2100&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;test_rejected_inputs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;assertRaises&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;ValueError&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
            &lt;span class="nf"&gt;date&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2027&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;29&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;assertRaises&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;ValueError&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
            &lt;span class="nf"&gt;anniversary&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;date&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;29&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="mi"&gt;2027&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;rollover&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;assertRaises&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;ValueError&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
            &lt;span class="nf"&gt;completed_years&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;date&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;29&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="nf"&gt;date&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1999&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;mar1&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;


&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;__name__&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;__main__&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;unittest&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For February 28, 2027, the year subtraction is &lt;code&gt;2027 - 2000 = 27&lt;/code&gt;. Under the March 1 policy the anniversary has not arrived, so subtract one and obtain 26. Under the February 28 policy it has arrived, so the result remains 27. The disagreement is exactly the declared policy, not an arithmetic error.&lt;/p&gt;

&lt;h2&gt;
  
  
  Do not hide the choice in exception handling
&lt;/h2&gt;

&lt;p&gt;A broad &lt;code&gt;except ValueError&lt;/code&gt; that substitutes the end of February can swallow unrelated input problems. An impossible source date, an unsupported year and a missing anniversary are different cases. Validate the source date first, then handle the known February 29 branch deliberately.&lt;/p&gt;

&lt;p&gt;The function also rejects unknown policy strings even for ordinary birthdays. That catches misspellings immediately instead of letting them survive until the next leap-day record reaches production. A typed enum can serve the same purpose in a larger application.&lt;/p&gt;

&lt;h2&gt;
  
  
  Reminders and age displays need a shared contract
&lt;/h2&gt;

&lt;p&gt;If the profile says a person is still 26 but the birthday banner says they turned 27 today, inspect whether two features used different conventions. Store the policy near the feature configuration and pass it to both calculations. Include it in bug reports alongside the birth date and reference date, using synthetic dates where possible.&lt;/p&gt;

&lt;p&gt;For legal age gates, get the applicable rule and jurisdiction separately. Do not infer those requirements from a birthday-party reminder, a general calculator or a developer's default. The functions above deliberately do not contain a country table.&lt;/p&gt;

&lt;p&gt;The web calculator also exposes a reference-date field. That is useful for inspecting a boundary without waiting for it to occur: compare the day before, the chosen anniversary and the day after. An executable test should use fixed dates for the same reason. A test that depends on today's date can pass for months while missing the only boundary that matters.&lt;/p&gt;

&lt;h2&gt;
  
  
  FAQ
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Why not add 365 days to find the next birthday?
&lt;/h3&gt;

&lt;p&gt;That advances a duration. It does not preserve a month and day across leap years.&lt;/p&gt;

&lt;h3&gt;
  
  
  Is February 28 always wrong?
&lt;/h3&gt;

&lt;p&gt;No. It is one explicit anniversary convention. This calculator's displayed rule uses March 1 instead.&lt;/p&gt;

&lt;h3&gt;
  
  
  Should I change the stored birth date?
&lt;/h3&gt;

&lt;p&gt;No. Keep the original valid date and derive each year's anniversary separately.&lt;/p&gt;

&lt;h3&gt;
  
  
  Does this calculate months and days too?
&lt;/h3&gt;

&lt;p&gt;No. The example returns anniversaries and completed years only. Month-and-day decomposition needs its own documented convention.&lt;/p&gt;

&lt;p&gt;The age calculator is free to use without signing up.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://agecalc.fyi/en/" rel="noopener noreferrer"&gt;Inspect an age at a chosen reference date&lt;/a&gt;&lt;/p&gt;

</description>
      <category>python</category>
      <category>dates</category>
      <category>testing</category>
      <category>programming</category>
    </item>
    <item>
      <title>When Fancy Names Collapse to One Search Key: Testing NFKC Collisions</title>
      <dc:creator>FUG</dc:creator>
      <pubDate>Fri, 16 Oct 2026 15:17:09 +0000</pubDate>
      <link>https://dev.to/neulketing/when-fancy-names-collapse-to-one-search-key-testing-nfkc-collisions-lkn</link>
      <guid>https://dev.to/neulketing/when-fancy-names-collapse-to-one-search-key-testing-nfkc-collisions-lkn</guid>
      <description>&lt;p&gt;As of 2026-10-15&lt;/p&gt;

&lt;p&gt;A directory may contain &lt;code&gt;Alice&lt;/code&gt;, &lt;code&gt;Ａlice&lt;/code&gt; and &lt;code&gt;𝐀lice&lt;/code&gt;. They look related, but a literal string comparison treats them as different text. Making them searchable under the same query is a product decision. Making them the same account is a much larger decision, and should not happen as an accidental side effect of a search helper.&lt;/p&gt;

&lt;p&gt;We build the free fancy text generator at fancyfont.fyi. Its page offers Unicode text styles, copying and a profile preview, and advises keeping searchable words plain. This article looks at a separate developer problem: how an application of your own can search stylized display names without destroying their original spelling.&lt;/p&gt;

&lt;h2&gt;
  
  
  Keep a second key
&lt;/h2&gt;

&lt;p&gt;Store the display text and the search key separately. The display value is what the person entered. The key expresses your chosen matching policy. The durable account ID should be independent of both.&lt;/p&gt;

&lt;p&gt;Unicode provides canonical and compatibility normalization. NFC can bring canonically equivalent forms together while retaining compatibility distinctions. NFKC also folds compatibility distinctions, so fullwidth and mathematical styled letters can become ordinary letters. Unicode cautions that compatibility normalization loses information; see its &lt;a href="https://www.unicode.org/faq/normalization.html" rel="noopener noreferrer"&gt;normalization FAQ&lt;/a&gt; and &lt;a href="https://www.unicode.org/standard/reports/tr15/" rel="noopener noreferrer"&gt;normalization specification&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;For a casual directory, that may be a useful search behavior. For mathematical text, a distinct styled letter may carry meaning. Applying NFKC to every stored document would be a much broader change than adding a forgiving name search. Keep the transformation close to the feature that needs it.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Value&lt;/th&gt;
&lt;th&gt;Purpose&lt;/th&gt;
&lt;th&gt;Can several records share it?&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Account ID&lt;/td&gt;
&lt;td&gt;Stable record identity&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Display name&lt;/td&gt;
&lt;td&gt;Presentation chosen by the person&lt;/td&gt;
&lt;td&gt;Often yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Normalized search key&lt;/td&gt;
&lt;td&gt;Match candidates&lt;/td&gt;
&lt;td&gt;Yes, by design&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Unique handle&lt;/td&gt;
&lt;td&gt;Product-defined public identifier&lt;/td&gt;
&lt;td&gt;Depends on a separate policy&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Run a collision fixture before building the index
&lt;/h2&gt;

&lt;p&gt;Save the following as &lt;code&gt;names.cjs&lt;/code&gt; and run it with Node.js. The sample uses NFKC and lowercase as a deliberately limited directory-search policy. It does not claim to implement full Unicode case folding or an identifier security standard.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;assert&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;node:assert/strict&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;searchKey&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;typeof&lt;/span&gt; &lt;span class="nx"&gt;value&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;string&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;TypeError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Expected text&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;normalize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;NFKC&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toLowerCase&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;normalize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;NFKC&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;people&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;u1&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;display&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Alice&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;u2&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;display&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Ａlice&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;u3&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;display&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;𝐀lice&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;u4&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;display&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Alicé&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;];&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;index&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Map&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;person&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nx"&gt;people&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;key&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;searchKey&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;person&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;display&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;index&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;has&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="nx"&gt;index&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[]);&lt;/span&gt;
  &lt;span class="nx"&gt;index&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;person&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;assert&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;equal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Ａ&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;normalize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;NFC&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Ａ&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nx"&gt;assert&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;equal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Ａ&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;normalize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;NFKC&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;A&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nx"&gt;assert&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;equal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;𝐀&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;normalize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;NFKC&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;A&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nx"&gt;assert&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;equal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;searchKey&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Cafe&lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;&lt;span class="s2"&gt;u0301&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="nf"&gt;searchKey&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Café&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;span class="nx"&gt;assert&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;notEqual&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;searchKey&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Café&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="nf"&gt;searchKey&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Cafe&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;span class="nx"&gt;assert&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;notEqual&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;searchKey&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Straße&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="nf"&gt;searchKey&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;STRASSE&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;span class="nx"&gt;assert&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;notEqual&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;searchKey&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;a&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="nf"&gt;searchKey&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;&lt;span class="s2"&gt;u0430&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;matches&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;index&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;searchKey&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;ALICE&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="p"&gt;[];&lt;/span&gt;
&lt;span class="nx"&gt;assert&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;deepEqual&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;matches&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;person&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;person&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;u1&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;u2&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;u3&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;
&lt;span class="nx"&gt;assert&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;equal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;people&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;display&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;𝐀lice&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;matches&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The search for &lt;code&gt;ALICE&lt;/code&gt; returns three candidates. That is the intended result: three records share a key. If the index stored a single person per key, each insertion would overwrite the previous match. A normalization improvement would then quietly become a data-loss bug.&lt;/p&gt;

&lt;p&gt;Notice what the assertions refuse to collapse. An accented letter is not automatically the unaccented letter. The German sharp s is not converted to &lt;code&gt;ss&lt;/code&gt; by this lowercase pipeline. A Cyrillic small a is not the Latin a, despite their appearance in many typefaces. These examples are executable boundaries for this particular helper.&lt;/p&gt;

&lt;h2&gt;
  
  
  Decide what a collision means in each workflow
&lt;/h2&gt;

&lt;p&gt;In a search box, a collision can produce several results. Show enough context to help the user choose without exposing private details. In an invitation flow, accept a stable identifier or require selection of the intended record. A display-name match alone is weak evidence of the recipient's identity.&lt;/p&gt;

&lt;p&gt;In account registration, the same collision may require rejection if the product promises unique handles under this matching rule. That uniqueness must also be enforced where records are stored. Checking in the browser and then inserting without a shared constraint leaves a race between simultaneous registrations.&lt;/p&gt;

&lt;p&gt;The key lesson is not to use one helper indiscriminately. Searching, displaying, authenticating and reserving a handle are separate operations. A convenient search result should not authorize access to somebody else's account or silently rename their profile.&lt;/p&gt;

&lt;h2&gt;
  
  
  Version the policy before changing existing data
&lt;/h2&gt;

&lt;p&gt;Suppose a directory originally indexed exact lowercase text and later adds NFKC. Previously distinct keys can now collide. Rebuild into a new index, inspect the collision groups, and decide how each affected feature should behave before switching traffic.&lt;/p&gt;

&lt;p&gt;Keep the old display values during that migration. Normalization is not a reversible encoding: once several inputs have become the same output, the output cannot tell you which original was used. Storing only the normalized value would make it impossible to restore the original presentation accurately.&lt;/p&gt;

&lt;p&gt;A migration report can list the policy version and measured collision groups without exposing private names.&lt;/p&gt;

&lt;h2&gt;
  
  
  Test the UI as well as the helper
&lt;/h2&gt;

&lt;p&gt;The sample verifies string behavior, but a useful search interface also needs tests for empty queries, pasted whitespace, sorting and selection. Decide whether trimming is part of your policy. This helper intentionally does not remove punctuation or trim a person's display name.&lt;/p&gt;

&lt;p&gt;Run the fixtures in the runtimes that will produce and consume the key. If your database applies a different collation, compare its uniqueness behavior with the application helper. Compare actual strings, not policy labels.&lt;/p&gt;

&lt;h2&gt;
  
  
  FAQ
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Should I replace display names with NFKC output?
&lt;/h3&gt;

&lt;p&gt;Usually keep the original and derive a separate key. Compatibility normalization can discard distinctions the person intended.&lt;/p&gt;

&lt;h3&gt;
  
  
  Does normalization stop impersonation?
&lt;/h3&gt;

&lt;p&gt;No. It is a text transformation, not proof of identity or a complete look-alike detection system.&lt;/p&gt;

&lt;h3&gt;
  
  
  Is lowercase the same as Unicode case folding?
&lt;/h3&gt;

&lt;p&gt;No. The sharp-s assertion demonstrates one difference. Pick a matching policy for the languages and feature you support.&lt;/p&gt;

&lt;h3&gt;
  
  
  Does the generator provide this search index?
&lt;/h3&gt;

&lt;p&gt;No. The page provides styled text. The index here is an independent example for developers.&lt;/p&gt;

&lt;p&gt;The text generator is free, with no sign-up or app required.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://fancyfont.fyi/en/" rel="noopener noreferrer"&gt;Create styled text to use in your own test fixtures&lt;/a&gt;&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>unicode</category>
      <category>search</category>
      <category>testing</category>
    </item>
    <item>
      <title>An Exact-Zero Cart Ledger with BigInt and a JSON Round Trip</title>
      <dc:creator>FUG</dc:creator>
      <pubDate>Thu, 15 Oct 2026 15:17:17 +0000</pubDate>
      <link>https://dev.to/neulketing/an-exact-zero-cart-ledger-with-bigint-and-a-json-round-trip-kjd</link>
      <guid>https://dev.to/neulketing/an-exact-zero-cart-ledger-with-bigint-and-a-json-round-trip-kjd</guid>
      <description>&lt;p&gt;As of 2026-10-15&lt;/p&gt;

&lt;p&gt;A spending game can make zero a meaningful state: the timer stops when the balance is exactly empty. That turns an ordinary shopping cart into a useful programming exercise. Every purchase, sale and restored save needs to preserve the same accounting identity, even after a long sequence of clicks.&lt;/p&gt;

&lt;p&gt;We build the free spending game at 40jo.fyi. Its English page describes buying and selling items, entering quantities, a receipt and a timer that stops at zero. The code below is an independent teaching example, not a description of the site's implementation. Its prices are invented test inputs, not market prices or values copied from the game.&lt;/p&gt;

&lt;h2&gt;
  
  
  Choose a unit before choosing a type
&lt;/h2&gt;

&lt;p&gt;Our sample ledger uses integer credits. If an application instead supports dollars and cents, its unit could be cents. Do not let some functions accept whole dollars while others expect cents: a precise type cannot repair an ambiguous unit.&lt;/p&gt;

&lt;p&gt;JavaScript's largest safe integer is &lt;code&gt;2 ** 53 - 1&lt;/code&gt;, or 9,007,199,254,740,991. A balance of 27 billion whole credits fits comfortably inside that boundary. A large-looking number alone is therefore not a reason to claim that &lt;code&gt;Number&lt;/code&gt; is broken. Here we use &lt;code&gt;BigInt&lt;/code&gt; to give an extensible integer ledger one consistent arithmetic type.&lt;/p&gt;

&lt;p&gt;BigInt represents integers, not arbitrary decimal fractions. Mixed Number/BigInt arithmetic is generally rejected, and a raw BigInt cannot be serialized by ordinary &lt;code&gt;JSON.stringify&lt;/code&gt; without a conversion policy. Those behaviors are specified by &lt;a href="https://tc39.es/ecma262/2024/" rel="noopener noreferrer"&gt;ECMAScript's numeric types and serialization rules&lt;/a&gt;. The interesting design work is at the boundaries: input, state transitions and storage.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Boundary&lt;/th&gt;
&lt;th&gt;Accepted representation&lt;/th&gt;
&lt;th&gt;Rejected example&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Quantity input&lt;/td&gt;
&lt;td&gt;Nonnegative decimal digit string&lt;/td&gt;
&lt;td&gt;&lt;code&gt;"1.5"&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Internal amount&lt;/td&gt;
&lt;td&gt;Integer credits as BigInt&lt;/td&gt;
&lt;td&gt;A floating-point price&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Saved quantity&lt;/td&gt;
&lt;td&gt;Decimal string in JSON&lt;/td&gt;
&lt;td&gt;A raw BigInt value&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Cart update&lt;/td&gt;
&lt;td&gt;A complete affordable new quantity&lt;/td&gt;
&lt;td&gt;A negative balance&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Make the update atomic
&lt;/h2&gt;

&lt;p&gt;The following program runs as one file with Node.js. It has no dependencies beyond Node's assertion module. Save it as &lt;code&gt;ledger.cjs&lt;/code&gt; and run &lt;code&gt;node ledger.cjs&lt;/code&gt;. A failed operation throws before changing the cart, so the caller can display the error without repairing partial state.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;assert&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;node:assert/strict&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;unsigned&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;typeof&lt;/span&gt; &lt;span class="nx"&gt;text&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;string&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="sr"&gt;/^&lt;/span&gt;&lt;span class="se"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;0|&lt;/span&gt;&lt;span class="se"&gt;[&lt;/span&gt;&lt;span class="sr"&gt;1-9&lt;/span&gt;&lt;span class="se"&gt;][&lt;/span&gt;&lt;span class="sr"&gt;0-9&lt;/span&gt;&lt;span class="se"&gt;]&lt;/span&gt;&lt;span class="sr"&gt;*&lt;/span&gt;&lt;span class="se"&gt;)&lt;/span&gt;&lt;span class="sr"&gt;$/&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;test&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;TypeError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Expected an unsigned decimal string&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nc"&gt;BigInt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;budget&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="nx"&gt;n&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;prices&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Map&lt;/span&gt;&lt;span class="p"&gt;([[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;book&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;125&lt;/span&gt;&lt;span class="nx"&gt;n&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;ticket&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;300&lt;/span&gt;&lt;span class="nx"&gt;n&lt;/span&gt;&lt;span class="p"&gt;]]);&lt;/span&gt;
&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;cart&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Map&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;spent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;items&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;total&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="nx"&gt;n&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;quantity&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nx"&gt;items&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;prices&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;has&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;RangeError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Unknown item&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;typeof&lt;/span&gt; &lt;span class="nx"&gt;quantity&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;bigint&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nx"&gt;quantity&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="nx"&gt;n&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;RangeError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Invalid quantity&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="nx"&gt;total&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="nx"&gt;prices&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;quantity&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;total&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;setQuantity&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;prices&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;has&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;RangeError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Unknown item&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;next&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;cart&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;next&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;unsigned&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;spent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;next&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;budget&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;RangeError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Over budget&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;cart&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;next&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;budget&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nf"&gt;spent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;cart&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;assert&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;equal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;setQuantity&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;book&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;2&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="mi"&gt;750&lt;/span&gt;&lt;span class="nx"&gt;n&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nx"&gt;assert&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;equal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;setQuantity&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;ticket&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;2&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="mi"&gt;150&lt;/span&gt;&lt;span class="nx"&gt;n&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nx"&gt;assert&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;throws&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;setQuantity&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;ticket&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;3&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="sr"&gt;/Over budget/&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nx"&gt;assert&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;equal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;budget&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nf"&gt;spent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;cart&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="mi"&gt;150&lt;/span&gt;&lt;span class="nx"&gt;n&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nx"&gt;assert&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;throws&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;setQuantity&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;book&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;1.5&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="sr"&gt;/decimal/&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// Selling both tickets restores their full cost.&lt;/span&gt;
&lt;span class="nx"&gt;assert&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;equal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;setQuantity&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;ticket&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;0&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="mi"&gt;750&lt;/span&gt;&lt;span class="nx"&gt;n&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nx"&gt;assert&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;equal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;setQuantity&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;book&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;8&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="nx"&gt;n&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;saved&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;version&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;quantities&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[...&lt;/span&gt;&lt;span class="nx"&gt;cart&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(([&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;q&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;q&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toString&lt;/span&gt;&lt;span class="p"&gt;()])&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;parsed&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;saved&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nx"&gt;assert&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;equal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;parsed&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;version&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;restored&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nx"&gt;parsed&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;quantities&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(([&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;q&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;unsigned&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;q&lt;/span&gt;&lt;span class="p"&gt;)])&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nx"&gt;assert&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;equal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;spent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;restored&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="nx"&gt;budget&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Ledger checks passed; remaining credits:&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;budget&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nf"&gt;spent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;restored&lt;/span&gt;&lt;span class="p"&gt;)).&lt;/span&gt;&lt;span class="nf"&gt;toString&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Two books cost &lt;code&gt;2 × 125 = 250&lt;/code&gt; credits. Two tickets add &lt;code&gt;2 × 300 = 600&lt;/code&gt;, so spending is 850 and the balance is &lt;code&gt;1,000 - 850 = 150&lt;/code&gt;. Asking for a third ticket would make the ticket subtotal 900 and the whole cart 1,150. The rejection leaves the previous 850-credit cart intact.&lt;/p&gt;

&lt;p&gt;Selling the tickets returns spending to 250. Setting the book quantity to eight makes it &lt;code&gt;8 × 125 = 1,000&lt;/code&gt;, which reaches exactly zero. No tolerance or rounding rule is involved because every operation is integer addition, multiplication or subtraction.&lt;/p&gt;

&lt;h2&gt;
  
  
  The receipt should come from the cart
&lt;/h2&gt;

&lt;p&gt;Avoid maintaining independent mutable values for the balance, receipt total and cart total. Three counters require every event handler to update all three correctly. In this example the cart is the state; spending and balance are derived views. A receipt renderer can use the same quantities and prices.&lt;/p&gt;

&lt;p&gt;The useful invariant is &lt;code&gt;budget = spent + remaining&lt;/code&gt;. Check it after buying, selling, resetting and restoring. Another useful property is reversibility: setting an item's quantity back to its old value should restore the old total, provided the catalog price has not changed.&lt;/p&gt;

&lt;p&gt;A real catalog needs a price-version policy. If a saved cart refers to yesterday's prices, should restoring it use the old catalog or recalculate at today's prices? Our small example fixes the catalog for its lifetime. A deployed application should make that choice explicit rather than silently calling a changed receipt the original one.&lt;/p&gt;

&lt;h2&gt;
  
  
  JSON is an interface, not a backup spell
&lt;/h2&gt;

&lt;p&gt;The sample restores only the JSON it just created. An imported save needs additional validation: object shape, version, allowed item IDs, duplicate IDs, quantity-string length and total affordability. A duplicate ID would otherwise be overwritten by &lt;code&gt;Map&lt;/code&gt;, concealing an ambiguous import. Cap input lengths before parsing huge integers as well.&lt;/p&gt;

&lt;p&gt;Store an explicit unit and catalog version when the file can leave the application. Strings preserve digits, but the receiver still needs to know what those digits mean. Converting a decimal string through &lt;code&gt;Number&lt;/code&gt; before calling &lt;code&gt;BigInt&lt;/code&gt; defeats that purpose if the number is outside the safe range.&lt;/p&gt;

&lt;h2&gt;
  
  
  FAQ
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Does a billion-credit budget require BigInt?
&lt;/h3&gt;

&lt;p&gt;No. Check the largest intermediate integer against Number's safe range. BigInt is an explicit integer design choice here.&lt;/p&gt;

&lt;h3&gt;
  
  
  Can I use BigInt for decimal prices directly?
&lt;/h3&gt;

&lt;p&gt;No. Choose a fixed smallest unit and parse into it, or use a decimal arithmetic design with documented rounding.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why store quantities instead of the displayed balance?
&lt;/h3&gt;

&lt;p&gt;The quantities and catalog reconstruct the receipt and total. A balance alone cannot explain what was purchased.&lt;/p&gt;

&lt;h3&gt;
  
  
  Does this reproduce the game's source code?
&lt;/h3&gt;

&lt;p&gt;No. It demonstrates a ledger invariant inspired by the page's exact-zero objective.&lt;/p&gt;

&lt;p&gt;The spending game is free to play without signing up.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://40jo.fyi/en/" rel="noopener noreferrer"&gt;Try an exact-zero spending challenge&lt;/a&gt;&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>bigint</category>
      <category>testing</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Date Differences Without Traps: Calendar Days, UTC, Midnight, and DST</title>
      <dc:creator>FUG</dc:creator>
      <pubDate>Wed, 14 Oct 2026 15:00:37 +0000</pubDate>
      <link>https://dev.to/neulketing/date-differences-without-traps-calendar-days-utc-midnight-and-dst-1bdg</link>
      <guid>https://dev.to/neulketing/date-differences-without-traps-calendar-days-utc-midnight-and-dst-1bdg</guid>
      <description>&lt;p&gt;As of October 8, 2026&lt;/p&gt;

&lt;p&gt;The interval from March 8 to March 9 is one calendar day. In a daylight-saving location, the elapsed time between two local midnights can be 23 hours. Code that divides milliseconds by &lt;code&gt;86_400_000&lt;/code&gt; and expects every local date transition to behave alike eventually returns the wrong answer.&lt;/p&gt;

&lt;p&gt;We make a date calculator that distinguishes a countdown from a day number and counts dates in the device’s local calendar. Its “count the start date as day 1” switch exists because the arithmetic is only safe after the product decides what its words mean.&lt;/p&gt;

&lt;h2&gt;
  
  
  Start by naming the interval
&lt;/h2&gt;

&lt;p&gt;An exclusive elapsed-day difference counts boundaries crossed: March 1 to March 2 is one day. Inclusive counting counts the labeled dates themselves: March 1 through March 2 is two days. Neither is universally correct.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Question&lt;/th&gt;
&lt;th&gt;Start date included?&lt;/th&gt;
&lt;th&gt;March 1 to March 2&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;“How many days until March 2?”&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;“What day number is March 2 if March 1 is day 1?”&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;“100 days after January 1”&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;April 11 in a non-leap year&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;“The 100th day from January 1”&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;April 10 in a non-leap year&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Adding one after a calculation is not a general fix. It is an explicit inclusive convention and needs to appear in the UI, API name, and tests. A legal deadline, a hotel stay, and a relationship anniversary can use different conventions even when their dates look identical.&lt;/p&gt;

&lt;h2&gt;
  
  
  A date-only value is not an instant
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;2026-10-08&lt;/code&gt; identifies a civil calendar date. &lt;code&gt;2026-10-08T00:00:00Z&lt;/code&gt; identifies one instant at UTC midnight. &lt;code&gt;new Date("2026-10-08")&lt;/code&gt; parses the date-only ISO form as UTC in JavaScript, then getters such as &lt;code&gt;getFullYear()&lt;/code&gt; and &lt;code&gt;getDate()&lt;/code&gt; display it in the host’s local zone. West of UTC, that can look like October 7.&lt;/p&gt;

&lt;p&gt;For user-entered date fields, keep the value as a &lt;code&gt;YYYY-MM-DD&lt;/code&gt; string until you deliberately perform calendar arithmetic. Do not create an instant merely to subtract two dates. A time-zone-aware appointment is a different data type and needs its own policy.&lt;/p&gt;

&lt;p&gt;The following helper parses a date-only string and converts it to a UTC day index. UTC is used here as a neutral calendar grid, not as a claim that the user chose UTC. Each civil date gets one index, so daylight-saving clock changes cannot alter the result.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;MS_PER_DAY&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;24&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;60&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;60&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;parseIsoDate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;match&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sr"&gt;/^&lt;/span&gt;&lt;span class="se"&gt;(\d{4})&lt;/span&gt;&lt;span class="sr"&gt;-&lt;/span&gt;&lt;span class="se"&gt;(\d{2})&lt;/span&gt;&lt;span class="sr"&gt;-&lt;/span&gt;&lt;span class="se"&gt;(\d{2})&lt;/span&gt;&lt;span class="sr"&gt;$/&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;exec&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;match&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;TypeError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Expected YYYY-MM-DD&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[,&lt;/span&gt; &lt;span class="nx"&gt;year&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;month&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;day&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;match&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;Number&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;stamp&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;UTC&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;year&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;month&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;day&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;check&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;stamp&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;check&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getUTCFullYear&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="nx"&gt;year&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt;
      &lt;span class="nx"&gt;check&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getUTCMonth&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="nx"&gt;month&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt;
      &lt;span class="nx"&gt;check&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getUTCDate&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="nx"&gt;day&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;RangeError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Invalid calendar date&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;stamp&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;daysBetween&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;start&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;end&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;inclusive&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{})&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;difference&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;parseIsoDate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;end&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nf"&gt;parseIsoDate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;start&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="nx"&gt;MS_PER_DAY&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;difference&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;inclusive&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;daysBetween&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;2026-03-01&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;2026-03-02&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;span class="c1"&gt;// 1&lt;/span&gt;
&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;daysBetween&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;2026-03-01&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;2026-03-02&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;inclusive&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt; &lt;span class="p"&gt;}));&lt;/span&gt;
&lt;span class="c1"&gt;// 2&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The round-trip check catches values such as February 30, which &lt;code&gt;Date.UTC&lt;/code&gt; would otherwise normalize into a later real date. The result may be negative when the end date precedes the start date; callers should decide whether a negative past-due count is useful or should be rejected.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why local midnight subtraction fails
&lt;/h2&gt;

&lt;p&gt;This common pattern treats local midnights as if they were always separated by the same duration:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;days&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2026&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;9&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2026&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;86&lt;/span&gt;&lt;span class="nx"&gt;_400_000&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In a zone whose clocks move forward during that interval, it can produce a fraction below one. Rounding it may hide a problem for one case while creating another around a clock change in the other direction. &lt;code&gt;setDate(getDate() + 1)&lt;/code&gt; is useful for stepping through local calendar dates, but subtracting two local &lt;code&gt;Date&lt;/code&gt; objects measures instants, not date labels.&lt;/p&gt;

&lt;p&gt;There is a converse trap: forcing all date-only inputs through UTC instants then rendering them with local getters changes the displayed date in some zones. Use UTC getters consistently when inspecting a UTC-grid date, or keep date-only strings and format them with a date-only formatter.&lt;/p&gt;

&lt;h2&gt;
  
  
  Keep calendar and elapsed-time features separate
&lt;/h2&gt;

&lt;p&gt;Calendar-day difference and elapsed duration are both valid operations. A timer until &lt;code&gt;2026-10-08T09:00:00+09:00&lt;/code&gt; should use instants and milliseconds. A “days until birthday” feature should use local calendar dates and show “Today” when the date labels match. Combining them in a single function with an undocumented boolean creates ambiguous results.&lt;/p&gt;

&lt;p&gt;The calculator page adds or subtracts days, weeks, months, and years; it also lists anniversary dates. Month arithmetic is not day arithmetic. January 31 plus one month has to choose a convention because February lacks the 31st. The page uses the last day of February for that case. Define and test the convention before offering a “+ 1 month” control.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Product feature&lt;/th&gt;
&lt;th&gt;Input model&lt;/th&gt;
&lt;th&gt;Recommended operation&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Countdown to a date&lt;/td&gt;
&lt;td&gt;Local date labels&lt;/td&gt;
&lt;td&gt;UTC day-index subtraction or calendar API&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;“Day 1” anniversary&lt;/td&gt;
&lt;td&gt;Local date labels&lt;/td&gt;
&lt;td&gt;Calendar arithmetic plus inclusive rule&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Meeting duration&lt;/td&gt;
&lt;td&gt;Zoned instants&lt;/td&gt;
&lt;td&gt;Instant subtraction&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Add one month&lt;/td&gt;
&lt;td&gt;Calendar date&lt;/td&gt;
&lt;td&gt;Explicit month-end convention&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Modern environments may offer &lt;code&gt;Temporal&lt;/code&gt;, whose &lt;code&gt;PlainDate&lt;/code&gt; represents a date without time or zone. Where it is available through the runtime or a supported polyfill, it makes the type distinction clearer. The small helper above remains useful when an application must depend only on broadly deployed &lt;code&gt;Date&lt;/code&gt; and strict date strings.&lt;/p&gt;

&lt;h2&gt;
  
  
  Test the boundary, not only the average day
&lt;/h2&gt;

&lt;p&gt;Put dates on both sides of a month end, leap day, year end, and a DST clock change into tests. Run any local-time logic under more than one &lt;code&gt;TZ&lt;/code&gt; setting in CI. Test reversed ranges and invalid dates. Most importantly, assert the product wording alongside the result: the test should say “exclusive calendar days” or “inclusive day number,” not merely &lt;code&gt;expect(result).toBe(1)&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  FAQ
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Should a countdown include today?
&lt;/h3&gt;

&lt;p&gt;Usually no: tomorrow is one day away and today is the destination. A day-number feature can legitimately include the start date instead.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why not divide milliseconds by 24 hours?
&lt;/h3&gt;

&lt;p&gt;That measures elapsed time between instants. Local daylight-saving transitions can make a calendar day shorter or longer than 24 hours.&lt;/p&gt;

&lt;h3&gt;
  
  
  Is UTC always the right user time zone?
&lt;/h3&gt;

&lt;p&gt;No. UTC in the helper is a stable indexing technique for date-only values. A meeting or deadline with a specified local time needs a real time-zone rule.&lt;/p&gt;

&lt;h3&gt;
  
  
  Does the calculator handle leap years?
&lt;/h3&gt;

&lt;p&gt;The tool page says it uses the real calendar, including February 29, and counts whole calendar days in the device’s local date.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://ddaycalc.date/en/" rel="noopener noreferrer"&gt;Check a free countdown, date difference, date addition, or anniversary date&lt;/a&gt;&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>dates</category>
      <category>timezone</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Wi-Fi and vCard QR Codes: The Text Payloads Behind the Squares</title>
      <dc:creator>FUG</dc:creator>
      <pubDate>Sun, 11 Oct 2026 21:17:42 +0000</pubDate>
      <link>https://dev.to/neulketing/wi-fi-and-vcard-qr-codes-the-text-payloads-behind-the-squares-dkc</link>
      <guid>https://dev.to/neulketing/wi-fi-and-vcard-qr-codes-the-text-payloads-behind-the-squares-dkc</guid>
      <description>&lt;p&gt;As of October 8, 2026&lt;/p&gt;

&lt;p&gt;A Wi-Fi QR code is not a special encrypted picture. At its core, it is text such as &lt;code&gt;WIFI:T:WPA;S:CafeNet;P:correct-horse-battery;H:false;;&lt;/code&gt;. A phone recognizes the convention and offers to join the network. A contact QR works the same way: it carries vCard text that a scanner can turn into a contact card.&lt;/p&gt;

&lt;p&gt;We build a QR generator with Wi-Fi and vCard options, so we treat the payload as the primary artifact. The rendered modules, colors, and download format come after the payload is correct. A beautifully styled code with a malformed escaped value still gives a guest the wrong result.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Wi-Fi payload is a small field language
&lt;/h2&gt;

&lt;p&gt;The widely used Wi-Fi QR convention starts with &lt;code&gt;WIFI:&lt;/code&gt; and uses semicolon-delimited fields. The common fields are &lt;code&gt;T&lt;/code&gt; for authentication type, &lt;code&gt;S&lt;/code&gt; for SSID, &lt;code&gt;P&lt;/code&gt; for password, and &lt;code&gt;H&lt;/code&gt; for hidden-network status. The payload ends with a second semicolon.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Field&lt;/th&gt;
&lt;th&gt;Meaning&lt;/th&gt;
&lt;th&gt;Example value&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;T&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Network authentication type&lt;/td&gt;
&lt;td&gt;&lt;code&gt;WPA&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;S&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Network name (SSID)&lt;/td&gt;
&lt;td&gt;&lt;code&gt;CafeNet&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;P&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Network password&lt;/td&gt;
&lt;td&gt;&lt;code&gt;correct-horse-battery&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;H&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Whether the network is hidden&lt;/td&gt;
&lt;td&gt;&lt;code&gt;false&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;For an ordinary WPA network, the full payload can be &lt;code&gt;WIFI:T:WPA;S:CafeNet;P:correct-horse-battery;H:false;;&lt;/code&gt;. A scanner may support a slightly different subset of values, so a generator should not promise identical behavior on every camera app. The reliable workflow is to scan the completed code with the intended devices before it reaches a sign or guest handout.&lt;/p&gt;

&lt;p&gt;Delimiters are data hazards. A semicolon ends a field; a colon separates its label from its value; a backslash introduces escaping. In this convention, backslash-escape characters that could be interpreted as syntax inside a value. Do not hand-concatenate untrusted input and hope that an SSID containing punctuation happens to work.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;wifiEscape&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="se"&gt;([\\&lt;/span&gt;&lt;span class="sr"&gt;;,:&lt;/span&gt;&lt;span class="se"&gt;\"])&lt;/span&gt;&lt;span class="sr"&gt;/g&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\\&lt;/span&gt;&lt;span class="s2"&gt;$1&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;wifiPayload&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;ssid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;password&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;auth&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;WPA&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;hidden&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="s2"&gt;`WIFI:T:&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nf"&gt;wifiEscape&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;auth&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;&lt;span class="s2"&gt;;S:&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nf"&gt;wifiEscape&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ssid&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;&lt;span class="s2"&gt;;`&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt;
    &lt;span class="s2"&gt;`P:&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nf"&gt;wifiEscape&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;password&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;&lt;span class="s2"&gt;;H:&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;hidden&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;true&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;false&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;;;`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;wifiPayload&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;ssid&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Tea;Time&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;password&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;a:b&lt;/span&gt;&lt;span class="se"&gt;\\&lt;/span&gt;&lt;span class="s2"&gt;c&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
&lt;span class="p"&gt;}));&lt;/span&gt;
&lt;span class="c1"&gt;// WIFI:T:WPA;S:Tea\;Time;P:a\:b\\c;H:false;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The exact accepted authentication labels and empty-password behavior deserve device testing. A payload builder should validate its own supported choices rather than silently changing a network setting. It should also make the privacy consequence visible: whoever scans the code can recover its plaintext network configuration. Do not place a guest code where an unrestricted password would be inappropriate.&lt;/p&gt;

&lt;h2&gt;
  
  
  vCard is contact text, not a screenshot
&lt;/h2&gt;

&lt;p&gt;vCard is a text contact interchange format. A small vCard QR often starts with &lt;code&gt;BEGIN:VCARD&lt;/code&gt;, declares a &lt;code&gt;VERSION&lt;/code&gt;, supplies properties such as &lt;code&gt;FN&lt;/code&gt; for the formatted name and &lt;code&gt;TEL&lt;/code&gt; or &lt;code&gt;EMAIL&lt;/code&gt;, then closes with &lt;code&gt;END:VCARD&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;BEGIN:VCARD
VERSION:3.0
FN:Jordan Lee
ORG:North Street Studio
TEL;TYPE=CELL:+1-555-0100
EMAIL:jordan@example.test
END:VCARD
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each line is a property. The property name can have parameters after semicolons, followed by a colon and its value. That looks simple until real names or notes contain commas, semicolons, line breaks, or backslashes. Escape text values according to the vCard version you choose, and do not send a display-formatted phone number when your destination expects an international number.&lt;/p&gt;

&lt;p&gt;Use a maintained vCard library when the input goes beyond a few controlled fields. It reduces the chance of mixing vCard versions or failing to fold long physical lines. A minimal helper can demonstrate the text escaping rule, but it is not a substitute for full conformance testing:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;vcardText&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="se"&gt;\\&lt;/span&gt;&lt;span class="sr"&gt;/g&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\\\\&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="sr"&gt;/g&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\\&lt;/span&gt;&lt;span class="s2"&gt;n&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/;/g&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\\&lt;/span&gt;&lt;span class="s2"&gt;;&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/,/g&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\\&lt;/span&gt;&lt;span class="s2"&gt;,&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For a contact, include only fields the recipient needs. A QR displayed at a public booth may be copied by anyone. A landing page can be changed later; static vCard data printed on a badge cannot. Verify the scanned result in the phone’s contact preview before distributing it.&lt;/p&gt;

&lt;h2&gt;
  
  
  The payload controls density
&lt;/h2&gt;

&lt;p&gt;The QR symbol carries a finite number of bits. More payload text can require a higher version, which makes the grid denser. A long vCard with multiple addresses, labels, and notes may therefore be harder to scan at a small printed size than a short Wi-Fi payload. Encoding mode and the QR library’s settings also affect the final symbol.&lt;/p&gt;

&lt;p&gt;Error correction adds redundant codewords. The familiar L, M, Q, and H levels trade capacity for resilience to damage. Higher correction can help a clean code survive a minor obstruction, but it can also increase density for the same payload. It is not a license to cover a fixed percentage of modules with an arbitrary logo.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Decision&lt;/th&gt;
&lt;th&gt;What it changes&lt;/th&gt;
&lt;th&gt;What to verify&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Add another vCard field&lt;/td&gt;
&lt;td&gt;Payload length and possibly density&lt;/td&gt;
&lt;td&gt;Scan distance at final print size&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Raise error correction&lt;/td&gt;
&lt;td&gt;Redundancy and often symbol size&lt;/td&gt;
&lt;td&gt;Finished code with any logo&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Escape punctuation&lt;/td&gt;
&lt;td&gt;Payload interpretation&lt;/td&gt;
&lt;td&gt;A scanner reads the original value&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Export SVG instead of PNG&lt;/td&gt;
&lt;td&gt;Scaling behavior, not encoded data&lt;/td&gt;
&lt;td&gt;Quiet zone and print contrast&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The generator page offers nine QR types, lets a user style a code, and exports PNG or SVG. It says the content is encoded directly rather than through a redirect, so the code has no redirect layer to maintain. That does not make the embedded Wi-Fi password or contact details timeless; those values still need review whenever they change.&lt;/p&gt;

&lt;h2&gt;
  
  
  A production checklist
&lt;/h2&gt;

&lt;p&gt;Log the payload before rendering, but never log a real Wi-Fi password in an analytics event or browser console. Generate the symbol from the exact string, preserving its encoding. Keep a quiet margin, strong contrast, and a label that tells people what will happen after scanning. Then test on more than one actual phone, not just the device that generated it.&lt;/p&gt;

&lt;p&gt;For vCards, check that each target contacts app assigns the name, phone, and email to the expected fields. For Wi-Fi, test joining a safe test network first and make a plan for password rotation. These are interface tests, not merely image tests.&lt;/p&gt;

&lt;h2&gt;
  
  
  FAQ
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Can a Wi-Fi QR code hide the password from someone who scans it?
&lt;/h3&gt;

&lt;p&gt;No. The code needs to contain the configuration a compatible scanner uses to join. Treat a shared QR as shared network access.&lt;/p&gt;

&lt;h3&gt;
  
  
  Does a vCard QR need a website?
&lt;/h3&gt;

&lt;p&gt;No. A vCard QR can contain the contact text directly. It can also become large when many fields are included.&lt;/p&gt;

&lt;h3&gt;
  
  
  Is higher error correction always better?
&lt;/h3&gt;

&lt;p&gt;No. It consumes capacity and can make a symbol denser. Select it for the actual print and decoration conditions, then scan-test the result.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why does punctuation break some codes?
&lt;/h3&gt;

&lt;p&gt;Payload formats use punctuation as syntax. Values must be escaped according to the format rather than inserted as raw text.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://makeqr.fyi/en/" rel="noopener noreferrer"&gt;Build and download a free Wi-Fi, vCard, or other QR code in your browser&lt;/a&gt;&lt;/p&gt;

</description>
      <category>qrcode</category>
      <category>wifi</category>
      <category>vcard</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Character Counts in JavaScript: Code Units, Code Points, Graphemes, and UTF-8 Bytes</title>
      <dc:creator>FUG</dc:creator>
      <pubDate>Sat, 10 Oct 2026 20:57:44 +0000</pubDate>
      <link>https://dev.to/neulketing/character-counts-in-javascript-code-units-code-points-graphemes-and-utf-8-bytes-beb</link>
      <guid>https://dev.to/neulketing/character-counts-in-javascript-code-units-code-points-graphemes-and-utf-8-bytes-beb</guid>
      <description>&lt;p&gt;As of October 8, 2026&lt;/p&gt;

&lt;p&gt;&lt;code&gt;"\u{1F44D}\u{1F3FD}".length&lt;/code&gt; is 4. A reader looking at the resulting single modified thumbs-up symbol sees one symbol. JavaScript sees four UTF-16 code units. Neither observation is broken; they answer different questions. The bug starts when a product requirement says “characters” without naming the unit.&lt;/p&gt;

&lt;p&gt;We make browser tools, including a counter that shows counts with and without spaces and UTF-8 bytes. That interface is deliberately more specific than a single number: a tweet field, a storage column, and a visible counter may each have a different definition of length.&lt;/p&gt;

&lt;h2&gt;
  
  
  Four units hiding behind one word
&lt;/h2&gt;

&lt;p&gt;JavaScript strings are sequences of UTF-16 code units. &lt;code&gt;String.prototype.length&lt;/code&gt; reports that representation. Basic Latin letters normally occupy one code unit. Many code points outside the Basic Multilingual Plane occupy a surrogate pair, so they take two. Emoji sequences can contain multiple code points: a base symbol, a skin-tone modifier, a variation selector, or a zero-width joiner sequence.&lt;/p&gt;

&lt;p&gt;A Unicode code point is the numbered value assigned by Unicode, such as &lt;code&gt;U+0061&lt;/code&gt; for &lt;code&gt;a&lt;/code&gt;. A grapheme cluster is a user-perceived text element. The cluster can be one code point, but it can also be a sequence. UTF-8 bytes are encoded storage or transport units; their number depends on encoding rather than on what is visibly drawn.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Example&lt;/th&gt;
&lt;th&gt;Visible symbols&lt;/th&gt;
&lt;th&gt;UTF-16 code units&lt;/th&gt;
&lt;th&gt;Code points&lt;/th&gt;
&lt;th&gt;UTF-8 bytes&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;A&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;é&lt;/code&gt; (precomposed)&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;U+1F600&lt;/code&gt; (grinning face)&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;U+1F44D U+1F3FD&lt;/code&gt; (modified thumbs-up)&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;8&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The second row carries an extra warning. The visible &lt;code&gt;é&lt;/code&gt; can also be written as &lt;code&gt;e&lt;/code&gt; followed by a combining acute accent. That alternative has two code points and three UTF-8 bytes while still usually displaying as one grapheme cluster. A rule that needs stable comparison may also need a normalization policy; counting alone does not supply one.&lt;/p&gt;

&lt;h2&gt;
  
  
  Use an explicit counter
&lt;/h2&gt;

&lt;p&gt;This small utility makes the four choices visible. It uses &lt;code&gt;for...of&lt;/code&gt; for code points, &lt;code&gt;Intl.Segmenter&lt;/code&gt; for grapheme clusters when available, and &lt;code&gt;TextEncoder&lt;/code&gt; for UTF-8 length.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;counts&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;locale&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;en&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;codeUnits&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;codePoints&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Array&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;from&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;bytesUtf8&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;TextEncoder&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;encode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;graphemes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;typeof&lt;/span&gt; &lt;span class="nx"&gt;Intl&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Segmenter&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;function&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
    &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="nb"&gt;Array&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;from&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;Intl&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Segmenter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;locale&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;granularity&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;grapheme&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;}).&lt;/span&gt;&lt;span class="nf"&gt;segment&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;
    &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;codeUnits&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;codePoints&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;graphemes&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;bytesUtf8&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;counts&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;A&lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;&lt;span class="s2"&gt;u{1F600}&lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;&lt;span class="s2"&gt;u{1F44D}&lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;&lt;span class="s2"&gt;u{1F3FD}&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;span class="c1"&gt;// { codeUnits: 7, codePoints: 4, graphemes: 3, bytesUtf8: 13 }&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;Array.from(text)&lt;/code&gt; and &lt;code&gt;[...text]&lt;/code&gt; iterate by code point, so they avoid splitting a surrogate pair into two values. They do not solve the grapheme problem: the modifier in the &lt;code&gt;U+1F44D U+1F3FD&lt;/code&gt; sequence remains a second code point. &lt;code&gt;Intl.Segmenter&lt;/code&gt; asks the platform to apply Unicode text-segmentation rules, which is much closer to the count a person expects in an input box.&lt;/p&gt;

&lt;p&gt;Treat the fallback carefully. Returning &lt;code&gt;null&lt;/code&gt; is more honest than quietly substituting code-point length and labelling it a grapheme count. If a server enforces a visible-symbol limit, use a compatible segmentation library or define the accepted client platforms; client-side advice alone cannot enforce a server rule.&lt;/p&gt;

&lt;h2&gt;
  
  
  “With spaces” is a policy, not another encoding
&lt;/h2&gt;

&lt;p&gt;The difference between characters with spaces and characters without spaces is usually a filtering rule applied before counting. On the counter page, the regular character total includes spaces and line breaks, while the no-spaces total excludes whitespace. That means a paragraph break affects one total and not the other.&lt;/p&gt;

&lt;p&gt;Do not implement “without spaces” as &lt;code&gt;text.replaceAll(" ", "")&lt;/code&gt; unless a single ASCII space is truly the stated policy. Tabs, newlines, non-breaking spaces, and other Unicode whitespace may be present after a paste. For a broad whitespace rule, use a Unicode-aware regular expression:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;countWithoutWhitespace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nb"&gt;Array&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;from&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="se"&gt;\s&lt;/span&gt;&lt;span class="sr"&gt;/gu&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;""&lt;/span&gt;&lt;span class="p"&gt;)).&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This example deliberately returns code-point length. If the product promise is “visible characters without whitespace,” segment the filtered string into graphemes instead. Also decide whether line endings are normalized before validation. &lt;code&gt;\r\n&lt;/code&gt; is two code units but represents one line break in many environments; a copied Windows text block can otherwise surprise both users and tests.&lt;/p&gt;

&lt;h2&gt;
  
  
  Match the counter to the boundary
&lt;/h2&gt;

&lt;p&gt;The right question is: what will reject, store, bill for, or display this text? A database byte limit calls for encoded-byte validation using the exact database encoding. A JavaScript API documented in UTF-16 units may legitimately use &lt;code&gt;.length&lt;/code&gt;. A field described to ordinary readers as “up to 50 characters” should normally consider grapheme clusters, then explain exceptions if the receiving platform uses another rule.&lt;/p&gt;

&lt;p&gt;Avoid claiming that a generic counter predicts every external service. Some platforms use weighted limits, normalize text, treat URLs specially, or apply their own version of Unicode segmentation. The counter page offers presets for X posts, meta descriptions, Instagram captions, and essays, but a final paste into the destination remains the authoritative check.&lt;/p&gt;

&lt;p&gt;A useful UI exposes the chosen measure in the label itself: “50 visible characters,” “256 UTF-8 bytes,” or “280 platform units.” It should show the remaining amount from the same function used to block submission. A meter driven by graphemes alongside a server rule driven by bytes creates the exact off-by-several error it was meant to prevent.&lt;/p&gt;

&lt;h2&gt;
  
  
  Test strings belong in the suite
&lt;/h2&gt;

&lt;p&gt;ASCII-only tests make almost every length function look correct. Add cases for a supplementary-plane character, combining marks, a skin-tone sequence, a family emoji joined with zero-width joiners, tabs, line endings, and non-breaking spaces. Store the expected unit in the test name, not merely a bare integer.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Requirement&lt;/th&gt;
&lt;th&gt;A defensible measurement&lt;/th&gt;
&lt;th&gt;Test that catches the wrong choice&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Browser field limited by UTF-16 units&lt;/td&gt;
&lt;td&gt;&lt;code&gt;text.length&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;A supplementary-plane emoji&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Reader-facing symbol counter&lt;/td&gt;
&lt;td&gt;Grapheme segmentation&lt;/td&gt;
&lt;td&gt;A modified symbol or a joined emoji sequence&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;API or database byte quota&lt;/td&gt;
&lt;td&gt;&lt;code&gt;TextEncoder().encode(text).length&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Korean text or emoji&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Copy limit excluding whitespace&lt;/td&gt;
&lt;td&gt;Stated filter plus stated unit&lt;/td&gt;
&lt;td&gt;Tab and newline input&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;This vocabulary may feel fussy until a customer cannot submit a short-looking name, or a byte-limited request fails only for Korean or emoji text. Naming the unit turns a mysterious edge case into an inspectable contract.&lt;/p&gt;

&lt;h2&gt;
  
  
  FAQ
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Is &lt;code&gt;string.length&lt;/code&gt; a character count?
&lt;/h3&gt;

&lt;p&gt;It is a UTF-16 code-unit count. It matches a simple character count for many basic strings, but not for all Unicode text.&lt;/p&gt;

&lt;h3&gt;
  
  
  Does spreading a string count what a person sees?
&lt;/h3&gt;

&lt;p&gt;Not always. &lt;code&gt;[...text]&lt;/code&gt; counts code points, while one visible grapheme can contain several code points.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why do UTF-8 byte counts matter?
&lt;/h3&gt;

&lt;p&gt;They matter where an encoded payload has a byte ceiling, such as a protocol or storage boundary. They are not automatically a reader-facing character count.&lt;/p&gt;

&lt;h3&gt;
  
  
  Should spaces be removed before validation?
&lt;/h3&gt;

&lt;p&gt;Only when the stated rule excludes whitespace. Preserve the original text for display unless the product explicitly transforms it.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://charcount.fyi/en/" rel="noopener noreferrer"&gt;Compare words, character totals, whitespace-free totals, and UTF-8 bytes with our free counter&lt;/a&gt;&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>unicode</category>
      <category>strings</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Fancy Text Is Unicode Data: What Developers Should Know About Accessibility</title>
      <dc:creator>FUG</dc:creator>
      <pubDate>Fri, 09 Oct 2026 20:57:55 +0000</pubDate>
      <link>https://dev.to/neulketing/fancy-text-is-unicode-data-what-developers-should-know-about-accessibility-29h4</link>
      <guid>https://dev.to/neulketing/fancy-text-is-unicode-data-what-developers-should-know-about-accessibility-29h4</guid>
      <description>&lt;p&gt;As of October 8, 2026&lt;/p&gt;

&lt;h2&gt;
  
  
  A copied letter is not necessarily a font
&lt;/h2&gt;

&lt;p&gt;When someone pastes a bold-looking name into a social profile, there may be no CSS or custom typeface involved. Many so-called fancy fonts substitute characters from Unicode's Mathematical Alphanumeric Symbols range, which begins at U+1D400. This means the text itself changes. We make small free web tools, and we think that distinction should be visible to anyone using a text converter.&lt;/p&gt;

&lt;p&gt;Take an ordinary capital A and a mathematical bold capital 𝐀. They resemble one another visually, but they are different code points. Copying the second one into another application preserves that distinct character if the platform supports it. Changing the CSS font on the first A, by contrast, leaves its underlying character intact.&lt;/p&gt;

&lt;p&gt;Mathematical alphabet characters were designed to distinguish symbolic variables in mathematical notation, not to supply every social-media typography preference. The characters are useful in the right context, but they can introduce unexpected behavior in ordinary names and sentences.&lt;/p&gt;

&lt;h2&gt;
  
  
  A tiny JavaScript experiment
&lt;/h2&gt;

&lt;p&gt;The bold mathematical Latin capitals start at U+1D400. Basic Latin uppercase A through Z are consecutive, so a demonstration converter can use code-point arithmetic:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;mathBoldCapitals&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;input&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;[...&lt;/span&gt;&lt;span class="nx"&gt;input&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;char&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;code&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;char&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;codePointAt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;code&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="mi"&gt;65&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;code&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="mi"&gt;90&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nb"&gt;String&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fromCodePoint&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mh"&gt;0x1D400&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;code&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;65&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;char&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}).&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;""&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;mathBoldCapitals&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;HELLO 123&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;span class="c1"&gt;// 𝐇𝐄𝐋𝐋𝐎 123&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The spread syntax iterates Unicode code points rather than individual UTF-16 code units. That matters because mathematical bold capitals sit outside the Basic Multilingual Plane and are represented by surrogate pairs in JavaScript strings.&lt;/p&gt;

&lt;p&gt;This snippet intentionally handles only A–Z. Lowercase letters and digits have their own ranges, and some visual styles contain gaps or special historical exceptions. A production converter should use an explicit, tested mapping rather than assuming every decorative alphabet forms a single contiguous sequence.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why display, search, and speech can diverge
&lt;/h2&gt;

&lt;p&gt;Visual similarity does not establish text equivalence everywhere. Search engines, application search boxes, screen readers, and copy-paste pipelines may normalize styled letters differently. A person searching for an ordinary spelling might miss a display name composed entirely of mathematical symbols, depending on the service.&lt;/p&gt;

&lt;p&gt;Assistive technology introduces a different concern. A screen reader could announce a mathematical letter by its formal name, read it with an unexpected pause, or handle it as a familiar letter. Results vary across operating systems, language settings, and assistive technologies. There is no honest universal claim that every decorative character is inaccessible, or that all screen readers interpret it the same way.&lt;/p&gt;

&lt;p&gt;Unicode normalization also deserves attention. NFKC normalization can map many mathematical alphanumeric symbols back to their plain-letter equivalents. This can help with comparisons, but it is not something we can assume every social app uses consistently. Nor should software silently rewrite all text without considering the user's intent.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Text form&lt;/th&gt;
&lt;th&gt;What changes?&lt;/th&gt;
&lt;th&gt;Practical consequence&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;CSS bold&lt;/td&gt;
&lt;td&gt;Presentation&lt;/td&gt;
&lt;td&gt;Underlying letters remain ordinary&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Mathematical bold 𝐀&lt;/td&gt;
&lt;td&gt;Character code point&lt;/td&gt;
&lt;td&gt;Copy and search behavior can differ&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Decorative separators&lt;/td&gt;
&lt;td&gt;Extra symbols&lt;/td&gt;
&lt;td&gt;May create pauses in spoken output&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Plain Latin text&lt;/td&gt;
&lt;td&gt;Neither&lt;/td&gt;
&lt;td&gt;Most predictable across systems&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  A good compromise for profile names
&lt;/h2&gt;

&lt;p&gt;We recommend keeping a readable core name in ordinary characters and using stylized text sparingly. The name people should type into search should remain findable. Decorative symbols can frame a short subtitle instead of replacing all essential information. When important details are involved, test the pasted version with selection, search, and spoken output.&lt;/p&gt;

&lt;p&gt;Short profile bios are a better place for experimentation than security codes, addresses, registration forms, or instructions people must copy exactly. Fancy symbols can also be confusing in code snippets, usernames with strict character restrictions, and fields where normalization affects matching.&lt;/p&gt;

&lt;p&gt;For teams that publish public-facing content, we like a simple checklist: compare the standard and stylized versions, copy both into the destination application, inspect them on mobile, and try a screen reader if accessibility matters. The last step is particularly valuable because the environment where the text ends up is the one that counts.&lt;/p&gt;

&lt;h2&gt;
  
  
  What a generator can and cannot change
&lt;/h2&gt;

&lt;p&gt;A fancy text generator can replace supported letters and numbers with styled Unicode alternatives. It cannot make every language's complete script magically acquire equivalent mathematical letter sets. Decorations such as dividers, however, can sit alongside words in any language. That distinction prevents disappointment when someone pastes Japanese or Korean text expecting each character's strokes to change.&lt;/p&gt;

&lt;p&gt;Our free text tool offers 56 styles, including bold, script, gothic, bubble, and small caps, with a copy workflow for names, bios, and captions. It also has a matching-name option for friends. Its role is to provide options; the user still decides where special characters belong.&lt;/p&gt;

&lt;h2&gt;
  
  
  FAQ
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Are mathematical bold letters the same as applying bold formatting?
&lt;/h3&gt;

&lt;p&gt;No. They are different Unicode code points. CSS formatting changes appearance without replacing the underlying Latin letter.&lt;/p&gt;

&lt;h3&gt;
  
  
  Will screen readers always read fancy text incorrectly?
&lt;/h3&gt;

&lt;p&gt;No single result applies everywhere. Some combinations may be understandable, while others may announce symbol names. Testing is the reliable approach.&lt;/p&gt;

&lt;h3&gt;
  
  
  Can decorations work around non-Latin writing?
&lt;/h3&gt;

&lt;p&gt;Yes. A divider or surrounding ornament can be used with any language, even where the letters themselves are not restyled.&lt;/p&gt;

&lt;p&gt;If you want to inspect the difference by copying a few examples, use our &lt;a href="https://fancyfont.fyi/en/" rel="noopener noreferrer"&gt;free fancy text generator&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>unicode</category>
      <category>a11y</category>
      <category>javascript</category>
      <category>typography</category>
    </item>
    <item>
      <title>QR Error Correction, Center Logos, and the Difference Between Static and Redirect Codes</title>
      <dc:creator>FUG</dc:creator>
      <pubDate>Thu, 08 Oct 2026 20:57:50 +0000</pubDate>
      <link>https://dev.to/neulketing/qr-error-correction-center-logos-and-the-difference-between-static-and-redirect-codes-196a</link>
      <guid>https://dev.to/neulketing/qr-error-correction-center-logos-and-the-difference-between-static-and-redirect-codes-196a</guid>
      <description>&lt;p&gt;As of October 8, 2026&lt;/p&gt;

&lt;h2&gt;
  
  
  What a QR code can recover
&lt;/h2&gt;

&lt;p&gt;When we add a logo to a QR code, we are deliberately covering part of the symbol. That sounds like an error, because it is one. The reason a phone can sometimes read the result is that QR codes encode redundant information using Reed–Solomon error correction. We build free web tools, and this is one of the places where understanding a small technical detail makes a large practical difference.&lt;/p&gt;

&lt;p&gt;QR codes offer four commonly named error correction levels: L, M, Q, and H. Higher correction adds redundancy, but it also takes capacity. Given the same content, a higher level may require a denser symbol. The percentages typically associated with each level describe approximate recoverable damage to codewords, &lt;strong&gt;not&lt;/strong&gt; a guarantee that a logo covering that percentage of the visible square is safe.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Level&lt;/th&gt;
&lt;th&gt;Approximate recovery&lt;/th&gt;
&lt;th&gt;Typical decision&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;L&lt;/td&gt;
&lt;td&gt;7%&lt;/td&gt;
&lt;td&gt;Clean, unobstructed environments&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;M&lt;/td&gt;
&lt;td&gt;15%&lt;/td&gt;
&lt;td&gt;General-purpose codes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Q&lt;/td&gt;
&lt;td&gt;25%&lt;/td&gt;
&lt;td&gt;Modest decoration with testing&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;H&lt;/td&gt;
&lt;td&gt;30%&lt;/td&gt;
&lt;td&gt;More margin for a centered logo&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;A central logo is particularly unforgiving when it overlaps functional patterns or when the code is already dense. The three large position markers, their surrounding quiet regions, and other structural elements are essential. Error correction cannot repair every arbitrary shape of damage.&lt;/p&gt;

&lt;h2&gt;
  
  
  A minimal implementation
&lt;/h2&gt;

&lt;p&gt;The familiar open-source JavaScript &lt;code&gt;qrcode&lt;/code&gt; package allows a level to be selected explicitly. For a simple test page, an example looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;QRCode&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;qrcode&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;destination&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;https://example.org/info&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;pngDataUrl&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;QRCode&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toDataURL&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;destination&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;errorCorrectionLevel&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;H&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;margin&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;640&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;querySelector&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;#preview&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;src&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;pngDataUrl&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This example generates a normal QR image. It does &lt;strong&gt;not&lt;/strong&gt; add a logo or prove that one is safe. If your UI overlays a logo afterward, you need a separate scan test of the finished composite. Keep the destination short when possible, because longer payloads can produce more modules. Test with multiple phones, camera distances, and lighting conditions, including the worst case you expect on paper.&lt;/p&gt;

&lt;p&gt;The margin option matters too. A QR code needs a quiet zone, usually at least four modules of uninterrupted background around the symbol. Cropping tightly for visual balance can break recognition even when the central data remains untouched.&lt;/p&gt;

&lt;h2&gt;
  
  
  Static payloads versus redirect services
&lt;/h2&gt;

&lt;p&gt;A static QR code contains the destination itself, such as a URL, Wi-Fi configuration, or contact fields. A redirect-based dynamic service commonly puts its own short URL in the symbol and forwards scans to the destination configured on its server. That approach can make the destination editable, but it also adds a service dependency.&lt;/p&gt;

&lt;p&gt;People often say a static QR code never expires. More precisely, the &lt;strong&gt;encoded data&lt;/strong&gt; does not carry a service-imposed expiration date. The destination website can still shut down, a domain can lapse, a Wi-Fi password can change, or an event can end. The pattern on the page stays the same, but what it points to may stop working.&lt;/p&gt;

&lt;p&gt;For a long-lived poster, we prefer an address the publisher controls. We also suggest printing the human-readable URL nearby. A scan failure should not make the information inaccessible.&lt;/p&gt;

&lt;h2&gt;
  
  
  PNG or SVG for production?
&lt;/h2&gt;

&lt;p&gt;PNG is a raster image. Its quality depends on the exported pixel dimensions relative to the size at which it is printed. A sufficiently large PNG can work perfectly well on a small card, but enlarging a small export introduces blurry edges. SVG uses vectors, so a designer can scale it to a larger sign without interpolation blur.&lt;/p&gt;

&lt;p&gt;Neither format rescues poor contrast, an undersized quiet zone, or an excessively ornate mark. Dark modules on a light background remain the safest starting point. Color combinations should be inspected in grayscale as well as on screen. A glossy surface or reflected light can undermine an apparently strong design.&lt;/p&gt;

&lt;p&gt;When handing files to a print shop, communicate the final physical dimensions. Exporting a huge bitmap without knowing the target size is less useful than confirming the final module size and printing a proof.&lt;/p&gt;

&lt;h2&gt;
  
  
  Our review routine before putting a QR code in public
&lt;/h2&gt;

&lt;p&gt;We first generate an undecorated version and scan it. Then we raise the error correction level if a logo is necessary, keep the logo compact, and scan the resulting composition. We check the printed proof rather than trusting a monitor preview. Finally, we verify the exact payload, including capitalization and punctuation in the destination URL.&lt;/p&gt;

&lt;p&gt;We treat QR codes as interfaces, not decoration. Their job is to transfer a small piece of information reliably. A beautiful code that nobody can scan fails that job.&lt;/p&gt;

&lt;h2&gt;
  
  
  FAQ
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Does level H guarantee that a large logo will scan?
&lt;/h3&gt;

&lt;p&gt;No. H adds more recovery capacity, but location, density, contrast, printing, and decoder behavior still matter. The finished code must be tested.&lt;/p&gt;

&lt;h3&gt;
  
  
  Does a static QR code need an account or monthly renewal?
&lt;/h3&gt;

&lt;p&gt;The encoded pattern needs neither. Whether the resource at the destination remains available is a separate question.&lt;/p&gt;

&lt;h3&gt;
  
  
  Which download should we use for a poster?
&lt;/h3&gt;

&lt;p&gt;SVG is convenient for scaling in a print layout. A large, sharply rendered PNG may also work when the final dimensions are known.&lt;/p&gt;

&lt;p&gt;We made a free browser-based QR generator with nine payload types, options for logos, colors, and shapes, and PNG or SVG downloads. Codes are generated in the browser without a redirect layer; it also includes a separate group check-in room feature. Try the &lt;a href="https://makeqr.fyi/en/" rel="noopener noreferrer"&gt;free QR generator&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>qrcode</category>
      <category>javascript</category>
      <category>printing</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Check a Password Against a Breach List Without Sending the Password</title>
      <dc:creator>FUG</dc:creator>
      <pubDate>Wed, 07 Oct 2026 20:11:23 +0000</pubDate>
      <link>https://dev.to/neulketing/check-a-password-against-a-breach-list-without-sending-the-password-4edf</link>
      <guid>https://dev.to/neulketing/check-a-password-against-a-breach-list-without-sending-the-password-4edf</guid>
      <description>&lt;p&gt;As of October 8, 2026&lt;/p&gt;

&lt;p&gt;A password check can query a breach list without putting the password in an HTTP request. The browser hashes the input, sends five hexadecimal characters, and checks the returned candidates locally. We build small web tools, and we like this approach because the network boundary is explicit enough to inspect.&lt;/p&gt;

&lt;p&gt;Have I Been Pwned provides this pattern through its Pwned Passwords range endpoint. The useful question for developers is what crosses that boundary, what stays in memory, and what the answer actually establishes.&lt;/p&gt;

&lt;h2&gt;
  
  
  Follow the value through the browser
&lt;/h2&gt;

&lt;p&gt;SHA-1 produces a 160-bit digest, represented here as 40 hexadecimal characters. Split that string into a five-character prefix and a 35-character suffix. Request &lt;code&gt;https://api.pwnedpasswords.com/range/{prefix}&lt;/code&gt;, substituting the prefix. The response contains candidate suffixes and their occurrence counts.&lt;/p&gt;

&lt;p&gt;The browser compares its own suffix with those candidates. It never needs to transmit the remaining hash characters to finish the lookup.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Value&lt;/th&gt;
&lt;th&gt;Where it is used&lt;/th&gt;
&lt;th&gt;Sent in the range request?&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Original password&lt;/td&gt;
&lt;td&gt;Browser input and hashing&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Complete SHA-1 hash&lt;/td&gt;
&lt;td&gt;Browser memory&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;First five hexadecimal characters&lt;/td&gt;
&lt;td&gt;Range URL&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Remaining 35 characters&lt;/td&gt;
&lt;td&gt;Local comparison&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Candidate suffixes and counts&lt;/td&gt;
&lt;td&gt;Response parsing&lt;/td&gt;
&lt;td&gt;Returned by the service&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;This is the k-anonymity lookup pattern: one prefix identifies a group of candidate hashes rather than identifying the complete hash in the request. It reduces disclosure. It does not make the request anonymous or hide ordinary network metadata from the service.&lt;/p&gt;

&lt;h2&gt;
  
  
  A browser function you can run
&lt;/h2&gt;

&lt;p&gt;Use this on an HTTPS page or localhost, where the browser's Web Crypto API is available. Call it from a submit handler with the password field's value. For experiments, use a disposable test string rather than a real credential.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;breachCount&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;password&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;bytes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;TextEncoder&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;encode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;password&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;digest&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;crypto&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;subtle&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;digest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;SHA-1&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;bytes&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;hash&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Array&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;from&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Uint8Array&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;digest&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="nx"&gt;byte&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;
    &lt;span class="nx"&gt;byte&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;16&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;padStart&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;0&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;''&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toUpperCase&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;prefix&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;hash&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;slice&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;suffix&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;hash&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;slice&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="s2"&gt;`https://api.pwnedpasswords.com/range/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;prefix&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Add-Padding&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;true&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ok&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`Lookup failed: HTTP &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;lines&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;text&lt;/span&gt;&lt;span class="p"&gt;()).&lt;/span&gt;&lt;span class="nf"&gt;trim&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="se"&gt;\r?\n&lt;/span&gt;&lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;line&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nx"&gt;lines&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;candidate&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;count&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;line&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;trim&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;:&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;candidate&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="nx"&gt;suffix&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nc"&gt;Number&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;count&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The function resolves to an occurrence count, or zero if its suffix is absent from a successful response. Network failures reject the promise; unsuccessful HTTP responses throw. A calling interface should catch those errors and show “check unavailable.” Turning a failed request into zero would give an unsupported reassurance.&lt;/p&gt;

&lt;p&gt;The optional &lt;code&gt;Add-Padding: true&lt;/code&gt; request header asks for padding entries, reducing information exposed through response size. Padding entries have zero counts. Our exact suffix comparison therefore still produces zero for a matching padding entry. The endpoint and padding behavior are documented in the &lt;a href="https://haveibeenpwned.com/API/v3#SearchingPwnedPasswordsByRange" rel="noopener noreferrer"&gt;official API reference&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Keep the surrounding interface honest
&lt;/h2&gt;

&lt;p&gt;Preserve the entered string exactly. Trimming whitespace or changing capitalization before hashing checks a different password. The uppercase conversion in the snippet applies only to the hexadecimal representation of the digest, so it does not alter the password itself.&lt;/p&gt;

&lt;p&gt;We would trigger this check after an explicit action rather than sending a request for every keystroke. Repeated prefixes from partially typed input disclose more than a single completed check. Keep password values out of URLs, console output, analytics events, and error reports, too.&lt;/p&gt;

&lt;p&gt;Local hashing describes this lookup mechanism. It cannot guarantee that every other script running on the page behaves safely. A browser implementation still needs a trustworthy page and careful handling of the input. SHA-1 also appears here for compatibility with the lookup dataset; it is not a suitable recommendation for storing account passwords.&lt;/p&gt;

&lt;h2&gt;
  
  
  Read the result as one piece of evidence
&lt;/h2&gt;

&lt;p&gt;A positive count says the password appears in the dataset. It does not identify which of the reader's accounts was affected, and the count is not a count of breaches involving that particular reader.&lt;/p&gt;

&lt;p&gt;Zero means no match was returned for this check. A new but predictable password can still be weak. Password strength estimation is a separate task, and a time-to-crack estimate depends on assumptions about guessing methods and attack conditions. Neither a score nor an estimated duration is a guarantee.&lt;/p&gt;

&lt;p&gt;Our free checker follows the local SHA-1 and five-character-prefix flow. It shows the breach count alongside a 0–100 security score and a time-to-crack estimate, and includes a strong-password generator. We describe those outputs separately so the lookup result does not masquerade as a complete security assessment.&lt;/p&gt;

&lt;h2&gt;
  
  
  FAQ
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Does the service receive my password or its full hash?
&lt;/h3&gt;

&lt;p&gt;In the range request shown above, it receives the first five hexadecimal characters only. The password, full hash, and suffix comparison remain in the browser. That statement concerns this code path, not every possible script on a page.&lt;/p&gt;

&lt;h3&gt;
  
  
  Is a password safe if the count is zero?
&lt;/h3&gt;

&lt;p&gt;No. Absence from the returned dataset does not establish strength or uniqueness. We would still avoid reuse and choose a long, unpredictable password for each account.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why use SHA-1 when it is unsuitable for password storage?
&lt;/h3&gt;

&lt;p&gt;Here it creates a lookup identifier compatible with the dataset. Secure password storage has different requirements, including resistance to repeated guessing. Reusing this snippet as a password storage design would confuse those two jobs.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://pwcheck.fyi/en/" rel="noopener noreferrer"&gt;Check a password with our free tool&lt;/a&gt;&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>security</category>
      <category>privacy</category>
      <category>webdev</category>
    </item>
  </channel>
</rss>
