parsidate/
lib.rs

1// ~/src/lib.rs
2//
3//  * Copyright (C) ParsiCore (parsidate) 2024-2025 <parsicore.dev@gmail.com>
4//  * Package : parsidate
5//  * License : Apache-2.0
6//  * Version : 1.7.1
7//  * URL     : https://github.com/parsicore/parsidate
8//  * Sign: parsidate-20250607-fea13e856dcd-459c6e73c83e49e10162ee28b26ac7cd
9//
10//! # ParsiDate: A Comprehensive Persian (Jalali) Calendar Library for Rust
11//!
12//! `parsidate` offers a powerful, ergonomic, and comprehensive suite of tools for working with the
13//! Persian (also known as Jalali or Shamsi) calendar in Rust. Built on top of `chrono`, it provides
14//! a familiar and robust API for date and time manipulations, conversions, and formatting.
15//!
16//! The library's core types are:
17//! - [`ParsiDate`]: Represents a date (year, month, day) in the Persian calendar.
18//! - [`ParsiDateTime`]: Represents a specific date and time, without timezone information.
19//! - [`ZonedParsiDateTime`]: A timezone-aware date and time (requires the `timezone` feature).
20//!
21//! ## Key Features
22//!
23//! *   **Seamless Gregorian Conversion**: Effortlessly convert between Persian and Gregorian (`chrono`) types.
24//! *   **Robust Validation**: Ensure date and time integrity with strict validation rules.
25//! *   **Flexible Formatting & Parsing**: Use `strftime`-like patterns for custom string representations.
26//! *   **Full-Featured Arithmetic**: Reliably perform calculations with days, months, years, and `chrono::Duration`.
27//! *   **Rich Date Information**: Access properties like weekday, ordinal day, and leap year status.
28//! *   **Season Calculation**: Determine the Persian season (`Bahar`, `Tabestan`, etc.) for any date.
29//! *   **Timezone Aware (Optional)**: Full support for timezones via the `timezone` feature.
30//! *   **Serialization (Optional)**: `serde` support for easy integration with data formats like JSON.
31//!
32//! ## Getting Started
33//!
34//! Add `parsidate` to your `Cargo.toml`:
35//!
36//! ```toml
37//! [dependencies]
38//! parsidate = "1.7.1"
39//! # For timezone and/or serde support, enable features:
40//! # parsidate = { version = "1.7.1", features = ["timezone", "serde"] }
41//! ```
42//!
43//! A quick example of creating a date, formatting it, and getting its season:
44//!
45//! ```rust
46//! use parsidate::{ParsiDate, ParsiDateTime, Season};
47//! use chrono::NaiveDate;
48//!
49//! fn main() -> Result<(), parsidate::DateError> {
50//!     // Create a ParsiDate for a summer day
51//!     let pd = ParsiDate::new(1403, 5, 2)?;
52//!
53//!     // Format it
54//!     println!("Formatted date: {}", pd.format("%A، %d %B %Y"));
55//!
56//!     // Get its season
57//!     assert_eq!(pd.season()?, Season::Tabestan);
58//!     println!("The season is: {}", pd.season()?.name_persian()); // "تابستان"
59//!
60//!     // Convert it to Gregorian
61//!     let gregorian_date = pd.to_gregorian()?;
62//!     assert_eq!(gregorian_date, NaiveDate::from_ymd_opt(2024, 7, 23).unwrap());
63//!     println!("Equivalent Gregorian date: {}", gregorian_date);
64//!
65//!     Ok(())
66//! }
67//! ```
68//!
69//! ## Examples
70//!
71//! A more detailed look at the library's capabilities.
72//!
73//! ```rust
74//! use chrono::{NaiveDate, NaiveDateTime, Duration};
75//! use parsidate::{ParsiDate, ParsiDateTime, DateError, Season};
76//!
77//! // --- ParsiDate (Date only) ---
78//! let pd = ParsiDate::new(1403, 5, 2).unwrap();
79//! assert_eq!(pd.format("%Y-%m-%d"), "1403-05-02");
80//!
81//! // Get today's date
82//! let today_date = ParsiDate::today().unwrap();
83//! println!("Today's Persian date: {}", today_date);
84//!
85//! // Get date properties
86//! assert_eq!(pd.weekday(), Ok("سه‌شنبه".to_string()));
87//! assert_eq!(pd.ordinal(), Ok(126));
88//! assert_eq!(pd.week_of_year(), Ok(19));
89//! assert!(!ParsiDate::is_persian_leap_year(1404));
90//!
91//! // --- Season Usage ---
92//! let autumn_date = ParsiDate::new(1403, 8, 15).unwrap();
93//! let season = autumn_date.season().unwrap();
94//! assert_eq!(season, Season::Paeez);
95//! assert_eq!(season.name_persian(), "پاییز");
96//! assert_eq!(season.name_english(), "Autumn");
97//!
98//! // --- ParsiDateTime (Date and Time) ---
99//! let pdt = ParsiDateTime::new(1403, 5, 2, 15, 30, 45).unwrap();
100//! assert_eq!(pdt.year(), 1403);
101//! assert_eq!(pdt.hour(), 15);
102//!
103//! // Convert Gregorian DateTime to Persian DateTime
104//! let g_dt = NaiveDate::from_ymd_opt(2024, 7, 23).unwrap().and_hms_opt(15, 30, 45).unwrap();
105//! let pdt_from_g = ParsiDateTime::from_gregorian(g_dt).unwrap();
106//! assert_eq!(pdt_from_g, pdt);
107//!
108//! // Convert Persian DateTime to Gregorian DateTime
109//! let g_dt_conv = pdt.to_gregorian().unwrap();
110//! assert_eq!(g_dt_conv, g_dt);
111//!
112//! // Formatting DateTime
113//! assert_eq!(pdt.format("%Y/%m/%d %H:%M:%S"), "1403/05/02 15:30:45");
114//! assert_eq!(pdt.format("%A %d %B ساعت %T"), "سه‌شنبه 02 مرداد ساعت 15:30:45");
115//! assert_eq!(pdt.to_string(), "1403/05/02 15:30:45"); // Default Display format
116//!
117//! // Parsing DateTime
118//! let parsed_dt = ParsiDateTime::parse("1403/05/02 15:30:45", "%Y/%m/%d %H:%M:%S").unwrap();
119//! assert_eq!(parsed_dt, pdt);
120//!
121//! // DateTime Arithmetic
122//! let next_hour = pdt.add_duration(Duration::hours(1)).unwrap();
123//! assert_eq!(next_hour, ParsiDateTime::new(1403, 5, 2, 16, 30, 45).unwrap());
124//!
125//! let next_day_dt = pdt.add_days(1).unwrap(); // Preserves time
126//! assert_eq!(next_day_dt, ParsiDateTime::new(1403, 5, 3, 15, 30, 45).unwrap());
127//!
128//! // Current DateTime
129//! let now_dt = ParsiDateTime::now().unwrap();
130//! println!("Current Persian DateTime: {}", now_dt);
131//!
132//! // Week of Year
133//! let week_of_year = pdt.week_of_year();
134//! assert_eq!(week_of_year, Ok(19));
135//!
136//! // Seasons
137//! let season = pdt.season();
138//! assert_eq!(season, Ok(Season::Tabestan)); // Assuming 1403/05/02 is in summer
139//!
140//! // Weekday Calculation
141//! let weekday = pd.weekday();
142//! assert_eq!(weekday, Ok("سه‌شنبه".to_string())); // Assuming 1403/05/02 is a Tuesday
143//!
144//! // --- ZonedParsiDateTime (requires 'timezone' feature) ---
145//! #[cfg(feature = "timezone")]
146//! {
147//!     use parsidate::ZonedParsiDateTime;
148//!     use chrono_tz::Asia::Tehran;
149//!     use chrono_tz::Europe::London;
150//!
151//!     // Get the current time in a specific timezone
152//!     let tehran_now = ZonedParsiDateTime::now(Tehran);
153//!     println!("The current time in Tehran is: {}", tehran_now);
154//!
155//!     // Create a specific zoned time
156//!     let dt = ZonedParsiDateTime::new(1403, 8, 15, 12, 0, 0, Tehran).unwrap();
157//!     assert_eq!(dt.hour(), 12);
158//!
159//!     // Convert to another timezone
160//!     let london_dt = dt.with_timezone(&London);
161//!     println!("{} in Tehran is {} in London.", dt, london_dt);
162//!     // In winter, Tehran is UTC+3:30, London is UTC+0.
163//!     assert_eq!(london_dt.hour(), 8);
164//!     assert_eq!(london_dt.minute(), 30);
165//! }
166//!
167//! // --- Serde Integration (requires `serde` feature) ---
168//! #[cfg(feature = "serde")]
169//! {
170//!     use serde_json;
171//!     // ParsiDate example
172//!     let pd_serde = ParsiDate::new(1403, 1, 1).unwrap();
173//!     let json_pd = serde_json::to_string(&pd_serde).unwrap();
174//!     println!("Serialized ParsiDate: {}", json_pd); // {"year":1403,"month":1,"day":1}
175//!     let deser_pd: ParsiDate = serde_json::from_str(&json_pd).unwrap();
176//!     assert_eq!(deser_pd, pd_serde);
177//!
178//!     // ParsiDateTime example
179//!     let pdt_serde = ParsiDateTime::new(1403, 5, 2, 10, 20, 30).unwrap();
180//!     // Expected structure includes the nested ParsiDate
181//!     let json_pdt = serde_json::to_string(&pdt_serde).unwrap();
182//!     println!("Serialized ParsiDateTime: {}", json_pdt); // {"date":{"year":1403,"month":5,"day":2},"hour":10,"minute":20,"second":30}
183//!     let deser_pdt: ParsiDateTime = serde_json::from_str(&json_pdt).unwrap();
184//!     assert_eq!(deser_pdt, pdt_serde);
185//!     assert!(deser_pdt.is_valid());
186//!
187//!     // Deserializing potentially invalid ParsiDateTime
188//!     let json_invalid_dt = r#"{"date":{"year":1404,"month":12,"day":30},"hour":25,"minute":0,"second":0}"#; // Invalid day AND hour
189//!     let deser_invalid_dt: ParsiDateTime = serde_json::from_str(json_invalid_dt).unwrap();
190//!     // Default serde derive populates fields, but is_valid() should fail
191//!     assert!(!deser_invalid_dt.is_valid());
192//! }
193//! ```
194//!
195//! ## Features
196//!
197//! This crate has two optional features:
198//!
199//! -   `serde`: Enables serialization and deserialization for `ParsiDate`, `ParsiDateTime`, and `Season`
200//!     via the `serde` crate. Add to `Cargo.toml` with `features = ["serde"]`.
201//! -   `timezone`: Enables the [`ZonedParsiDateTime`] struct for timezone-aware operations,
202//!     powered by the `chrono-tz` crate. Add to `Cargo.toml` with `features = ["timezone"]`.
203//!
204//! You can enable both with `features = ["serde", "timezone"]`.
205
206// --- Library Module Declarations ---
207// These `mod` statements declare the modules that form the library's internal structure.
208
209mod constants;
210mod date;
211mod datetime;
212mod error;
213mod season;
214
215// Conditionally compile and declare the `zoned` module only when the `timezone` feature is enabled.
216#[cfg(feature = "timezone")]
217mod zoned;
218
219// Conditionally compile the tests module, ensuring it's only included during `cargo test`.
220#[cfg(test)]
221mod tests;
222
223// --- Public API Re-exports ---
224// Re-export the core public types to make them accessible directly from the crate root
225// (e.g., `use parsidate::ParsiDate;` instead of `use parsidate::date::ParsiDate;`).
226
227pub use constants::{MAX_PARSI_DATE, MIN_PARSI_DATE};
228pub use date::ParsiDate;
229pub use datetime::ParsiDateTime;
230pub use error::{DateError, ParseErrorKind};
231pub use season::Season;
232
233// Conditionally re-export the `ZonedParsiDateTime` struct if the `timezone` feature is active.
234#[cfg(feature = "timezone")]
235pub use zoned::ZonedParsiDateTime;