Added time converter.

Signed-off-by: Pavel Kirilin <win10@list.ru>
This commit is contained in:
2023-03-08 04:15:34 +04:00
parent 8b5a588770
commit 7ad82b4a25
5 changed files with 197 additions and 1 deletions

11
Cargo.lock generated
View File

@ -1355,6 +1355,15 @@ dependencies = [
"windows-sys 0.45.0",
]
[[package]]
name = "itertools"
version = "0.10.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473"
dependencies = [
"either",
]
[[package]]
name = "itoa"
version = "1.0.5"
@ -2152,8 +2161,10 @@ dependencies = [
"grammers-client",
"grammers-session",
"grammers-tl-types",
"itertools",
"lazy_static",
"log",
"num-integer",
"rand 0.8.5",
"rayon",
"regex",

View File

@ -35,3 +35,5 @@ tokio = { version = "1.25.0", features = [
"macros",
"rt-multi-thread",
] }
num-integer = "0.1.45"
itertools = "0.10.5"

View File

@ -2,4 +2,5 @@ pub mod currency_converter;
pub mod get_chat_id;
pub mod help;
pub mod notify_all;
pub mod time_converter;
pub mod weather_forecaster;

View File

@ -0,0 +1,175 @@
use crate::{bot::handlers::Handler, utils::messages::get_message};
use chrono::{FixedOffset, NaiveDateTime, NaiveTime, TimeZone, Utc};
use grammers_client::{Client, InputMessage, Update};
use itertools::Itertools;
use num_integer::div_floor;
const HOUR: i32 = 3600;
#[derive(Clone)]
pub struct TimeConverter;
#[allow(clippy::trivially_copy_pass_by_ref)]
pub fn to_utc_name(offset: &FixedOffset) -> String {
let seconds = offset.local_minus_utc();
let hours = div_floor(seconds, HOUR);
if hours >= 0 {
format!("UTC+{hours}")
} else {
format!("UTC{hours}")
}
}
pub fn convert_time(offsets: &[FixedOffset], times: &[NaiveTime]) -> Vec<String> {
let mut replies = Vec::new();
let now = Utc::now();
let Some(main_offset) = offsets.get(0) else {
return vec![];
};
for time in times {
let dt = NaiveDateTime::new(now.date_naive(), *time);
let Some(start_time) = main_offset.from_local_datetime(&dt).latest() else {
continue;
};
for offset in offsets {
if offset == main_offset && offsets.len() > 1 {
continue;
}
let end_time = start_time.with_timezone(offset);
replies.push(format!(
"{} {} = {} {}",
start_time.format("%H:%M"),
to_utc_name(main_offset),
end_time.format("%H:%M"),
to_utc_name(offset)
));
}
}
replies
}
#[async_trait::async_trait]
impl Handler for TimeConverter {
async fn react(&self, _: &Client, update: &Update) -> anyhow::Result<()> {
let Some(message) = get_message(update) else{return Ok(());};
let mut offsets = Vec::new();
let mut times = Vec::new();
for part in message.text().strip_prefix(".t").unwrap_or("").split(' ') {
if let Some(offset) = part
.parse::<i32>()
.ok()
.and_then(|offset| FixedOffset::east_opt(offset * HOUR))
{
offsets.push(offset);
} else if let Ok(naive_time) = NaiveTime::parse_from_str(part, "%H:%M") {
times.push(naive_time);
}
}
if offsets.is_empty() && times.is_empty() {
message
.reply(format!(
"Текущее время в UTC+0: {}",
Utc::now().time().format("%H:%M")
))
.await?;
return Ok(());
}
if offsets.len() > 50 || times.len() > 50 {
message.reply("Ты меня походу спамишь, дядь.").await?;
return Ok(());
}
if offsets.is_empty() {
message
.reply("Добавь оффсеты. Например: .t 1 +1 -1")
.await?;
return Ok(());
}
if times.is_empty() {
offsets = [FixedOffset::east_opt(0).unwrap()]
.into_iter()
.chain(offsets.into_iter())
.collect::<Vec<_>>();
times.push(Utc::now().time());
}
let replies = convert_time(
offsets.into_iter().unique().collect_vec().as_slice(),
times.into_iter().unique().collect_vec().as_slice(),
)
.into_iter()
.map(|reply| format!("<pre>{reply}</pre><br>"))
.join("\n");
if replies.trim().is_empty() {
message.reply("Что-то я ничего не смог насчитать.").await?;
return Ok(());
}
message
.reply(InputMessage::html(format!(
"<b>Вот что я насчитал по времени: </b>\n\n{replies}"
)))
.await?;
Ok(())
}
}
#[cfg(test)]
mod tests {
use chrono::{FixedOffset, NaiveTime};
use super::{convert_time, HOUR};
#[test]
pub fn test_time_conversion() {
let replies = convert_time(
&[
FixedOffset::east_opt(0).unwrap(),
FixedOffset::east_opt(1 * HOUR).unwrap(),
FixedOffset::east_opt(2 * HOUR).unwrap(),
],
&[NaiveTime::from_hms_opt(0, 0, 0).unwrap()],
);
assert_eq!(replies.len(), 2);
assert_eq!(
replies,
vec![
String::from("00:00 UTC+0 = 01:00 UTC+1"),
String::from("00:00 UTC+0 = 02:00 UTC+2"),
]
);
}
#[test]
pub fn test_time_conversion_negatives() {
let replies = convert_time(
&[
FixedOffset::east_opt(-3 * HOUR).unwrap(),
FixedOffset::east_opt(1 * HOUR).unwrap(),
FixedOffset::east_opt(2 * HOUR).unwrap(),
],
&[NaiveTime::from_hms_opt(0, 0, 0).unwrap()],
);
assert_eq!(replies.len(), 2);
assert_eq!(
replies,
vec![
String::from("00:00 UTC-3 = 04:00 UTC+1"),
String::from("00:00 UTC-3 = 05:00 UTC+2"),
]
);
}
}

View File

@ -21,6 +21,7 @@ use super::{
get_chat_id::GetChatId,
help::Help,
notify_all::NotifyAll,
time_converter::TimeConverter,
weather_forecaster::WeatherForecaster,
},
fun::{
@ -155,7 +156,13 @@ async fn run(args: BotConfig, client: Client) -> anyhow::Result<()> {
FilteredHandler::new(NotifyAll)
.add_filter(UpdateTypeFilter(&[UpdateType::New]))
.add_filter(SilentFilter)
.add_filter(TextFilter(&["@all"], TextMatchMethod::Contains)),
.add_filter(TextFilter(&["@all"], TextMatchMethod::Contains))
.add_middleware::<MembersCount<100>>(),
// Time conversion utils
FilteredHandler::new(TimeConverter)
.add_filter(UpdateTypeFilter(&[UpdateType::New]))
.add_filter(SilentFilter)
.add_filter(TextFilter(&[".t"], TextMatchMethod::StartsWith)),
];
let mut errors_count = 0;