Skip to content

Commit

Permalink
py35+ compatibility, modularize, update Julia 1.0
Browse files Browse the repository at this point in the history
prereq
  • Loading branch information
scivision committed Sep 9, 2018
1 parent c7039da commit 17b9d18
Show file tree
Hide file tree
Showing 12 changed files with 128 additions and 105 deletions.
17 changes: 9 additions & 8 deletions .travis.yml
@@ -1,29 +1,30 @@
language: python
sudo: required
dist: xenial
group: travis_latest

git:
depth: 3
quiet: true

python:
- 3.5
- 3.6
- 3.7
- pypy3

os:
- linux

install: pip install -e .[tests]
install:
- pip install -e .[tests]
- if [[ $TRAVIS_PYTHON_VERSION == 3.6* && $TRAVIS_OS_NAME == linux ]]; then pip install -e .[cov]; fi

script:
- pytest -rsv
- flake8
- mypy . --ignore-missing-imports
- if [[ $TRAVIS_PYTHON_VERSION == 3.6* && $TRAVIS_OS_NAME == linux ]]; then flake8; fi
- if [[ $TRAVIS_PYTHON_VERSION == 3.6* && $TRAVIS_OS_NAME == linux ]]; then mypy . --ignore-missing-imports; fi

after_success:
- if [[ $TRAVIS_PYTHON_VERSION == 3.6* ]]; then
pytest --cov;
- if [[ $TRAVIS_PYTHON_VERSION == 3.6* && $TRAVIS_OS_NAME == linux ]]; then
pytest --cov --cov-config=setup.cfg;
coveralls;
fi

1 change: 1 addition & 0 deletions MANIFEST.in
@@ -1 +1,2 @@
include LICENSE
recursive-include tests *.py
3 changes: 1 addition & 2 deletions README.md
Expand Up @@ -10,8 +10,7 @@
# Science Dates & Times

Date & time conversions used in the sciences.
The assumption is that datetimes are timezone-naive, as this will be required soon in Numpy *et al* for
`numpy.datetime64`.
The assumption is that datetimes are timezone-naive, as this is required in Numpy *et al* for `numpy.datetime64`.


## Install
Expand Down
2 changes: 1 addition & 1 deletion date2doy.jl
Expand Up @@ -2,7 +2,7 @@
#=
command-line utility to convert date to day of year
=#
using Base.Dates
using Dates
using ArgParse


Expand Down
14 changes: 7 additions & 7 deletions randomdate.jl
Expand Up @@ -4,21 +4,21 @@ generate a random date (month and day) in a year.
Michael Hirsch, Ph.D.
=#

using Base.Dates
using Dates
using ArgParse

function randomdate(year)
t = DateTime(year)
t += Day(rand(0:daysinyear(t)-1))
t = DateTime(year)
t += Day(rand(0:daysinyear(t)-1))
end


s = ArgParseSettings()
@add_arg_table s begin
"year"
help = "year in which to generate a random date"
arg_type = Int
required = true
"year"
help = "year in which to generate a random date"
arg_type = Int
required = true
end
s = parse_args(s)

Expand Down
86 changes: 10 additions & 76 deletions sciencedates/__init__.py
@@ -1,11 +1,15 @@
from __future__ import division
import datetime
from pytz import UTC
import numpy as np
from dateutil.parser import parse
import calendar
import random
from typing import Union, Tuple, Any, List
from typing import Union, Tuple, List

from .findnearest import find_nearest # noqa: F401
try:
from .tz import forceutc # noqa: F401
except ImportError:
pass


