<?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: tapadyuti chatterjee</title>
    <description>The latest articles on DEV Community by tapadyuti chatterjee (@tapadyutichatterjee).</description>
    <link>https://dev.to/tapadyutichatterjee</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%2F4079519%2F8c945cf0-ed17-434f-8721-cd600b5308e4.png</url>
      <title>DEV Community: tapadyuti chatterjee</title>
      <link>https://dev.to/tapadyutichatterjee</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/tapadyutichatterjee"/>
    <language>en</language>
    <item>
      <title>Making HTTP Fail on Purpose: Building a Small Chaos Library for Java - Flaky HTTP</title>
      <dc:creator>tapadyuti chatterjee</dc:creator>
      <pubDate>Fri, 28 Aug 2026 03:46:54 +0000</pubDate>
      <link>https://dev.to/tapadyutichatterjee/making-http-fail-on-purpose-building-a-small-chaos-library-for-java-flaky-http-18ia</link>
      <guid>https://dev.to/tapadyutichatterjee/making-http-fail-on-purpose-building-a-small-chaos-library-for-java-flaky-http-18ia</guid>
      <description>&lt;p&gt;I recently built and open-sourced &lt;a href="https://github.com/tapadyutichatterjee/flaky-http" rel="noopener noreferrer"&gt;Flaky HTTP&lt;/a&gt;, a small Java 11 library for deliberately making HTTP calls less reliable.&lt;/p&gt;

&lt;p&gt;That may sound like an unusual goal. Most of the time, we work hard to make HTTP calls reliable. We add retries, timeouts, circuit breakers, fallbacks, caches, and monitoring. But eventually we need to answer a more difficult question:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;How do we know any of that behavior actually works?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The original idea was simple: wrap Java's standard &lt;code&gt;HttpClient&lt;/code&gt;, add controlled latency or synthetic HTTP errors to selected requests, and leave the rest of the application unchanged.&lt;/p&gt;

&lt;p&gt;That simple idea led to a few interesting decisions around API design, asynchronous cancellation, response body handling, deterministic testing, and the boundary between application-level failure injection and real network chaos.&lt;/p&gt;

&lt;p&gt;This article goes beyond a launch announcement. I want to explain why I built the library, how it works internally, where it is useful, and where it is deliberately limited.&lt;/p&gt;

&lt;h2&gt;
  
  
  TL;DR
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://github.com/tapadyutichatterjee/flaky-http" rel="noopener noreferrer"&gt;Flaky HTTP&lt;/a&gt; is a lightweight wrapper around Java 11's &lt;code&gt;java.net.http.HttpClient&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;It can:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;add fixed or random latency;&lt;/li&gt;
&lt;li&gt;return synthetic HTTP errors with a configurable probability;&lt;/li&gt;
&lt;li&gt;target requests using a full-URI regular expression;&lt;/li&gt;
&lt;li&gt;handle synchronous and asynchronous calls;&lt;/li&gt;
&lt;li&gt;propagate cancellation for delayed asynchronous work; and&lt;/li&gt;
&lt;li&gt;run without runtime dependencies beyond Java 11.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The Maven coordinate is &lt;code&gt;com.tapadyuti:flaky-http:1.0.0&lt;/code&gt;. The shortest useful test setup is a deterministic failure:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nc"&gt;FlakyConfig&lt;/span&gt; &lt;span class="n"&gt;config&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;FlakyConfig&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;builder&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;
        &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;failureRate&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;1.0&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
        &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;errorStatus&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;503&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
        &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;build&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Every targeted call now returns an empty synthetic &lt;code&gt;503&lt;/code&gt; response without reaching the network. Replace &lt;code&gt;1.0&lt;/code&gt; with &lt;code&gt;0.0&lt;/code&gt; and add &lt;code&gt;LatencyStrategy.fixed(500)&lt;/code&gt; when the test should exercise slowness without an HTTP error.&lt;/p&gt;

&lt;p&gt;It is intended for integration tests, resilience tests, local development, and controlled demonstrations. It is not a replacement for a network proxy or a full chaos-engineering platform.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;AI note:&lt;/strong&gt; AI helped structure and polish this article. The underlying architecture, codebase, and implementation decisions are entirely my own.&lt;/p&gt;

&lt;h2&gt;
  
  
  The problem I wanted to solve
&lt;/h2&gt;

&lt;p&gt;The successful path of an HTTP integration is usually easy to test.&lt;/p&gt;

&lt;p&gt;Start a test server, return &lt;code&gt;200 OK&lt;/code&gt;, deserialize the response, and assert the result. But production dependencies rarely fail in one neat way. They become slow. They return &lt;code&gt;429&lt;/code&gt;, &lt;code&gt;500&lt;/code&gt;, or &lt;code&gt;503&lt;/code&gt;. They recover after a retry. They stay unhealthy long enough to open a circuit breaker. Sometimes one endpoint fails while everything else continues to work.&lt;/p&gt;

&lt;p&gt;The surrounding application is expected to handle all of this correctly.&lt;/p&gt;

&lt;p&gt;In many test suites, however, the failure setup becomes more complicated than the behavior being tested. We may need to modify a mock server, add proxy rules, change container networking, or create one-off test doubles for every client abstraction.&lt;/p&gt;

&lt;p&gt;I wanted something smaller for a common case:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;My application already uses Java's &lt;code&gt;HttpClient&lt;/code&gt;. I want selected calls to become slow or return an error, without changing the real service.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That became the scope of Flaky HTTP.&lt;/p&gt;

&lt;h2&gt;
  
  
  The basic model
&lt;/h2&gt;

&lt;p&gt;The library uses composition. An application gives &lt;code&gt;FlakyHttpClient&lt;/code&gt; a real &lt;code&gt;HttpClient&lt;/code&gt; and an immutable configuration.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Application
    |
    v
FlakyHttpClient
    |
    +-- URI does not match ----------&amp;gt; Real HttpClient
    |
    +-- URI matches
          |
          +-- apply latency
          |
          +-- failure selected ------&amp;gt; Synthetic HTTP response
          |
          +-- otherwise -------------&amp;gt; Real HttpClient
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For a targeted request, latency is applied first. The client then makes a failure decision. If failure is selected, it returns a synthetic response without touching the network. Otherwise, it delegates the original request and body handler to the real client.&lt;/p&gt;

&lt;p&gt;A minimal configuration looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nc"&gt;FlakyConfig&lt;/span&gt; &lt;span class="n"&gt;config&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;FlakyConfig&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;builder&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;
        &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;failureRate&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;0.30&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
        &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;latency&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;LatencyStrategy&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;random&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;500&lt;/span&gt;&lt;span class="o"&gt;))&lt;/span&gt;
        &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;errorStatus&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;503&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
        &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;targetUrls&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"https://api\\.example\\.com/.*"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
        &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;build&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This configuration adds between 100 and 500 milliseconds of latency to matching requests. After the delay, 30 percent of those requests receive a synthetic &lt;code&gt;503 Service Unavailable&lt;/code&gt;. The remaining requests go to the real service.&lt;/p&gt;

&lt;p&gt;The code using it remains close to normal &lt;code&gt;HttpClient&lt;/code&gt; code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nc"&gt;HttpRequest&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;HttpRequest&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;newBuilder&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;
        &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;uri&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="no"&gt;URI&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;create&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"https://api.example.com/orders/42"&lt;/span&gt;&lt;span class="o"&gt;))&lt;/span&gt;
        &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;GET&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;
        &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;build&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;

&lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;FlakyHttpClient&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
             &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;FlakyHttpClient&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;HttpClient&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;newHttpClient&lt;/span&gt;&lt;span class="o"&gt;(),&lt;/span&gt; &lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="o"&gt;))&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="nc"&gt;HttpResponse&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
            &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;send&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;HttpResponse&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;BodyHandlers&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;ofString&lt;/span&gt;&lt;span class="o"&gt;());&lt;/span&gt;

    &lt;span class="nc"&gt;System&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;out&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;println&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;statusCode&lt;/span&gt;&lt;span class="o"&gt;());&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Why I chose composition
&lt;/h2&gt;

&lt;p&gt;One of the first design decisions was whether &lt;code&gt;FlakyHttpClient&lt;/code&gt; should behave as another &lt;code&gt;HttpClient&lt;/code&gt; subtype or be an explicit wrapper.&lt;/p&gt;

&lt;p&gt;I chose an explicit composition-based wrapper.&lt;/p&gt;

&lt;p&gt;This makes the boundary visible. Code that opts into failure injection receives a &lt;code&gt;FlakyHttpClient&lt;/code&gt;; code that should always use the original client can continue using &lt;code&gt;HttpClient&lt;/code&gt;. It also keeps the library focused on the two operations it needs to control: &lt;code&gt;send&lt;/code&gt; and &lt;code&gt;sendAsync&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;There is a tradeoff. A &lt;code&gt;FlakyHttpClient&lt;/code&gt; cannot be passed directly to a method that requires an &lt;code&gt;HttpClient&lt;/code&gt;. In a larger application, I would normally place HTTP access behind an application-owned interface anyway. That interface can then be backed by the real client or by the flaky wrapper in tests.&lt;/p&gt;

&lt;p&gt;The key word is &lt;strong&gt;explicit&lt;/strong&gt;. Failure injection should not quietly appear in unrelated calls.&lt;/p&gt;

&lt;h2&gt;
  
  
  Synthetic responses still need to respect &lt;code&gt;BodyHandler&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;Returning an integer status code is easy. Returning a useful &lt;code&gt;HttpResponse&amp;lt;T&amp;gt;&lt;/code&gt; is more subtle.&lt;/p&gt;

&lt;p&gt;Java's HTTP API lets the caller decide how a response body should be converted:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nc"&gt;HttpResponse&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;BodyHandlers&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;ofString&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;
&lt;span class="nc"&gt;HttpResponse&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;BodyHandlers&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;ofByteArray&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;
&lt;span class="nc"&gt;HttpResponse&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;BodyHandlers&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;discarding&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If Flaky HTTP simply cast an empty string to &lt;code&gt;T&lt;/code&gt;, it would work for one handler and fail for others. The synthetic path therefore applies the caller's real &lt;code&gt;BodyHandler&lt;/code&gt; to response metadata, creates its &lt;code&gt;BodySubscriber&lt;/code&gt;, and completes that subscriber with an empty body.&lt;/p&gt;

&lt;p&gt;As a result:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;ofString()&lt;/code&gt; receives &lt;code&gt;""&lt;/code&gt;;&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;ofByteArray()&lt;/code&gt; receives an empty byte array;&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;discarding()&lt;/code&gt; receives its normal result; and&lt;/li&gt;
&lt;li&gt;a custom handler still controls conversion.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The synthetic response also includes &lt;code&gt;Content-Length: 0&lt;/code&gt;, the original request and URI, the configured error status, and the wrapped client's preferred HTTP version.&lt;/p&gt;

&lt;p&gt;This was one of the most important implementation details. A failure-testing utility should not introduce a completely different response contract from the API it wraps.&lt;/p&gt;

&lt;h2&gt;
  
  
  Synchronous and asynchronous latency are different problems
&lt;/h2&gt;

&lt;p&gt;For synchronous calls, artificial latency is straightforward. The calling thread sleeps before the failure decision or real network call. If it is interrupted, the request is not delegated and &lt;code&gt;InterruptedException&lt;/code&gt; is propagated.&lt;/p&gt;

&lt;p&gt;The asynchronous path needed more care.&lt;/p&gt;

&lt;p&gt;Calling &lt;code&gt;Thread.sleep&lt;/code&gt; inside &lt;code&gt;sendAsync&lt;/code&gt; would make an asynchronous API block the caller, which defeats the purpose. Flaky HTTP instead uses a &lt;code&gt;ScheduledExecutorService&lt;/code&gt; to begin the next step after the configured delay.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;sendAsync&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;HttpResponse&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;BodyHandlers&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;ofString&lt;/span&gt;&lt;span class="o"&gt;())&lt;/span&gt;
        &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;thenAccept&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;
                &lt;span class="nc"&gt;System&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;out&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;println&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;statusCode&lt;/span&gt;&lt;span class="o"&gt;()))&lt;/span&gt;
        &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;join&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Cancellation also has two possible states:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The artificial delay is still pending.&lt;/li&gt;
&lt;li&gt;The delay has finished and the real HTTP request has started.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Cancelling the returned &lt;code&gt;CompletableFuture&lt;/code&gt; cancels the scheduled delay when possible. If delegation has already started, it attempts to cancel the delegate future as well.&lt;/p&gt;

&lt;p&gt;The default constructor creates a small internal daemon scheduler. Because that is a resource with a lifecycle, &lt;code&gt;FlakyHttpClient&lt;/code&gt; implements &lt;code&gt;AutoCloseable&lt;/code&gt; and works naturally with try-with-resources.&lt;/p&gt;

&lt;p&gt;Applications that already manage executors can supply their own scheduler:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nc"&gt;ScheduledExecutorService&lt;/span&gt; &lt;span class="n"&gt;scheduler&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
        &lt;span class="nc"&gt;Executors&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;newScheduledThreadPool&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;FlakyHttpClient&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
             &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;FlakyHttpClient&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;HttpClient&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;newHttpClient&lt;/span&gt;&lt;span class="o"&gt;(),&lt;/span&gt; &lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;scheduler&lt;/span&gt;&lt;span class="o"&gt;))&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;sendAsync&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;HttpResponse&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;BodyHandlers&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;ofString&lt;/span&gt;&lt;span class="o"&gt;()).&lt;/span&gt;&lt;span class="na"&gt;join&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt; &lt;span class="k"&gt;finally&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;scheduler&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;shutdown&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The ownership rule is intentionally simple: the client closes a scheduler it creates, but never closes one supplied by the caller.&lt;/p&gt;

&lt;h2&gt;
  
  
  Deterministic tests and probabilistic experiments
&lt;/h2&gt;

&lt;p&gt;A random failure rate is useful when exploring system behavior locally. It is usually a poor foundation for a repeatable automated test.&lt;/p&gt;

&lt;p&gt;For tests, the boundary values are more useful:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nc"&gt;FlakyConfig&lt;/span&gt; &lt;span class="n"&gt;alwaysUnavailable&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;FlakyConfig&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;builder&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;
        &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;failureRate&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;1.0&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
        &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;errorStatus&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;503&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
        &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;targetUrls&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"http://localhost:8080/orders/.*"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
        &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;build&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A rate of &lt;code&gt;1.0&lt;/code&gt; always returns the synthetic error. A rate of &lt;code&gt;0.0&lt;/code&gt; never injects an error, but can still add latency. Both configurations are deterministic.&lt;/p&gt;

&lt;p&gt;For example, an integration test can make the upstream orders endpoint consistently unavailable and verify the application's fallback path:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nd"&gt;@Test&lt;/span&gt;
&lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;usesFallbackWhenOrdersApiIsUnavailable&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="kd"&gt;throws&lt;/span&gt; &lt;span class="nc"&gt;Exception&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="nc"&gt;FlakyConfig&lt;/span&gt; &lt;span class="n"&gt;chaos&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;FlakyConfig&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;builder&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;
            &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;failureRate&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;1.0&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
            &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;errorStatus&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;503&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
            &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;targetUrls&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"http://localhost:8080/orders/.*"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
            &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;build&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;

    &lt;span class="nc"&gt;HttpRequest&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;HttpRequest&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;newBuilder&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;
            &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;uri&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="no"&gt;URI&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;create&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"http://localhost:8080/orders/42"&lt;/span&gt;&lt;span class="o"&gt;))&lt;/span&gt;
            &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;GET&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;
            &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;build&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;

    &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;FlakyHttpClient&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
             &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;FlakyHttpClient&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;HttpClient&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;newHttpClient&lt;/span&gt;&lt;span class="o"&gt;(),&lt;/span&gt; &lt;span class="n"&gt;chaos&lt;/span&gt;&lt;span class="o"&gt;))&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="nc"&gt;HttpResponse&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
                &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;send&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;HttpResponse&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;BodyHandlers&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;ofString&lt;/span&gt;&lt;span class="o"&gt;());&lt;/span&gt;

        &lt;span class="nc"&gt;OrderResult&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;orderService&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;handle&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;

        &lt;span class="n"&gt;assertEquals&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;503&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;statusCode&lt;/span&gt;&lt;span class="o"&gt;());&lt;/span&gt;
        &lt;span class="n"&gt;assertTrue&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;isFallback&lt;/span&gt;&lt;span class="o"&gt;());&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;OrderResult&lt;/code&gt; and &lt;code&gt;orderService&lt;/code&gt; represent the application code under test. The important part is that the request never needs a cooperating failure mode from the local server.&lt;/p&gt;

