From Gregorian - jalalvandi/ParsiDate GitHub Wiki

Static Method from_gregorian

Converts a Gregorian calendar date (chrono::NaiveDate) into its equivalent Persian (Solar Hijri / Jalali) calendar date (ParsiDate).

Description

This static method takes a date represented in the standard Gregorian calendar system (using chrono::NaiveDate) and calculates the corresponding year, month, and day in the Persian calendar system.

The conversion algorithm typically works by:

  1. Calculating the number of days that have elapsed between a known reference point (usually the start of the Persian epoch, which corresponds to Gregorian March 21st, 622 CE) and the input gregorian_date.
  2. Mapping this total day count back into the structure of the Persian calendar (accounting for its specific month lengths and leap year rules) to determine the Persian year, month, and day.

This method ensures that the resulting ParsiDate accurately reflects the same moment in time as the input gregorian_date, just represented within a different calendar system.

Arguments

  • gregorian_date: A chrono::NaiveDate instance representing the Gregorian date that needs to be converted.

Returns

  • Ok(ParsiDate): If the conversion is successful and the resulting Persian date falls within the supported range (typically years 1-9999), returns the equivalent ParsiDate wrapped in Ok.
  • Err(DateError::GregorianConversionError): If the conversion fails for any of the reasons listed below.

Errors

The method returns Err(DateError::GregorianConversionError) under the following conditions:

  • Date Too Early: The input gregorian_date is chronologically earlier than the starting point of the Persian calendar epoch (approximately March 21st, 622 CE). Dates before this point cannot be represented in the standard Persian calendar.
  • Year Out of Range: The calculation results in a Persian year that is outside the range supported by the ParsiDate type (typically less than 1 or greater than 9999). This can happen with Gregorian dates very far in the past (before 622 CE) or very far in the future.
  • Internal Calculation Failure: An unexpected error occurs during the internal date computations. This could involve issues with the underlying chrono library (e.g., failure to represent an intermediate date) or potential arithmetic overflows during the day count calculation, though this is less common for dates within chrono::NaiveDate's standard range.

Examples (Rust)

use chrono::NaiveDate;
use parsidate::{ParsiDate, DateError}; // Assuming these types exist

// --- Standard Conversions ---

// Convert a typical Gregorian date in 2024
let g_date_1 = NaiveDate::from_ymd_opt(2024, 7, 23).unwrap();
let expected_p_date_1 = ParsiDate::new(1403, 5, 2).unwrap(); // Mordad 2nd, 1403
assert_eq!(ParsiDate::from_gregorian(g_date_1), Ok(expected_p_date_1));

// Convert a Gregorian date just before Nowruz
let g_date_2 = NaiveDate::from_ymd_opt(2023, 3, 20).unwrap();
// This corresponds to the last day of 1401 (a common year)
let expected_p_date_2 = ParsiDate::new(1401, 12, 29).unwrap();
assert_eq!(ParsiDate::from_gregorian(g_date_2), Ok(expected_p_date_2));

// Convert the Gregorian date for Nowruz (Persian New Year)
let g_date_nowruz = NaiveDate::from_ymd_opt(2023, 3, 21).unwrap();
let expected_p_date_nowruz = ParsiDate::new(1402, 1, 1).unwrap(); // Farvardin 1st, 1402
assert_eq!(ParsiDate::from_gregorian(g_date_nowruz), Ok(expected_p_date_nowruz));


// --- Epoch Conversion ---

// Convert the approximate Gregorian start date of the Persian epoch
// Note: Precise epoch definitions can vary slightly, use the one consistent with the library's implementation.
// Assuming March 21, 622 CE corresponds to 1/1/1 Persian.
let epoch_gregorian = NaiveDate::from_ymd_opt(622, 3, 21).unwrap();
let expected_epoch_persian = ParsiDate::new(1, 1, 1).unwrap();
assert_eq!(ParsiDate::from_gregorian(epoch_gregorian), Ok(expected_epoch_persian));


// --- Error Cases ---

// Error: Gregorian date before the Persian epoch start
let before_epoch = NaiveDate::from_ymd_opt(622, 3, 20).unwrap();
assert_eq!(ParsiDate::from_gregorian(before_epoch), Err(DateError::GregorianConversionError));

let much_before_epoch = NaiveDate::from_ymd_opt(1, 1, 1).unwrap();
assert_eq!(ParsiDate::from_gregorian(much_before_epoch), Err(DateError::GregorianConversionError));


// Error: Gregorian date potentially too far in the future
// The exact behavior depends on NaiveDate::MAX and the ParsiDate year limit (e.g., 9999)
let far_future_g = NaiveDate::MAX; // Chrono's maximum representable date

match ParsiDate::from_gregorian(far_future_g) {
    Ok(pd) => {
        // This would only happen if NaiveDate::MAX corresponds to a Persian year <= 9999
        println!("Conversion unexpectedly succeeded for NaiveDate::MAX: {}", pd);
        assert!(pd.year() <= 9999); // Check if assumption holds
    }
    Err(e) => {
        // This is the expected outcome if NaiveDate::MAX converts to a Persian year > 9999
        println!("Correctly failed for NaiveDate::MAX: {:?}", e);
        assert!(matches!(e, DateError::GregorianConversionError));
    }
}