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.
views:
151answers:
3It'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.
The babel
internationalization library seems to have no problems with it. See the docs for babel.dates
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()