Skip to content

Commit

Permalink
Merge pull request #29 from benfitzpatrick/strftime_strptime
Browse files Browse the repository at this point in the history
Implement subset of strftime/strptime POSIX standard
  • Loading branch information
matthewrmshin committed Apr 1, 2014
2 parents c1ee478 + 0be9995 commit 8a894d8
Show file tree
Hide file tree
Showing 6 changed files with 398 additions and 72 deletions.
13 changes: 12 additions & 1 deletion isodatetime/data.py
Original file line number Diff line number Diff line change
Expand Up @@ -1313,18 +1313,29 @@ def _tick_over_day_of_month(self):
self.day_of_month = day
return

def __str__(self, override_custom_dump_format=False):
def __str__(self, override_custom_dump_format=False,
strftime_format=None):
if self.expanded_year_digits not in TIMEPOINT_DUMPER_MAP:
TIMEPOINT_DUMPER_MAP[self.expanded_year_digits] = (
dumpers.TimePointDumper(
self.expanded_year_digits))
dumper = TIMEPOINT_DUMPER_MAP[self.expanded_year_digits]
if strftime_format is not None:
return dumper.strftime(self, strftime_format)
if self.truncated:
return dumper.dump(self, self._get_truncated_dump_format())
if self.dump_format and not override_custom_dump_format:
return dumper.dump(self, self.dump_format)
return dumper.dump(self, self._get_dump_format())

def strftime(self, strftime_format):
"""Implement equivalent of Python 2's datetime.datetime.strftime.
Dump based on the format given in the strftime_format string.
"""
return self.__str__(strftime_format=strftime_format)

def _get_dump_format(self):
year_digits = 4 + self.expanded_year_digits
year_string = "%0" + str(year_digits) + "d"
Expand Down
26 changes: 26 additions & 0 deletions isodatetime/dumpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,32 @@ def dump(self, timepoint, formatting_string):
"""
expression, properties = self._get_expression_and_properties(
formatting_string)
return self._dump_expression_with_properties(
timepoint, expression, properties)

def strftime(self, timepoint, formatting_string):
"""Implement equivalent of Python 2's datetime.datetime.strftime.
Dump timepoint based on the format given in formatting_string.
"""
split_format = parser_spec.REC_SPLIT_STRFTIME_DIRECTIVE.split(
formatting_string)
expression = ""
properties = []
for item in split_format:
if parser_spec.REC_STRFTIME_DIRECTIVE_TOKEN.search(item):
item_expression, item_properties = (
parser_spec.translate_strftime_token(item))
expression += item_expression
properties += item_properties
else:
expression += item
return self._dump_expression_with_properties(
timepoint, expression, properties)

def _dump_expression_with_properties(self, timepoint, expression,
properties):
if (not timepoint.truncated and
("week_of_year" in properties or
"day_of_week" in properties) and
Expand Down
91 changes: 91 additions & 0 deletions isodatetime/parser_spec.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,9 @@

"""This provides data to drive ISO 8601 parsing functionality."""

import re
from . import timezone


DATE_EXPRESSIONS = {
"basic": {
Expand Down Expand Up @@ -211,6 +214,47 @@
"Z", None)
]

LOCALE_TIMEZONE_BASIC = timezone.get_timezone_format_for_locale()
LOCALE_TIMEZONE_BASIC_NO_Z = LOCALE_TIMEZONE_BASIC
if LOCALE_TIMEZONE_BASIC_NO_Z == "Z":
LOCALE_TIMEZONE_BASIC_NO_Z = "+0000"
LOCALE_TIMEZONE_EXTENDED = timezone.get_timezone_format_for_locale(
extended_mode=True)
LOCALE_TIMEZONE_EXTENDED_NO_Z = LOCALE_TIMEZONE_EXTENDED
if LOCALE_TIMEZONE_EXTENDED_NO_Z == "Z":
LOCALE_TIMEZONE_EXTENDED_NO_Z = "+0000"

# Note: we only accept the following subset of strftime syntax.
# This is due to inconsistencies with the ISO 8601 representations.
REC_SPLIT_STRFTIME_DIRECTIVE = re.compile(r"(%\w)")
REC_STRFTIME_DIRECTIVE_TOKEN = re.compile(r"^%\w$")
STRFTIME_TRANSLATE_INFO = {
"%d": ["day_of_month"],
"%H": ["hour_of_day"],
"%j": ["day_of_year"],
"%m": ["month_of_year"],
"%M": ["minute_of_hour"],
"%S": ["second_of_minute"],
"%X": ["hour_of_day", ":", "minute_of_hour", ":", "second_of_minute"],
"%y": ["year_of_century"],
"%Y": ["century", "year_of_century"],
"%z": LOCALE_TIMEZONE_BASIC_NO_Z,
}
STRPTIME_EXCLUSIVE_GROUP_INFO = {
"%Y": ("%y",),
"%X": ("%H", "%M", "%S")
}


class StrftimeSyntaxError(ValueError):

"""An error denoting invalid or unsupported strftime/strptime syntax."""

BAD_STRFTIME_INPUT = "Invalid strftime/strptime representation: {0}"

def __str__(self):
return self.BAD_STRFTIME_INPUT.format(*self.args)


def get_date_translate_info(num_expanded_year_digits=2):
expanded_year_digit_regex = "\d" * num_expanded_year_digits
Expand All @@ -229,3 +273,50 @@ def get_time_translate_info():
def get_timezone_translate_info():
return _TIMEZONE_TRANSLATE_INFO


def translate_strftime_token(strftime_token, num_expanded_year_digits=2):
"""Convert a strftime format into our own dump format."""
return _translate_strftime_token(
strftime_token, dump_mode=True,
num_expanded_year_digits=num_expanded_year_digits
)


def translate_strptime_token(strptime_token, num_expanded_year_digits=2):
"""Convert a strptime format into our own parsing format."""
return _translate_strftime_token(
strptime_token, dump_mode=False,
num_expanded_year_digits=num_expanded_year_digits
)


def _translate_strftime_token(strftime_token, dump_mode=False,
num_expanded_year_digits=2):
if strftime_token not in STRFTIME_TRANSLATE_INFO:
raise StrftimeSyntaxError(strftime_token)
our_translation = ""
our_translate_info = (
get_date_translate_info(
num_expanded_year_digits=num_expanded_year_digits) +
get_time_translate_info() +
get_timezone_translate_info()
)
attr_names = STRFTIME_TRANSLATE_INFO[strftime_token]
if isinstance(attr_names, basestring):
if dump_mode:
return attr_names, []
return re.escape(attr_names), []
attr_names = list(attr_names)
for attr_name in list(attr_names):
for expr_regex, substitute, format_, name in our_translate_info:
if name == attr_name:
if dump_mode:
our_translation += format_
else:
our_translation += substitute
break
else:
# Not an attribute name, just a delimiter or something.
our_translation += attr_name
attr_names.remove(attr_name)
return our_translation, attr_names
Loading

0 comments on commit 8a894d8

Please sign in to comment.