Skip to content

Commit

Permalink
SF patch #691928: Use datetime in _strptime
Browse files Browse the repository at this point in the history
Contributed by Brett Cannon.

To prevent code duplication, I patched _strptime to use datetime's date
object to do Julian day, Gregorian, and day of the week calculations.

Patch also includes new regression tests to test results and the
calculation gets triggered.

Very minor comment changes and the contact email are also changed.
  • Loading branch information
rhettinger committed Mar 9, 2003
1 parent c8df578 commit 1fdb633
Show file tree
Hide file tree
Showing 2 changed files with 56 additions and 88 deletions.
74 changes: 18 additions & 56 deletions Lib/_strptime.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,32 +6,25 @@
time information as is returned by time.strftime()
FUNCTIONS:
firstjulian -- Calculates the Julian date up to the first of the specified
year
gregorian -- Calculates the Gregorian date based on the Julian day and
year
julianday -- Calculates the Julian day since the first of the year based
on the Gregorian date
dayofweek -- Calculates the day of the week from the Gregorian date.
_getlang -- Figure out what language is being used for the locale
strptime -- Calculates the time struct represented by the passed-in string
Requires Python 2.2.1 or higher.
Requires Python 2.2.1 or higher (mainly because of the use of property()).
Can be used in Python 2.2 if the following line is added:
>>> True = 1; False = 0
True = 1; False = 0
"""
import time
import locale
import calendar
from re import compile as re_compile
from re import IGNORECASE
from datetime import date as datetime_date

__author__ = "Brett Cannon"
__email__ = "[email protected]"
__email__ = "[email protected]"

__all__ = ['strptime']

RegexpType = type(re_compile(''))

def _getlang():
# Figure out what the current language is set to.
current_lang = locale.getlocale(locale.LC_TIME)[0]
Expand Down Expand Up @@ -425,7 +418,7 @@ def strptime(data_string, format="%a %b %d %H:%M:%S %Y"):
month = day = 1
hour = minute = second = 0
tz = -1
# Defaulted to -1 so as to signal using functions to calc values
# weekday and julian defaulted to -1 so as to signal need to calculate values
weekday = julian = -1
found_dict = found.groupdict()
for group_key in found_dict.iterkeys():
Expand Down Expand Up @@ -495,16 +488,21 @@ def strptime(data_string, format="%a %b %d %H:%M:%S %Y"):
tz = 1
elif locale_time.timezone[2].lower() == found_zone:
tz = -1
#XXX <bc>: If calculating fxns are never exposed to the general
#populous then just inline calculations. Also might be able to use
#``datetime`` and the methods it provides.
# Cannot pre-calculate datetime_date() since can change in Julian
#calculation and thus could have different value for the day of the week
#calculation
if julian == -1:
julian = julianday(year, month, day)
else: # Assuming that if they bothered to include Julian day it will
# Need to add 1 to result since first day of the year is 1, not 0.
julian = datetime_date(year, month, day).toordinal() - \
datetime_date(year, 1, 1).toordinal() + 1
else: # Assume that if they bothered to include Julian day it will
#be accurate
year, month, day = gregorian(julian, year)
datetime_result = datetime_date.fromordinal((julian - 1) + datetime_date(year, 1, 1).toordinal())
year = datetime_result.year
month = datetime_result.month
day = datetime_result.day
if weekday == -1:
weekday = dayofweek(year, month, day)
weekday = datetime_date(year, month, day).weekday()
return time.struct_time((year, month, day,
hour, minute, second,
weekday, julian, tz))
Expand All @@ -522,39 +520,3 @@ def _insensitiveindex(lst, findme):
else:
raise ValueError("value not in list")

def firstjulian(year):
"""Calculate the Julian date up until the first of the year."""
return ((146097 * (year + 4799)) // 400) - 31738

def julianday(year, month, day):
"""Calculate the Julian day since the beginning of the year.
Calculated from the Gregorian date.
"""
a = (14 - month) // 12
return (day - 32045
+ (((153 * (month + (12 * a) - 3)) + 2) // 5)
+ ((146097 * (year + 4800 - a)) // 400)) - firstjulian(year) + 1

def gregorian(julian, year):
"""Return 3-item list containing Gregorian date based on the Julian day."""
a = 32043 + julian + firstjulian(year)
b = ((4 * a) + 3) // 146097
c = a - ((146097 * b) // 4)
d = ((4 * c) + 3) // 1461
e = c - ((1461 * d) // 4)
m = ((5 * e) + 2) // 153
day = 1 + e - (((153 * m) + 2) // 5)
month = m + 3 - (12 * (m // 10))
year = (100 * b) + d - 4800 + (m // 10)
return [year, month, day]

def dayofweek(year, month, day):
"""Calculate the day of the week (Monday is 0)."""
a = (14 - month) // 12
y = year - a
weekday = (day + y + ((97 * y) // 400)
+ ((31 * (month + (12 * a) -2 )) // 12)) % 7
if weekday == 0:
return 6
else:
return weekday-1
70 changes: 38 additions & 32 deletions Lib/test/test_strptime.py
Original file line number Diff line number Diff line change
Expand Up @@ -329,37 +329,6 @@ def test_defaults(self):
"Default values for strptime() are incorrect;"
" %s != %s" % (strp_output, defaults))

class FxnTests(unittest.TestCase):
"""Test functions that fill in info by validating result and are triggered
properly."""

def setUp(self):
"""Create an initial time tuple."""
self.time_tuple = time.gmtime()

def test_julianday_result(self):
# Test julianday
result = _strptime.julianday(self.time_tuple[0], self.time_tuple[1],
self.time_tuple[2])
self.failUnless(result == self.time_tuple[7],
"julianday failed; %s != %s" %
(result, self.time_tuple[7]))

def test_gregorian_result(self):
# Test gregorian
result = _strptime.gregorian(self.time_tuple[7], self.time_tuple[0])
comparison = [self.time_tuple[0], self.time_tuple[1], self.time_tuple[2]]
self.failUnless(result == comparison,
"gregorian() failed; %s != %s" % (result, comparison))

def test_dayofweek_result(self):
# Test dayofweek
result = _strptime.dayofweek(self.time_tuple[0], self.time_tuple[1],
self.time_tuple[2])
comparison = self.time_tuple[6]
self.failUnless(result == comparison,
"dayofweek() failed; %s != %s" % (result, comparison))

class Strptime12AMPMTests(unittest.TestCase):
"""Test a _strptime regression in '%I %p' at 12 noon (12 PM)"""

Expand All @@ -380,15 +349,52 @@ def test_all_julian_days(self):
# use 2004, since it is a leap year, we have 366 days
eq(_strptime.strptime('%d 2004' % i, '%j %Y')[7], i)

class CalculationTests(unittest.TestCase):
"""Test that strptime() fills in missing info correctly"""

def setUp(self):
self.time_tuple = time.gmtime()

def test_julian_calculation(self):
# Make sure that when Julian is missing that it is calculated
format_string = "%Y %m %d %H %M %S %w %Z"
result = _strptime.strptime(time.strftime(format_string, self.time_tuple),
format_string)
self.failUnless(result.tm_yday == self.time_tuple.tm_yday,
"Calculation of tm_yday failed; %s != %s" %
(result.tm_yday, self.time_tuple.tm_yday))

def test_gregorian_calculation(self):
# Test that Gregorian date can be calculated from Julian day
format_string = "%Y %H %M %S %w %j %Z"
result = _strptime.strptime(time.strftime(format_string, self.time_tuple),
format_string)
self.failUnless(result.tm_year == self.time_tuple.tm_year and
result.tm_mon == self.time_tuple.tm_mon and
result.tm_mday == self.time_tuple.tm_mday,
"Calculation of Gregorian date failed;"
"%s-%s-%s != %s-%s-%s" %
(result.tm_year, result.tm_mon, result.tm_mday,
self.time_tuple.tm_year, self.time_tuple.tm_mon,
self.time_tuple.tm_mday))

def test_day_of_week_calculation(self):
# Test that the day of the week is calculated as needed
format_string = "%Y %m %d %H %S %j %Z"
result = _strptime.strptime(time.strftime(format_string, self.time_tuple),
format_string)
self.failUnless(result.tm_wday == self.time_tuple.tm_wday,
"Calculation of day of the week failed;"
"%s != %s" % (result.tm_wday, self.time_tuple.tm_wday))

def test_main():
suite = unittest.TestSuite()
suite.addTest(unittest.makeSuite(LocaleTime_Tests))
suite.addTest(unittest.makeSuite(TimeRETests))
suite.addTest(unittest.makeSuite(StrptimeTests))
suite.addTest(unittest.makeSuite(FxnTests))
suite.addTest(unittest.makeSuite(Strptime12AMPMTests))
suite.addTest(unittest.makeSuite(JulianTests))
suite.addTest(unittest.makeSuite(CalculationTests))
test_support.run_suite(suite)


Expand Down

0 comments on commit 1fdb633

Please sign in to comment.