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

AttributeError: 'NoneType' object has no attribute 'convert' #527

Open
2 tasks done
Q-back opened this issue Dec 29, 2020 · 6 comments
Open
2 tasks done

AttributeError: 'NoneType' object has no attribute 'convert' #527

Q-back opened this issue Dec 29, 2020 · 6 comments

Comments

@Q-back
Copy link

Q-back commented Dec 29, 2020

  • I am on the latest Pendulum version.
  • I have searched the issues of this repo and believe that this is not a duplicate.
  • OS version and name: MacOS 10.14.6
  • Pendulum version: 2.1.2

Issue

It's highly probable to break the adding/substracting time feature after messing with the timezone. And the error is very confusing.

How to reproduce

Run the following script:

import pendulum
import datetime
import pytz

duration = pendulum.duration(days=2)
timezone = pendulum.timezone('UTC')
datetime = pendulum.datetime(2020, 12, 12, tz=tz)
datetime = dt.astimezone(pytz.UTC)  # Let's break some things
result = datetime - duration  # Exception is raised!

Exception

...
    dt = self.tz.convert(dt)
AttributeError: 'NoneType' object has no attribute 'convert'

How to fix

Go to https://github.com/sdispater/pendulum/blob/2.1.2/pendulum/datetime.py#L225
and change the line to something like return pendulum.timezone(self.tzinfo). Try to cast it to pendulum's timezone.

The story behind a bug

  1. I'm using Django Rest Framework, and I have serializer like this:
class SomeSerializer(serializers.Serializer):
    date = serializers.DateTimeField(...)

    def to_internal_value(self, data):
        data['date'] = pendulum.now()
        return super().to_internal_value(data)
  1. Django Rest Framework does call .as_timezone with some pytz.UTC data somewhere like in fields.py
    def enforce_timezone(self, value):
        """
        When `self.default_timezone` is `None`, always return naive datetimes.
        When `self.default_timezone` is not `None`, always return aware datetimes.
        """
        field_timezone = getattr(self, 'timezone', self.default_timezone())

        if field_timezone is not None:
            if timezone.is_aware(value):
                try:
                    return value.astimezone(field_timezone)
        ...
@larryturner
Copy link

I have the same issue using pendulum 2.1.2.

I believe this issue was also reported in #160 ( 2017 closed ) and #472 ( 2020 open ).

Assigning datetime.timezone.utc in astimezone, the timezone is not the expected datetime.timezone type, but a pendulum.tz.timezone.FixedTimezone. The pendulum.Datetime then throws exceptions in operations including adding a datetime.timedelta, operations that behave as expected in datetime.

  • datetime
import datetime
import pendulum

epoch = '2021-04-30T14:34:56'
date = datetime.datetime.strptime( epoch, '%Y-%m-%dT%H:%M:%S' )
print( f'Date = {date.isoformat( )} Timezone = {date.tzinfo}' )
date = date.astimezone( datetime.timezone.utc )
print( f'Date = {date.isoformat( )} Timezone = {date.tzinfo}' )
date += datetime.timedelta( seconds = 1 )

Date = 2021-04-30T14:34:56 Timezone = None
Date = 2021-04-30T18:34:56+00:00 Timezone = UTC

  • pendulum
date = pendulum.parse( epoch )
print( f'Date = {date.isoformat( )} Timezone = {date.tzinfo}' )
date = date.astimezone( datetime.timezone.utc )  # Problem.
print( f'Date = {date.isoformat( )} Timezone = {date.tzinfo}' )
try :
    date += datetime.timedelta( seconds = 1 )
except Exception as ex :
    print( f'Exception = {ex.__class__.__name__} {ex}' )

Date = 2021-04-30T14:34:56+00:00 Timezone = Timezone('UTC')
Date = 2021-04-30T14:34:56+00:00 Timezone = UTC
Exception = AttributeError 'NoneType' object has no attribute 'convert'

@Nitrooo
Copy link

Nitrooo commented Jun 20, 2022

Pendulum version: 2.1.2

import pendulum

now = pendulum.now()
s = now.isoformat()
now2 = pendulum.DateTime.fromisoformat(s)
now2.add(seconds=1)

You get:

Traceback (most recent call last):
  File ".../arrow_test.py", line 13, in <module>
    now2.add(seconds=1)
  File ".../venv/lib/python3.10/site-packages/pendulum/datetime.py", line 667, in add
    dt = self.tz.convert(dt)
AttributeError: 'NoneType' object has no attribute 'convert'

I'd like to use Pendulum with TinyDB by providing a custom serializer that uses isoformat and fromisoformat, but apparently the library is broken and abandoned.

@specialorange
Copy link

@Q-back s answer, but modified for the __lower__ is to return pendulum.timezone(self.tzinfo.key) on line 225 : Go to https://github.com/sdispater/pendulum/blob/2.1.2/pendulum/datetime.py#L225

specialorange added a commit to specialorange/pendulum that referenced this issue Oct 1, 2022
AttributeError: 'NoneType' object has no attribute 'convert' python-pendulum#527
@FirefighterBlu3
Copy link

FirefighterBlu3 commented Jan 29, 2023

