Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

dateutil parser for month/year strings

Somewhat related to this post: dateutil parser for month/year format: return beginning of month

Given a date string of the form 'Sep-2020', dateutil.parser.parse correctly identifies the month and the year but adds the day as well. If a default is provided, it takes the day from it. Else, it will just use today's day. Is there anyway to tell if the parser used any of the default terms?

For example, how can I tell from the three options below that the input date string in the first case did not include day and that the default value was used?

>>> from datetime import datetime
>>> from dateutil import parser
>>> d = datetime(1978, 1, 1, 0, 0)
>>> parser.parse('Sep-2020', default=d)
datetime.datetime(2020, 9, 1, 0, 0)
>>> parser.parse('1-Sep-2020', default=d)
datetime.datetime(2020, 9, 1, 0, 0)
>>> parser.parse('Sep-1-2020', default=d)
datetime.datetime(2020, 9, 1, 0, 0)
``
like image 634
vkkodali Avatar asked Sep 12 '25 00:09

vkkodali


1 Answers

I did something a little mad to solve this. It's mad since it's not guaranteed to work with future versions of dateutil (since it's relying on some dateutil internals).

Currently I'm using: python-dateutil 2.8.1.

I wrote my own class and passed it as default to the parser:

from datetime import datetime


class SentinelDateTime:

    def __init__(self, year=0, month=0, day=0, default=None):
        self._year = year
        self._month = month
        self._day = day

        if default is None:
            default = datetime.now().replace(
                hour=0, minute=0,
                second=0, microsecond=0
            )

        self.year = default.year
        self.month = default.month
        self.day = default.day
        self.default = default

    @property
    def has_year(self):
        return self._year != 0

    @property
    def has_month(self):
        return self._month != 0

    @property
    def has_day(self):
        return self._day != 0

    def todatetime(self):
        res = {
            attr: value
            for attr, value in [
                ("year", self._year),
                ("month", self._month),
                ("day", self._day),
            ] if value
        }
        return self.default.replace(**res)

    def replace(self, **result):
        return SentinelDateTime(**result, default=self.default)

    def __repr__(self):
        return "%s(%d, %d, %d)" % (
            self.__class__.__qualname__,
            self._year,
            self._month,
            self._day
        )

The dateutils method now returns this SentinelDateTime class:


>>> from dateutil import parser
>>> from datetime import datetime
>>> from snippet1 import SentinelDateTime
>>>
>>> sentinel = SentinelDateTime()
>>> s = parser.parse('Sep-2020', default=sentinel)
>>> s
SentinelDateTime(2020, 9, 0)
>>> s.has_day
False
>>> s.todatetime()
datetime.datetime(2020, 9, 9, 0, 0)


>>> d = datetime(1978, 1, 1)
>>> sentinel = SentinelDateTime(default=d)
>>> s = parser.parse('Sep-2020', default=sentinel)
>>> s
SentinelDateTime(2020, 9, 0)
>>> s.has_day
False
>>> s.todatetime()
datetime.datetime(2020, 9, 1, 0, 0)

I wrote this answer into a little package: https://github.com/foxyblue/sentinel-datetime

like image 107
foxyblue Avatar answered Sep 13 '25 14:09

foxyblue