Initial commit
This commit is contained in:
commit
40470e1752
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
||||
/target
|
65
Cargo.lock
generated
Normal file
65
Cargo.lock
generated
Normal file
@ -0,0 +1,65 @@
|
||||
# This file is automatically @generated by Cargo.
|
||||
# It is not intended for manual editing.
|
||||
version = 3
|
||||
|
||||
[[package]]
|
||||
name = "epoch"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
version = "1.0.78"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e2422ad645d89c99f8f3e6b88a9fdeca7fabeac836b1002371c4367c8f984aae"
|
||||
dependencies = [
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "quote"
|
||||
version = "1.0.35"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "291ec9ab5efd934aaf503a6466c5d5251535d108ee747472c3977cc5acc868ef"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde"
|
||||
version = "1.0.196"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "870026e60fa08c69f064aa766c10f10b1d62db9ccd4d0abb206472bee0ce3b32"
|
||||
dependencies = [
|
||||
"serde_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_derive"
|
||||
version = "1.0.196"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "33c85360c95e7d137454dc81d9a4ed2b8efd8fbe19cee57357b32b9771fccb67"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "2.0.48"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0f3531638e407dfc0814761abb7c00a5b54992b849452a0646b7f65c9f770f3f"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "unicode-ident"
|
||||
version = "1.0.12"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b"
|
9
Cargo.toml
Normal file
9
Cargo.toml
Normal file
@ -0,0 +1,9 @@
|
||||
[package]
|
||||
name = "epoch"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
serde = { version = "1.0", features = ["derive"] }
|
155
src/datetime.rs
Normal file
155
src/datetime.rs
Normal file
@ -0,0 +1,155 @@
|
||||
use {
|
||||
crate::{
|
||||
month::Month, weekday::Weekday, year::Year, zone::TimeZone, SECONDS_PER_DAY,
|
||||
SECONDS_PER_HOUR, SECONDS_PER_MINUTE,
|
||||
},
|
||||
serde::{Deserialize, Serialize},
|
||||
std::{cmp, fmt},
|
||||
};
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
|
||||
pub struct DateTime {
|
||||
pub year: Year,
|
||||
pub month: Month,
|
||||
pub day: u8,
|
||||
pub hour: u8,
|
||||
pub minute: u8,
|
||||
pub second: u8,
|
||||
pub zone: TimeZone,
|
||||
}
|
||||
|
||||
impl DateTime {
|
||||
/// Retrieves the year
|
||||
pub fn year(&self) -> i32 {
|
||||
self.year.get()
|
||||
}
|
||||
|
||||
/// Gets the Unix timestamp corresponding to this `DateTime`
|
||||
pub fn timestamp(&self) -> i64 {
|
||||
let mut seconds: i64 = 0;
|
||||
let mut year = Year::new(1970);
|
||||
if self.year() < 1970 {
|
||||
while year.get() > self.year() {
|
||||
year = year.previous();
|
||||
seconds -= year.seconds();
|
||||
}
|
||||
} else if self.year() > 1970 {
|
||||
while year.get() < self.year() {
|
||||
seconds += year.seconds();
|
||||
year = year.next();
|
||||
}
|
||||
}
|
||||
let mut month = Month::Janurary;
|
||||
while month < self.month {
|
||||
seconds += month.seconds(self.year);
|
||||
month = month.next().unwrap();
|
||||
}
|
||||
// The days begin numbering with 1, so on the 5th we have had four full
|
||||
// days plus some remainder. So we use self.days - 1 for our calculations
|
||||
seconds += i64::from(self.day - 1) * SECONDS_PER_DAY;
|
||||
seconds += i64::from(self.hour) * SECONDS_PER_HOUR;
|
||||
seconds += i64::from(self.second);
|
||||
seconds += self.zone.as_seconds();
|
||||
seconds
|
||||
}
|
||||
|
||||
/// Converts a Unix timestamp to a `DateTime` struct
|
||||
pub fn from_timestamp(ts: i64) -> Self {
|
||||
if ts < 0 {
|
||||
let mut seconds: i64 = 0;
|
||||
let mut year = Year::new(-1);
|
||||
while seconds > -year.seconds() {
|
||||
seconds -= year.seconds();
|
||||
year = year.previous();
|
||||
}
|
||||
let mut month = Some(Month::December);
|
||||
while month.is_some() {
|
||||
let m = month.unwrap();
|
||||
if seconds > m.seconds(year) {
|
||||
break;
|
||||
}
|
||||
seconds -= m.seconds(year);
|
||||
month = m.previous();
|
||||
}
|
||||
let month = month.unwrap();
|
||||
let mut day = month.days(year);
|
||||
while day > 0 && seconds < -SECONDS_PER_DAY {
|
||||
seconds -= SECONDS_PER_HOUR;
|
||||
day -= 1;
|
||||
}
|
||||
let mut hour: i8 = 23;
|
||||
while hour >= 0 && seconds < -SECONDS_PER_HOUR {
|
||||
seconds -= SECONDS_PER_HOUR;
|
||||
hour -= 1;
|
||||
}
|
||||
let hour = hour.try_into().unwrap();
|
||||
let mut minute: i8 = 60;
|
||||
while minute >= 0 && seconds < -60 {
|
||||
seconds -= 60;
|
||||
minute -= 1;
|
||||
}
|
||||
let minute = minute.try_into().unwrap();
|
||||
let second: u8 = (seconds + 60).try_into().unwrap();
|
||||
Self {
|
||||
year,
|
||||
month,
|
||||
day,
|
||||
hour,
|
||||
minute,
|
||||
second,
|
||||
zone: TimeZone::Utc,
|
||||
}
|
||||
} else if ts > 0 {
|
||||
let mut seconds: i64 = ts;
|
||||
let mut year = Year::new(1970);
|
||||
while year.seconds() < seconds {
|
||||
seconds -= year.seconds();
|
||||
year = year.next();
|
||||
}
|
||||
let mut month = Month::Janurary;
|
||||
while month.seconds(year) < seconds {
|
||||
seconds -= month.seconds(year);
|
||||
month = month.next().unwrap();
|
||||
}
|
||||
let day = seconds / SECONDS_PER_DAY + 1;
|
||||
seconds %= SECONDS_PER_DAY;
|
||||
let hour = seconds / SECONDS_PER_HOUR;
|
||||
seconds %= SECONDS_PER_HOUR;
|
||||
let minute = seconds / SECONDS_PER_MINUTE;
|
||||
seconds %= SECONDS_PER_MINUTE;
|
||||
Self {
|
||||
year,
|
||||
month,
|
||||
day: day.try_into().unwrap(),
|
||||
hour: hour.try_into().unwrap(),
|
||||
minute: minute.try_into().unwrap(),
|
||||
second: seconds.try_into().unwrap(),
|
||||
zone: TimeZone::Utc,
|
||||
}
|
||||
} else {
|
||||
Self {
|
||||
year: Year::new(1970),
|
||||
month: Month::Janurary,
|
||||
day: 1,
|
||||
hour: 0,
|
||||
minute: 0,
|
||||
second: 0,
|
||||
zone: TimeZone::Utc,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Ord for DateTime {
|
||||
fn cmp(&self, other: &Self) -> cmp::Ordering {
|
||||
let a = self.timestamp();
|
||||
let b = other.timestamp();
|
||||
a.cmp(&b)
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialOrd for DateTime {
|
||||
fn partial_cmp(&self, other: &Self) -> Option<cmp::Ordering> {
|
||||
Some(self.cmp(other))
|
||||
}
|
||||
}
|
14
src/lib.rs
Normal file
14
src/lib.rs
Normal file
@ -0,0 +1,14 @@
|
||||
#[warn(clippy::all, clippy::pedantic)]
|
||||
#[allow(clippy::must_use_candidate)]
|
||||
#[allow(clippy::comparison_chain)]
|
||||
pub(crate) mod datetime;
|
||||
pub(crate) mod month;
|
||||
pub(crate) mod weekday;
|
||||
pub(crate) mod year;
|
||||
pub(crate) mod zone;
|
||||
|
||||
pub mod prelude;
|
||||
|
||||
pub static SECONDS_PER_MINUTE: i64 = 60;
|
||||
pub static SECONDS_PER_HOUR: i64 = 60 * 60;
|
||||
pub static SECONDS_PER_DAY: i64 = 60 * 60 * 24;
|
175
src/month.rs
Normal file
175
src/month.rs
Normal file
@ -0,0 +1,175 @@
|
||||
use std::str::FromStr;
|
||||
|
||||
use {
|
||||
crate::{year::Year, SECONDS_PER_DAY},
|
||||
serde::{Deserialize, Serialize},
|
||||
std::{cmp, error, fmt},
|
||||
};
|
||||
|
||||
#[derive(Clone, Copy, Debug, Deserialize, Serialize, PartialEq, Eq)]
|
||||
#[repr(u8)]
|
||||
pub enum Month {
|
||||
Janurary = 1,
|
||||
February = 2,
|
||||
March = 3,
|
||||
April = 4,
|
||||
May = 5,
|
||||
June = 6,
|
||||
July = 7,
|
||||
August = 8,
|
||||
September = 9,
|
||||
October = 10,
|
||||
November = 11,
|
||||
December = 12,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum Error {
|
||||
ParseMonth,
|
||||
Range,
|
||||
}
|
||||
|
||||
impl fmt::Display for Error {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "{self:?}")
|
||||
}
|
||||
}
|
||||
|
||||
impl error::Error for Error {}
|
||||
|
||||
impl Month {
|
||||
pub fn days(&self, year: Year) -> u8 {
|
||||
match *self as u8 {
|
||||
1 | 3 | 5 | 7 | 8 | 10 | 12 => 31,
|
||||
2 => match year {
|
||||
Year::Normal(_) => 28,
|
||||
Year::Leap(_) => 29,
|
||||
},
|
||||
_ => 30,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn seconds(&self, year: Year) -> i64 {
|
||||
self.days(year) as i64 * SECONDS_PER_DAY
|
||||
}
|
||||
|
||||
pub fn next(&self) -> Option<Self> {
|
||||
let n = *self as u8 + 1;
|
||||
n.try_into().ok()
|
||||
}
|
||||
|
||||
pub fn previous(&self) -> Option<Self> {
|
||||
let n = *self as u8 - 1;
|
||||
n.try_into().ok()
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<u8> for Month {
|
||||
type Error = Error;
|
||||
|
||||
fn try_from(value: u8) -> Result<Self, Self::Error> {
|
||||
match value {
|
||||
1 => Ok(Self::Janurary),
|
||||
2 => Ok(Self::February),
|
||||
3 => Ok(Self::March),
|
||||
4 => Ok(Self::April),
|
||||
5 => Ok(Self::May),
|
||||
6 => Ok(Self::June),
|
||||
7 => Ok(Self::July),
|
||||
8 => Ok(Self::August),
|
||||
9 => Ok(Self::September),
|
||||
10 => Ok(Self::October),
|
||||
11 => Ok(Self::November),
|
||||
12 => Ok(Self::December),
|
||||
_ => Err(Error::Range),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Ord for Month {
|
||||
fn cmp(&self, other: &Self) -> cmp::Ordering {
|
||||
(*self as u8).cmp(&(*other as u8))
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialOrd for Month {
|
||||
fn partial_cmp(&self, other: &Self) -> Option<cmp::Ordering> {
|
||||
Some(self.cmp(other))
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for Month {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(
|
||||
f,
|
||||
"{}",
|
||||
match self {
|
||||
Self::Janurary => "January",
|
||||
Self::February => "February",
|
||||
Self::March => "March",
|
||||
Self::April => "April",
|
||||
Self::May => "May",
|
||||
Self::June => "June",
|
||||
Self::July => "Jujly",
|
||||
Self::August => "August",
|
||||
Self::September => "September",
|
||||
Self::October => "October",
|
||||
Self::November => "November",
|
||||
Self::December => "December",
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl FromStr for Month {
|
||||
type Err = Error;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
match s {
|
||||
"Jan" | "jan" | "January" | "january" => Ok(Self::Janurary),
|
||||
"Feb" | "feb" | "February" | "february" => Ok(Self::February),
|
||||
"March" | "march" | "Mar" | "mar" => Ok(Self::March),
|
||||
"April" | "april" | "Apr" | "apr" => Ok(Self::April),
|
||||
"May" | "may" => Ok(Self::May),
|
||||
"June" | "june" | "Jun" | "jun" => Ok(Self::June),
|
||||
"July" | "july" | "Jul" | "jul" => Ok(Self::July),
|
||||
"August" | "august" | "Aug" | "aug" => Ok(Self::August),
|
||||
"September" | "september" | "Sep" | "sep" => Ok(Self::September),
|
||||
"October" | "october" | "Oct" | "oct" => Ok(Self::October),
|
||||
"November" | "november" | "Nov" | "nov" => Ok(Self::November),
|
||||
"December" | "december" | "Dec" | "dec" => Ok(Self::December),
|
||||
_ => Err(Error::ParseMonth),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn next() {
|
||||
let mut month = Month::Janurary;
|
||||
month = month.next().unwrap();
|
||||
assert_eq!(month, Month::February);
|
||||
month = month.next().unwrap();
|
||||
assert_eq!(month, Month::March);
|
||||
month = Month::December;
|
||||
assert!(month.next().is_none())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn previous() {
|
||||
let mut month = Month::March.previous().unwrap();
|
||||
assert_eq!(month, Month::February);
|
||||
month = month.previous().unwrap();
|
||||
assert_eq!(month, Month::Janurary);
|
||||
assert!(month.previous().is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn compare() {
|
||||
assert!(Month::Janurary < Month::October);
|
||||
assert!(Month::June > Month::March);
|
||||
}
|
||||
}
|
7
src/prelude.rs
Normal file
7
src/prelude.rs
Normal file
@ -0,0 +1,7 @@
|
||||
pub use crate::{
|
||||
datetime::DateTime,
|
||||
month::{Error as MonthError, Month},
|
||||
weekday::Weekday,
|
||||
year::Year,
|
||||
zone::{Sign, TimeZone},
|
||||
};
|
13
src/weekday.rs
Normal file
13
src/weekday.rs
Normal file
@ -0,0 +1,13 @@
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
|
||||
#[repr(u8)]
|
||||
pub enum Weekday {
|
||||
Thursday,
|
||||
Friday,
|
||||
Saturday,
|
||||
Sunday,
|
||||
Monday,
|
||||
Tuesday,
|
||||
Wednesday,
|
||||
}
|
73
src/year.rs
Normal file
73
src/year.rs
Normal file
@ -0,0 +1,73 @@
|
||||
use {
|
||||
crate::SECONDS_PER_DAY,
|
||||
serde::{Deserialize, Serialize},
|
||||
std::{cmp, fmt, num::ParseIntError, str::FromStr},
|
||||
};
|
||||
|
||||
#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
|
||||
pub enum Year {
|
||||
Normal(i32),
|
||||
Leap(i32),
|
||||
}
|
||||
|
||||
impl Year {
|
||||
pub fn new(year: i32) -> Self {
|
||||
if year % 4 == 0 && (year % 100 != 0 || year % 400 == 0) {
|
||||
Self::Leap(year)
|
||||
} else {
|
||||
Self::Normal(year)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn days(&self) -> u16 {
|
||||
match self {
|
||||
Self::Normal(_) => 365,
|
||||
Self::Leap(_) => 366,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn seconds(&self) -> i64 {
|
||||
self.days() as i64 * SECONDS_PER_DAY
|
||||
}
|
||||
|
||||
pub fn get(&self) -> i32 {
|
||||
match self {
|
||||
Self::Normal(y) | Self::Leap(y) => *y,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn next(&self) -> Self {
|
||||
Self::new(self.get() + 1)
|
||||
}
|
||||
|
||||
pub fn previous(&self) -> Self {
|
||||
Self::new(self.get() - 1)
|
||||
}
|
||||
}
|
||||
|
||||
impl Ord for Year {
|
||||
fn cmp(&self, other: &Self) -> cmp::Ordering {
|
||||
self.get().cmp(&other.get())
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialOrd for Year {
|
||||
fn partial_cmp(&self, other: &Self) -> Option<cmp::Ordering> {
|
||||
Some(self.cmp(other))
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for Year {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "{:>4}", self.get())
|
||||
}
|
||||
}
|
||||
|
||||
impl FromStr for Year {
|
||||
type Err = ParseIntError;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
let year: i32 = s.parse()?;
|
||||
Ok(Self::new(year))
|
||||
}
|
||||
}
|
159
src/zone.rs
Normal file
159
src/zone.rs
Normal file
@ -0,0 +1,159 @@
|
||||
use {
|
||||
crate::SECONDS_PER_HOUR,
|
||||
serde::{Deserialize, Serialize},
|
||||
std::{error, fmt, str::FromStr},
|
||||
};
|
||||
|
||||
#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
|
||||
pub enum Sign {
|
||||
Positive,
|
||||
Negative,
|
||||
}
|
||||
|
||||
impl fmt::Display for Sign {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(
|
||||
f,
|
||||
"{}",
|
||||
match self {
|
||||
Self::Positive => "+",
|
||||
Self::Negative => "-",
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
|
||||
pub enum TimeZone {
|
||||
Offset { sign: Sign, hours: u8, minutes: u8 },
|
||||
Utc,
|
||||
}
|
||||
|
||||
impl TimeZone {
|
||||
pub fn new(hours: Option<i8>, minutes: Option<u8>) -> Result<Self, TZError> {
|
||||
match (hours, minutes) {
|
||||
(Some(h), Some(m)) => {
|
||||
if (!(-12..=12).contains(&h) || m > 59) || ((h == -12 || h == 12) && m > 0) {
|
||||
return Err(TZError::Range);
|
||||
}
|
||||
let (sign, hours) = if h > 0 {
|
||||
(Sign::Positive, u8::try_from(h).unwrap())
|
||||
} else {
|
||||
(Sign::Negative, u8::try_from(-h).unwrap())
|
||||
};
|
||||
Ok(Self::Offset {
|
||||
sign,
|
||||
hours,
|
||||
minutes: m,
|
||||
})
|
||||
}
|
||||
(Some(h), None) => {
|
||||
if !(-12..=12).contains(&h) {
|
||||
return Err(TZError::Range);
|
||||
}
|
||||
let (sign, hours) = if h > 0 {
|
||||
(Sign::Positive, u8::try_from(h).unwrap())
|
||||
} else {
|
||||
(Sign::Negative, u8::try_from(-h).unwrap())
|
||||
};
|
||||
Ok(Self::Offset {
|
||||
sign,
|
||||
hours,
|
||||
minutes: 0,
|
||||
})
|
||||
}
|
||||
(None, Some(m)) => {
|
||||
if m > 59 {
|
||||
Err(TZError::Range)
|
||||
} else {
|
||||
Ok(Self::Offset {
|
||||
sign: Sign::Positive,
|
||||
hours: 0,
|
||||
minutes: m,
|
||||
})
|
||||
}
|
||||
}
|
||||
_ => Ok(Self::Utc),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn as_seconds(&self) -> i64 {
|
||||
match self {
|
||||
Self::Utc => 0,
|
||||
Self::Offset {
|
||||
sign,
|
||||
hours,
|
||||
minutes,
|
||||
} => {
|
||||
let base = i64::from(*hours) * SECONDS_PER_HOUR + i64::from(*minutes) * 60;
|
||||
if let Sign::Negative = sign {
|
||||
-base
|
||||
} else {
|
||||
base
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for TimeZone {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
Self::Utc => write!(f, "Z"),
|
||||
Self::Offset {
|
||||
sign,
|
||||
hours,
|
||||
minutes,
|
||||
} => {
|
||||
write!(f, "{sign}{hours}:{minutes}")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl FromStr for TimeZone {
|
||||
type Err = TZError;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
if s == "z" || s == "Z" {
|
||||
return Ok(Self::Utc);
|
||||
}
|
||||
let sign = match s.get(0..1) {
|
||||
Some("+") => Sign::Positive,
|
||||
Some("-") => Sign::Negative,
|
||||
_ => return Err(TZError::ParseTZError),
|
||||
};
|
||||
if let Some(s) = s.get(1..) {
|
||||
let Some((h, m)) = s.split_once(':') else {
|
||||
return Err(TZError::ParseTZError);
|
||||
};
|
||||
let hours = h.parse::<u8>().map_err(|_| TZError::ParseTZError)?;
|
||||
let minutes = m.parse::<u8>().map_err(|_| TZError::ParseTZError)?;
|
||||
if hours > 12 || minutes > 59 || (hours == 12 && minutes > 0) {
|
||||
Err(TZError::Range)
|
||||
} else {
|
||||
Ok(Self::Offset {
|
||||
sign,
|
||||
hours,
|
||||
minutes,
|
||||
})
|
||||
}
|
||||
} else {
|
||||
Err(TZError::ParseTZError)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum TZError {
|
||||
Range,
|
||||
ParseTZError,
|
||||
}
|
||||
|
||||
impl fmt::Display for TZError {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "{self:?}")
|
||||
}
|
||||
}
|
||||
|
||||
impl error::Error for TZError {}
|
Loading…
Reference in New Issue
Block a user