&lt;p&gt;For exploratory testing, a value such as &lt;code&gt;0.2&lt;/code&gt; or &lt;code&gt;0.3&lt;/code&gt; is useful. It creates a mixed stream of successful and unsuccessful calls that can expose assumptions in logs, metrics, retry behavior, and user-facing error handling.&lt;/p&gt;

&lt;p&gt;It was a good reminder that deterministic testing and random fault injection solve related but different problems. The library supports both, but the caller should choose deliberately.&lt;/p&gt;

&lt;h2&gt;
  
  
  Targeting only the dependency that matters
&lt;/h2&gt;

&lt;p&gt;Most applications call more than one endpoint. Making every request fail can hide the behavior we actually want to observe.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;targetUrls&lt;/code&gt; accepts a Java regular expression and matches it against the complete URI string:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Every path on one host&lt;/span&gt;
&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;targetUrls&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"https://api\\.example\\.com/.*"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;// Only the payments endpoint, with an optional query string&lt;/span&gt;
&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;targetUrls&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"https://api\\.example\\.com/payments(?:\\?.*)?"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;// Any URI containing /experimental/&lt;/span&gt;
&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;targetUrls&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;".*/experimental/.*"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The full-match behavior matters. A pattern such as &lt;code&gt;/payments&lt;/code&gt; does not search inside the URI. Callers need &lt;code&gt;.*&lt;/code&gt; when they want a partial match.&lt;/p&gt;

&lt;p&gt;Requests that do not match bypass both latency and failure injection.&lt;/p&gt;

&lt;p&gt;This makes a few useful scenarios possible:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Scenario&lt;/th&gt;
&lt;th&gt;Injection&lt;/th&gt;
&lt;th&gt;What it can verify&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Retry policy&lt;/td&gt;
&lt;td&gt;Repeated &lt;code&gt;503&lt;/code&gt; responses&lt;/td&gt;
&lt;td&gt;Attempt limits, backoff, and eventual failure&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Rate limiting&lt;/td&gt;
&lt;td&gt;A synthetic &lt;code&gt;429&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Backpressure and retry suppression&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Circuit breaker&lt;/td&gt;
&lt;td&gt;Guaranteed &lt;code&gt;500&lt;/code&gt; or &lt;code&gt;503&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Open, half-open, and recovery behavior&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Fallback or cache&lt;/td&gt;
&lt;td&gt;Failure on one endpoint&lt;/td&gt;
&lt;td&gt;Degraded or cached responses are selected&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Timeout handling&lt;/td&gt;
&lt;td&gt;Fixed latency beyond an app deadline&lt;/td&gt;
&lt;td&gt;Cancellation, cleanup, and error propagation&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Observability&lt;/td&gt;
&lt;td&gt;Random delay and intermittent errors&lt;/td&gt;
&lt;td&gt;Logs, traces, metrics, dashboards, and alerts&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Bulkhead behavior&lt;/td&gt;
&lt;td&gt;Concurrent delayed async calls&lt;/td&gt;
&lt;td&gt;A slow dependency does not consume unrelated capacity&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Local demos&lt;/td&gt;
&lt;td&gt;Predictable failures&lt;/td&gt;
&lt;td&gt;Error states can be reproduced without changing a service&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The library is small, but its most valuable use cases sit outside the library. They are tests of the application behavior surrounding an unreliable dependency.&lt;/p&gt;

&lt;p&gt;Here are three compact configurations I expect to use most often:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Verify retry, circuit-breaker, or fallback behavior&lt;/span&gt;
&lt;span class="nc"&gt;FlakyConfig&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;builder&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;
        &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;failureRate&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;1.0&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
        &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;errorStatus&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;503&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
        &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;build&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;

&lt;span class="c1"&gt;// Verify an application-level timeout without injecting an error&lt;/span&gt;
&lt;span class="nc"&gt;FlakyConfig&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;builder&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;
        &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;failureRate&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;0.0&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
        &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;latency&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;LatencyStrategy&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;fixed&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1_000&lt;/span&gt;&lt;span class="o"&gt;))&lt;/span&gt;
        &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;build&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;

&lt;span class="c1"&gt;// Verify rate-limit handling only for the payments API&lt;/span&gt;
&lt;span class="nc"&gt;FlakyConfig&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;builder&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;
        &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;failureRate&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;1.0&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
        &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;errorStatus&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;429&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
        &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;targetUrls&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"https://api\\.example\\.com/payments(?:\\?.*)?"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
        &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;build&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;These boundary-value configurations are predictable enough for CI. I would reserve random latency and partial failure rates for exploratory tests, longer-running resilience suites, and demonstrations.&lt;/p&gt;

&lt;h2&gt;
  
  
  A subtle timeout boundary
&lt;/h2&gt;

&lt;p&gt;Artificial latency happens before the real request is delegated to &lt;code&gt;HttpClient&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;That means a timeout configured directly on &lt;code&gt;HttpRequest&lt;/code&gt; does not include the injected pre-request delay. The wrapped client has not seen the request yet.&lt;/p&gt;

&lt;p&gt;When I want to test an end-to-end deadline, I apply the timeout around the complete operation. For asynchronous code, that could be a future timeout:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;sendAsync&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;HttpResponse&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;BodyHandlers&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;ofString&lt;/span&gt;&lt;span class="o"&gt;())&lt;/span&gt;
        &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;orTimeout&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;300&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;TimeUnit&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;MILLISECONDS&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
        &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;join&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This makes the caller observe an application-level timeout around the whole future. One more detail matters: &lt;code&gt;orTimeout&lt;/code&gt; completes a future exceptionally; it does not mean the underlying work was cancelled. A test that specifically verifies cancellation should retain the original future and call &lt;code&gt;cancel(true)&lt;/code&gt;, which lets Flaky HTTP cancel a pending artificial delay and attempt to cancel a delegated request.&lt;/p&gt;

&lt;p&gt;This distinction is small but important. A test should be clear about whether it is exercising the transport's request timeout, the application's complete-operation deadline, or explicit cancellation.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Flaky HTTP intentionally does not simulate
&lt;/h2&gt;

&lt;p&gt;Flaky HTTP sits immediately above &lt;code&gt;HttpClient&lt;/code&gt;. It is not a proxy and it does not alter packets on the wire.&lt;/p&gt;

&lt;p&gt;It can simulate application-visible latency and HTTP error responses. It cannot faithfully reproduce:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;DNS resolution failures;&lt;/li&gt;
&lt;li&gt;connection refusal;&lt;/li&gt;
&lt;li&gt;TLS negotiation errors;&lt;/li&gt;
&lt;li&gt;connection resets;&lt;/li&gt;
&lt;li&gt;truncated response bodies;&lt;/li&gt;
&lt;li&gt;malformed HTTP;&lt;/li&gt;
&lt;li&gt;bandwidth restrictions; or&lt;/li&gt;
&lt;li&gt;packet loss.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Those tests need a different layer: a fault-injecting proxy, a container networking tool, a faulty test server, or a network emulator.&lt;/p&gt;

&lt;p&gt;I think this limitation is healthy. Small libraries are more useful when their boundary is explicit. Flaky HTTP covers the cases where the application needs to observe a slow call or an HTTP error. It does not pretend that every distributed-systems failure is equivalent to a &lt;code&gt;503&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  A few decisions that paid off
&lt;/h2&gt;

&lt;p&gt;A few implementation choices had an outsized effect on the final library.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Immutable configuration
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;FlakyConfig&lt;/code&gt; is built once and then shared safely. The failure rate, latency strategy, error status, and URL pattern cannot change underneath an in-flight request.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Validation at the boundary
&lt;/h3&gt;

&lt;p&gt;Failure rates must be finite values from &lt;code&gt;0.0&lt;/code&gt; through &lt;code&gt;1.0&lt;/code&gt;. Error statuses must be between &lt;code&gt;400&lt;/code&gt; and &lt;code&gt;599&lt;/code&gt;. The built-in latency factories reject negative values and invalid ranges; custom strategies are required by their API contract to return a non-negative delay. Invalid regular expressions fail during configuration rather than during a later request.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. No runtime dependencies
&lt;/h3&gt;

&lt;p&gt;The implementation uses Java 11's HTTP, concurrency, and flow APIs. Keeping the runtime dependency list empty makes the library easier to introduce into test suites without creating version conflicts.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Explicit scheduler ownership
&lt;/h3&gt;

&lt;p&gt;The constructor determines who owns the scheduler, and &lt;code&gt;close()&lt;/code&gt; follows that decision. This avoids both leaked internal threads and surprising shutdowns of shared application infrastructure.&lt;/p&gt;

&lt;h3&gt;
  
  
  5. Honest documentation of edge cases
&lt;/h3&gt;

&lt;p&gt;The README and Javadocs describe full-URI matching, body-handler behavior, timeout boundaries, asynchronous cancellation, and the difference between synthetic HTTP failures and network faults.&lt;/p&gt;

&lt;p&gt;For a testing library, those details are part of the API. A false assumption in a failure test can be worse than having no test at all.&lt;/p&gt;

&lt;h2&gt;
  
  
  Installation
&lt;/h2&gt;

&lt;p&gt;The Maven coordinates for version &lt;code&gt;1.0.0&lt;/code&gt; are:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;dependency&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;groupId&amp;gt;&lt;/span&gt;com.tapadyuti&lt;span class="nt"&gt;&amp;lt;/groupId&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;artifactId&amp;gt;&lt;/span&gt;flaky-http&lt;span class="nt"&gt;&amp;lt;/artifactId&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;version&amp;gt;&lt;/span&gt;1.0.0&lt;span class="nt"&gt;&amp;lt;/version&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/dependency&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For Gradle:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight gradle"&gt;&lt;code&gt;&lt;span class="n"&gt;testImplementation&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"com.tapadyuti:flaky-http:1.0.0"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The project requires Java 11 or later. The repository also includes a comprehensive demo covering fixed latency, random latency, URL targeting, guaranteed failures, asynchronous calls, and a caller-owned scheduler. The test suite additionally verifies cancellation of a pending asynchronous delay.&lt;/p&gt;

&lt;p&gt;Flaky HTTP is available under the &lt;a href="https://github.com/tapadyutichatterjee/flaky-http/blob/master/LICENSE" rel="noopener noreferrer"&gt;Apache License 2.0&lt;/a&gt;. Focused issues and pull requests are welcome, especially when they include a reproducible failure scenario and tests.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone https://github.com/tapadyutichatterjee/flaky-http.git
&lt;span class="nb"&gt;cd &lt;/span&gt;flaky-http
mvn clean verify
java &lt;span class="nt"&gt;-cp&lt;/span&gt; target/classes com.tapadyuti.flakyhttp.FlakyHttpDemo
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  What I would improve next
&lt;/h2&gt;

&lt;p&gt;The current API is deliberately small, but there are several useful directions for future versions:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Injectable failure selection&lt;/strong&gt; for deterministic sequences such as “fail twice, then succeed.”&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Richer synthetic responses&lt;/strong&gt; with configurable headers and bodies, especially for &lt;code&gt;Retry-After&lt;/code&gt; and structured error payloads.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Transport-style failure modes&lt;/strong&gt; that complete with selected exceptions, while keeping their semantics clearly separate from HTTP responses.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;More timing strategies&lt;/strong&gt;, including progressive latency and scripted delay sequences.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Examples with common resilience libraries&lt;/strong&gt; to show retry, circuit-breaker, and time-limiter tests end to end.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;I would add these carefully. The main value of the project is that a reader can understand its behavior quickly. More features should not turn a small failure-injection wrapper into an unpredictable simulation framework.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I learned
&lt;/h2&gt;

&lt;p&gt;A few lessons stood out while building this project.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Failure paths deserve the same API fidelity as success paths.&lt;/strong&gt; Respecting &lt;code&gt;BodyHandler&lt;/code&gt;, cancellation, interruption, and resource ownership matters even when the response is synthetic.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Randomness is a tool, not a testing strategy by itself.&lt;/strong&gt; Random failure is useful for exploration. Guaranteed boundary values are better for regression tests.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The layer of injection defines the failures you can claim to test.&lt;/strong&gt; An HTTP wrapper can validate application behavior around latency and status codes. It cannot prove behavior under DNS, TLS, or connection-level faults.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Small utilities benefit from detailed documentation.&lt;/strong&gt; The code may be compact, but users still need to understand timing, matching, lifecycle, and failure semantics.&lt;/p&gt;

&lt;p&gt;The hardest part was not generating a &lt;code&gt;503&lt;/code&gt;. It was making that generated response behave enough like the real API that application tests remain meaningful.&lt;/p&gt;

&lt;h2&gt;
  
  
  Closing thoughts
&lt;/h2&gt;

&lt;p&gt;Flaky HTTP started from a narrow question: can I make an existing Java HTTP integration fail on purpose, without changing the remote service?&lt;/p&gt;

&lt;p&gt;The result is intentionally modest. It is a wrapper, not a platform. But it creates a useful seam for testing retries, timeouts, circuit breakers, fallbacks, caches, observability, and other behavior that is easy to design and surprisingly easy to leave unverified.&lt;/p&gt;

&lt;p&gt;The project is available on &lt;a href="https://github.com/tapadyutichatterjee/flaky-http" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;, with the complete &lt;a href="https://github.com/tapadyutichatterjee/flaky-http#readme" rel="noopener noreferrer"&gt;README&lt;/a&gt;, examples, Javadocs, and tests.&lt;/p&gt;

&lt;p&gt;If you work with Java HTTP integrations, I would be interested to hear which failure modes are hardest to reproduce in your own test suites.&lt;/p&gt;

&lt;p&gt;You can also find me at &lt;a href="https://tapadyuti.com/" rel="noopener noreferrer"&gt;tapadyuti.com&lt;/a&gt; and here on &lt;a href="https://dev.to/tapadyutichatterjee/"&gt;DEV Community&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>java</category>
      <category>testing</category>
      <category>opensource</category>
      <category>programming</category>
    </item>
    <item>
      <title>A Date Is Never Just a Date: From West Bengal's Panjika to a Bengali Calendar Library for Java 21</title>
      <dc:creator>tapadyuti chatterjee</dc:creator>
      <pubDate>Mon, 24 Aug 2026 10:33:12 +0000</pubDate>
      <link>https://dev.to/tapadyutichatterjee/a-date-is-never-just-a-date-from-west-bengals-panjika-to-a-bengali-calendar-library-4pe5</link>
      <guid>https://dev.to/tapadyutichatterjee/a-date-is-never-just-a-date-from-west-bengals-panjika-to-a-bengali-calendar-library-4pe5</guid>
      <description>&lt;p&gt;&lt;code&gt;LocalDate.of(2024, 4, 14)&lt;/code&gt; looks innocent enough.&lt;/p&gt;

&lt;p&gt;Ask Java what day it represents and the answer is easy. Ask what &lt;strong&gt;Bengali date&lt;/strong&gt; it represents, and a more interesting question appears:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Which Bengali calendar system do you mean?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That question is exactly why I built &lt;strong&gt;bengali-calendar&lt;/strong&gt;, a Java 21 library for converting between Gregorian and Bengali dates while making the selected calendar convention explicit. Version &lt;code&gt;1.0.0&lt;/code&gt; provides &lt;strong&gt;West Bengal Traditional&lt;/strong&gt; and &lt;strong&gt;Bangladesh Revised&lt;/strong&gt; modes; the precise scope of the latter is explained below.&lt;/p&gt;

&lt;p&gt;I am from &lt;strong&gt;West Bengal, India&lt;/strong&gt;, so this subject is personal to me. Bengali dates are not an abstract alternative numbering system that I discovered while designing an API. They were already present in the panjika at home, in the arrival of Poila Boishakh, in shopkeepers' &lt;em&gt;haal khata&lt;/em&gt;, in the six seasons, and in the familiar question many families in West Bengal ask: &lt;em&gt;When is Pujo this year?&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;This library grew from the wish to represent part of that lived culture in Java without pretending that one Bengali calendar convention speaks for every Bengali community.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Cover illustration: AI-generated and inspired by the Patua/Patachitra tradition of West Bengal; it is not presented as artisan-made traditional work.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Version &lt;code&gt;1.0.0&lt;/code&gt; is now available on Maven Central: &lt;br&gt;
&lt;a href="https://central.sonatype.com/artifact/com.tapadyuti/bengali-calendar/overview" rel="noopener noreferrer"&gt;https://central.sonatype.com/artifact/com.tapadyuti/bengali-calendar/overview&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;dependency&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;groupId&amp;gt;&lt;/span&gt;com.tapadyuti&lt;span class="nt"&gt;&amp;lt;/groupId&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;artifactId&amp;gt;&lt;/span&gt;bengali-calendar&lt;span class="nt"&gt;&amp;lt;/artifactId&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;version&amp;gt;&lt;/span&gt;1.0.0&lt;span class="nt"&gt;&amp;lt;/version&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/dependency&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This post is about what the library does and how to use it. I will not go deep into the implementation because I have not opened the source repository for contributions yet. But there is plenty to explore from the public API alone.&lt;/p&gt;

