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

fix for issue #198 (take 3) #202

Merged
merged 37 commits into from
Oct 27, 2020
Merged

fix for issue #198 (take 3) #202

merged 37 commits into from
Oct 27, 2020

Conversation

jswhit
Copy link
Collaborator

@jswhit jswhit commented Sep 28, 2020

This approach removes the use of sub-classes to create 'calendar-aware' cftime.datetime instances. Instead, the cftime.datetime base class uses the calendar kwarg to create 'calendar-aware' instances. The sub-classes are no longer used internally, but retained for backwards compatibility. The disadvantage of this approach is that user code which uses issubclass may break.

@mcgibbon
Copy link
Contributor

mcgibbon commented Sep 29, 2020

I like this solution! It looks like it shouldn't break existing user issubclass code, unless/until they move to using datetime as the class instantiator instead of the calendar-specific class.

@spencerkclark
Copy link
Collaborator

Wow, thanks for your work on this @jswhit! I agree with @mcgibbon that this is a nice solution. I'd be happy to give it a closer review if you feel it's ready -- just let me know.

@jswhit2
Copy link
Contributor

jswhit2 commented Oct 9, 2020

@spencerkclark thanks for offering to review - I think it is ready

Copy link
Collaborator

@spencerkclark spencerkclark left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks great @jswhit. I mostly have just cosmetic comments. One general question -- is there a strong reason why the default value for the calendar argument in the cftime.datetime constructor is 'standard'? As I understand it, we could make things completely backwards compatible if it was ''.

Comment on lines +321 to +340
#def to_calendar_specific_datetime(dt, calendar, use_python_datetime):
# if use_python_datetime:
# return real_datetime(
# dt.year,
# dt.month,
# dt.day,
# dt.hour,
# dt.minute,
# dt.second,
# dt.microsecond)
# else:
# return datetime(
# dt.year,
# dt.month,
# dt.day,
# dt.hour,
# dt.minute,
# dt.second,
# dt.microsecond,
# calendar=calendar)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should this be deleted now?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The commented out version does not return the calendar-specific subclasses. Since we might want to remove those sub-classes in a future release, I left it in there.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Gotcha -- it makes sense to leave it in then.

@@ -1554,7 +1520,9 @@ def test_num2date_only_use_cftime_datetimes_negative_years(
calendar, expected_date_type):
result = num2date(-1000., units='days since 0001-01-01', calendar=calendar,
only_use_cftime_datetimes=True)
assert isinstance(result, expected_date_type)
#assert isinstance(result, expected_date_type)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
#assert isinstance(result, expected_date_type)

@@ -1565,7 +1533,9 @@ def test_num2date_only_use_cftime_datetimes_pre_gregorian(
calendar, expected_date_type):
result = num2date(1., units='days since 0001-01-01', calendar=calendar,
only_use_cftime_datetimes=True)
assert isinstance(result, expected_date_type)
#assert isinstance(result, expected_date_type)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
#assert isinstance(result, expected_date_type)

test/test_cftime.py Outdated Show resolved Hide resolved
test/test_cftime.py Outdated Show resolved Hide resolved
# return calendar-specific subclasses for backward compatbility,
# even though after 1.3.0 this is no longer necessary.
if calendar == '360_day':
#return dt.__class__(*add_timedelta_360_day(dt, delta),calendar=calendar)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are these commented lines relevant?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I left those there in case we decide to remove the calendar-specific subclasses. They are really not needed, but I use them here just in case some is expected them to be returned by __add__ and __sub__ (since that was the previous behavior).

self.datetime_compatible = True
assert_valid_date(self, is_leap_proleptic_gregorian, False)
self.has_year_zero = False
elif calendar == '':
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there a reason '' was chosen instead of None?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Probably, but the reason has been lost in the fog of history. I think @davidhassell may have suggested this.

@jswhit
Copy link
Collaborator Author

jswhit commented Oct 13, 2020

This looks great @jswhit. I mostly have just cosmetic comments. One general question -- is there a strong reason why the default value for the calendar argument in the cftime.datetime constructor is 'standard'? As I understand it, we could make things completely backwards compatible if it was ''.

The default has always been 'standard', but before this didn't actually create a calendar-aware instance.

@spencerkclark
Copy link
Collaborator

spencerkclark commented Oct 13, 2020

The default has always been 'standard', but before this didn't actually create a calendar-aware instance.

Right, I guess the question is: do we prefer to preserve the way the cftime.datetime constructor effectively behaved (i.e. it created a calendar-naive instance) or do we preserve the value it assigned to the calendar attribute by default (even if that attribute didn't really do anything)?

The advantage of the former is that it would not require any changes in nc-time-axis or xarray. The advantage of the latter is that it might be friendlier default behavior to return a calendar-aware instance.

The xarray changes are trivial -- we just need to modify this test, which honestly probably should be modified anyway. If that was all that was required, I'd be inclined to go with the user-friendlier option. I'm a little more concerned about nc-time-axis, since they still have this CalendarDateTime object, which wraps a calendar-naive cftime.datetime instance and includes calendar metadata1. In my opinion this object should be deprecated at this point -- nc-time-axis can now handle calendar-aware datetime instances directly -- but it would take some work to do that.


1It may be that things still work most of the time even if there is a mismatch between the calendar type of the instance and the calendar specified in the CalendarDateTime constructor, but that feels a little awkward to me. There are edge cases where it would not, e.g. February 30th in a 360-day calendar as in the example in their README.

@jswhit
Copy link
Collaborator Author

jswhit commented Oct 14, 2020

Right, I guess the question is: do we prefer to preserve the way the cftime.datetime constructor effectively behaved (i.e. it created a calendar-naive instance) or do we preserve the value it assigned to the calendar attribute by default (even if that attribute didn't really do anything)?

I'm happy either way. I'm not sure what the use case is for creating a 'dumb' (calendar-naive) instance though.

@jswhit
Copy link
Collaborator Author

jswhit commented Oct 14, 2020

I went ahead and changed the default calendar to None, which behaves the same as '' (and creates a calendar-naive instance). The __add__, __sub__,dayofwk,dayofyr, daysinmonth, timetuple and strftime methods will throw exceptions unless a calendar is specified. Arguably, the calendar kwarg should be mandatory, but I know some downstream users depend on the ability to create calendar-naive instances.

@jswhit
Copy link
Collaborator Author

jswhit commented Oct 15, 2020

Based on the discussion in issue #173, I got nervous about the consequences of changing the default calendar (so I changed it back to 'standard'). However, the default behaviour will still change, in that the datetime instance will now be calendar aware by default. @davidhassell - do you foresee any issues with this change?

Copy link
Collaborator

@spencerkclark spencerkclark left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sounds good @jswhit; I'll take care of the xarray change. It looks like you've already made a start on the nc-time-axis changes, which is great. Hopefully the developers there will be able to provide guidance on how they'd like to adapt to this update.

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

Successfully merging this pull request may close these issues.

4 participants