Open the-kenny opened 8 years ago
Duplicate with #29. See my comment for the workaround; .pred()
is a key method. I believe this is not a good fit for Chrono, as i) the calendar system is fixed and ii) other similar libraries don't seem to have such features (especially for those where the calendar system is fixed).
Don't mean to jump into an old issue here, but I was going to ask the same question.
@lifthrasiir I understand and respect where you're coming from, but I'm not so sure I agree that chrono wouldn't be a good place to have such a function.
First of all, other libraries across other languages do indeed provide such utilities. To list a few ones I know of:
C++ (Boost library):
date dt(y, m, d);
date eom = dt.end_of_month_day();
gregorian_calendar::is_leap_year(y);
C# (built-in):
DateTime.DaysInMonth(y, m);
DateTime.IsLeapYear(y);
Ruby on Rails (built-in):
Time.days_in_month(m, y)
Time.days_in_year(y)
Python (built-in, calendar module):
calendar.monthrange(y, m)
calendar.isleap(y)
Java (built-in):
Calendar.getInstance().getActualMaximum(Calendar.DAY_OF_MONTH);
// No equivalent for isLeapYear(y)
JavaScript (Datejs library):
Date.getDaysInMonth(y, m)
Date.isLeapYear(y)
JavaScript (moment library):
moment('yyyy-mm', 'YYYY-MM').daysInMonth()
moment([y]).isLeapYear()
However, to play devil's advocate, some languages indeed don't provide such a utility, namely Go, Swift, and Objective-C.
I did see the workaround, though you mentioned it may not perform well. I just don't know enough (still absorbing Rust!) to know how "badly" it performs. Is it really that much of a performance/memory issue?
All said, I really think that get_days_in_month(month, year)
and is_leap_year(year)
would be a good addition. But I have an open mind if you have a better idea. Maybe I could write my first library with only 2 functions? :stuck_out_tongue_winking_eye:
@lifthrasiir does the fact that chrono is tied to a specific calendar system make this functionality seem too trivial to be worth implementing, or does it seem wrong because it will require too much refactoring if/when it gets pluggable calendar systems?
I can imagine refactoring our Month
implementation to a Month
enum with a MonthLike
trait that implements a variety of utility methods, including those requested here.
(In addition to the libs @vsimonian mentioned, JSR310 and JodaTime implement these methods.)
@lifthrasiir does the fact that chrono is tied to a specific calendar system make this functionality seem too trivial to be worth implementing, or does it seem wrong because it will require too much refactoring if/when it gets pluggable calendar systems?
Close to the former, but more like the fact that there are plenty of helper methods that will be too trivial or too specific to be included in the generic library, and so far I have avoided them in favor of code samples in the documentation. But that is just my personal preference and this request seems to be more popular than others (e.g. Julian Days).
As you have mentioned, with a specific type like Month
(I'm personally for YearMonth
though) having utility methods is much more doable because the method can be applied to the correct type. Guess that this general direction will also solve #152 as well.
YearMonth being something like enum YearMonth { Jan(year: i32), ...}
? I can see the appeal of that, it would certainly be more correct and more flexible.
On the other hand something like struct YearMonth { year: Year(i32), month: Month }
might allow a more fine-grained distribution of methods?
We might be able to experiment with enhanced ergonomic APIs behind an unstable
feature. I will say that I recently had to do a large amount of datetime manipulation using the new java 8 "time" APIs and the extensive API surface made for some very surprisingly elegant functions.
You can add this functionality for your project with:
trait NaiveDateExt {
fn days_in_month(&self) -> i32;
fn days_in_year(&self) -> i32;
fn is_leap_year(&self) -> bool;
}
impl NaiveDateExt for chrono::NaiveDate {
fn days_in_month(&self) -> i32 {
let month = self.month();
match month {
1 | 3 | 5 | 7 | 8 | 10 | 12 => 31,
4 | 6 | 9 | 11 => 30,
2 => if self.is_leap_year() { 29 } else { 28 },
_ => panic!("Invalid month: {}" , month),
}
}
fn days_in_year(&self) -> i32 {
if self.is_leap_year() { 366 } else { 365 }
}
fn is_leap_year(&self) -> bool {
let year = self.year();
return year % 4 == 0 && (year % 100 != 0 || year % 400 == 0)
}
}
I am not sure yet about the best API for something like days_in_month()
. In one case (the NaiveDate::diff_months_days()
method in https://github.com/chronotope/chrono/pull/1247) it was not enough for me to know the number of days in the current month, it needed the number in the previous month. https://github.com/chronotope/chrono/issues/69#issuecomment-311495955 suggests adding a type such as YearMonth
and implementing the method on that would be more flexible, and https://github.com/chronotope/chrono/issues/203 also requests such a type.
For some calculations it's useful to get the number of days in the current month.
For example, I want to get the first and last day in a given month in my current project. The former is possible via
.with_day(1)
, but the latter isn't easily possible (or at least I don't see it)