Update Arrow to 0.10.0

This commit is contained in:
JonnyWong16 2018-03-17 23:08:06 -07:00
parent bbca0b3b42
commit 960e147e10
7 changed files with 469 additions and 87 deletions

View file

@ -4,5 +4,5 @@ from .arrow import Arrow
from .factory import ArrowFactory from .factory import ArrowFactory
from .api import get, now, utcnow from .api import get, now, utcnow
__version__ = '0.7.0' __version__ = '0.10.0'
VERSION = __version__ VERSION = __version__

View file

@ -51,5 +51,5 @@ def factory(type):
return ArrowFactory(type) return ArrowFactory(type)
__all__ = ['get', 'utcnow', 'now', 'factory', 'iso'] __all__ = ['get', 'utcnow', 'now', 'factory']

View file

@ -12,6 +12,8 @@ from dateutil import tz as dateutil_tz
from dateutil.relativedelta import relativedelta from dateutil.relativedelta import relativedelta
import calendar import calendar
import sys import sys
import warnings
from arrow import util, locales, parser, formatter from arrow import util, locales, parser, formatter
@ -45,6 +47,7 @@ class Arrow(object):
_ATTRS = ['year', 'month', 'day', 'hour', 'minute', 'second', 'microsecond'] _ATTRS = ['year', 'month', 'day', 'hour', 'minute', 'second', 'microsecond']
_ATTRS_PLURAL = ['{0}s'.format(a) for a in _ATTRS] _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, def __init__(self, year, month, day, hour=0, minute=0, second=0, microsecond=0,
tzinfo=None): tzinfo=None):
@ -306,6 +309,9 @@ class Arrow(object):
if name == 'week': if name == 'week':
return self.isocalendar()[1] return self.isocalendar()[1]
if name == 'quarter':
return int((self.month-1)/self._MONTHS_PER_QUARTER) + 1
if not name.startswith('_'): if not name.startswith('_'):
value = getattr(self._datetime, name, None) value = getattr(self._datetime, name, None)
@ -378,16 +384,16 @@ class Arrow(object):
>>> arw.replace(year=2014, month=6) >>> arw.replace(year=2014, month=6)
<Arrow [2014-06-11T22:27:34.787885+00:00]> <Arrow [2014-06-11T22:27:34.787885+00:00]>
Use plural property names to shift their current value relatively:
>>> arw.replace(years=1, months=-1)
<Arrow [2014-04-11T22:27:34.787885+00:00]>
You can also provide a timezone expression can also be replaced: You can also provide a timezone expression can also be replaced:
>>> arw.replace(tzinfo=tz.tzlocal()) >>> arw.replace(tzinfo=tz.tzlocal())
<Arrow [2013-05-11T22:27:34.787885-07:00]> <Arrow [2013-05-11T22:27:34.787885-07:00]>
Use plural property names to shift their current value relatively (**deprecated**):
>>> arw.replace(years=1, months=-1)
<Arrow [2014-04-11T22:27:34.787885+00:00]>
Recognized timezone expressions: Recognized timezone expressions:
- A ``tzinfo`` object. - A ``tzinfo`` object.
@ -398,21 +404,29 @@ class Arrow(object):
''' '''
absolute_kwargs = {} absolute_kwargs = {}
relative_kwargs = {} relative_kwargs = {} # TODO: DEPRECATED; remove in next release
for key, value in kwargs.items(): for key, value in kwargs.items():
if key in self._ATTRS: if key in self._ATTRS:
absolute_kwargs[key] = value 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 relative_kwargs[key] = value
elif key == 'week': elif key in ['week', 'quarter']:
raise AttributeError('setting absolute week is not supported') raise AttributeError('setting absolute {0} is not supported'.format(key))
elif key !='tzinfo': 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 = self._datetime.replace(**absolute_kwargs)
current += relativedelta(**relative_kwargs) current += relativedelta(**relative_kwargs) # TODO: DEPRECATED
tzinfo = kwargs.get('tzinfo') tzinfo = kwargs.get('tzinfo')
@ -422,9 +436,41 @@ class Arrow(object):
return self.fromdatetime(current) return self.fromdatetime(current)
def shift(self, **kwargs):
''' Returns a new :class:`Arrow <arrow.arrow.Arrow>` object with attributes updated
according to inputs.
Use plural property names to shift their current value relatively:
>>> import arrow
>>> arw = arrow.utcnow()
>>> arw
<Arrow [2013-05-11T22:27:34.787885+00:00]>
>>> arw.shift(years=1, months=-1)
<Arrow [2014-04-11T22:27:34.787885+00:00]>
'''
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): def to(self, tz):
''' Returns a new :class:`Arrow <arrow.arrow.Arrow>` object, converted to the target ''' Returns a new :class:`Arrow <arrow.arrow.Arrow>` object, converted
timezone. to the target timezone.
:param tz: an expression representing a timezone. :param tz: an expression representing a timezone.
@ -587,6 +633,7 @@ class Arrow(object):
Defaults to now in the current :class:`Arrow <arrow.arrow.Arrow>` object's timezone. Defaults to now in the current :class:`Arrow <arrow.arrow.Arrow>` object's timezone.
:param locale: (optional) a ``str`` specifying a locale. Defaults to 'en_us'. :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. :param only_distance: (optional) returns only time difference eg: "11 seconds" without "in" or "ago" part.
Usage:: Usage::
>>> earlier = arrow.utcnow().replace(hours=-2) >>> earlier = arrow.utcnow().replace(hours=-2)
@ -651,7 +698,8 @@ class Arrow(object):
elif diff < 29808000: elif diff < 29808000:
self_months = self._datetime.year * 12 + self._datetime.month self_months = self._datetime.year * 12 + self._datetime.month
other_months = dt.year * 12 + dt.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) return locale.describe('months', months, only_distance=only_distance)
@ -676,7 +724,7 @@ class Arrow(object):
def __sub__(self, other): def __sub__(self, other):
if isinstance(other, timedelta): if isinstance(other, (timedelta, relativedelta)):
return self.fromdatetime(self._datetime - other, self._datetime.tzinfo) return self.fromdatetime(self._datetime - other, self._datetime.tzinfo)
elif isinstance(other, datetime): elif isinstance(other, datetime):
@ -688,7 +736,11 @@ class Arrow(object):
raise TypeError() raise TypeError()
def __rsub__(self, other): def __rsub__(self, other):
return self.__sub__(other)
if isinstance(other, datetime):
return other - self._datetime
raise TypeError()
# comparisons # comparisons
@ -702,8 +754,6 @@ class Arrow(object):
if not isinstance(other, (Arrow, datetime)): if not isinstance(other, (Arrow, datetime)):
return False return False
other = self._get_datetime(other)
return self._datetime == self._get_datetime(other) return self._datetime == self._get_datetime(other)
def __ne__(self, other): def __ne__(self, other):
@ -882,7 +932,9 @@ class Arrow(object):
return cls.max, limit return cls.max, limit
else: else:
return end, sys.maxsize if limit is None:
return end, sys.maxsize
return end, limit
@staticmethod @staticmethod
def _get_timestamp_from_input(timestamp): def _get_timestamp_from_input(timestamp):

