<?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: Bazi Clarity</title>
    <description>The latest articles on DEV Community by Bazi Clarity (@baziclarity).</description>
    <link>https://dev.to/baziclarity</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%2F4053512%2Feb9b405d-7c58-42d7-9032-51004a4d526d.png</url>
      <title>DEV Community: Bazi Clarity</title>
      <link>https://dev.to/baziclarity</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/baziclarity"/>
    <language>en</language>
    <item>
      <title>Testing a Two-Chart Compatibility Engine Without Inventing Missing Data</title>
      <dc:creator>Bazi Clarity</dc:creator>
      <pubDate>Fri, 31 Jul 2026 18:00:23 +0000</pubDate>
      <link>https://dev.to/baziclarity/testing-a-two-chart-compatibility-engine-without-inventing-missing-data-3e38</link>
      <guid>https://dev.to/baziclarity/testing-a-two-chart-compatibility-engine-without-inventing-missing-data-3e38</guid>
      <description>&lt;p&gt;A compatibility engine can be deterministic and still be dishonest. The obvious failure is a wrong calculation. The subtler failure is unjustified certainty: turning a blank birth time into noon, allowing a nickname to change a result, or comparing an Hour Branch that was never calculated.&lt;/p&gt;

&lt;p&gt;This tutorial describes a test strategy for a two-chart pipeline. The domain example is BaZi, a traditional symbolic framework for reflection. It is not scientifically validated as a prediction method. The fixture below is fictional software test data, not a customer story. Its public evidence repository is brand-owned by Bazi Clarity, so it is implementation evidence—not independent validation or social proof.&lt;/p&gt;

&lt;p&gt;The engineering goal is narrow: given two input records, preserve what is known, expose what is unknown, and make every comparison reproducible.&lt;/p&gt;

&lt;h2&gt;
  
  
  Define the integrity contract before the formula
&lt;/h2&gt;

&lt;p&gt;Start with invariants, not an attractive compatibility percentage. A useful contract for this pipeline is:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;calculate Person A and Person B independently;&lt;/li&gt;
&lt;li&gt;use birth date, optional time, and resolved place data for each chart;&lt;/li&gt;
&lt;li&gt;never infer an Hour Pillar from a missing time;&lt;/li&gt;
&lt;li&gt;keep names outside every numeric and symbolic calculation;&lt;/li&gt;
&lt;li&gt;compare only stems, branches, and element data that exist;&lt;/li&gt;
&lt;li&gt;return observations and reflection prompts, not a verdict.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These rules define which outputs are allowed. They also produce strong negative tests. It is easy to assert that a result contains a Day Master. It is more valuable to assert that it contains no &lt;code&gt;score&lt;/code&gt;, no assumed hour, and no relationship guarantee.&lt;/p&gt;

&lt;h2&gt;
  
  
  Calculate two charts before comparing either one
&lt;/h2&gt;

&lt;p&gt;The safest architecture is a composition of three boundaries:&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;leftChart&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;calculateNatal&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="nx"&gt;leftInput&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;rightChart&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;calculateNatal&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="nx"&gt;rightInput&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;comparison&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;compareKnownFacts&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;leftChart&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;rightChart&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;calculateNatal&lt;/code&gt; should know nothing about the other person. &lt;code&gt;compareKnownFacts&lt;/code&gt; should receive calculated chart objects, not raw form fields. That separation prevents Person A's city, time setting, or display name from leaking into Person B's chart.&lt;/p&gt;

&lt;p&gt;A metamorphic test makes this visible: change only the left record and assert that the serialized right chart remains byte-for-byte equal. Then swap the input order and assert that each natal chart still matches its original owner. If either test fails, the comparison layer is contaminating chart construction.&lt;/p&gt;

&lt;h2&gt;
  
  
  Make unknown time an explicit state
&lt;/h2&gt;

&lt;p&gt;An empty string is too easy to coerce into a convenient default. Represent time precision in the input contract:&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;known&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;time&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;09:30&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;timePrecision&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;exact&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;unknown&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;time&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="na"&gt;timePrecision&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&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;An unknown birth time must produce no Hour Pillar. A compact serialized boundary is &lt;code&gt;{ label: "Hour", unknown: true }&lt;/code&gt;. It should survive calculation, caching, rendering, export, and later comparison.&lt;/p&gt;

