Skip to content

Commit

Permalink
Add to_datetime and to_timedelta to the Date class
Browse files Browse the repository at this point in the history
The to_datetime method can be used (optionally with an offset year)
to convert a Date to a datetime.  The date is clamped to a valid
range supported by numpy's datetime64[ns], used by xarray and
pandas.  This method will be useful for computing datetime bounds on
time-series data sets.

The to_timedelta method converts a Date object with
isInterval==True to a timedelta object.
  • Loading branch information
xylar committed Nov 28, 2016
1 parent e1fe6eb commit e30389f
Show file tree
Hide file tree
Showing 2 changed files with 77 additions and 9 deletions.
61 changes: 52 additions & 9 deletions mpas_analysis/shared/timekeeping/Date.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@

import functools
import numpy
import datetime

@functools.total_ordering
class Date(object):
Expand Down Expand Up @@ -75,6 +76,50 @@ def __init__(self, dateString=None, isInterval=False, totalSeconds=None,
self.seconds = numpy.int64(seconds)
self._setTotalSeconds()

def to_datetime(self, yearOffset=0):
"""
Converts the date object to a datetime object.
The yearOffset is added to this date's year, and
the resulting date is clamped to the range supported by
numpy's datetime64[ns], used internally by xarray an
pandas
Last modified: 11/28/2016
Author: Xylar Asay-Davis
"""
if self.isInterval:
raise ValueError("self.isInterval == True. Use to_timedelta "
"instead of to_datetime")

year = numpy.maximum(datetime.MINYEAR,
numpy.minimum(datetime.MAXYEAR,
self.years+yearOffset))
outDate = datetime.datetime(year=year, month=self.months+1,
day=self.days+1, hour=self.hours,
minute=self.minutes, second=self.seconds)

minDate = datetime.datetime(year=1678, month=1, day=1,
hour=0, minute=0, second=0)
maxDate = datetime.datetime(year=2262, month=1, day=1,
hour=0, minute=0, second=0)
outDate = max(minDate, min(maxDate, outDate))
return outDate

def to_timedelta(self):
"""
Converts the date object to a timedelta object
Last modified: 11/28/2016
Author: Xylar Asay-Davis
"""
if not self.isInterval:
raise ValueError("self.isInterval == False. Use to_datetime "
"instead of to_timedelta")

days = 365*self.years + self._monthsToDays(self.months) + self.days
return datetime.timedelta(days=self.days, hours=self.hours,
minutes=self.minutes, seconds=self.seconds)

def __lt__(self, other):
if self.isInterval != other.isInterval:
raise ValueError('Comparing interval with non-interval Date '
Expand Down Expand Up @@ -130,24 +175,22 @@ def __sub__(self, other):
return Date(isInterval=isInterval, years=years, months=months,
days=days, hours=hours, minutes=minutes, seconds=seconds)


def __str__(self):
if self.isInterval:
offset = 0
else:
offset = 1
return '{:04d}-{:02d}-{:02d} {:02d}:{:02d}:{:02d}' \
.format(self.years, self.months+offset, self.days+offset,
self.hours, self.minutes, self.seconds)
return '{:04d}-{:02d}-{:02d} {:02d}:{:02d}:{:02d}'.format(
self.years, self.months+offset, self.days+offset,
self.hours, self.minutes, self.seconds)

def _diffSeconds(self, other):
return

def _setTotalSeconds(self):
days = self.years*365 + self._monthsToDays(self.months) + self.days
self.totalSeconds = (((days*24 + self.hours)*60 + self.minutes)*60
+ self.seconds)

self.totalSeconds = (((days*24 + self.hours)*60 + self.minutes)*60 +
self.seconds)

def _monthsToDays(self, months):
daysInMonth = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]
Expand Down Expand Up @@ -182,7 +225,7 @@ def _secondsToDate(self, seconds):
def _parseDate(self, dateString):
"""
parses a dateString in one of the following formats into
a datetime object:
a Date object:
YYYY-MM-DD_hh:mm:ss
YYYY-MM-DD_hh.mm.ss
YYYY-MM-DD_SSSSS
Expand Down Expand Up @@ -213,7 +256,7 @@ def _parseDate(self, dateString):
hms = dateString

if '.' in hms:
hms = hms.replace('.',':')
hms = hms.replace('.', ':')

if '-' in ymd:
(self.years, self.months, self.days) \
Expand Down
25 changes: 25 additions & 0 deletions mpas_analysis/test/test_date.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
"""

import pytest
import datetime
from mpas_analysis.test import TestCase, loaddatadir
from mpas_analysis.shared.timekeeping.Date import Date

Expand Down Expand Up @@ -118,5 +119,29 @@ def test_date(self):
diff = date1-date2
self.assertEqual(diff, Date(dateString='1995-12-26', isInterval=False))

date = Date(dateString='1996-01-15', isInterval=False)
datetime1 = date.to_datetime(yearOffset=0)
datetime2 = datetime.datetime(year=1996, month=1, day=15)
self.assertEqual(datetime1, datetime2)

date = Date(dateString='0000-00-20', isInterval=True)
timedelta1 = date.to_timedelta()
timedelta2 = datetime.timedelta(days=20)
self.assertEqual(timedelta1, timedelta2)

date = Date(dateString='0001-01-01', isInterval=False)
datetime1 = date.to_datetime(yearOffset=0)
datetime2 = datetime.datetime(year=1678, month=1, day=1)
self.assertEqual(datetime1, datetime2)

date = Date(dateString='0001-01-01', isInterval=False)
datetime1 = date.to_datetime(yearOffset=1849)
datetime2 = datetime.datetime(year=1850, month=1, day=1)
self.assertEqual(datetime1, datetime2)

date = Date(dateString='9999-01-01', isInterval=False)
datetime1 = date.to_datetime(yearOffset=0)
datetime2 = datetime.datetime(year=2262, month=1, day=1)
self.assertEqual(datetime1, datetime2)

# vim: foldmethod=marker ai ts=4 sts=4 et sw=4 ft=python

0 comments on commit e30389f

Please sign in to comment.