&lt;h2&gt;
  
  
  TL;DR
&lt;/h2&gt;

&lt;p&gt;If you are already reaching for the copy button:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;com.tapadyuti.bengalicalendar.*&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;java.time.LocalDate&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

&lt;span class="nc"&gt;BengaliDate&lt;/span&gt; &lt;span class="n"&gt;date&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;BengaliCalendar&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;from&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
    &lt;span class="nc"&gt;LocalDate&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;of&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2024&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;14&lt;/span&gt;&lt;span class="o"&gt;),&lt;/span&gt;
    &lt;span class="nc"&gt;BengaliCalendarSystem&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;WEST_BENGAL_TRADITIONAL&lt;/span&gt;
&lt;span class="o"&gt;);&lt;/span&gt;

&lt;span class="nc"&gt;System&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;out&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;println&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;date&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;format&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"dd MMMM yyyy E"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;BengaliLocale&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;BENGALI&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="o"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// ০১ বৈশাখ ১৪৩১ WB&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The library provides:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Gregorian to Bengali conversion&lt;/li&gt;
&lt;li&gt;Bengali to Gregorian conversion&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;WEST_BENGAL_TRADITIONAL&lt;/code&gt; and &lt;code&gt;BANGLADESH_REVISED&lt;/code&gt; systems, with the version-specific compatibility note below&lt;/li&gt;
&lt;li&gt;English and Bengali-script formatting&lt;/li&gt;
&lt;li&gt;Bengali numeral parsing&lt;/li&gt;
&lt;li&gt;Immutable date arithmetic&lt;/li&gt;
&lt;li&gt;Year-month and date-range helpers&lt;/li&gt;
&lt;li&gt;Bengali seasons, weekdays, and selected holidays&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;java.time&lt;/code&gt; interoperability through &lt;code&gt;ChronoLocalDate&lt;/code&gt; and &lt;code&gt;Chronology&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Java 21 and JPMS support&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Maven coordinates:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;com.tapadyuti:bengali-calendar:1.0.0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  A calendar carries more than dates
&lt;/h2&gt;

&lt;p&gt;Most of us meet calendars as grids: rows, columns, holidays, and perhaps a red circle around a deadline we are trying not to think about.&lt;/p&gt;

&lt;p&gt;But calendars are also records of astronomy, agriculture, administration, language, and culture. The Bengali calendar is a particularly good example.&lt;/p&gt;

&lt;p&gt;Historians do not agree on a single origin story. One historical tradition connects the Bengali era with &lt;strong&gt;Shashanka&lt;/strong&gt;, ruler of Gauda in the early seventh century, and dates the beginning of the era to approximately 593–594 CE. Another influential account, recorded by &lt;a href="https://en.banglapedia.org/index.php?title=Bangabda" rel="noopener noreferrer"&gt;Banglapedia&lt;/a&gt;, describes Mughal emperor &lt;strong&gt;Akbar&lt;/strong&gt; promulgating a reformed calendar in 1584 and backdating it to his accession in 1556. According to that account, one practical concern was revenue collection: the Hijri calendar was lunar, while crops and harvests followed the solar year. The astronomer &lt;strong&gt;Fathullah Shirazi&lt;/strong&gt; is associated with the effort to align the calendar with the agricultural cycle.&lt;/p&gt;

&lt;p&gt;Even the month names carry traces of the sky. Banglapedia associates names such as Baishakh, Jyaistha, and Falgun with stars or constellations used in older calendrical traditions.&lt;/p&gt;

&lt;p&gt;The story did not stop there. Calendars are living civic systems, and Bengali calendrical practice continued to evolve. Bangladesh adopted standardized civil rules following reform efforts associated with the Bangla Academy and the committee led by &lt;strong&gt;Muhammad Shahidullah&lt;/strong&gt;, and revised its official civil calendar again in 2019. Many traditional panjikas used in West Bengal continue to determine month boundaries through astronomical solar transitions.&lt;/p&gt;

&lt;p&gt;That history also shows why software should identify a calendar rule set precisely. Version &lt;code&gt;1.0.0&lt;/code&gt; currently exposes these two modes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;West Bengal Traditional&lt;/strong&gt; follows astronomical month boundaries associated with sankranti.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;BANGLADESH_REVISED&lt;/code&gt; in version &lt;code&gt;1.0.0&lt;/code&gt;&lt;/strong&gt; follows the earlier standardized Bangladesh rules used before the 2019 revision.&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Bangladesh compatibility note:&lt;/strong&gt; The &lt;code&gt;BANGLADESH_REVISED&lt;/code&gt; mode in version &lt;code&gt;1.0.0&lt;/code&gt; should not be interpreted as the current post-2019 official Bangladesh civil calendar. Under the &lt;a href="https://www.prothomalo.com/bangladesh/%E0%A6%AC%E0%A6%BE%E0%A6%82%E0%A6%B2%E0%A6%BE-%E0%A6%A6%E0%A6%BF%E0%A6%A8%E0%A6%AA%E0%A6%9E%E0%A7%8D%E0%A6%9C%E0%A6%BF-%E0%A6%AC%E0%A6%A6%E0%A6%B2-%E0%A6%86%E0%A6%9C-%E0%A6%AA%E0%A7%9F%E0%A6%B2%E0%A6%BE-%E0%A6%95%E0%A6%BE%E0%A6%B0%E0%A7%8D%E0%A6%A4%E0%A6%BF%E0%A6%95" rel="noopener noreferrer"&gt;2019 Bangla Academy reform reported by Prothom Alo&lt;/a&gt;, the first six months have 31 days, Falgun has 29 days—or 30 in a Gregorian leap year—and the remaining five months have 30 days. If your application requires current official Bangladesh civil dates, validate against an authoritative source; this version does not yet model that scheme separately.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Neither mode should be silently guessed by a programming library. The choice affects real output, particularly near month and year boundaries. So the selected rule set is part of the date's identity in this library.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nc"&gt;BengaliDate&lt;/span&gt; &lt;span class="n"&gt;date&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;BengaliCalendar&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;from&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
    &lt;span class="nc"&gt;LocalDate&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;now&lt;/span&gt;&lt;span class="o"&gt;(),&lt;/span&gt;
    &lt;span class="nc"&gt;BengaliCalendarSystem&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;WEST_BENGAL_TRADITIONAL&lt;/span&gt;
&lt;span class="o"&gt;);&lt;/span&gt;

&lt;span class="nc"&gt;System&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;out&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;println&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;date&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getCalendarSystem&lt;/span&gt;&lt;span class="o"&gt;());&lt;/span&gt;

&lt;span class="c1"&gt;// WEST_BENGAL_TRADITIONAL&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That explicitness is intentional.&lt;/p&gt;

&lt;h2&gt;
  
  
  The West Bengal thread: Bangabda as a living calendar
&lt;/h2&gt;

&lt;p&gt;In West Bengal, traditional calendrical practice remains woven into everyday cultural life. It is solar in structure, and month boundaries are connected to the Sun's movement from one zodiacal region to another—the transitions known as &lt;em&gt;sankranti&lt;/em&gt;. This astronomical lineage is commonly associated with the &lt;strong&gt;Surya Siddhanta&lt;/strong&gt;, a foundational Sanskrit work on astronomy. The traditional West Bengal convention therefore cannot be represented accurately as a simple fixed offset from the Gregorian calendar.&lt;/p&gt;

&lt;p&gt;And then there is the &lt;strong&gt;panjika&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Calling a panjika a “calendar” is accurate, but incomplete. It has traditionally brought together dates, tithi, nakshatra, festivals, eclipses, auspicious times, and other information people use to organize religious and social life. Printed Bengali almanacs also became part of Bengal's wider print culture. Nineteenth-century editions could carry practical information ranging from fairs and holidays to postal rates, weights, measures, agriculture, and health. The panjika was not merely something hanging on a wall; it was a compact annual guide to the world.&lt;/p&gt;

&lt;p&gt;That legacy remains visible in how many people in West Bengal experience the year. Poila Boishakh marks the Bengali New Year, and businesses may open a fresh &lt;strong&gt;haal khata&lt;/strong&gt;, traditionally beginning a new ledger and renewing relationships with customers. Celebrations vary across families and communities; common practices include exchanging &lt;em&gt;Shubho Noboborsho&lt;/em&gt; greetings, wearing new clothes, sharing sweets and festive meals, and, for some, visiting temples.&lt;/p&gt;

&lt;p&gt;Traditional panjikas also help many communities determine the dates of observances such as Durga Puja, Kali Puja, Saraswati Puja, Jamai Shashthi, and Bhai Phonta. Many of these depend on tithi and other lunisolar or ritual calculations. To be clear, &lt;strong&gt;bengali-calendar is a date-conversion library, not a complete replacement for a traditional panjika or a general-purpose tithi engine&lt;/strong&gt;. That boundary matters just as much as the features the library does provide.&lt;/p&gt;

&lt;p&gt;For me, the West Bengal calendar carries memories as much as dates: the turn of a season, handwritten entries in a new calendar, the anticipation before Pujo, and the familiar Bengali month name that makes a Gregorian date feel closer to home.&lt;/p&gt;

&lt;h2&gt;
  
  
  Meet the twelve Bengali months
&lt;/h2&gt;

&lt;p&gt;The Bengali year begins with &lt;strong&gt;Baishakh&lt;/strong&gt;, not January, and runs through twelve months grouped into six traditional seasons. Each pair of months gives the year a different mood—from the heat of &lt;em&gt;Grishma&lt;/em&gt; to the rain of &lt;em&gt;Barsha&lt;/em&gt;, the clear skies of &lt;em&gt;Sharat&lt;/em&gt;, and the colour of &lt;em&gt;Basanta&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;Gregorian ranges below are deliberately approximate. Exact boundaries can shift with the year and the chosen Bengali calendar system.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;No.&lt;/th&gt;
&lt;th&gt;Bengali month&lt;/th&gt;
&lt;th&gt;Transliteration&lt;/th&gt;
&lt;th&gt;Season&lt;/th&gt;
&lt;th&gt;Rough Gregorian period&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;১&lt;/td&gt;
&lt;td&gt;বৈশাখ&lt;/td&gt;
&lt;td&gt;Baishakh&lt;/td&gt;
&lt;td&gt;গ্রীষ্ম — Grishma (summer)&lt;/td&gt;
&lt;td&gt;April–May&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;২&lt;/td&gt;
&lt;td&gt;জ্যৈষ্ঠ&lt;/td&gt;
&lt;td&gt;Jyaistha&lt;/td&gt;
&lt;td&gt;গ্রীষ্ম — Grishma (summer)&lt;/td&gt;
&lt;td&gt;May–June&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;৩&lt;/td&gt;
&lt;td&gt;আষাঢ়&lt;/td&gt;
&lt;td&gt;Ashadha&lt;/td&gt;
&lt;td&gt;বর্ষা — Barsha (monsoon)&lt;/td&gt;
&lt;td&gt;June–July&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;৪&lt;/td&gt;
&lt;td&gt;শ্রাবণ&lt;/td&gt;
&lt;td&gt;Shraban&lt;/td&gt;
&lt;td&gt;বর্ষা — Barsha (monsoon)&lt;/td&gt;
&lt;td&gt;July–August&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;৫&lt;/td&gt;
&lt;td&gt;ভাদ্র&lt;/td&gt;
&lt;td&gt;Bhadra&lt;/td&gt;
&lt;td&gt;শরৎ — Sharat (autumn)&lt;/td&gt;
&lt;td&gt;August–September&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;৬&lt;/td&gt;
&lt;td&gt;আশ্বিন&lt;/td&gt;
&lt;td&gt;Ashwin&lt;/td&gt;
&lt;td&gt;শরৎ — Sharat (autumn)&lt;/td&gt;
&lt;td&gt;September–October&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;৭&lt;/td&gt;
&lt;td&gt;কার্তিক&lt;/td&gt;
&lt;td&gt;Kartik&lt;/td&gt;
&lt;td&gt;হেমন্ত — Hemanta (late autumn)&lt;/td&gt;
&lt;td&gt;October–November&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;৮&lt;/td&gt;
&lt;td&gt;অগ্রহায়ণ&lt;/td&gt;
&lt;td&gt;Agrahayan&lt;/td&gt;
&lt;td&gt;হেমন্ত — Hemanta (late autumn)&lt;/td&gt;
&lt;td&gt;November–December&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;৯&lt;/td&gt;
&lt;td&gt;পৌষ&lt;/td&gt;
&lt;td&gt;Paush&lt;/td&gt;
&lt;td&gt;শীত — Shita (winter)&lt;/td&gt;
&lt;td&gt;December–January&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;১০&lt;/td&gt;
&lt;td&gt;মাঘ&lt;/td&gt;
&lt;td&gt;Magh&lt;/td&gt;
&lt;td&gt;শীত — Shita (winter)&lt;/td&gt;
&lt;td&gt;January–February&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;১১&lt;/td&gt;
&lt;td&gt;ফাল্গুন&lt;/td&gt;
&lt;td&gt;Falgun&lt;/td&gt;
&lt;td&gt;বসন্ত — Basanta (spring)&lt;/td&gt;
&lt;td&gt;February–March&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;১২&lt;/td&gt;
&lt;td&gt;চৈত্র&lt;/td&gt;
&lt;td&gt;Chaitra&lt;/td&gt;
&lt;td&gt;বসন্ত — Basanta (spring)&lt;/td&gt;
&lt;td&gt;March–April&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;These names are not just labels. Baishakh opens the year with Poila Boishakh; Ashwin is inseparable in West Bengal from the anticipation of Durga Puja; Agrahayan carries the memory of the harvest; Paush evokes winter fairs and &lt;em&gt;pithe-puli&lt;/em&gt;; Falgun arrives with spring colour; and Chaitra closes the year before the cycle begins again.&lt;/p&gt;

&lt;p&gt;The library exposes the months in calendar order and in both scripts:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;BengaliMonth&lt;/span&gt; &lt;span class="n"&gt;month&lt;/span&gt; &lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;BengaliMonth&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;values&lt;/span&gt;&lt;span class="o"&gt;())&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="nc"&gt;System&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;out&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;printf&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
        &lt;span class="s"&gt;"%d. %s — %s%n"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;month&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getValue&lt;/span&gt;&lt;span class="o"&gt;(),&lt;/span&gt;
        &lt;span class="n"&gt;month&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getDisplayName&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;BengaliLocale&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;BENGALI&lt;/span&gt;&lt;span class="o"&gt;),&lt;/span&gt;
        &lt;span class="n"&gt;month&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getDisplayName&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;BengaliLocale&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;ENGLISH&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
    &lt;span class="o"&gt;);&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// 1. বৈশাখ — Baishakh&lt;/span&gt;
&lt;span class="c1"&gt;// 2. জ্যৈষ্ঠ — Jyaistha&lt;/span&gt;
&lt;span class="c1"&gt;// 3. আষাঢ় — Ashadha&lt;/span&gt;
&lt;span class="c1"&gt;// 4. শ্রাবণ — Shraban&lt;/span&gt;
&lt;span class="c1"&gt;// 5. ভাদ্র — Bhadra&lt;/span&gt;
&lt;span class="c1"&gt;// 6. আশ্বিন — Ashwin&lt;/span&gt;
&lt;span class="c1"&gt;// 7. কার্তিক — Kartik&lt;/span&gt;
&lt;span class="c1"&gt;// 8. অগ্রহায়ণ — Agrahayan&lt;/span&gt;
&lt;span class="c1"&gt;// 9. পৌষ — Paush&lt;/span&gt;
&lt;span class="c1"&gt;// 10. মাঘ — Magh&lt;/span&gt;
&lt;span class="c1"&gt;// 11. ফাল্গুন — Falgun&lt;/span&gt;
&lt;span class="c1"&gt;// 12. চৈত্র — Chaitra&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Why West Bengal Traditional is the default
&lt;/h2&gt;