View file

@ -94,7 +94,7 @@ class DateTimeFormatter(object):
tz = dateutil_tz.tzutc() if dt.tzinfo is None else dt.tzinfo tz = dateutil_tz.tzutc() if dt.tzinfo is None else dt.tzinfo
total_minutes = int(util.total_seconds(tz.utcoffset(dt)) / 60) 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) total_minutes = abs(total_minutes)
hour, minute = divmod(total_minutes, 60) hour, minute = divmod(total_minutes, 60)

View file

@ -7,8 +7,8 @@ import sys
def get_locale(name): def get_locale(name):
'''Returns an appropriate :class:`Locale <locale.Locale>` corresponding '''Returns an appropriate :class:`Locale <arrow.locales.Locale>`
to an inpute locale name. corresponding to an inpute locale name.
:param name: the name of the locale. :param name: the name of the locale.
@ -186,7 +186,7 @@ class Locale(object):
class EnglishLocale(Locale): 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' past = '{0} ago'
future = 'in {0}' future = 'in {0}'
@ -263,10 +263,10 @@ class ItalianLocale(Locale):
day_names = ['', 'lunedì', 'martedì', 'mercoledì', 'giovedì', 'venerdì', 'sabato', 'domenica'] day_names = ['', 'lunedì', 'martedì', 'mercoledì', 'giovedì', 'venerdì', 'sabato', 'domenica']
day_abbreviations = ['', 'lun', 'mar', 'mer', 'gio', 'ven', 'sab', 'dom'] day_abbreviations = ['', 'lun', 'mar', 'mer', 'gio', 'ven', 'sab', 'dom']
ordinal_day_re = r'((?P<value>[1-3]?[0-9](?=°))°)' ordinal_day_re = r'((?P<value>[1-3]?[0-9](?=[ºª]))[ºª])'
def _ordinal_number(self, n): def _ordinal_number(self, n):
return '{0}°'.format(n) return '{0}º'.format(n)
class SpanishLocale(Locale): class SpanishLocale(Locale):
@ -297,10 +297,10 @@ class SpanishLocale(Locale):
day_names = ['', 'lunes', 'martes', 'miércoles', 'jueves', 'viernes', 'sábado', 'domingo'] day_names = ['', 'lunes', 'martes', 'miércoles', 'jueves', 'viernes', 'sábado', 'domingo']
day_abbreviations = ['', 'lun', 'mar', 'mie', 'jue', 'vie', 'sab', 'dom'] day_abbreviations = ['', 'lun', 'mar', 'mie', 'jue', 'vie', 'sab', 'dom']
ordinal_day_re = r'((?P<value>[1-3]?[0-9](?=°))°)' ordinal_day_re = r'((?P<value>[1-3]?[0-9](?=[ºª]))[ºª])'
def _ordinal_number(self, n): def _ordinal_number(self, n):
return '{0}°'.format(n) return '{0}º'.format(n)
class FrenchLocale(Locale): class FrenchLocale(Locale):
@ -379,7 +379,7 @@ class JapaneseLocale(Locale):
timeframes = { timeframes = {
'now': '現在', 'now': '現在',
'seconds': '', 'seconds': '',
'minute': '1分', 'minute': '1分',
'minutes': '{0}', 'minutes': '{0}',
'hour': '1時間', 'hour': '1時間',
@ -559,8 +559,8 @@ class KoreanLocale(Locale):
timeframes = { timeframes = {
'now': '지금', 'now': '지금',
'seconds': '', 'seconds': ' ',
'minute': '', 'minute': '1',
'minutes': '{0}', 'minutes': '{0}',
'hour': '1시간', 'hour': '1시간',
'hours': '{0}시간', 'hours': '{0}시간',
@ -919,7 +919,7 @@ class NewNorwegianLocale(Locale):
class PortugueseLocale(Locale): class PortugueseLocale(Locale):
names = ['pt', 'pt_pt'] names = ['pt', 'pt_pt']
past = '{0}' past = '{0}'
future = 'em {0}' future = 'em {0}'
@ -946,11 +946,11 @@ class PortugueseLocale(Locale):
day_names = ['', 'segunda-feira', 'terça-feira', 'quarta-feira', 'quinta-feira', 'sexta-feira', day_names = ['', 'segunda-feira', 'terça-feira', 'quarta-feira', 'quinta-feira', 'sexta-feira',
'sábado', 'domingo'] 'sábado', 'domingo']
day_abbreviations = ['', 'seg', 'ter', 'qua', 'qui', 'sex', 'sab', 'dom'] day_abbreviations = ['', 'seg', 'ter', 'qua', 'qui', 'sex', 'sab', 'dom']
class BrazilianPortugueseLocale(PortugueseLocale): class BrazilianPortugueseLocale(PortugueseLocale):
names = ['pt_br'] names = ['pt_br']
past = 'fazem {0}' past = 'fazem {0}'
@ -1034,7 +1034,7 @@ class TurkishLocale(Locale):
'days': '{0} gün', 'days': '{0} gün',
'month': 'bir ay', 'month': 'bir ay',
'months': '{0} ay', 'months': '{0} ay',
'year': 'a yıl', 'year': 'yıl',
'years': '{0} yıl', 'years': '{0} yıl',
} }
@ -1047,6 +1047,37 @@ class TurkishLocale(Locale):
day_abbreviations = ['', 'Pzt', 'Sal', 'Çar', 'Per', 'Cum', 'Cmt', 'Paz'] 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): class ArabicLocale(Locale):
names = ['ar', 'ar_eg'] names = ['ar', 'ar_eg']
@ -1205,11 +1236,11 @@ class HindiLocale(Locale):
future = '{0} बाद' future = '{0} बाद'
timeframes = { timeframes = {
'now': 'अभि', 'now': 'अभ',
'seconds': 'सेकंड्', 'seconds': 'सेकंड्',
'minute': 'एक मिनट ', 'minute': 'एक मिनट ',
'minutes': '{0} मिनट ', 'minutes': '{0} मिनट ',
'hour': 'एक घंट', 'hour': 'एक घंट',
'hours': '{0} घंटे', 'hours': '{0} घंटे',
'day': 'एक दिन', 'day': 'एक दिन',
'days': '{0} दिन', 'days': '{0} दिन',
@ -1226,8 +1257,8 @@ class HindiLocale(Locale):
'PM': 'शाम', 'PM': 'शाम',
} }
month_names = ['', 'जनवरी', 'रवरी', 'मार्च', 'अप्रैल ', 'मई', 'जून', 'जुलाई', month_names = ['', 'जनवरी', 'रवरी', 'मार्च', 'अप्रैल ', 'मई', 'जून', 'जुलाई',
'आगस्त', 'सितम्बर', 'अकतूबर', 'नवेम्बर', 'दिसम्बर'] 'अगस्त', 'सितंबर', 'अक्टूबर', 'नवंबर', 'दिसंबर']
month_abbreviations = ['', 'जन', 'फ़र', 'मार्च', 'अप्रै', 'मई', 'जून', 'जुलाई', 'आग', month_abbreviations = ['', 'जन', 'फ़र', 'मार्च', 'अप्रै', 'मई', 'जून', 'जुलाई', 'आग',
'सित', 'अकत', 'नवे', 'दिस'] 'सित', 'अकत', 'नवे', 'दिस']
@ -1284,7 +1315,8 @@ class CzechLocale(Locale):
def _format_timeframe(self, timeframe, delta): 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] form = self.timeframes[timeframe]
if isinstance(form, dict): if isinstance(form, dict):
if delta == 0: if delta == 0:
@ -1293,7 +1325,7 @@ class CzechLocale(Locale):
form = form['future'] form = form['future']
else: else:
form = form['past'] form = form['past']
delta = abs(delta) delta = abs(delta)
if isinstance(form, list): if isinstance(form, list):
if 2 <= delta % 10 <= 4 and (delta % 100 < 10 or delta % 100 >= 20): if 2 <= delta % 10 <= 4 and (delta % 100 < 10 or delta % 100 >= 20):
@ -1303,6 +1335,78 @@ class CzechLocale(Locale):
return form.format(delta) 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): class FarsiLocale(Locale):
names = ['fa', 'fa_ir'] names = ['fa', 'fa_ir']
@ -1463,7 +1567,7 @@ class MarathiLocale(Locale):
day_names = ['', 'सोमवार', 'मंगळवार', 'बुधवार', 'गुरुवार', 'शुक्रवार', 'शनिवार', 'रविवार'] day_names = ['', 'सोमवार', 'मंगळवार', 'बुधवार', 'गुरुवार', 'शुक्रवार', 'शनिवार', 'रविवार']
day_abbreviations = ['', 'सोम', 'मंगळ', 'बुध', 'गुरु', 'शुक्र', 'शनि', 'रवि'] day_abbreviations = ['', 'सोम', 'मंगळ', 'बुध', 'गुरु', 'शुक्र', 'शनि', 'रवि']
def _map_locales(): def _map_locales():
locales = {} locales = {}
@ -1471,14 +1575,14 @@ def _map_locales():
for cls_name, cls in inspect.getmembers(sys.modules[__name__], inspect.isclass): for cls_name, cls in inspect.getmembers(sys.modules[__name__], inspect.isclass):
if issubclass(cls, Locale): if issubclass(cls, Locale):
for name in cls.names: for name in cls.names:
locales[name.lower()] = cls locales[name.lower()] = cls
return locales return locales
class CatalaLocale(Locale): class CatalanLocale(Locale):
names = ['ca', 'ca_ca'] names = ['ca', 'ca_es', 'ca_ad', 'ca_fr', 'ca_it']
past = 'Fa {0}' past = 'Fa {0}'
future = '{0}' # I don't know what's the right phrase in catala for the future. future = 'En {0}'
timeframes = { timeframes = {
'now': 'Ara mateix', 'now': 'Ara mateix',
@ -1490,15 +1594,15 @@ class CatalaLocale(Locale):
'day': 'un dia', 'day': 'un dia',
'days': '{0} dies', 'days': '{0} dies',
'month': 'un mes', 'month': 'un mes',
'months': '{0} messos', 'months': '{0} mesos',
'year': 'un any', 'year': 'un any',
'years': '{0} anys', 'years': '{0} anys',
} }
month_names = ['', 'Jener', 'Febrer', 'Març', 'Abril', 'Maig', 'Juny', 'Juliol', 'Agost', 'Setembre', 'Octubre', 'Novembre', 'Decembre'] month_names = ['', 'Gener', 'Febrer', 'Març', 'Abril', 'Maig', 'Juny', 'Juliol', 'Agost', 'Setembre', 'Octubre', 'Novembre', 'Desembre']
month_abbreviations = ['', 'Jener', 'Febrer', 'Març', 'Abril', 'Maig', 'Juny', 'Juliol', 'Agost', 'Setembre', 'Octubre', 'Novembre', 'Decembre'] month_abbreviations = ['', 'Gener', 'Febrer', 'Març', 'Abril', 'Maig', 'Juny', 'Juliol', 'Agost', 'Setembre', 'Octubre', 'Novembre', 'Desembre']
day_names = ['', 'Dilluns', 'Dimars', 'Dimecres', 'Dijous', 'Divendres', 'Disabte', 'Diumenge'] day_names = ['', 'Dilluns', 'Dimarts', 'Dimecres', 'Dijous', 'Divendres', 'Dissabte', 'Diumenge']
day_abbreviations = ['', 'Dilluns', 'Dimars', 'Dimecres', 'Dijous', 'Divendres', 'Disabte', 'Diumenge'] day_abbreviations = ['', 'Dilluns', 'Dimarts', 'Dimecres', 'Dijous', 'Divendres', 'Dissabte', 'Diumenge']
class BasqueLocale(Locale): class BasqueLocale(Locale):
names = ['eu', 'eu_eu'] names = ['eu', 'eu_eu']
@ -1587,6 +1691,50 @@ class HungarianLocale(Locale):
return form.format(abs(delta)) 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<value>[1-3]?[0-9](?=a))a)'
def _ordinal_number(self, n):
return '{0}a'.format(n)
class ThaiLocale(Locale): class ThaiLocale(Locale):
names = ['th', 'th_th'] names = ['th', 'th_th']
@ -1700,4 +1848,164 @@ class BengaliLocale(Locale):
return '{0}ষ্ঠ'.format(n) 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() _locales = _map_locales()

