From 960e147e10119c9fec0a48988830be8867cea3fd Mon Sep 17 00:00:00 2001 From: JonnyWong16 Date: Sat, 17 Mar 2018 23:08:06 -0700 Subject: [PATCH] Update Arrow to 0.10.0 --- lib/arrow/__init__.py | 2 +- lib/arrow/api.py | 2 +- lib/arrow/arrow.py | 90 +++++++--- lib/arrow/formatter.py | 2 +- lib/arrow/locales.py | 370 +++++++++++++++++++++++++++++++++++++---- lib/arrow/parser.py | 88 ++++++---- lib/arrow/util.py | 2 + 7 files changed, 469 insertions(+), 87 deletions(-) diff --git a/lib/arrow/__init__.py b/lib/arrow/__init__.py index 8407d996..63dd6be9 100644 --- a/lib/arrow/__init__.py +++ b/lib/arrow/__init__.py @@ -4,5 +4,5 @@ from .arrow import Arrow from .factory import ArrowFactory from .api import get, now, utcnow -__version__ = '0.7.0' +__version__ = '0.10.0' VERSION = __version__ diff --git a/lib/arrow/api.py b/lib/arrow/api.py index 495eef49..16de39fe 100644 --- a/lib/arrow/api.py +++ b/lib/arrow/api.py @@ -51,5 +51,5 @@ def factory(type): return ArrowFactory(type) -__all__ = ['get', 'utcnow', 'now', 'factory', 'iso'] +__all__ = ['get', 'utcnow', 'now', 'factory'] diff --git a/lib/arrow/arrow.py b/lib/arrow/arrow.py index d8c63857..131eec07 100644 --- a/lib/arrow/arrow.py +++ b/lib/arrow/arrow.py @@ -12,6 +12,8 @@ from dateutil import tz as dateutil_tz from dateutil.relativedelta import relativedelta import calendar import sys +import warnings + from arrow import util, locales, parser, formatter @@ -45,6 +47,7 @@ class Arrow(object): _ATTRS = ['year', 'month', 'day', 'hour', 'minute', 'second', 'microsecond'] _ATTRS_PLURAL = ['{0}s'.format(a) for a in _ATTRS] + _MONTHS_PER_QUARTER = 3 def __init__(self, year, month, day, hour=0, minute=0, second=0, microsecond=0, tzinfo=None): @@ -306,6 +309,9 @@ class Arrow(object): if name == 'week': return self.isocalendar()[1] + if name == 'quarter': + return int((self.month-1)/self._MONTHS_PER_QUARTER) + 1 + if not name.startswith('_'): value = getattr(self._datetime, name, None) @@ -378,16 +384,16 @@ class Arrow(object): >>> arw.replace(year=2014, month=6) - Use plural property names to shift their current value relatively: - - >>> arw.replace(years=1, months=-1) - - You can also provide a timezone expression can also be replaced: >>> arw.replace(tzinfo=tz.tzlocal()) + Use plural property names to shift their current value relatively (**deprecated**): + + >>> arw.replace(years=1, months=-1) + + Recognized timezone expressions: - A ``tzinfo`` object. @@ -398,21 +404,29 @@ class Arrow(object): ''' absolute_kwargs = {} - relative_kwargs = {} + relative_kwargs = {} # TODO: DEPRECATED; remove in next release for key, value in kwargs.items(): if key in self._ATTRS: absolute_kwargs[key] = value - elif key in self._ATTRS_PLURAL or key == 'weeks': + elif key in self._ATTRS_PLURAL or key in ['weeks', 'quarters']: + # TODO: DEPRECATED + warnings.warn("replace() with plural property to shift value" + "is deprecated, use shift() instead", + DeprecationWarning) relative_kwargs[key] = value - elif key == 'week': - raise AttributeError('setting absolute week is not supported') + elif key in ['week', 'quarter']: + raise AttributeError('setting absolute {0} is not supported'.format(key)) elif key !='tzinfo': - raise AttributeError() + raise AttributeError('unknown attribute: "{0}"'.format(key)) + + # core datetime does not support quarters, translate to months. + relative_kwargs.setdefault('months', 0) + relative_kwargs['months'] += relative_kwargs.pop('quarters', 0) * self._MONTHS_PER_QUARTER current = self._datetime.replace(**absolute_kwargs) - current += relativedelta(**relative_kwargs) + current += relativedelta(**relative_kwargs) # TODO: DEPRECATED tzinfo = kwargs.get('tzinfo') @@ -422,9 +436,41 @@ class Arrow(object): return self.fromdatetime(current) + def shift(self, **kwargs): + ''' Returns a new :class:`Arrow ` object with attributes updated + according to inputs. + + Use plural property names to shift their current value relatively: + + >>> import arrow + >>> arw = arrow.utcnow() + >>> arw + + >>> arw.shift(years=1, months=-1) + + + ''' + + relative_kwargs = {} + + for key, value in kwargs.items(): + + if key in self._ATTRS_PLURAL or key in ['weeks', 'quarters']: + relative_kwargs[key] = value + else: + raise AttributeError() + + # core datetime does not support quarters, translate to months. + relative_kwargs.setdefault('months', 0) + relative_kwargs['months'] += relative_kwargs.pop('quarters', 0) * self._MONTHS_PER_QUARTER + + current = self._datetime + relativedelta(**relative_kwargs) + + return self.fromdatetime(current) + def to(self, tz): - ''' Returns a new :class:`Arrow ` object, converted to the target - timezone. + ''' Returns a new :class:`Arrow ` object, converted + to the target timezone. :param tz: an expression representing a timezone. @@ -587,6 +633,7 @@ class Arrow(object): Defaults to now in the current :class:`Arrow ` object's timezone. :param locale: (optional) a ``str`` specifying a locale. Defaults to 'en_us'. :param only_distance: (optional) returns only time difference eg: "11 seconds" without "in" or "ago" part. + Usage:: >>> earlier = arrow.utcnow().replace(hours=-2) @@ -651,7 +698,8 @@ class Arrow(object): elif diff < 29808000: self_months = self._datetime.year * 12 + self._datetime.month other_months = dt.year * 12 + dt.month - months = sign * abs(other_months - self_months) + + months = sign * int(max(abs(other_months - self_months), 2)) return locale.describe('months', months, only_distance=only_distance) @@ -676,7 +724,7 @@ class Arrow(object): def __sub__(self, other): - if isinstance(other, timedelta): + if isinstance(other, (timedelta, relativedelta)): return self.fromdatetime(self._datetime - other, self._datetime.tzinfo) elif isinstance(other, datetime): @@ -688,7 +736,11 @@ class Arrow(object): raise TypeError() def __rsub__(self, other): - return self.__sub__(other) + + if isinstance(other, datetime): + return other - self._datetime + + raise TypeError() # comparisons @@ -702,8 +754,6 @@ class Arrow(object): if not isinstance(other, (Arrow, datetime)): return False - other = self._get_datetime(other) - return self._datetime == self._get_datetime(other) def __ne__(self, other): @@ -882,7 +932,9 @@ class Arrow(object): return cls.max, limit else: - return end, sys.maxsize + if limit is None: + return end, sys.maxsize + return end, limit @staticmethod def _get_timestamp_from_input(timestamp): diff --git a/lib/arrow/formatter.py b/lib/arrow/formatter.py index 0ae23895..50fd3a17 100644 --- a/lib/arrow/formatter.py +++ b/lib/arrow/formatter.py @@ -94,7 +94,7 @@ class DateTimeFormatter(object): tz = dateutil_tz.tzutc() if dt.tzinfo is None else dt.tzinfo total_minutes = int(util.total_seconds(tz.utcoffset(dt)) / 60) - sign = '+' if total_minutes > 0 else '-' + sign = '+' if total_minutes >= 0 else '-' total_minutes = abs(total_minutes) hour, minute = divmod(total_minutes, 60) diff --git a/lib/arrow/locales.py b/lib/arrow/locales.py index c1141674..7cf7f4c3 100644 --- a/lib/arrow/locales.py +++ b/lib/arrow/locales.py @@ -7,8 +7,8 @@ import sys def get_locale(name): - '''Returns an appropriate :class:`Locale ` corresponding - to an inpute locale name. + '''Returns an appropriate :class:`Locale ` + corresponding to an inpute locale name. :param name: the name of the locale. @@ -186,7 +186,7 @@ class Locale(object): class EnglishLocale(Locale): - names = ['en', 'en_us', 'en_gb', 'en_au', 'en_be', 'en_jp', 'en_za'] + names = ['en', 'en_us', 'en_gb', 'en_au', 'en_be', 'en_jp', 'en_za', 'en_ca'] past = '{0} ago' future = 'in {0}' @@ -263,10 +263,10 @@ class ItalianLocale(Locale): day_names = ['', 'lunedì', 'martedì', 'mercoledì', 'giovedì', 'venerdì', 'sabato', 'domenica'] day_abbreviations = ['', 'lun', 'mar', 'mer', 'gio', 'ven', 'sab', 'dom'] - ordinal_day_re = r'((?P[1-3]?[0-9](?=°))°)' + ordinal_day_re = r'((?P[1-3]?[0-9](?=[ºª]))[ºª])' def _ordinal_number(self, n): - return '{0}°'.format(n) + return '{0}º'.format(n) class SpanishLocale(Locale): @@ -297,10 +297,10 @@ class SpanishLocale(Locale): day_names = ['', 'lunes', 'martes', 'miércoles', 'jueves', 'viernes', 'sábado', 'domingo'] day_abbreviations = ['', 'lun', 'mar', 'mie', 'jue', 'vie', 'sab', 'dom'] - ordinal_day_re = r'((?P[1-3]?[0-9](?=°))°)' + ordinal_day_re = r'((?P[1-3]?[0-9](?=[ºª]))[ºª])' def _ordinal_number(self, n): - return '{0}°'.format(n) + return '{0}º'.format(n) class FrenchLocale(Locale): @@ -379,7 +379,7 @@ class JapaneseLocale(Locale): timeframes = { 'now': '現在', - 'seconds': '秒', + 'seconds': '数秒', 'minute': '1分', 'minutes': '{0}分', 'hour': '1時間', @@ -559,8 +559,8 @@ class KoreanLocale(Locale): timeframes = { 'now': '지금', - 'seconds': '몇초', - 'minute': '일 분', + 'seconds': '몇 초', + 'minute': '1분', 'minutes': '{0}분', 'hour': '1시간', 'hours': '{0}시간', @@ -919,7 +919,7 @@ class NewNorwegianLocale(Locale): class PortugueseLocale(Locale): names = ['pt', 'pt_pt'] - + past = 'há {0}' future = 'em {0}' @@ -946,11 +946,11 @@ class PortugueseLocale(Locale): day_names = ['', 'segunda-feira', 'terça-feira', 'quarta-feira', 'quinta-feira', 'sexta-feira', 'sábado', 'domingo'] day_abbreviations = ['', 'seg', 'ter', 'qua', 'qui', 'sex', 'sab', 'dom'] - - + + class BrazilianPortugueseLocale(PortugueseLocale): names = ['pt_br'] - + past = 'fazem {0}' @@ -1034,7 +1034,7 @@ class TurkishLocale(Locale): 'days': '{0} gün', 'month': 'bir ay', 'months': '{0} ay', - 'year': 'a yıl', + 'year': 'yıl', 'years': '{0} yıl', } @@ -1047,6 +1047,37 @@ class TurkishLocale(Locale): day_abbreviations = ['', 'Pzt', 'Sal', 'Çar', 'Per', 'Cum', 'Cmt', 'Paz'] +class AzerbaijaniLocale(Locale): + + names = ['az', 'az_az'] + + past = '{0} əvvəl' + future = '{0} sonra' + + timeframes = { + 'now': 'indi', + 'seconds': 'saniyə', + 'minute': 'bir dəqiqə', + 'minutes': '{0} dəqiqə', + 'hour': 'bir saat', + 'hours': '{0} saat', + 'day': 'bir gün', + 'days': '{0} gün', + 'month': 'bir ay', + 'months': '{0} ay', + 'year': 'il', + 'years': '{0} il', + } + + month_names = ['', 'Yanvar', 'Fevral', 'Mart', 'Aprel', 'May', 'İyun', 'İyul', + 'Avqust', 'Sentyabr', 'Oktyabr', 'Noyabr', 'Dekabr'] + month_abbreviations = ['', 'Yan', 'Fev', 'Mar', 'Apr', 'May', 'İyn', 'İyl', 'Avq', + 'Sen', 'Okt', 'Noy', 'Dek'] + + day_names = ['', 'Bazar ertəsi', 'Çərşənbə axşamı', 'Çərşənbə', 'Cümə axşamı', 'Cümə', 'Şənbə', 'Bazar'] + day_abbreviations = ['', 'Ber', 'Çax', 'Çər', 'Cax', 'Cüm', 'Şnb', 'Bzr'] + + class ArabicLocale(Locale): names = ['ar', 'ar_eg'] @@ -1205,11 +1236,11 @@ class HindiLocale(Locale): future = '{0} बाद' timeframes = { - 'now': 'अभि', + 'now': 'अभी', 'seconds': 'सेकंड्', 'minute': 'एक मिनट ', 'minutes': '{0} मिनट ', - 'hour': 'एक घंट', + 'hour': 'एक घंटा', 'hours': '{0} घंटे', 'day': 'एक दिन', 'days': '{0} दिन', @@ -1226,8 +1257,8 @@ class HindiLocale(Locale): 'PM': 'शाम', } - month_names = ['', 'जनवरी', 'फ़रवरी', 'मार्च', 'अप्रैल ', 'मई', 'जून', 'जुलाई', - 'आगस्त', 'सितम्बर', 'अकतूबर', 'नवेम्बर', 'दिसम्बर'] + month_names = ['', 'जनवरी', 'फरवरी', 'मार्च', 'अप्रैल ', 'मई', 'जून', 'जुलाई', + 'अगस्त', 'सितंबर', 'अक्टूबर', 'नवंबर', 'दिसंबर'] month_abbreviations = ['', 'जन', 'फ़र', 'मार्च', 'अप्रै', 'मई', 'जून', 'जुलाई', 'आग', 'सित', 'अकत', 'नवे', 'दिस'] @@ -1284,7 +1315,8 @@ class CzechLocale(Locale): def _format_timeframe(self, timeframe, delta): - '''Czech aware time frame format function, takes into account the differences between past and future forms.''' + '''Czech aware time frame format function, takes into account + the differences between past and future forms.''' form = self.timeframes[timeframe] if isinstance(form, dict): if delta == 0: @@ -1293,7 +1325,7 @@ class CzechLocale(Locale): form = form['future'] else: form = form['past'] - delta = abs(delta) + delta = abs(delta) if isinstance(form, list): if 2 <= delta % 10 <= 4 and (delta % 100 < 10 or delta % 100 >= 20): @@ -1303,6 +1335,78 @@ class CzechLocale(Locale): return form.format(delta) + +class SlovakLocale(Locale): + names = ['sk', 'sk_sk'] + + timeframes = { + 'now': 'Teraz', + 'seconds': { + 'past': 'pár sekundami', + 'future': ['{0} sekundy', '{0} sekúnd'] + }, + 'minute': {'past': 'minútou', 'future': 'minútu', 'zero': '{0} minút'}, + 'minutes': { + 'past': '{0} minútami', + 'future': ['{0} minúty', '{0} minút'] + }, + 'hour': {'past': 'hodinou', 'future': 'hodinu', 'zero': '{0} hodín'}, + 'hours': { + 'past': '{0} hodinami', + 'future': ['{0} hodiny', '{0} hodín'] + }, + 'day': {'past': 'dňom', 'future': 'deň', 'zero': '{0} dní'}, + 'days': { + 'past': '{0} dňami', + 'future': ['{0} dni', '{0} dní'] + }, + 'month': {'past': 'mesiacom', 'future': 'mesiac', 'zero': '{0} mesiacov'}, + 'months': { + 'past': '{0} mesiacmi', + 'future': ['{0} mesiace', '{0} mesiacov'] + }, + 'year': {'past': 'rokom', 'future': 'rok', 'zero': '{0} rokov'}, + 'years': { + 'past': '{0} rokmi', + 'future': ['{0} roky', '{0} rokov'] + } + } + + past = 'Pred {0}' + future = 'O {0}' + + month_names = ['', 'január', 'február', 'marec', 'apríl', 'máj', 'jún', + 'júl', 'august', 'september', 'október', 'november', 'december'] + month_abbreviations = ['', 'jan', 'feb', 'mar', 'apr', 'máj', 'jún', 'júl', + 'aug', 'sep', 'okt', 'nov', 'dec'] + + day_names = ['', 'pondelok', 'utorok', 'streda', 'štvrtok', 'piatok', + 'sobota', 'nedeľa'] + day_abbreviations = ['', 'po', 'ut', 'st', 'št', 'pi', 'so', 'ne'] + + + def _format_timeframe(self, timeframe, delta): + '''Slovak aware time frame format function, takes into account + the differences between past and future forms.''' + form = self.timeframes[timeframe] + if isinstance(form, dict): + if delta == 0: + form = form['zero'] # And *never* use 0 in the singular! + elif delta > 0: + form = form['future'] + else: + form = form['past'] + delta = abs(delta) + + if isinstance(form, list): + if 2 <= delta % 10 <= 4 and (delta % 100 < 10 or delta % 100 >= 20): + form = form[0] + else: + form = form[1] + + return form.format(delta) + + class FarsiLocale(Locale): names = ['fa', 'fa_ir'] @@ -1463,7 +1567,7 @@ class MarathiLocale(Locale): day_names = ['', 'सोमवार', 'मंगळवार', 'बुधवार', 'गुरुवार', 'शुक्रवार', 'शनिवार', 'रविवार'] day_abbreviations = ['', 'सोम', 'मंगळ', 'बुध', 'गुरु', 'शुक्र', 'शनि', 'रवि'] - + def _map_locales(): locales = {} @@ -1471,14 +1575,14 @@ def _map_locales(): for cls_name, cls in inspect.getmembers(sys.modules[__name__], inspect.isclass): if issubclass(cls, Locale): for name in cls.names: - locales[name.lower()] = cls + locales[name.lower()] = cls return locales -class CatalaLocale(Locale): - names = ['ca', 'ca_ca'] +class CatalanLocale(Locale): + names = ['ca', 'ca_es', 'ca_ad', 'ca_fr', 'ca_it'] past = 'Fa {0}' - future = '{0}' # I don't know what's the right phrase in catala for the future. + future = 'En {0}' timeframes = { 'now': 'Ara mateix', @@ -1490,15 +1594,15 @@ class CatalaLocale(Locale): 'day': 'un dia', 'days': '{0} dies', 'month': 'un mes', - 'months': '{0} messos', + 'months': '{0} mesos', 'year': 'un any', 'years': '{0} anys', } - month_names = ['', 'Jener', 'Febrer', 'Març', 'Abril', 'Maig', 'Juny', 'Juliol', 'Agost', 'Setembre', 'Octubre', 'Novembre', 'Decembre'] - month_abbreviations = ['', 'Jener', 'Febrer', 'Març', 'Abril', 'Maig', 'Juny', 'Juliol', 'Agost', 'Setembre', 'Octubre', 'Novembre', 'Decembre'] - day_names = ['', 'Dilluns', 'Dimars', 'Dimecres', 'Dijous', 'Divendres', 'Disabte', 'Diumenge'] - day_abbreviations = ['', 'Dilluns', 'Dimars', 'Dimecres', 'Dijous', 'Divendres', 'Disabte', 'Diumenge'] + month_names = ['', 'Gener', 'Febrer', 'Març', 'Abril', 'Maig', 'Juny', 'Juliol', 'Agost', 'Setembre', 'Octubre', 'Novembre', 'Desembre'] + month_abbreviations = ['', 'Gener', 'Febrer', 'Març', 'Abril', 'Maig', 'Juny', 'Juliol', 'Agost', 'Setembre', 'Octubre', 'Novembre', 'Desembre'] + day_names = ['', 'Dilluns', 'Dimarts', 'Dimecres', 'Dijous', 'Divendres', 'Dissabte', 'Diumenge'] + day_abbreviations = ['', 'Dilluns', 'Dimarts', 'Dimecres', 'Dijous', 'Divendres', 'Dissabte', 'Diumenge'] class BasqueLocale(Locale): names = ['eu', 'eu_eu'] @@ -1587,6 +1691,50 @@ class HungarianLocale(Locale): return form.format(abs(delta)) +class EsperantoLocale(Locale): + names = ['eo', 'eo_xx'] + past = 'antaŭ {0}' + future = 'post {0}' + + timeframes = { + 'now': 'nun', + 'seconds': 'kelkaj sekundoj', + 'minute': 'unu minuto', + 'minutes': '{0} minutoj', + 'hour': 'un horo', + 'hours': '{0} horoj', + 'day': 'unu tago', + 'days': '{0} tagoj', + 'month': 'unu monato', + 'months': '{0} monatoj', + 'year': 'unu jaro', + 'years': '{0} jaroj', + } + + month_names = ['', 'januaro', 'februaro', 'marto', 'aprilo', 'majo', + 'junio', 'julio', 'aŭgusto', 'septembro', 'oktobro', + 'novembro', 'decembro'] + month_abbreviations = ['', 'jan', 'feb', 'mar', 'apr', 'maj', 'jun', + 'jul', 'aŭg', 'sep', 'okt', 'nov', 'dec'] + + day_names = ['', 'lundo', 'mardo', 'merkredo', 'ĵaŭdo', 'vendredo', + 'sabato', 'dimanĉo'] + day_abbreviations = ['', 'lun', 'mar', 'mer', 'ĵaŭ', 'ven', + 'sab', 'dim'] + + meridians = { + 'am': 'atm', + 'pm': 'ptm', + 'AM': 'ATM', + 'PM': 'PTM', + } + + ordinal_day_re = r'((?P[1-3]?[0-9](?=a))a)' + + def _ordinal_number(self, n): + return '{0}a'.format(n) + + class ThaiLocale(Locale): names = ['th', 'th_th'] @@ -1700,4 +1848,164 @@ class BengaliLocale(Locale): return '{0}ষ্ঠ'.format(n) +class RomanshLocale(Locale): + + names = ['rm', 'rm_ch'] + + past = 'avant {0}' + future = 'en {0}' + + timeframes = { + 'now': 'en quest mument', + 'seconds': 'secundas', + 'minute': 'ina minuta', + 'minutes': '{0} minutas', + 'hour': 'in\'ura', + 'hours': '{0} ura', + 'day': 'in di', + 'days': '{0} dis', + 'month': 'in mais', + 'months': '{0} mais', + 'year': 'in onn', + 'years': '{0} onns', + } + + month_names = [ + '', 'schaner', 'favrer', 'mars', 'avrigl', 'matg', 'zercladur', + 'fanadur', 'avust', 'settember', 'october', 'november', 'december' + ] + + month_abbreviations = [ + '', 'schan', 'fav', 'mars', 'avr', 'matg', 'zer', 'fan', 'avu', + 'set', 'oct', 'nov', 'dec' + ] + + day_names = [ + '', 'glindesdi', 'mardi', 'mesemna', 'gievgia', 'venderdi', + 'sonda', 'dumengia' + ] + + day_abbreviations = [ + '', 'gli', 'ma', 'me', 'gie', 've', 'so', 'du' + ] + + +class SwissLocale(Locale): + + names = ['de', 'de_ch'] + + past = 'vor {0}' + future = 'in {0}' + + timeframes = { + 'now': 'gerade eben', + 'seconds': 'Sekunden', + 'minute': 'einer Minute', + 'minutes': '{0} Minuten', + 'hour': 'einer Stunde', + 'hours': '{0} Stunden', + 'day': 'einem Tag', + 'days': '{0} Tage', + 'month': 'einem Monat', + 'months': '{0} Monaten', + 'year': 'einem Jahr', + 'years': '{0} Jahren', + } + + month_names = [ + '', 'Januar', 'Februar', 'März', 'April', 'Mai', 'Juni', 'Juli', + 'August', 'September', 'Oktober', 'November', 'Dezember' + ] + + month_abbreviations = [ + '', 'Jan', 'Feb', 'Mär', 'Apr', 'Mai', 'Jun', 'Jul', 'Aug', 'Sep', + 'Okt', 'Nov', 'Dez' + ] + + day_names = [ + '', 'Montag', 'Dienstag', 'Mittwoch', 'Donnerstag', 'Freitag', + 'Samstag', 'Sonntag' + ] + + day_abbreviations = [ + '', 'Mo', 'Di', 'Mi', 'Do', 'Fr', 'Sa', 'So' + ] + + +class RomanianLocale(Locale): + names = ['ro', 'ro_ro'] + + past = '{0} în urmă' + future = 'peste {0}' + + timeframes = { + 'now': 'acum', + 'seconds': 'câteva secunde', + 'minute': 'un minut', + 'minutes': '{0} minute', + 'hour': 'o oră', + 'hours': '{0} ore', + 'day': 'o zi', + 'days': '{0} zile', + 'month': 'o lună', + 'months': '{0} luni', + 'year': 'un an', + 'years': '{0} ani', + } + + month_names = ['', 'ianuarie', 'februarie', 'martie', 'aprilie', 'mai', 'iunie', 'iulie', + 'august', 'septembrie', 'octombrie', 'noiembrie', 'decembrie'] + month_abbreviations = ['', 'ian', 'febr', 'mart', 'apr', 'mai', 'iun', 'iul', 'aug', 'sept', 'oct', 'nov', 'dec'] + + day_names = ['', 'luni', 'marți', 'miercuri', 'joi', 'vineri', 'sâmbătă', 'duminică'] + day_abbreviations = ['', 'Lun', 'Mar', 'Mie', 'Joi', 'Vin', 'Sâm', 'Dum'] + + +class SlovenianLocale(Locale): + names = ['sl', 'sl_si'] + + past = 'pred {0}' + future = 'čez {0}' + + timeframes = { + 'now': 'zdaj', + 'seconds': 'sekund', + 'minute': 'minuta', + 'minutes': '{0} minutami', + 'hour': 'uro', + 'hours': '{0} ur', + 'day': 'dan', + 'days': '{0} dni', + 'month': 'mesec', + 'months': '{0} mesecev', + 'year': 'leto', + 'years': '{0} let', + } + + meridians = { + 'am': '', + 'pm': '', + 'AM': '', + 'PM': '', + } + + month_names = [ + '', 'Januar', 'Februar', 'Marec', 'April', 'Maj', 'Junij', 'Julij', + 'Avgust', 'September', 'Oktober', 'November', 'December' + ] + + month_abbreviations = [ + '', 'Jan', 'Feb', 'Mar', 'Apr', 'Maj', 'Jun', 'Jul', 'Avg', + 'Sep', 'Okt', 'Nov', 'Dec' + ] + + day_names = [ + '', 'Ponedeljek', 'Torek', 'Sreda', 'Četrtek', 'Petek', 'Sobota', 'Nedelja' + ] + + day_abbreviations = [ + '', 'Pon', 'Tor', 'Sre', 'Čet', 'Pet', 'Sob', 'Ned' + ] + + _locales = _map_locales() diff --git a/lib/arrow/parser.py b/lib/arrow/parser.py index 2c204aee..f3ed56cf 100644 --- a/lib/arrow/parser.py +++ b/lib/arrow/parser.py @@ -5,7 +5,6 @@ from __future__ import unicode_literals from datetime import datetime from dateutil import tz import re - from arrow import locales @@ -15,16 +14,14 @@ class ParserError(RuntimeError): class DateTimeParser(object): - _FORMAT_RE = re.compile('(YYY?Y?|MM?M?M?|Do|DD?D?D?|HH?|hh?|mm?|ss?|SS?S?S?S?S?|ZZ?Z?|a|A|X)') + _FORMAT_RE = re.compile('(YYY?Y?|MM?M?M?|Do|DD?D?D?|d?d?d?d|HH?|hh?|mm?|ss?|S+|ZZ?Z?|a|A|X)') + _ESCAPE_RE = re.compile('\[[^\[\]]*\]') - _ONE_THROUGH_SIX_DIGIT_RE = re.compile('\d{1,6}') - _ONE_THROUGH_FIVE_DIGIT_RE = re.compile('\d{1,5}') - _ONE_THROUGH_FOUR_DIGIT_RE = re.compile('\d{1,4}') - _ONE_TWO_OR_THREE_DIGIT_RE = re.compile('\d{1,3}') + _ONE_OR_MORE_DIGIT_RE = re.compile('\d+') _ONE_OR_TWO_DIGIT_RE = re.compile('\d{1,2}') _FOUR_DIGIT_RE = re.compile('\d{4}') _TWO_DIGIT_RE = re.compile('\d{2}') - _TZ_RE = re.compile('[+\-]?\d{2}:?\d{2}') + _TZ_RE = re.compile('[+\-]?\d{2}:?(\d{2})?') _TZ_NAME_RE = re.compile('\w[\w+\-/]+') @@ -47,12 +44,7 @@ class DateTimeParser(object): 'ZZZ': _TZ_NAME_RE, 'ZZ': _TZ_RE, 'Z': _TZ_RE, - 'SSSSSS': _ONE_THROUGH_SIX_DIGIT_RE, - 'SSSSS': _ONE_THROUGH_FIVE_DIGIT_RE, - 'SSSS': _ONE_THROUGH_FOUR_DIGIT_RE, - 'SSS': _ONE_TWO_OR_THREE_DIGIT_RE, - 'SS': _ONE_OR_TWO_DIGIT_RE, - 'S': re.compile('\d'), + 'S': _ONE_OR_MORE_DIGIT_RE, } MARKERS = ['YYYY', 'MM', 'DD'] @@ -67,6 +59,10 @@ class DateTimeParser(object): 'MMM': self._choice_re(self.locale.month_abbreviations[1:], re.IGNORECASE), 'Do': re.compile(self.locale.ordinal_day_re), + 'dddd': self._choice_re(self.locale.day_names[1:], re.IGNORECASE), + 'ddd': self._choice_re(self.locale.day_abbreviations[1:], + re.IGNORECASE), + 'd' : re.compile("[1-7]"), 'a': self._choice_re( (self.locale.meridians['am'], self.locale.meridians['pm']) ), @@ -88,11 +84,10 @@ class DateTimeParser(object): time_parts = re.split('[+-]', time_string, 1) has_tz = len(time_parts) > 1 has_seconds = time_parts[0].count(':') > 1 - has_subseconds = '.' in time_parts[0] + has_subseconds = re.search('[.,]', time_parts[0]) if has_subseconds: - subseconds_token = 'S' * min(len(re.split('\D+', time_parts[0].split('.')[1], 1)[0]), 6) - formats = ['YYYY-MM-DDTHH:mm:ss.%s' % subseconds_token] + formats = ['YYYY-MM-DDTHH:mm:ss%sS' % has_subseconds.group()] elif has_seconds: formats = ['YYYY-MM-DDTHH:mm:ss'] else: @@ -123,10 +118,18 @@ class DateTimeParser(object): # we construct a new string by replacing each # token by its pattern: # 'YYYY-MM-DD' -> '(?P\d{4})-(?P\d{2})-(?P
\d{2})' - fmt_pattern = fmt tokens = [] offset = 0 - for m in self._FORMAT_RE.finditer(fmt): + + # Extract the bracketed expressions to be reinserted later. + escaped_fmt = re.sub(self._ESCAPE_RE, "#" , fmt) + # Any number of S is the same as one. + escaped_fmt = re.sub('S+', 'S', escaped_fmt) + escaped_data = re.findall(self._ESCAPE_RE, fmt) + + fmt_pattern = escaped_fmt + + for m in self._FORMAT_RE.finditer(escaped_fmt): token = m.group(0) try: input_re = self._input_re_map[token] @@ -140,9 +143,20 @@ class DateTimeParser(object): # are returned in the order found by finditer. fmt_pattern = fmt_pattern[:m.start() + offset] + input_pattern + fmt_pattern[m.end() + offset:] offset += len(input_pattern) - (m.end() - m.start()) - match = re.search(fmt_pattern, string, flags=re.IGNORECASE) + + final_fmt_pattern = "" + a = fmt_pattern.split("#") + b = escaped_data + + # Due to the way Python splits, 'a' will always be longer + for i in range(len(a)): + final_fmt_pattern += a[i] + if i < len(b): + final_fmt_pattern += b[i][1:-1] + + match = re.search(final_fmt_pattern, string, flags=re.IGNORECASE) if match is None: - raise ParserError('Failed to match \'{0}\' when parsing \'{1}\''.format(fmt_pattern, string)) + raise ParserError('Failed to match \'{0}\' when parsing \'{1}\''.format(final_fmt_pattern, string)) parts = {} for token in tokens: if token == 'Do': @@ -181,18 +195,22 @@ class DateTimeParser(object): elif token in ['ss', 's']: parts['second'] = int(value) - elif token == 'SSSSSS': - parts['microsecond'] = int(value) - elif token == 'SSSSS': - parts['microsecond'] = int(value) * 10 - elif token == 'SSSS': - parts['microsecond'] = int(value) * 100 - elif token == 'SSS': - parts['microsecond'] = int(value) * 1000 - elif token == 'SS': - parts['microsecond'] = int(value) * 10000 elif token == 'S': - parts['microsecond'] = int(value) * 100000 + # We have the *most significant* digits of an arbitrary-precision integer. + # We want the six most significant digits as an integer, rounded. + # FIXME: add nanosecond support somehow? + value = value.ljust(7, str('0')) + + # floating-point (IEEE-754) defaults to half-to-even rounding + seventh_digit = int(value[6]) + if seventh_digit == 5: + rounding = int(value[5]) % 2 + elif seventh_digit > 5: + rounding = 1 + else: + rounding = 0 + + parts['microsecond'] = int(value[:6]) + rounding elif token == 'X': parts['timestamp'] = int(value) @@ -242,7 +260,7 @@ class DateTimeParser(object): try: _datetime = self.parse(string, fmt) break - except: + except ParserError: pass if _datetime is None: @@ -273,7 +291,7 @@ class DateTimeParser(object): class TzinfoParser(object): - _TZINFO_RE = re.compile('([+\-])?(\d\d):?(\d\d)') + _TZINFO_RE = re.compile('([+\-])?(\d\d):?(\d\d)?') @classmethod def parse(cls, string): @@ -292,6 +310,8 @@ class TzinfoParser(object): if iso_match: sign, hours, minutes = iso_match.groups() + if minutes is None: + minutes = 0 seconds = int(hours) * 3600 + int(minutes) * 60 if sign == '-': @@ -303,6 +323,6 @@ class TzinfoParser(object): tzinfo = tz.gettz(string) if tzinfo is None: - raise ParserError('Could not parse timezone expression "{0}"', string) + raise ParserError('Could not parse timezone expression "{0}"'.format(string)) return tzinfo diff --git a/lib/arrow/util.py b/lib/arrow/util.py index 546cff2c..3eed4faa 100644 --- a/lib/arrow/util.py +++ b/lib/arrow/util.py @@ -22,6 +22,8 @@ else: # pragma: no cover total_seconds = _total_seconds_27 def is_timestamp(value): + if type(value) == bool: + return False try: float(value) return True