&lt;p&gt;If no calendar system is supplied, the library uses:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nc"&gt;BengaliDate&lt;/span&gt; &lt;span class="n"&gt;today&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;BengaliCalendar&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;today&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;

&lt;span class="nc"&gt;System&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;out&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;println&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;today&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getCalendarSystem&lt;/span&gt;&lt;span class="o"&gt;());&lt;/span&gt;
&lt;span class="c1"&gt;// WEST_BENGAL_TRADITIONAL&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I chose that default because I am from West Bengal and it is the convention closest to the context in which I experienced the Bengali calendar. It is a personal and documented default—not a claim that the West Bengal convention is more authentic, or that every Bengali application should use it.&lt;/p&gt;

&lt;p&gt;Bengali-speaking communities in West Bengal and Bangladesh use distinct calendar conventions shaped by their own histories. Software should respect those differences by making the chosen system visible:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nc"&gt;BengaliDate&lt;/span&gt; &lt;span class="n"&gt;westBengalDate&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;BengaliCalendar&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;from&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
    &lt;span class="nc"&gt;LocalDate&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;now&lt;/span&gt;&lt;span class="o"&gt;(),&lt;/span&gt;
    &lt;span class="nc"&gt;BengaliCalendarSystem&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;WEST_BENGAL_TRADITIONAL&lt;/span&gt;
&lt;span class="o"&gt;);&lt;/span&gt;

&lt;span class="nc"&gt;BengaliDate&lt;/span&gt; &lt;span class="n"&gt;bangladeshDate&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;BengaliCalendar&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;from&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
    &lt;span class="nc"&gt;LocalDate&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;now&lt;/span&gt;&lt;span class="o"&gt;(),&lt;/span&gt;
    &lt;span class="nc"&gt;BengaliCalendarSystem&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;BANGLADESH_REVISED&lt;/span&gt;
&lt;span class="o"&gt;);&lt;/span&gt;

&lt;span class="nc"&gt;System&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;out&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;println&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;westBengalDate&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getCalendarSystem&lt;/span&gt;&lt;span class="o"&gt;());&lt;/span&gt;
&lt;span class="nc"&gt;System&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;out&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;println&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;bangladeshDate&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getCalendarSystem&lt;/span&gt;&lt;span class="o"&gt;());&lt;/span&gt;

&lt;span class="c1"&gt;// WEST_BENGAL_TRADITIONAL&lt;/span&gt;
&lt;span class="c1"&gt;// BANGLADESH_REVISED&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The default reflects where I come from. The API makes room for more than where I come from.&lt;/p&gt;

&lt;h2&gt;
  
  
  Installation
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Maven
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;dependency&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;groupId&amp;gt;&lt;/span&gt;com.tapadyuti&lt;span class="nt"&gt;&amp;lt;/groupId&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;artifactId&amp;gt;&lt;/span&gt;bengali-calendar&lt;span class="nt"&gt;&amp;lt;/artifactId&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;version&amp;gt;&lt;/span&gt;1.0.0&lt;span class="nt"&gt;&amp;lt;/version&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/dependency&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Gradle
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight groovy"&gt;&lt;code&gt;&lt;span class="n"&gt;implementation&lt;/span&gt; &lt;span class="s1"&gt;'com.tapadyuti:bengali-calendar:1.0.0'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The library requires &lt;strong&gt;Java 21 or later&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;If your project uses JPMS, add:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="n"&gt;requires&lt;/span&gt; &lt;span class="n"&gt;com&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;tapadyuti&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;bengalicalendar&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Your first conversion
&lt;/h2&gt;

&lt;p&gt;The smallest useful example is a Gregorian-to-Bengali conversion:&lt;/p&gt;

&lt;p&gt;In the examples below, lines beginning with &lt;code&gt;//&lt;/code&gt; show the expected console output. Results based on &lt;code&gt;LocalDate.now()&lt;/code&gt; are marked as variable.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;com.tapadyuti.bengalicalendar.BengaliCalendar&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;com.tapadyuti.bengalicalendar.BengaliCalendarSystem&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;com.tapadyuti.bengalicalendar.BengaliDate&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;java.time.LocalDate&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;BengaliCalendarDemo&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="o"&gt;[]&lt;/span&gt; &lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="nc"&gt;LocalDate&lt;/span&gt; &lt;span class="n"&gt;gregorian&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;LocalDate&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;of&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2024&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;14&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;

        &lt;span class="nc"&gt;BengaliDate&lt;/span&gt; &lt;span class="n"&gt;bengali&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;BengaliCalendar&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;from&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;gregorian&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
            &lt;span class="nc"&gt;BengaliCalendarSystem&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;BANGLADESH_REVISED&lt;/span&gt;
        &lt;span class="o"&gt;);&lt;/span&gt;

        &lt;span class="nc"&gt;System&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;out&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;println&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;bengali&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getYear&lt;/span&gt;&lt;span class="o"&gt;());&lt;/span&gt;       &lt;span class="c1"&gt;// 1431&lt;/span&gt;
        &lt;span class="nc"&gt;System&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;out&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;println&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;bengali&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getMonth&lt;/span&gt;&lt;span class="o"&gt;());&lt;/span&gt;      &lt;span class="c1"&gt;// BAISHAKH&lt;/span&gt;
        &lt;span class="nc"&gt;System&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;out&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;println&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;bengali&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getDayOfMonth&lt;/span&gt;&lt;span class="o"&gt;());&lt;/span&gt; &lt;span class="c1"&gt;// 1&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The conversion is bidirectional:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nc"&gt;LocalDate&lt;/span&gt; &lt;span class="n"&gt;original&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;LocalDate&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;of&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2024&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;14&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;

&lt;span class="nc"&gt;BengaliDate&lt;/span&gt; &lt;span class="n"&gt;bengali&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;BengaliCalendar&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;from&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;original&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
    &lt;span class="nc"&gt;BengaliCalendarSystem&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;BANGLADESH_REVISED&lt;/span&gt;
&lt;span class="o"&gt;);&lt;/span&gt;

&lt;span class="nc"&gt;LocalDate&lt;/span&gt; &lt;span class="n"&gt;convertedBack&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;BengaliCalendar&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;toGregorian&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;bengali&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;

&lt;span class="nc"&gt;System&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;out&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;println&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;convertedBack&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// 2024-04-14&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can also ask for today's Bengali date:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nc"&gt;BengaliDate&lt;/span&gt; &lt;span class="n"&gt;today&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;BengaliCalendar&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;today&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
&lt;span class="nc"&gt;System&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;out&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;println&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;today&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;format&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"dd MMMM yyyy E"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;BengaliLocale&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;ENGLISH&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="o"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// Output varies with the date; the final marker is WB by default.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The default is &lt;code&gt;WEST_BENGAL_TRADITIONAL&lt;/code&gt;. In reusable or shared code, I still recommend specifying the system when the distinction matters:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nc"&gt;BengaliDate&lt;/span&gt; &lt;span class="n"&gt;westBengalToday&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;BengaliCalendar&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;today&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
    &lt;span class="nc"&gt;BengaliCalendarSystem&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;WEST_BENGAL_TRADITIONAL&lt;/span&gt;
&lt;span class="o"&gt;);&lt;/span&gt;

&lt;span class="nc"&gt;BengaliDate&lt;/span&gt; &lt;span class="n"&gt;bangladeshToday&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;BengaliCalendar&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;today&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
    &lt;span class="nc"&gt;BengaliCalendarSystem&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;BANGLADESH_REVISED&lt;/span&gt;
&lt;span class="o"&gt;);&lt;/span&gt;

&lt;span class="nc"&gt;System&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;out&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;println&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;westBengalToday&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getCalendarSystem&lt;/span&gt;&lt;span class="o"&gt;());&lt;/span&gt;
&lt;span class="nc"&gt;System&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;out&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;println&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;bangladeshToday&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getCalendarSystem&lt;/span&gt;&lt;span class="o"&gt;());&lt;/span&gt;

&lt;span class="c1"&gt;// WEST_BENGAL_TRADITIONAL&lt;/span&gt;
&lt;span class="c1"&gt;// BANGLADESH_REVISED&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  One Gregorian date, two Bengali conventions
&lt;/h2&gt;

&lt;p&gt;The two systems are deliberately represented by an enum:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nc"&gt;LocalDate&lt;/span&gt; &lt;span class="n"&gt;date&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;LocalDate&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;of&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2024&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;14&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;

&lt;span class="nc"&gt;BengaliDate&lt;/span&gt; &lt;span class="n"&gt;wb&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;BengaliCalendar&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;from&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;date&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
    &lt;span class="nc"&gt;BengaliCalendarSystem&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;WEST_BENGAL_TRADITIONAL&lt;/span&gt;
&lt;span class="o"&gt;);&lt;/span&gt;

&lt;span class="nc"&gt;BengaliDate&lt;/span&gt; &lt;span class="n"&gt;bd&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;BengaliCalendar&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;from&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;date&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
    &lt;span class="nc"&gt;BengaliCalendarSystem&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;BANGLADESH_REVISED&lt;/span&gt;
&lt;span class="o"&gt;);&lt;/span&gt;

&lt;span class="nc"&gt;System&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;out&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;println&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;wb&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getCalendarSystem&lt;/span&gt;&lt;span class="o"&gt;());&lt;/span&gt; &lt;span class="c1"&gt;// WEST_BENGAL_TRADITIONAL&lt;/span&gt;
&lt;span class="nc"&gt;System&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;out&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;println&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;bd&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getCalendarSystem&lt;/span&gt;&lt;span class="o"&gt;());&lt;/span&gt; &lt;span class="c1"&gt;// BANGLADESH_REVISED&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is more than metadata. A &lt;code&gt;BengaliDate&lt;/code&gt; keeps its calendar system with it, so later operations know which rules to apply:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nc"&gt;BengaliDate&lt;/span&gt; &lt;span class="n"&gt;nextWeek&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;wb&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;plusDays&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;7&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;

&lt;span class="nc"&gt;System&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;out&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;println&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;nextWeek&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getCalendarSystem&lt;/span&gt;&lt;span class="o"&gt;());&lt;/span&gt;
&lt;span class="c1"&gt;// WEST_BENGAL_TRADITIONAL&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Configure an application-wide default
&lt;/h2&gt;

&lt;p&gt;If your application consistently uses one convention, set it once during startup:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nc"&gt;BengaliCalendar&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;setDefault&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
    &lt;span class="nc"&gt;BengaliCalendarSystem&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;BANGLADESH_REVISED&lt;/span&gt;
&lt;span class="o"&gt;);&lt;/span&gt;

&lt;span class="nc"&gt;BengaliDate&lt;/span&gt; &lt;span class="n"&gt;today&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;BengaliCalendar&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;today&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
&lt;span class="nc"&gt;BengaliDate&lt;/span&gt; &lt;span class="n"&gt;converted&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;BengaliCalendar&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;from&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;LocalDate&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;now&lt;/span&gt;&lt;span class="o"&gt;());&lt;/span&gt;

&lt;span class="nc"&gt;System&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;out&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;println&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;today&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getCalendarSystem&lt;/span&gt;&lt;span class="o"&gt;());&lt;/span&gt;
&lt;span class="nc"&gt;System&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;out&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;println&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;converted&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getCalendarSystem&lt;/span&gt;&lt;span class="o"&gt;());&lt;/span&gt;

&lt;span class="c1"&gt;// BANGLADESH_REVISED&lt;/span&gt;
&lt;span class="c1"&gt;// BANGLADESH_REVISED&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can read the current default too:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nc"&gt;BengaliCalendarSystem&lt;/span&gt; &lt;span class="n"&gt;system&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;BengaliCalendar&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getDefault&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
&lt;span class="nc"&gt;System&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;out&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;println&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;system&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// BANGLADESH_REVISED&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For servers and containers, the same choice can be supplied as a JVM property:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;java &lt;span class="nt"&gt;-Dbengalicalendar&lt;/span&gt;.default.system&lt;span class="o"&gt;=&lt;/span&gt;BANGLADESH_REVISED &lt;span class="nt"&gt;-jar&lt;/span&gt; app.jar
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Accepted short forms are &lt;code&gt;BD&lt;/code&gt; and &lt;code&gt;WB&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Bengali numerals: ০ থেকে ৯
&lt;/h2&gt;

&lt;p&gt;Bengali has its own decimal digits. They represent exactly the same values as Western digits; only the glyphs change.&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;Western digit&lt;/th&gt;
&lt;th&gt;Bengali digit&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Zero&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;০&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;One&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;১&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Two&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;২&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Three&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;৩&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Four&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;৪&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Five&lt;/td&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;৫&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Six&lt;/td&gt;
&lt;td&gt;6&lt;/td&gt;
&lt;td&gt;৬&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Seven&lt;/td&gt;
&lt;td&gt;7&lt;/td&gt;
&lt;td&gt;৭&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Eight&lt;/td&gt;
&lt;td&gt;8&lt;/td&gt;
&lt;td&gt;৮&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Nine&lt;/td&gt;
&lt;td&gt;9&lt;/td&gt;
&lt;td&gt;৯&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;So the Bengali year &lt;code&gt;1431&lt;/code&gt; is written &lt;code&gt;১৪৩১&lt;/code&gt;, and &lt;code&gt;01 Baishakh 1431&lt;/code&gt; becomes &lt;code&gt;০১ বৈশাখ ১৪৩১&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;You do not need to replace the digits yourself. Selecting &lt;code&gt;BengaliLocale.BENGALI&lt;/code&gt; makes the formatter use Bengali month names and Bengali numerals together:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nc"&gt;BengaliDate&lt;/span&gt; &lt;span class="n"&gt;pohelaBoishakh&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;BengaliDate&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;of&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
    &lt;span class="mi"&gt;1431&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
    &lt;span class="nc"&gt;BengaliMonth&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;BAISHAKH&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="nc"&gt;BengaliCalendarSystem&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;WEST_BENGAL_TRADITIONAL&lt;/span&gt;
&lt;span class="o"&gt;);&lt;/span&gt;

&lt;span class="nc"&gt;System&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;out&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;println&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;pohelaBoishakh&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;format&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"dd/MM/yyyy"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;BengaliLocale&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;ENGLISH&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="o"&gt;);&lt;/span&gt;
&lt;span class="nc"&gt;System&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;out&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;println&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;pohelaBoishakh&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;format&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"dd/MM/yyyy"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;BengaliLocale&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;BENGALI&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="o"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// 01/01/1431&lt;/span&gt;
&lt;span class="c1"&gt;// ০১/০১/১৪৩১&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The same applies in reverse: the parser can read Bengali digits directly, so users do not have to transliterate their dates before submitting them.&lt;/p&gt;

&lt;h2&gt;
  
  
  Formatting in English and Bengali
&lt;/h2&gt;

&lt;p&gt;A cultural date library should not stop at returning an enum and an integer.&lt;/p&gt;

&lt;p&gt;Format a Bengali date in English:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nc"&gt;BengaliDate&lt;/span&gt; &lt;span class="n"&gt;date&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;BengaliDate&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;of&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
    &lt;span class="mi"&gt;1431&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
    &lt;span class="nc"&gt;BengaliMonth&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;BAISHAKH&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="nc"&gt;BengaliCalendarSystem&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;WEST_BENGAL_TRADITIONAL&lt;/span&gt;
&lt;span class="o"&gt;);&lt;/span&gt;

&lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;english&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;date&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;format&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
    &lt;span class="s"&gt;"dd MMMM yyyy E"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
    &lt;span class="nc"&gt;BengaliLocale&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;ENGLISH&lt;/span&gt;
&lt;span class="o"&gt;);&lt;/span&gt;

&lt;span class="nc"&gt;System&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;out&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;println&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;english&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;span class="c1"&gt;// 01 Baishakh 1431 WB&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now format the same date in Bengali script and Bengali numerals:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;bengali&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;date&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;format&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
    &lt;span class="s"&gt;"dd MMMM yyyy E"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
    &lt;span class="nc"&gt;BengaliLocale&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;BENGALI&lt;/span&gt;
&lt;span class="o"&gt;);&lt;/span&gt;