&lt;p&gt;The comparison code can filter unavailable pillars before building cross-chart pairs:&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;available&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;pillars&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;pillars&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;p&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;unknown&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;left&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nf"&gt;available&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;leftChart&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;pillars&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="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;right&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nf"&gt;available&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;rightChart&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;pillars&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;inspectBranchPair&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;left&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;right&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Test both directions: left time unknown and right time unknown. Also test that an explicitly supplied &lt;code&gt;12:00&lt;/code&gt; differs from unknown time. Noon may be a real user value; it must never double as a missing-data sentinel.&lt;/p&gt;

&lt;h2&gt;
  
  
  Prove that names cannot change the result
&lt;/h2&gt;

&lt;p&gt;Names are display metadata, not calculation inputs or compatibility score ingredients. This is best protected with an invariance test rather than a code comment.&lt;/p&gt;

&lt;p&gt;Create two requests with identical birth facts and different labels—full name, nickname, Unicode characters, and an empty label. Remove display-only fields from the response and assert deep equality across all variants. Run the same test on both sides of the pair.&lt;/p&gt;

&lt;p&gt;Do not hash a whole form object if it contains a name. Build a canonical calculation payload from an allowlist of relevant fields. This keeps cache identity aligned with the actual model and prevents a renamed person from generating a supposedly different chart.&lt;/p&gt;

&lt;p&gt;The public model also omits a compatibility percentage. That is a separate contract: assert that &lt;code&gt;score&lt;/code&gt; is absent, rather than merely hiding it in the interface.&lt;/p&gt;

&lt;h2&gt;
  
  
  Treat birthplace as a resolution pipeline
&lt;/h2&gt;

&lt;p&gt;A city label alone is not enough. It should resolve to an IANA time zone plus latitude and longitude. For a supplied clock time, the engine applies the historical UTC offset, calculates the disclosed true solar time correction, and reports a DST flag from offset comparisons. That flag is not a complete classification of every historical civil-time change.&lt;/p&gt;

&lt;p&gt;Those values have boundaries. An IANA time zone represents civil-time history, not longitude. Coordinates support the meridian correction, but they do not reveal a missing clock time. The equation-of-time adjustment is date-dependent, and different BaZi schools may choose different solar-time conventions. Version and disclose the chosen profile instead of presenting it as universal.&lt;/p&gt;

&lt;p&gt;Tests should include dates on both sides of a DST transition, places with the same zone but different longitudes, and times close to a two-hour branch boundary. A nearby-city change may leave the final pillar unchanged; the test should still prove that the intermediate resolution used the selected coordinates.&lt;/p&gt;

&lt;h2&gt;
  
  
  Compare only facts that survived calculation
&lt;/h2&gt;

&lt;p&gt;Once both charts exist, the comparison layer can describe a limited set of relationships: the two Day Master elements, each chart's Five Element snapshot, and combinations, clashes, or harms among known branches.&lt;/p&gt;

&lt;p&gt;The fictional regression pair uses a known time for Shanghai and an unknown time for New York. The right chart therefore contains Year, Month, and Day Pillars plus an unknown Hour marker. Known branches can still participate in comparisons. The missing right Hour Branch cannot.&lt;/p&gt;

&lt;p&gt;That distinction matters when reading an interaction list. An interaction between the known left Hour Branch and the known right Year Branch is allowed. An interaction involving the right Hour position would be fabricated. Assert every returned interaction against the set of available pillar labels for its own side.&lt;/p&gt;

&lt;p&gt;Element summaries should also remain separate. Combining two distributions into one relationship grade destroys provenance. Preserve the left and right raw contributions, seasonal weights, rounded percentages, strongest element, and weakest element as two inspectable objects.&lt;/p&gt;

&lt;h2&gt;
  
  
  Build one fixture that tells a complete story
&lt;/h2&gt;

&lt;p&gt;A useful integration fixture stores the inputs, expected natal charts, expected comparison, method version, and source hashes together. It should be large enough to cross the important boundaries without pretending to be a representative human case.&lt;/p&gt;

