refactor: replace chrono with time-rs
This commit is contained in:
parent
b5f6b7a174
commit
3c1cc92461
16 changed files with 195 additions and 112 deletions
|
|
@ -13,7 +13,7 @@ include = ["/src", "README.md", "LICENSE", "!snapshots"]
|
|||
members = [".", "codegen", "cli"]
|
||||
|
||||
[features]
|
||||
default = ["default-tls"]
|
||||
default = ["default-tls", "rss"]
|
||||
all = ["rss", "html"]
|
||||
|
||||
rss = ["quick-xml"]
|
||||
|
|
@ -38,8 +38,7 @@ serde = { version = "1.0", features = ["derive"] }
|
|||
serde_json = "1.0.82"
|
||||
serde_with = {version = "2.0.0", features = ["json"] }
|
||||
rand = "0.8.5"
|
||||
chrono = {version = "0.4.19", default-features = false, features = ["clock", "serde"]}
|
||||
chronoutil = "0.2.3"
|
||||
time = {version = "0.3.15", features = ["macros", "serde", "serde-well-known"]}
|
||||
futures = "0.3.21"
|
||||
indicatif = "0.17.0"
|
||||
filenamify = "0.1.0"
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
use std::borrow::Cow;
|
||||
|
||||
use serde::Serialize;
|
||||
use time::OffsetDateTime;
|
||||
use url::Url;
|
||||
|
||||
use crate::{
|
||||
|
|
@ -191,7 +192,8 @@ impl MapResponse<Channel<ChannelInfo>> for response::Channel {
|
|||
lang,
|
||||
&meta.joined_date_text,
|
||||
&mut warnings,
|
||||
),
|
||||
)
|
||||
.map(OffsetDateTime::date),
|
||||
view_count: meta
|
||||
.view_count_text
|
||||
.and_then(|txt| util::parse_numeric_or_warn(&txt, &mut warnings)),
|
||||
|
|
|
|||
|
|
@ -18,13 +18,13 @@ mod channel_rss;
|
|||
use std::sync::Arc;
|
||||
use std::{borrow::Cow, fmt::Debug};
|
||||
|
||||
use chrono::{DateTime, Duration, Utc};
|
||||
use fancy_regex::Regex;
|
||||
use log::{debug, error, warn};
|
||||
use once_cell::sync::Lazy;
|
||||
use rand::Rng;
|
||||
use reqwest::{header, Client, ClientBuilder, Request, RequestBuilder, Response};
|
||||
use serde::{de::DeserializeOwned, Deserialize, Serialize};
|
||||
use time::{Duration, OffsetDateTime};
|
||||
use tokio::sync::RwLock;
|
||||
|
||||
use crate::{
|
||||
|
|
@ -240,7 +240,8 @@ enum CacheEntry<T> {
|
|||
#[default]
|
||||
None,
|
||||
Some {
|
||||
last_update: DateTime<Utc>,
|
||||
#[serde(with = "time::serde::rfc3339")]
|
||||
last_update: OffsetDateTime,
|
||||
data: T,
|
||||
},
|
||||
}
|
||||
|
|
@ -254,7 +255,7 @@ impl<T> CacheEntry<T> {
|
|||
fn get(&self) -> Option<&T> {
|
||||
match self {
|
||||
CacheEntry::Some { last_update, data } => {
|
||||
if last_update < &(Utc::now() - Duration::hours(24)) {
|
||||
if last_update < &(OffsetDateTime::now_utc() - Duration::hours(24)) {
|
||||
None
|
||||
} else {
|
||||
Some(data)
|
||||
|
|
@ -268,7 +269,7 @@ impl<T> CacheEntry<T> {
|
|||
impl<T> From<T> for CacheEntry<T> {
|
||||
fn from(f: T) -> Self {
|
||||
Self::Some {
|
||||
last_update: Utc::now(),
|
||||
last_update: OffsetDateTime::now_utc(),
|
||||
data: f,
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,7 @@
|
|||
use std::{borrow::Cow, convert::TryFrom};
|
||||
|
||||
use time::OffsetDateTime;
|
||||
|
||||
use crate::{
|
||||
deobfuscate::Deobfuscator,
|
||||
error::{Error, ExtractionError},
|
||||
|
|
@ -152,9 +154,9 @@ impl MapResponse<Playlist> for response::Playlist {
|
|||
.and_then(|link| ChannelId::try_from(link).ok());
|
||||
|
||||
let mut warnings = video_items.warnings;
|
||||
let last_update = last_update_txt
|
||||
.as_ref()
|
||||
.and_then(|txt| timeago::parse_textual_date_or_warn(lang, txt, &mut warnings));
|
||||
let last_update = last_update_txt.as_ref().and_then(|txt| {
|
||||
timeago::parse_textual_date_or_warn(lang, txt, &mut warnings).map(OffsetDateTime::date)
|
||||
});
|
||||
|
||||
Ok(MapResult {
|
||||
c: Playlist {
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
use chrono::{DateTime, Utc};
|
||||
use serde::Deserialize;
|
||||
use time::OffsetDateTime;
|
||||
|
||||
use super::Thumbnail;
|
||||
|
||||
|
|
@ -9,8 +9,8 @@ pub(crate) struct ChannelRss {
|
|||
pub channel_id: String,
|
||||
#[serde(rename = "$unflatten=title")]
|
||||
pub title: String,
|
||||
#[serde(rename = "$unflatten=published")]
|
||||
pub create_date: DateTime<Utc>,
|
||||
#[serde(rename = "$unflatten=published", with = "time::serde::rfc3339")]
|
||||
pub create_date: OffsetDateTime,
|
||||
pub entry: Vec<Entry>,
|
||||
}
|
||||
|
||||
|
|
@ -20,10 +20,10 @@ pub(crate) struct Entry {
|
|||
pub video_id: String,
|
||||
#[serde(rename = "$unflatten=title")]
|
||||
pub title: String,
|
||||
#[serde(rename = "$unflatten=published")]
|
||||
pub published: DateTime<Utc>,
|
||||
#[serde(rename = "$unflatten=updated")]
|
||||
pub updated: DateTime<Utc>,
|
||||
#[serde(rename = "$unflatten=published", with = "time::serde::rfc3339")]
|
||||
pub published: OffsetDateTime,
|
||||
#[serde(rename = "$unflatten=updated", with = "time::serde::rfc3339")]
|
||||
pub updated: OffsetDateTime,
|
||||
#[serde(rename = "$unflatten=media:group")]
|
||||
pub media_group: MediaGroup,
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
use chrono::TimeZone;
|
||||
use serde::Deserialize;
|
||||
use serde_with::{json::JsonString, serde_as, DefaultOnError, VecSkipError};
|
||||
use time::OffsetDateTime;
|
||||
|
||||
use super::{ChannelBadge, ContinuationEndpoint, Thumbnails};
|
||||
use crate::{
|
||||
|
|
@ -342,12 +342,7 @@ impl<T> YouTubeListMapper<T> {
|
|||
publish_date: video
|
||||
.upcoming_event_data
|
||||
.as_ref()
|
||||
.map(|upc| {
|
||||
chrono::Local.from_utc_datetime(&chrono::NaiveDateTime::from_timestamp(
|
||||
upc.start_time,
|
||||
0,
|
||||
))
|
||||
})
|
||||
.and_then(|upc| OffsetDateTime::from_unix_timestamp(upc.start_time).ok())
|
||||
.or_else(|| {
|
||||
video
|
||||
.published_time_text
|
||||
|
|
|
|||
|
|
@ -143,7 +143,7 @@ Channel(
|
|||
),
|
||||
],
|
||||
content: ChannelInfo(
|
||||
create_date: Some("2009-04-04T00:00:00+02:00"),
|
||||
create_date: Some("2009-04-04"),
|
||||
view_count: Some(186854342),
|
||||
links: [
|
||||
("EEVblog Web Site", "http://www.eevblog.com/"),
|
||||
|
|
|
|||
|
|
@ -160,7 +160,7 @@ Channel(
|
|||
),
|
||||
],
|
||||
channel: None,
|
||||
publish_date: Some("2022-09-27T18:00:00+02:00"),
|
||||
publish_date: Some("2022-09-27T16:00:00Z"),
|
||||
publish_date_txt: None,
|
||||
view_count: Some(237),
|
||||
is_live: false,
|
||||
|
|
|
|||
|
|
@ -5,13 +5,14 @@ mod paginator;
|
|||
pub mod richtext;
|
||||
|
||||
pub use paginator::Paginator;
|
||||
use serde_with::serde_as;
|
||||
|
||||
use std::ops::Range;
|
||||
|
||||
use chrono::{DateTime, Local, Utc};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use time::{Date, OffsetDateTime};
|
||||
|
||||
use crate::{error::Error, util};
|
||||
use crate::{error::Error, serializer::DateYmd, util};
|
||||
|
||||
use self::richtext::RichText;
|
||||
|
||||
|
|
@ -437,6 +438,7 @@ pub struct Subtitle {
|
|||
*/
|
||||
|
||||
/// YouTube playlist
|
||||
#[serde_as]
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
#[non_exhaustive]
|
||||
pub struct Playlist {
|
||||
|
|
@ -455,7 +457,8 @@ pub struct Playlist {
|
|||
/// Channel of the playlist
|
||||
pub channel: Option<ChannelId>,
|
||||
/// Last update date
|
||||
pub last_update: Option<DateTime<Local>>,
|
||||
#[serde_as(as = "Option<DateYmd>")]
|
||||
pub last_update: Option<Date>,
|
||||
/// Textual last update date
|
||||
pub last_update_txt: Option<String>,
|
||||
}
|
||||
|
|
@ -512,7 +515,8 @@ pub struct VideoDetails {
|
|||
/// Video publishing date. Start date in case of a livestream.
|
||||
///
|
||||
/// [`None`] if the date could not be parsed.
|
||||
pub publish_date: Option<DateTime<Local>>,
|
||||
#[serde(with = "time::serde::rfc3339::option")]
|
||||
pub publish_date: Option<OffsetDateTime>,
|
||||
/// Textual video publishing date (e.g. `Aug 2, 2013`, depends on language)
|
||||
pub publish_date_txt: String,
|
||||
/// Is the video a livestream?
|
||||
|
|
@ -616,7 +620,8 @@ pub struct Comment {
|
|||
/// Comment publishing date.
|
||||
///
|
||||
/// [`None`] if the date could not be parsed.
|
||||
pub publish_date: Option<DateTime<Local>>,
|
||||
#[serde(with = "time::serde::rfc3339::option")]
|
||||
pub publish_date: Option<OffsetDateTime>,
|
||||
/// Textual comment publish date (e.g. `14 hours ago`), depends on language setting
|
||||
pub publish_date_txt: String,
|
||||
/// Number of comment likes
|
||||
|
|
@ -675,11 +680,13 @@ pub struct Channel<T> {
|
|||
}
|
||||
|
||||
/// Additional channel metadata fetched from the "About" tab.
|
||||
#[serde_as]
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
|
||||
#[non_exhaustive]
|
||||
pub struct ChannelInfo {
|
||||
/// Channel creation date
|
||||
pub create_date: Option<DateTime<Local>>,
|
||||
#[serde_as(as = "Option<DateYmd>")]
|
||||
pub create_date: Option<Date>,
|
||||
/// Channel view count
|
||||
pub view_count: Option<u64>,
|
||||
/// Links to other websites or social media profiles
|
||||
|
|
@ -697,7 +704,8 @@ pub struct ChannelRss {
|
|||
/// List of the latest channel videos
|
||||
pub videos: Vec<ChannelRssVideo>,
|
||||
/// Channel creation date (second-accurate).
|
||||
pub create_date: DateTime<Utc>,
|
||||
#[serde(with = "time::serde::rfc3339")]
|
||||
pub create_date: OffsetDateTime,
|
||||
}
|
||||
|
||||
/// YouTube video fetched from a channel's RSS feed
|
||||
|
|
@ -713,9 +721,11 @@ pub struct ChannelRssVideo {
|
|||
/// Video thumbnail
|
||||
pub thumbnail: Thumbnail,
|
||||
/// Video publishing date (second-accurate).
|
||||
pub publish_date: DateTime<Utc>,
|
||||
#[serde(with = "time::serde::rfc3339")]
|
||||
pub publish_date: OffsetDateTime,
|
||||
/// Date and time when the RSS feed entry was last updated.
|
||||
pub update_date: DateTime<Utc>,
|
||||
#[serde(with = "time::serde::rfc3339")]
|
||||
pub update_date: OffsetDateTime,
|
||||
/// Number of views / current viewers in case of a livestream.
|
||||
pub view_count: u64,
|
||||
/// Number of likes
|
||||
|
|
@ -767,7 +777,8 @@ pub struct VideoItem {
|
|||
/// Video publishing date.
|
||||
///
|
||||
/// [`None`] if the date could not be parsed.
|
||||
pub publish_date: Option<DateTime<Local>>,
|
||||
#[serde(with = "time::serde::rfc3339::option")]
|
||||
pub publish_date: Option<OffsetDateTime>,
|
||||
/// Textual video publish date (e.g. `11 months ago`, depends on language)
|
||||
///
|
||||
/// Is [`None`] for livestreams.
|
||||
|
|
|
|||
|
|
@ -6,13 +6,17 @@ use std::{
|
|||
path::{Path, PathBuf},
|
||||
};
|
||||
|
||||
use chrono::{DateTime, Local};
|
||||
use log::error;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use time::macros::format_description;
|
||||
use time::OffsetDateTime;
|
||||
|
||||
use crate::deobfuscate::DeobfData;
|
||||
use crate::error::Error;
|
||||
|
||||
const FILENAME_FORMAT: &[time::format_description::FormatItem] =
|
||||
format_description!("[year]-[month]-[day]_[hour]-[minute]-[second]");
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
#[non_exhaustive]
|
||||
pub struct Report {
|
||||
|
|
@ -40,7 +44,8 @@ pub struct Info {
|
|||
/// Package version (`0.1.0`)
|
||||
pub version: String,
|
||||
/// Date/Time when the event occurred
|
||||
pub date: DateTime<Local>,
|
||||
#[serde(with = "time::serde::rfc3339")]
|
||||
pub date: OffsetDateTime,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
|
|
@ -76,7 +81,7 @@ impl Default for Info {
|
|||
Self {
|
||||
package: "rustypipe".to_owned(),
|
||||
version: "0.1.0".to_owned(),
|
||||
date: chrono::Local::now(),
|
||||
date: OffsetDateTime::now_utc(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -126,7 +131,7 @@ fn get_report_path(root: &Path, report: &Report, ext: &str) -> Result<PathBuf, E
|
|||
|
||||
let filename_prefix = format!(
|
||||
"{}_{:?}",
|
||||
report.info.date.format("%F_%H-%M-%S"),
|
||||
report.info.date.format(FILENAME_FORMAT).unwrap_or_default(),
|
||||
report.level
|
||||
);
|
||||
|
||||
|
|
|
|||
50
src/serializer/date.rs
Normal file
50
src/serializer/date.rs
Normal file
|
|
@ -0,0 +1,50 @@
|
|||
use serde::{
|
||||
de::{self, Visitor},
|
||||
ser, Serialize,
|
||||
};
|
||||
use serde_with::{DeserializeAs, SerializeAs};
|
||||
use time::{macros::format_description, Date};
|
||||
|
||||
const YMD_FORMAT: &[time::format_description::FormatItem] =
|
||||
format_description!("[year]-[month]-[day]");
|
||||
|
||||
pub struct DateYmd;
|
||||
|
||||
impl SerializeAs<Date> for DateYmd {
|
||||
fn serialize_as<S>(date: &Date, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: serde::Serializer,
|
||||
{
|
||||
date.format(YMD_FORMAT)
|
||||
.map_err(ser::Error::custom)?
|
||||
.serialize(serializer)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'de> DeserializeAs<'de, Date> for DateYmd {
|
||||
fn deserialize_as<D>(deserializer: D) -> Result<Date, D::Error>
|
||||
where
|
||||
D: serde::Deserializer<'de>,
|
||||
{
|
||||
struct DateYmdVisitor;
|
||||
|
||||
impl<'de> Visitor<'de> for DateYmdVisitor {
|
||||
type Value = Date;
|
||||
|
||||
fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||
formatter.write_str("a YYYY-MM-DD formatted date")
|
||||
}
|
||||
|
||||
fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
|
||||
where
|
||||
E: serde::de::Error,
|
||||
{
|
||||
Date::parse(v, YMD_FORMAT).map_err(|_| {
|
||||
de::Error::invalid_value(de::Unexpected::Str(v), &"a YYYY-MM-DD formatted date")
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
deserializer.deserialize_str(DateYmdVisitor)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,8 +1,10 @@
|
|||
pub mod text;
|
||||
|
||||
mod date;
|
||||
mod range;
|
||||
mod vec_log_err;
|
||||
|
||||
pub use date::DateYmd;
|
||||
pub use range::Range;
|
||||
pub use vec_log_err::VecLogError;
|
||||
|
||||
|
|
|
|||
|
|
@ -17,8 +17,8 @@
|
|||
|
||||
use std::ops::Mul;
|
||||
|
||||
use chrono::{DateTime, Duration, Local, NaiveDate, NaiveDateTime, NaiveTime, TimeZone};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use time::{Date, Duration, OffsetDateTime};
|
||||
|
||||
use crate::{
|
||||
param::Language,
|
||||
|
|
@ -42,7 +42,7 @@ pub struct TimeAgo {
|
|||
/// - "2 months ago" => `ParsedDate::Relative(TimeAgo {n: 2, unit: TimeUnit::Month})`
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
|
||||
pub enum ParsedDate {
|
||||
Absolute(NaiveDate),
|
||||
Absolute(Date),
|
||||
Relative(TimeAgo),
|
||||
}
|
||||
|
||||
|
|
@ -82,27 +82,25 @@ impl Mul<u8> for TimeAgo {
|
|||
}
|
||||
}
|
||||
|
||||
impl From<TimeAgo> for DateTime<Local> {
|
||||
impl From<TimeAgo> for OffsetDateTime {
|
||||
fn from(ta: TimeAgo) -> Self {
|
||||
let ts = Local::now();
|
||||
let ts = OffsetDateTime::now_utc();
|
||||
match ta.unit {
|
||||
TimeUnit::Second => ts - Duration::seconds(ta.n as i64),
|
||||
TimeUnit::Minute => ts - Duration::minutes(ta.n as i64),
|
||||
TimeUnit::Hour => ts - Duration::hours(ta.n as i64),
|
||||
TimeUnit::Day => ts - Duration::days(ta.n as i64),
|
||||
TimeUnit::Week => ts - Duration::weeks(ta.n as i64),
|
||||
TimeUnit::Month => chronoutil::shift_months(ts, -(ta.n as i32)),
|
||||
TimeUnit::Year => chronoutil::shift_years(ts, -(ta.n as i32)),
|
||||
TimeUnit::Month => ts.replace_date(util::shift_months(ts.date(), -(ta.n as i32))),
|
||||
TimeUnit::Year => ts.replace_date(util::shift_years(ts.date(), -(ta.n as i32))),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<ParsedDate> for DateTime<Local> {
|
||||
impl From<ParsedDate> for OffsetDateTime {
|
||||
fn from(date: ParsedDate) -> Self {
|
||||
match date {
|
||||
ParsedDate::Absolute(date) => Local
|
||||
.from_local_datetime(&NaiveDateTime::new(date, NaiveTime::from_hms(0, 0, 0)))
|
||||
.unwrap(),
|
||||
ParsedDate::Absolute(date) => date.with_hms(0, 0, 0).unwrap().assume_utc(),
|
||||
ParsedDate::Relative(timeago) => timeago.into(),
|
||||
}
|
||||
}
|
||||
|
|
@ -180,7 +178,7 @@ pub fn parse_timeago(lang: Language, textual_date: &str) -> Option<TimeAgo> {
|
|||
/// Parse a TimeAgo string (e.g. "29 minutes ago") into a Chrono DateTime object.
|
||||
///
|
||||
/// Returns None if the date could not be parsed.
|
||||
pub fn parse_timeago_to_dt(lang: Language, textual_date: &str) -> Option<DateTime<Local>> {
|
||||
pub fn parse_timeago_to_dt(lang: Language, textual_date: &str) -> Option<OffsetDateTime> {
|
||||
parse_timeago(lang, textual_date).map(|ta| ta.into())
|
||||
}
|
||||
|
||||
|
|
@ -219,10 +217,9 @@ pub fn parse_textual_date(lang: Language, textual_date: &str) -> Option<ParsedDa
|
|||
}
|
||||
|
||||
match (y, m, d) {
|
||||
(Some(y), Some(m), Some(d)) => {
|
||||
NaiveDate::from_ymd_opt(y.into(), m.into(), d.into())
|
||||
.map(ParsedDate::Absolute)
|
||||
}
|
||||
(Some(y), Some(m), Some(d)) => util::month_from_n(m as u8)
|
||||
.and_then(|m| Date::from_calendar_date(y.into(), m, d as u8).ok())
|
||||
.map(ParsedDate::Absolute),
|
||||
_ => None,
|
||||
}
|
||||
} else {
|
||||
|
|
@ -236,7 +233,7 @@ pub fn parse_textual_date(lang: Language, textual_date: &str) -> Option<ParsedDa
|
|||
/// Parse a textual date (e.g. "29 minutes ago" or "Jul 2, 2014") into a Chrono DateTime object.
|
||||
///
|
||||
/// Returns None if the date could not be parsed.
|
||||
pub fn parse_textual_date_to_dt(lang: Language, textual_date: &str) -> Option<DateTime<Local>> {
|
||||
pub fn parse_textual_date_to_dt(lang: Language, textual_date: &str) -> Option<OffsetDateTime> {
|
||||
parse_textual_date(lang, textual_date).map(|ta| ta.into())
|
||||
}
|
||||
|
||||
|
|
@ -244,7 +241,7 @@ pub(crate) fn parse_textual_date_or_warn(
|
|||
lang: Language,
|
||||
textual_date: &str,
|
||||
warnings: &mut Vec<String>,
|
||||
) -> Option<DateTime<Local>> {
|
||||
) -> Option<OffsetDateTime> {
|
||||
let res = parse_textual_date_to_dt(lang, textual_date);
|
||||
if res.is_none() {
|
||||
warnings.push(format!("could not parse timeago `{}`", textual_date));
|
||||
|
|
@ -256,8 +253,8 @@ pub(crate) fn parse_textual_date_or_warn(
|
|||
mod tests {
|
||||
use std::{collections::BTreeMap, fs::File, io::BufReader, path::Path};
|
||||
|
||||
use chrono::Datelike;
|
||||
use rstest::rstest;
|
||||
use time::macros::{date, datetime};
|
||||
|
||||
use super::*;
|
||||
|
||||
|
|
@ -494,7 +491,7 @@ mod tests {
|
|||
#[case(
|
||||
Language::En,
|
||||
"Last updated on Jun 04, 2003",
|
||||
Some(ParsedDate::Absolute(NaiveDate::from_ymd(2003, 6, 4)))
|
||||
Some(ParsedDate::Absolute(date!(2003-6-4)))
|
||||
)]
|
||||
fn t_parse_date(
|
||||
#[case] lang: Language,
|
||||
|
|
@ -546,73 +543,73 @@ mod tests {
|
|||
);
|
||||
assert_eq!(
|
||||
parse_textual_date(*lang, samples.get("Jan").unwrap()),
|
||||
Some(ParsedDate::Absolute(NaiveDate::from_ymd(2020, 1, 3))),
|
||||
Some(ParsedDate::Absolute(date!(2020 - 1 - 3))),
|
||||
"lang: {}",
|
||||
lang
|
||||
);
|
||||
assert_eq!(
|
||||
parse_textual_date(*lang, samples.get("Feb").unwrap()),
|
||||
Some(ParsedDate::Absolute(NaiveDate::from_ymd(2016, 2, 7))),
|
||||
Some(ParsedDate::Absolute(date!(2016 - 2 - 7))),
|
||||
"lang: {}",
|
||||
lang
|
||||
);
|
||||
assert_eq!(
|
||||
parse_textual_date(*lang, samples.get("Mar").unwrap()),
|
||||
Some(ParsedDate::Absolute(NaiveDate::from_ymd(2015, 3, 9))),
|
||||
Some(ParsedDate::Absolute(date!(2015 - 3 - 9))),
|
||||
"lang: {}",
|
||||
lang
|
||||
);
|
||||
assert_eq!(
|
||||
parse_textual_date(*lang, samples.get("Apr").unwrap()),
|
||||
Some(ParsedDate::Absolute(NaiveDate::from_ymd(2017, 4, 2))),
|
||||
Some(ParsedDate::Absolute(date!(2017 - 4 - 2))),
|
||||
"lang: {}",
|
||||
lang
|
||||
);
|
||||
assert_eq!(
|
||||
parse_textual_date(*lang, samples.get("May").unwrap()),
|
||||
Some(ParsedDate::Absolute(NaiveDate::from_ymd(2014, 5, 22))),
|
||||
Some(ParsedDate::Absolute(date!(2014 - 5 - 22))),
|
||||
"lang: {}",
|
||||
lang
|
||||
);
|
||||
assert_eq!(
|
||||
parse_textual_date(*lang, samples.get("Jun").unwrap()),
|
||||
Some(ParsedDate::Absolute(NaiveDate::from_ymd(2014, 6, 28))),
|
||||
Some(ParsedDate::Absolute(date!(2014 - 6 - 28))),
|
||||
"lang: {}",
|
||||
lang
|
||||
);
|
||||
assert_eq!(
|
||||
parse_textual_date(*lang, samples.get("Jul").unwrap()),
|
||||
Some(ParsedDate::Absolute(NaiveDate::from_ymd(2014, 7, 2))),
|
||||
Some(ParsedDate::Absolute(date!(2014 - 7 - 2))),
|
||||
"lang: {}",
|
||||
lang
|
||||
);
|
||||
assert_eq!(
|
||||
parse_textual_date(*lang, samples.get("Aug").unwrap()),
|
||||
Some(ParsedDate::Absolute(NaiveDate::from_ymd(2015, 8, 23))),
|
||||
Some(ParsedDate::Absolute(date!(2015 - 8 - 23))),
|
||||
"lang: {}",
|
||||
lang
|
||||
);
|
||||
assert_eq!(
|
||||
parse_textual_date(*lang, samples.get("Sep").unwrap()),
|
||||
Some(ParsedDate::Absolute(NaiveDate::from_ymd(2018, 9, 16))),
|
||||
Some(ParsedDate::Absolute(date!(2018 - 9 - 16))),
|
||||
"lang: {}",
|
||||
lang
|
||||
);
|
||||
assert_eq!(
|
||||
parse_textual_date(*lang, samples.get("Oct").unwrap()),
|
||||
Some(ParsedDate::Absolute(NaiveDate::from_ymd(2014, 10, 31))),
|
||||
Some(ParsedDate::Absolute(date!(2014 - 10 - 31))),
|
||||
"lang: {}",
|
||||
lang
|
||||
);
|
||||
assert_eq!(
|
||||
parse_textual_date(*lang, samples.get("Nov").unwrap()),
|
||||
Some(ParsedDate::Absolute(NaiveDate::from_ymd(2016, 11, 3))),
|
||||
Some(ParsedDate::Absolute(date!(2016 - 11 - 3))),
|
||||
"lang: {}",
|
||||
lang
|
||||
);
|
||||
assert_eq!(
|
||||
parse_textual_date(*lang, samples.get("Dec").unwrap()),
|
||||
Some(ParsedDate::Absolute(NaiveDate::from_ymd(2021, 12, 24))),
|
||||
Some(ParsedDate::Absolute(date!(2021 - 12 - 24))),
|
||||
"lang: {}",
|
||||
lang
|
||||
);
|
||||
|
|
@ -623,19 +620,11 @@ mod tests {
|
|||
fn t_to_datetime() {
|
||||
// Absolute date
|
||||
let date = parse_textual_date_to_dt(Language::En, "Last updated on Jan 3, 2020").unwrap();
|
||||
assert_eq!(
|
||||
date,
|
||||
Local
|
||||
.from_local_datetime(&NaiveDateTime::new(
|
||||
NaiveDate::from_ymd(2020, 1, 3),
|
||||
NaiveTime::from_hms(0, 0, 0)
|
||||
))
|
||||
.unwrap()
|
||||
);
|
||||
assert_eq!(date, datetime!(2020-1-3 0:00 +0));
|
||||
|
||||
// Relative date
|
||||
let date = parse_textual_date_to_dt(Language::En, "1 year ago").unwrap();
|
||||
let now = Local::now();
|
||||
let now = OffsetDateTime::now_utc();
|
||||
assert_eq!(date.year(), now.year() - 1);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
44
src/util/date.rs
Normal file
44
src/util/date.rs
Normal file
|
|
@ -0,0 +1,44 @@
|
|||
use time::{Date, Month};
|
||||
|
||||
pub const fn month_from_n(n: u8) -> Option<Month> {
|
||||
match n {
|
||||
1 => Some(Month::January),
|
||||
2 => Some(Month::February),
|
||||
3 => Some(Month::March),
|
||||
4 => Some(Month::April),
|
||||
5 => Some(Month::May),
|
||||
6 => Some(Month::June),
|
||||
7 => Some(Month::July),
|
||||
8 => Some(Month::August),
|
||||
9 => Some(Month::September),
|
||||
10 => Some(Month::October),
|
||||
11 => Some(Month::November),
|
||||
12 => Some(Month::December),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Shift a date by the given number of months.
|
||||
/// Ambiguous month-ends are shifted backwards as necessary.
|
||||
pub fn shift_months(date: Date, months: i32) -> Date {
|
||||
let mut year = date.year() + (date.month() as i32 + months) / 12;
|
||||
let mut month = (date.month() as i32 + months) % 12;
|
||||
let mut day = date.day();
|
||||
|
||||
if month < 1 {
|
||||
year -= 1;
|
||||
month += 12;
|
||||
}
|
||||
|
||||
let month = month_from_n(month as u8).unwrap();
|
||||
let month_days = time::util::days_in_year_month(year, month);
|
||||
|
||||
day = day.min(month_days);
|
||||
Date::from_calendar_date(year, month, day).unwrap()
|
||||
}
|
||||
|
||||
/// Shift a date by the given number of years.
|
||||
/// Ambiguous month-ends are shifted backwards as necessary.
|
||||
pub fn shift_years(date: Date, years: i32) -> Date {
|
||||
shift_months(date, years * 12)
|
||||
}
|
||||
|
|
@ -1,7 +1,9 @@
|
|||
mod date;
|
||||
mod protobuf;
|
||||
|
||||
pub mod dictionary;
|
||||
|
||||
pub use date::{month_from_n, shift_months, shift_years};
|
||||
pub use protobuf::ProtoBuilder;
|
||||
|
||||
use std::{
|
||||
|
|
|
|||
|
|
@ -1,7 +1,8 @@
|
|||
use std::collections::HashSet;
|
||||
|
||||
use chrono::Datelike;
|
||||
use rstest::rstest;
|
||||
use time::macros::{date, datetime};
|
||||
use time::OffsetDateTime;
|
||||
|
||||
use rustypipe::client::{ClientType, RustyPipe};
|
||||
use rustypipe::error::{Error, ExtractionError};
|
||||
|
|
@ -417,9 +418,7 @@ async fn get_video_details() {
|
|||
);
|
||||
|
||||
let date = details.publish_date.unwrap();
|
||||
assert_eq!(date.year(), 2020);
|
||||
assert_eq!(date.month(), 11);
|
||||
assert_eq!(date.day(), 17);
|
||||
assert_eq!(date.date(), date!(2020 - 11 - 17));
|
||||
|
||||
assert!(!details.is_live);
|
||||
assert!(!details.is_ccommons);
|
||||
|
|
@ -470,9 +469,7 @@ async fn get_video_details_music() {
|
|||
);
|
||||
|
||||
let date = details.publish_date.unwrap();
|
||||
assert_eq!(date.year(), 2020);
|
||||
assert_eq!(date.month(), 8);
|
||||
assert_eq!(date.day(), 6);
|
||||
assert_eq!(date.date(), date!(2020 - 8 - 6));
|
||||
|
||||
assert!(!details.is_live);
|
||||
assert!(!details.is_ccommons);
|
||||
|
|
@ -528,9 +525,7 @@ async fn get_video_details_ccommons() {
|
|||
);
|
||||
|
||||
let date = details.publish_date.unwrap();
|
||||
assert_eq!(date.year(), 2019);
|
||||
assert_eq!(date.month(), 12);
|
||||
assert_eq!(date.day(), 29);
|
||||
assert_eq!(date.date(), date!(2019 - 12 - 29));
|
||||
|
||||
assert!(!details.is_live);
|
||||
assert!(details.is_ccommons);
|
||||
|
|
@ -585,9 +580,7 @@ async fn get_video_details_chapters() {
|
|||
);
|
||||
|
||||
let date = details.publish_date.unwrap();
|
||||
assert_eq!(date.year(), 2022);
|
||||
assert_eq!(date.month(), 9);
|
||||
assert_eq!(date.day(), 15);
|
||||
assert_eq!(date.date(), date!(2022 - 9 - 15));
|
||||
|
||||
assert!(!details.is_live);
|
||||
assert!(!details.is_ccommons);
|
||||
|
|
@ -728,9 +721,7 @@ async fn get_video_details_live() {
|
|||
);
|
||||
|
||||
let date = details.publish_date.unwrap();
|
||||
assert_eq!(date.year(), 2021);
|
||||
assert_eq!(date.month(), 9);
|
||||
assert_eq!(date.day(), 23);
|
||||
assert_eq!(date.date(), date!(2021 - 9 - 23));
|
||||
|
||||
assert!(details.is_live);
|
||||
assert!(!details.is_ccommons);
|
||||
|
|
@ -777,9 +768,7 @@ async fn get_video_details_agegate() {
|
|||
assert!(details.like_count.is_none(), "like count not hidden");
|
||||
|
||||
let date = details.publish_date.unwrap();
|
||||
assert_eq!(date.year(), 2019);
|
||||
assert_eq!(date.month(), 1);
|
||||
assert_eq!(date.day(), 2);
|
||||
assert_eq!(date.date(), date!(2019 - 1 - 2));
|
||||
|
||||
assert!(!details.is_live);
|
||||
assert!(!details.is_ccommons);
|
||||
|
|
@ -885,7 +874,7 @@ async fn channel_videos(#[case] order: ChannelOrder) {
|
|||
|
||||
let first_video = &channel.content.items[0];
|
||||
let first_video_date = first_video.publish_date.unwrap();
|
||||
let age_days = (chrono::Local::now() - first_video_date).num_days();
|
||||
let age_days = (OffsetDateTime::now_utc() - first_video_date).whole_days();
|
||||
|
||||
match order {
|
||||
ChannelOrder::Latest => {
|
||||
|
|
@ -946,9 +935,7 @@ async fn channel_info() {
|
|||
assert_channel_eevblog(&channel);
|
||||
|
||||
let created = channel.content.create_date.unwrap();
|
||||
assert_eq!(created.year(), 2009);
|
||||
assert_eq!(created.month(), 4);
|
||||
assert_eq!(created.day(), 4);
|
||||
assert_eq!(created, date!(2009 - 4 - 4));
|
||||
|
||||
assert!(
|
||||
channel.content.view_count.unwrap() > 186854340,
|
||||
|
|
@ -1072,8 +1059,6 @@ async fn channel_not_found(#[case] id: &str) {
|
|||
mod channel_rss {
|
||||
use super::*;
|
||||
|
||||
use chrono::Timelike;
|
||||
|
||||
#[tokio::test]
|
||||
async fn get_channel_rss() {
|
||||
let rp = RustyPipe::builder().strict().build();
|
||||
|
|
@ -1085,11 +1070,7 @@ mod channel_rss {
|
|||
|
||||
assert_eq!(channel.id, "UCHnyfMqiRRG1u-2MsSQLbXA");
|
||||
assert_eq!(channel.name, "Veritasium");
|
||||
assert_eq!(channel.create_date.year(), 2010);
|
||||
assert_eq!(channel.create_date.month(), 7);
|
||||
assert_eq!(channel.create_date.day(), 21);
|
||||
assert_eq!(channel.create_date.hour(), 7);
|
||||
assert_eq!(channel.create_date.minute(), 18);
|
||||
assert_eq!(channel.create_date, datetime!(2010-07-21 7:18:02 +0));
|
||||
|
||||
assert!(!channel.videos.is_empty());
|
||||
}
|
||||
|
|
|
|||
Reference in a new issue