Formatting date string in Python for dates prior to 1900?

后端 未结 4 1808
梦谈多话
梦谈多话 2020-12-21 07:00

Can anyone explain the best way to format a date time string in Python where the date value is prior to the year 1900? strftime requires dates later than 1900.<

相关标签:
4条回答
  • 2020-12-21 07:09

    It's a bit cumbersome, but it works (at least in stable versions of python):

    >>> ts = datetime.datetime(1895, 10, 6, 16, 4, 5)
    >>> '{0.year}-{0.month:{1}}-{0.day:{1}} {0.hour:{1}}:{0.minute:{1}}'.format(ts, '02')
    '1895-10-06 16:04'
    

    note that str would still produce a readable string:

    >>> str(ts)
    '1895-10-06 16:04:05'
    

    edit
    The closest possible way to emulate the default behaviour is to hard-code the dictionary such as:

    >>> d = {'%Y': '{0.year}', '%m': '{0.month:02}'}    # need to include all the formats
    >>> '{%Y}-{%m}'.format(**d).format(ts)
    '1895-10'
    

    You'll need to enclose all format specifiers into the curly braces with the simple regex:

    >>> re.sub('(%\w)', r'{\1}', '%Y-%m-%d %H sdf')
    '{%Y}-{%m}-{%d} {%H} sdf'
    

    and at the end we come to simple code:

    def ancient_fmt(ts, fmt):
        fmt = fmt.replace('%%', '%')
        fmt = re.sub('(%\w)', r'{\1}', fmt)
        return fmt.format(**d).format(ts)
    
    def main(ts, format):
        if ts.year < 1900:
            return ancient_format(ts, fmt)
        else:
            return ts.strftime(fmt)
    

    where d is a global dictionary with keys corresponding to some specifiers in strftime table.

    edit 2
    To clarify: this approach will work only for the following specifiers: %Y, %m, %d, %H, %M, %S, %f, i.e., those that are numeric, if you need textual information, you'd better off with babel or any other solution.

    0 讨论(0)
  • 2020-12-21 07:10

    The calendar is exactly the same every 400 years. Therefore it is sufficient to change year by multiple of 400 such as year >= 1900 before calling datetime.strftime().

    The code shows what problems such approach has:

    #/usr/bin/env python2.6
    import re
    import warnings
    from datetime import datetime
    
    
    def strftime(datetime_, format, force=False):
        """`strftime()` that works for year < 1900.
    
        Disregard calendars shifts.
    
        >>> def f(fmt, force=False):
        ...     return strftime(datetime(1895, 10, 6, 11, 1, 2), fmt, force)
        >>> f('abc %Y %m %D') 
        'abc 1895 10 10/06/95'
        >>> f('%X')
        '11:01:02'
        >>> f('%c') #doctest:+NORMALIZE_WHITESPACE
        Traceback (most recent call last):
        ValueError: '%c', '%x' produce unreliable results for year < 1900
        use force=True to override
        >>> f('%c', force=True)
        'Sun Oct  6 11:01:02 1895'
        >>> f('%x') #doctest:+NORMALIZE_WHITESPACE
        Traceback (most recent call last):
        ValueError: '%c', '%x' produce unreliable results for year < 1900
        use force=True to override
        >>> f('%x', force=True)
        '10/06/95'
        >>> f('%%x %%Y %Y')
        '%x %Y 1895'
        """
        year = datetime_.year
        if year >= 1900:
           return datetime_.strftime(format)
    
        # make year larger then 1900 using 400 increment
        assert year < 1900
        factor = (1900 - year - 1) // 400 + 1
        future_year = year + factor * 400
        assert future_year > 1900
    
        format = Specifier('%Y').replace_in(format, year)
        result = datetime_.replace(year=future_year).strftime(format)
        if any(f.ispresent_in(format) for f in map(Specifier, ['%c', '%x'])):
            msg = "'%c', '%x' produce unreliable results for year < 1900"
            if not force:
                raise ValueError(msg + " use force=True to override")
            warnings.warn(msg)
            result = result.replace(str(future_year), str(year))
        assert (future_year % 100) == (year % 100) # last two digits are the same
        return result
    
    
    class Specifier(str):
        """Model %Y and such in `strftime`'s format string."""
        def __new__(cls, *args):
            self = super(Specifier, cls).__new__(cls, *args)
            assert self.startswith('%')
            assert len(self) == 2
            self._regex = re.compile(r'(%*{0})'.format(str(self)))
            return self
    
        def ispresent_in(self, format):
            m = self._regex.search(format)
            return m and m.group(1).count('%') & 1 # odd number of '%'
    
        def replace_in(self, format, by):
            def repl(m):
                n = m.group(1).count('%')
                if n & 1: # odd number of '%'
                    prefix = '%'*(n-1) if n > 0 else ''
                    return prefix + str(by) # replace format
                else:
                    return m.group(0) # leave unchanged
            return self._regex.sub(repl, format)
    
    
    if __name__=="__main__":
        import doctest; doctest.testmod()
    
    0 讨论(0)
  • 2020-12-21 07:18

    The babel internationalization library seems to have no problems with it. See the docs for babel.dates

    0 讨论(0)
  • 2020-12-21 07:32

    Just for fun, I endend up with this solution.

    a = datetime.datetime(1322, 10, 10)
    # %Y%m%d
    ''.join(map(lambda x: '{:02}'.format(getattr(a, x)), ('year', 'month', 'day')))
    # %Y-%m-%d
    '-'.join(map(lambda x: '{:02}'.format(getattr(a, x)), ('year', 'month', 'day')))
    # %Y%m%d%H%M%S
    ''.join(map(lambda x: '{:02}'.format(getattr(a, x)), ('year', 'month', 'day', 'hour', 'minute', 'second')))
    
    0 讨论(0)
提交回复
热议问题