&lt;p&gt;For the mixed-time fixture, assert at least these facts:&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="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;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;leftChart&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;timeKnown&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="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;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;rightChart&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;timeKnown&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;false&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;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;rightChart&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;pillars&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="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;label&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Hour&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;unknown&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="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;score&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;comparison&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;false&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;match&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;comparison&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;confidence&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;notice&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sr"&gt;/provided data/i&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then validate interaction provenance. Each referenced branch must equal the branch stored at the referenced Year, Month, Day, or known Hour position. This catches an easy snapshot-testing blind spot: a list can look stable while silently containing a value derived from a fallback hour.&lt;/p&gt;

&lt;h2&gt;
  
  
  Hash evidence, but understand what the hash proves
&lt;/h2&gt;

&lt;p&gt;Canonical JSON plus SHA-256 can make silent fixture changes detectable. Sort object keys recursively, serialize the value, and compare its digest with a checked-in manifest. Recorded source hashes are provenance references, not independently reproducible unless the matching source and build procedure are public.&lt;/p&gt;

&lt;p&gt;This shows that the package matches its checked-in manifest. Detecting changes relative to a reviewed release also requires an independently retained digest. It does not prove that a traditional interpretation is scientifically true, that every time-zone record is perfect, or that the software covers every BaZi school. Integrity and validity are different claims.&lt;/p&gt;

&lt;p&gt;Run the static verifier in a clean checkout. It should recalculate package hashes, confirm the unknown-hour object, reject a score field, and scan for prohibited verdict language. It checks package consistency; it does not rerun the production engine or validate recorded source hashes.&lt;/p&gt;

&lt;h2&gt;
  
  
  Add negative vectors for input boundaries
&lt;/h2&gt;

&lt;p&gt;Happy-path snapshots are not enough. Reject a city without a time zone, an invalid date, or a location without valid longitude. Treat ambiguous civil time according to an explicit policy. The current engine deliberately downgrades an "exact" flag with empty time to the explicit unknown-time state; test that behavior so it never becomes an invented hour. Rejecting the mismatch would be a versioned contract change.&lt;/p&gt;

&lt;p&gt;Property-style tests add useful pressure. Changing names must leave the model unchanged. Repeating identical inputs must produce identical canonical output. Removing a known time may remove Hour-dependent findings but must not add any. No branch interaction may reference an unknown pillar. These relations remain valuable even when the expected Chinese calendar values differ under another disclosed school profile.&lt;/p&gt;

&lt;h2&gt;
  
  
  Keep interpretation outside the evidence claim
&lt;/h2&gt;

&lt;p&gt;The deterministic layer can prove which inputs and rules produced which chart facts. The text layer may turn those facts into neutral prompts about support, boundaries, or repairing tension. It must not turn software reproducibility into a soulmate label, clinical judgment, or guaranteed outcome.&lt;/p&gt;

&lt;p&gt;That boundary belongs in tests and interface copy. A paid or longer explanation can add context, but it cannot create missing birth data. Real-world compatibility still depends on behavior, consent, communication, safety, and circumstances that no natal calculation measures.&lt;/p&gt;

&lt;h2&gt;
  
  
  Review the published evidence package
&lt;/h2&gt;

&lt;p&gt;The &lt;a href="https://github.com/ming33925sz-ux/baziclarity-calculation-evidence/blob/main/COMPATIBILITY_CALCULATION.md" rel="noopener noreferrer"&gt;compatibility calculation evidence guide&lt;/a&gt; contains the fictional mixed-time vector, manifest, and verifier described here. You can also &lt;a href="https://baziclarity.com/compatibility?utm_source=devto&amp;amp;utm_medium=organic_article&amp;amp;utm_campaign=compatibility_engine&amp;amp;utm_content=free_comparison" rel="noopener noreferrer"&gt;try the free two-chart comparison&lt;/a&gt; without supplying an email; birth time remains optional for either chart.&lt;/p&gt;

&lt;p&gt;The durable lesson applies beyond BaZi: missing data is a fact about your data. Model it, preserve it, and test that no later layer upgrades uncertainty into certainty.&lt;/p&gt;

