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}