View file

@ -5,7 +5,6 @@ from __future__ import unicode_literals
from datetime import datetime from datetime import datetime
from dateutil import tz from dateutil import tz
import re import re
from arrow import locales from arrow import locales
@ -15,16 +14,14 @@ class ParserError(RuntimeError):
class DateTimeParser(object): 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_OR_MORE_DIGIT_RE = re.compile('\d+')
_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_TWO_DIGIT_RE = re.compile('\d{1,2}') _ONE_OR_TWO_DIGIT_RE = re.compile('\d{1,2}')
_FOUR_DIGIT_RE = re.compile('\d{4}') _FOUR_DIGIT_RE = re.compile('\d{4}')
_TWO_DIGIT_RE = re.compile('\d{2}') _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+\-/]+') _TZ_NAME_RE = re.compile('\w[\w+\-/]+')
@ -47,12 +44,7 @@ class DateTimeParser(object):
'ZZZ': _TZ_NAME_RE, 'ZZZ': _TZ_NAME_RE,
'ZZ': _TZ_RE, 'ZZ': _TZ_RE,
'Z': _TZ_RE, 'Z': _TZ_RE,
'SSSSSS': _ONE_THROUGH_SIX_DIGIT_RE, 'S': _ONE_OR_MORE_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'),
} }
MARKERS = ['YYYY', 'MM', 'DD'] MARKERS = ['YYYY', 'MM', 'DD']
@ -67,6 +59,10 @@ class DateTimeParser(object):
'MMM': self._choice_re(self.locale.month_abbreviations[1:], 'MMM': self._choice_re(self.locale.month_abbreviations[1:],
re.IGNORECASE), re.IGNORECASE),
'Do': re.compile(self.locale.ordinal_day_re), '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( 'a': self._choice_re(
(self.locale.meridians['am'], self.locale.meridians['pm']) (self.locale.meridians['am'], self.locale.meridians['pm'])
), ),
@ -88,11 +84,10 @@ class DateTimeParser(object):
time_parts = re.split('[+-]', time_string, 1) time_parts = re.split('[+-]', time_string, 1)
has_tz = len(time_parts) > 1 has_tz = len(time_parts) > 1
has_seconds = time_parts[0].count(':') > 1 has_seconds = time_parts[0].count(':') > 1
has_subseconds = '.' in time_parts[0] has_subseconds = re.search('[.,]', time_parts[0])
if has_subseconds: 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%sS' % has_subseconds.group()]
formats = ['YYYY-MM-DDTHH:mm:ss.%s' % subseconds_token]
elif has_seconds: elif has_seconds:
formats = ['YYYY-MM-DDTHH:mm:ss'] formats = ['YYYY-MM-DDTHH:mm:ss']
else: else:
@ -123,10 +118,18 @@ class DateTimeParser(object):
# we construct a new string by replacing each # we construct a new string by replacing each
# token by its pattern: # token by its pattern:
# 'YYYY-MM-DD' -> '(?P<YYYY>\d{4})-(?P<MM>\d{2})-(?P<DD>\d{2})' # 'YYYY-MM-DD' -> '(?P<YYYY>\d{4})-(?P<MM>\d{2})-(?P<DD>\d{2})'
fmt_pattern = fmt
tokens = [] tokens = []
offset = 0 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) token = m.group(0)
try: try:
input_re = self._input_re_map[token] input_re = self._input_re_map[token]
@ -140,9 +143,20 @@ class DateTimeParser(object):
# are returned in the order found by finditer. # are returned in the order found by finditer.
fmt_pattern = fmt_pattern[:m.start() + offset] + input_pattern + fmt_pattern[m.end() + offset:] fmt_pattern = fmt_pattern[:m.start() + offset] + input_pattern + fmt_pattern[m.end() + offset:]
offset += len(input_pattern) - (m.end() - m.start()) 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: 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 = {} parts = {}
for token in tokens: for token in tokens:
if token == 'Do': if token == 'Do':
@ -181,18 +195,22 @@ class DateTimeParser(object):
elif token in ['ss', 's']: elif token in ['ss', 's']:
parts['second'] = int(value) 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': 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': elif token == 'X':
parts['timestamp'] = int(value) parts['timestamp'] = int(value)
@ -242,7 +260,7 @@ class DateTimeParser(object):
try: try:
_datetime = self.parse(string, fmt) _datetime = self.parse(string, fmt)
break break
except: except ParserError:
pass pass
if _datetime is None: if _datetime is None:
@ -273,7 +291,7 @@ class DateTimeParser(object):
class TzinfoParser(object): class TzinfoParser(object):
_TZINFO_RE = re.compile('([+\-])?(\d\d):?(\d\d)') _TZINFO_RE = re.compile('([+\-])?(\d\d):?(\d\d)?')
@classmethod @classmethod
def parse(cls, string): def parse(cls, string):
@ -292,6 +310,8 @@ class TzinfoParser(object):
if iso_match: if iso_match:
sign, hours, minutes = iso_match.groups() sign, hours, minutes = iso_match.groups()
if minutes is None:
minutes = 0
seconds = int(hours) * 3600 + int(minutes) * 60 seconds = int(hours) * 3600 + int(minutes) * 60
if sign == '-': if sign == '-':
@ -303,6 +323,6 @@ class TzinfoParser(object):
tzinfo = tz.gettz(string) tzinfo = tz.gettz(string)
if tzinfo is None: 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 return tzinfo

View file

@ -22,6 +22,8 @@ else: # pragma: no cover
total_seconds = _total_seconds_27 total_seconds = _total_seconds_27
def is_timestamp(value): def is_timestamp(value):
if type(value) == bool:
return False
try: try:
float(value) float(value)
return True return True