Compare commits

..

No commits in common. "main" and "v1.0.0" have entirely different histories.
main ... v1.0.0

5 changed files with 9 additions and 56 deletions

View file

@ -5,16 +5,6 @@ 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,12 +234,13 @@ int _fcnaDaysInMonth(int hy, int hm) {
// ---- FCNA Gregorian -> Hijri ---- // ---- FCNA Gregorian -> Hijri ----
HijriDate? _fcnaToHijri(DateTime date) { HijriDate? _fcnaToHijri(DateTime date) {
// Normalize to UTC calendar day so the result is deterministic regardless of // FCNA criterion is UTC-based, so UTC date components ensure correct round-trips.
// 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(d.year, d.month, d.day).millisecondsSinceEpoch.toDouble(); DateTime.utc(
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,11 +36,7 @@ int _dateUtcMs(int year, int month, int day) {
} }
HijriDate? _uaqToHijri(DateTime date) { HijriDate? _uaqToHijri(DateTime date) {
// Normalize to UTC calendar day so the result is deterministic regardless of final inputUtc = _dateUtcMs(date.year, date.month, date.day);
// 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,10 +2,11 @@ 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.1 version: 1.0.0
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,41 +93,6 @@ 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', () {