&lt;p&gt;Disclosure: AI assistance was used to draft and edit this tutorial. Technical claims and links were checked against the brand-owned evidence package and its static consistency verifier before publication.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>javascript</category>
      <category>testing</category>
      <category>opensource</category>
    </item>
    <item>
      <title>Why Birth City Is a Real Calculation Input in BaZi Software</title>
      <dc:creator>Bazi Clarity</dc:creator>
      <pubDate>Thu, 30 Jul 2026 06:52:59 +0000</pubDate>
      <link>https://dev.to/baziclarity/why-birth-city-is-a-real-calculation-input-in-bazi-software-5g59</link>
      <guid>https://dev.to/baziclarity/why-birth-city-is-a-real-calculation-input-in-bazi-software-5g59</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;Time zones, daylight-saving rules, longitude, solar terms, and true solar time are not optional details in a transparent calculator.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  The city field is part of the calculation
&lt;/h2&gt;

&lt;p&gt;Many online forms ask for a birth city, but the result would be unchanged if you typed a different place. That makes the field decorative rather than meaningful.&lt;/p&gt;

&lt;p&gt;In a transparent BaZi calculator, a city field is a computational input. It helps identify the civil time zone that applied at the birthplace, the historical daylight-saving rule for that date, and the location’s longitude. Those details can change the time used to determine a pillar, especially when a birth falls close to an hour or solar-term boundary.&lt;/p&gt;

&lt;p&gt;This does not mean that changing a nearby city will always produce a different chart. Often it will not. The important question is whether the location is actually used, whether the correction is disclosed, and whether a boundary-sensitive result can be reproduced.&lt;/p&gt;

&lt;h2&gt;
  
  
  Civil time, UTC, and history are different layers
&lt;/h2&gt;

&lt;p&gt;A birth certificate normally records local civil time: the clock time recognized by local authorities at that place and date. It is not automatically the same as mean solar time, and it is not useful globally until it can be related to a UTC offset.&lt;/p&gt;

&lt;p&gt;That offset is not always the modern offset shown by a phone today. Historical time-zone rules matter. Countries and regions have changed offsets, moved borders, introduced daylight-saving time, suspended it, or used it only in selected years. A calculator that applies the current offset to every historical date can silently shift the underlying timestamp.&lt;/p&gt;

&lt;p&gt;A responsible workflow therefore starts with a place, resolves an IANA time zone identifier, applies the rule in force on the stated date, and records the resulting UTC conversion. The calculation should also handle ambiguous or nonexistent clock times around seasonal clock changes. If two instants shared the same clock label, the interface should explain how the ambiguity was resolved rather than quietly choosing one.&lt;/p&gt;

&lt;p&gt;No public location database is perfect for every place and era. A responsible service cannot promise universal historical completeness. It can state which database and version it uses, disclose fallback behavior, and flag cases that need extra caution.&lt;/p&gt;

&lt;h2&gt;
  
  
  Daylight-saving time must follow the date
&lt;/h2&gt;

&lt;p&gt;Daylight-saving time is a historical rule, not a permanent property of a city. “New York is UTC−5” is only a partial statement: the applicable historical UTC offset depends on the date, and past transition dates have not always matched today’s schedule.&lt;/p&gt;

&lt;p&gt;The same issue appears in many countries, including places that tried seasonal clock changes briefly and later abandoned them. A checkbox asking the user to remember whether daylight-saving time applied may be better than ignoring the issue, but it still transfers a difficult historical lookup to the person least likely to know the answer.&lt;/p&gt;

&lt;p&gt;An auditable calculator should derive the rule from the birthplace and date, show the selected zone and offset, and let the user report a known discrepancy. It should never imply that a city name alone guarantees a perfectly resolved historical record.&lt;/p&gt;

&lt;h2&gt;
  
  
  Longitude and true solar time require disclosure
&lt;/h2&gt;

&lt;p&gt;Civil time zones cover wide areas, yet the sun does not cross the local meridian at the same clock minute across an entire zone. Longitude provides a correction between a zone’s reference meridian and the birthplace. Some BaZi traditions also apply the equation of time, which reflects the seasonal difference between apparent and mean solar time.&lt;/p&gt;