def datetime2yeardoy(time: Union[str, datetime.datetime]) -> Tuple[int, float]:
Expand All @@ -17,7 +21,7 @@ def datetime2yeardoy(time: Union[str, datetime.datetime]) -> Tuple[int, float]:
yd: yyyyddd four digit year, 3 digit day of year (INTEGER)
utsec: seconds from midnight utc
"""
T: np.ndarray = np.atleast_1d(time)
T = np.atleast_1d(time)

utsec = np.empty_like(T, float)
yd = np.empty_like(T, int)
Expand Down Expand Up @@ -116,7 +120,7 @@ def datetime2gtd(time: Union[str, datetime.datetime, np.datetime64],
elif isinstance(t, (datetime.datetime, datetime.date)):
pass
else:
raise TypeError(f'unknown time datatype {type(t)}')
raise TypeError('unknown time datatype {}'.format(type(t)))
# %% Day of year
doy[i] = int(t.strftime('%j'))
# %% seconds since utc midnight
Expand Down Expand Up @@ -145,36 +149,6 @@ def datetime2utsec(t: Union[str, datetime.date, datetime.datetime, np.datetime64
datetime.datetime.min.time()))


def forceutc(t: Union[str, datetime.datetime, datetime.date, np.datetime64]) -> Union[datetime.datetime, datetime.date]:
"""
Add UTC to datetime-naive and convert to UTC for datetime aware
input: python datetime (naive, utc, non-utc) or Numpy datetime64 #FIXME add Pandas and AstroPy time classes
output: utc datetime
"""
# need to passthrough None for simpler external logic.
# %% polymorph to datetime
if isinstance(t, str):
t = parse(t)
elif isinstance(t, np.datetime64):
t = t.astype(datetime.datetime)
elif isinstance(t, datetime.datetime):
pass
elif isinstance(t, datetime.date):
return t
elif isinstance(t, (np.ndarray, list, tuple)):
return np.asarray([forceutc(T) for T in t])
else:
raise TypeError('datetime only input')
# %% enforce UTC on datetime
if t.tzinfo is None: # datetime-naive
t = t.replace(tzinfo=UTC)
else: # datetime-aware
t = t.astimezone(UTC) # changes timezone, preserving absolute time. E.g. noon EST = 5PM UTC

return t


def yeardec2datetime(atime: float) -> datetime.datetime:
"""
Convert atime (a float) to DT.datetime
Expand Down Expand Up @@ -223,7 +197,7 @@ def datetime2yeardec(time: Union[str, datetime.datetime, datetime.date]) -> floa
elif isinstance(time, (tuple, list, np.ndarray)):
return np.asarray([datetime2yeardec(t) for t in time])
else:
raise TypeError(f'unknown input type {type(time)}')
raise TypeError('unknown input type {}'.format(type(time)))

year = t.year

Expand All @@ -233,46 +207,6 @@ def datetime2yeardec(time: Union[str, datetime.datetime, datetime.date]) -> floa
return year + ((t - boy).total_seconds() / ((eoy - boy).total_seconds()))


# %%
def find_nearest(x, x0) -> Tuple[int, Any]:
"""
This find_nearest function does NOT assume sorted input
inputs:
x: array (float, int, datetime, h5py.Dataset) within which to search for x0
x0: singleton or array of values to search for in x
outputs:
idx: index of flattened x nearest to x0 (i.e. works with higher than 1-D arrays also)
xidx: x[idx]
Observe how bisect.bisect() gives the incorrect result!
idea based on:
http://stackoverflow.com/questions/2566412/find-nearest-value-in-numpy-array
"""
x = np.asanyarray(x) # for indexing upon return
x0 = np.atleast_1d(x0)
# %%
if x.size == 0 or x0.size == 0:
raise ValueError('empty input(s)')

if x0.ndim not in (0, 1):
raise ValueError('2-D x0 not handled yet')
# %%
ind = np.empty_like(x0, dtype=int)

# NOTE: not trapping IndexError (all-nan) becaues returning None can surprise with slice indexing
for i, xi in enumerate(x0):
if xi is not None and (isinstance(xi, (datetime.datetime, datetime.date, np.datetime64)) or np.isfinite(xi)):
ind[i] = np.nanargmin(abs(x-xi))
else:
raise ValueError('x0 must NOT be None or NaN to avoid surprising None return value')

return ind.squeeze()[()], x[ind].squeeze()[()] # [()] to pop scalar from 0d array while being OK with ndim>0


def randomdate(year: int) -> datetime.date:
""" gives random date in year"""
if calendar.isleap(year):
Expand Down
42 changes: 42 additions & 0 deletions sciencedates/findnearest.py
@@ -0,0 +1,42 @@
from typing import Tuple, Any
import numpy as np
import datetime


