Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement Duration::num_years() #416

Closed
clawoflight opened this issue May 2, 2020 · 13 comments
Closed

Implement Duration::num_years() #416

clawoflight opened this issue May 2, 2020 · 13 comments

Comments

@clawoflight
Copy link

clawoflight commented May 2, 2020

It's not entirely trivial to calculate without time zone information, but it would be nice to have.

@azzamsa
Copy link

azzamsa commented Aug 12, 2020

num_years, num_months is nice to have.

So we could have this:

# python
print(f"Your age: {diff.years} years, {diff.months} months, {diff.days} days")

@quodlibetor
Copy link
Contributor

I would definitely take a PR for this.

@azzamsa
Copy link

azzamsa commented Aug 12, 2020

Thank you for taking the step.

I've asked about the situation here and here.

Some people in the discord said that chrono doesn't implement this because there is no single answer for how many days in year, and how many days in months. But, other popular datetime libraries handle them already. Such python datetime std or Pendulum of Python, Moment of js, or Joda of Java.


I agree that we should have num_years and num_months. Let the chrono handle this. because writing them manually requires so much time and knowledge (such as 356 or 366 days in a year, 28 or 30 or 31 in a month).

Yesterday I try to port some of my daily utilities to Rust. One of them is birthday utilities.

#!/usr/bin/env python3
import datetime
from dateutil.relativedelta import relativedelta

current_dt = datetime.datetime.now()
birth_date = datetime.datetime.strptime("2000-11-28", "%Y-%m-%d")
birth_date_formatted = birth_date.strftime("%A, %d %B %Y ")

diff = relativedelta(current_dt, birth_date)  # <---
print(f"Your age: {diff.years} years, {diff.months} months, {diff.days} days") # <---

Take a look at the two last lines. I need more than 2 hours to figure it out in rust. If only we had num_years and num_months. it will take only less than half an hour.

Thank you

@arossbell
Copy link

arossbell commented Sep 6, 2020

Would num_months and num_years both return i64 types (rather than float types)?
(It appears that dateutil.relativedelta acts like that, but making certain for this ticket.)

Nevermind on that question. I was in the process of trying to put together some code to cover both num_months and num_years (one idea somewhat coming from what dateutil's relativedelta does here), but the code couldn't fit in with Duration (where other things like num_days are) -- and it doesn't make sense to have this code be elsewhere.

It seems like one of the following is necessary for a num_years and/or a num_months to work in chrono:

  1. Duration has the start and end Dates/DateTimes within the struct; or
  2. chrono standardizes what a month and year are relative to what a second is.

Either of those are fairly large changes to chrono based off of what's already written.

Thoughts?

@gpgkd906
Copy link

gpgkd906 commented Feb 19, 2021

It hard to calculate months and years with only Duration because leap years...
so I just create a simple crate for this...
maybe helps?

https://crates.io/crates/date_component

use chrono::prelude::*;
use date_component::date_component;

fn main() {
    let date1 = Utc.ymd(2015, 4, 20).and_hms(0, 0, 0);
    let date2 =  Utc.ymd(2015, 12, 19).and_hms(0, 0, 0);
    
    let date_interval = date_component::calculate(&date1, &date2);
    println!("{:?}", date_interval);
}
// DateComponent { year: 0, month: 7, day: 29, interval_day: 243 }

@azzamsa
Copy link

azzamsa commented Feb 23, 2021

I use this code in the end.

fn diff(date1: Date<Local>, date2: Date<Local>) -> (i32, i32, i32) {
    // ported from icza/gox/timex.go
    let y1 = date1.year();
    let m1 = date1.month();
    let d1 = date1.day();

    let y2 = date2.year();
    let m2 = date2.month();
    let d2 = date2.day();

    let mut year = y1 as i32 - y2 as i32;
    let mut month = m1 as i32 - m2 as i32;
    let mut day = d1 as i32 - d2 as i32;

    // Normalize negative values
    let days_in_m1 = get_days_from_month(y1, m1);
    if day < 0 {
        // days in month:
        let t = Local.ymd(y1, m1, days_in_m1 as u32);
        day += days_in_m1 - t.day() as i32;
        month -= 1;
    }
    if month < 0 {
        month += 12;
        year -= 1;
    }

    return (year, month, day.abs());
}

@Rafael-Conde
Copy link

Hello People! I would like to see the functionality that date_component provides in chrono, is something like that already under implementation or on the library? I checked the docs but I couldn't find anything. I'm interested on implementing it, if there isn't already.

As far as I saw in PR and issues, this functionality fits the library, right? please let me know if I'm misunderstanding the goals.

@esheppa
Copy link
Collaborator

esheppa commented Nov 21, 2022

Hi @Rafael-Conde if you are willing to do a PR that would be great. Initially just adding a 'Years' data type that works similarly to the existing Days and Months would be great.

@Rafael-Conde
Copy link

Rafael-Conde commented Nov 22, 2022

Ok @esheppa ! I'll try to implement that then! Thank you for pointing it out!

I was reading the implementation for Days and Months, which I believe were the types you were talking about and I tried to grasp what would be necessary for the Years type, I came up with this, is this what you guys had in mind?

I feel like I'm missing something but I can't figure out what it is.

// I'm trying to focus on the necesesary methods
// but later I'll add proper privacy for the functions
// I'll also add the proper implementations

struct Years(i64);

// would to the same for all the others
impl<Tz: TimeZone> DateTime<Tz> {
    fn add_years(&mut self, num_years: Years) {}
}

impl Add<Years> for NaiveDate {}

impl Add<Years> for NaiveDateTime {}

impl Add<Years, Tz: TimeZone> for DateTime<Tz> {}

impl Sub<Years> For NaiveDate {}

impl Sub<Years> for NaiveDateTime {}

impl Sub<Years, Tz: TimeZone> For DateTime<Tz> {}

// I'm not entirely sure about this one
#[cfg(feature = "serde")]
mod years_serde
{
    impl ser::Serialize for Years {
        fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
            where
               S: ser::Serializer,
            {}
        }
}

I believe Years should be simpler than Months and Days, since it doesn't influence in the months and days as the other types do.

I might take some time to digest everything, the library is quite extended at this point and getting the overall picture for me may take time.

Edit: I'll implement that and open the PR, prolly till the end of the week I'll be opening it.

@esheppa
Copy link
Collaborator

esheppa commented Nov 26, 2022

Thanks @Rafael-Conde - that is looking pretty good. A couple things to note:

// I'm not entirely sure about this one
#[cfg(feature = "serde")]

I don't think we have this for Months or Days, we can probably skip it here and add later if it is requested.

fn add_years(&mut self, num_years: Years) {}

We generally take &self or self given that most of the types are Copy anyway. Also this should probably be checked_add_years/checked_sub_years

struct Years(i64);

This should be an unsigned type. Probably u32 or u16

@gitmalong
Copy link

I'ld like to see this implemented for https://crates.io/crates/date_component but with generic TimeZones. https://crates.io/crates/date_component does only support Utc.

@pitdicker
Copy link
Collaborator

@gitmalong Could you comment if diff_months_days in #1247 would be enough for your use case?

@pitdicker
Copy link
Collaborator

#1282 is now the issue to track for a duration type with more features.

@pitdicker pitdicker closed this as not planned Won't fix, can't repro, duplicate, stale Sep 12, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

9 participants