Coverage for /var/srv/projects/api.amasfac.comuna18.com/tmp/venv/lib/python3.9/site-packages/xlrd/xldate.py: 23%
94 statements
« prev ^ index » next coverage.py v6.4.4, created at 2023-07-17 14:22 -0600
« prev ^ index » next coverage.py v6.4.4, created at 2023-07-17 14:22 -0600
1# -*- coding: utf-8 -*-
2# Copyright (c) 2005-2008 Stephen John Machin, Lingfo Pty Ltd
3# This module is part of the xlrd package, which is released under a
4# BSD-style licence.
5# No part of the content of this file was derived from the works of David Giffin.
6"""
7Tools for working with dates and times in Excel files.
9The conversion from ``days`` to ``(year, month, day)`` starts with
10an integral "julian day number" aka JDN.
11FWIW:
13- JDN 0 corresponds to noon on Monday November 24 in Gregorian year -4713.
15More importantly:
17- Noon on Gregorian 1900-03-01 (day 61 in the 1900-based system) is JDN 2415080.0
18- Noon on Gregorian 1904-01-02 (day 1 in the 1904-based system) is JDN 2416482.0
20"""
21import datetime
23_JDN_delta = (2415080 - 61, 2416482 - 1)
24assert _JDN_delta[1] - _JDN_delta[0] == 1462
26# Pre-calculate the datetime epochs for efficiency.
27epoch_1904 = datetime.datetime(1904, 1, 1)
28epoch_1900 = datetime.datetime(1899, 12, 31)
29epoch_1900_minus_1 = datetime.datetime(1899, 12, 30)
31# This is equivalent to 10000-01-01:
32_XLDAYS_TOO_LARGE = (2958466, 2958466 - 1462)
35class XLDateError(ValueError):
36 "A base class for all datetime-related errors."
39class XLDateNegative(XLDateError):
40 "``xldate < 0.00``"
43class XLDateAmbiguous(XLDateError):
44 "The 1900 leap-year problem ``(datemode == 0 and 1.0 <= xldate < 61.0)``"
47class XLDateTooLarge(XLDateError):
48 "Gregorian year 10000 or later"
51class XLDateBadDatemode(XLDateError):
52 "``datemode`` arg is neither 0 nor 1"
55class XLDateBadTuple(XLDateError):
56 pass
59def xldate_as_tuple(xldate, datemode):
60 """
61 Convert an Excel number (presumed to represent a date, a datetime or a time) into
62 a tuple suitable for feeding to datetime or mx.DateTime constructors.
64 :param xldate: The Excel number
65 :param datemode: 0: 1900-based, 1: 1904-based.
66 :raises xlrd.xldate.XLDateNegative:
67 :raises xlrd.xldate.XLDateAmbiguous:
69 :raises xlrd.xldate.XLDateTooLarge:
70 :raises xlrd.xldate.XLDateBadDatemode:
71 :raises xlrd.xldate.XLDateError:
72 :returns: Gregorian ``(year, month, day, hour, minute, nearest_second)``.
74 .. warning::
76 When using this function to interpret the contents of a workbook, you
77 should pass in the :attr:`~xlrd.book.Book.datemode`
78 attribute of that workbook. Whether the workbook has ever been anywhere
79 near a Macintosh is irrelevant.
81 .. admonition:: Special case
83 If ``0.0 <= xldate < 1.0``, it is assumed to represent a time;
84 ``(0, 0, 0, hour, minute, second)`` will be returned.
86 .. note::
88 ``1904-01-01`` is not regarded as a valid date in the ``datemode==1``
89 system; its "serial number" is zero.
90 """
91 if datemode not in (0, 1):
92 raise XLDateBadDatemode(datemode)
93 if xldate == 0.00:
94 return (0, 0, 0, 0, 0, 0)
95 if xldate < 0.00:
96 raise XLDateNegative(xldate)
97 xldays = int(xldate)
98 frac = xldate - xldays
99 seconds = int(round(frac * 86400.0))
100 assert 0 <= seconds <= 86400
101 if seconds == 86400:
102 hour = minute = second = 0
103 xldays += 1
104 else:
105 # second = seconds % 60; minutes = seconds // 60
106 minutes, second = divmod(seconds, 60)
107 # minute = minutes % 60; hour = minutes // 60
108 hour, minute = divmod(minutes, 60)
109 if xldays >= _XLDAYS_TOO_LARGE[datemode]:
110 raise XLDateTooLarge(xldate)
112 if xldays == 0:
113 return (0, 0, 0, hour, minute, second)
115 if xldays < 61 and datemode == 0:
116 raise XLDateAmbiguous(xldate)
118 jdn = xldays + _JDN_delta[datemode]
119 yreg = ((((jdn * 4 + 274277) // 146097) * 3 // 4) + jdn + 1363) * 4 + 3
120 mp = ((yreg % 1461) // 4) * 535 + 333
121 d = ((mp % 16384) // 535) + 1
122 # mp /= 16384
123 mp >>= 14
124 if mp >= 10:
125 return ((yreg // 1461) - 4715, mp - 9, d, hour, minute, second)
126 else:
127 return ((yreg // 1461) - 4716, mp + 3, d, hour, minute, second)
130def xldate_as_datetime(xldate, datemode):
131 """
132 Convert an Excel date/time number into a :class:`datetime.datetime` object.
134 :param xldate: The Excel number
135 :param datemode: 0: 1900-based, 1: 1904-based.
137 :returns: A :class:`datetime.datetime` object.
138 """
140 # Set the epoch based on the 1900/1904 datemode.
141 if datemode:
142 epoch = epoch_1904
143 else:
144 if xldate < 60:
145 epoch = epoch_1900
146 else:
147 # Workaround Excel 1900 leap year bug by adjusting the epoch.
148 epoch = epoch_1900_minus_1
150 # The integer part of the Excel date stores the number of days since
151 # the epoch and the fractional part stores the percentage of the day.
152 days = int(xldate)
153 fraction = xldate - days
155 # Get the the integer and decimal seconds in Excel's millisecond resolution.
156 seconds = int(round(fraction * 86400000.0))
157 seconds, milliseconds = divmod(seconds, 1000)
159 return epoch + datetime.timedelta(days, seconds, 0, milliseconds)
162# === conversions from date/time to xl numbers
164def _leap(y):
165 if y % 4: return 0
166 if y % 100: return 1
167 if y % 400: return 0
168 return 1
170_days_in_month = (None, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31)
173def xldate_from_date_tuple(date_tuple, datemode):
174 """
175 Convert a date tuple (year, month, day) to an Excel date.
177 :param year: Gregorian year.
178 :param month: ``1 <= month <= 12``
179 :param day: ``1 <= day <= last day of that (year, month)``
180 :param datemode: 0: 1900-based, 1: 1904-based.
181 :raises xlrd.xldate.XLDateAmbiguous:
182 :raises xlrd.xldate.XLDateBadDatemode:
183 :raises xlrd.xldate.XLDateBadTuple:
184 ``(year, month, day)`` is too early/late or has invalid component(s)
185 :raises xlrd.xldate.XLDateError:
186 """
187 year, month, day = date_tuple
189 if datemode not in (0, 1):
190 raise XLDateBadDatemode(datemode)
192 if year == 0 and month == 0 and day == 0:
193 return 0.00
195 if not (1900 <= year <= 9999):
196 raise XLDateBadTuple("Invalid year: %r" % ((year, month, day),))
197 if not (1 <= month <= 12):
198 raise XLDateBadTuple("Invalid month: %r" % ((year, month, day),))
199 if (day < 1 or
200 (day > _days_in_month[month] and not(day == 29 and month == 2 and _leap(year)))):
201 raise XLDateBadTuple("Invalid day: %r" % ((year, month, day),))
203 Yp = year + 4716
204 M = month
205 if M <= 2:
206 Yp = Yp - 1
207 Mp = M + 9
208 else:
209 Mp = M - 3
210 jdn = (1461 * Yp // 4) + ((979 * Mp + 16) // 32) + \
211 day - 1364 - (((Yp + 184) // 100) * 3 // 4)
212 xldays = jdn - _JDN_delta[datemode]
213 if xldays <= 0:
214 raise XLDateBadTuple("Invalid (year, month, day): %r" % ((year, month, day),))
215 if xldays < 61 and datemode == 0:
216 raise XLDateAmbiguous("Before 1900-03-01: %r" % ((year, month, day),))
217 return float(xldays)
220def xldate_from_time_tuple(time_tuple):
221 """
222 Convert a time tuple ``(hour, minute, second)`` to an Excel "date" value
223 (fraction of a day).
225 :param hour: ``0 <= hour < 24``
226 :param minute: ``0 <= minute < 60``
227 :param second: ``0 <= second < 60``
228 :raises xlrd.xldate.XLDateBadTuple: Out-of-range hour, minute, or second
229 """
230 hour, minute, second = time_tuple
231 if 0 <= hour < 24 and 0 <= minute < 60 and 0 <= second < 60:
232 return ((second / 60.0 + minute) / 60.0 + hour) / 24.0
233 raise XLDateBadTuple("Invalid (hour, minute, second): %r" % ((hour, minute, second),))
236def xldate_from_datetime_tuple(datetime_tuple, datemode):
237 """
238 Convert a datetime tuple ``(year, month, day, hour, minute, second)`` to an
239 Excel date value.
240 For more details, refer to other xldate_from_*_tuple functions.
242 :param datetime_tuple: ``(year, month, day, hour, minute, second)``
243 :param datemode: 0: 1900-based, 1: 1904-based.
244 """
245 return (
246 xldate_from_date_tuple(datetime_tuple[:3], datemode) +
247 xldate_from_time_tuple(datetime_tuple[3:])
248 )