ParsiDate: Comprehensive Persian Calendar Date & Time for Rust
parsidate provides comprehensive functionality for working with the Persian (Jalali/Shamsi) calendar system in Rust. It allows for seamless representation, conversion, validation, formatting, parsing, and arithmetic for both dates (ParsiDate) and date-times (ParsiDateTime). It leverages the chrono crate for Gregorian representations, current time, and duration calculations.
✨ Features
- Date and DateTime Handling: Represents both dates (
ParsiDate) and date-times (ParsiDateTimewith hour, minute, second). - Conversion: Easily convert between
chrono::NaiveDate/NaiveDateTime(Gregorian) andParsiDate/ParsiDateTime. - Validation: Check if combinations form valid Persian dates or date-times.
- Formatting: Display dates and times in various formats using custom
strftime-like patterns with Persian names and time components. - Parsing: Parse strings into
ParsiDateorParsiDateTimeobjects based on specified formats, including Persian month names and time components. - Arithmetic:
- Add or subtract days, months, and years to
ParsiDateandParsiDateTime(preserving time for the latter), correctly handling month lengths and leap years (including day clamping). - Add or subtract
chrono::Durationto/fromParsiDateTimefor precise time calculations.
- Add or subtract days, months, and years to
- Leap Year Calculation: Determine if a Persian year is leap (using a 33-year cycle approximation) or if a Gregorian year is leap.
- Date/Time Information: Get the Persian weekday name (شنبه-جمعه), weekday number (0-6), ordinal day of the year (1-366), Persian season (
Seasonenum), and access individual date/time components. - Helpers: Get the first/last day of the month/year/season, or create modified dates/datetimes easily (
with_year,with_month,with_day,with_hour,with_minute,with_second,with_time). - Current Date/Time: Get the current system date (
ParsiDate::today()) or date-time (ParsiDateTime::now()) as Persian objects. - Week of Year: Calculate the week number within the Persian year (Saturday start).
- Season: Display and work with Persian seasons (Spring, Summer, Autumn, Winter), get the Persian name of a season, determine the season for a given date, and access the start and end dates of each season.
- Serde Support: Optional serialization/deserialization for
ParsiDate,ParsiDateTime, andSeasonvia theserdefeature flag. - Range: Supports Persian years from 1 to 9999.
⚙️ Installation
Add parsidate to your Cargo.toml:
[]
= "1.5.0"
= "0.4"
If you need serialization/deserialization support, enable the serde feature:
[]
= { = "1.5.0", = ["serde"] }
= { = "0.4", = ["serde"] }
= { = "1.0", = ["derive"] } # Required for derive
🚀 Usage Examples
use ;
// Import Season along with other types
use ;
// --- ParsiDate Usage (Date only) ---
// Create a ParsiDate (validates on creation)
let pd = new.unwrap; // 2 Mordad 1403
assert_eq!;
assert_eq!; // 5 = Mordad
assert_eq!;
// Check validity
assert!;
let invalid_date_res = new; // 1404 is not leap
assert_eq!;
// Gregorian to Persian Date
let g_date = from_ymd_opt.unwrap;
let pd_from_g = from_gregorian.unwrap;
assert_eq!;
// Persian Date to Gregorian
let g_date_conv = pd.to_gregorian.unwrap;
assert_eq!;
// Formatting Date
assert_eq!;
assert_eq!;
assert_eq!; // Default Display
// Parsing Date
let parsed_short = parse.unwrap;
assert_eq!;
let parsed_long = parse.unwrap;
assert_eq!;
// Date Arithmetic
let next_day_date = pd.add_days.unwrap;
assert_eq!;
let prev_month_date = pd.sub_months.unwrap;
assert_eq!; // Tir 2nd
// Get Today's Date
match today
// --- ParsiDateTime Usage (Date and Time) ---
// Create a ParsiDateTime (validates date and time)
let pdt = new.unwrap;
assert_eq!;
assert_eq!;
assert_eq!;
assert_eq!;
assert_eq!; // Access the ParsiDate part
// Invalid time creation
let invalid_time_res = new;
assert_eq!;
// Gregorian DateTime to Persian DateTime
let g_dt = from_ymd_opt.unwrap.and_hms_opt.unwrap;
let pdt_from_g = from_gregorian.unwrap;
assert_eq!;
// Persian DateTime to Gregorian DateTime
let g_dt_conv = pdt.to_gregorian.unwrap;
assert_eq!;
// Formatting DateTime
assert_eq!;
assert_eq!;
assert_eq!; // Default Display
// Parsing DateTime
let parsed_dt = parse.unwrap;
assert_eq!;
let parsed_dt_t = parse.unwrap;
assert_eq!;
let parsed_dt_b = parse.unwrap;
assert_eq!;
// DateTime Arithmetic with Duration
let next_hour = pdt.add_duration.unwrap;
assert_eq!;
let prev_minute_rollover = pdt.sub_duration.unwrap;
assert_eq!;
// Using operators
assert_eq!;
// DateTime Arithmetic with days/months/years (preserves time)
let next_day_dt = pdt.add_days.unwrap;
assert_eq!;
let next_month_dt = pdt.add_months.unwrap;
assert_eq!;
// Modifying time components
let pdt_morning = pdt.with_hour.unwrap;
assert_eq!;
let pdt_start_of_minute = pdt.with_second.unwrap;
assert_eq!;
// Get Current DateTime
match now
// --- Season Support Examples ---
let winter_date = new.unwrap; // Bahman 10th (Winter)
let season = winter_date.season.unwrap;
assert_eq!;
assert_eq!;
assert_eq!;
// Get season boundaries
let start_winter = winter_date.start_of_season.unwrap;
let end_winter = winter_date.end_of_season.unwrap; // 1403 is leap
assert_eq!; // Dey 1st
assert_eq!; // Esfand 30th
// Season support works with ParsiDateTime too
let dt_spring = new.unwrap; // Ordibehesht 20th (Spring)
assert_eq!;
assert_eq!;
let spring_end_dt = dt_spring.end_of_season.unwrap;
assert_eq!; // Khordad 31st
assert_eq!; // Time preserved
Serialization/Deserialization Support (serde feature)
// --- Serde (Requires 'serde' feature) ---
Formatting and Parsing Specifiers
Formatting (ParsiDate::format, ParsiDateTime::format)
| Specifier | Description | Example (1403-05-02, 15:30:45) |
Notes |
|---|---|---|---|
%Y |
Year with century | 1403 |
|
%m |
Month as zero-padded number | 05 |
|
%d |
Day of month as zero-padded number | 02 |
|
%B |
Full Persian month name | مرداد |
|
%A |
Full Persian weekday name | سهشنبه |
|
%w |
Weekday as number (Saturday=0) | 3 |
|
%j |
Day of year as zero-padded number | 126 |
|
%K |
Full Persian season name | تابستان |
|
%H |
Hour (24-hour clock), zero-padded | 15 |
DateTime only |
%M |
Minute, zero-padded | 30 |
DateTime only |
%S |
Second, zero-padded | 45 |
DateTime only |
%T |
Equivalent to %H:%M:%S |
15:30:45 |
DateTime only |
%W |
Week number of the year | 19 |
|
%% |
A literal % character |
% |
Parsing (ParsiDate::parse, ParsiDateTime::parse)
| Specifier | Description | Notes |
|---|---|---|
%Y |
Parses a 4-digit year | Requires exactly 4 digits. |
%m |
Parses a 2-digit month (01-12) | Requires exactly 2 digits. |
%d |
Parses a 2-digit day (01-31) | Requires exactly 2 digits. |
%B |
Parses a full Persian month name | Case-sensitive, matches names like "فروردین", "مرداد". |
%H |
Parses a 2-digit hour (00-23) | Requires exactly 2 digits. DateTime only. |
%M |
Parses a 2-digit minute (00-59) | Requires exactly 2 digits. DateTime only. |
%S |
Parses a 2-digit second (00-59) | Requires exactly 2 digits. DateTime only. |
%T |
Parses time in HH:MM:SS format |
Requires correct separators and 2 digits per component. DateTime only. |
%% |
Matches a literal % character |
Note: Parsing requires the input string to exactly match the format string, including separators and the number of digits specified (e.g., %d requires 02, not 2). %A, %w, %j, %K are not supported for parsing. The final parsed date/time is validated logically (e.g., day exists in month, time components are in range).
⚠️ Error Handling
Most methods that can fail return a Result<T, DateError>. The DateError enum indicates the type of error:
InvalidDate: Date components do not form a valid Persian date.InvalidTime: Time components (hour, minute, second) are out of range (e.g., hour 24). Specific toParsiDateTime.GregorianConversionError: Error during Gregorian <=> Persian conversion (e.g., date out of supported range).ParseError(ParseErrorKind): Input string failed to parse.ParseErrorKindgives specific details like:FormatMismatch: Input doesn't match format structure/literals.InvalidNumber: Failed to parse numeric component or wrong digit count.InvalidMonthName: Failed to parse%B.UnsupportedSpecifier: Used a format specifier not supported for parsing.InvalidDateValue: Parsed date components are logically invalid.InvalidTimeValue: Parsed time components are logically invalid.
ArithmeticOverflow: Date/time arithmetic resulted in a value outside the supported range (Year 1-9999) or internal overflow (e.g., adding largeDuration).InvalidOrdinal: Invalid day-of-year number provided (e.g., 0 or > 366).
Contributing
Contributions (bug reports, feature requests, pull requests) are welcome! Please open an issue to discuss significant changes first.
📄 License
Licensed under the Apache License, Version 2.0.
Version:1.6.0
Sign: parsidate-20250415-a7a78013d25e-f7c1ad27b18ba6d800f915500eda993f