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 .api import get, now, utcnow
__version__ = '0.7.0'
__version__ = '0.10.0'
VERSION = __version__

View file

@ -51,5 +51,5 @@ def factory(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
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)
<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:
>>> arw.replace(tzinfo=tz.tzlocal())
<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:
- 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 <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):
''' Returns a new :class:`Arrow <arrow.arrow.Arrow>` object, converted to the target
timezone.
''' Returns a new :class:`Arrow <arrow.arrow.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 <arrow.arrow.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):

View file

@ -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)

View file

@ -7,8 +7,8 @@ import sys
def get_locale(name):
'''Returns an appropriate :class:`Locale <locale.Locale>` corresponding
to an inpute locale name.
'''Returns an appropriate :class:`Locale <arrow.locales.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<value>[1-3]?[0-9](?=°))°)'
ordinal_day_re = r'((?P<value>[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<value>[1-3]?[0-9](?=°))°)'
ordinal_day_re = r'((?P<value>[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}시간',
@ -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:
@ -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']
@ -1475,10 +1579,10 @@ def _map_locales():
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<value>[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()

View file

@ -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<YYYY>\d{4})-(?P<MM>\d{2})-(?P<DD>\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

View file

@ -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