&lt;span class="nc"&gt;System&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;out&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;println&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;bengali&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;span class="c1"&gt;// ০১ বৈশাখ ১৪৩১ WB&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The formatter supports a compact set of familiar tokens:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Token&lt;/th&gt;
&lt;th&gt;Meaning&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;d&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Day without padding&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;dd&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Zero-padded day&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;M&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Month number&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;MM&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Zero-padded month number&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;MMM&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Abbreviated month name&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;MMMM&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Full month name&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;y&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Bengali year&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;yyyy&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Four-digit Bengali year&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;E&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Calendar-system abbreviation (&lt;code&gt;BD&lt;/code&gt; or &lt;code&gt;WB&lt;/code&gt;)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nc"&gt;System&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;out&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;println&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;date&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;format&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"d/M/y"&lt;/span&gt;&lt;span class="o"&gt;));&lt;/span&gt;
&lt;span class="nc"&gt;System&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;out&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;println&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;date&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;format&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"dd-MM-yyyy E"&lt;/span&gt;&lt;span class="o"&gt;));&lt;/span&gt;
&lt;span class="nc"&gt;System&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;out&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;println&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;date&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;format&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"MMMM d, yyyy"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;BengaliLocale&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;ENGLISH&lt;/span&gt;&lt;span class="o"&gt;));&lt;/span&gt;

&lt;span class="c1"&gt;// 1/1/1431&lt;/span&gt;
&lt;span class="c1"&gt;// 01-01-1431 WB&lt;/span&gt;
&lt;span class="c1"&gt;// Baishakh 1, 1431&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Parsing—including Bengali numerals
&lt;/h2&gt;

&lt;p&gt;Formatting is only half the job. User input has to make the return journey.&lt;/p&gt;

&lt;p&gt;Parse an English-numeral date:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nc"&gt;BengaliDate&lt;/span&gt; &lt;span class="n"&gt;parsed&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;BengaliDateFormatter&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;parse&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
    &lt;span class="s"&gt;"01-01-1431"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
    &lt;span class="s"&gt;"dd-MM-yyyy"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
    &lt;span class="nc"&gt;BengaliCalendarSystem&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;BANGLADESH_REVISED&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
    &lt;span class="nc"&gt;BengaliLocale&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;ENGLISH&lt;/span&gt;
&lt;span class="o"&gt;);&lt;/span&gt;

&lt;span class="nc"&gt;System&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;out&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;println&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;parsed&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;format&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"dd MMMM yyyy E"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;BengaliLocale&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;ENGLISH&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="o"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// 01 Baishakh 1431 BD&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Or parse Bengali numerals directly:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nc"&gt;BengaliDate&lt;/span&gt; &lt;span class="n"&gt;parsedBengali&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;BengaliDateFormatter&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;parse&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
    &lt;span class="s"&gt;"০১-০১-১৪৩১"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
    &lt;span class="s"&gt;"dd-MM-yyyy"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
    &lt;span class="nc"&gt;BengaliCalendarSystem&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;WEST_BENGAL_TRADITIONAL&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
    &lt;span class="nc"&gt;BengaliLocale&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;BENGALI&lt;/span&gt;
&lt;span class="o"&gt;);&lt;/span&gt;

&lt;span class="nc"&gt;System&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;out&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;println&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;parsedBengali&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;format&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"dd MMMM yyyy E"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;BengaliLocale&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;BENGALI&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="o"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// ০১ বৈশাখ ১৪৩১ WB&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then convert the result into a regular Java date whenever you need to store, compare, or integrate it with another system:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nc"&gt;LocalDate&lt;/span&gt; &lt;span class="n"&gt;isoDate&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;parsedBengali&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;toGregorian&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
&lt;span class="nc"&gt;System&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;out&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;println&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;isoDate&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// 2024-04-14&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Date arithmetic without manual month tables
&lt;/h2&gt;

&lt;p&gt;Nobody should have to write a chain of &lt;code&gt;if&lt;/code&gt; statements just to find “ten Bengali days from now.”&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nc"&gt;BengaliDate&lt;/span&gt; &lt;span class="n"&gt;pohelaBoishakh&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;BengaliDate&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;of&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
    &lt;span class="mi"&gt;1431&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
    &lt;span class="nc"&gt;BengaliMonth&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;BAISHAKH&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="nc"&gt;BengaliCalendarSystem&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;BANGLADESH_REVISED&lt;/span&gt;
&lt;span class="o"&gt;);&lt;/span&gt;

&lt;span class="nc"&gt;BengaliDate&lt;/span&gt; &lt;span class="n"&gt;tenDaysLater&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pohelaBoishakh&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;plusDays&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;span class="nc"&gt;BengaliDate&lt;/span&gt; &lt;span class="n"&gt;nextMonth&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pohelaBoishakh&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;plusMonths&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="nc"&gt;BengaliDate&lt;/span&gt; &lt;span class="n"&gt;nextYear&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pohelaBoishakh&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;plusYears&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="nc"&gt;BengaliDate&lt;/span&gt; &lt;span class="n"&gt;previousDay&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pohelaBoishakh&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;minusDays&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="nc"&gt;System&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;out&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;println&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tenDaysLater&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;format&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"dd MMMM yyyy E"&lt;/span&gt;&lt;span class="o"&gt;));&lt;/span&gt;
&lt;span class="nc"&gt;System&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;out&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;println&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;nextMonth&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;format&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"dd MMMM yyyy E"&lt;/span&gt;&lt;span class="o"&gt;));&lt;/span&gt;
&lt;span class="nc"&gt;System&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;out&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;println&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;nextYear&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;format&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"dd MMMM yyyy E"&lt;/span&gt;&lt;span class="o"&gt;));&lt;/span&gt;
&lt;span class="nc"&gt;System&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;out&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;println&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;previousDay&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;format&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"dd MMMM yyyy E"&lt;/span&gt;&lt;span class="o"&gt;));&lt;/span&gt;

&lt;span class="c1"&gt;// 11 Baishakh 1431 BD&lt;/span&gt;
&lt;span class="c1"&gt;// 01 Jyaistha 1431 BD&lt;/span&gt;
&lt;span class="c1"&gt;// 01 Baishakh 1432 BD&lt;/span&gt;
&lt;span class="c1"&gt;// 30 Chaitra 1430 BD&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Comparisons are available directly:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tenDaysLater&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;isAfter&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pohelaBoishakh&lt;/span&gt;&lt;span class="o"&gt;))&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="nc"&gt;System&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;out&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;println&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Time still works."&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// Time still works.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Because &lt;code&gt;BengaliDate&lt;/code&gt; implements &lt;code&gt;ChronoLocalDate&lt;/code&gt;, it also participates in Java's temporal APIs:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="kt"&gt;long&lt;/span&gt; &lt;span class="n"&gt;epochDay&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pohelaBoishakh&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;toEpochDay&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
&lt;span class="kt"&gt;long&lt;/span&gt; &lt;span class="n"&gt;daysBetween&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pohelaBoishakh&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;until&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tenDaysLater&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;java&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;time&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;temporal&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;ChronoUnit&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;DAYS&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;

&lt;span class="nc"&gt;System&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;out&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;println&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;epochDay&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;span class="nc"&gt;System&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;out&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;println&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;daysBetween&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// 19827&lt;/span&gt;
&lt;span class="c1"&gt;// 10&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Building a calendar UI
&lt;/h2&gt;

&lt;p&gt;For a calendar grid, &lt;code&gt;BengaliYearMonth&lt;/code&gt; avoids repeatedly reconstructing year and month information:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nc"&gt;BengaliCalendar&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;setDefault&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
    &lt;span class="nc"&gt;BengaliCalendarSystem&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;WEST_BENGAL_TRADITIONAL&lt;/span&gt;
&lt;span class="o"&gt;);&lt;/span&gt;

&lt;span class="nc"&gt;BengaliYearMonth&lt;/span&gt; &lt;span class="n"&gt;baishakh&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;BengaliYearMonth&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;of&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
    &lt;span class="mi"&gt;1431&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
    &lt;span class="nc"&gt;BengaliMonth&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;BAISHAKH&lt;/span&gt;
&lt;span class="o"&gt;);&lt;/span&gt;

&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;daysInMonth&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;baishakh&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;lengthOfMonth&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
&lt;span class="nc"&gt;BengaliDate&lt;/span&gt; &lt;span class="n"&gt;twelfthDay&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;baishakh&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;atDay&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;12&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;

&lt;span class="nc"&gt;System&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;out&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;println&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;daysInMonth&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;span class="nc"&gt;System&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;out&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;println&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;twelfthDay&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;format&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"dd MMMM yyyy E"&lt;/span&gt;&lt;span class="o"&gt;));&lt;/span&gt;

&lt;span class="c1"&gt;// 31&lt;/span&gt;
&lt;span class="c1"&gt;// 12 Baishakh 1431 WB&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For inclusive intervals, use &lt;code&gt;BengaliDateRange&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nc"&gt;BengaliDate&lt;/span&gt; &lt;span class="n"&gt;start&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;BengaliDate&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;of&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
    &lt;span class="mi"&gt;1431&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
    &lt;span class="nc"&gt;BengaliMonth&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;BAISHAKH&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="nc"&gt;BengaliCalendarSystem&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;WEST_BENGAL_TRADITIONAL&lt;/span&gt;
&lt;span class="o"&gt;);&lt;/span&gt;

&lt;span class="nc"&gt;BengaliDate&lt;/span&gt; &lt;span class="n"&gt;end&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;start&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;plusDays&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;span class="nc"&gt;BengaliDateRange&lt;/span&gt; &lt;span class="n"&gt;firstWeek&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;BengaliDateRange&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;of&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;start&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;end&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;

&lt;span class="nc"&gt;System&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;out&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;println&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;firstWeek&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;lengthInDays&lt;/span&gt;&lt;span class="o"&gt;());&lt;/span&gt; &lt;span class="c1"&gt;// 7&lt;/span&gt;

&lt;span class="n"&gt;firstWeek&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;stream&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;
    &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;map&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;d&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;format&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"dd MMMM"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;BengaliLocale&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;BENGALI&lt;/span&gt;&lt;span class="o"&gt;))&lt;/span&gt;
    &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;forEach&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;System&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;out&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;println&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// ০১ বৈশাখ&lt;/span&gt;
&lt;span class="c1"&gt;// ০২ বৈশাখ&lt;/span&gt;
&lt;span class="c1"&gt;// ০৩ বৈশাখ&lt;/span&gt;
&lt;span class="c1"&gt;// ০৪ বৈশাখ&lt;/span&gt;
&lt;span class="c1"&gt;// ০৫ বৈশাখ&lt;/span&gt;
&lt;span class="c1"&gt;// ০৬ বৈশাখ&lt;/span&gt;
&lt;span class="c1"&gt;// ০৭ বৈশাখ&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can also ask whether a date belongs to a range:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="kt"&gt;boolean&lt;/span&gt; &lt;span class="n"&gt;inside&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;firstWeek&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;contains&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;start&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;plusDays&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="o"&gt;));&lt;/span&gt;
&lt;span class="nc"&gt;System&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;out&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;println&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;inside&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// true&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Seasons, weekdays, and selected holidays
&lt;/h2&gt;

&lt;p&gt;The Bengali year is traditionally divided into six seasons, each associated with two months. That relationship is available from a date:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nc"&gt;BengaliSeason&lt;/span&gt; &lt;span class="n"&gt;season&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pohelaBoishakh&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getSeason&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;

&lt;span class="nc"&gt;System&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;out&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;println&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;season&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getDisplayName&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;BengaliLocale&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;BENGALI&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="o"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// গ্রীষ্ম&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Weekday names are available as well:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nc"&gt;BengaliDayOfWeek&lt;/span&gt; &lt;span class="n"&gt;weekday&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pohelaBoishakh&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getDayOfWeek&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;

&lt;span class="nc"&gt;System&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;out&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;println&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;weekday&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getDisplayName&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;BengaliLocale&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;BENGALI&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="o"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// রবিবার&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And selected cultural holidays can be queried from a date:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="n"&gt;pohelaBoishakh&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getHoliday&lt;/span&gt;&lt;span class="o"&gt;().&lt;/span&gt;&lt;span class="na"&gt;ifPresent&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;holiday&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;
    &lt;span class="nc"&gt;System&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;out&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;println&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;holiday&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getDisplayName&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;BengaliLocale&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;ENGLISH&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
    &lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="o"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// Pohela Boishakh&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Or collected for a Bengali year and system:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nc"&gt;Map&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;BengaliDate&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;BengaliHoliday&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;holidays&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
    &lt;span class="nc"&gt;BengaliHoliday&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;forYear&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
        &lt;span class="mi"&gt;1431&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
        &lt;span class="nc"&gt;BengaliCalendarSystem&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;BANGLADESH_REVISED&lt;/span&gt;
    &lt;span class="o"&gt;);&lt;/span&gt;

&lt;span class="nc"&gt;System&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;out&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;println&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;holidays&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;size&lt;/span&gt;&lt;span class="o"&gt;());&lt;/span&gt;

&lt;span class="c1"&gt;// 3&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Holiday data should be treated as a cultural convenience, not as a replacement for an official legal or organizational holiday schedule.&lt;/p&gt;

&lt;h2&gt;
  
  
  Working with &lt;code&gt;java.time.Chronology&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;For advanced integrations, the chronology can be resolved through Java's service-loading mechanism:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;java.time.chrono.Chronology&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

&lt;span class="nc"&gt;Chronology&lt;/span&gt; &lt;span class="n"&gt;bengali&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Chronology&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;of&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Bengali"&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;

&lt;span class="nc"&gt;System&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;out&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;println&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;bengali&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getId&lt;/span&gt;&lt;span class="o"&gt;());&lt;/span&gt;           &lt;span class="c1"&gt;// Bengali&lt;/span&gt;
&lt;span class="nc"&gt;System&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;out&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;println&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;bengali&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getCalendarType&lt;/span&gt;&lt;span class="o"&gt;());&lt;/span&gt; &lt;span class="c1"&gt;// bengali&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Or accessed directly:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nc"&gt;BengaliChronology&lt;/span&gt; &lt;span class="n"&gt;chronology&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;BengaliChronology&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;INSTANCE&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="nc"&gt;System&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;out&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;println&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;chronology&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getId&lt;/span&gt;&lt;span class="o"&gt;());&lt;/span&gt;

&lt;span class="c1"&gt;// Bengali&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Most applications will find &lt;code&gt;BengaliCalendar&lt;/code&gt; and &lt;code&gt;BengaliDate&lt;/code&gt; simpler. The chronology integration is there for code that already works generically with Java calendar systems.&lt;/p&gt;

&lt;h2&gt;
  
  
  A small testing detail that matters
&lt;/h2&gt;

&lt;p&gt;“Today” is usually the enemy of deterministic tests. The library therefore accepts a &lt;code&gt;Clock&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;java.time.Clock&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;java.time.Instant&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;java.time.ZoneOffset&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

&lt;span class="nc"&gt;Clock&lt;/span&gt; &lt;span class="n"&gt;fixedClock&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Clock&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;fixed&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
    &lt;span class="nc"&gt;Instant&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;parse&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"2024-04-14T00:00:00Z"&lt;/span&gt;&lt;span class="o"&gt;),&lt;/span&gt;
    &lt;span class="nc"&gt;ZoneOffset&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;UTC&lt;/span&gt;
&lt;span class="o"&gt;);&lt;/span&gt;

&lt;span class="nc"&gt;BengaliCalendar&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;setDefault&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
    &lt;span class="nc"&gt;BengaliCalendarSystem&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;WEST_BENGAL_TRADITIONAL&lt;/span&gt;
&lt;span class="o"&gt;);&lt;/span&gt;

&lt;span class="nc"&gt;BengaliDate&lt;/span&gt; &lt;span class="n"&gt;predictableToday&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;BengaliCalendar&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;today&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;fixedClock&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;

&lt;span class="nc"&gt;System&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;out&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;println&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;predictableToday&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;format&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"dd MMMM yyyy E"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;BengaliLocale&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;ENGLISH&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="o"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// 01 Baishakh 1431 WB&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That lets application tests stay stable regardless of when or where they run.&lt;/p&gt;

&lt;h2&gt;
  
  
  What this library is—and what it is not
&lt;/h2&gt;

&lt;p&gt;This library is intended to provide a practical Java API for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;application-level date conversion&lt;/li&gt;
&lt;li&gt;locale-aware display and parsing&lt;/li&gt;
&lt;li&gt;date arithmetic&lt;/li&gt;
&lt;li&gt;calendar UI helpers&lt;/li&gt;
&lt;li&gt;interoperability with &lt;code&gt;java.time&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It does not claim that the history or practice of Bengali calendrical computation can be reduced to one universally agreed table. Traditional almanacs, astronomical conventions, civil reforms, locations, and historical periods all matter.&lt;/p&gt;