&lt;p&gt;Together, these adjustments are often described as true solar time. They can move a recorded time across a two-hour branch boundary in some cases. However, not every BaZi school or convention applies the same correction. The honest approach is not to present one choice as universally settled; it is to name the convention, show the corrected time, and allow another analyst to reproduce it.&lt;/p&gt;

&lt;p&gt;A useful result should display at least the recorded civil time, resolved time zone, UTC offset, location coordinates, any daylight-saving adjustment, the solar-time correction method, and the final time used for the pillars. Without that chain, a polished chart may still be impossible to audit.&lt;/p&gt;

&lt;h2&gt;
  
  
  Solar terms can affect more than the hour
&lt;/h2&gt;

&lt;p&gt;BaZi year and month pillars are not simply copied from January 1 or the first day of a Gregorian month. Common calculation systems use solar terms as boundaries. A birth near Li Chun or another relevant term may therefore sit on a different side of the year or month boundary than a simple calendar lookup suggests, affecting the year or month pillar.&lt;/p&gt;

&lt;p&gt;The boundary has an exact instant, which must be interpreted in the correct local context. A wrong UTC conversion, an ignored daylight-saving rule, or an undocumented solar-time adjustment can place a near-boundary birth on the other side. That is why solar-term boundaries, time-zone history, and location handling belong in one calculation pipeline rather than in separate decorative features.&lt;/p&gt;

&lt;p&gt;Most births are not close enough to a boundary for every small correction to change a pillar. Transparency still matters: the system should show the boundary instant and the birth instant it compared, not claim precision while hiding the comparison.&lt;/p&gt;

&lt;h2&gt;
  
  
  When the birth time is unknown
&lt;/h2&gt;

&lt;p&gt;If the birth time is unknown, the correct response is to preserve that uncertainty. A calculator should build a three-pillar view from the known date and location, omit the Hour Pillar, and label any time-dependent interpretation as unavailable.&lt;/p&gt;

&lt;p&gt;Inventing noon, midnight, or a random hour creates false precision. It can affect the hour branch, hidden stems, relationship patterns, and any later interpretation built on them. A time-unknown chart may be less complete, but it is more trustworthy than a four-pillar chart based on a fabricated input.&lt;/p&gt;

&lt;p&gt;The interface should also distinguish “unknown” from a blank field accidentally submitted under an “exact time” setting. Those are different states and deserve different validation messages.&lt;/p&gt;

&lt;h2&gt;
  
  
  A practical calculator checklist
&lt;/h2&gt;

&lt;p&gt;Before relying on a BaZi result, ask:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Does changing the birthplace affect the disclosed time-zone or longitude data?&lt;/li&gt;
&lt;li&gt;Is the historical UTC offset shown for the actual birth date?&lt;/li&gt;
&lt;li&gt;Is daylight-saving time derived from a dated rule rather than a modern assumption?&lt;/li&gt;
&lt;li&gt;Are coordinates and the true-solar-time convention visible?&lt;/li&gt;
&lt;li&gt;Are near-boundary solar terms shown with precise comparison times?&lt;/li&gt;
&lt;li&gt;Does an unknown time remain unknown, without an invented Hour Pillar?&lt;/li&gt;
&lt;li&gt;Can the same inputs reproduce the same pillars and intermediate values?&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;You can inspect these details in the &lt;a href="https://baziclarity.com/free-reading?utm_source=devto&amp;amp;utm_medium=organic_article&amp;amp;utm_campaign=birth_city_calculation&amp;amp;utm_content=free_chart" rel="noopener noreferrer"&gt;free chart&lt;/a&gt;, read the documented &lt;a href="https://baziclarity.com/methodology?utm_source=devto&amp;amp;utm_medium=organic_article&amp;amp;utm_campaign=birth_city_calculation&amp;amp;utm_content=methodology" rel="noopener noreferrer"&gt;calculation methodology&lt;/a&gt;, and review the public &lt;a href="https://github.com/ming33925sz-ux/baziclarity-calculation-evidence" rel="noopener noreferrer"&gt;calculation-evidence repository&lt;/a&gt;. The repository’s evidence vectors are fictional software tests created to check reproducibility and boundary behavior; they are not customer stories or proof of predictive accuracy.&lt;/p&gt;

