Source code for openpyxl.utils.datetime

from __future__ import division
# Copyright (c) 2010-2019 openpyxl

"""Manage Excel date weirdness."""

# Python stdlib imports
import datetime
from datetime import timedelta, tzinfo
from math import isnan
import re

from jdcal import (
    gcal2jd,
    jd2gcal,
    MJD_0
)


# constants
MAC_EPOCH = datetime.date(1904, 1, 1)
WINDOWS_EPOCH = datetime.date(1899, 12, 30)
CALENDAR_WINDOWS_1900 = sum(gcal2jd(WINDOWS_EPOCH.year, WINDOWS_EPOCH.month, WINDOWS_EPOCH.day))
CALENDAR_MAC_1904 = sum(gcal2jd(MAC_EPOCH.year, MAC_EPOCH.month, MAC_EPOCH.day))
SECS_PER_DAY = 86400

EPOCH = datetime.datetime.utcfromtimestamp(0)
ISO_FORMAT = '%Y-%m-%dT%H:%M:%SZ'
ISO_REGEX = re.compile(r'''
(?P<date>(?P<year>\d{4})-(?P<month>\d{2})-(?P<day>\d{2}))?T?
(?P<time>(?P<hour>\d{2}):(?P<minute>\d{2}):(?P<second>\d{2})(.(?P<ms>\d{2}))?)?Z?''',
                                       re.VERBOSE)


[docs]def to_ISO8601(dt): """Convert from a datetime to a timestamp string.""" return datetime.datetime.strftime(dt, ISO_FORMAT)
[docs]def from_ISO8601(formatted_string): """Convert from a timestamp string to a datetime object. According to 18.17.4 in the specification the following ISO 8601 formats are supported. Dates B.1.1 and B.2.1 Times B.1.2 and B.2.2 Datetimes B.1.3 and B.2.3 There is no concept of timedeltas """ match = ISO_REGEX.match(formatted_string) if not match: raise ValueError("Invalid datetime value {}".format(formatted_string)) parts = {k:int(v) for k, v in match.groupdict().items() if v is not None and v.isdigit()} if 'year' not in parts: dt = datetime.time(parts['hour'], parts['minute'], parts['second']) elif 'hour' not in parts: dt = datetime.date(parts['year'], parts['month'], parts['day']) else: dt = datetime.datetime(year=parts['year'], month=parts['month'], day=parts['day'], hour=parts['hour'], minute=parts['minute'], second=parts['second']) if 'ms' in parts: dt += timedelta(microseconds=parts['ms']) return dt
[docs]def to_excel(dt, offset=CALENDAR_WINDOWS_1900): if isinstance(dt, datetime.time): return time_to_days(dt) if isinstance(dt, datetime.timedelta): return timedelta_to_days(dt) if isnan(dt.year): # Pandas supports Not a Date return jul = sum(gcal2jd(dt.year, dt.month, dt.day)) - offset if jul <= 60 and offset == CALENDAR_WINDOWS_1900: jul -= 1 if hasattr(dt, 'time'): jul += time_to_days(dt) return jul
[docs]def from_excel(value, offset=CALENDAR_WINDOWS_1900): if value is None: return if 1 < value < 60 and offset == CALENDAR_WINDOWS_1900: value += 1 parts = list(jd2gcal(MJD_0, value + offset - MJD_0)) _, fraction = divmod(value, 1) jumped = (parts[-1] == 0 and fraction > 0) diff = datetime.timedelta(days=fraction) if 0 < abs(value) < 1: return days_to_time(diff) if not jumped: return datetime.datetime(*parts[:3]) + diff else: return datetime.datetime(*parts[:3] + [0])
[docs]class GMT(tzinfo):
[docs] def utcoffset(self, dt): return timedelta(0)
[docs] def dst(self, dt): return timedelta(0)
[docs] def tzname(self,dt): return "GMT"
try: from datetime import timezone UTC = timezone(timedelta(0)) except ImportError: # Python 2 UTC = GMT()
[docs]def time_to_days(value): """Convert a time value to fractions of day""" if value.tzinfo is not None: value = value.astimezone(UTC) return ( (value.hour * 3600) + (value.minute * 60) + value.second + value.microsecond / 10**6 ) / SECS_PER_DAY
[docs]def timedelta_to_days(value): """Convert a timedelta value to fractions of a day""" if not hasattr(value, 'total_seconds'): secs = (value.microseconds + (value.seconds + value.days * SECS_PER_DAY) * 10**6) / 10**6 else: secs = value.total_seconds() return secs / SECS_PER_DAY
[docs]def days_to_time(value): mins, seconds = divmod(value.seconds, 60) hours, mins = divmod(mins, 60) return datetime.time(hours, mins, seconds, value.microseconds)