&lt;p&gt;For production code, choose the calendar system explicitly when the distinction has business or cultural significance. For historical research, religious observance, or legal use, validate results against an authoritative source for the relevant place and period.&lt;/p&gt;

&lt;p&gt;That is not a weakness of the subject. It is the subject.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why I built it
&lt;/h2&gt;

&lt;p&gt;The first version began with a straightforward requirement: convert a Gregorian date into a Bengali date.&lt;/p&gt;

&lt;p&gt;The requirement did not stay straightforward for long.&lt;/p&gt;

&lt;p&gt;Once I considered the different calendar conventions used in West Bengal and Bangladesh, along with Bengali numerals, parsing, date arithmetic, seasons, holidays, ranges, and the &lt;code&gt;java.time&lt;/code&gt; type system, it became clear that a converter function would not be enough. I wanted an API that felt natural to a Java developer without flattening the cultural differences it represents.&lt;/p&gt;

&lt;p&gt;The result is intentionally familiar:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nc"&gt;BengaliDate&lt;/span&gt; &lt;span class="n"&gt;tomorrow&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;BengaliCalendar&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;today&lt;/span&gt;&lt;span class="o"&gt;().&lt;/span&gt;&lt;span class="na"&gt;plusDays&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="nc"&gt;System&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;out&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;println&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;tomorrow&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;format&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"dd MMMM yyyy E"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;BengaliLocale&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;ENGLISH&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="o"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// Output varies with the date; the final marker is WB by default.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;But it also keeps the important part visible:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nc"&gt;BengaliDate&lt;/span&gt; &lt;span class="n"&gt;current&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;BengaliCalendar&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;from&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
    &lt;span class="nc"&gt;LocalDate&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;now&lt;/span&gt;&lt;span class="o"&gt;(),&lt;/span&gt;
    &lt;span class="nc"&gt;BengaliCalendarSystem&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;WEST_BENGAL_TRADITIONAL&lt;/span&gt;
&lt;span class="o"&gt;);&lt;/span&gt;

&lt;span class="nc"&gt;System&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;out&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;println&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;current&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getCalendarSystem&lt;/span&gt;&lt;span class="o"&gt;());&lt;/span&gt;

&lt;span class="c1"&gt;// WEST_BENGAL_TRADITIONAL&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Convenience where it helps; explicitness where it matters.&lt;/p&gt;

&lt;h2&gt;
  
  
  I am open to collaboration
&lt;/h2&gt;

&lt;p&gt;The repository is not open for public contribution yet, but I am very open to conversations and collaboration around the project.&lt;/p&gt;

&lt;p&gt;I would especially like to hear from:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Bengali calendar and panjika experts&lt;/li&gt;
&lt;li&gt;historians and researchers working on West Bengal's calendrical and almanac traditions&lt;/li&gt;
&lt;li&gt;developers from Bangladesh and West Bengal with real-world date examples&lt;/li&gt;
&lt;li&gt;Java &lt;code&gt;java.time&lt;/code&gt; and chronology specialists&lt;/li&gt;
&lt;li&gt;localization and Bengali-language contributors&lt;/li&gt;
&lt;li&gt;developers building calendar, cultural, education, or archival applications&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Useful collaboration could include validating edge cases, comparing authoritative date tables, expanding tests, improving transliterations, refining cultural data, or shaping a future public contribution model.&lt;/p&gt;

&lt;p&gt;If the library is useful to you—or if you find a date that deserves a closer look—leave a comment or reach out through my Dev.to profile. I would rather improve the library through informed discussion than pretend calendars are simpler than they are.&lt;/p&gt;

&lt;h2&gt;
  
  
  Links
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://central.sonatype.com/artifact/com.tapadyuti/bengali-calendar/1.0.0" rel="noopener noreferrer"&gt;Maven Central&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://javadoc.io/doc/com.tapadyuti/bengali-calendar/1.0.0" rel="noopener noreferrer"&gt;Javadocs&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://en.banglapedia.org/index.php?title=Bangabda" rel="noopener noreferrer"&gt;Bangabda — Banglapedia&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://thefinancialexpress.com.bd/views/views/origins-of-bangla-calendar" rel="noopener noreferrer"&gt;Origins of the Bangla calendar — The Financial Express&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://indianexpress.com/article/when-is/when-is-pohela-boishakh-2024-date-history-ritual-and-all-you-need-to-know-9264588/" rel="noopener noreferrer"&gt;Poila Boishakh: history, rituals, and haal khata — The Indian Express&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://ignca.gov.in/Asi_data/34958.pdf" rel="noopener noreferrer"&gt;The Indian Calendar — Government of India, IGNCA&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://cec.nic.in/webpath/curriculum/Module/FART/Paper42/12/downloads/script.pdf" rel="noopener noreferrer"&gt;The history of printed Bengali almanacs — Consortium for Educational Communication&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.prothomalo.com/bangladesh/%E0%A6%AC%E0%A6%BE%E0%A6%82%E0%A6%B2%E0%A6%BE-%E0%A6%A6%E0%A6%BF%E0%A6%A8%E0%A6%AA%E0%A6%9E%E0%A7%8D%E0%A6%9C%E0%A6%BF-%E0%A6%AC%E0%A6%A6%E0%A6%B2-%E0%A6%86%E0%A6%9C-%E0%A6%AA%E0%A7%9F%E0%A6%B2%E0%A6%BE-%E0%A6%95%E0%A6%BE%E0%A6%B0%E0%A7%8D%E0%A6%A4%E0%A6%BF%E0%A6%95" rel="noopener noreferrer"&gt;Bangladesh's 2019 civil calendar revision — Prothom Alo&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.dhakatribune.com/bangladesh/191106/ashshin-in-31-days-in-revised-bangla-calendar" rel="noopener noreferrer"&gt;Ashwin becomes a 31-day month under the revised Bangladesh calendar — Dhaka Tribune&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you try it, I would love to know what you build.&lt;/p&gt;

</description>
      <category>java</category>
      <category>maven</category>
      <category>localization</category>
      <category>programming</category>
    </item>
    <item>
      <title>Building Tab Timekeeper: A Small Chrome Extension for Making Browsing Time Visible</title>
      <dc:creator>tapadyuti chatterjee</dc:creator>
      <pubDate>Wed, 19 Aug 2026 02:53:36 +0000</pubDate>
      <link>https://dev.to/tapadyutichatterjee/building-tab-timekeeper-a-small-chrome-extension-for-making-browsing-time-visible-5gb0</link>
      <guid>https://dev.to/tapadyutichatterjee/building-tab-timekeeper-a-small-chrome-extension-for-making-browsing-time-visible-5gb0</guid>
      <description>&lt;p&gt;I built &lt;a href="https://chromewebstore.google.com/detail/tab-timekeeper/fncodeopdbkmbfkljmjdaekmimnbpdeh" rel="noopener noreferrer"&gt;Tab Timekeeper&lt;/a&gt; around a small question that is surprisingly easy to lose track of while browsing:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;How long have I been on this website in this tab?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Browsers make it easy to open a page, follow a link, switch tabs, and return much later. What they do not normally show is the time behind that activity. Tab Timekeeper adds that missing bit of context. Click the extension icon and its popup displays a running timer for the active tab, along with the website's favicon.&lt;/p&gt;

&lt;p&gt;The extension is intentionally small. There is no framework, backend, account, analytics pipeline, or cross-device synchronization. It is a Manifest V3 extension built with HTML and plain JavaScript, using browser-provided storage and extension APIs.&lt;/p&gt;

&lt;p&gt;That small scope made it a useful project for learning how the parts of a Chrome extension cooperate: the manifest, service worker, content script, popup, page context, and permissions.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Flh3.googleusercontent.com%2F57qxmXpijmqumwTbvC5I5IzOHfart4EbLw-s9L1K3HkcywtggZ8Cx7BYlh2tTJRGpypNHHrBn4j6W5ie8Ijj7zw6%3Ds1280-w1280-h800" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Flh3.googleusercontent.com%2F57qxmXpijmqumwTbvC5I5IzOHfart4EbLw-s9L1K3HkcywtggZ8Cx7BYlh2tTJRGpypNHHrBn4j6W5ie8Ijj7zw6%3Ds1280-w1280-h800" alt="Tab Timekeeper running on Wikipedia, with the extension popup showing one minute and four seconds" width="1280" height="800"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Tab Timekeeper keeps the interaction simple: open the popup and see the elapsed time for the current website in this tab.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;The extension is available on the Chrome Web Store, where it has also received Chrome's &lt;strong&gt;Featured&lt;/strong&gt; badge.&lt;/p&gt;

&lt;h2&gt;
  
  
  TL;DR
&lt;/h2&gt;

&lt;p&gt;Tab Timekeeper is a lightweight Chrome extension that shows how long the current website has been open in the active tab.&lt;/p&gt;

&lt;p&gt;It uses a Manifest V3 configuration, a background service worker, a content script, and a popup written in plain HTML and JavaScript. The content script stores a start timestamp and the current origin in the page's &lt;code&gt;sessionStorage&lt;/code&gt;. The popup queries the active tab once per second, injects a small function into that page, and reads back the elapsed time and favicon. The background service worker listens for tab activation and completed page loads, although its timestamp is written to &lt;code&gt;localStorage&lt;/code&gt; and is separate from the &lt;code&gt;sessionStorage&lt;/code&gt; value currently used by the popup.&lt;/p&gt;

&lt;p&gt;The project reinforced a few useful lessons: browser storage scope is an architectural decision, extension contexts need an explicit bridge, permissions should stay narrow, and a small utility benefits from keeping its interface and implementation equally focused.&lt;/p&gt;

&lt;p&gt;Transparency note: I used AI to help format and polish the wording of this article. The app, architecture, implementation decisions, and experiences described here are my own.&lt;/p&gt;

&lt;h2&gt;
  
  
  The idea behind Tab Timekeeper
&lt;/h2&gt;

&lt;p&gt;Many productivity tools begin by trying to change behavior. Tab Timekeeper begins one step earlier: it makes behavior visible.&lt;/p&gt;

&lt;p&gt;The goal was not to build a full activity tracker or a system that judges which sites are productive. I wanted a quick answer for the page already in front of me. That led to a deliberately short interaction:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Open or browse to a webpage.&lt;/li&gt;
&lt;li&gt;Continue using it normally.&lt;/li&gt;
&lt;li&gt;Click the extension icon.&lt;/li&gt;
&lt;li&gt;See the elapsed time for that website in that tab.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This can be useful during a study session, while researching a topic, or simply when a short break has quietly become a long one.&lt;/p&gt;

&lt;p&gt;The distinction between a &lt;strong&gt;website&lt;/strong&gt; and a &lt;strong&gt;page&lt;/strong&gt; matters here. The implementation uses &lt;code&gt;window.location.origin&lt;/code&gt; as the identity of a site. Moving between paths on the same origin can therefore remain part of the same session, while moving to another origin starts a new one.&lt;/p&gt;

&lt;h2&gt;
  
  
  The extension at a glance
&lt;/h2&gt;

&lt;p&gt;The repository has only a few functional files:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;manifest.json
background.js
content.js
popup.html
popup.js
images/
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Their responsibilities are straightforward:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;manifest.json&lt;/code&gt; declares the extension, permissions, service worker, popup, icons, and content script.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;content.js&lt;/code&gt; initializes per-tab website timing state in &lt;code&gt;sessionStorage&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;background.js&lt;/code&gt; responds to tab lifecycle events and injects a timestamp update.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;popup.html&lt;/code&gt; defines the compact interface.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;popup.js&lt;/code&gt; queries the active tab, obtains the elapsed time and favicon, and refreshes the display.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Conceptually, the active path looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Page loads
    ↓
content.js records origin + start time in sessionStorage
    ↓
User opens the extension popup
    ↓
popup.js finds the active tab
    ↓
chrome.scripting.executeScript runs a small reader in that page
    ↓
Elapsed seconds + favicon URL return to the popup
    ↓
The popup formats and refreshes the timer every second
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;There is no application server in this architecture. All of the work happens locally in Chrome.&lt;/p&gt;

&lt;h2&gt;
  
  
  Manifest V3 as the wiring diagram
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;manifest.json&lt;/code&gt; is the entry point Chrome uses to understand the extension. Tab Timekeeper declares Manifest V3 and registers &lt;code&gt;background.js&lt;/code&gt; as its service worker:&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="nl"&gt;"background"&lt;/span&gt;&lt;span class="p"&gt;:&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;span class="nl"&gt;"service_worker"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"background.js"&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;It also declares the toolbar popup:&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="nl"&gt;"action"&lt;/span&gt;&lt;span class="p"&gt;:&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;span class="nl"&gt;"default_popup"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"popup.html"&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;The content script runs on all normal web URLs:&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="nl"&gt;"content_scripts"&lt;/span&gt;&lt;span class="p"&gt;:&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;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"matches"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"&amp;lt;all_urls&amp;gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"js"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"content.js"&lt;/span&gt;&lt;span class="p"&gt;]&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;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;The extension requests &lt;code&gt;activeTab&lt;/code&gt; and &lt;code&gt;scripting&lt;/code&gt;, plus host access for HTTP and HTTPS pages. Those permissions match the central operation of the app: identify the current tab and execute a small function inside it.&lt;/p&gt;

&lt;p&gt;An earlier version declared the Chrome &lt;code&gt;storage&lt;/code&gt; permission, but it was removed. The current implementation does not use &lt;code&gt;chrome.storage&lt;/code&gt;; it uses the webpage's own Web Storage instead. Removing an unused permission keeps the manifest closer to the actual design and reduces what the extension asks Chrome and the user to trust.&lt;/p&gt;

&lt;h2&gt;
  
  
  Timing a website with &lt;code&gt;sessionStorage&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;The content script begins by reading two values from the current page's &lt;code&gt;sessionStorage&lt;/code&gt;:&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;baseUrl&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;location&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;origin&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;storedBaseUrl&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;sessionStorage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getItem&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;baseUrl&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;storedBaseUrl&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="nx"&gt;baseUrl&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;sessionStorage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setItem&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;baseUrl&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;baseUrl&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;sessionStorage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setItem&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;startTime&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;now&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is the core of the timer.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;sessionStorage&lt;/code&gt; is a good fit for the current behavior because its state is associated with a browsing context rather than a permanent extension-wide history. Each tab can maintain its own timing session. The extension stores a timestamp rather than incrementing a counter in the background, so calculating elapsed time is simply:&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="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;floor&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nb"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;now&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;startTime&lt;/span&gt;&lt;span class="p"&gt;)&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That choice avoids needing a long-running one-second timer merely to preserve state. The popup can calculate the current value whenever it needs to render it.&lt;/p&gt;

&lt;p&gt;Using the origin as the boundary also gives the feature a clear meaning: the timer represents time on a website in a tab, not necessarily time on one exact URL.&lt;/p&gt;

&lt;p&gt;There is a tradeoff. Web Storage belongs to the page's origin, not to the extension. That keeps the solution lightweight, but it also means this is not a centralized browsing-history model. If I wanted reports across days, aggregate totals, or durable extension-owned data, &lt;code&gt;chrome.storage&lt;/code&gt; or another extension-owned persistence layer would be a more appropriate next step.&lt;/p&gt;

&lt;h2&gt;
  
  
  The popup is the live view
&lt;/h2&gt;

&lt;p&gt;The popup is a small HTML document with two visible pieces: a website icon and a timer.&lt;/p&gt;

&lt;p&gt;When its DOM is ready, &lt;code&gt;popup.js&lt;/code&gt; asks Chrome for the active tab in the current window:&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;chrome&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;tabs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;query&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;active&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;currentWindow&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;tabs&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="c1"&gt;// Read timing data from the active page.&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 normal webpage, it then uses &lt;code&gt;chrome.scripting.executeScript&lt;/code&gt; to run &lt;code&gt;getElapsedTimeAndFavicon&lt;/code&gt; in the tab. That function reads &lt;code&gt;startTime&lt;/code&gt; from the page's &lt;code&gt;sessionStorage&lt;/code&gt;, calculates the elapsed seconds, finds the page's favicon link, and returns both values.&lt;/p&gt;

&lt;p&gt;The result crosses back through the callback returned by &lt;code&gt;executeScript&lt;/code&gt;:&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;timerElement&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;textContent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;formatTime&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;results&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="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;elapsedTime&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nx"&gt;websiteIconElement&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;results&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="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;faviconUrl&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is the extension's communication mechanism. It does &lt;strong&gt;not&lt;/strong&gt; use &lt;code&gt;chrome.runtime.sendMessage&lt;/code&gt; or a long-lived port. For this small request-response flow, the result of script execution is enough.&lt;/p&gt;