edit: see my updated code in a more recent comment

For those of you that encounter this problem and don't control the library (such as running your app on Google appspot), monkey patch it in Django. Put this in monkey_pendulum/__init__.py (the root of your Django project) and add monkey_pendulum to your settings.INSTALLED_APPS

#
# https://github.com/sdispater/pendulum/issues/527
#

import logging
import pendulum

from pendulum.datetime import DateTime
from pendulum.tz.timezone import Timezone

logging.getLogger().warning('monkey patching pendulum.datetime.DateTime')


def monkey_timezone(self):  # type: () -> Optional[Timezone]
    if not isinstance(self.tzinfo, Timezone):
        return pendulum.timezone(self.tzinfo.key)

    return self.tzinfo


DateTime.timezone = property(monkey_timezone)

@sanderr
Copy link

sanderr commented Sep 21, 2023

I have encountered this issue when trying to convert a project from datetime to pendulum. Our code base is full of datetime.now().astimezone() in order to work with timezone aware datetimes. Given the declaration of pendulum to be a drop-in replacement for datetime, I had expected I could just replace this with pendulum.DateTime.now().astimezone() but as reported above this does not work.

I would like to add to this that on the latest alpha and on this project's master branch, this behavior seems to be broken in a different way. The subtraction no longer raises an exception, but the result no longer seems to have any timezone information whatsoever. It seems to be a naive datetime object, in UTC (implicit).

>>> import pendulum
>>> import datetime
>>> now_in_some_non_local_non_utc_timezone = pendulum.DateTime.now(tz=datetime.timezone.max)
>>> now_in_some_non_local_non_utc_timezone
DateTime(2023, 9, 22, 12, 29, 3, 536950, tzinfo=FixedTimezone(86340, name="+23:59"))
>>> now_in_some_non_local_non_utc_timezone.tz
FixedTimezone(86340, name="+23:59")
>>> now_in_some_non_local_non_utc_timezone.tzinfo
FixedTimezone(86340, name="+23:59")
>>> now_in_local_timezone = now_in_some_non_local_non_utc_timezone.astimezone()
>>> now_in_local_timezone
DateTime(2023, 9, 21, 14, 30, 3, 536950, tzinfo=datetime.timezone(datetime.timedelta(seconds=7200), 'CEST'))
>>> now_in_local_timezone.tz
>>> now_in_local_timezone.tzinfo
datetime.timezone(datetime.timedelta(seconds=7200), 'CEST')
>>> one_second_ago_in_local_timezone = now_in_local_timezone - pendulum.Duration(seconds=1)
>>> one_second_ago_in_local_timezone
DateTime(2023, 9, 21, 12, 30, 2, 536950)
>>> one_second_ago_in_local_timezone.tz
>>> one_second_ago_in_local_timezone.tzinfo
>>> one_second_ago_in_local_timezone < now_in_local_timezone
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: can't compare offset-naive and offset-aware datetimes

@FirefighterBlu3
Copy link

followup to my fixup above, I've encountered other situations so, here's the monkey patch. this one includes:

  • logging so you can see what package is being used for the datetime object
  • an override for a UTC object because "+00:00" isn't recognized as a tz key for the name
  • a limited traceback for other packages to show where the call is originating from
#
# https://github.com/sdispater/pendulum/issues/527
#

import logging
import pendulum
import traceback

from pendulum.datetime import DateTime
from pendulum.tz.timezone import FixedTimezone, Timezone

logger = logging.getLogger()
logger.warning('monkey patching pendulum.datetime.DateTime')


def monkey_timezone(self):  # type: () -> Optional[Timezone]
    if not isinstance(self.tzinfo, Timezone):
        if hasattr(self.tzinfo, 'key'):
            # zoneinfo timezone
            logger.info(f'looking up monkey_timezone(zoneinfo) using tzinfo.key {self.tzinfo.key}')
            return pendulum.timezone(self.tzinfo.key)
        elif hasattr(self.tzinfo, 'name'):
            # pendulum Timezone
            logger.info(f'looking up monkey_timezone(pendulum) using tzinfo.name {self.tzinfo.name}')
            if self.tzinfo.name == '+00:00':
                return pendulum.timezone('UTC')
            else:
                return pendulum.timezone(self.tzinfo.name)
        elif hasattr(self.tzinfo, 'zone'):
            # pytz
            logger.info(f'looking up monkey_timezone(pytz) using tzinfo.zone {self.tzinfo.zone}')
            return pendulum.timezone(self.tzinfo.zone)
    elif isinstance(self.tzinfo, FixedTimezone):
        # pendulum.FixedTimezone
        logger.info(f'looking up monkey_timezone(FixedTimezone), probably naive object {self.tzinfo}')
        return self.tzinfo

    logger.info(f'uhoh, monkey_timezone(naive?) simply returning tzinfo {type(self.tzinfo)}')
    stack = traceback.format_stack(limit=6)
    stack = ''.join([x for x in stack if 'venv' not in x and 'monkey' not in x])
    logger.info(stack)
    return self.tzinfo


DateTime.timezone = property(monkey_timezone)

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

6 participants