&lt;h2&gt;
  
  
  What reproducible calculation can—and cannot—show
&lt;/h2&gt;

&lt;p&gt;A deterministic pipeline can show that the same stated inputs, rules, and data version produce the same calendar result. It can expose where a time-zone correction, solar-term boundary, or unknown input affected the output. That is valuable engineering evidence.&lt;/p&gt;

&lt;p&gt;BaZi is not a scientifically validated prediction method. It is a traditional symbolic framework, and interpretations should be treated as reflective material rather than guaranteed forecasts. They are not substitutes for qualified medical, legal, financial, or psychological advice, and they cannot guarantee an outcome.&lt;/p&gt;

&lt;p&gt;The strongest standard is therefore twofold: make the calendar calculation reproducible, and keep interpretive claims proportionate. Birth city matters because transparent inputs and visible transformations let a reader understand how a chart was constructed—even when the final symbolic meaning remains a matter of tradition and personal reflection.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>datascience</category>
      <category>opensource</category>
      <category>programming</category>
    </item>
    <item>
      <title>Unknown Time Is Not Noon: Modeling Missing Temporal Data Without Inventing Facts</title>
      <dc:creator>Bazi Clarity</dc:creator>
      <pubDate>Wed, 29 Jul 2026 15:35:19 +0000</pubDate>
      <link>https://dev.to/baziclarity/unknown-time-is-not-noon-modeling-missing-temporal-data-without-inventing-facts-198d</link>
      <guid>https://dev.to/baziclarity/unknown-time-is-not-noon-modeling-missing-temporal-data-without-inventing-facts-198d</guid>
      <description>&lt;p&gt;Missing data is not the same thing as a convenient default. That sounds obvious, yet temporal software regularly converts an empty time field into midnight, noon, the current time, or the start of a day. The interface may look complete after that conversion, but the program has silently changed an unknown fact into a known one.&lt;/p&gt;

&lt;p&gt;This matters anywhere an hour can change the result: medical timelines, transport schedules, legal deadlines, astronomical calculations, historical records, and calendrical systems. I encountered the problem while working with a BaZi calculation pipeline. A BaZi chart can use year, month, day, and hour components. If the birth time is absent, the honest result is a three-component analysis with hour-dependent conclusions withheld. Inserting noon would make the output look richer while making its provenance weaker.&lt;/p&gt;

&lt;p&gt;The useful engineering question is not “Which fallback time should we choose?” It is “How do we keep uncertainty visible through every layer of the system?”&lt;/p&gt;

&lt;p&gt;The public &lt;a href="https://github.com/ming33925sz-ux/baziclarity-calculation-evidence" rel="noopener noreferrer"&gt;calculation evidence repository&lt;/a&gt; provides the concrete calendar-domain fixtures referenced below. The rest of this article focuses on the reusable software boundary behind them.&lt;/p&gt;

&lt;h2&gt;
  
  
  Model knowledge, not just a string
&lt;/h2&gt;

&lt;p&gt;A common input model makes absence too easy to erase:&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;birthTime&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;form&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;time&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;12:00&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After this line runs, downstream code cannot tell whether noon came from the user or the fallback. Validation, analytics, caching, and the result renderer all see the same string. The information loss happens before the calculation begins.&lt;/p&gt;

&lt;p&gt;A small discriminated union keeps the two states separate:&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="cm"&gt;/**
 * @typedef {{ kind: "known", localTime: string, source: "user" }}
 *   KnownTime
 * @typedef {{ kind: "unknown" }} UnknownTime
 * @typedef {KnownTime | UnknownTime} BirthTime
 */&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;parseBirthTime&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;normalized&lt;/span&gt; &lt;span class="o"&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;trim&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;normalized&lt;/span&gt;
    &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;known&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;localTime&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;normalized&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;source&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;user&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="na"&gt;kind&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&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;This type makes the missing state part of the program's vocabulary. A caller must inspect &lt;code&gt;kind&lt;/code&gt; before using &lt;code&gt;localTime&lt;/code&gt;. More importantly, the source of a known value remains available for debugging and display.&lt;/p&gt;

