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