mirror of
https://github.com/kennethreitz/heroku-buildpack-python.git
synced 2026-06-05 23:10:16 +00:00
Added more tests
This commit is contained in:
Vendored
+114
@@ -0,0 +1,114 @@
|
||||
Maya: Datetime for Humans™
|
||||
==========================
|
||||
|
||||
.. image:: https://img.shields.io/pypi/v/maya.svg
|
||||
:target: https://pypi.python.org/pypi/maya
|
||||
|
||||
.. image:: https://travis-ci.org/kennethreitz/maya.svg?branch=master
|
||||
:target: https://travis-ci.org/kennethreitz/maya
|
||||
|
||||
.. image:: https://img.shields.io/badge/SayThanks.io-☼-1EAEDB.svg
|
||||
:target: https://saythanks.io/to/kennethreitz
|
||||
|
||||
|
||||
Datetimes are very frustrating to work with in Python, especially when dealing
|
||||
with different locales on different systems. This library exists to make the
|
||||
simple things **much** easier, while admitting that time is an illusion
|
||||
(timezones doubly so).
|
||||
|
||||
Datetimes should be interacted with via an API written for humans.
|
||||
|
||||
Maya is mostly built around the headaches and use-cases around parsing datetime data from websites.
|
||||
|
||||
|
||||
☤ Basic Usage of Maya
|
||||
---------------------
|
||||
|
||||
Behold, datetimes for humans!
|
||||
|
||||
.. code-block:: pycon
|
||||
|
||||
>>> now = maya.now()
|
||||
<MayaDT epoch=1481850660.9>
|
||||
|
||||
>>> tomorrow = maya.when('tomorrow')
|
||||
<MayaDT epoch=1481919067.23>
|
||||
|
||||
>>> tomorrow.slang_date()
|
||||
'tomorrow'
|
||||
|
||||
>>> tomorrow.slang_time()
|
||||
'23 hours from now'
|
||||
|
||||
>>> tomorrow.iso8601()
|
||||
'2016-12-16T15:11:30.263350Z'
|
||||
|
||||
>>> tomorrow.rfc2822()
|
||||
'Fri, 16 Dec 2016 20:11:30 -0000'
|
||||
|
||||
>>> tomorrow.datetime()
|
||||
datetime.datetime(2016, 12, 16, 15, 11, 30, 263350, tzinfo=<UTC>)
|
||||
|
||||
# Automatically parse datetime strings and generate naive datetimes.
|
||||
>>> scraped = '2016-12-16 18:23:45.423992+00:00'
|
||||
>>> maya.parse(scraped).datetime(to_timezone='US/Eastern', naive=True)
|
||||
datetime.datetime(2016, 12, 16, 13, 23, 45, 423992)
|
||||
|
||||
>>> rand_day = maya.when('2011-02-07', timezone='US/Eastern')
|
||||
<MayaDT epoch=1297036800.0>
|
||||
|
||||
# Note how this is the 6th, not the 7th.
|
||||
>>> rand_day.day
|
||||
6
|
||||
|
||||
# Always.
|
||||
>>> rand_day.timezone
|
||||
UTC
|
||||
|
||||
☤ Why is this useful?
|
||||
---------------------
|
||||
|
||||
- All timezone algebra will behave identically on all machines, regardless of system locale.
|
||||
- Complete symmetric import and export of both ISO 8601 and RFC 2822 datetime stamps.
|
||||
- Fantastic parsing of both dates written for/by humans and machines (``maya.when()`` vs ``maya.parse()``).
|
||||
- Support for human slang, both import and export (e.g. `an hour ago`).
|
||||
- Datetimes can very easily be generated, with or without tzinfo attached.
|
||||
- This library is based around epoch time, but dates before Jan 1 1970 are indeed supported, via negative integers.
|
||||
- Maya never panics, and always carries a towel.
|
||||
|
||||
|
||||
☤ What about Delorean, Arrow, & Pendulum?
|
||||
-----------------------------------------
|
||||
|
||||
Arrow, for example, is a fantastic library, but isn't what I wanted in a datetime library. In many ways, it's better than Maya for certain things. In some ways, in my opinion, it's not.
|
||||
|
||||
I simply desire a sane API for datetimes that made sense to me for all the things I'd ever want to do—especially when dealing with timezone algebra. Arrow doesn't do all of the things I need (but it does a lot more!). Maya does do exactly what I need.
|
||||
|
||||
I think these projects complement each-other, personally. Maya is great for parsing websites. For example- Arrow supports floors and ceilings and spans of dates, which Maya does not at all.
|
||||
|
||||
|
||||
☤ Installing Maya
|
||||
-----------------
|
||||
|
||||
Installation is easy, with pip::
|
||||
|
||||
$ pip install maya
|
||||
|
||||
✨🍰✨
|
||||
|
||||
☤ Like it?
|
||||
----------
|
||||
|
||||
`Say Thanks <https://saythanks.io/to/kennethreitz>`_!
|
||||
|
||||
|
||||
How to Contribute
|
||||
-----------------
|
||||
|
||||
#. Check for open issues or open a fresh issue to start a discussion around a feature idea or a bug.
|
||||
#. Fork `the repository`_ on GitHub to start making your changes to the **master** branch (or branch off of it).
|
||||
#. Write a test which shows that the bug was fixed or that the feature works as expected.
|
||||
#. Send a pull request and bug the maintainer until it gets merged and published. :) Make sure to add yourself to AUTHORS_.
|
||||
|
||||
.. _`the repository`: http://github.com/kennethreitz/maya
|
||||
.. _AUTHORS: https://github.com/kennethreitz/maya/blob/master/AUTHORS.rst
|
||||
Vendored
+273
@@ -0,0 +1,273 @@
|
||||
|
||||
# ___ __ ___ _ _ ___
|
||||
# || \/ | ||=|| \\// ||=||
|
||||
# || | || || // || ||
|
||||
|
||||
# Ignore warnings for yaml usage.
|
||||
import warnings
|
||||
import ruamel.yaml
|
||||
warnings.simplefilter('ignore', ruamel.yaml.error.UnsafeLoaderWarning)
|
||||
|
||||
|
||||
import email.utils
|
||||
import time
|
||||
from datetime import datetime as Datetime
|
||||
|
||||
import pytz
|
||||
import humanize
|
||||
import dateparser
|
||||
import iso8601
|
||||
import dateutil.parser
|
||||
from tzlocal import get_localzone
|
||||
|
||||
_EPOCH_START = (1970, 1, 1)
|
||||
|
||||
|
||||
def validate_class_type_arguments(operator):
|
||||
"""
|
||||
Decorator to validate all the arguments to function
|
||||
are of the type of calling class
|
||||
"""
|
||||
|
||||
def inner(function):
|
||||
def wrapper(self, *args, **kwargs):
|
||||
for arg in args + tuple(kwargs.values()):
|
||||
if not isinstance(arg, self.__class__):
|
||||
raise TypeError('unorderable types: {}() {} {}()'.format(
|
||||
type(self).__name__, operator, type(arg).__name__))
|
||||
return function(self, *args, **kwargs)
|
||||
|
||||
return wrapper
|
||||
|
||||
return inner
|
||||
|
||||
|
||||
|
||||
class MayaDT(object):
|
||||
"""The Maya Datetime object."""
|
||||
|
||||
def __init__(self, epoch):
|
||||
super(MayaDT, self).__init__()
|
||||
self._epoch = epoch
|
||||
|
||||
def __repr__(self):
|
||||
return '<MayaDT epoch={}>'.format(self._epoch)
|
||||
|
||||
def __str__(self):
|
||||
return self.rfc2822()
|
||||
|
||||
def __format__(self, *args, **kwargs):
|
||||
"""Return's the datetime's format"""
|
||||
return format(self.datetime(), *args, **kwargs)
|
||||
|
||||
|
||||
@validate_class_type_arguments('==')
|
||||
def __eq__(self, maya_dt):
|
||||
return self._epoch == maya_dt._epoch
|
||||
|
||||
@validate_class_type_arguments('!=')
|
||||
def __ne__(self, maya_dt):
|
||||
return self._epoch != maya_dt._epoch
|
||||
|
||||
@validate_class_type_arguments('<')
|
||||
def __lt__(self, maya_dt):
|
||||
return self._epoch < maya_dt._epoch
|
||||
|
||||
@validate_class_type_arguments('<=')
|
||||
def __le__(self, maya_dt):
|
||||
return self._epoch <= maya_dt._epoch
|
||||
|
||||
@validate_class_type_arguments('>')
|
||||
def __gt__(self, maya_dt):
|
||||
return self._epoch > maya_dt._epoch
|
||||
|
||||
@validate_class_type_arguments('>=')
|
||||
def __ge__(self, maya_dt):
|
||||
return self._epoch >= maya_dt._epoch
|
||||
|
||||
|
||||
# Timezone Crap
|
||||
# -------------
|
||||
|
||||
@property
|
||||
def timezone(self):
|
||||
"""Returns the UTC tzinfo name. It's always UTC. Always."""
|
||||
return 'UTC'
|
||||
|
||||
@property
|
||||
def _tz(self):
|
||||
"""Returns the UTC tzinfo object."""
|
||||
return pytz.timezone(self.timezone)
|
||||
|
||||
@property
|
||||
def local_timezone(self):
|
||||
"""Returns the name of the local timezone, for informational purposes."""
|
||||
return self._local_tz.zone
|
||||
|
||||
@property
|
||||
def _local_tz(self):
|
||||
"""Returns the local timezone."""
|
||||
return get_localzone()
|
||||
|
||||
@staticmethod
|
||||
def __dt_to_epoch(dt):
|
||||
"""Converts a datetime into an epoch."""
|
||||
|
||||
# Assume UTC if no datetime is provided.
|
||||
if dt.tzinfo is None:
|
||||
dt = dt.replace(tzinfo=pytz.utc)
|
||||
|
||||
epoch_start = Datetime(*_EPOCH_START, tzinfo=pytz.timezone('UTC'))
|
||||
return (dt - epoch_start).total_seconds()
|
||||
|
||||
# Importers
|
||||
# ---------
|
||||
|
||||
@classmethod
|
||||
def from_datetime(klass, dt):
|
||||
"""Returns MayaDT instance from datetime."""
|
||||
return klass(klass.__dt_to_epoch(dt))
|
||||
|
||||
@classmethod
|
||||
def from_iso8601(klass, string):
|
||||
"""Returns MayaDT instance from iso8601 string."""
|
||||
dt = iso8601.parse_date(string)
|
||||
return klass.from_datetime(dt)
|
||||
|
||||
@staticmethod
|
||||
def from_rfc2822(string):
|
||||
"""Returns MayaDT instance from rfc2822 string."""
|
||||
return parse(string)
|
||||
|
||||
# Exporters
|
||||
# ---------
|
||||
|
||||
def datetime(self, to_timezone=None, naive=False):
|
||||
"""Returns a timezone-aware datetime...
|
||||
Defaulting to UTC (as it should).
|
||||
|
||||
Keyword Arguments:
|
||||
to_timezone {string} -- timezone to convert to (default: None/UTC)
|
||||
naive {boolean} -- if True, the tzinfo is simply dropped (default: False)
|
||||
"""
|
||||
if to_timezone:
|
||||
dt = self.datetime().astimezone(pytz.timezone(to_timezone))
|
||||
else:
|
||||
dt = Datetime.utcfromtimestamp(self._epoch)
|
||||
dt.replace(tzinfo=self._tz)
|
||||
|
||||
# Strip the timezone info if requested to do so.
|
||||
if naive:
|
||||
return dt.replace(tzinfo=None)
|
||||
else:
|
||||
if dt.tzinfo is None:
|
||||
dt = dt.replace(tzinfo=self._tz)
|
||||
|
||||
return dt
|
||||
|
||||
def iso8601(self):
|
||||
"""Returns an ISO 8601 representation of the MayaDT."""
|
||||
# Get a timezone-naive datetime.
|
||||
dt = self.datetime(naive=True)
|
||||
return '{}Z'.format(dt.isoformat())
|
||||
|
||||
def rfc2822(self):
|
||||
"""Returns an RFC 2822 representation of the MayaDT."""
|
||||
return email.utils.formatdate(self.epoch, usegmt=True)
|
||||
|
||||
# Properties
|
||||
# ----------
|
||||
|
||||
@property
|
||||
def year(self):
|
||||
return self.datetime().year
|
||||
|
||||
@property
|
||||
def month(self):
|
||||
return self.datetime().month
|
||||
|
||||
@property
|
||||
def day(self):
|
||||
return self.datetime().day
|
||||
|
||||
@property
|
||||
def week(self):
|
||||
return self.datetime().isocalendar()[1]
|
||||
|
||||
@property
|
||||
def weekday(self):
|
||||
"""Return the day of the week as an integer. Monday is 1 and Sunday is 7"""
|
||||
return self.datetime().isoweekday()
|
||||
|
||||
@property
|
||||
def hour(self):
|
||||
return self.datetime().hour
|
||||
|
||||
@property
|
||||
def minute(self):
|
||||
return self.datetime().minute
|
||||
|
||||
@property
|
||||
def second(self):
|
||||
return self.datetime().second
|
||||
|
||||
@property
|
||||
def microsecond(self):
|
||||
return self.datetime().microsecond
|
||||
|
||||
@property
|
||||
def epoch(self):
|
||||
return self._epoch
|
||||
|
||||
# Human Slang Extras
|
||||
# ------------------
|
||||
|
||||
def slang_date(self):
|
||||
""""Returns human slang representation of date."""
|
||||
dt = self.datetime(naive=True, to_timezone=self.local_timezone)
|
||||
return humanize.naturaldate(dt)
|
||||
|
||||
def slang_time(self):
|
||||
""""Returns human slang representation of time."""
|
||||
dt = self.datetime(naive=True, to_timezone=self.local_timezone)
|
||||
return humanize.naturaltime(dt)
|
||||
|
||||
|
||||
|
||||
|
||||
def now():
|
||||
"""Returns a MayaDT instance for this exact moment."""
|
||||
epoch = time.time()
|
||||
return MayaDT(epoch=epoch)
|
||||
|
||||
def when(string, timezone='UTC'):
|
||||
""""Returns a MayaDT instance for the human moment specified.
|
||||
|
||||
Powered by dateparser. Useful for scraping websites.
|
||||
|
||||
Examples:
|
||||
'next week', 'now', 'tomorrow', '300 years ago', 'August 14, 2015'
|
||||
|
||||
Keyword Arguments:
|
||||
string -- string to be parsed
|
||||
timezone -- timezone referenced from (default: 'UTC')
|
||||
|
||||
"""
|
||||
dt = dateparser.parse(string, settings={'TIMEZONE': timezone, 'RETURN_AS_TIMEZONE_AWARE': True, 'TO_TIMEZONE': 'UTC'})
|
||||
|
||||
if dt is None:
|
||||
raise ValueError('invalid datetime input specified.')
|
||||
|
||||
return MayaDT.from_datetime(dt)
|
||||
|
||||
def parse(string, day_first=False):
|
||||
""""Returns a MayaDT instance for the machine-produced moment specified.
|
||||
|
||||
Powered by dateutil. Accepts most known formats. Useful for working with data.
|
||||
|
||||
Keyword Arguments:
|
||||
string -- string to be parsed
|
||||
day_first -- if true, the first value (e.g. 01/05/2016) is parsed as day (default: False)
|
||||
"""
|
||||
dt = dateutil.parser.parse(string, dayfirst=day_first)
|
||||
return MayaDT.from_datetime(dt)
|
||||
Vendored
+51
@@ -0,0 +1,51 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
import os
|
||||
import sys
|
||||
import codecs
|
||||
|
||||
from setuptools import setup
|
||||
|
||||
try:
|
||||
# Python 3
|
||||
from os import dirname
|
||||
except ImportError:
|
||||
# Python 2
|
||||
from os.path import dirname
|
||||
|
||||
here = os.path.abspath(dirname(__file__))
|
||||
|
||||
with codecs.open(os.path.join(here, 'README.rst'), encoding='utf-8') as f:
|
||||
long_description = '\n' + f.read()
|
||||
|
||||
|
||||
if sys.argv[-1] == "publish":
|
||||
os.system("python setup.py sdist bdist_wheel upload")
|
||||
sys.exit()
|
||||
|
||||
required = [
|
||||
'humanize',
|
||||
'pytz',
|
||||
'dateparser',
|
||||
'iso8601',
|
||||
'python-dateutil',
|
||||
'ruamel.yaml',
|
||||
'tzlocal'
|
||||
]
|
||||
|
||||
setup(
|
||||
name='maya',
|
||||
version='0.1.6',
|
||||
description='Datetimes for Humans.',
|
||||
long_description=long_description,
|
||||
author='Kenneth Reitz',
|
||||
author_email='me@kennethreitz.com',
|
||||
url='https://github.com/kennethreitz/maya',
|
||||
py_modules=['maya'],
|
||||
install_requires=required,
|
||||
license='MIT',
|
||||
classifiers=(
|
||||
|
||||
),
|
||||
)
|
||||
|
||||
@@ -1,5 +1,17 @@
|
||||
#!/usr/bin/env bash
|
||||
# See README.md for info on running these tests.
|
||||
|
||||
testNoRequirements() {
|
||||
compile "no-requirements"
|
||||
assertCapturedError
|
||||
}
|
||||
|
||||
|
||||
testSetupPy() {
|
||||
compile "setup-py"
|
||||
assertCaptured "maya"
|
||||
assertCapturedSuccess
|
||||
}
|
||||
|
||||
|
||||
testStandardRequirements() {
|
||||
compile "requirements-standard"
|
||||
@@ -26,6 +38,8 @@ testPython3() {
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
pushd $(dirname 0) >/dev/null
|
||||
popd >/dev/null
|
||||
|
||||
|
||||
@@ -147,6 +147,11 @@ _assertContains()
|
||||
fi
|
||||
}
|
||||
|
||||
debug()
|
||||
{
|
||||
cat $STD_OUT
|
||||
}
|
||||
|
||||
assertContains()
|
||||
{
|
||||
_assertContains "$@" 0 "text"
|
||||
|
||||
Reference in New Issue
Block a user