&lt;p&gt;The same pattern works in TypeScript, Rust enums, database check constraints, JSON Schema, or protocol buffers. The syntax is less important than preserving the distinction between observed and inferred data.&lt;/p&gt;

&lt;h2&gt;
  
  
  Separate date-level context from hour-level output
&lt;/h2&gt;

&lt;p&gt;Unknown time does not make every calculation impossible. A date and place may still establish civil-date context, time-zone rules, and some date-level boundaries. The mistake is to let an internal helper value escape as if it were supplied by the user.&lt;/p&gt;

&lt;p&gt;For example, a pipeline may need a stable instant to ask whether a date lies near a time-zone transition. It can use an internal reference time, provided that reference is labeled and never becomes an hour 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;function&lt;/span&gt; &lt;span class="nf"&gt;buildTemporalContext&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;date&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;city&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;birthTime&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;date&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;city&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;birthTime&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;dateProbe&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
      &lt;span class="nx"&gt;birthTime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;kind&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;known&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;localTime&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;birthTime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;localTime&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;purpose&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;user-instant&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="na"&gt;localTime&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;12:00&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;purpose&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;date-context-only&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here noon is not a birth-time fallback. It is a bounded implementation detail used only for date-level context. Naming the purpose prevents a later maintainer from reusing &lt;code&gt;dateProbe.localTime&lt;/code&gt; to calculate an hour-dependent field.&lt;/p&gt;

&lt;p&gt;Time zones are also data, not a fixed numeric offset. Historical offsets and daylight-saving rules change by place and date. A production system should use a maintained zone identifier and a versioned data source such as the &lt;a href="https://www.iana.org/time-zones" rel="noopener noreferrer"&gt;IANA Time Zone Database&lt;/a&gt;, while retaining the original city and resolution result for auditability.&lt;/p&gt;

&lt;h2&gt;
  
  
  Make derived sections conditional by construction
&lt;/h2&gt;

&lt;p&gt;Rendering logic should not ask whether an hour value happens to exist after several transformations. It should receive an explicit capability map from the calculation layer.&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;buildCapabilities&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;birthTime&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;hasHour&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;birthTime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;kind&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;known&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="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;yearAnalysis&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="na"&gt;monthAnalysis&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="na"&gt;dayAnalysis&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="na"&gt;hourPillar&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;hasHour&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;hourInteractions&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;hasHour&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;preciseHourTiming&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;hasHour&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="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;buildHourResult&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;birthTime&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="nx"&gt;birthTime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;kind&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;unknown&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="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;unknown&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="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;calculateHourDependentFields&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;birthTime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;localTime&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 serialized boundary for an absent hour is deliberately small:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"unknown"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That shape is easier to test than a collection of empty strings, zeroes, or placeholder labels. It also stops clients from treating &lt;code&gt;null&lt;/code&gt; as a transient loading state. The UI can render a clear explanation: date-level sections are available, while hour-dependent sections require a supplied time.&lt;/p&gt;

&lt;p&gt;Conditional output should be structural, not cosmetic. Hiding an invented hour in CSS does not repair a payload that already contains fabricated calculations. The server response, cached artifact, downloadable file, and on-screen result should all share the same boundary.&lt;/p&gt;

&lt;h2&gt;
  
  
  Preserve uncertainty in cache keys
&lt;/h2&gt;

&lt;p&gt;Caching can reintroduce the bug even when the calculator is correct. Consider two requests with the same date and city: one has an unknown time, and the other contains a user-supplied noon. They must not share an identity.&lt;/p&gt;

&lt;p&gt;A canonical cache payload can include the discriminant:&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;canonicalInput&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="na"&gt;date&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="nx"&gt;date&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;cityId&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="nx"&gt;cityId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;time&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="nx"&gt;birthTime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;kind&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;known&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;kind&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;known&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="na"&gt;localTime&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="nx"&gt;birthTime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;localTime&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="na"&gt;source&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="nx"&gt;birthTime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;source&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="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;kind&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&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Hash the canonical payload rather than a display string. The unknown result can still be cached and restored quickly, but it must restore the same limitations. A fast cache hit is not permission to upgrade uncertainty into precision.&lt;/p&gt;

