Skip to content

Commit

Permalink
streak time periods in responses
Browse files Browse the repository at this point in the history
  • Loading branch information
akorchyn committed Jun 20, 2024
1 parent f80df69 commit f210d73
Show file tree
Hide file tree
Showing 3 changed files with 301 additions and 26 deletions.
58 changes: 33 additions & 25 deletions server/src/entrypoints/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,9 @@ pub struct Streak {
streak_type: String,
current: u32,
longest: u32,
achived: bool,
start_time: chrono::DateTime<chrono::Utc>,
end_time: chrono::DateTime<chrono::Utc>,
}

impl Streak {
Expand All @@ -129,23 +132,31 @@ impl Streak {
.unwrap_or_default();
let current_time_string = time_period.time_string(current_time as u64);
let previous_period_string = time_period.time_string(previous_period);
if current_time_string == time_period_string
|| previous_period_string == time_period_string
{
return Self {
name,
streak_type,
current,
longest,
};

let mut result = Self {
name,
streak_type,
current,
longest,
achived: time_period_string == current_time_string,
start_time: time_period
.start_period(current_time as u64)
.unwrap_or_default(),
end_time: time_period
.end_period(current_time as u64)
.unwrap_or_default(),
};
}

Self {
name,
streak_type,
current: 0,
longest,
if current_time_string != time_period_string
&& previous_period_string != time_period_string
{
result.current = 0;
}

result
} else {
// TODO: probably we need to return error here for user request
Self::default()
}
}
}
Expand All @@ -156,7 +167,7 @@ pub struct UserProfile {
pub rating: u32,
pub contributions: u32,
pub leaderboard_places: HashMap<String, u32>,
pub streaks: HashMap<String, Streak>,
pub streaks: Vec<Streak>,
}