def find_nearest(x, x0) -> Tuple[int, Any]:
"""
This find_nearest function does NOT assume sorted input
inputs:
x: array (float, int, datetime, h5py.Dataset) within which to search for x0
x0: singleton or array of values to search for in x
outputs:
idx: index of flattened x nearest to x0 (i.e. works with higher than 1-D arrays also)
xidx: x[idx]
Observe how bisect.bisect() gives the incorrect result!
idea based on:
http://stackoverflow.com/questions/2566412/find-nearest-value-in-numpy-array
"""
x = np.asanyarray(x) # for indexing upon return
x0 = np.atleast_1d(x0)
# %%
if x.size == 0 or x0.size == 0:
raise ValueError('empty input(s)')

if x0.ndim not in (0, 1):
raise ValueError('2-D x0 not handled yet')
# %%
ind = np.empty_like(x0, dtype=int)

# NOTE: not trapping IndexError (all-nan) becaues returning None can surprise with slice indexing
for i, xi in enumerate(x0):
if xi is not None and (isinstance(xi, (datetime.datetime, datetime.date, np.datetime64)) or np.isfinite(xi)):
ind[i] = np.nanargmin(abs(x-xi))
else:
raise ValueError('x0 must NOT be None or NaN to avoid surprising None return value')

return ind.squeeze()[()], x[ind].squeeze()[()] # [()] to pop scalar from 0d array while being OK with ndim>0
35 changes: 35 additions & 0 deletions sciencedates/tz.py
@@ -0,0 +1,35 @@
from typing import Union
import numpy as np
import datetime
from pytz import UTC
from dateutil.parser import parse


def forceutc(t: Union[str, datetime.datetime, datetime.date, np.datetime64]) -> Union[datetime.datetime, datetime.date]:
"""
Add UTC to datetime-naive and convert to UTC for datetime aware
input: python datetime (naive, utc, non-utc) or Numpy datetime64 #FIXME add Pandas and AstroPy time classes
output: utc datetime
"""
# need to passthrough None for simpler external logic.
# %% polymorph to datetime
if isinstance(t, str):
t = parse(t)
elif isinstance(t, np.datetime64):
t = t.astype(datetime.datetime)
elif isinstance(t, datetime.datetime):
pass
elif isinstance(t, datetime.date):
return t
elif isinstance(t, (np.ndarray, list, tuple)):
return np.asarray([forceutc(T) for T in t])
else:
raise TypeError('datetime only input')
# %% enforce UTC on datetime
if t.tzinfo is None: # datetime-naive
t = t.replace(tzinfo=UTC)
else: # datetime-aware
t = t.astimezone(UTC) # changes timezone, preserving absolute time. E.g. noon EST = 5PM UTC

return t
18 changes: 11 additions & 7 deletions setup.cfg
@@ -1,7 +1,8 @@
[metadata]
name = sciencedates
version = 1.4.3
version = 1.4.4
author = Michael Hirsch, Ph.D.
author_email = scivision@users.noreply.github.com
url = https://github.com/scivision/sciencedates
description = Date conversions used in the sciences.
keywords =
Expand All @@ -11,17 +12,19 @@ classifiers =
Development Status :: 5 - Production/Stable
Environment :: Console
Intended Audience :: Science/Research
License :: OSI Approved :: MIT License
Operating System :: OS Independent
Programming Language :: Python :: 3.5
Programming Language :: Python :: 3.6
Programming Language :: Python :: 3.7
Programming Language :: Python :: Implementation :: CPython
Programming Language :: Python :: Implementation :: PyPy
Topic :: Utilities
license_file = LICENSE
long_description = file: README.md
long_description_content_type = text/markdown

[options]
python_requires = >= 3.6
python_requires = >= 3.5
setup_requires =
setuptools >= 38.6
pip >= 10
Expand All @@ -30,19 +33,21 @@ include_package_data = True
packages = find:
install_requires =
numpy
pytz
python-dateutil

[options.extras_require]
tests =
pytest
cov =
pytest-cov
coveralls
flake8
mypy
plot =
xarray
plot =
xarray
matplotlib
timezone =
pytz

[options.entry_points]
console_scripts =
Expand All @@ -59,7 +64,6 @@ omit =
/home/travis/virtualenv/*
*/site-packages/*
*/bin/*
*/build/*

[coverage:report]
exclude_lines =
Expand Down

0 comments on commit 17b9d18

Please sign in to comment.