├── .gitignore ├── .travis.yml ├── LICENSE ├── README.md ├── README.rst ├── pytimekr ├── __init__.py ├── exception.py ├── filter.py └── pytimekr.py ├── rd_to_rst.sh ├── requirements.txt ├── setup.py └── tests ├── __init__.py └── test_normal.py /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *.idea 5 | .DS_Store 6 | 7 | # C extensions 8 | *.so 9 | 10 | # Distribution / packaging 11 | .Python 12 | env/ 13 | build/ 14 | develop-eggs/ 15 | dist/ 16 | downloads/ 17 | eggs/ 18 | .eggs/ 19 | lib/ 20 | lib64/ 21 | parts/ 22 | sdist/ 23 | var/ 24 | *.egg-info/ 25 | .installed.cfg 26 | *.egg 27 | 28 | # PyInstaller 29 | # Usually these files are written by a python script from a template 30 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 31 | *.manifest 32 | *.spec 33 | 34 | # Installer logs 35 | pip-log.txt 36 | pip-delete-this-directory.txt 37 | 38 | # Unit test / coverage reports 39 | htmlcov/ 40 | .tox/ 41 | .coverage 42 | .coverage.* 43 | .cache 44 | nosetests.xml 45 | coverage.xml 46 | *,cover 47 | 48 | # Translations 49 | *.mo 50 | *.pot 51 | 52 | # Django stuff: 53 | *.log 54 | 55 | # Sphinx documentation 56 | docs/_build/ 57 | 58 | # PyBuilder 59 | target/ 60 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: python 2 | 3 | python: 4 | - "2.6" 5 | - "2.7" 6 | - "3.2" 7 | - "3.3" 8 | - "3.4" 9 | 10 | install: 11 | - if [[ $TRAVIS_PYTHON_VERSION == 2.6 ]]; then travis_retry pip install unittest2; fi 12 | - pip install coverage 13 | - pip install coveralls 14 | 15 | script: nosetests 16 | 17 | after_success: 18 | - coverage combine 19 | - coveralls 20 | 21 | notifications: 22 | email: 23 | on_success: never 24 | on_failure: always 25 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Sinux 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # PyTimeKR 2 | 3 | PyTimeKR은 [PyTime](https://github.com/shnode/PyTime) 의 포크로 대한민국의 공휴일 등 을 추가하였습니다. 4 | 5 | ## 설치 6 | ```python 7 | pip install pytimekr 8 | ``` 9 | ## 간단 사용법 10 | 11 | ```python 12 | >>>from pytimekr import pytimekr 13 | >>> 14 | >>>chuseok = pytimekr.chuseok() # 추석 15 | >>>print chuseok 16 | datetime.date(2015, 9, 27) 17 | >>> 18 | >>>pytimekr.red_days(chuseok) # 추석 연휴 19 | [datetime.date(2015, 9, 26), 20 | datetime.date(2015, 9, 27), 21 | datetime.date(2015, 9, 28), 22 | datetime.date(2015, 9, 29)] 23 | >>> 24 | >>>ly = pytimekr.lunar_newyear(1995) # 1995년도 설날 25 | >>>print ly 26 | datetime.date(1995, 1, 31) 27 | ``` 28 | 29 | 다른 공휴일 30 | ```python 31 | >>>pytimekr.hangul() # 한글날 32 | datetime.date(2015, 10, 9) 33 | >>> 34 | >>>pytimekr.children() # 어린이날 35 | datetime.date(2015, 5, 5) 36 | >>> 37 | >>>pytimekr.independence() # 광복절 38 | datetime.date(2015, 8, 15) 39 | >>> 40 | >>>pytimekr.memorial() # 현충일 41 | datetime.date(2015, 6, 6) 42 | >>> 43 | >>>pytimekr.buddha() # 석가탄신일 44 | datetime.date(2015, 5, 25) 45 | >>> 46 | >>>pytimekr.samiljeol() # 삼일절 47 | datetime.date(2015, 3, 1) 48 | >>> 49 | >>>pytimekr.constitution() # 제헌절 50 | datetime.datetime(2015, 7, 17) 51 | >>> 52 | >>>pytimekr.constitution() # 제헌절 53 | datetime.datetime(2015, 7, 17) 54 | ``` 55 | 56 | 57 | ## License 58 | 59 | MIT 60 | -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | PyTimeKR 2 | ======== 3 | 4 | PyTimeKR은 `PyTime `__ 의 포크로 대한민국의 공휴일 등 을 추가하였습니다. 5 | 6 | 설치 7 | ------- 8 | 9 | .. code:: python 10 | 11 | pip install pytimekr 12 | 13 | 14 | 간단 사용법 15 | ------------ 16 | 17 | .. code:: python 18 | 19 | >>>from pytimekr import pytimekr 20 | >>> 21 | >>>chuseok = pytimekr.chuseok() # 추석 22 | >>>print chuseok 23 | datetime.date(2015, 9, 27) 24 | >>> 25 | >>>pytimekr.red_days(chuseok) # 추석 연휴 26 | [datetime.date(2015, 9, 26), 27 | datetime.date(2015, 9, 27), 28 | datetime.date(2015, 9, 28), 29 | datetime.date(2015, 9, 29)] 30 | >>> 31 | >>>ly = pytimekr.lunar_newyear(1995) # 1995년도 설날 32 | >>>print ly 33 | datetime.date(1995, 1, 31) 34 | 35 | 다른 공휴일 36 | 37 | .. code:: python 38 | 39 | >>>pytimekr.hangul() # 한글날 40 | datetime.date(2015, 10, 9) 41 | >>> 42 | >>>pytimekr.children() # 어린이날 43 | datetime.date(2015, 5, 5) 44 | >>> 45 | >>>pytimekr.independence() # 광복절 46 | datetime.date(2015, 8, 15) 47 | >>> 48 | >>>pytimekr.memorial() # 현충일 49 | datetime.date(2015, 6, 6) 50 | >>> 51 | >>>pytimekr.buddha() # 석가탄신일 52 | datetime.date(2015, 5, 25) 53 | >>> 54 | >>>pytimekr.samiljeol() # 삼일절 55 | datetime.date(2015, 3, 1) 56 | >>> 57 | >>>pytimekr.constitution() # 제헌절 58 | datetime.datetime(2015, 7, 17) 59 | >>> 60 | >>>pytimekr.constitution() # 제헌절 61 | datetime.datetime(2015, 7, 17) 62 | 63 | 64 | License 65 | ------- 66 | 67 | MIT 68 | -------------------------------------------------------------------------------- /pytimekr/__init__.py: -------------------------------------------------------------------------------- 1 | __title__ = 'pytimekr' 2 | __license__ = 'MIT' 3 | __author__ = 'Parkayun (iamparkayun@gmail.com)' 4 | __version__ = '0.1.0' 5 | -------------------------------------------------------------------------------- /pytimekr/exception.py: -------------------------------------------------------------------------------- 1 | 2 | 3 | class PyTimeException(Exception): 4 | """ A base class for exceptions used by pytime. """ 5 | 6 | 7 | class UnexpectedTypeError(PyTimeException): 8 | """unexpected type appears""" 9 | 10 | 11 | class CanNotFormatError(PyTimeException): 12 | """parameter too odd""" -------------------------------------------------------------------------------- /pytimekr/filter.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # encoding: utf-8 3 | 4 | import sys 5 | import datetime 6 | import re 7 | from itertools import chain 8 | from .exception import UnexpectedTypeError, CanNotFormatError 9 | 10 | 11 | py = sys.version_info 12 | py3k = py >= (3, 0, 0) 13 | py2k = (2, 6, 0) < py < (3, 0, 0) 14 | py25 = py < (2, 6, 0) 15 | 16 | str_tuple = (str,) if py3k else (str, unicode) 17 | 18 | _current = str(datetime.datetime.today().date()) 19 | 20 | 21 | def _exchange_y_d(string, y_l): 22 | d_l = 2 if len(string.split('-')[2]) > 1 else 1 23 | _st_l = list(string) 24 | _st_l[:d_l], _st_l[-y_l:] = _st_l[-y_l:], _st_l[:d_l] 25 | return ''.join(_st_l) 26 | 27 | 28 | # with letter `M` for minutes, `m` for month 29 | UNIT_DICT = {'years': ['years', 'year', 'yea', 'ye', 'yr', 'y', 'Y'], 30 | 'months': ['months', 'month', 'mont', 'mon', 'mo', 'm'], 31 | 'weeks': ['weeks', 'week', 'wee', 'we', 'wk', 'w', 'W'], 32 | 'days': ['days', 'day', 'dy', 'da', 'd', 'D'], 33 | 'hours': ['hours', 'hour', 'hou', 'hr', 'ho', 'h', 'H'], 34 | 'minutes': ['minutes', 'minute', 'minut', 'minu', 'min', 'mi', 'M'], 35 | 'seconds': ['seconds', 'second', 'sec', 'se', 's', 'S']} 36 | 37 | NAMED_MONTHS = {'Jan': 1, 'Feb': 2, 'Mar': 3, 'Apr': 4, 38 | 'May': 5, 'Jun': 6, 'Jul': 7, 'Agu': 8, 39 | 'Sep': 9, 'Oct': 10, 'Nov': 11, 'Dec': 12} 40 | 41 | def filter_unit(arg): 42 | if len(arg) > 1: 43 | arg = arg.lower() 44 | result = [key for key in UNIT_DICT.keys() if arg in UNIT_DICT[key]] 45 | if result: 46 | return result[0] 47 | else: 48 | raise CanNotFormatError 49 | 50 | 51 | class BaseParser(object): 52 | """Parse string to regular datetime/date type 53 | 54 | 1990-10-28 23:23:23 - 19 55 | 90-10-28 23:23:23 - 17 56 | 57 | 1990-10-28 - 10 58 | 28-10-1990 - 10 59 | 1990/10/28 - 10 60 | 28/10/1990 - 10 61 | 1990.10.28 - 10 62 | 28.10.1990 - 10 63 | 64 | 10-28-90 USA - 8 65 | 28-10-90 on hold - 8 66 | 10/28/90 USA - 8 67 | 28/10/90 on hold - 8 68 | 69 | 19901028 - 8 70 | 90-10-28 - 8 71 | 90/10/28 - 8 72 | 73 | 23:23:23 - 8 74 | 75 | 23:23 - 5 76 | 23:2 - 4 77 | 5:14 - 4 78 | 5:2 - 3 79 | """ 80 | 81 | def __init__(self, *args, **kwargs): 82 | pass 83 | 84 | @staticmethod 85 | def _str_parser(string): 86 | """ 87 | return method by the length of string 88 | :param string: string 89 | :return: method 90 | """ 91 | if not any(c.isalpha() for c in string): 92 | _string = string[:19] 93 | _length = len(_string) 94 | if _length > 10: 95 | return BaseParser.parse_datetime 96 | elif 6 <= _length <= 10: 97 | if ':' in _string: 98 | return BaseParser.parse_time 99 | else: 100 | return BaseParser.parse_date 101 | elif _length < 6: 102 | return BaseParser.parse_time 103 | else: 104 | return BaseParser.parse_special 105 | else: 106 | return BaseParser.__parse_not_only_str 107 | 108 | @staticmethod 109 | def __parse_not_only_str(string): 110 | functions_to_try = [BaseParser.from_str, BaseParser.parse_diff] 111 | raised_exception = None 112 | for function in functions_to_try: 113 | try: 114 | return function(string) 115 | except Exception as e: 116 | raised_exception = e 117 | 118 | if raised_exception: 119 | raise raised_exception 120 | 121 | @staticmethod 122 | def _datetime_parser(value): 123 | return value 124 | 125 | @staticmethod 126 | def _timestamp_parser(value): 127 | return datetime.datetime.fromtimestamp(value) 128 | 129 | @staticmethod 130 | def _special_parser(value): 131 | return value 132 | 133 | def _main_parser(func): 134 | def wrapper(cls, *args, **kwargs): 135 | value = args[0] 136 | if isinstance(value, str_tuple): 137 | method = BaseParser._str_parser(value) 138 | elif isinstance(value, (datetime.date, datetime.time, datetime.datetime)): 139 | method = BaseParser._datetime_parser 140 | elif isinstance(value, (int, float)): 141 | method = BaseParser._timestamp_parser 142 | else: 143 | method = BaseParser._special_parser 144 | 145 | if hasattr(method, '__call__'): 146 | return method(value) 147 | else: 148 | raise UnexpectedTypeError( 149 | 'can not generate method for {value} type:{type}'.format(value=value, type=type(value))) 150 | 151 | return wrapper 152 | 153 | @classmethod 154 | @_main_parser 155 | def main(cls, value): 156 | """parse all type value""" 157 | 158 | @staticmethod 159 | def parse_datetime(string, formation=None): 160 | if formation: 161 | _stamp = datetime.datetime.strptime(string, formation) 162 | elif len(string) >= 18: 163 | _stamp = datetime.datetime.strptime(string, '%Y-%m-%d %H:%M:%S') 164 | elif len(string) < 18: 165 | if '-' in string: 166 | _stamp = datetime.datetime.strptime(string, '%y-%m-%d %H:%M:%S') 167 | else: 168 | try: 169 | _stamp = datetime.datetime.strptime(string, '%Y%m%d %H:%M:%S') 170 | except ValueError: 171 | _stamp = datetime.datetime.strptime(string, '%y%m%d %H:%M:%S') 172 | else: 173 | raise CanNotFormatError('Need %Y-%m-%d %H:%M:%S or %y-%m-%d %H:%M:%S') 174 | return _stamp 175 | 176 | @staticmethod 177 | def parse_date(string, formation=None): 178 | """ 179 | string to date stamp 180 | :param string: date string 181 | :param formation: format string 182 | :return: datetime.date 183 | """ 184 | if formation: 185 | _stamp = datetime.datetime.strptime(string, formation).date() 186 | return _stamp 187 | 188 | _string = string.replace('.', '-').replace('/', '-') 189 | if '-' in _string: 190 | if len(_string.split('-')[0]) > 3 or len(_string.split('-')[2]) > 3: 191 | try: 192 | _stamp = datetime.datetime.strptime(_string, '%Y-%m-%d').date() 193 | except ValueError: 194 | try: 195 | _stamp = datetime.datetime.strptime(_string, '%m-%d-%Y').date() 196 | except ValueError: 197 | _stamp = datetime.datetime.strptime(_string, '%d-%m-%Y').date() 198 | else: 199 | try: 200 | _stamp = datetime.datetime.strptime(_string, '%y-%m-%d').date() 201 | except ValueError: 202 | try: 203 | _stamp = datetime.datetime.strptime(_string, '%m-%d-%y').date() 204 | except ValueError: 205 | _stamp = datetime.datetime.strptime(_string, '%d-%m-%y').date() 206 | else: 207 | if len(_string) > 6: 208 | try: 209 | _stamp = datetime.datetime.strptime(_string, '%Y%m%d').date() 210 | except ValueError: 211 | _stamp = datetime.datetime.strptime(_string, '%m%d%Y').date() 212 | elif len(_string) <= 6: 213 | try: 214 | _stamp = datetime.datetime.strptime(_string, '%y%m%d').date() 215 | except ValueError: 216 | _stamp = datetime.datetime.strptime(_string, '%m%d%y').date() 217 | else: 218 | raise CanNotFormatError 219 | return _stamp 220 | 221 | @staticmethod 222 | def parse_time(string): 223 | pass 224 | 225 | @staticmethod 226 | def parse_special(string): 227 | pass 228 | 229 | @staticmethod 230 | def parse_diff(base_str): 231 | """ 232 | parse string to regular timedelta 233 | :param base_str: str 234 | :return: dict 235 | """ 236 | temp_dict = {'years': 0, 237 | 'months': 0, 238 | 'weeks': 0, 239 | 'days': 0, 240 | 'hours': 0, 241 | 'minutes': 0, 242 | 'seconds': 0} 243 | 244 | _pure_str = re.findall("[a-zA-Z]+", base_str) 245 | pure_num = [int(_) for _ in re.findall(r'\d+', base_str)] 246 | pure_str = [filter_unit(_) for _ in _pure_str] 247 | result_dict = dict(chain(temp_dict.items(), dict(zip(pure_str, pure_num)).items())) 248 | if result_dict['months'] >= 12: 249 | advance = result_dict['months'] // 12 250 | remain = result_dict['months'] % 12 251 | result_dict['years'] += advance 252 | result_dict['months'] = remain 253 | if result_dict['weeks']: 254 | result_dict['days'] += result_dict['weeks'] * 7 255 | return result_dict 256 | 257 | @staticmethod 258 | def from_str(date): 259 | """ 260 | Given a date in the format: Jan,21st.2015 261 | will return a datetime of it. 262 | """ 263 | month = date[:3][0] + date[:3][-2:].lower() 264 | if month not in NAMED_MONTHS: 265 | raise CanNotFormatError('Month not recognized') 266 | 267 | date = date.replace(',','').replace(' ', '').replace('.', '') 268 | try: 269 | day_unit = [x for x in ['st', 'rd', 'nd', 'th'] if x in date][0] 270 | day = int(re.search(r'\d+', date.split(day_unit)[0]).group()) 271 | year = int(re.search(r'\d+', date.split(day_unit)[1]).group()) 272 | numeric_month = NAMED_MONTHS[month] 273 | return datetime.date(int(year), numeric_month, day) 274 | except: 275 | raise CanNotFormatError('Not well formated. Expecting something like May,21st.2015') 276 | 277 | if __name__ == "__main__": 278 | BaseParser.main(_current) 279 | -------------------------------------------------------------------------------- /pytimekr/pytimekr.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # encoding: utf-8 3 | 4 | """ 5 | pytimekr 6 | ~~~~~~~~~~~~~ 7 | 8 | fork pytime for Korean. 9 | 10 | :copyright: (c) 2015 by Parkayun 11 | :license: MIT, see LICENSE for more details. 12 | """ 13 | 14 | import datetime 15 | import calendar 16 | 17 | from lunardate import LunarDate 18 | 19 | from .filter import BaseParser, str_tuple 20 | from .exception import CanNotFormatError, UnexpectedTypeError 21 | 22 | 23 | bp = BaseParser.main 24 | dp = BaseParser.parse_diff 25 | 26 | 27 | def parse(value): 28 | return bp(value) 29 | 30 | 31 | def count(value1, value2): 32 | _val1, _val2 = parse(value1), parse(value2) 33 | if type(_val1) == type(_val2): 34 | return _val1 - _val2 35 | else: 36 | _val1 = _val1 if isinstance(_val1, datetime.datetime) else midnight(_val1) 37 | _val2 = _val2 if isinstance(_val2, datetime.datetime) else midnight(_val2) 38 | return _val1 - _val2 39 | 40 | 41 | # max, min 42 | 43 | 44 | _date = datetime.date.today() 45 | _datetime = datetime.datetime.now() 46 | _year = _date.year 47 | _month = _date.month 48 | _day = _date.day 49 | 50 | _SEVEN_DAYS = datetime.timedelta(days=7) 51 | _ONE_DAY = datetime.timedelta(days=1) 52 | 53 | 54 | def today(year=None): 55 | """this day, last year""" 56 | return datetime.date(int(year), _date.month, _date.day) if year else _date 57 | 58 | 59 | def tomorrow(date=None): 60 | """tomorrow is another day""" 61 | if not date: 62 | return _date + datetime.timedelta(days=1) 63 | else: 64 | current_date = parse(date) 65 | return current_date + datetime.timedelta(days=1) 66 | 67 | 68 | def yesterday(date=None): 69 | """yesterday once more""" 70 | if not date: 71 | return _date - datetime.timedelta(days=1) 72 | else: 73 | current_date = parse(date) 74 | return current_date - datetime.timedelta(days=1) 75 | 76 | 77 | ######################## 78 | # function method 79 | ######################## 80 | 81 | 82 | def daysrange(first=None, second=None, wipe=False): 83 | """ 84 | get all days between first and second 85 | 86 | :param first: datetime, date or string 87 | :param second: datetime, date or string 88 | :return: list 89 | """ 90 | _first, _second = parse(first), parse(second) 91 | (_start, _end) = (_second, _first) if _first > _second else (_first, _second) 92 | days_between = (_end - _start).days 93 | date_list = [_end - datetime.timedelta(days=x) for x in range(0, days_between + 1)] 94 | if wipe and len(date_list) >= 2: 95 | date_list = date_list[1:-1] 96 | return date_list 97 | 98 | 99 | def lastday(year=_year, month=_month): 100 | """ 101 | get the current month's last day 102 | :param year: default to current year 103 | :param month: default to current month 104 | :return: month's last day 105 | """ 106 | last_day = calendar.monthrange(year, month)[1] 107 | return datetime.date(year=year, month=month, day=last_day) 108 | 109 | 110 | def midnight(arg=None): 111 | """ 112 | convert date to datetime as midnight or get current day's midnight 113 | :param arg: string or date/datetime 114 | :return: datetime at 00:00:00 115 | """ 116 | if arg: 117 | _arg = parse(arg) 118 | if isinstance(_arg, datetime.date): 119 | return datetime.datetime.combine(_arg, datetime.datetime.min.time()) 120 | elif isinstance(_arg, datetime.datetime): 121 | return datetime.datetime.combine(_arg.date(), datetime.datetime.min.time()) 122 | else: 123 | return datetime.datetime.combine(_date, datetime.datetime.min.time()) 124 | 125 | 126 | def before(base=_datetime, diff=None): 127 | """ 128 | count datetime before `base` time 129 | :param base: minuend -> str/datetime/date 130 | :param diff: str 131 | :return: datetime 132 | """ 133 | _base = parse(base) 134 | if isinstance(_base, datetime.date): 135 | _base = midnight(_base) 136 | if not diff: 137 | return _base 138 | result_dict = dp(diff) 139 | # weeks already convert to days in diff_parse function(dp) 140 | for unit in result_dict: 141 | _val = result_dict[unit] 142 | if not _val: 143 | continue 144 | if unit == 'years': 145 | _base = _base.replace(year=(_base.year - _val)) 146 | elif unit == 'months': 147 | if _base.month <= _val: 148 | _month_diff = 12 - (_val - _base.month) 149 | _base = _base.replace(year=_base.year - 1).replace(month=_month_diff) 150 | else: 151 | _base = _base.replace(month=_base.month - _val) 152 | elif unit in ['days', 'hours', 'minutes', 'seconds']: 153 | _base = _base - datetime.timedelta(**{unit: _val}) 154 | return _base 155 | 156 | 157 | def after(base=_datetime, diff=None): 158 | """ 159 | count datetime after diff args 160 | :param base: str/datetime/date 161 | :param diff: str 162 | :return: datetime 163 | """ 164 | _base = parse(base) 165 | if isinstance(_base, datetime.date): 166 | _base = midnight(_base) 167 | result_dict = dp(diff) 168 | for unit in result_dict: 169 | _val = result_dict[unit] 170 | if not _val: 171 | continue 172 | if unit == 'years': 173 | _base = _base.replace(year=(_base.year + _val)) 174 | elif unit == 'months': 175 | if _base.month + _val <= 12: 176 | _base = _base.replace(month=_base.month + _val) 177 | else: 178 | _month_diff = (_base.month + _val) - 12 179 | _base = _base.replace(year=_base.year + 1).replace(month=_month_diff) 180 | elif unit in ['days', 'hours', 'minutes', 'seconds']: 181 | _base = _base + datetime.timedelta(**{unit: _val}) 182 | return _base 183 | 184 | 185 | def _datetime_to_date(arg): 186 | """ 187 | convert datetime/str to date 188 | :param arg: 189 | :return: 190 | """ 191 | _arg = parse(arg) 192 | if isinstance(_arg, datetime.datetime): 193 | _arg = _arg.date() 194 | return _arg 195 | 196 | 197 | # Monday to Monday -> 00:00:00 to 00:00:00 month 1st - next month 1st 198 | def this_week(arg=_date, clean=False): 199 | _arg = _datetime_to_date(arg) 200 | return _arg - datetime.timedelta(days=_arg.weekday()), _arg + datetime.timedelta( 201 | days=6 - _arg.weekday()) if clean else _arg + datetime.timedelta(days=6 - _arg.weekday()) + _ONE_DAY 202 | 203 | 204 | def last_week(arg=_date, clean=False): 205 | this_week_tuple = this_week(arg) 206 | return this_week_tuple[0] - _SEVEN_DAYS, this_week_tuple[1] - _SEVEN_DAYS if clean \ 207 | else this_week_tuple[1] - _SEVEN_DAYS + _ONE_DAY 208 | 209 | 210 | def next_week(arg=_date, clean=False): 211 | this_week_tuple = this_week(arg) 212 | return this_week_tuple[0] + _SEVEN_DAYS, this_week_tuple[1] + _SEVEN_DAYS if clean \ 213 | else this_week_tuple[1] + _SEVEN_DAYS + _ONE_DAY 214 | 215 | 216 | def this_month(arg=_date, clean=False): 217 | _arg = _datetime_to_date(arg) 218 | return datetime.date(_arg.year, _arg.month, 1), lastday(_arg.year, _arg.month) if clean \ 219 | else lastday(_arg.year, _arg.month) + _ONE_DAY 220 | 221 | 222 | def last_month(arg=_date, clean=False): 223 | _arg = _datetime_to_date(arg) 224 | this_month_first_day = datetime.date(_arg.year, _arg.month, 1) 225 | last_month_last_day = this_month_first_day - _ONE_DAY 226 | last_month_first_day = datetime.date(last_month_last_day.year, last_month_last_day.month, 1) 227 | return last_month_first_day, last_month_last_day if clean else this_month_first_day 228 | 229 | 230 | def next_month(arg=_date, clean=False): 231 | _arg = _datetime_to_date(arg) 232 | this_month_last_day = lastday(_arg.year, _arg.month) 233 | next_month_first_day = this_month_last_day + _ONE_DAY 234 | next_month_last_day = lastday(next_month_first_day.year, next_month_first_day.month) 235 | return next_month_first_day, next_month_last_day if clean else next_month_last_day + _ONE_DAY 236 | 237 | 238 | ###################### 239 | # festival 240 | ###################### 241 | 242 | 243 | def newyear(year=None): 244 | return datetime.date(int(year), 1, 1) if year else datetime.date(_year, 1, 1) 245 | 246 | 247 | def valentine(year=None): 248 | return datetime.date(int(year), 2, 14) if year else datetime.date(_year, 2, 14) 249 | 250 | 251 | def fool(year=None): 252 | return datetime.date(int(year), 4, 1) if year else datetime.date(_year, 4, 1) 253 | 254 | 255 | def christmas(year=None): 256 | return datetime.date(int(year), 12, 25) if year else datetime.date(_year, 12, 25) 257 | 258 | 259 | def christeve(year=None): 260 | return yesterday(christmas(year)) 261 | 262 | 263 | def mother(year=None): 264 | """ 265 | the 2nd Sunday in May 266 | :param year: int 267 | :return: Mother's day 268 | """ 269 | may_first = datetime.date(_year, 5, 1) if not year else datetime.date(int(year), 5, 1) 270 | weekday_seq = may_first.weekday() 271 | return datetime.date(may_first.year, 5, (14 - weekday_seq)) 272 | 273 | 274 | def father(year=None): 275 | """ 276 | the 3rd Sunday in June 277 | :param year: int 278 | :return: Father's day 279 | """ 280 | june_first = datetime.date(_year, 6, 1) if not year else datetime.date(int(year), 6, 1) 281 | weekday_seq = june_first.weekday() 282 | return datetime.date(june_first.year, 6, (21 - weekday_seq)) 283 | 284 | 285 | def halloween(year=None): 286 | return lastday(month=10) if not year else lastday(year, 10) 287 | 288 | 289 | def easter(year=None): 290 | """ 291 | 1900 - 2099 limit 292 | :param year: int 293 | :return: Easter day 294 | """ 295 | y = int(year) if year else _year 296 | n = y - 1900 297 | a = n % 19 298 | q = n // 4 299 | b = (7 * a + 1) // 19 300 | m = (11 * a + 4 - b) % 29 301 | w = (n + q + 31 - m) % 7 302 | d = 25 - m - w 303 | if d > 0: 304 | return datetime.date(y, 4, d) 305 | else: 306 | return datetime.date(y, 3, (31 - d)) 307 | 308 | 309 | def thanks(year=None): 310 | """ 311 | 4rd Thursday in Nov 312 | :param year: int 313 | :return: Thanksgiving Day 314 | """ 315 | nov_first = datetime.date(_year, 11, 1) if not year else datetime.date(int(year), 11, 1) 316 | weekday_seq = nov_first.weekday() 317 | if weekday_seq > 3: 318 | current_day = 32 - weekday_seq 319 | else: 320 | current_day = 25 - weekday_seq 321 | return datetime.date(nov_first.year, 11, current_day) 322 | 323 | 324 | def independence(year=None): 325 | """ 326 | 15, August 327 | :param year: int 328 | :return: Independence Day of Korea 329 | """ 330 | year = year if year else _year 331 | return datetime.date(int(year), 8, 15) 332 | 333 | 334 | def hangul(year=None): 335 | """ 336 | 9, October 337 | :param year: int 338 | :return: Hangul Day 339 | """ 340 | year = year if year else _year 341 | return datetime.date(int(year), 10, 9) 342 | 343 | 344 | def constitution(year=None): 345 | """ 346 | 17, July 347 | :param year: int 348 | :return: Constitution Day of Korea 349 | """ 350 | year = year if year else _year 351 | return datetime.date(int(year), 7, 17) 352 | 353 | 354 | def lunar_newyear(year=None): 355 | """ 356 | :parm year: int 357 | :return: Lunar New Year Day 358 | """ 359 | year = year if year else _year 360 | return LunarDate(year, 1, 1).toSolarDate() 361 | 362 | 363 | def chuseok(year=None): 364 | """ 365 | :parm year: int 366 | :return: Thanksgiving Day of Korea 367 | """ 368 | year = year if year else _year 369 | return LunarDate(year, 8, 15).toSolarDate() 370 | 371 | 372 | def samiljeol(year=None): 373 | """ 374 | :parm year: int 375 | :return: Independence Movement Day of Korea 376 | """ 377 | year = year if year else _year 378 | return datetime.date(int(year), 3, 1) 379 | 380 | 381 | def children(year=None): 382 | """ 383 | :parm year: int 384 | :return: Children's Day of Korea 385 | """ 386 | year = year if year else _year 387 | return datetime.date(int(year), 5, 5) 388 | 389 | 390 | def buddha(year=None): 391 | """ 392 | :parm year: int 393 | :return: Buddha's Birthday Day 394 | """ 395 | year = year if year else _year 396 | return LunarDate(year, 4, 8).toSolarDate() 397 | 398 | 399 | def memorial(year=None): 400 | """ 401 | :parm year: int 402 | :return: Memorial Day of Korea 403 | """ 404 | year = year if year else _year 405 | return datetime.date(int(year), 6, 6) 406 | 407 | 408 | def foundation(year=None): 409 | """ 410 | :parm year: int 411 | :return: National Foundation Day of Korea 412 | """ 413 | year = year if year else _year 414 | return datetime.date(int(year), 10, 3) 415 | 416 | 417 | def red_days(date): 418 | if date == chuseok(date.year) or date == lunar_newyear(date.year): 419 | delta = datetime.timedelta(days=1) 420 | return [date-delta, date, date+delta] 421 | 422 | 423 | def holidays(year=None): 424 | year = year if year else _year 425 | holidays = red_days(lunar_newyear(year)) + red_days(chuseok(year)) 426 | holidays += [ 427 | newyear(year), samiljeol(year), children(year), buddha(year), 428 | memorial(year), independence(year), hangul(year), foundation(year), 429 | christmas(year) 430 | ] 431 | return holidays 432 | 433 | 434 | def is_red_day(date): 435 | weekday = date.isoweekday() 436 | if 6 <= weekday: 437 | return True 438 | for holiday in holidays(date.year): 439 | if date == holiday: 440 | return True 441 | return False 442 | 443 | if __name__ == '__main__': 444 | # _time_filter('2015-01-03') 445 | # print(calendar.monthrange(2015, 10)) 446 | print(bp('2015-01-03')) 447 | -------------------------------------------------------------------------------- /rd_to_rst.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | pandoc --from=markdown --to=rst --output=README.rst README.md 3 | 4 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | unittest2==1.0.1 2 | lunardate==0.1.5 3 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # encoding: utf-8 3 | 4 | from setuptools import setup, find_packages 5 | from codecs import open 6 | from os import path 7 | 8 | 9 | here = path.abspath(path.dirname(__file__)) 10 | with open(path.join(here, 'README.rst'), encoding='utf-8') as f: 11 | long_description = f.read() 12 | 13 | 14 | import pytimekr 15 | setup( 16 | name='pytimekr', 17 | version=pytimekr.__version__, 18 | description='PyTime fork for Korean', 19 | long_description=long_description, 20 | url='https://github.com/Parkayun/PyTimeKR', 21 | author='Parkayun', 22 | author_email='iamparkayun@gmail.com', 23 | license='MIT', 24 | keywords='datetime time datetime timeparser korea holiday', 25 | packages=find_packages(exclude=['contrib', 'docs', 'tests*']), 26 | package_data={'': ['README.md']}, 27 | # tests_require=['coverage'], 28 | # extras_require={ 29 | # 'coveralls': ['coveralls'] 30 | # }, 31 | install_requires=[ 32 | 'lunardate>=0.1.5', 33 | ], 34 | include_package_data=True, 35 | classifiers=[ 36 | 'Intended Audience :: Developers', 37 | 'License :: OSI Approved :: MIT License', 38 | 'Operating System :: OS Independent', 39 | 'Programming Language :: Python', 40 | 'Programming Language :: Python :: 2.6', 41 | 'Programming Language :: Python :: 2.7', 42 | 'Programming Language :: Python :: 3.2', 43 | 'Programming Language :: Python :: 3.3', 44 | 'Programming Language :: Python :: 3.4', 45 | 'Topic :: Software Development :: Libraries :: Python Modules', 46 | 'Programming Language :: Python :: Implementation :: PyPy', 47 | 'Programming Language :: Python :: Implementation :: CPython', 48 | ], 49 | ) 50 | -------------------------------------------------------------------------------- /tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Parkayun/PyTimeKR/ccb926a7572a31eaa2ac70a909d857ae735ca285/tests/__init__.py -------------------------------------------------------------------------------- /tests/test_normal.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # encoding: utf-8 3 | 4 | import sys 5 | import unittest 6 | import os 7 | import time 8 | import datetime 9 | import calendar 10 | import calendar 11 | from pprint import pprint 12 | 13 | sys.path.insert(0, '.') 14 | sys.path.insert(0, '../') 15 | 16 | from pytime import pytime, filter 17 | from pytime import exception 18 | 19 | gmt8offset = time.timezone + 28800 20 | 21 | class TestPyTime(unittest.TestCase): 22 | def setUp(self): 23 | pass 24 | 25 | def test_parse(self): 26 | this1 = pytime.parse('2015-5-17') == datetime.date(2015, 5, 17) 27 | self.assertTrue(this1) 28 | this2 = pytime.parse('2015/5/17') == datetime.date(2015, 5, 17) 29 | self.assertTrue(this2) 30 | this3 = pytime.parse('2015-5-17 23:23:23') == datetime.datetime(2015, 5, 17, 23, 23, 23) 31 | self.assertTrue(this3) 32 | this4 = pytime.parse('15-5-17 23:23:23') == datetime.datetime(2015, 5, 17, 23, 23, 23) 33 | self.assertTrue(this4) 34 | this5 = pytime.parse('2015517') == datetime.date(2015, 5, 17) 35 | self.assertTrue(this5) 36 | this6 = pytime.parse('2015.5.17') == datetime.date(2015, 5, 17) 37 | self.assertTrue(this6) 38 | this7 = pytime.parse('15-5-17') == datetime.date(2015, 5, 17) 39 | self.assertTrue(this7) 40 | this8 = pytime.parse('15.5.17') == datetime.date(2015, 5, 17) 41 | self.assertTrue(this8) 42 | this9 = pytime.parse('15/5/17') == datetime.date(2015, 5, 17) 43 | self.assertTrue(this9) 44 | this10 = pytime.parse('5/17/2015') == datetime.date(2015, 5, 17) 45 | self.assertTrue(this10) 46 | this11 = pytime.parse('17/5/2015') == datetime.date(2015, 5, 17) 47 | self.assertTrue(this11) 48 | this12 = pytime.parse('15517 23:23:23') == datetime.datetime(2015, 5, 17, 23, 23, 23) 49 | self.assertTrue(this12) 50 | this13 = pytime.parse('2015517 23:23:23') == datetime.datetime(2015, 5, 17, 23, 23, 23) 51 | self.assertTrue(this13) 52 | this14 = pytime.parse(1420041600 + gmt8offset) == datetime.datetime(2015, 1, 1, 0, 0, 0) 53 | self.assertTrue(this14) 54 | 55 | def test_count(self): 56 | self.assertEqual(pytime.count('2015517', '2015519'), datetime.timedelta(-2)) 57 | self.assertEqual(pytime.count('2015517', '2015519 23:23:23'), datetime.timedelta(-3, 2197)) 58 | self.assertEqual(pytime.count('2015517 23:23:23', '2015519 23:23:23'), datetime.timedelta(-2)) 59 | self.assertEqual(pytime.count('2015519 23:23:23', '2015-5-17'), datetime.timedelta(2, 84203)) 60 | 61 | def test_function(self): 62 | this1 = pytime.today() == datetime.date.today() 63 | self.assertTrue(this1) 64 | this2 = pytime.today(2014) == datetime.date.today().replace(year=2014) 65 | self.assertTrue(this2) 66 | this3 = pytime.tomorrow() == datetime.date.today() + datetime.timedelta(days=1) 67 | self.assertTrue(this3) 68 | this4 = pytime.tomorrow('2015-5-19') == datetime.date(2015, 5, 20) 69 | self.assertTrue(this4) 70 | this5 = pytime.yesterday() == datetime.date.today() - datetime.timedelta(days=1) 71 | self.assertTrue(this5) 72 | this6 = pytime.yesterday('2015-5-29') == datetime.date(2015, 5, 28) 73 | self.assertTrue(this6) 74 | this7 = pytime.yesterday(1432310400 + gmt8offset) == datetime.datetime(2015, 5, 22) 75 | self.assertTrue(this7) 76 | this8 = pytime.tomorrow(1432310400 + gmt8offset) == datetime.datetime(2015, 5, 24) 77 | self.assertTrue(this8) 78 | 79 | def test_method(self): 80 | self.assertEqual(pytime.daysrange('2015-5-17', '2015-5-21'), 81 | [datetime.date(2015, 5, 21), 82 | datetime.date(2015, 5, 20), 83 | datetime.date(2015, 5, 19), 84 | datetime.date(2015, 5, 18), 85 | datetime.date(2015, 5, 17)]) 86 | self.assertEqual(pytime.daysrange('2015-5-17', '2015-5-21', True), 87 | [datetime.date(2015, 5, 20), 88 | datetime.date(2015, 5, 19), 89 | datetime.date(2015, 5, 18)]) 90 | self.assertEqual(pytime.daysrange(datetime.date(2015, 5, 17), '2015-5-21', True), 91 | pytime.daysrange('2015-5-17', datetime.date(2015, 5, 21), True)) 92 | this1 = pytime.lastday(2015, 5) == datetime.date(2015, 5, 31) 93 | self.assertTrue(this1) 94 | this2 = pytime.lastday(2015) == pytime.lastday() 95 | self.assertTrue(this2) 96 | this3 = pytime.lastday(month=6) == pytime.lastday(2015, 6) 97 | self.assertTrue(this3) 98 | this4 = pytime.midnight('2015-5-17') == datetime.datetime(2015, 5, 17, 0, 0, 0) 99 | self.assertTrue(this4) 100 | this5 = pytime.midnight() == datetime.datetime.combine(datetime.datetime.today(), datetime.datetime.min.time()) 101 | self.assertTrue(this5) 102 | this6 = pytime.before('2015-5-17') == datetime.datetime(2015, 5, 17, 0, 0) 103 | self.assertTrue(this6) 104 | this7 = pytime.before('2015-5-17', '3days 3hou 2mi 1s') == datetime.datetime(2015, 5, 13, 20, 57, 59) 105 | self.assertTrue(this7) 106 | this8 = pytime.before('2015-5-17 23:23:23', '2ye 3mon 2dy 1s') == datetime.datetime(2013, 2, 14, 23, 59, 59) 107 | self.assertTrue(this8) 108 | this9 = pytime.after('2015-5-17', '32month 2days 1years') == datetime.datetime(2019, 1, 19, 0, 0) 109 | self.assertTrue(this9) 110 | this10 = pytime.after('2015-5-17 23:23:23', '59days 280minu, 22222sec') == datetime.datetime(2015, 7, 15, 10, 111 | 50, 22) 112 | self.assertTrue(this10) 113 | this11 = pytime.after('2015-5-17', '59days 9week') == datetime.datetime(2015, 9, 16, 0, 0) 114 | self.assertTrue(this11) 115 | this12 = pytime.before('2015-5-17', '5y 6m 7w 8d 9h 10mi 59s') == datetime.datetime(2009, 9, 20, 14, 49, 1) 116 | self.assertTrue(this12) 117 | 118 | self.assertEqual(pytime.this_week('2015-5-17'), (datetime.date(2015, 5, 11), datetime.date(2015, 5, 18))) 119 | self.assertEqual(pytime.this_week('2015-5-17', True), (datetime.date(2015, 5, 11), datetime.date(2015, 5, 17))) 120 | self.assertEqual(pytime.last_week('2015-5-17'), (datetime.date(2015, 5, 4), datetime.date(2015, 5, 12))) 121 | self.assertEqual(pytime.last_week('2015-5-17', True), (datetime.date(2015, 5, 4), datetime.date(2015, 5, 11))) 122 | self.assertEqual(pytime.next_week('2015-5-17'), (datetime.date(2015, 5, 18), datetime.date(2015, 5, 26))) 123 | self.assertEqual(pytime.next_week('2015-5-17', True), (datetime.date(2015, 5, 18), datetime.date(2015, 5, 25))) 124 | self.assertEqual(pytime.this_month('2015-5-17'), (datetime.date(2015, 5, 1), datetime.date(2015, 6, 1))) 125 | self.assertEqual(pytime.this_month('2015-5-17', True), (datetime.date(2015, 5, 1), datetime.date(2015, 5, 31))) 126 | self.assertEqual(pytime.last_month('2015-5-17'), (datetime.date(2015, 4, 1), datetime.date(2015, 5, 1))) 127 | self.assertEqual(pytime.last_month('2015-5-17', True), (datetime.date(2015, 4, 1), datetime.date(2015, 4, 30))) 128 | self.assertEqual(pytime.next_month('2015-5-17'), (datetime.date(2015, 6, 1), datetime.date(2015, 7, 1))) 129 | self.assertEqual(pytime.next_month('2015-5-17', True), (datetime.date(2015, 6, 1), datetime.date(2015, 6, 30))) 130 | self.assertTrue(pytime.next_month(1432310400 + gmt8offset, True), (datetime.datetime(2015, 6, 1), datetime.datetime(2015, 6, 30))) 131 | 132 | def test_festival(self): 133 | this1 = pytime.newyear(2015) == datetime.date(2015, 1, 1) 134 | self.assertTrue(this1) 135 | this2 = pytime.valentine(2014) == datetime.date(2014, 2, 14) 136 | self.assertTrue(this2) 137 | this3 = pytime.fool(2013) == datetime.date(2013, 4, 1) 138 | self.assertTrue(this3) 139 | this4 = pytime.christmas(2012) == datetime.date(2012, 12, 25) 140 | self.assertTrue(this4) 141 | this5 = pytime.christeve(2011) == datetime.date(2011, 12, 24) 142 | self.assertTrue(this5) 143 | this6 = pytime.mother(2010) == datetime.date(2010, 5, 9) 144 | self.assertTrue(this6) 145 | this7 = pytime.father(2009) == datetime.date(2009, 6, 21) 146 | self.assertTrue(this7) 147 | this8 = pytime.halloween(2008) == datetime.date(2008, 10, 31) 148 | self.assertTrue(this8) 149 | this9 = pytime.easter(2007) == datetime.date(2007, 4, 8) 150 | self.assertTrue(this9) 151 | this10 = pytime.thanks(2006) == datetime.date(2006, 11, 23) 152 | self.assertTrue(this10) 153 | 154 | def test_from_str(self): 155 | self.assertRaises(exception.CanNotFormatError, pytime.parse, 'App.19st,2015') 156 | 157 | #validating the use with blank spaces 158 | self.assertEqual(datetime.date(2015, 1, 1), pytime.parse('Jan.1 st, 2015')) 159 | self.assertEqual(datetime.date(2015, 1, 2), pytime.parse('January 2nd 2015')) 160 | self.assertEqual(datetime.date(2015, 1, 3), pytime.parse('Jan, 3rd 2015')) 161 | 162 | #validating the name of months and the returned datetime 163 | self.assertEqual(datetime.date(2015, 1, 2), pytime.parse('Jan.2st,2015')) 164 | self.assertEqual(datetime.date(2015, 2, 19), pytime.parse('Feb.19st,2015')) 165 | self.assertEqual(datetime.date(2015, 3, 19), pytime.parse('Mar.19st,2015')) 166 | self.assertEqual(datetime.date(2015, 4, 19), pytime.parse('Apr.19st,2015')) 167 | self.assertEqual(datetime.date(2015, 5, 19), pytime.parse('May.19st,2015')) 168 | self.assertEqual(datetime.date(2015, 6, 19), pytime.parse('Jun.19st,2015')) 169 | self.assertEqual(datetime.date(2014, 7, 19), pytime.parse('Jul.19st,2014')) 170 | self.assertEqual(datetime.date(2015, 8, 19), pytime.parse('Agu.19st,2015')) 171 | self.assertEqual(datetime.date(2015, 9, 19), pytime.parse('Sep.19st,2015')) 172 | self.assertEqual(datetime.date(2015, 10, 19), pytime.parse('Oct.19st,2015')) 173 | self.assertEqual(datetime.date(2015, 11, 19), pytime.parse('Nov.19st,2015')) 174 | self.assertEqual(datetime.date(2014, 12, 19), pytime.parse('Dec.19st,2014')) 175 | 176 | 177 | if __name__ == '__main__': 178 | unittest.main() 179 | 180 | --------------------------------------------------------------------------------