&lt;p&gt;The popup repeats the query every second while it is open:&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="nf"&gt;setInterval&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;updateTimer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nf"&gt;updateTimer&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Calling &lt;code&gt;updateTimer()&lt;/code&gt; immediately is a small but important detail. Without it, the popup would initially show its placeholder and wait up to a second before displaying the real value.&lt;/p&gt;

&lt;p&gt;The timer formatter converts raw seconds into a compact &lt;code&gt;Xm YYs&lt;/code&gt; display. The popup also avoids trying to inject into &lt;code&gt;chrome://&lt;/code&gt; pages, where ordinary extensions cannot run scripts, and falls back to &lt;code&gt;0m 00s&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the service worker does
&lt;/h2&gt;

&lt;p&gt;Manifest V3 background logic runs as a service worker rather than as a permanently open background page.&lt;/p&gt;

&lt;p&gt;Tab Timekeeper listens for two events:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;chrome.tabs.onActivated&lt;/code&gt;, when the active tab changes.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;chrome.tabs.onUpdated&lt;/code&gt;, when a tab finishes loading.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Both call &lt;code&gt;updateStartTime&lt;/code&gt;, which injects a function into the selected tab and writes the current time to &lt;code&gt;window.localStorage&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;It also checks &lt;code&gt;chrome.runtime.lastError&lt;/code&gt;, which matters because injection can fail on restricted pages or other contexts where Chrome does not permit the operation.&lt;/p&gt;

&lt;p&gt;There is an implementation detail here that is easy to miss: the service worker writes &lt;code&gt;localStorage.startTime&lt;/code&gt;, while the content script and popup use &lt;code&gt;sessionStorage.startTime&lt;/code&gt;. These are separate stores. As the code currently stands, the visible timer is driven by the content-script/session-storage path; the local-storage timestamp written by the service worker is not read by the popup.&lt;/p&gt;

&lt;p&gt;That does not need to be hidden in an architecture explanation. Small projects often preserve traces of an earlier design as the behavior evolves. In a future cleanup, I would either remove the unused background timestamp path or make one component the clear owner of timing state. A single source of truth would make tab activation, page loading, and same-site navigation semantics easier to define and test.&lt;/p&gt;

&lt;h2&gt;
  
  
  Storage scope was the main architectural choice
&lt;/h2&gt;

&lt;p&gt;The most interesting decision in this extension is not the timer formatting. It is where the timestamp lives.&lt;/p&gt;

&lt;p&gt;There are several possible models:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A popup-local counter would disappear each time the popup closed.&lt;/li&gt;
&lt;li&gt;An in-memory service-worker counter would be unreliable because Manifest V3 workers can stop when idle.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;chrome.storage&lt;/code&gt; would provide extension-owned persistence and make aggregation easier.&lt;/li&gt;
&lt;li&gt;Page &lt;code&gt;localStorage&lt;/code&gt; would persist by origin beyond a single tab session.&lt;/li&gt;
&lt;li&gt;Page &lt;code&gt;sessionStorage&lt;/code&gt; naturally supports a tab-oriented session.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The current implementation uses the last option for the value users see. It is a compact solution to the stated feature, and it avoids pretending that the extension is a full historical tracker.&lt;/p&gt;

&lt;p&gt;The tradeoff is that exact behavior follows browser storage and navigation rules. That is fine for a focused utility, but it is also why the README's broader claim about displaying total time across all tabs is not something I would make for the current code. The implementation reports the active website's time in the current tab; it does not maintain a cross-tab aggregate.&lt;/p&gt;

&lt;h2&gt;
  
  
  A small UI with one job
&lt;/h2&gt;

&lt;p&gt;The popup is only 200 pixels wide. It uses inline CSS, a centered 24-pixel timer, and a 16-pixel favicon.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Flh3.googleusercontent.com%2FXHctOqb0SQdVgVbiwwjk1WZwRYxAdsVXwQ7PMgXMHL2_ZCC9F3XxWB4aLnAm_ui7IjfqP1h0WMkj_ifrnS5pLLMNrA%3Ds1280-w1280-h800" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Flh3.googleusercontent.com%2FXHctOqb0SQdVgVbiwwjk1WZwRYxAdsVXwQ7PMgXMHL2_ZCC9F3XxWB4aLnAm_ui7IjfqP1h0WMkj_ifrnS5pLLMNrA%3Ds1280-w1280-h800" alt="A close-up of the Tab Timekeeper popup showing the Wikipedia favicon and an elapsed time of 12 seconds" width="640" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;The popup combines the active website's favicon with a timer that updates once per second.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;That limited interface reflects the product decision: opening the extension should answer the question immediately. There are no dashboards, settings, graphs, or controls competing with the result.&lt;/p&gt;

&lt;p&gt;Adding the favicon was a useful improvement over showing only text. It provides a quick visual association with the site without requiring the popup to parse and format a hostname.&lt;/p&gt;

&lt;p&gt;There is still room to make this path more defensive. Some pages do not declare a &lt;code&gt;link[rel~="icon"]&lt;/code&gt;, so favicon lookup should ideally handle a missing element and use a fallback. The popup could also surface a clearer unavailable state for protected browser pages instead of displaying a zero that looks like a measurement.&lt;/p&gt;

&lt;h2&gt;
  
  
  Decisions and tradeoffs that shaped the project
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Plain JavaScript instead of a framework
&lt;/h3&gt;

&lt;p&gt;For five small source files, a UI framework and build pipeline would add more structure than value. Plain JavaScript makes the runtime behavior visible and keeps the extension easy to load unpacked during development.&lt;/p&gt;

&lt;h3&gt;
  
  
  Calculate from a timestamp instead of counting ticks
&lt;/h3&gt;

&lt;p&gt;The extension stores a start time and derives elapsed seconds. This avoids timer drift caused by assuming that a callback will run at an exact interval, and it fits Manifest V3's event-oriented execution model.&lt;/p&gt;

&lt;h3&gt;
  
  
  Inject a reader instead of maintaining a message bus
&lt;/h3&gt;

&lt;p&gt;The popup needs two values from one active page. Returning them from &lt;code&gt;executeScript&lt;/code&gt; keeps communication direct. A runtime message system would become more valuable if content scripts pushed events, multiple extension views consumed the same state, or the service worker coordinated a larger model.&lt;/p&gt;

&lt;h3&gt;
  
  
  Keep permissions aligned with behavior
&lt;/h3&gt;

&lt;p&gt;The removal of the unused &lt;code&gt;storage&lt;/code&gt; permission is a good example of permission discipline. The current extension still needs broad host access because its feature is intended to work across ordinary HTTP and HTTPS sites, but every declared capability should have a specific reason to exist.&lt;/p&gt;

&lt;h3&gt;
  
  
  Accept the limits of a focused tool
&lt;/h3&gt;

&lt;p&gt;Tab Timekeeper is not a surveillance-style browser analytics product. It does not collect data, require an account, or send browsing activity to a backend. The architecture supports that claim: timing is performed locally in the browser.&lt;/p&gt;

&lt;h2&gt;
  
  
  From a tiny utility to a Featured extension
&lt;/h2&gt;

&lt;p&gt;The Chrome Web Store listing currently shows Tab Timekeeper as version 1.1 and marks it &lt;strong&gt;Featured&lt;/strong&gt;. It also describes the extension as collecting no user data.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I learned
&lt;/h2&gt;

&lt;p&gt;A few lessons from Tab Timekeeper have stayed with me.&lt;/p&gt;

&lt;p&gt;First, &lt;strong&gt;storage scope defines product behavior&lt;/strong&gt;. Choosing between page storage and extension storage is not just an implementation detail; it determines whether data belongs to a tab, an origin, the browser profile, or a longer-lived history.&lt;/p&gt;

&lt;p&gt;Second, &lt;strong&gt;extension code runs in multiple contexts&lt;/strong&gt;. The service worker, popup, content script, and webpage do not share one global JavaScript environment. Data has to cross those boundaries intentionally, whether through injection results, messaging, or shared extension storage.&lt;/p&gt;

&lt;p&gt;Third, &lt;strong&gt;Manifest V3 rewards event-driven design&lt;/strong&gt;. A timestamp that can be evaluated on demand is a better foundation than assuming a background process will remain alive and increment a counter forever.&lt;/p&gt;

&lt;p&gt;Fourth, &lt;strong&gt;permissions are part of the product&lt;/strong&gt;. They affect user trust and review, not only whether an API call succeeds.&lt;/p&gt;

&lt;p&gt;Finally, &lt;strong&gt;small scope makes inconsistencies easier to see&lt;/strong&gt;. The separate local-storage and session-storage paths work differently. Keeping documentation, manifest permissions, and runtime behavior synchronized is part of maintaining even a very small extension.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I would improve next
&lt;/h2&gt;

&lt;p&gt;The next version could remain lightweight while tightening a few areas:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Consolidate timing state so the content script, service worker, and popup share one explicit source of truth.&lt;/li&gt;
&lt;li&gt;Decide and document exact reset semantics for tab activation, reloads, and navigation within the same origin.&lt;/li&gt;
&lt;li&gt;Add safe handling for pages without a declared favicon.&lt;/li&gt;
&lt;li&gt;Display a clear “unavailable on this page” state for protected Chrome URLs.&lt;/li&gt;
&lt;li&gt;Add automated tests for elapsed-time formatting and the storage/reset rules.&lt;/li&gt;
&lt;li&gt;Update the README so every advertised feature matches the shipped implementation.&lt;/li&gt;
&lt;li&gt;Consider &lt;code&gt;chrome.storage&lt;/code&gt; only if durable history or cross-tab totals become a real product requirement.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The important part would be preserving the extension's current character: quick, local, and simple.&lt;/p&gt;

&lt;h2&gt;
  
  
  Closing thoughts
&lt;/h2&gt;

&lt;p&gt;Tab Timekeeper began with a narrow idea: make the time spent on the current website visible.&lt;/p&gt;

&lt;p&gt;Building it showed how much architecture can exist inside a tiny browser utility. The manifest defines trust and capabilities. The content script gives each page context. Web Storage determines the lifetime of the timing session. Script injection bridges the popup and active page. The service worker responds to browser events without needing to stay alive permanently.&lt;/p&gt;

&lt;p&gt;None of those pieces is large, but their boundaries matter.&lt;/p&gt;

&lt;p&gt;You can try &lt;a href="https://chromewebstore.google.com/detail/tab-timekeeper/fncodeopdbkmbfkljmjdaekmimnbpdeh" rel="noopener noreferrer"&gt;Tab Timekeeper on the Chrome Web Store&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;If you try it, I would love to hear whether seeing the timer changes the way you use a tab—or which improvement would make it more useful for you.&lt;/p&gt;

</description>
      <category>productivity</category>
      <category>webdev</category>
      <category>javascript</category>
      <category>chromeextension</category>
    </item>
    <item>
      <title>Building Mathastic Twice: One Math Puzzle Game, Two Native Architectures</title>
      <dc:creator>tapadyuti chatterjee</dc:creator>
      <pubDate>Sat, 15 Aug 2026 23:29:23 +0000</pubDate>
      <link>https://dev.to/tapadyutichatterjee/building-mathastic-twice-one-math-puzzle-game-two-native-architectures-1og6</link>
      <guid>https://dev.to/tapadyutichatterjee/building-mathastic-twice-one-math-puzzle-game-two-native-architectures-1og6</guid>
      <description>&lt;p&gt;I recently released &lt;strong&gt;Mathastic&lt;/strong&gt;, a fast-paced math puzzle game for iOS and Android.&lt;/p&gt;

&lt;p&gt;I’m Tapadyuti Chatterjee, a software engineer interested in distributed systems, mobile development, and practical applications of AI. You can learn more about my work on &lt;a href="https://tapadyuti.com/" rel="noopener noreferrer"&gt;my personal website&lt;/a&gt; or connect with me on &lt;a href="https://www.linkedin.com/in/tapadyutichatterjee/" rel="noopener noreferrer"&gt;LinkedIn&lt;/a&gt;.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://apps.apple.com/us/app/mathastic/id6740511685" rel="noopener noreferrer"&gt;Download Mathastic on the App Store&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://play.google.com/store/apps/details?id=com.tapadyuti.mathastic" rel="noopener noreferrer"&gt;Download Mathastic on Google Play&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is also my first post on DEV, so I wanted to go beyond a simple launch announcement. Instead, I want to share how the app works, how I structured the two native codebases, and what I learned while translating the same product into SwiftUI and Jetpack Compose.&lt;/p&gt;

&lt;h2&gt;
  
  
  TL;DR
&lt;/h2&gt;

&lt;p&gt;Mathastic is a native iOS and Android game that turns arithmetic practice into short, replayable runs. Players choose a game mode, difficulty, and operation mix, then build streaks, complete missions, earn XP, unlock themes, and track their performance over time.&lt;/p&gt;

&lt;p&gt;The iOS version uses SwiftUI, observable state, and &lt;code&gt;UserDefaults&lt;/code&gt;. The Android version uses Jetpack Compose, a &lt;code&gt;ViewModel&lt;/code&gt; with &lt;code&gt;StateFlow&lt;/code&gt;, Hilt for dependency injection, and &lt;code&gt;SharedPreferences&lt;/code&gt; with Gson.&lt;/p&gt;

&lt;p&gt;Both apps share the same product rules and domain concepts, but each follows the conventions of its platform instead of forcing an identical implementation.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Transparency note: I used AI to help format and polish the wording of this article. The app, architecture, implementation decisions, and experiences described here are my own.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  A quick look at Mathastic
&lt;/h2&gt;

&lt;p&gt;These screens show the main game flow—from choosing a mode to playing a timed run, tracking local progress, and returning for the deterministic Daily Challenge.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Choose your run&lt;/th&gt;
&lt;th&gt;Play under pressure&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F7i9r50hnqnvmkpp17d6x.jpg" alt="Mathastic mode selection screen" width="600" height="1299"&gt;&lt;/td&gt;
&lt;td&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F8vb0gump210dycoykqz5.jpg" alt="Mathastic Timed Sprint gameplay" width="600" height="1299"&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Mode selection and run configuration&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Timed Sprint gameplay&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Track your progress&lt;/th&gt;
&lt;th&gt;Return for the daily challenge&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fif3svhct9e6ppbt11pgl.jpg" alt="Mathastic local leaderboard and player progress" width="600" height="1299"&gt;&lt;/td&gt;
&lt;td&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fwuh5i6i35dbbrzs2v3dm.jpg" alt="Mathastic deterministic Daily Challenge" width="600" height="1299"&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Local progress and leaderboard&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Deterministic Daily Challenge&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  The idea behind Mathastic
&lt;/h2&gt;

&lt;p&gt;The original idea was simple: arithmetic practice should feel less like a worksheet and more like a game you want to replay.&lt;/p&gt;

&lt;p&gt;A basic math quiz can ask a question, accept an answer, and display a score. That works, but it does not create much momentum. For Mathastic, I wanted each session to have a small emotional arc:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Start with approachable questions.&lt;/li&gt;
&lt;li&gt;Build a streak.&lt;/li&gt;
&lt;li&gt;Feel the pressure increase.&lt;/li&gt;
&lt;li&gt;Decide when to use a hint, skip, or time bonus.&lt;/li&gt;
&lt;li&gt;Finish with a useful summary of the run.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;That led to several game modes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Timed Sprint&lt;/strong&gt; is the classic race against the clock.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Practice&lt;/strong&gt; removes the timer and penalties so the player can focus on repetition.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Endless&lt;/strong&gt; increases the pressure as the run progresses.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Daily Challenge&lt;/strong&gt; generates a consistent challenge from the current day.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Players can focus on addition, subtraction, multiplication, division, or a mixed set. Difficulty changes more than operand size: it also affects the timer, scoring, penalties, streak bonuses, and XP.&lt;/p&gt;

&lt;p&gt;The surrounding progression system—missions, achievements, levels, unlockable themes, score history, and operation-level accuracy—exists to give players a reason to return without getting in the way of the central activity.&lt;/p&gt;

&lt;h2&gt;
  
  
  Starting with a shared domain model
&lt;/h2&gt;

&lt;p&gt;Although the iOS and Android projects are separate native applications, I kept their domain language deliberately similar.&lt;/p&gt;

