Unit Testing Prayer Time Logic: Accuracy at Edge-Case Coordinates
A technical deep dive into how we test the Salah Companion engine against extreme latitudes and longitudinal drift to ensure reliable prayer timings.
Calculating prayer times is fundamentally a geometric challenge wrapped in an astronomical one. For a tool like Salah Companion, the core utility depends entirely on the accuracy of the underlying calculation engine. While standard trigonometric formulas for the sun's position work reliably at mid-latitudes, they frequently break down when applied to extreme geographical coordinates or regions with complex daylight saving rules. Relying solely on manual testing in a few major cities is a recipe for runtime crashes or, worse, silent logic errors that provide users with incorrect timings. To prevent this, we employ a rigorous unit testing framework that isolates the calculation logic from the Android framework, allowing us to simulate thousands of coordinate and date combinations in milliseconds.
The Problem with Geometric Edge Cases
Most prayer time algorithms rely on the solar declination and the equation of time. These variables change daily but follow a predictable path. However, as a user moves closer to the poles—specifically beyond 48 degrees latitude—the sun may not reach the required angle below the horizon for Fajr or Isha during certain times of the year. In these "higher latitude" scenarios, the mathematical functions (specifically the inverse cosine used to find the hour angle) receive an input outside the range of -1 to 1. Without robust handling, the software returns NaN (Not a Number), which can cascade through the UI, causing empty states or application crashes.
Unit testing allows us to verify how our engine handles these mathematical impossibilities. Do we fall back to the 'Nearest Latitude' method? Do we use 'Middle of the Night' or 'One Seventh' rules? By writing tests that specifically target coordinates like Reykjavik, Iceland, or Ushuaia, Argentina, we ensure that the logic remains sound even when the sun behaves atypically.
Decoupling Logic for Testability
To test this effectively, we had to move away from placing calculation logic inside Android ViewModels or Activity classes. In the development of Salah Companion, we isolated the calculation engine into a pure Kotlin library. This separation is a principle we apply across our studio's portfolio, including ShariahSignal, where financial ratios are calculated independently of the UI.
By keeping the engine "pure" (inputting coordinates and dates, outputting a data class of times), we can run JUnit tests without an emulator. This speeds up the development cycle and allows us to test hundreds of years of daily transitions in a single test run.
A Concrete Example: Testing for 'NaN' at High Latitudes
Consider a test case for a city at 60°N latitude during the Summer Solstice. At this point, the sun might never reach 18 degrees below the horizon. Below is a simplified representation of how we structure a test to ensure our 'Higher Latitude' adjustment logic is firing correctly.
| Input Parameter | Value |
|---|---|
| Latitude | 64.1265° N (Reykjavik) |
| Longitude | -21.8277° W |
| Date | June 21st |
| Calculation Method | ISNA (Fajr: 15°, Isha: 15°) |
| Expected Outcome | Valid Timings (No NaN values) using 'Nearest Latitude' fallback |
In our test suite, we don't just check if the app stays open. We assert that the resulting times fall within a logical range (e.g., Maghrib must occur after Dhuhr). If the engine returns a time that is physically impossible or a null value, the test fails immediately, flagging a regression in our coordinate handling.
Handling Longitudinal Drifts and Time Zones
Another common source of error is the discrepancy between a user’s physical longitude and their legal time zone. A user might be physically located in a spot where the sun peaks at 1:00 PM local time due to a wide time zone boundary or Daylight Saving Time (DST). We learned through building Testing Reminders Across Time Zones and Daylight Saving that these offsets are the primary cause of "timing drift."
We test for this by creating a matrix of "High-Offset" locations. For example, parts of Western China use Beijing Time (UTC+8) despite being geographically closer to UTC+5. We feed these specific coordinates into our unit tests to ensure that the UTC-to-Local conversion logic accounts for the offset correctly without shifting the prayer times into the wrong calendar day.
The Engineering Checklist for Prayer Logic Testing
When we update the calculation engine, we run it through a multi-point verification checklist. This ensures that a fix for one region doesn't break accuracy in another.
- 1.Null-Island Check: Verify that (0,0) coordinates do not result in a crash.
- 2.Pole Simulation: Test latitudes 90° and -90° to verify boundary condition handling.
- 3.DST Transition: Test dates where the local clock jumps forward or backward (e.g., 2:00 AM becomes 3:00 AM) to ensure the Fajr alarm doesn't go missing.
- 4.Anti-Meridian Test: Test locations at the 180th meridian (International Date Line) to ensure date-wrapping logic works.
- 5.Method Consistency: Ensure that switching from 'Muslim World League' to 'Umm al-Qura' updates all five prayer times correctly according to their respective angle definitions.
Managing Precision: Double vs. Float
In our engineering journey, we discovered that small precision errors in Float calculations could lead to a 1-2 minute discrepancy when compared to official observatory data. While a one-minute difference might seem negligible, it undermines user trust. We shifted our core engine to use Double precision for all trigonometric operations.
Our unit tests enforce this precision by comparing our engine's output against a "Golden Dataset"—a collection of thousands of pre-verified prayer times from trusted astronomical sources. We allow for a maximum variance of 60 seconds; anything higher triggers a manual review of the rounding logic.
Why This Matters for Small Studios
As a small studio, we don't have the resources for a massive QA department to manually check the app in every country. We rely on automation to be our safeguard. By investing in these unit tests, we ensure that Salah Companion remains reliable for a user in a remote village just as much as a user in a major city. This technical discipline allows us to focus on building new features, like the trackers and reminders, knowing that the foundation is mathematically sound. It follows our broader philosophy of Privacy-First Analytics: we don't need to track your location history to make the app work; we just need the math to be right the first time.
What to do next
- 1.Audit your math logic: If you are building a location-aware app, move your core calculations into a pure Kotlin/Java/Swift library that has zero dependencies on the OS framework.
- 2.Create an Edge-Case Matrix: Identify the physical boundaries of your service (e.g., extreme latitudes or time zone offsets) and write specific unit tests for those coordinates.
- 3.Automate your Golden Dataset: Build a small script to compare your app's output against a known-good source once a month to catch any drift caused by library updates or compiler changes.