Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add remaining-time module #44

Merged
merged 1 commit into from
Jan 20, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@ include = ["src/**/*.rs", "Cargo.toml", "LICENSE", "README.md"]


[dependencies]
regex = "1.10.2"
urlencoding = { version = "2.1.3", optional = true }
serde = { version = "1.0.195", features = ["derive"], optional = true }
thiserror = { version = "1.0.56", optional = true }
Expand Down Expand Up @@ -49,6 +48,7 @@ full = [
"legal-id",
"words-to-number",
"sheba",
"remaining-time",
] # For now, by default we enable all features:


Expand All @@ -75,6 +75,7 @@ half-space = []
legal-id = ["dep:thiserror"]
words-to-number = ["dep:thiserror", "commas", "digits", "remove-ordinal-suffix"]
sheba = ["dep:thiserror"]
remaining-time = ["time-ago"]

[package.metadata.docs.rs]
all-features = true
Expand Down
8 changes: 7 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ all: build check test docs
fmt:
cargo fmt

build: full default add-ordinal-suffix commas digits find-capital-by-province persian-chars national-id remove-ordinal-suffix url-fix verity-card-number time-ago phone-number bill number-to-words get-bank-name-by-card-number extract-card-number get-place-by-iran-national-id half-space legal-id words-to-number sheba
build: full default add-ordinal-suffix commas digits find-capital-by-province persian-chars national-id remove-ordinal-suffix url-fix verity-card-number time-ago phone-number bill number-to-words get-bank-name-by-card-number extract-card-number get-place-by-iran-national-id half-space legal-id words-to-number sheba remaining-time

check: clippy lint

