2

I want a class with all the features of DateTime except:

  1. I want the constructor to pass a particular formatted string as the only argument.
  2. I want the str output to also default to the same format

I am not extending the class I am really limiting it. The functionality is all in the DateTime module but is subclassing an immutable the best way to go?

I have read up on immutable types and new

399022, 2732256, 2673651, Subclass tuple

But the DateTime Module says the DateTime objects has Other constructors, all class methods
Such as:

classmethod datetime.strptime(date_string, format)

But how do I change my new function to call the super of the class method for an immutable type Or is there an easier way to get what I described above?

tharple
  • 23
  • 1
  • 4

1 Answers1

1

Subclassing is almost always preferred over trying to monkey patch. Does the following work?

class LimitedDatetime(datetime.datetime):

    format = "%y-%m-%dT%H:%M:%S"  

    def __new__(cls, date_string):
        return super(LimitedDatetime, cls).strptime(date_string, cls.format)

    def __str__(self):
        return super(LimitedDatetime, self).strftime(self.format)

LD = LimitedDatetime("2017-10-02T07:24:21")
print str(LD)

adapted from What does 'super' do in Python?, which describes specifically how classmethod alternate constructors are invoked using super.

EDIT:

That didn't seem to work due because the alternate constructor returns the entire object that seems to want to be passed through the strptime again, but the following works, don't inherit from datetime, create a new object that is LIKE datetime in every other way but overriding the appropriate methods (str callable). This is called composition (and you don't need super):

import datetime

class LimitedDatetime(object):

    fmt = r'%Y-%m-%dT%H:%M:%S'  

    # fake isinstance checking:

    @property
    def __class__(self):
        return datetime.datetime

    def __init__(self, date_string):
        self._ = datetime.datetime.strptime(date_string, self.fmt)

    # this lets us access all of datetime's attributes as if they were our own
    def __getattr__(self, attr):
        return getattr(self._, attr)

    def __str__(self):
        return self.strftime(self.fmt)

LD = LimitedDatetime('2017-10-02T07:24:21')
print type(LD)
print isinstance(LD, datetime.datetime)
print str(LD)

> <class '__main__.LimitedDatetime'>
> True
> 2017-01-01T01:01:01

This approach is from inheritance from str or int, with the class faking from How to fake type with Python

EDIT 2018-02-09

Operators are special methods (__op__), so they have to be overridden:

def __add__(self, td):
    return LimitedDatetime(self._ + td)

# support `timedelta + LimitedDatetime`
def __radd__(self, td):
    return LimitedDatetime(self._ + td)

The rule for operator methods is they need to return a new instance of the object with the new state, and this means will need to adjust the signature of the constructor to support calling it 2 different ways (one, the original date string, the second, with an existing datetime object, which is what an operator with a timedelta object returns:

def __init__(self, obj):
    if isinstance(obj, basestring):
        # support LimitedDatetime('YYYY-MM-DD')
        self._ = datetime.datetime.strptime(obj, self.fmt)
    elif isinstance(obj, datetime.datetime):
        # support re-instantiation from operators
        self._ = obj

td = datetime.timedelta(1)
enddate = LD + td
print str(enddate)
print type(enddate)

# need __radd__ to support:

enddate = td + LD
print str(enddate)
print type(enddate)

> 2017-01-02T01:01:01
> <class '__main__.LimitedDatetime'>
> 2017-01-02T01:01:01
> <class '__main__.LimitedDatetime'>
cowbert
  • 3,212
  • 2
  • 25
  • 34
  • Not sure if this is an issue with the method or TimeDate parsing which seems to be a different issue. ValueError: time data '2017-10-02T07:24:21' does not match format '%y-%m-%dT%H:%M:%S' – tharple Feb 07 '18 at 22:22
  • it is because %Y needs to be capitalized, but because strptime returns itself, it ultimately fails (since new wants to then call strptime on the strptime returned object. Therefore see if the second approach works for you. – cowbert Feb 08 '18 at 00:18
  • I took your idea and went with it, but when I try to do timedate operations it seems to not really know that it is a timedate. Do I have to override each method with something similar to __getattr__? Sorry to drag this on. https://pymotw.com/2/datetime/ – tharple Feb 08 '18 at 22:57