Skip to content

Commit

Permalink
Merging with timedelta_formatting
Browse files Browse the repository at this point in the history
  • Loading branch information
pylover committed Oct 25, 2016
2 parents 01db488 + 0d2498c commit 2beaf69
Show file tree
Hide file tree
Showing 12 changed files with 308 additions and 14 deletions.
2 changes: 1 addition & 1 deletion .gitignore
Expand Up @@ -43,8 +43,8 @@ nosetests.xml

sphinx/_build
sphinx/_inv-cache
sphinx/_site
docs/doctest
docs/doctrees
docs/html
htmlcov
_site
9 changes: 6 additions & 3 deletions khayyam/__init__.py
Expand Up @@ -3,9 +3,10 @@
from .jalali_date import JalaliDate
from .jalali_datetime import JalaliDatetime
from .timezones import TehranTimezone, Timezone
from .formatting import JalaliDateFormatter, JalaliDatetimeFormatter
from .formatting import JalaliDateFormatter, JalaliDatetimeFormatter, JalaliTimedeltaFormatter
from .jalali_timedelta import JalaliTimedelta

__version__ = '3.0.15'
__version__ = '3.1.0-dev.0'


teh_tz = TehranTimezone()
Expand All @@ -23,9 +24,11 @@
'FRIDAY',
'JalaliDate',
'JalaliDatetime',
'JalaliTimedelta',
'TehranTimezone',
'Timezone',
'teh_tz',
'JalaliDateFormatter',
'JalaliDatetimeFormatter'
'JalaliDatetimeFormatter',
'JalaliTimedeltaFormatter'
]
3 changes: 1 addition & 2 deletions khayyam/formatting/__init__.py
@@ -1,4 +1,3 @@
# -*- coding: utf-8 -*-
from .constants import *
from .formatters import BaseFormatter, JalaliDateFormatter, JalaliDatetimeFormatter
__author__ = 'vahid'
from .formatters import BaseFormatter, JalaliDateFormatter, JalaliDatetimeFormatter, JalaliTimedeltaFormatter
2 changes: 2 additions & 0 deletions khayyam/formatting/constants.py
Expand Up @@ -148,6 +148,8 @@
AM_PM_ASCII_REGEX = '([aA][mM]|[pP][mM])'
HOUR12_REGEX = '(0[1-9]|1[0-2])'
HOUR24_REGEX = '([01]\d|2[0-3])'
UNLIMITED_INT_REGEX = '\d+'
PERSIAN_UNLIMITED_INT_REGEX = '[۰۱۲۳۴۵۶۷۸۹]+'
MINUTE_REGEX = '([0]?\d|[1-5]\d)'
SECOND_REGEX = '([0]?\d|[1-5]\d)'
MICROSECOND_REGEX = '\d{1,6}'
Expand Down
21 changes: 20 additions & 1 deletion khayyam/formatting/directives/__init__.py
Expand Up @@ -20,8 +20,9 @@
PersianYearDirective, \
PersianShortYearDirective
from .tz import UTCOffsetDirective, TimezoneNameDirective, PersianUTCOffsetDirective
from .timedelta import TotalHoursDirective, TotalMinutesDirective, PersianTotalHoursDirective, \
PersianHoursDirective

__author__ = 'vahid'

DATE_FORMAT_DIRECTIVES = [

Expand Down Expand Up @@ -166,6 +167,19 @@
), "%i:%r:%s %p"),
]


TIMEDELTA_FORMAT_DIRECTIVES = [
TotalHoursDirective('H', 'totalhours'),
PersianTotalHoursDirective('k', 'persiantotalhours'),
Directive('I', 'hours', consts.HOUR24_REGEX, int, lambda t: '%.2d' % t.hours),
PersianHoursDirective('h', 'persianhours'),

TotalMinutesDirective('M', 'totalminutes', consts.UNLIMITED_INT_REGEX, int, lambda t: '%d' % t.total_minutes),
Directive('m', 'minutes', consts.MINUTE_REGEX, int, lambda t: '%.2d' % t.minutes),

]


DATETIME_FORMAT_DIRECTIVES = DATE_FORMAT_DIRECTIVES + TIME_FORMAT_DIRECTIVES

__all__ = [
Expand All @@ -190,6 +204,11 @@
'DATE_FORMAT_DIRECTIVES',
'TIME_FORMAT_DIRECTIVES',
'DATETIME_FORMAT_DIRECTIVES',
'TIMEDELTA_FORMAT_DIRECTIVES',
'TotalHoursDirective',
'PersianTotalHoursDirective',
'PersianHoursDirective',
'TotalMinutesDirective'
]