&lt;p&gt;Both versions have equivalents of:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;GameConfiguration&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;MathQuestion&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Score&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;GameResult&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;GameMission&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;MissionProgress&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;PlayerProfile&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;OperationStat&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;DailyChallenge&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Enumerations represent the major rule choices: difficulty, operation, game mode, theme, and mission type.&lt;/p&gt;

&lt;p&gt;This was one of the most useful architectural decisions in the project. When the product has a stable vocabulary, platform-specific code becomes easier to reason about. “Timed Sprint” should mean the same thing whether its state is stored in a Swift property wrapper or a Kotlin &lt;code&gt;StateFlow&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The UI implementations can differ. The game rules should not.&lt;/p&gt;

&lt;h2&gt;
  
  
  The high-level architecture
&lt;/h2&gt;

&lt;p&gt;Conceptually, Mathastic is divided into four layers:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;UI and navigation
        ↓
Game session state
        ↓
Question, mission, and challenge generation
        ↓
Local scores, progress, and preferences
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The UI renders the current state and sends player actions such as answering, requesting a hint, skipping a question, or ending a run.&lt;/p&gt;

&lt;p&gt;The game-state layer applies the rules: scoring, streaks, timing, progression, and mission updates.&lt;/p&gt;

&lt;p&gt;Small factory components generate questions, daily challenges, and missions.&lt;/p&gt;

&lt;p&gt;Finally, a local score store persists completed runs and derives higher-level information such as XP, levels, achievements, best scores, and operation accuracy.&lt;/p&gt;

&lt;p&gt;I chose a local-first design. Mathastic does not require an account or server round trip to begin a game. For this kind of app, immediate startup and offline play are more valuable than introducing a backend before it is necessary.&lt;/p&gt;

&lt;h2&gt;
  
  
  Question generation is more than choosing two numbers
&lt;/h2&gt;

&lt;p&gt;The question factory is one of the most important pieces of the app.&lt;/p&gt;

&lt;p&gt;It selects an operation, chooses operands based on difficulty, calculates the correct result, and creates three plausible wrong answers. Mixed mode resolves to a specific operation for every question.&lt;/p&gt;

&lt;p&gt;A few details improve the experience:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Division questions are built from a divisor and quotient, so answers remain whole numbers.&lt;/li&gt;
&lt;li&gt;Some addition questions hide an operand instead of always hiding the result.&lt;/li&gt;
&lt;li&gt;Wrong options are generated near the correct answer rather than being completely random.&lt;/li&gt;
&lt;li&gt;Endless mode can promote the effective difficulty as the run develops.&lt;/li&gt;
&lt;li&gt;Each question records its operation so the app can calculate per-operation accuracy later.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This logic is isolated from the visual layer. A screen should not need to know how to construct a valid division problem or produce convincing distractors. It only needs a &lt;code&gt;MathQuestion&lt;/code&gt; containing a prompt, a correct answer, and a set of options.&lt;/p&gt;

&lt;h2&gt;
  
  
  Deterministic daily challenges
&lt;/h2&gt;

&lt;p&gt;The Daily Challenge created an interesting requirement: randomness needed to be predictable.&lt;/p&gt;

&lt;p&gt;A normal run can generate a fresh sequence. A daily challenge should be tied to the day so that different sessions receive the same underlying challenge configuration.&lt;/p&gt;

&lt;p&gt;Both apps derive a numeric seed from the current date. That seed determines the day’s difficulty, operation mix, theme, and question sequence.&lt;/p&gt;

&lt;p&gt;This approach has several advantages:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;It does not require a backend to publish each challenge.&lt;/li&gt;
&lt;li&gt;The challenge remains stable during the day.&lt;/li&gt;
&lt;li&gt;Scores can be associated with the daily seed.&lt;/li&gt;
&lt;li&gt;The behavior is easy to reproduce while debugging.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It was a good reminder that “random” and “uncontrolled” are not the same thing. Seeded randomness preserves variety while still giving the system repeatable behavior.&lt;/p&gt;

&lt;h2&gt;
  
  
  The iOS implementation
&lt;/h2&gt;

&lt;p&gt;The iOS app is written with &lt;strong&gt;SwiftUI&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;A &lt;code&gt;NavigationStack&lt;/code&gt; begins at the welcome screen and moves into the active game configuration. Shared progress is held by a &lt;code&gt;ScoreStore&lt;/code&gt; created as a &lt;code&gt;StateObject&lt;/code&gt; at the app level and passed through the SwiftUI environment.&lt;/p&gt;

&lt;p&gt;The game screen uses SwiftUI state for the active session:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Current question&lt;/li&gt;
&lt;li&gt;Score and streak&lt;/li&gt;
&lt;li&gt;Remaining time&lt;/li&gt;
&lt;li&gt;Hint and skip counts&lt;/li&gt;
&lt;li&gt;Mission progress&lt;/li&gt;
&lt;li&gt;Operation statistics&lt;/li&gt;
&lt;li&gt;Tutorial state&lt;/li&gt;
&lt;li&gt;Final result&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Persistent player settings use &lt;code&gt;@AppStorage&lt;/code&gt;, while completed scores are encoded and stored through &lt;code&gt;UserDefaults&lt;/code&gt;. The score store publishes a derived snapshot containing the player profile, achievements, and operation insights.&lt;/p&gt;

&lt;p&gt;This creates a straightforward flow: changing game state causes SwiftUI to redraw the relevant parts of the interface, while saving a completed run rebuilds the player’s longer-term progress.&lt;/p&gt;

&lt;p&gt;For reminders, iOS uses &lt;code&gt;UNUserNotificationCenter&lt;/code&gt; with a repeating calendar trigger. The app asks for notification permission only when the player chooses to enable the reminder, which was important to me. A reminder should be an opt-in convenience, not an automatic interruption.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Android implementation
&lt;/h2&gt;

&lt;p&gt;The Android app uses &lt;strong&gt;Kotlin&lt;/strong&gt;, &lt;strong&gt;Jetpack Compose&lt;/strong&gt;, &lt;strong&gt;Material 3&lt;/strong&gt;, and &lt;strong&gt;Navigation Compose&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;The main architectural difference is that the active game logic lives in a &lt;code&gt;GameViewModel&lt;/code&gt;. The view model exposes an immutable &lt;code&gt;StateFlow&amp;lt;GameUiState&amp;gt;&lt;/code&gt;, and Compose collects that state to render the game.&lt;/p&gt;

&lt;p&gt;Player actions call methods on the view model:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Player action
    → ViewModel updates GameUiState
    → StateFlow emits a new value
    → Compose recomposes the UI
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The timer is implemented as a coroutine job inside the view model, which makes cancellation and lifecycle handling more explicit than keeping timer behavior inside a composable.&lt;/p&gt;

&lt;p&gt;Android also uses Hilt for dependency injection. The question factory, mission factory, daily challenge factory, and score store are provided to the components that need them. &lt;code&gt;SharedPreferences&lt;/code&gt; and Gson provide lightweight local persistence for scores and settings.&lt;/p&gt;

&lt;p&gt;Daily reminders require more platform plumbing on Android. The implementation uses:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;AlarmManager&lt;/code&gt; to schedule the repeating event&lt;/li&gt;
&lt;li&gt;A &lt;code&gt;BroadcastReceiver&lt;/code&gt; to receive it&lt;/li&gt;
&lt;li&gt;A notification channel on Android 8 and newer&lt;/li&gt;
&lt;li&gt;A &lt;code&gt;PendingIntent&lt;/code&gt; to reopen the app&lt;/li&gt;
&lt;li&gt;Runtime notification permission handling on newer Android versions&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The end result looks similar to the user, but the route to that result is distinctly Android.&lt;/p&gt;

&lt;h2&gt;
  
  
  SwiftUI and Compose: similar ideas, different centers of gravity
&lt;/h2&gt;

&lt;p&gt;SwiftUI and Jetpack Compose feel philosophically related. Both encourage declarative interfaces where the UI is a function of state.&lt;/p&gt;

&lt;p&gt;The differences become clearer once the app grows beyond a few screens.&lt;/p&gt;

&lt;p&gt;On iOS, SwiftUI property wrappers make it natural to keep a moderate amount of session state close to the view. Shared progress fits neatly into an observable environment object.&lt;/p&gt;

&lt;p&gt;On Android, the &lt;code&gt;ViewModel&lt;/code&gt; and &lt;code&gt;StateFlow&lt;/code&gt; combination creates a stronger separation between rendering and game logic. Compose primarily observes state and forwards events.&lt;/p&gt;

&lt;p&gt;Neither structure is automatically better. The important question is whether state has a clear owner.&lt;/p&gt;

&lt;p&gt;If I continued expanding the iOS version, I would likely move more of the active game-session logic into a dedicated observable model. The Android version already has that boundary because its view model owns the session.&lt;/p&gt;

&lt;p&gt;This is one of the advantages of building the same idea twice: each platform reveals architectural improvements that can inform the other.&lt;/p&gt;

&lt;h2&gt;
  
  
  Native parity does not mean identical code
&lt;/h2&gt;

&lt;p&gt;My goal was feature parity, not line-by-line parity.&lt;/p&gt;

&lt;p&gt;The two apps share concepts and behavior, but the implementations use native platform tools:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Concern&lt;/th&gt;
&lt;th&gt;iOS&lt;/th&gt;
&lt;th&gt;Android&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;UI&lt;/td&gt;
&lt;td&gt;SwiftUI&lt;/td&gt;
&lt;td&gt;Jetpack Compose&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Reactive state&lt;/td&gt;
&lt;td&gt;SwiftUI state and &lt;code&gt;ObservableObject&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;ViewModel&lt;/code&gt; and &lt;code&gt;StateFlow&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Navigation&lt;/td&gt;
&lt;td&gt;&lt;code&gt;NavigationStack&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Navigation Compose&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Preferences&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;@AppStorage&lt;/code&gt; and &lt;code&gt;UserDefaults&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;&lt;code&gt;SharedPreferences&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Score serialization&lt;/td&gt;
&lt;td&gt;&lt;code&gt;Codable&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Gson&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Dependency management&lt;/td&gt;
&lt;td&gt;App-level environment object and direct factories&lt;/td&gt;
&lt;td&gt;Hilt&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Timers&lt;/td&gt;
&lt;td&gt;Foundation &lt;code&gt;Timer&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Coroutines&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Reminders&lt;/td&gt;
&lt;td&gt;User Notifications framework&lt;/td&gt;
&lt;td&gt;AlarmManager, receiver, and notification channel&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Visual language&lt;/td&gt;
&lt;td&gt;SF Symbols and SwiftUI styling&lt;/td&gt;
&lt;td&gt;Material icons and Material 3&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Trying to hide all these differences behind a rigid cross-platform shape would have made both implementations less natural.&lt;/p&gt;

&lt;p&gt;Instead, I treated the domain model and game behavior as the contract. Everything around that contract was allowed to follow platform conventions.&lt;/p&gt;

&lt;h2&gt;
  
  
  Technical decisions that paid off
&lt;/h2&gt;

&lt;p&gt;A few decisions had an outsized effect on maintainability.&lt;/p&gt;

&lt;h3&gt;
  
  
  Keep generation logic outside the UI
&lt;/h3&gt;

&lt;p&gt;Question, mission, and daily challenge generation live in dedicated factories. This keeps the screens focused on presentation and interaction.&lt;/p&gt;

&lt;h3&gt;
  
  
  Model difficulty as behavior
&lt;/h3&gt;

&lt;p&gt;Difficulty is not just a label. Each difficulty owns values such as time limit, operand range, correct-answer points, wrong-answer penalty, streak bonus, and XP multiplier.&lt;/p&gt;

&lt;p&gt;That makes balancing changes easier and avoids scattering conditionals throughout the app.&lt;/p&gt;

&lt;h3&gt;
  
  
  Save raw run data, derive progression
&lt;/h3&gt;

&lt;p&gt;Each completed score stores useful facts about the run: accuracy inputs, streak, XP, completed missions, game mode, difficulty, theme, daily seed, and operation statistics.&lt;/p&gt;

&lt;p&gt;The score store then derives the profile, achievements, unlocked themes, and analytics. This is more flexible than persisting every calculated label separately.&lt;/p&gt;

&lt;h3&gt;
  
  
  Keep persistence lightweight
&lt;/h3&gt;

&lt;p&gt;The data currently fits comfortably in local preferences as encoded JSON. Adding a database would create migration and query infrastructure without yet providing enough value.&lt;/p&gt;

&lt;p&gt;That choice may change if the app gains cloud sync, social leaderboards, or a much larger history. Architecture should reflect current needs while leaving room for the next likely step.&lt;/p&gt;

&lt;h3&gt;
  
  
  Build fairness into generated questions
&lt;/h3&gt;

&lt;p&gt;Clean division answers, plausible distractors, controlled operand ranges, and reproducible daily challenges are small implementation details with a large effect on player trust.&lt;/p&gt;

&lt;p&gt;A math game can be visually polished and still feel wrong if its question generator is careless.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I learned
&lt;/h2&gt;

&lt;p&gt;The hardest part of building the app twice was not translating Swift into Kotlin. It was preserving the same experience across two different ecosystems.&lt;/p&gt;

&lt;p&gt;A few lessons stood out.&lt;/p&gt;

&lt;p&gt;First, &lt;strong&gt;write down the game rules as data&lt;/strong&gt;. When scoring and difficulty values are centralized, the two versions are much easier to compare.&lt;/p&gt;

&lt;p&gt;Second, &lt;strong&gt;state ownership matters more than framework syntax&lt;/strong&gt;. Declarative UI is pleasant only when it is clear which component controls the timer, score, current question, and final result.&lt;/p&gt;

&lt;p&gt;Third, &lt;strong&gt;small platform features can require very different implementations&lt;/strong&gt;. A “daily reminder” is one checkbox in the interface, but underneath it involves different permissions, scheduling systems, lifecycle rules, and APIs.&lt;/p&gt;

&lt;p&gt;Fourth, &lt;strong&gt;offline-first was the right constraint for this version&lt;/strong&gt;. Avoiding accounts and network dependencies kept the core loop fast and let me spend more time on the actual game.&lt;/p&gt;

&lt;p&gt;Finally, &lt;strong&gt;parity needs a checklist&lt;/strong&gt;. It is easy to add a scoring adjustment, mission, or tutorial improvement on one platform and forget the other. Shared terminology helps, but explicit feature and rule comparisons are even better.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I would improve next
&lt;/h2&gt;

&lt;p&gt;There are several natural directions for Mathastic:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Expand automated tests around question generation, seeded challenges, scoring, and progression&lt;/li&gt;
&lt;li&gt;Move more iOS session logic into a dedicated game-state object&lt;/li&gt;
&lt;li&gt;Add stronger persistence migrations as the score model evolves&lt;/li&gt;
&lt;li&gt;Improve accessibility and adaptive layouts across more device sizes&lt;/li&gt;
&lt;li&gt;Explore optional cloud sync or shared leaderboards&lt;/li&gt;
&lt;li&gt;Add deeper analytics that turn weak operations into targeted practice&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The key word is &lt;em&gt;optional&lt;/em&gt;. I still want the app to open quickly and let someone solve a math problem without creating an account or waiting for a server.&lt;/p&gt;

&lt;h2&gt;
  
  
  Closing thoughts
&lt;/h2&gt;

&lt;p&gt;Mathastic began as a small math puzzle idea, but building it natively for two platforms turned it into a useful architecture exercise.&lt;/p&gt;

&lt;p&gt;The project taught me that a shared product does not require a shared UI framework. With a clear domain model, deterministic rules, and well-defined state ownership, two native implementations can feel like the same app while still respecting their platforms.&lt;/p&gt;

&lt;p&gt;If you try Mathastic, I would love to hear which mode you play and where the experience could improve:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://apps.apple.com/us/app/mathastic/id6740511685" rel="noopener noreferrer"&gt;Mathastic for iPhone and iPad&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://play.google.com/store/apps/details?id=com.tapadyuti.mathastic" rel="noopener noreferrer"&gt;Mathastic for Android&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Thanks for reading my first DEV post! You can find more of my projects and writing at &lt;a href="https://tapadyuti.com/" rel="noopener noreferrer"&gt;tapadyuti.com&lt;/a&gt; or connect with me on &lt;a href="https://www.linkedin.com/in/tapadyutichatterjee/" rel="noopener noreferrer"&gt;LinkedIn&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>mobile</category>
      <category>ios</category>
      <category>android</category>
      <category>gamedev</category>
    </item>
  </channel>
</rss>
