1use {
30 alloc::borrow::Cow,
31 core::{
32 cmp::Ordering,
33 fmt::{self, Debug, Formatter},
34 time::Duration,
35 },
36 crate::{Error, Month, Result as CrateResult, Weekday, month},
37};
38
39#[cfg(feature="libc")]
40use core::{
41 ffi::CStr,
42 ptr,
43};
44
45#[cfg(all(windows, feature="libc"))]
46use crate::{DAY, HOUR, MINUTE};
47
48#[cfg(any(feature="std", all(feature="std", feature="libc")))]
49use std::time::{SystemTime, UNIX_EPOCH};
50
51pub type Year = i64;
53
54pub type Day = u8;
56
57pub type Hour = u8;
59
60pub type Minute = u8;
62
63pub type Second = u8;
65
66pub type UnixSecond = i64;
68
69pub type GmtOffset = i32;
71
72#[cfg(test)]
73mod tests;
74
75const MAX_GMT_OFFSET: GmtOffset = 14 * 3600;
76const MIN_GMT_OFFSET: GmtOffset = -12 * 3600;
77
78const ONE_YEAR: UnixSecond = (crate::DAY * 365) as UnixSecond;
79const ONE_LEAP_YEAR: UnixSecond = (crate::DAY * 366) as UnixSecond;
80
81const SECONDS_OF_400_YEARS: UnixSecond = 12_622_780_800;
82const SECONDS_OF_1970_YEARS: UnixSecond = 62_167_219_200;
83
84#[derive(Eq, Hash, PartialOrd, Clone)]
96pub struct Time {
97 year: Year,
98 month: Month,
99 day: Day,
100 weekday: Weekday,
101 hour: Hour,
102 minute: Minute,
103 second: Second,
104 gmt_offset: Option<GmtOffset>,
105}
106
107impl Time {
108
109 pub fn make(year: Year, month: Month, day: Day, hour: Hour, minute: Minute, second: Second, gmt_offset: Option<GmtOffset>)
111 -> CrateResult<Self> {
112 if day == 0 || match month {
114 Month::January | Month::March | Month::May | Month::July | Month::August | Month::October | Month::December => day > 31,
115 Month::April | Month::June | Month::September | Month::November => day > 30,
116 Month::February => day > if is_leap_year(year) {
117 month::END_OF_FEBRUARY_IN_LEAP_YEARS
118 } else {
119 month::END_OF_FEBRUARY_IN_COMMON_YEARS
120 },
121 } {
122 return Err(err!("Invalid day: {day} of {month}", day=day, month=month));
123 }
124
125 if hour > 23 {
126 return Err(err!("Invalid hour: {hour}", hour=hour));
127 }
128 if minute > 59 {
129 return Err(err!("Invalid minute: {minute}", minute=minute));
130 }
131 if second > 59 {
132 return Err(err!("Invalid second: {second}", second=second));
133 }
134
135 if let Some(gmt_offset) = gmt_offset.as_ref() {
136 if gmt_offset < &MIN_GMT_OFFSET || gmt_offset > &MAX_GMT_OFFSET {
137 return Err(err!("Invalid GMT offset: {gmt_offset}", gmt_offset=gmt_offset));
138 }
139 }
140
141 let mut result = Self {
142 year, month, day,
143 weekday: Weekday::Monday,
144 hour, minute, second,
145 gmt_offset,
146 };
147 result.weekday = {
148 let tmp = result.try_into_unix_seconds()?
149 .checked_add(gmt_offset.unwrap_or(0).into()).ok_or_else(|| err!())?
150 .checked_abs().ok_or_else(|| err!())?;
151 Weekday::try_from_unix((tmp / i64::try_from(crate::DAY).map_err(|_| err!())? + 4) % 7)?
152 };
153 Ok(result)
154 }
155
156 pub const fn year(&self) -> Year {
158 self.year
159 }
160
161 pub const fn month(&self) -> Month {
163 self.month
164 }
165
166 pub const fn day(&self) -> Day {
168 self.day
169 }
170
171 pub const fn weekday(&self) -> Weekday {
173 self.weekday
174 }
175
176 pub const fn hour(&self) -> Hour {
178 self.hour
179 }
180
181 pub const fn minute(&self) -> Minute {
183 self.minute
184 }
185
186 pub const fn second(&self) -> Second {
188 self.second
189 }
190
191 pub const fn gmt_offset(&self) -> Option<GmtOffset> {
193 self.gmt_offset
194 }
195
196 #[cfg(feature="std")]
198 #[doc(cfg(feature="std"))]
199 pub fn make_utc() -> CrateResult<Self> {
200 let (unix_seconds, is_positive) = match {
201 SystemTime::now().duration_since(UNIX_EPOCH).map(|d| d.as_secs()).map_err(|e| e.duration().as_secs())
202 } {
203 Ok(unix_seconds) => (unix_seconds, true),
204 Err(unix_seconds) => (unix_seconds, false),
205 };
206 let unix_seconds = UnixSecond::try_from(unix_seconds).map_err(|_| err!("Failed to convert {} into UnixSecond", unix_seconds))?;
207 try_unix_seconds_into_time(if is_positive { unix_seconds } else { -unix_seconds }, None)
208 }
209
210 pub fn try_into_utc(&self) -> CrateResult<Self> {
212 match self.gmt_offset {
213 None => Ok(self.clone()),
214 Some(0) => Ok(Self {
215 gmt_offset: None,
216 ..*self
217 }),
218 Some(gmt_offset) => {
219 let today_seconds = (crate::HOUR * u64::from(self.hour) + crate::MINUTE * u64::from(self.minute) + u64::from(self.second))
220 as GmtOffset;
221 let seconds = today_seconds - gmt_offset;
222 let (year, month, day, hour, minute, second) = if seconds < 0 {
223 let (year, month, day) = match self.day {
224 1 => {
225 let year = match self.month {
226 Month::January => self.year.checked_sub(1)
227 .ok_or_else(|| err!("Failed to subtract '{year}' by 1", year=self.year))?,
228 _ => self.year,
229 };
230 let month = self.month.wrapping_last();
231 let day = month::last_day_of_month(&month, is_leap_year(year));
232 (year, month, day)
233 },
234 _ => (self.year, self.month, self.day - 1),
235 };
236 let (_, hour, minute, second) = crate::duration_to_dhms(&Duration::from_secs(crate::DAY - seconds.abs() as u64));
237 (year, month, day, hour, minute, second)
238 } else if seconds < crate::DAY as GmtOffset {
239 let (_, hour, minute, second) = crate::duration_to_dhms(&Duration::from_secs(seconds as u64));
240 (self.year, self.month, self.day, hour, minute, second)
241 } else {
242 let (year, month, day) = if self.day == month::last_day_of_month(&self.month, is_leap_year(self.year)) {
243 let year = match self.month {
244 Month::December => self.year.checked_add(1)
245 .ok_or_else(|| err!("Failed to add 1 to '{year}'", year=self.year))?,
246 _ => self.year,
247 };
248 (year, self.month.wrapping_next(), 1)
249 } else {
250 (self.year, self.month, self.day + 1)
251 };
252 let (_, hour, minute, second) = crate::duration_to_dhms(&Duration::from_secs(seconds as u64 - crate::DAY));
253 (year, month, day, hour, minute, second)
254 };
255 Self::make(year, month, day, hour as Hour, minute as Minute, second as Second, None)
256 },
257 }
258 }
259
260 pub fn is_utc(&self) -> bool {
262 matches!(&self.gmt_offset, Some(0) | None)
263 }
264
265 #[cfg(all(feature="std", feature="libc"))]
267 #[doc(cfg(all(feature="std", feature="libc")))]
268 pub fn make_local() -> CrateResult<Self> {
269 let gmt_offset = load_gmt_offset()?;
270
271 let (unix_seconds, is_positive) = match {
272 SystemTime::now().duration_since(UNIX_EPOCH).map(|d| d.as_secs()).map_err(|e| e.duration().as_secs())
273 } {
274 Ok(unix_seconds) => (unix_seconds, true),
275 Err(unix_seconds) => (unix_seconds, false),
276 };
277 let unix_seconds = UnixSecond::try_from(unix_seconds).map_err(|_| err!("Failed to convert {} into UnixSecond", unix_seconds))?;
278 try_unix_seconds_into_time(if is_positive { unix_seconds } else { -unix_seconds }, gmt_offset)
279 }
280
281 #[cfg(feature="libc")]
283 #[doc(cfg(feature="libc"))]
284 pub fn try_into_local(&self) -> CrateResult<Self> {
285 try_unix_seconds_into_time(self.try_into_unix_seconds()?, load_gmt_offset()?)
286 }
287
288 pub fn try_into_unix_seconds(&self) -> CrateResult<UnixSecond> {
290 if self.year >= 0 {
291 self.try_positive_time_into_unix_seconds()
292 } else {
293 self.try_negative_time_into_unix_seconds()
294 }
295 }
296
297 fn try_positive_time_into_unix_seconds(&self) -> CrateResult<UnixSecond> {
299 if self.year < 0 {
300 return Err(err!("Year is negative"));
301 }
302
303 let mut unix_seconds = match (self.year / 400).checked_mul(SECONDS_OF_400_YEARS).map(|s|
304 s.checked_sub(SECONDS_OF_1970_YEARS).map(|mut s| {
305 for y in 0..self.year % 400 {
306 s = match s.checked_add(if is_leap_year(y) { ONE_LEAP_YEAR } else { ONE_YEAR }) {
307 Some(s) => s,
308 None => return None,
309 };
310 }
311 Some(s)
312 })
313 ) {
314 Some(Some(Some(unix_seconds))) => unix_seconds,
315 _ => return Err(err!("Year '{}' is too large", self.year))?,
316 };
317
318 let mut month = Month::January;
319 let is_leap_year = is_leap_year(self.year);
320 loop {
321 if month < self.month {
322 unix_seconds = unix_seconds.checked_add(month::seconds_of_month(&month, is_leap_year))
323 .ok_or_else(|| err!("Time is too large"))?;
324 month = month.wrapping_next();
325 } else {
326 break;
327 }
328 }
329
330 unix_seconds = match unix_seconds.checked_add((crate::DAY * u64::from(self.day - 1)) as UnixSecond).map(|s|
331 s.checked_add((crate::HOUR * u64::from(self.hour)) as UnixSecond).map(|s|
332 s.checked_add((crate::MINUTE * u64::from(self.minute)) as UnixSecond)
333 .map(|s| s.checked_add(self.second.into()))
334 )
335 ) {
336 Some(Some(Some(Some(unix_seconds)))) => unix_seconds,
337 _ => return Err(err!("Time is too large")),
338 };
339
340 if let Some(gmt_offset) = self.gmt_offset {
341 unix_seconds = unix_seconds.checked_sub(gmt_offset.into()).ok_or_else(|| err!("Time is too small"))?;
342 }
343
344 Ok(unix_seconds)
345 }
346
347 fn try_negative_time_into_unix_seconds(&self) -> CrateResult<UnixSecond> {
349 if self.year >= 0 {
350 return Err(err!("Year is positive"));
351 }
352
353 let mut unix_seconds = match (self.year / 400).checked_mul(SECONDS_OF_400_YEARS).map(|s|
354 s.checked_sub(SECONDS_OF_1970_YEARS).map(|mut s| {
355 let remaining = (self.year % 400).abs();
356 match remaining {
357 0 => s.checked_add(ONE_LEAP_YEAR),
358 _ => {
359 for y in 1..remaining {
360 s = match s.checked_sub(if is_leap_year(y) { ONE_LEAP_YEAR } else { ONE_YEAR }) {
361 Some(s) => s,
362 None => return None,
363 };
364 }
365 Some(s)
366 },
367 }
368 })
369 ) {
370 Some(Some(Some(unix_seconds))) => unix_seconds,
371 _ => return Err(err!("Year '{}' is too small", self.year))?,
372 };
373
374 let mut month = Month::December;
375 let is_leap_year = is_leap_year(self.year);
376 loop {
377 if month > self.month {
378 unix_seconds = unix_seconds.checked_sub(month::seconds_of_month(&month, is_leap_year))
379 .ok_or_else(|| err!("Time is too small"))?;
380 month = month.wrapping_last();
381 } else {
382 break;
383 }
384 }
385
386 unix_seconds = match unix_seconds.checked_sub(
387 month::seconds_of_month(&self.month, is_leap_year) - (crate::DAY * u64::from(self.day)) as UnixSecond
388 ).map(|s| {
389 let today_seconds = crate::HOUR * u64::from(self.hour) + crate::MINUTE * u64::from(self.minute) + u64::from(self.second);
390 s.checked_sub((crate::DAY - today_seconds) as UnixSecond)
391 }) {
392 Some(Some(unix_seconds)) => unix_seconds,
393 _ => return Err(err!("Time is too small")),
394 };
395
396 if let Some(gmt_offset) = self.gmt_offset {
397 unix_seconds = unix_seconds.checked_sub(gmt_offset.into()).ok_or_else(|| err!("Time is too small"))?;
398 }
399
400 Ok(unix_seconds)
401 }
402
403}
404
405impl PartialEq for Time {
406
407 fn eq(&self, other: &Self) -> bool {
408 self.cmp(other) == Ordering::Equal
409 }
410
411}
412
413impl Ord for Time {
414
415 fn cmp(&self, other: &Self) -> Ordering {
416 let self_seconds = match self.year.checked_sub(other.year) {
417 Some(0) => match self.month.to_unix() - other.month.to_unix() {
418 0 => match self.day as i8 - other.day as i8 {
419 0 => 0 as GmtOffset,
420 -1 => -(crate::DAY as GmtOffset),
421 1 => crate::DAY as GmtOffset,
422 other => return if other > 0 { Ordering::Greater } else { Ordering::Less },
423 },
424 month_delta => {
425 let is_leap_year = is_leap_year(self.year);
426 match month_delta {
427 -1 => if month::last_day_of_month(&self.month, is_leap_year) == self.day && other.day == 1 {
428 -(crate::DAY as GmtOffset)
429 } else {
430 return Ordering::Less;
431 },
432 1 => if self.day == 1 && month::last_day_of_month(&other.month, is_leap_year) == other.day {
433 crate::DAY as GmtOffset
434 } else {
435 return Ordering::Greater;
436 },
437 other => return if other > 0 { Ordering::Greater } else { Ordering::Less },
438 }
439 },
440 },
441 Some(1) => match (self.month, self.day, other.month, other.day) {
442 (Month::January, 1, Month::December, 31) => crate::DAY as GmtOffset,
443 _ => return Ordering::Greater,
444 },
445 Some(-1) => match (self.month, self.day, other.month, other.day) {
446 (Month::December, 31, Month::January, 1) => -(crate::DAY as GmtOffset),
447 _ => return Ordering::Less,
448 },
449 _ => return self.year.cmp(&other.year),
450 };
451
452 let self_seconds = self_seconds
453 + (crate::HOUR * u64::from(self.hour) + crate::MINUTE * u64::from(self.minute) + u64::from(self.second)) as GmtOffset
454 - self.gmt_offset.unwrap_or(0);
455 let other_seconds =
456 (crate::HOUR * u64::from(other.hour) + crate::MINUTE * u64::from(other.minute) + u64::from(other.second)) as GmtOffset
457 - other.gmt_offset.unwrap_or(0);
458 self_seconds.cmp(&other_seconds)
459 }
460
461}
462
463impl TryFrom<&Time> for Duration {
464
465 type Error = Error;
466
467 fn try_from(time: &Time) -> Result<Self, Self::Error> {
468 let unix_seconds = time.try_into_unix_seconds()?;
469 u64::try_from(unix_seconds).map(|s| Duration::from_secs(s)).map_err(|_| err!(
470 "Failed to convert '{unix_seconds}' into u64",
471 unix_seconds=unix_seconds,
472 ))
473 }
474
475}
476
477impl TryFrom<Time> for Duration {
478
479 type Error = Error;
480
481 fn try_from(time: Time) -> Result<Self, Self::Error> {
482 Self::try_from(&time)
483 }
484
485}
486
487impl TryFrom<&Duration> for Time {
488
489 type Error = Error;
490
491 fn try_from(duration: &Duration) -> Result<Self, Self::Error> {
492 try_duration_into_time(duration, None)
493 }
494
495}
496
497impl TryFrom<Duration> for Time {
498
499 type Error = Error;
500
501 fn try_from(duration: Duration) -> Result<Self, Self::Error> {
502 Self::try_from(&duration)
503 }
504
505}
506
507#[cfg(feature="std")]
508#[doc(cfg(feature="std"))]
509impl TryFrom<&SystemTime> for Time {
510
511 type Error = Error;
512
513 fn try_from(system_time: &SystemTime) -> Result<Self, Self::Error> {
514 match system_time.duration_since(UNIX_EPOCH) {
515 Ok(duration) => Self::try_from(duration),
516 Err(err) => Err(err!(
517 "Failed calculating duration of {system_time:?} since UNIX Epoch: {err}",
518 system_time=system_time, err=err,
519 )),
520 }
521 }
522
523}
524
525#[cfg(feature="std")]
526#[doc(cfg(feature="std"))]
527impl TryFrom<SystemTime> for Time {
528
529 type Error = Error;
530
531 fn try_from(system_time: SystemTime) -> Result<Self, Self::Error> {
532 Self::try_from(&system_time)
533 }
534
535}
536
537impl Debug for Time {
538
539 fn fmt(&self, f: &mut Formatter) -> Result<(), fmt::Error> {
540 let month = match self.month {
541 Month::January => 1,
542 Month::February => 2,
543 Month::March => 3,
544 Month::April => 4,
545 Month::May => 5,
546 Month::June => 6,
547 Month::July => 7,
548 Month::August => 8,
549 Month::September => 9,
550 Month::October => 10,
551 Month::November => 11,
552 Month::December => 12,
553 };
554 let gmt_offset = match self.gmt_offset {
555 Some(gmt_offset) if gmt_offset != 0 => {
556 let hour = gmt_offset / crate::HOUR as GmtOffset;
557 let minute = ((gmt_offset - (hour * crate::HOUR as GmtOffset)) / crate::MINUTE as GmtOffset).abs();
558 Cow::Owned(format!(
559 "[{sign}{hour:02}:{minute:02}]",
560 sign=if gmt_offset >= 0 { concat!('+') } else { concat!() }, hour=hour, minute=minute,
561 ))
562 },
563 _ => Cow::Borrowed("UTC"),
564 };
565 write!(
566 f,
567 "{year:04}-{month:02}-{day:02} {hour:02}:{minute:02}:{second:02} {gmt_offset}",
568 year=self.year, month=month, day=self.day, hour=self.hour, minute=self.minute, second=self.second, gmt_offset=gmt_offset,
569 )
570 }
571
572}
573
574#[cfg(all(feature="libc", not(windows)))]
576fn load_gmt_offset() -> CrateResult<Option<GmtOffset>> {
577 match unsafe {
578 libc::time(ptr::null_mut())
579 } {
580 -1 => Err(fmt_err_code("time")),
581 time => {
582 let mut tm = libc::tm {
583 tm_year: 0, tm_mon: 0, tm_mday: 0,
584 tm_hour: 0, tm_min: 0, tm_sec: 0,
585 tm_wday: 0, tm_yday: 0, tm_isdst: 0, tm_gmtoff: 0,
586 #[cfg(any(target_os = "macos", target_os = "freebsd", target_os = "netbsd"))]
587 tm_zone: ptr::null_mut(),
588 #[cfg(not(any(target_os = "macos", target_os = "freebsd", target_os = "netbsd")))]
589 tm_zone: ptr::null(),
590 };
591 if unsafe {
592 libc::localtime_r(&time, &mut tm).is_null()
594 } {
595 return Err(fmt_err_code("localtime_r"));
596 }
597 match tm.tm_gmtoff {
598 0 => Ok(None),
599 _ => Ok(Some(GmtOffset::try_from(tm.tm_gmtoff).map_err(|_| err!("Invalid Unix GMT offset: {}", tm.tm_gmtoff))?)),
600 }
601 },
602 }
603}
604
605#[cfg(all(windows, feature="libc"))]
607fn load_gmt_offset() -> CrateResult<Option<GmtOffset>> {
608 let time = unsafe {
609 libc::time(ptr::null_mut())
610 };
611 let mut local = libc::tm {
612 tm_year: 0, tm_mon: 0, tm_mday: 0, tm_hour: 0, tm_min: 0, tm_sec: 0, tm_wday: 0, tm_yday: 0, tm_isdst: 0,
613 };
614 let mut gmt = local;
615 match unsafe {
616 (libc::localtime_s(&mut local, &time), libc::gmtime_s(&mut gmt, &time))
617 } {
618 (0, 0) => {},
619 (local, gmt) => return Err(fmt_fn_name_and_err_code("localtime_s", if local != 0 { local } else { gmt })),
620 };
621
622 let same_day = local.tm_mday == gmt.tm_mday;
623 match (|| {
624 let day_in_seconds = GmtOffset::try_from(DAY).ok()?;
625 let hour_in_seconds = GmtOffset::try_from(HOUR).ok()?;
626 let minute_in_seconds = GmtOffset::try_from(MINUTE).ok()?;
627
628 let seconds = |tm: &libc::tm| tm.tm_hour.checked_mul(hour_in_seconds)?.checked_add(tm.tm_min.checked_mul(minute_in_seconds)?);
629 let local_seconds = seconds(&local)?;
630 let gmt_seconds = seconds(&gmt)?;
631 let result = match cmp_tm(&local, &gmt) {
632 Ordering::Equal => 0,
633 Ordering::Greater | Ordering::Less if same_day => local_seconds.checked_sub(gmt_seconds)?,
634 Ordering::Greater => local_seconds.checked_add(day_in_seconds.checked_sub(gmt_seconds)?)?,
635 Ordering::Less => gmt_seconds.checked_add(day_in_seconds.checked_sub(local_seconds)?).map(|n| -n)?,
636 };
637 if local.tm_isdst > 0 {
638 result.checked_sub(hour_in_seconds)
639 } else {
640 Some(result)
641 }
642 })().ok_or_else(|| err!())? {
643 0 => Ok(None),
644 other => Ok(Some(GmtOffset::try_from(other).map_err(|_| err!("Invalid Unix GMT offset: {other}"))?)),
645 }
646}
647
648#[cfg(all(windows, feature="libc"))]
649fn cmp_tm(first: &libc::tm, second: &libc::tm) -> Ordering {
650 first.tm_year.cmp(&second.tm_year).then_with(||
651 first.tm_mon.cmp(&second.tm_mon).then_with(||
652 first.tm_mday.cmp(&second.tm_mday).then_with(||
653 first.tm_hour.cmp(&second.tm_hour).then_with(||
654 first.tm_min.cmp(&second.tm_min).then_with(|| first.tm_sec.cmp(&second.tm_sec))
655 )
656 )
657 )
658 )
659}
660
661pub fn try_unix_seconds_into_time(unix_seconds: UnixSecond, gmt_offset: Option<GmtOffset>) -> CrateResult<Time> {
663 let unix_seconds = match gmt_offset {
664 Some(gmt_offset) => unix_seconds.checked_add(gmt_offset.into()).ok_or_else(|| err!(
665 "Failed adding GMT offset '{gmt_offset}' to Unix seconds '{unix_seconds}'", gmt_offset=gmt_offset, unix_seconds=unix_seconds,
666 ))?,
667 None => unix_seconds,
668 };
669
670 fn try_positive(all_seconds: UnixSecond, gmt_offset: Option<GmtOffset>) -> CrateResult<Time> {
671 let mut year = all_seconds / SECONDS_OF_400_YEARS * 400;
672 let mut all_seconds = all_seconds % SECONDS_OF_400_YEARS;
673 loop {
674 let one_year = if is_leap_year(year) { ONE_LEAP_YEAR } else { ONE_YEAR };
675 match all_seconds.checked_sub(one_year) {
676 Some(new_secs) if new_secs >= 0 => {
677 year += 1;
678 all_seconds = new_secs;
679 },
680 _ => break,
681 };
682 }
683
684 let (month, day, hour, minute, second) = month_day_hour_minute_second(is_leap_year(year), all_seconds);
685 Time::make(year, month, day, hour, minute, second, gmt_offset)
686 }
687
688 fn try_negative(all_seconds: UnixSecond, gmt_offset: Option<GmtOffset>) -> CrateResult<Time> {
689 let mut year = (all_seconds / SECONDS_OF_400_YEARS * 400).checked_sub(1).ok_or_else(|| err!("Failed to calculating year"))?;
690 let mut all_seconds = all_seconds % SECONDS_OF_400_YEARS;
691 loop {
692 let one_year = if is_leap_year(year) { ONE_LEAP_YEAR } else { ONE_YEAR };
693 match all_seconds.checked_add(one_year) {
694 Some(new_secs) if new_secs <= 0 => {
695 year -= 1;
696 all_seconds = new_secs;
697 },
698 _ => break,
699 };
700 }
701
702 let is_leap_year = is_leap_year(year);
703 all_seconds += if is_leap_year { ONE_LEAP_YEAR } else { ONE_YEAR };
704 let (month, day, hour, minute, second) = month_day_hour_minute_second(is_leap_year, all_seconds);
705 Time::make(year, month, day, hour, minute, second, gmt_offset)
706 }
707
708 fn month_day_hour_minute_second(is_leap_year: bool, mut all_seconds: UnixSecond) -> (Month, Day, Hour, Minute, Second) {
709 let mut month = Month::January;
710 loop {
711 match all_seconds.checked_sub(month::seconds_of_month(&month, is_leap_year)) {
712 Some(new_secs) if new_secs >= 0 => {
713 month = month.wrapping_next();
714 all_seconds = new_secs;
715 },
716 _ => break,
717 };
718 }
719
720 let day = ((all_seconds / crate::DAY as UnixSecond) + 1) as Day;
721 let all_seconds = all_seconds % crate::DAY as UnixSecond;
722
723 let hour = (all_seconds / crate::HOUR as UnixSecond) as Hour;
724 let all_seconds = all_seconds % crate::HOUR as UnixSecond;
725
726 let minute = (all_seconds / crate::MINUTE as UnixSecond) as Minute;
727 let second = (all_seconds % crate::MINUTE as UnixSecond) as Second;
728
729 (month, day, hour, minute, second)
730 }
731
732 let all_seconds = unix_seconds.checked_add(SECONDS_OF_1970_YEARS)
733 .ok_or_else(|| err!("Failed transforming Unix seconds: {}", unix_seconds))?;
734 if all_seconds >= 0 {
735 try_positive(all_seconds, gmt_offset)
736 } else {
737 try_negative(all_seconds, gmt_offset)
738 }
739}
740
741fn try_duration_into_time(duration: &Duration, gmt_offset: Option<GmtOffset>) -> CrateResult<Time> {
743 let unix_seconds = duration.as_secs();
744 let unix_seconds = UnixSecond::try_from(unix_seconds)
745 .map_err(|_| err!("Failed to convert {} into UnixSecond", unix_seconds))?;
746 try_unix_seconds_into_time(unix_seconds, gmt_offset)
747}
748
749#[cfg(feature="libc")]
751#[doc(cfg(feature="libc"))]
752pub fn try_duration_into_local_time(duration: &Duration) -> CrateResult<Time> {
753 try_duration_into_time(duration, load_gmt_offset()?)
754}
755
756fn is_leap_year(year: Year) -> bool {
758 match year % 4 {
759 0 => match year % 100 {
760 0 => year % 400 == 0,
761 _ => true,
762 }
763 _ => false,
764 }
765}
766
767#[cfg(all(feature="libc", not(windows)))]
768fn fmt_err_code<S>(fn_name: S) -> Error where S: AsRef<str> {
769 fmt_fn_name_and_err_code(fn_name, unsafe {
770 *libc::__errno_location()
771 })
772}
773
774#[cfg(feature="libc")]
775fn fmt_fn_name_and_err_code<S>(fn_name: S, err_code: i32) -> Error where S: AsRef<str> {
776 err!(
777 "libc::{fn_name}() failed: {err_code}: {err:?}",
778 fn_name=fn_name.as_ref(),
779 err=unsafe { CStr::from_ptr(libc::strerror(err_code)) },
780 )
781}