Compare commits

..

4 commits
v1.0.0 ... main

Author SHA1 Message Date
Aric Camarata
a2f947b011 style: apply dart format 2026-06-13 11:57:56 -04:00
Aric Camarata
eb1890b4ef chore: release v1.0.1 2026-06-13 11:53:22 -04:00
Aric Camarata
c79368a4ee fix: interpret DateTime by UTC calendar day for exact round-trips
toHijri in both UAQ and FCNA engines read .year/.month/.day from the
input DateTime — local calendar components — then passed them to
DateTime.utc(). On hosts west of UTC a UTC-midnight local DateTime
resolves to the previous local day, causing toHijri(toGregorian(y,m,d))
to return the wrong Hijri day.

Fix: call date.toUtc() first in both engines before extracting calendar
components. This is symmetric with toGregorian which always returns
DateTime.utc(). Adds 3 regression tests covering the round-trip and the
local-DateTime-on-UTC-west-host case.
2026-06-13 10:28:16 -04:00
Aric Camarata
5dd4cecc12 fix: remove invalid publisher field from pubspec
The publisher field is not recognized by pub and caused dart pub publish
--dry-run to exit 65. Publisher is configured on pub.dev, not in pubspec.
2026-05-29 15:53:36 -04:00
5 changed files with 56 additions and 9 deletions

View file

@ -5,6 +5,16 @@ All notable changes to this project will be documented in this file.
The format follows [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). The format follows [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
This project adheres to [Semantic Versioning](https://semver.org/). This project adheres to [Semantic Versioning](https://semver.org/).
## 1.0.1
### Fixed
- `toHijri` now normalizes the input `DateTime` to its UTC calendar day before
lookup (via `date.toUtc()`), matching the UTC-midnight contract of `toGregorian`.
Previously, passing a local `DateTime` on a host west of UTC could return the
previous Hijri day, breaking `toHijri(toGregorian(y, m, d))` round-trips.
Applies to both the UAQ and FCNA engines.
## [1.0.0] - 2026-05-25 ## [1.0.0] - 2026-05-25
### Added ### Added

View file

@ -234,13 +234,12 @@ int _fcnaDaysInMonth(int hy, int hm) {
// ---- FCNA Gregorian -> Hijri ---- // ---- FCNA Gregorian -> Hijri ----
HijriDate? _fcnaToHijri(DateTime date) { HijriDate? _fcnaToHijri(DateTime date) {
// FCNA criterion is UTC-based, so UTC date components ensure correct round-trips. // Normalize to UTC calendar day so the result is deterministic regardless of
// the host timezone and whether the caller passes a UTC or local DateTime.
// This is symmetric with toGregorian, which always returns DateTime.utc().
final d = date.toUtc();
final inputMs = final inputMs =
DateTime.utc( DateTime.utc(d.year, d.month, d.day).millisecondsSinceEpoch.toDouble();
date.year,
date.month,
date.day,
).millisecondsSinceEpoch.toDouble();
final kApprox = _utcMsToKApprox(inputMs - 15 * msPerDay); final kApprox = _utcMsToKApprox(inputMs - 15 * msPerDay);
final k0 = kApprox.floor(); final k0 = kApprox.floor();

View file

@ -36,7 +36,11 @@ int _dateUtcMs(int year, int month, int day) {
} }
HijriDate? _uaqToHijri(DateTime date) { HijriDate? _uaqToHijri(DateTime date) {
final inputUtc = _dateUtcMs(date.year, date.month, date.day); // Normalize to UTC calendar day so the result is deterministic regardless of
// the host timezone and whether the caller passes a UTC or local DateTime.
// This is symmetric with toGregorian, which always returns DateTime.utc().
final d = date.toUtc();
final inputUtc = _dateUtcMs(d.year, d.month, d.day);
// Binary search: find the last table entry whose Gregorian start date <= input. // Binary search: find the last table entry whose Gregorian start date <= input.
int lo = 0; int lo = 0;

View file

@ -2,11 +2,10 @@ name: hijri_core
description: > description: >
Hijri/Gregorian calendar conversion for Dart and Flutter. Pluggable engine Hijri/Gregorian calendar conversion for Dart and Flutter. Pluggable engine
system with built-in Umm al-Qura and FCNA calendars. Zero dependencies. system with built-in Umm al-Qura and FCNA calendars. Zero dependencies.
version: 1.0.0 version: 1.0.1
homepage: https://github.com/acamarata/hijri-core-dart homepage: https://github.com/acamarata/hijri-core-dart
repository: https://github.com/acamarata/hijri-core-dart repository: https://github.com/acamarata/hijri-core-dart
issue_tracker: https://github.com/acamarata/hijri-core-dart/issues issue_tracker: https://github.com/acamarata/hijri-core-dart/issues
publisher: ariccamarata.com
topics: topics:
- hijri - hijri
- islamic - islamic

View file

@ -93,6 +93,41 @@ void main() {
}); });
}); });
// ---- UAQ round-trips (UTC day boundary) ----
group('UAQ round-trips', () {
test('toHijri(toGregorian(1446,9,1)) == 1446/9/1', () {
final greg = toGregorian(1446, 9, 1);
expect(greg, isNotNull);
final h = toHijri(greg!);
expect(h, isNotNull);
expect(h!.hy, equals(1446));
expect(h.hm, equals(9));
expect(h.hd, equals(1));
});
test('local DateTime with same UTC day round-trips correctly', () {
// toGregorian returns DateTime.utc(); converting it to local must still
// produce the correct Hijri day (regression for UTC-west hosts).
final greg = toGregorian(1446, 9, 1);
expect(greg, isNotNull);
final localGreg = greg!.toLocal();
final h = toHijri(localGreg);
expect(h, isNotNull);
expect(h!.hy, equals(1446));
expect(h.hm, equals(9));
expect(h.hd, equals(1));
});
test('toHijri(toGregorian(1318,1,1)) == 1318/1/1 (table lower bound)', () {
final greg = toGregorian(1318, 1, 1);
expect(greg, isNotNull);
final h = toHijri(greg!);
expect(h, isNotNull);
expect(h!.hy, equals(1318));
expect(h.hm, equals(1));
expect(h.hd, equals(1));
});
});
// ---- UAQ isValid ---- // ---- UAQ isValid ----
group('UAQ isValid', () { group('UAQ isValid', () {