5
\$\begingroup\$

I wrote a silly Rust program inspired by a WolframAlpha query. I know, that's nothing serious, but if there's anything that could be made more, well, proper Rust-like, I'd love to know! Of course, general feedback is welcomed too.

For starters, here's my Cargo.toml:

[package]
name = "rusted-days"
version = "0.1.0"
authors = ["baduker"]
edition = "2018"

[dependencies]
chrono = "0.4.19"

And the code:

use std::io;
use std::io::Write;
use chrono::{NaiveDateTime, DateTime, Utc, Datelike};

fn main() {
    let prompt = "What's your date (DD/MM/YYYY)?: ";
    let date = input(prompt).expect("Something went wrong! o.O");
    show_interpretation(parse_date(date));
}

fn input(user_input: &str) -> io::Result<String> {
    print!("{}", user_input);
    io::stdout().flush()?;

    let mut buffer: String = String::new();
    io::stdin().read_line(&mut buffer)?;

    Ok(buffer.trim_end().to_owned())
}

fn parse_date(date: String) -> DateTime<Utc> {
    let naive_date = chrono::NaiveDate::parse_from_str(
        date.as_str(), "%d/%m/%Y"
    ).unwrap();

    let naive_datetime: NaiveDateTime = naive_date
        .and_hms(0, 0, 0);

    DateTime::<Utc>::from_utc(naive_datetime, Utc)
}

fn show_interpretation(date: DateTime<Utc>) {
    println!("Input interpretation: days since {}", date.format("%A, %B %d, %Y"));

    let total_days = Utc::now() - date;
    println!("Result:\n{} days have rusted away ¯\\_(ツ)_/¯", total_days.num_days());

    let years = Utc::now().year() - date.year();
    let months = Utc::now().month() - date.month();
    let days = Utc::now().day() - date.day();

    println!("Timespan:\n{} year(s), {} month(s), {} day(s)", years, months, days);
    println!("{} weeks", total_days.num_weeks());
    println!("{:.2} years", total_days.num_days() as f32 / 365_f32);
}

A sample output (you should get):

What's your date (DD/MM/YYYY): 01/01/1970
Input interpretation: days since Thursday, January 01, 1970
Result:
18567 days have rusted away ¯\_(ツ)_/¯
Timespan:
50 years, 10 month(s), 0 days
2652 weeks
50.87 years
\$\endgroup\$
2
\$\begingroup\$

I can't see any issues in terms of making your code idiomatic; it looks very Rust-like! I did however notice a problem with your code's logic.

Integer overflow

I tried inputting 31/12/2019 and obtained a panic when I ran it today (07/11/2020). This was because Utc::now().month() is a u32 equal to 11, and the code attempts to subtract 12:

let months = Utc::now().month() - date.month();

This leads to an overflow because we're using unsigned integers. The same story applies for the day, which is also a u32, but not the year, which is an i32 and so functions fine.

Even if we convert the relevant types to signed integers, the following isn't what you mean:

let years = Utc::now().year() - date.year();

This would tell you that 31/12/2019 and 1/1/2020 are a year apart, when in fact this isn't true. Even dividing the number of days by 365 isn't quite right because of leap years.

Making the date logic work correctly is a lot more frustrating than it sounds: the number of months passed depends on the length of each month as well as the Duration instance. That's why Duration doesn't implement anything beyond num_weeks(): it's simply a difference between two dates, so it doesn't know how to account for the varying month lengths.

\$\endgroup\$
3
  • 1
    \$\begingroup\$ Thanks for the feedback! Glad to hear the code is proper Rust-like. Not so great to learn I've missed a few things. Guess, it's time to learn writing tests in Rust. :) By the way, any tips on dealing with the overflow issue? \$\endgroup\$
    – baduker
    Nov 7 '20 at 19:04
  • 1
    \$\begingroup\$ No problem, @baduker. As far as stopping the overflows, you can just cast Utc::now().month() etc to i64 and your code won't panic any more, for example let month = Utc::now().month() as i64 - date.month(). This doesn't solve your issue though, because then this says that the difference between 31/12/2019 and 31/10/2020 is 1 year, -2 months. I am not sure if the relativedelta crate might be helpful? I've not used it and just reviewed the documentation, but it might be able to do what you need. \$\endgroup\$
    – htl
    Nov 7 '20 at 19:25
  • 1
    \$\begingroup\$ Right, getting rid of overflow still doesn't remedy the date delta issue. I'll poke around the crate and if I can't find what I need, I'll take this to Stackoverflow. \$\endgroup\$
    – baduker
    Nov 7 '20 at 19:38

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.