&lt;p&gt;If the user later supplies a time, that is a new input state and should produce a different key. The application may explain that more sections are now available, but it should not imply that the earlier result was wrong; it was correctly bounded by the earlier evidence.&lt;/p&gt;

&lt;h2&gt;
  
  
  Test the boundary, not the prose
&lt;/h2&gt;

&lt;p&gt;Copy such as “time optional” is useful, but it cannot guarantee behavior. The contract belongs in executable tests that cover parsing, serialization, capabilities, and cache identity.&lt;/p&gt;

&lt;p&gt;The following fictional test vector is named &lt;code&gt;shanghai_unknown_time&lt;/code&gt;. It represents a software fixture, not a real person or customer:&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;assert&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;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;const&lt;/span&gt; &lt;span class="nx"&gt;shanghai_unknown_time&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;date&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;1990-06-15&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;cityId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Asia/Shanghai&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;birthTime&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;kind&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&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;shanghai_known_noon&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="nx"&gt;shanghai_unknown_time&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;birthTime&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;known&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;localTime&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;12:00&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;source&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;user&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;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="nf"&gt;buildHourResult&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;shanghai_unknown_time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;birthTime&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;unknown&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="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;buildCapabilities&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;shanghai_unknown_time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;birthTime&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;hourPillar&lt;/span&gt;&lt;span class="p"&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="nx"&gt;assert&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;notDeepEqual&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nf"&gt;canonicalInput&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;shanghai_unknown_time&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="nf"&gt;canonicalInput&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;shanghai_known_noon&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;Additional fixtures should cover blank strings, whitespace, malformed times, daylight-saving transitions, places that changed civil offsets, and dates near domain-specific boundaries. The most important negative assertion is that a missing time never produces an hour-dependent value.&lt;/p&gt;

&lt;p&gt;The repository shows this style of fictional vector and boundary documentation in a concrete calendar domain. Its value is reproducibility: a reader can inspect the input, expected behavior, and limitations without relying on a marketing claim.&lt;/p&gt;

&lt;h2&gt;
  
  
  Explanations are part of the data contract
&lt;/h2&gt;

&lt;p&gt;An unknown result should not look like an error. The system successfully calculated everything justified by the available input. The UI therefore needs three distinct states:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Loading: the calculation has not finished.&lt;/li&gt;
&lt;li&gt;Unknown: the calculation finished, but the required fact was not supplied.&lt;/li&gt;
&lt;li&gt;Known: the calculation finished with a user-supplied fact.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Collapsing these states into one blank card creates confusion. Users may retry, assume the app is broken, or believe the system inferred something it did not. A concise explanation next to the omitted section is more trustworthy than silently filling the space.&lt;/p&gt;

&lt;p&gt;This distinction should also survive exports and APIs. A generated document can say that hour-dependent sections were intentionally omitted. An API can return a typed unknown object. An event log can record &lt;code&gt;time_kind=unknown&lt;/code&gt; without storing a fabricated clock value.&lt;/p&gt;

&lt;h2&gt;
  
  
  A practical review checklist
&lt;/h2&gt;

&lt;p&gt;When reviewing any temporal feature with optional time input, ask:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Can the program distinguish a supplied value from an internal probe?&lt;/li&gt;
&lt;li&gt;Does the missing state survive parsing, calculation, caching, rendering, and export?&lt;/li&gt;
&lt;li&gt;Are hour-independent and hour-dependent capabilities separated?&lt;/li&gt;
&lt;li&gt;Do known noon and unknown time produce different canonical inputs?&lt;/li&gt;
&lt;li&gt;Is the unknown state explained without presenting it as a failure?&lt;/li&gt;
&lt;li&gt;Can an executable fixture prove that no hour value is invented?&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The broader principle is simple: uncertainty is information. Preserve it with the same care as a date, location, or identifier. A result with explicit limits is more useful than a precise-looking result built on a fact nobody supplied.&lt;/p&gt;

&lt;p&gt;Disclosure: AI assistance was used to draft and edit this article. The examples, links, and boundary assertions were checked against the referenced repository and executable local tests before publication.&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>testing</category>
      <category>data</category>
      <category>opensource</category>
    </item>
  </channel>
</rss>
