1extern crate time;
22
23use std::cmp::Ordering;
24use std::ops::{Add, Sub};
25use std::fmt;
26
27#[derive(Copy, Clone, PartialEq, Eq, Debug, Hash)]
29#[cfg_attr(feature = "rustc-serialize", derive(RustcEncodable, RustcDecodable))]
30pub struct Tm {
31 pub tm_sec: i32,
33
34 pub tm_min: i32,
36
37 pub tm_hour: i32,
39
40 pub tm_mday: i32,
42
43 pub tm_mon: i32,
45
46 pub tm_year: i32,
48
49 pub tm_wday: i32,
51
52 pub tm_yday: i32,
54
55 pub tm_isdst: i32,
57
58 pub tm_utcoff: i32,
60
61 pub tm_nsec: i32,
63}
64
65impl fmt::Display for Tm {
66 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
67 write!(f, "{}", self.to_string("yyyy-MM-ddTHH:mm:ss.ns"))
68 }
69}
70
71impl Add<time::Duration> for Tm {
72 type Output = Tm;
73
74 fn add(self, other: time::Duration) -> Tm {
76 at_utc(self.to_timespec() + other)
77 }
78}
79
80impl Sub<time::Duration> for Tm {
81 type Output = Tm;
82
83 fn sub(self, other: time::Duration) -> Tm {
85 at_utc(self.to_timespec() - other)
86 }
87}
88
89impl Sub<Tm> for Tm {
90 type Output = time::Duration;
91
92 fn sub(self, other: Tm) -> time::Duration {
93 self.to_timespec() - other.to_timespec()
94 }
95}
96
97impl Sub<time::Tm> for Tm {
98 type Output = time::Duration;
99
100 fn sub(self, other: time::Tm) -> time::Duration {
101 self.to_timespec() - other.to_timespec()
102 }
103}
104
105impl PartialOrd for Tm {
106 fn partial_cmp(&self, other: &Tm) -> Option<Ordering> {
107 self.to_timespec().partial_cmp(&other.to_timespec())
108 }
109}
110
111impl Ord for Tm {
112 fn cmp(&self, other: &Tm) -> Ordering {
113 self.to_timespec().cmp(&other.to_timespec())
114 }
115}
116
117impl Tm {
118 pub fn to_gregorian(&self) -> time::Tm {
120 let year: i32;
121 let month: i32;
122 let day: i32;
123
124 let jdn = get_jdn(self.tm_year, self.tm_mon + 1, self.tm_mday);
125
126 if jdn > 2299160 {
127 let mut l = jdn + 68569;
128 let n = 4 * l / 146097;
129 l = l - (146097 * n + 3) / 4;
130 let i = 4000 * (l + 1) / 1461001;
131 l = l - 1461 * i / 4 + 31;
132 let j = 80 * l / 2447;
133 day = l - 2447 * j / 80;
134 l = j / 11;
135 month = j + 2 - 12 * l;
136 year = 100 * (n - 49) + i + l;
137 } else {
138 let mut j = jdn + 1402;
139 let k = (j - 1) / 1461;
140 let l = j - 1461 * k;
141 let n = (l - 1) / 365 - l / 1461;
142 let mut i = l - 365 * n + 30;
143 j = 80 * i / 2447;
144 day = i - 2447 * j / 80;
145 i = j / 11;
146 month = j + 2 - 12 * i;
147 year = 4 * k + n + i - 4716;
148 }
149
150 time::Tm {
151 tm_sec: self.tm_sec,
152 tm_min: self.tm_min,
153 tm_hour: self.tm_hour,
154 tm_mday: day,
155 tm_mon: month - 1,
156 tm_year: year - 1900,
157 tm_wday: get_gregorian_weekday(self.tm_wday),
158 tm_yday: get_gregorian_yday(year, month - 1, day),
159 tm_isdst: self.tm_isdst,
160 tm_utcoff: self.tm_utcoff,
161 tm_nsec: self.tm_nsec,
162 }
163 }
164
165 pub fn to_timespec(&self) -> time::Timespec {
167 self.to_gregorian().to_timespec()
168 }
169
170 pub fn is_leap(&self) -> bool {
172 is_persian_leap(self.tm_year)
173 }
174
175 pub fn to_local(&self) -> Tm {
177 match self.tm_utcoff {
178 0 => at(self.to_timespec()),
179 _ => *self
180 }
181 }
182
183 pub fn to_utc(&self) -> Tm {
185 match self.tm_utcoff {
186 0 => *self,
187 _ => at_utc(self.to_timespec())
188 }
189 }
190
191 pub fn to_string<'a>(&'a self, format: &'a str) -> String {
219 format
220 .replace("yyyy", &self.tm_year.to_string())
221 .replace("yyy", &self.tm_year.to_string())
222 .replace("yy", &self.tm_year.to_string()[2..])
223 .replace("y", &self.tm_year.to_string())
224 .replace("MMM", match self.tm_mon {
225 0 => "فروردین",
226 1 => "اردیبهشت",
227 2 => "خرداد",
228 3 => "تیر",
229 4 => "مرداد",
230 5 => "شهریور",
231 6 => "مهر",
232 7 => "آبان",
233 8 => "آذر",
234 9 => "دی",
235 10 => "بهمن",
236 11 => "اسفند",
237 _ => panic!("invalid month value of {}", self.tm_mon),
238 })
239 .replace("MM", &format!("{:02}", self.tm_mon + 1))
240 .replace("M", &format!("{}", self.tm_mon + 1))
241 .replace("DD", &format!("{}", self.tm_yday + 1))
242 .replace("D", &self.tm_yday.to_string())
243 .replace("dd", &format!("{:02}", self.tm_mday))
244 .replace("d", &self.tm_mday.to_string())
245 .replace("E", match self.tm_wday {
246 0 => "شنبه",
247 1 => "یکشنبه",
248 2 => "دوشنبه",
249 3 => "سهشنبه",
250 4 => "چهارشنبه",
251 5 => "پنجشنبه",
252 6 => "جمعه",
253 _ => panic!("invalid weekday value of {}", self.tm_wday),
254 })
255 .replace("e", match self.tm_wday {
256 0 => "ش",
257 1 => "ی",
258 2 => "د",
259 3 => "س",
260 4 => "چ",
261 5 => "پ",
262 6 => "ج",
263 _ => panic!("invalid weekday value of {}", self.tm_wday),
264 })
265 .replace("A", if self.tm_hour < 12 {
266 "قبل از ظهر"
267 } else {
268 "بعد از ظهر"
269 })
270 .replace("a", if self.tm_hour < 12 {
271 "ق.ظ"
272 } else {
273 "ب.ظ"
274 })
275 .replace("HH", &format!("{:02}", self.tm_hour))
276 .replace("H", &self.tm_hour.to_string())
277 .replace("kk", &format!("{:02}", self.tm_hour + 1))
278 .replace("k", &format!("{}", self.tm_hour + 1))
279 .replace("hh", &format!("{:02}", if self.tm_hour > 11 {
280 self.tm_hour - 12
281 } else {
282 self.tm_hour
283 } + 1))
284 .replace("h", &format!("{}", if self.tm_hour > 11 {
285 self.tm_hour - 12
286 } else {
287 self.tm_hour
288 } + 1))
289 .replace("KK", &format!("{:02}", if self.tm_hour > 11 {
290 self.tm_hour - 12
291 } else {
292 self.tm_hour
293 }))
294 .replace("K", &format!("{}", if self.tm_hour > 11 {
295 self.tm_hour - 12
296 } else {
297 self.tm_hour
298 }))
299 .replace("mm", &format!("{:02}", self.tm_min))
300 .replace("m", &self.tm_min.to_string())
301 .replace("ns", &self.tm_nsec.to_string())
302 .replace("ss", &format!("{:02}", self.tm_sec))
303 .replace("s", &self.tm_sec.to_string())
304 }
305}
306
307pub fn empty_tm() -> Tm {
309 Tm {
310 tm_sec: 0,
311 tm_min: 0,
312 tm_hour: 0,
313 tm_mday: 0,
314 tm_mon: 0,
315 tm_year: 0,
316 tm_wday: 0,
317 tm_yday: 0,
318 tm_isdst: 0,
319 tm_utcoff: 0,
320 tm_nsec: 0,
321 }
322}
323
324pub fn from_gregorian(gregorian_tm:time::Tm) -> Tm {
326 let mut year: i32;
327 let gy = gregorian_tm.tm_year + 1900;
328 let gm = gregorian_tm.tm_mon + 1;
329 let gd = gregorian_tm.tm_mday;
330
331 let jdn: i32 = if gy > 1582 || (gy == 1582 && gm > 10) || (gy == 1582 && gm == 10 && gd > 14) {
332 ((1461 * (gy + 4800 + ((gm - 14) / 12))) / 4) + ((367 * (gm - 2 - 12*((gm-14)/12))) / 12) - ((3 * ((gy + 4900 + ((gm - 14) / 12)) / 100)) / 4) + gd - 32075
333 } else {
334 367 * gy - ((7 * (gy + 5001 + ((gm - 9) / 7))) / 4) + ((275 * gm) / 9) + gd + 1729777
335 };
336
337 let dep = jdn - get_jdn(475, 1, 1);
338 let cyc = dep / 1029983;
339 let rem = dep % 1029983;
340 let ycyc = if rem == 1029982 {
341 2820
342 } else {
343 let a = rem / 366;
344 (2134 * a + 2816 * (rem % 366) + 2815) / 1028522 + a + 1
345 };
346
347 year = ycyc + 2820 * cyc + 474;
348 if year <= 0 {
349 year -= 1;
350 }
351
352 let dy: f64 = (jdn - get_jdn(year, 1, 1) + 1) as f64;
353 let month: i32 = if dy <= 186f64 {
354 let mod_dy: f64 = dy / 31f64;
355 mod_dy.ceil() as i32
356 } else {
357 let mod_dy: f64 = (dy - 6f64) / 30f64;
358 mod_dy.ceil() as i32
359 } - 1;
360 let day = jdn - get_jdn(year, month + 1, 1) + 1;
361
362 Tm {
363 tm_sec: gregorian_tm.tm_sec,
364 tm_min: gregorian_tm.tm_min,
365 tm_hour: gregorian_tm.tm_hour,
366 tm_mday: day,
367 tm_mon: month,
368 tm_year: year,
369 tm_wday: get_persian_weekday(gregorian_tm.tm_wday),
370 tm_yday: get_persian_yday(month, day),
371 tm_isdst: gregorian_tm.tm_isdst,
372 tm_utcoff: gregorian_tm.tm_utcoff,
373 tm_nsec: gregorian_tm.tm_nsec,
374 }
375}
376
377pub fn from_gregorian_date(g_year: i32, g_month: i32, g_day: i32) -> Option<Tm> {
379 from_gregorian_components(g_year, g_month, g_day, 0, 0, 0, 0)
380}
381
382pub fn from_persian_date(p_year: i32, p_month: i32, p_day: i32) -> Option<Tm> {
384 from_persian_components(p_year, p_month, p_day, 0, 0, 0, 0)
385}
386
387pub fn from_gregorian_components(g_year: i32, g_month: i32, g_day: i32, hour: i32, minute: i32, second: i32, nanosecond: i32) -> Option<Tm> {
389 if is_time_valid(hour, minute, second, nanosecond) && is_gregorian_date_valid(g_year, g_month, g_day) {
390 let tm = time::Tm{
391 tm_sec: second,
392 tm_min: minute,
393 tm_hour: hour,
394 tm_mday: g_day,
395 tm_mon: g_month,
396 tm_year: g_year - 1900,
397 tm_wday: 0,
398 tm_yday: 0,
399 tm_isdst: 0,
400 tm_utcoff: 0,
401 tm_nsec: nanosecond,
402 };
403 return Some(at_utc(tm.to_timespec()))
404 }
405 None
406}
407
408pub fn from_persian_components(p_year: i32, p_month: i32, p_day: i32, hour: i32, minute: i32, second: i32, nanosecond: i32) -> Option<Tm> {
411 if is_time_valid(hour, minute, second, nanosecond) && is_persian_date_valid(p_year, p_month, p_day) {
412 let mut tm = Tm{
413 tm_sec: second,
414 tm_min: minute,
415 tm_hour: hour,
416 tm_mday: p_day,
417 tm_mon: p_month,
418 tm_year: p_year,
419 tm_wday: 0,
420 tm_yday: get_persian_yday(p_month, p_day),
421 tm_isdst: 0,
422 tm_utcoff: 0,
423 tm_nsec: nanosecond,
424 };
425 tm.tm_wday = get_persian_weekday(time::at_utc(tm.to_timespec()).tm_wday);
426 return Some(tm)
427 }
428 None
429}
430
431pub fn at_utc(clock: time::Timespec) -> Tm {
433 from_gregorian(time::at_utc(clock))
434}
435
436pub fn at(clock: time::Timespec) -> Tm {
438 from_gregorian(time::at(clock))
439}
440
441pub fn now_utc() -> Tm {
443 from_gregorian(time::now_utc())
444}
445
446pub fn now() -> Tm {
448 from_gregorian(time::now())
449}
450
451fn divider(num: i32, den: i32) -> i32 {
452 if num > 0 {
453 num % den
454 } else {
455 num - ((((num + 1) / den) - 1) * den)
456 }
457}
458
459fn get_jdn(year: i32, month: i32, day: i32) -> i32 {
460 let base = if year >= 0 {
461 year - 474
462 } else {
463 year - 473
464 };
465
466 let epy = 474 + (base % 2820);
467
468 let md = if month <= 7 {
469 (month - 1) * 31
470 } else {
471 (month - 1) * 30 + 6
472 };
473
474 day + md + (epy * 682 - 110) / 2816 + (epy - 1) * 365 + base / 2820 * 1029983 + 1948320
475}
476
477fn get_persian_weekday(wd: i32) -> i32 {
478 match wd {
479 0 => 1,
480 1 => 2,
481 2 => 3,
482 3 => 4,
483 4 => 5,
484 5 => 6,
485 6 => 0,
486 _ => panic!("invalid weekday value of {}", wd),
487 }
488}
489
490fn get_gregorian_weekday(wd: i32) -> i32 {
491 match wd {
492 0 => 6,
493 1 => 0,
494 2 => 1,
495 3 => 2,
496 4 => 3,
497 5 => 4,
498 6 => 5,
499 _ => panic!("invalid weekday value of {}", wd),
500 }
501}
502
503fn get_persian_yday(month: i32, day: i32) -> i32 {
504 [
505 0, 31, 62, 93, 124, 155, 186, 216, 246, 276, 306, 336, ][month as usize] + day - 1
518}
519
520fn get_gregorian_yday(year: i32, month: i32, day: i32) -> i32 {
521 [
522 [0, 0],
523 [31, 31],
524 [59, 60],
525 [90, 91],
526 [120, 121],
527 [151, 152],
528 [181, 182],
529 [212, 213],
530 [243, 244],
531 [273, 274],
532 [304, 305],
533 [334, 335],
534 ][month as usize][is_gregorian_leap(year) as usize] + day - 1
535}
536
537fn is_persian_leap(year: i32) -> bool {
538 divider(25 * year + 11, 33) < 8
539}
540
541fn is_gregorian_leap(year: i32) -> bool {
542 year % 4 == 0 && (year % 100 != 0 || year % 400 == 0)
543}
544
545fn is_persian_date_valid(year: i32, month: i32, day: i32) -> bool {
546 if month < 0 || month > 11 {
547 return false
548 }
549
550 [
551 [31, 31],
552 [31, 31],
553 [31, 31],
554 [31, 31],
555 [31, 31],
556 [31, 31],
557 [30, 30],
558 [30, 30],
559 [30, 30],
560 [30, 30],
561 [30, 30],
562 [29, 30],
563 ][month as usize][is_persian_leap(year) as usize] >= day
564}
565
566fn is_gregorian_date_valid(year: i32, month: i32, day: i32) -> bool {
567 if month < 0 || month > 11 {
568 return false
569 }
570
571 [
572 [31, 31],
573 [28, 29],
574 [31, 31],
575 [30, 30],
576 [31, 31],
577 [30, 30],
578 [31, 31],
579 [31, 31],
580 [30, 30],
581 [31, 31],
582 [30, 30],
583 [31, 31],
584 ][month as usize][is_gregorian_leap(year) as usize] >= day
585}
586
587fn is_time_valid(hour: i32, minute: i32, second: i32, nanosecond: i32) -> bool {
588 !(hour < 0 || hour > 23 || minute < 0 || minute > 59 || second < 0 || second > 59 || nanosecond < 0 || nanosecond > 999999999)
589}