1#![cfg_attr(not(any(feature = "std", feature = "parse", feature = "json")), no_std)]
81
82#[cfg(any(feature = "std", feature = "parse", feature = "json"))]
83#[cfg(test)]
84mod tests;
85#[cfg(any(feature = "std", feature = "parse", feature = "json"))]
86extern crate std;
87#[cfg(any(feature = "std", feature = "parse", feature = "json"))]
88use std::{
89 error, fmt, fs::File, io::Read, str::from_utf8, string::String, string::ToString, vec::Vec,
90};
91
92#[cfg(not(any(feature = "std", feature = "parse", feature = "json")))]
93#[cfg(test)]
94mod tests_nostd;
95#[cfg(not(any(feature = "std", feature = "parse", feature = "json")))]
96extern crate alloc;
97#[cfg(not(any(feature = "std", feature = "parse", feature = "json")))]
98use alloc::{str::from_utf8, string::String, string::ToString, vec::Vec};
99
100#[cfg(any(feature = "parse", feature = "json"))]
101use chrono::{DateTime, FixedOffset, TimeZone, Utc};
102#[cfg(feature = "json")]
103use serde::Serialize;
104
105#[cfg(feature = "json")]
106mod offset_serializer {
107 use serde::Serialize;
108 use std::{format, string::String};
109 fn offset_to_json(t: chrono::FixedOffset) -> String {
110 format!("{:?}", t)
111 }
112
113 pub fn serialize<S: serde::Serializer>(
114 time: &chrono::FixedOffset,
115 serializer: S,
116 ) -> Result<S::Ok, S::Error> {
117 offset_to_json(time.clone()).serialize(serializer)
118 }
119}
120
121use byteorder::{ByteOrder, BE};
122
123const MAGIC: u32 = 0x545A6966;
125const HEADER_LEN: usize = 0x2C;
127
128#[derive(Debug, PartialEq, Eq, Clone)]
129pub enum TzError {
130 InvalidTimezone,
132 InvalidMagic,
134 BadUtf8String,
136 UnsupportedFormat,
138 NoData,
140 ParseError,
142 EmptyString,
144 JsonError,
146}
147
148#[cfg(any(feature = "std", feature = "parse", feature = "json"))]
149impl fmt::Display for TzError {
150 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
151 f.write_str("TZfile error : ")?;
152 f.write_str(match self {
153 TzError::InvalidTimezone => "Invalid timezone",
154 TzError::InvalidMagic => "Invalid TZfile",
155 TzError::BadUtf8String => "Bad utf8 string",
156 TzError::UnsupportedFormat => "Only V2 format is supported",
157 TzError::NoData => "No data matched the request",
158 TzError::ParseError => "Parsing error",
159 TzError::EmptyString => "Empty string",
160 TzError::JsonError => "Could not convert to json",
161 })
162 }
163}
164
165#[cfg(any(feature = "std", feature = "parse", feature = "json"))]
166impl From<std::io::Error> for TzError {
167 fn from(_e: std::io::Error) -> TzError {
168 TzError::InvalidTimezone
169 }
170}
171
172#[cfg(any(feature = "std", feature = "parse", feature = "json"))]
173impl From<std::num::ParseIntError> for TzError {
174 fn from(_e: std::num::ParseIntError) -> TzError {
175 TzError::ParseError
176 }
177}
178
179#[cfg(any(feature = "std", feature = "parse", feature = "json"))]
180impl From<std::str::Utf8Error> for TzError {
181 fn from(_e: std::str::Utf8Error) -> TzError {
182 TzError::BadUtf8String
183 }
184}
185
186#[cfg(any(feature = "std", feature = "parse", feature = "json"))]
187impl From<TzError> for std::io::Error {
188 fn from(e: TzError) -> std::io::Error {
189 std::io::Error::new(std::io::ErrorKind::Other, e)
190 }
191}
192
193#[cfg(feature = "json")]
194impl From<serde_json::error::Error> for TzError {
195 fn from(_e: serde_json::error::Error) -> TzError {
196 TzError::JsonError
197 }
198}
199
200#[cfg(any(feature = "std", feature = "parse", feature = "json"))]
201impl error::Error for TzError {}
202
203#[derive(Debug)]
205pub struct Tz {
206 pub tzh_timecnt_data: Vec<i64>,
208 pub tzh_timecnt_indices: Vec<u8>,
210 pub tzh_typecnt: Vec<Ttinfo>,
212 pub tz_abbr: Vec<String>,
214 #[cfg(any(feature = "parse", feature = "json"))]
215 name: String,
216}
217
218#[derive(Debug)]
220pub struct Ttinfo {
221 pub tt_utoff: isize,
222 pub tt_isdst: u8,
223 pub tt_abbrind: u8,
224}
225
226#[derive(Debug, PartialEq)]
227struct Header {
228 tzh_ttisutcnt: usize,
229 tzh_ttisstdcnt: usize,
230 tzh_leapcnt: usize,
231 tzh_timecnt: usize,
232 tzh_typecnt: usize,
233 tzh_charcnt: usize,
234 v2_header_start: usize,
235}
236
237#[cfg(any(feature = "parse", feature = "json"))]
238#[derive(Debug, PartialEq)]
240pub struct TransitionTime {
241 pub time: DateTime<Utc>,
243 pub utc_offset: isize,
245 pub isdst: bool,
247 pub abbreviation: String,
249}
250
251#[cfg(feature = "json")]
259#[derive(Debug, Serialize)]
260pub struct Tzinfo {
261 pub timezone: String,
263 pub utc_datetime: DateTime<Utc>,
265 pub datetime: DateTime<FixedOffset>,
267 pub dst_from: Option<DateTime<Utc>>,
269 pub dst_until: Option<DateTime<Utc>>,
271 pub dst_period: bool,
273 pub raw_offset: isize,
275 pub dst_offset: isize,
277 #[serde(with = "offset_serializer")]
279 pub utc_offset: FixedOffset,
280 pub abbreviation: String,
282 pub week_number: i32,
284}
285
286#[cfg(feature = "parse")]
287#[derive(Debug)]
288pub struct Tzinfo {
289 pub timezone: String,
291 pub utc_datetime: DateTime<Utc>,
293 pub datetime: DateTime<FixedOffset>,
295 pub dst_from: Option<DateTime<Utc>>,
297 pub dst_until: Option<DateTime<Utc>>,
299 pub dst_period: bool,
301 pub raw_offset: isize,
303 pub dst_offset: isize,
305 pub utc_offset: FixedOffset,
307 pub abbreviation: String,
309 pub week_number: i32,
311}
312
313#[cfg(feature = "json")]
314impl Tzinfo {
315 pub fn to_json(&self) -> Result<String, serde_json::error::Error> {
331 serde_json::to_string(self)
332 }
333}
334
335impl Tz {
336 #[cfg(not(any(feature = "std", feature = "parse", feature = "json")))]
337 pub fn new(buf: Vec<u8>) -> Result<Tz, TzError> {
338 let header = Tz::parse_header(&buf)?;
340 Tz::parse_data(&buf, header)
342 }
343
344 #[cfg(any(feature = "std", feature = "parse", feature = "json"))]
345 pub fn new(tz: &str) -> Result<Tz, TzError> {
356 let buf = Tz::read(tz)?;
358 let header = Tz::parse_header(&buf)?;
360 Tz::parse_data(&buf, header, tz)
362 }
363
364 fn parse_header(buffer: &[u8]) -> Result<Header, TzError> {
365 let magic = BE::read_u32(&buffer[0x00..=0x03]);
366 if magic != MAGIC {
367 return Err(TzError::InvalidMagic);
368 }
369 if buffer[4] != 50 {
370 return Err(TzError::UnsupportedFormat);
371 }
372 let tzh_ttisutcnt = BE::read_i32(&buffer[0x14..=0x17]) as usize;
373 let tzh_ttisstdcnt = BE::read_i32(&buffer[0x18..=0x1B]) as usize;
374 let tzh_leapcnt = BE::read_i32(&buffer[0x1C..=0x1F]) as usize;
375 let tzh_timecnt = BE::read_i32(&buffer[0x20..=0x23]) as usize;
376 let tzh_typecnt = BE::read_i32(&buffer[0x24..=0x27]) as usize;
377 let tzh_charcnt = BE::read_i32(&buffer[0x28..=0x2b]) as usize;
378 let s: usize = tzh_timecnt * 5
380 + tzh_typecnt * 6
381 + tzh_leapcnt * 8
382 + tzh_charcnt
383 + tzh_ttisstdcnt
384 + tzh_ttisutcnt
385 + 44;
386 Ok(Header {
387 tzh_ttisutcnt: BE::read_i32(&buffer[s + 0x14..=s + 0x17]) as usize,
388 tzh_ttisstdcnt: BE::read_i32(&buffer[s + 0x18..=s + 0x1B]) as usize,
389 tzh_leapcnt: BE::read_i32(&buffer[s + 0x1C..=s + 0x1F]) as usize,
390 tzh_timecnt: BE::read_i32(&buffer[s + 0x20..=s + 0x23]) as usize,
391 tzh_typecnt: BE::read_i32(&buffer[s + 0x24..=s + 0x27]) as usize,
392 tzh_charcnt: BE::read_i32(&buffer[s + 0x28..=s + 0x2b]) as usize,
393 v2_header_start: s,
394 })
395 }
396
397 #[cfg(not(any(feature = "std", feature = "parse", feature = "json")))]
398 fn parse_data(buffer: &Vec<u8>, header: Header) -> Result<Tz, TzError> {
399 let tzh_timecnt_len: usize = header.tzh_timecnt * 9;
401 let tzh_typecnt_len: usize = header.tzh_typecnt * 6;
402 let tzh_leapcnt_len: usize = header.tzh_leapcnt * 12;
403 let tzh_charcnt_len: usize = header.tzh_charcnt;
404 let tzh_timecnt_end: usize = HEADER_LEN + header.v2_header_start + tzh_timecnt_len;
405 let tzh_typecnt_end: usize = tzh_timecnt_end + tzh_typecnt_len;
406 let tzh_leapcnt_end: usize = tzh_typecnt_end + tzh_leapcnt_len;
407 let tzh_charcnt_end: usize = tzh_leapcnt_end + tzh_charcnt_len;
408
409 let tzh_timecnt_data: Vec<i64> = buffer[HEADER_LEN + header.v2_header_start
411 ..HEADER_LEN + header.v2_header_start + header.tzh_timecnt * 8]
412 .chunks_exact(8)
413 .map(|tt| BE::read_i64(tt))
414 .collect();
415
416 let tzh_timecnt_indices: &[u8] =
417 &buffer[HEADER_LEN + header.v2_header_start + header.tzh_timecnt * 8..tzh_timecnt_end];
418
419 let abbrs = from_utf8(&buffer[tzh_leapcnt_end..tzh_charcnt_end]).unwrap();
420
421 let tzh_typecnt: Vec<Ttinfo> = buffer[tzh_timecnt_end..tzh_typecnt_end]
422 .chunks_exact(6)
423 .map(|tti| {
424 let offset = tti[5];
425 let index = abbrs
426 .chars()
427 .take(offset as usize)
428 .filter(|x| *x == '\0')
429 .count();
430 Ttinfo {
431 tt_utoff: BE::read_i32(&tti[0..4]) as isize,
432 tt_isdst: tti[4],
433 tt_abbrind: index as u8,
434 }
435 })
436 .collect();
437
438 let mut tz_abbr: Vec<String> = abbrs.split("\u{0}").map(|st| st.to_string()).collect();
439 if tz_abbr.pop().is_none() {
441 return Err(TzError::EmptyString);
442 };
443
444 Ok(Tz {
445 tzh_timecnt_data,
446 tzh_timecnt_indices: tzh_timecnt_indices.to_vec(),
447 tzh_typecnt,
448 tz_abbr,
449 })
450 }
451
452 #[cfg(feature = "std")]
453 fn parse_data(buffer: &[u8], header: Header, filename: &str) -> Result<Tz, TzError> {
454 let tzh_timecnt_len: usize = header.tzh_timecnt * 9;
456 let tzh_typecnt_len: usize = header.tzh_typecnt * 6;
457 let tzh_leapcnt_len: usize = header.tzh_leapcnt * 12;
458 let tzh_charcnt_len: usize = header.tzh_charcnt;
459 let tzh_timecnt_end: usize = HEADER_LEN + header.v2_header_start + tzh_timecnt_len;
460 let tzh_typecnt_end: usize = tzh_timecnt_end + tzh_typecnt_len;
461 let tzh_leapcnt_end: usize = tzh_typecnt_end + tzh_leapcnt_len;
462 let tzh_charcnt_end: usize = tzh_leapcnt_end + tzh_charcnt_len;
463
464 let tzh_timecnt_data: Vec<i64> = buffer[HEADER_LEN + header.v2_header_start
466 ..HEADER_LEN + header.v2_header_start + header.tzh_timecnt * 8]
467 .chunks_exact(8)
468 .map(BE::read_i64)
469 .collect();
470
471 let tzh_timecnt_indices: &[u8] =
472 &buffer[HEADER_LEN + header.v2_header_start + header.tzh_timecnt * 8..tzh_timecnt_end];
473
474 let abbrs = from_utf8(&buffer[tzh_leapcnt_end..tzh_charcnt_end])?;
475
476 let tzh_typecnt: Vec<Ttinfo> = buffer[tzh_timecnt_end..tzh_typecnt_end]
477 .chunks_exact(6)
478 .map(|tti| {
479 let offset = tti[5];
480 let index = abbrs
481 .chars()
482 .take(offset as usize)
483 .filter(|x| *x == '\0')
484 .count();
485 Ttinfo {
486 tt_utoff: BE::read_i32(&tti[0..4]) as isize,
487 tt_isdst: tti[4],
488 tt_abbrind: index as u8,
489 }
490 })
491 .collect();
492
493 let mut tz_abbr: Vec<String> = abbrs.split('\u{0}').map(|st| st.to_string()).collect();
494 if tz_abbr.pop().is_none() {
496 return Err(TzError::EmptyString);
497 };
498
499 let mut timezone = String::new();
501 #[cfg(not(windows))]
502 let mut tz: Vec<&str> = filename.split('/').collect();
503 #[cfg(windows)]
504 let mut tz: Vec<&str> = filename.split("\\").collect();
505 if tz.len() < 3 {
507 return Err(TzError::InvalidTimezone);
508 }
509 for _ in 0..(tz.len()) - 2 {
510 tz.remove(0);
511 }
512 if tz[0] != "zoneinfo" {
513 timezone.push_str(tz[0]);
514 timezone.push('/');
515 }
516 timezone.push_str(tz[1]);
517
518 #[cfg(any(feature = "parse", feature = "json"))]
519 {
520 return Ok(Tz {
521 tzh_timecnt_data,
522 tzh_timecnt_indices: tzh_timecnt_indices.to_vec(),
523 tzh_typecnt,
524 tz_abbr,
525 name: timezone,
526 });
527 }
528
529 #[cfg(not(any(feature = "parse", feature = "json")))]
530 Ok(Tz {
531 tzh_timecnt_data,
532 tzh_timecnt_indices: tzh_timecnt_indices.to_vec(),
533 tzh_typecnt,
534 tz_abbr,
535 })
536 }
537
538 #[cfg(any(feature = "std", feature = "parse", feature = "json"))]
539 fn read(tz: &str) -> Result<Vec<u8>, std::io::Error> {
540 let mut f = File::open(tz)?;
541 let mut buffer = Vec::new();
542 f.read_to_end(&mut buffer)?;
543 Ok(buffer)
544 }
545
546 #[cfg(any(feature = "parse", feature = "json"))]
547 pub fn transition_times(&self, y: Option<i32>) -> Result<Vec<TransitionTime>, TzError> {
562 let timezone = self;
563
564 if timezone.tzh_timecnt_data.len() == 0 {
567 return Err(TzError::NoData);
568 }
569
570 let mut timechanges = Vec::new();
572 let mut nearest_timechange: usize = 0;
573
574 let mut parsedtimechanges = Vec::new();
576
577 if y.is_some() {
579 let d = Utc::now();
580 let y = y.unwrap();
581 let y = if y == 0 {
583 d.format("%Y").to_string().parse()?
584 } else {
585 y
586 };
587 let yearbeg = Utc.with_ymd_and_hms(y, 1, 1, 0, 0, 0).unwrap().timestamp();
591 let yearend = Utc
592 .with_ymd_and_hms(y, 12, 31, 0, 0, 0)
593 .unwrap()
594 .timestamp();
595 for t in 0..timezone.tzh_timecnt_data.len() {
596 if timezone.tzh_timecnt_data[t] > yearbeg && timezone.tzh_timecnt_data[t] < yearend
597 {
598 timechanges.push(t);
599 }
600 if timezone.tzh_timecnt_data[t] < yearbeg {
601 nearest_timechange = t;
602 };
603 }
604 } else {
605 for t in 0..timezone.tzh_timecnt_data.len() {
607 if timezone.tzh_timecnt_data[t] != -576460752303423488 {
609 timechanges.push(t)
610 };
611 }
612 }
613
614 if timechanges.len() != 0 {
616 for t in 0..timechanges.len() {
617 let tc = TransitionTime {
618 time: Utc
619 .timestamp_opt(timezone.tzh_timecnt_data[timechanges[t]], 0)
620 .unwrap(),
621 utc_offset: timezone.tzh_typecnt
622 [timezone.tzh_timecnt_indices[timechanges[t]] as usize]
623 .tt_utoff,
624 isdst: timezone.tzh_typecnt
625 [timezone.tzh_timecnt_indices[timechanges[t]] as usize]
626 .tt_isdst
627 == 1,
628 abbreviation: timezone.tz_abbr[timezone.tzh_typecnt
629 [timezone.tzh_timecnt_indices[timechanges[t]] as usize]
630 .tt_abbrind as usize]
631 .to_string(),
632 };
633 parsedtimechanges.push(tc);
634 }
635 } else {
636 let tc = TransitionTime {
637 time: Utc
638 .timestamp_opt(timezone.tzh_timecnt_data[nearest_timechange], 0)
639 .unwrap(),
640 utc_offset: timezone.tzh_typecnt
641 [timezone.tzh_timecnt_indices[nearest_timechange] as usize]
642 .tt_utoff,
643 isdst: timezone.tzh_typecnt
644 [timezone.tzh_timecnt_indices[nearest_timechange] as usize]
645 .tt_isdst
646 == 1,
647 abbreviation: timezone.tz_abbr[timezone.tzh_typecnt
648 [timezone.tzh_timecnt_indices[nearest_timechange] as usize]
649 .tt_abbrind as usize]
650 .to_string(),
651 };
652 parsedtimechanges.push(tc);
653 }
654 Ok(parsedtimechanges)
655 }
656
657 #[cfg(any(feature = "parse", feature = "json"))]
658 pub fn zoneinfo(&self) -> Result<Tzinfo, TzError> {
669 let parsedtimechanges = match self.transition_times(Some(0)) {
670 Ok(p) => p,
671 Err(TzError::NoData) => Vec::new(),
672 Err(e) => return Err(e),
673 };
674 let d = Utc::now();
675 if parsedtimechanges.len() == 2 {
676 let dst = d > parsedtimechanges[0].time && d < parsedtimechanges[1].time;
679 let utc_offset = if dst == true {
680 FixedOffset::east_opt(parsedtimechanges[0].utc_offset as i32).unwrap()
681 } else {
682 FixedOffset::east_opt(parsedtimechanges[1].utc_offset as i32).unwrap()
683 };
684 Ok(Tzinfo {
685 timezone: (self.name).clone(),
686 week_number: d
687 .with_timezone(&utc_offset)
688 .format("%V")
689 .to_string()
690 .parse()?,
691 utc_datetime: d,
692 datetime: d.with_timezone(&utc_offset),
693 dst_from: Some(parsedtimechanges[0].time),
694 dst_until: Some(parsedtimechanges[1].time),
695 dst_period: dst,
696 raw_offset: parsedtimechanges[1].utc_offset,
697 dst_offset: parsedtimechanges[0].utc_offset,
698 utc_offset: utc_offset,
699 abbreviation: if dst == true {
700 parsedtimechanges[0].abbreviation.clone()
701 } else {
702 parsedtimechanges[1].abbreviation.clone()
703 },
704 })
705 } else if parsedtimechanges.len() == 1 {
706 let utc_offset = FixedOffset::east_opt(parsedtimechanges[0].utc_offset as i32).unwrap();
707 Ok(Tzinfo {
708 timezone: (self.name).clone(),
709 week_number: d
710 .with_timezone(&utc_offset)
711 .format("%V")
712 .to_string()
713 .parse()?,
714 utc_datetime: d,
715 datetime: d.with_timezone(&utc_offset),
716 dst_from: None,
717 dst_until: None,
718 dst_period: false,
719 raw_offset: parsedtimechanges[0].utc_offset,
720 dst_offset: 0,
721 utc_offset: utc_offset,
722 abbreviation: parsedtimechanges[0].abbreviation.clone(),
723 })
724 } else if parsedtimechanges.len() == 0 {
725 let utc_offset = FixedOffset::east_opt(self.tzh_typecnt[0].tt_utoff as i32).unwrap();
727 Ok(Tzinfo {
728 timezone: (self.name).clone(),
729 week_number: d
730 .with_timezone(&utc_offset)
731 .format("%V")
732 .to_string()
733 .parse()?,
734 utc_datetime: d,
735 datetime: d.with_timezone(&utc_offset),
736 dst_from: None,
737 dst_until: None,
738 dst_period: false,
739 raw_offset: self.tzh_typecnt[0].tt_utoff,
740 dst_offset: 0,
741 utc_offset: utc_offset,
742 abbreviation: (self.name).clone(),
743 })
744 } else {
745 Err(TzError::NoData)
746 }
747 }
748}