chrono_persian/
lib.rs

1//! # chrono-persian
2//! ## About
3//! this crate contains a set of helper functions to convert chrono datetimes to persian (jalali) calender
4//! it provides a simple Trait `ToPersian` which is implemented for `NaiveDateTime`, `DateTime<Utc>` and `DateTime<Local`
5//! ##
6//!
7//! ## Example
8//! ```
9//!use chrono::{DateTime, Utc, Local, NaiveDateTime};
10//!use chrono_persian::ToPersian;
11//!
12//!// convert a datetime utc
13//!let utc = "2024-11-09 22:38:28 UTC".parse::<DateTime<Utc>>().unwrap();
14//!let a = utc.to_persian().unwrap();
15//!assert_eq!(a.to_string(), "1403-08-20 02:08:28 UTC");
16//!
17//!//convert a datetime local
18//!let local = "2024-11-10 02:17:54 +03:30".parse::<DateTime<Local>>().unwrap();
19//!let b = local.to_persian().unwrap();
20//!assert_eq!(b.to_string(), "1403-08-20 02:17:54 +00:00");
21//!   
22//!//convert a naivedatetime
23//!let now = NaiveDateTime::parse_from_str("2024-11-26 08:55:11","%Y-%m-%d %H:%M:%S").unwrap();
24//!let a = now.to_persian().unwrap();
25//!assert_eq!(a.to_string(),"1403-09-06 12:25:11");
26//!
27//! ```
28
29use chrono::{DateTime, Datelike, FixedOffset, Local, NaiveDate, NaiveDateTime, TimeZone, Utc};
30use std::ops::Deref;
31use std::sync::LazyLock;
32
33static LOCAL: LazyLock<Local> = LazyLock::new(|| Local::from_offset(&FIXED_OFFSET));
34
35/// Iran's offset, already tested, so its safe to unwrap
36static FIXED_OFFSET: LazyLock<FixedOffset> =
37    LazyLock::new(|| unsafe { FixedOffset::east_opt(3 * 3600 + 1800).unwrap_unchecked() });
38
39static ZERO_OFFSET: LazyLock<FixedOffset> =
40    LazyLock::new(|| unsafe { FixedOffset::east_opt(0).unwrap_unchecked() });
41
42/// Convert a chrono type to the persian equivalent
43pub trait ToPersian {
44    fn to_persian(&self) -> Option<Self>
45    where
46        Self: Sized;
47}
48
49impl ToPersian for DateTime<Utc> {
50    /// Convert a `DateTime<Utc>` to the persian equivalent
51    /// ```rust
52    ///use chrono::{DateTime, Utc};
53    ///use chrono_persian::ToPersian;
54    ///
55    ///let utc = "2024-11-09 22:38:28 UTC".parse::<DateTime<Utc>>().unwrap();
56    ///let a = utc.to_persian().unwrap();
57    ///assert_eq!(a.to_string(), "1403-08-20 02:08:28 UTC");
58    /// ```
59    fn to_persian(&self) -> Option<Self> {
60        let now = self.with_timezone(LOCAL.deref());
61        let (y, m, d) = gregorian_to_jalali(now.year(), now.month(), now.day());
62        Some(NaiveDateTime::new(NaiveDate::from_ymd_opt(y, m, d)?, now.time()).and_utc())
63    }
64}
65
66impl ToPersian for DateTime<Local> {
67    /// Convert a `DateTime<Local>` to the persian equivalent
68    /// ```rust
69    ///use chrono::{DateTime, Local};
70    ///use chrono_persian::ToPersian;
71    ///
72    ///let local = "2024-11-10 02:17:54 +03:30".parse::<DateTime<Local>>().unwrap();
73    ///let b = local.to_persian().unwrap();
74    ///assert_eq!(b.to_string(), "1403-08-20 02:17:54 +00:00");
75    /// ```
76    fn to_persian(&self) -> Option<Self> {
77        let now = self.with_timezone(LOCAL.deref());
78        let (y, m, d) = gregorian_to_jalali(now.year(), now.month(), now.day());
79        let a = NaiveDateTime::new(NaiveDate::from_ymd_opt(y, m, d)?, now.time());
80        Some(DateTime::<Local>::from_naive_utc_and_offset(
81            a,
82            *ZERO_OFFSET,
83        ))
84    }
85}
86
87impl ToPersian for NaiveDateTime {
88    /// Convert a `NaiveDateTime` to the persian equivalent
89    /// ```rust
90    ///use chrono::NaiveDateTime;
91    ///use chrono_persian::ToPersian;
92    ///
93    ///let now = NaiveDateTime::parse_from_str("2024-11-26 08:55:11","%Y-%m-%d %H:%M:%S").unwrap();
94    ///let a = now.to_persian().unwrap();
95    ///assert_eq!(a.to_string(),"1403-09-06 12:25:11");
96    /// ```
97    fn to_persian(&self) -> Option<Self> {
98        let now = DateTime::<Local>::from_naive_utc_and_offset(*self, *FIXED_OFFSET);
99        let (y, m, d) = gregorian_to_jalali(now.year(), now.month(), now.day());
100        Some(NaiveDateTime::new(
101            NaiveDate::from_ymd_opt(y, m, d)?,
102            now.time(),
103        ))
104    }
105}
106
107/// source: https://jdf.scr.ir
108fn gregorian_to_jalali(gy: i32, gm: u32, gd: u32) -> (i32, u32, u32) {
109    const G_D_M: [i32; 12] = [0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334];
110    let gy2 = if gm > 2 { gy + 1 } else { gy };
111
112    let mut days = 355666 + (365 * gy) + ((gy2 + 3) / 4) - ((gy2 + 99) / 100)
113        + ((gy2 + 399) / 400)
114        + gd as i32
115        + G_D_M[(gm - 1) as usize];
116
117    let mut jy = -1595 + (33 * (days / 12053));
118    days %= 12053;
119    jy += 4 * (days / 1461);
120    days %= 1461;
121
122    if days > 365 {
123        jy += (days - 1) / 365;
124        days = (days - 1) % 365;
125    }
126
127    let jm = if days < 186 {
128        1 + (days / 31)
129    } else {
130        7 + ((days - 186) / 30)
131    };
132
133    let jd = if days < 186 {
134        1 + (days % 31)
135    } else {
136        1 + ((days - 186) % 30)
137    };
138
139    (jy, jm as u32, jd as u32)
140}