Expand Down Expand Up @@ -141,3 +141,9 @@ sheba:
cargo build --no-default-features --features=sheba
@ ls -sh target/debug/*.rlib


remaining-time:
@ echo ""
cargo build --no-default-features --features=remaining-time
@ ls -sh target/debug/*.rlib

4 changes: 4 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
feature = "legal-id",
feature = "words-to-number",
feature = "sheba",
feature = "remaining-time",
)))]
compile_error!("No available Cargo feature is included");

Expand Down Expand Up @@ -85,3 +86,6 @@ pub mod words_to_number;

#[cfg(feature = "sheba")]
pub mod sheba;

#[cfg(feature = "remaining-time")]
pub mod remaining_time;
192 changes: 192 additions & 0 deletions src/remaining_time/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,192 @@
use std::fmt;

use crate::time_ago::{
convert_to_timestamp, get_current_timestamp, TimeAgoError, DAY, HOUR, MINUTE, MONTH, YEAR,
};

#[derive(Debug, PartialEq)]
pub struct RemainingTime {
pub years: u32,
pub months: u8,
pub days: u8,
pub hours: u8,
pub minutes: u8,
pub seconds: u8,
pub is_finished: bool,
}

impl fmt::Display for RemainingTime {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
// Format the struct fields as needed

let mut periods: Vec<String> = Vec::new();

if self.years > 0 {
periods.push(format!("{} سال", self.years))
}
if self.months > 0 {
periods.push(format!("{} ماه", self.months))
}
if self.days > 0 {
periods.push(format!("{} روز", self.days))
}
if self.hours > 0 {
periods.push(format!("{} ساعت", self.hours))
}
if self.minutes > 0 {
periods.push(format!("{} دقیقه", self.minutes))
}
if self.seconds > 0 {
periods.push(format!("{} ثانیه", self.seconds))
}

write!(f, "{}", periods.join(" و "))
}
}

/// returns [RemainingTime] as result if the datetime has a valid format
///
/// in [RemainingTime] you can use its field like ```remaining_time.hours``` or ```remaining_time.days```
/// also Display trais is implmented for [RemainingTime] so if you could try try ```println("{}" , remaining_time)``` and a string like : ```۱ سال و ۱ ماه و ۲ روز و ۳ ساعت و ۵ دقیقه و ۸ ثانیه```
///
/// # Warning
/// This function is desgined to only works for these date time formats :
///
///
/// - `%Y-%m-%d %H:%M:%S`: Sortable format
/// - `%Y/%m/%d %H:%M:%S`: Sortable format
/// - `%Y-%m-%dT%H:%M:%S%:z`: ISO 8601 with timezone offset
/// - `%Y-%m-%dT%H:%M:%S%.3f%:z`: ISO 8601 with milliseconds and timezone offset
/// - `%a, %d %b %Y %H:%M:%S %z`: RFC 2822 Format
///
///
/// timezone is set with the current timezone of the OS.
///
/// ```
/// use rust_persian_tools::remaining_time::{remaining_time , RemainingTime};
/// use chrono::{Duration, Local};
///
/// let current_time = Local::now();
/// let due_date = current_time
/// + Duration::weeks(320)
/// + Duration::hours(7)
/// + Duration::minutes(13)
/// + Duration::seconds(37);
/// let formatted_time = due_date.format("%Y-%m-%d %H:%M:%S").to_string();
///
/// assert_eq!(
/// remaining_time(&formatted_time).unwrap(),
/// RemainingTime {
/// years: 6,
/// months: 1,
/// days: 20,
/// hours: 7,
/// minutes: 13,
/// seconds: 37,
/// is_finished: false,
/// }
/// );
///
/// assert_eq!(
/// format!("{}", remaining_time(&formatted_time).unwrap()),
/// String::from("6 سال و 1 ماه و 20 روز و 7 ساعت و 13 دقیقه و 37 ثانیه")
/// );
///
///
/// ```
pub fn remaining_time(datetime: impl AsRef<str>) -> Result<RemainingTime, TimeAgoError> {
let datetime = datetime.as_ref();

let due_date = convert_to_timestamp(datetime)?;
let now = get_current_timestamp();

let mut remaining_timestamp = due_date - now;

if remaining_timestamp <= 0 {
// if its due
return Ok(RemainingTime {
years: 0,
months: 0,
days: 0,
hours: 0,
minutes: 0,
seconds: 0,
is_finished: true,
});
}

let years: u32 = (remaining_timestamp / YEAR) as u32;
remaining_timestamp %= YEAR;

let months: u8 = ((remaining_timestamp / MONTH) % MONTH) as u8;
remaining_timestamp %= MONTH;

let days: u8 = ((remaining_timestamp / DAY) % DAY) as u8;
remaining_timestamp %= DAY;

let hours: u8 = ((remaining_timestamp / HOUR) % HOUR) as u8;
remaining_timestamp %= HOUR;

let minutes: u8 = ((remaining_timestamp / MINUTE) % MINUTE) as u8;
remaining_timestamp %= MINUTE;

let seconds: u8 = remaining_timestamp as u8;

Ok(RemainingTime {
years,
months,
days,
hours,
minutes,
seconds,
is_finished: false,
})
}

#[cfg(test)]
mod tests {
use super::*;
use chrono::{Duration, Local};

#[test]
fn remaining_time_test() {
let current_time = Local::now();
let due_date = current_time
+ Duration::weeks(320)
+ Duration::hours(7)
+ Duration::minutes(13)
+ Duration::seconds(37);
let formatted_time = due_date.format("%Y-%m-%d %H:%M:%S").to_string();

assert_eq!(
remaining_time(formatted_time).unwrap(),
RemainingTime {
years: 6,
months: 1,
days: 20,
hours: 7,
minutes: 13,
seconds: 37,
is_finished: false,
}
);
}

#[test]
fn remaining_time_as_string_test() {
let current_time = Local::now();
let due_date =
current_time + Duration::weeks(340) + Duration::minutes(12) + Duration::seconds(37);
let formatted_time = due_date.format("%Y-%m-%d %H:%M:%S").to_string();

assert_eq!(
format!("{}", remaining_time(formatted_time).unwrap()),
String::from("6 سال و 6 ماه و 10 روز و 12 دقیقه و 37 ثانیه")
);
}

#[test]
fn remaining_time_fail_test() {
assert!(remaining_time("123:12312").is_err());
}
}
12 changes: 6 additions & 6 deletions src/time_ago/mod.rs
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
use chrono::{DateTime, Local, NaiveDateTime, TimeZone};
use thiserror::Error;

const MINUTE: i64 = 60;
const HOUR: i64 = MINUTE * 60;
const DAY: i64 = HOUR * 24;
const WEEK: i64 = DAY * 7;
const MONTH: i64 = DAY * 30;
const YEAR: i64 = DAY * 365;
pub(crate) const MINUTE: i64 = 60;
pub(crate) const HOUR: i64 = MINUTE * 60;
pub(crate) const DAY: i64 = HOUR * 24;
pub(crate) const WEEK: i64 = DAY * 7;
pub(crate) const MONTH: i64 = DAY * 30;
pub(crate) const YEAR: i64 = DAY * 365;

#[derive(Error, Debug)]
pub enum TimeAgoError {
Expand Down