"""
Expand Down
9 changes: 7 additions & 2 deletions khayyam/formatting/directives/persian.py
Expand Up @@ -9,8 +9,13 @@
eng_to_persian_dict = {e: p[0] for p, e in PERSIAN_DIGIT_MAPPING}
persian_to_eng_dict = {p[0]: e for p, e in PERSIAN_DIGIT_MAPPING}

eng_to_persian = lambda s: ''.join([eng_to_persian_dict[c] if c in eng_to_persian_dict else c for c in s])
persian_to_eng = lambda s: ''.join([persian_to_eng_dict[c] if c in persian_to_eng_dict else c for c in s])

def eng_to_persian(s):
return ''.join([eng_to_persian_dict[c] if c in eng_to_persian_dict else c for c in s])


def persian_to_eng(s):
return ''.join([persian_to_eng_dict[c] if c in persian_to_eng_dict else c for c in s])


class PersianNumberDirective(Directive):
Expand Down
69 changes: 69 additions & 0 deletions khayyam/formatting/directives/timedelta.py
@@ -0,0 +1,69 @@
# -*- coding: utf-8 -*-
from .base import Directive
from .persian import PersianNumberDirective
from khayyam.formatting import constants as consts


class TotalHoursDirective(Directive):
"""
Representing total hours in a timedelta.
"""

def __init__(self, key, name):
super(TotalHoursDirective, self).__init__(key, name, consts.UNLIMITED_INT_REGEX, int)

def format(self, d):
return str(int(d.total_hours))

def post_parser(self, ctx, formatter):
super(TotalHoursDirective, self).post_parser(ctx, formatter)
if self.name in ctx and ctx[self.name]:
ctx['hours'] = ctx[self.name]


class PersianTotalHoursDirective(PersianNumberDirective):
"""
Representing total hours in persian form.
"""

def __init__(self, key, name):
super(PersianTotalHoursDirective, self).__init__(key, name, consts.PERSIAN_UNLIMITED_INT_REGEX, int)

def format(self, d):
return super(PersianTotalHoursDirective, self).format(int(d.total_hours))

def post_parser(self, ctx, formatter):
super(PersianTotalHoursDirective, self).post_parser(ctx, formatter)
if self.name in ctx and ctx[self.name]:
ctx['hours'] = ctx[self.name]


class PersianHoursDirective(PersianNumberDirective):
"""
Representing Hour24 format in persian form.
"""

def __init__(self, key, name):
super(PersianHoursDirective, self).__init__(key, name, consts.PERSIAN_HOUR24_ZERO_PADDED_REGEX, int)

def format(self, d):
return super(PersianHoursDirective, self).format(int(d.hours))

def post_parser(self, ctx, formatter):
super(PersianHoursDirective, self).post_parser(ctx, formatter)
if self.name in ctx and ctx[self.name]:
ctx['hours'] = ctx[self.name]


class TotalMinutesDirective(Directive):
"""
Representing total minutes in a timedelta.
"""

def format(self, d):
return super(TotalMinutesDirective, self).format(d.total_minutes)

def post_parser(self, ctx, formatter):
super(TotalMinutesDirective, self).post_parser(ctx, formatter)
if self.name in ctx and ctx[self.name]:
ctx['minutes'] = ctx[self.name]
26 changes: 23 additions & 3 deletions khayyam/formatting/formatters.py
@@ -1,7 +1,7 @@
# -*- coding: utf-8 -*-
import re
from khayyam.formatting import constants as consts
from .directives import DATE_FORMAT_DIRECTIVES
from .directives import DATE_FORMAT_DIRECTIVES, DATETIME_FORMAT_DIRECTIVES, TIMEDELTA_FORMAT_DIRECTIVES
__author__ = 'vahid'


Expand Down Expand Up @@ -110,7 +110,7 @@ def post_parsers(self):

def _parse_post_processor(self, parse_result):
for directive_name in self.post_parsers:
if directive_name in parse_result:
if directive_name in parse_result.keys():
self.directives_by_name[directive_name].post_parser(parse_result, self)

def parse(self, date_string):
Expand Down Expand Up @@ -165,6 +165,26 @@ class JalaliDatetimeFormatter(JalaliDateFormatter):

def __init__(self, format_string, directive_db=None):
if not directive_db:
from .directives import DATETIME_FORMAT_DIRECTIVES
directive_db = DATETIME_FORMAT_DIRECTIVES
super(JalaliDatetimeFormatter, self).__init__(format_string, directive_db=directive_db)


class JalaliTimedeltaFormatter(JalaliDateFormatter):
"""
Responsible to parse and formatting of a :py:class:`khayyam.JalaliTimedelta` instance.
"""

_post_parsers = [

'totalhours',
'persiantotalhours',
'totalminutes',

]

def __init__(self, format_string, directive_db=None):
if not directive_db:
directive_db = TIMEDELTA_FORMAT_DIRECTIVES
super(JalaliTimedeltaFormatter, self).__init__(format_string, directive_db=directive_db)

2 changes: 1 addition & 1 deletion khayyam/jalali_datetime.py
Expand Up @@ -7,7 +7,7 @@
from khayyam.helpers import force_encoded_string_output


__author__ = 'vahid'
__author__ = 'vahid' ## TODO: remove it


class JalaliDatetime(khayyam.JalaliDate):
Expand Down
99 changes: 99 additions & 0 deletions khayyam/jalali_timedelta.py
@@ -0,0 +1,99 @@
# -*- coding: utf-8 -*-
from __future__ import division
from datetime import timedelta

from khayyam.formatting import JalaliTimedeltaFormatter


class JalaliTimedelta(timedelta):
_parts = None

@classmethod
def formatterfactory(cls, fmt):
return JalaliTimedeltaFormatter(fmt)

def strftime(self, format_string):
"""
Format codes referring to hours, minutes or seconds will see 0 values.
For a complete list of formatting directives, see :doc:`/directives`.
:param format_string: The format string.
:return: A string representing the date, controlled by an explicit format string
:rtype: unicode
"""
return self.formatterfactory(format_string).format(self)

@classmethod
def strptime(cls, date_string, fmt):
"""
Return a :py:class:`khayyam.JalaliTimedelta` corresponding to *date_string*, parsed according to format.
:py:class:`ValueError` is raised if the *date_string* and format can't be parsed with
:py:class:`khayyam.formatting.JalaliTimedeltaFormatter` instance returned by
:py:meth:`khayyam.JalaliTimedelta.formatterfactory` method.
:param date_string: str The representing date & time in specified format.
:param fmt: str The format string.
:return: Jalali timedelta object.
:rtype: :py:class:`khayyam.JalaliTimedelta`
"""

result = cls.formatterfactory(fmt).parse(date_string)
#
# def assert_a_xor_b(a, b):
# if a in result:
# if b in result:
# raise ValueError('Cannot use %s and %s, together.' % (a, b))
# result[b] = result[a]
# del result[a]
#
# assert_a_xor_b('total_hours', 'hours')
# assert_a_xor_b('total_minutes', 'minutes')

result = {k: v for k, v in result.items() if k in (
'days', 'hours', 'minutes', 'seconds', 'milliseconds', 'microseconds'
)}

return cls(**result)

def _calculate_parts(self):
# days, seconds, microseconds
remaining_seconds = self.seconds
hours = remaining_seconds // 3600 # 1-23
remaining_seconds %= 3600
total_hours = self.days * 24 + hours + remaining_seconds / 3600

minutes = remaining_seconds / 60 # 1-59
remaining_seconds %= 60
total_minutes = total_hours * 60 + remaining_seconds / 60
seconds = remaining_seconds # 1-59

self._parts = {
'hours': hours,
'minutes': minutes,
'seconds': seconds,
'total_hours': total_hours,
'total_minutes': total_minutes
}

@property
def parts(self):
if self._parts is None:
self._calculate_parts()
return self._parts

@property
def hours(self):
return self.parts['hours']

@property
def total_hours(self):
return self.parts['total_hours']

@property
def minutes(self):
return self.parts['minutes']

@property
def total_minutes(self):
return self.parts['total_minutes']
41 changes: 41 additions & 0 deletions khayyam/tests/test_jalali_timedelta.py
@@ -0,0 +1,41 @@
# -*- coding: utf-8 -*-
import unittest

from khayyam import JalaliTimedelta


class TestJalaliTimedelta(unittest.TestCase):
def setUp(self):
pass

def test_strftime_strptime(self):
d1 = JalaliTimedelta(12, 23, 12, 3, 45, 34567)
self.assertEqual(d1.total_hours, 34855.75638888889)
self.assertEqual(d1.strftime('%H'), '34855')
self.assertEqual(d1.strftime('%I'), '07')
self.assertEqual(d1.strftime('%k'), u'۳۴۸۵۵')
self.assertEqual(d1.strftime('%h'), u'۰۷')

self.assertEqual(JalaliTimedelta.strptime('34855', '%H').total_seconds(), 125478000)
self.assertEqual(JalaliTimedelta.strptime(u'۳۴۸۵۵', '%k').total_seconds(), 125478000)

self.assertEqual(JalaliTimedelta.strptime('07', '%I').total_seconds(), 25200)
self.assertEqual(JalaliTimedelta.strptime('34343 08', '%H %I').total_hours, 34343)
self.assertEqual(JalaliTimedelta.strptime('34343 08', '%H %I').hours, 23)

self.assertEqual(JalaliTimedelta.strptime('34855', '%M').total_seconds(), 2091300)
self.assertEqual(JalaliTimedelta.strptime('07', '%m').total_seconds(), 420)
self.assertEqual(JalaliTimedelta.strptime('34343 08', '%M %m').total_minutes, 34343)
self.assertEqual(JalaliTimedelta.strptime('34343 08', '%M %m').minutes, 23)

# self.assertEqual(
# d1.strptime('Panjshanbeh 23 Esfand 1375 12:03:45 PM', '%Q'),
# d1 - timedelta(microseconds=34567))
#
# self.assertEqual(d1.isoformat(), '%s-12-23T12:03:45.034567' % self.leap_year)
# tz_datetime = d1.astimezone(teh_tz)
# self.assertEqual(tz_datetime.strftime('%Z'), 'Iran/Tehran')


if __name__ == '__main__': # pragma: no cover
unittest.main()

0 comments on commit 2beaf69

Please sign in to comment.