Hide keyboard shortcuts

Hot-keys on this page

r m x p   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

1from __future__ import division 

2# Copyright (c) 2010-2020 openpyxl 

3 

4"""Manage Excel date weirdness.""" 

5 

6# Python stdlib imports 

7import datetime 

8from datetime import timedelta, tzinfo 

9from math import isnan 

10import re 

11 

12from jdcal import ( 

13 gcal2jd, 

14 jd2gcal, 

15 MJD_0 

16) 

17 

18 

19# constants 

20MAC_EPOCH = datetime.date(1904, 1, 1) 

21WINDOWS_EPOCH = datetime.date(1899, 12, 30) 

22CALENDAR_WINDOWS_1900 = sum(gcal2jd(WINDOWS_EPOCH.year, WINDOWS_EPOCH.month, WINDOWS_EPOCH.day)) 

23CALENDAR_MAC_1904 = sum(gcal2jd(MAC_EPOCH.year, MAC_EPOCH.month, MAC_EPOCH.day)) 

24SECS_PER_DAY = 86400 

25 

26EPOCH = datetime.datetime.utcfromtimestamp(0) 

27ISO_FORMAT = '%Y-%m-%dT%H:%M:%SZ' 

28ISO_REGEX = re.compile(r''' 

29(?P<date>(?P<year>\d{4})-(?P<month>\d{2})-(?P<day>\d{2}))?T? 

30(?P<time>(?P<hour>\d{2}):(?P<minute>\d{2}):(?P<second>\d{2})(.(?P<ms>\d{2}))?)?Z?''', 

31 re.VERBOSE) 

32 

33 

34def to_ISO8601(dt): 

35 """Convert from a datetime to a timestamp string.""" 

36 return datetime.datetime.strftime(dt, ISO_FORMAT) 

37 

38 

39def from_ISO8601(formatted_string): 

40 """Convert from a timestamp string to a datetime object. According to 

41 18.17.4 in the specification the following ISO 8601 formats are 

42 supported. 

43 

44 Dates B.1.1 and B.2.1 

45 Times B.1.2 and B.2.2 

46 Datetimes B.1.3 and B.2.3 

47 

48 There is no concept of timedeltas 

49 """ 

50 match = ISO_REGEX.match(formatted_string) 

51 if not match: 

52 raise ValueError("Invalid datetime value {}".format(formatted_string)) 

53 

54 parts = {k:int(v) for k, v in match.groupdict().items() if v is not None and v.isdigit()} 

55 if 'year' not in parts: 

56 dt = datetime.time(parts['hour'], parts['minute'], parts['second']) 

57 elif 'hour' not in parts: 

58 dt = datetime.date(parts['year'], parts['month'], parts['day']) 

59 else: 

60 dt = datetime.datetime(year=parts['year'], month=parts['month'], 

61 day=parts['day'], hour=parts['hour'], minute=parts['minute'], 

62 second=parts['second']) 

63 if 'ms' in parts: 

64 dt += timedelta(microseconds=parts['ms']) 

65 return dt 

66 

67 

68def to_excel(dt, offset=CALENDAR_WINDOWS_1900): 

69 if isinstance(dt, datetime.time): 

70 return time_to_days(dt) 

71 if isinstance(dt, datetime.timedelta): 

72 return timedelta_to_days(dt) 

73 if isnan(dt.year): # Pandas supports Not a Date 

74 return 

75 jul = sum(gcal2jd(dt.year, dt.month, dt.day)) - offset 

76 if jul <= 60 and offset == CALENDAR_WINDOWS_1900: 

77 jul -= 1 

78 if hasattr(dt, 'time'): 

79 jul += time_to_days(dt) 

80 return jul 

81 

82 

83def from_excel(value, offset=CALENDAR_WINDOWS_1900): 

84 if value is None: 

85 return 

86 if 1 < value < 60 and offset == CALENDAR_WINDOWS_1900: 

87 value += 1 

88 parts = list(jd2gcal(MJD_0, value + offset - MJD_0)) 

89 _, fraction = divmod(value, 1) 

90 jumped = (parts[-1] == 0 and fraction > 0) 

91 diff = datetime.timedelta(days=fraction) 

92 

93 if 0 < abs(value) < 1: 

94 return days_to_time(diff) 

95 if not jumped: 

96 return datetime.datetime(*parts[:3]) + diff 

97 else: 

98 return datetime.datetime(*parts[:3] + [0]) 

99 

100 

101class GMT(tzinfo): 

102 

103 def utcoffset(self, dt): 

104 return timedelta(0) 

105 

106 def dst(self, dt): 

107 return timedelta(0) 

108 

109 def tzname(self,dt): 

110 return "GMT" 

111 

112try: 

113 from datetime import timezone 

114 UTC = timezone(timedelta(0)) 

115except ImportError: 

116 # Python 2 

117 UTC = GMT() 

118 

119 

120def time_to_days(value): 

121 """Convert a time value to fractions of day""" 

122 if value.tzinfo is not None: 

123 value = value.astimezone(UTC) 

124 return ( 

125 (value.hour * 3600) 

126 + (value.minute * 60) 

127 + value.second 

128 + value.microsecond / 10**6 

129 ) / SECS_PER_DAY 

130 

131 

132def timedelta_to_days(value): 

133 """Convert a timedelta value to fractions of a day""" 

134 if not hasattr(value, 'total_seconds'): 

135 secs = (value.microseconds + 

136 (value.seconds + value.days * SECS_PER_DAY) * 10**6) / 10**6 

137 else: 

138 secs = value.total_seconds() 

139 return secs / SECS_PER_DAY 

140 

141 

142def days_to_time(value): 

143 mins, seconds = divmod(value.seconds, 60) 

144 hours, mins = divmod(mins, 60) 

145 return datetime.time(hours, mins, seconds, value.microseconds)