impl From<UserRecord> for UserProfile {
Expand All @@ -175,15 +186,12 @@ impl From<UserRecord> for UserProfile {
.streaks
.into_iter()
.map(|streak| {
(
streak.name.clone(),
Streak::new(
streak.name,
streak.amount as u32,
streak.best as u32,
streak.latest_time_string,
streak.streak_type,
),
Streak::new(
streak.name,
streak.amount as u32,
streak.best as u32,
streak.latest_time_string,
streak.streak_type,
)
})
.collect(),
Expand Down
1 change: 1 addition & 0 deletions shared/src/pr.rs
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,7 @@ impl PRWithRating {
let percentage = (self.percentage_multiplier + 100) as f64;
((score as f64 * percentage / 100.0).ceil()) as u32
}

pub fn score(&self) -> Option<u32> {
self.score
.iter()
Expand Down
268 changes: 267 additions & 1 deletion shared/src/timeperiod.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use chrono::{DateTime, Datelike, Days, Months};
use chrono::{DateTime, Datelike, Days, Months, NaiveDate, TimeZone, Utc};
use near_sdk::Timestamp;
use strum::EnumIter;

Expand Down Expand Up @@ -80,6 +80,69 @@ impl TimePeriod {

Some(result)
}

pub fn end_period(&self, timestamp: Timestamp) -> Option<chrono::DateTime<Utc>> {
let date_time = DateTime::<Utc>::from_timestamp_nanos(timestamp as i64).date_naive();

match self {
TimePeriod::Day => (date_time + chrono::Duration::days(1))
.and_hms_opt(0, 0, 0)
.map(|d| d.and_utc()),
TimePeriod::Week => {
let iso_week = date_time.iso_week();
NaiveDate::from_isoywd_opt(
iso_week.year(),
iso_week.week() + 1,
chrono::Weekday::Mon,
)
.and_then(|d| d.and_hms_opt(0, 0, 0).map(|d| d.and_utc()))
}
TimePeriod::Month => if date_time.month() == 12 {
Utc.with_ymd_and_hms(date_time.year() + 1, 1, 1, 0, 0, 0)
} else {
Utc.with_ymd_and_hms(date_time.year(), date_time.month() + 1, 1, 0, 0, 0)
}
.earliest(),
TimePeriod::Quarter => {
let current_quarter = (date_time.month() - 1) / 3 + 1;
if current_quarter == 4 {
Utc.with_ymd_and_hms(date_time.year() + 1, 1, 1, 0, 0, 0)
} else {
Utc.with_ymd_and_hms(date_time.year(), current_quarter * 3 + 1, 1, 0, 0, 0)
}
.earliest()
}
TimePeriod::Year => Utc
.with_ymd_and_hms(date_time.year() + 1, 1, 1, 0, 0, 0)
.earliest(),
TimePeriod::AllTime => None,
}
}

pub fn start_period(&self, timestamp: Timestamp) -> Option<chrono::DateTime<Utc>> {
let date_time = DateTime::<Utc>::from_timestamp_nanos(timestamp as i64).date_naive();

match self {
TimePeriod::Day => date_time.and_hms_opt(0, 0, 0).map(|d| d.and_utc()),
TimePeriod::Week => {
let iso_week = date_time.iso_week();
NaiveDate::from_isoywd_opt(iso_week.year(), iso_week.week(), chrono::Weekday::Mon)
.and_then(|d| d.and_hms_opt(0, 0, 0).map(|d| d.and_utc()))
}
TimePeriod::Month => Utc
.with_ymd_and_hms(date_time.year(), date_time.month(), 1, 0, 0, 0)
.earliest(),
TimePeriod::Quarter => {
let current_quarter = (date_time.month() - 1) / 3 + 1;
Utc.with_ymd_and_hms(date_time.year(), current_quarter * 3 - 2, 1, 0, 0, 0)
.earliest()
}
TimePeriod::Year => Utc
.with_ymd_and_hms(date_time.year(), 1, 1, 0, 0, 0)
.earliest(),
TimePeriod::AllTime => None,
}
}
}

// Helper function to convert timestamp to quarter string
Expand All @@ -103,3 +166,206 @@ fn timestamp_to_month_string(timestamp: u64) -> TimePeriodString {
let date = DateTime::from_timestamp_nanos(timestamp as i64);
format!("{:02}{:04}", date.month(), date.year())
}

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

#[test]
fn test_from_streak_type() {
assert_eq!(TimePeriod::from_streak_type("daily"), Some(TimePeriod::Day));
assert_eq!(
TimePeriod::from_streak_type("weekly"),
Some(TimePeriod::Week)
);
assert_eq!(
TimePeriod::from_streak_type("monthly"),
Some(TimePeriod::Month)
);
assert_eq!(
TimePeriod::from_streak_type("quarterly"),
Some(TimePeriod::Quarter)
);
assert_eq!(
TimePeriod::from_streak_type("yearly"),
Some(TimePeriod::Year)
);
assert_eq!(
TimePeriod::from_streak_type("all-time"),
Some(TimePeriod::AllTime)
);
assert_eq!(TimePeriod::from_streak_type("unknown"), None);
}

#[test]
fn test_time_string() {
let timestamp = Utc
.with_ymd_and_hms(2023, 6, 20, 0, 0, 0)
.unwrap()
.timestamp_nanos_opt()
.unwrap() as Timestamp;

assert_eq!(TimePeriod::Day.time_string(timestamp), "20062023");
assert_eq!(TimePeriod::Week.time_string(timestamp), "2023W25");
assert_eq!(TimePeriod::Month.time_string(timestamp), "062023");
assert_eq!(TimePeriod::Quarter.time_string(timestamp), "2023Q2");
assert_eq!(TimePeriod::Year.time_string(timestamp), "2023");
assert_eq!(
TimePeriod::AllTime.time_string(timestamp),
"all-time".to_string()
);
}

#[test]
fn test_previous_period() {
let timestamp = Utc
.with_ymd_and_hms(2023, 6, 20, 0, 0, 0)
.unwrap()
.timestamp_nanos_opt()
.unwrap() as Timestamp;

let previous_day = Utc
.with_ymd_and_hms(2023, 6, 19, 0, 0, 0)
.unwrap()
.timestamp_nanos_opt()
.unwrap() as Timestamp;
assert_eq!(
TimePeriod::Day.previous_period(timestamp),
Some(previous_day)
);

let previous_week = Utc
.with_ymd_and_hms(2023, 6, 13, 0, 0, 0)
.unwrap()
.timestamp_nanos_opt()
.unwrap() as Timestamp;
assert_eq!(
TimePeriod::Week.previous_period(timestamp),
Some(previous_week)
);

let previous_month = Utc
.with_ymd_and_hms(2023, 5, 20, 0, 0, 0)
.unwrap()
.timestamp_nanos_opt()
.unwrap() as Timestamp;
assert_eq!(
TimePeriod::Month.previous_period(timestamp),
Some(previous_month)
);

let previous_quarter = Utc
.with_ymd_and_hms(2023, 3, 20, 0, 0, 0)
.unwrap()
.timestamp_nanos_opt()
.unwrap() as Timestamp;
assert_eq!(
TimePeriod::Quarter.previous_period(timestamp),
Some(previous_quarter)
);

let previous_year = Utc
.with_ymd_and_hms(2022, 6, 20, 0, 0, 0)
.unwrap()
.timestamp_nanos_opt()
.unwrap() as Timestamp;
assert_eq!(
TimePeriod::Year.previous_period(timestamp),
Some(previous_year)
);

assert_eq!(TimePeriod::AllTime.previous_period(timestamp), None);
}

#[test]
fn test_timestamp_to_day_string() {
let timestamp = Utc
.with_ymd_and_hms(2023, 6, 20, 0, 0, 0)
.unwrap()
.timestamp_nanos_opt()
.unwrap() as Timestamp;
assert_eq!(timestamp_to_day_string(timestamp), "20062023");
}

#[test]
fn test_timestamp_to_week_string() {
let timestamp = Utc
.with_ymd_and_hms(2023, 6, 20, 0, 0, 0)
.unwrap()
.timestamp_nanos_opt()
.unwrap() as Timestamp;
assert_eq!(timestamp_to_week_string(timestamp), "2023W25");
}

#[test]
fn test_timestamp_to_quarter_string() {
let timestamp = Utc
.with_ymd_and_hms(2023, 6, 20, 0, 0, 0)
.unwrap()
.timestamp_nanos_opt()
.unwrap() as Timestamp;
assert_eq!(timestamp_to_quarter_string(timestamp), "2023Q2");
}

#[test]
fn test_timestamp_to_month_string() {
let timestamp = Utc
.with_ymd_and_hms(2023, 6, 20, 0, 0, 0)
.unwrap()
.timestamp_nanos_opt()
.unwrap() as Timestamp;
assert_eq!(timestamp_to_month_string(timestamp), "062023");
}

#[test]
fn test_end_period() {
let timestamp = Utc
.with_ymd_and_hms(2023, 5, 16, 5, 13, 0)
.unwrap()
.timestamp_nanos_opt()
.unwrap() as Timestamp;

let end_day = Utc.with_ymd_and_hms(2023, 5, 17, 0, 0, 0).unwrap();
assert_eq!(TimePeriod::Day.end_period(timestamp), Some(end_day));

let end_week = Utc.with_ymd_and_hms(2023, 5, 22, 0, 0, 0).unwrap();
assert_eq!(TimePeriod::Week.end_period(timestamp), Some(end_week));

let end_month = Utc.with_ymd_and_hms(2023, 6, 1, 0, 0, 0).unwrap();
assert_eq!(TimePeriod::Month.end_period(timestamp), Some(end_month));

let end_quarter = Utc.with_ymd_and_hms(2023, 7, 1, 0, 0, 0).unwrap();
assert_eq!(TimePeriod::Quarter.end_period(timestamp), Some(end_quarter));

let end_year = Utc.with_ymd_and_hms(2024, 1, 1, 0, 0, 0).unwrap();
assert_eq!(TimePeriod::Year.end_period(timestamp), Some(end_year));
}

#[test]
fn test_start_period() {
let timestamp = Utc
.with_ymd_and_hms(2023, 5, 16, 5, 13, 0)
.unwrap()
.timestamp_nanos_opt()
.unwrap() as Timestamp;

let start_day = Utc.with_ymd_and_hms(2023, 5, 16, 0, 0, 0).unwrap();
assert_eq!(TimePeriod::Day.start_period(timestamp), Some(start_day));

let start_week = Utc.with_ymd_and_hms(2023, 5, 15, 0, 0, 0).unwrap();
assert_eq!(TimePeriod::Week.start_period(timestamp), Some(start_week));

let start_month = Utc.with_ymd_and_hms(2023, 5, 1, 0, 0, 0).unwrap();
assert_eq!(TimePeriod::Month.start_period(timestamp), Some(start_month));

let start_quarter = Utc.with_ymd_and_hms(2023, 4, 1, 0, 0, 0).unwrap();
assert_eq!(
TimePeriod::Quarter.start_period(timestamp),
Some(start_quarter)
);

let start_year = Utc.with_ymd_and_hms(2023, 1, 1, 0, 0, 0).unwrap();
assert_eq!(TimePeriod::Year.start_period(timestamp), Some(start_year));
}
}

0 comments on commit f210d73

Please sign in to comment.