Use super() where applicable
[stgit.git] / stgit / lib / git / date.py
blobcd2f819c8fb99010315425e12894d2ab76cae2b0
1 import email.utils
2 import re
3 from datetime import datetime, timedelta, tzinfo
5 from stgit.exception import StgException
6 from stgit.lib.git.base import Immutable
7 from stgit.run import Run, RunException
10 class DateException(StgException):
11 """Exception raised when a date+time string could not be parsed."""
13 def __init__(self, string, type):
14 super().__init__('"%s" is not a valid %s' % (string, type))
17 class TimeZone(tzinfo):
18 """A simple time zone class for static offsets from UTC. (We have to
19 define our own since Python's standard library doesn't define any
20 time zone classes.)"""
22 def __init__(self, tzstring):
23 m = re.match(r'^([+-])(\d{2}):?(\d{2})$', tzstring)
24 if not m:
25 raise DateException(tzstring, 'time zone')
26 sign = int(m.group(1) + '1')
27 try:
28 self._offset = timedelta(
29 hours=sign * int(m.group(2)), minutes=sign * int(m.group(3))
31 except OverflowError:
32 raise DateException(tzstring, 'time zone')
33 self._name = tzstring
35 def utcoffset(self, dt):
36 return self._offset
38 def tzname(self, dt):
39 return self._name
41 def dst(self, dt):
42 return timedelta(0)
44 def __repr__(self):
45 return self._name
48 def system_date(datestring):
49 m = re.match(r"^(.+)([+-]\d\d:?\d\d)$", datestring)
50 if m:
51 # Time zone included; we parse it ourselves, since "date"
52 # would convert it to the local time zone.
53 (ds, z) = m.groups()
54 try:
55 t = Run("date", "+%Y-%m-%d-%H-%M-%S", "-d", ds).output_one_line()
56 except RunException:
57 return None
58 else:
59 # Time zone not included; we ask "date" to provide it for us.
60 try:
61 d = Run("date", "+%Y-%m-%d-%H-%M-%S_%z", "-d", datestring).output_one_line()
62 except RunException:
63 return None
64 (t, z) = d.split("_")
65 year, month, day, hour, minute, second = [int(x) for x in t.split("-")]
66 try:
67 return datetime(year, month, day, hour, minute, second, tzinfo=TimeZone(z))
68 except ValueError:
69 raise DateException(datestring, "date")
72 def git_date(datestring=''):
73 try:
74 ident = (
75 Run('git', 'var', 'GIT_AUTHOR_IDENT')
76 .env(
78 'GIT_AUTHOR_NAME': 'XXX',
79 'GIT_AUTHOR_EMAIL': 'XXX',
80 'GIT_AUTHOR_DATE': datestring,
83 .output_one_line()
85 except RunException:
86 return None
87 _, _, timestamp, offset = ident.split()
88 return datetime.fromtimestamp(int(timestamp), TimeZone(offset))
91 class Date(Immutable):
92 """Represents a timestamp used in git commits."""
94 def __init__(self, datestring):
95 # Try git-formatted date.
96 m = re.match(r'^(\d+)\s+([+-]\d\d:?\d\d)$', datestring)
97 if m:
98 try:
99 self._time = datetime.fromtimestamp(
100 int(m.group(1)), TimeZone(m.group(2))
102 except ValueError:
103 raise DateException(datestring, 'date')
104 return
106 # Try iso-formatted date.
107 m = re.match(
108 r'^(\d{4})-(\d{2})-(\d{2})\s+(\d{2}):(\d{2}):(\d{2})\s+'
109 r'([+-]\d\d:?\d\d)$',
110 datestring,
112 if m:
113 try:
114 self._time = datetime(
115 *[int(m.group(i + 1)) for i in range(6)],
116 **{'tzinfo': TimeZone(m.group(7))},
118 except ValueError:
119 raise DateException(datestring, 'date')
120 return
122 if datestring == 'now':
123 self._time = git_date()
124 assert self._time
125 return
127 # Try parsing with `git var`.
128 gd = git_date(datestring)
129 if gd:
130 self._time = gd
131 return
133 # Try parsing with the system's "date" command.
134 sd = system_date(datestring)
135 if sd:
136 self._time = sd
137 return
139 raise DateException(datestring, 'date')
141 def __repr__(self):
142 return self.isoformat()
144 def isoformat(self):
145 """Human-friendly ISO 8601 format."""
146 return '%s %s' % (
147 self._time.replace(tzinfo=None).isoformat(str(' ')),
148 self._time.tzinfo,
151 def rfc2822_format(self):
152 """Format date in RFC-2822 format, as used in email."""
153 return email.utils.format_datetime(self._time)
155 @classmethod
156 def maybe(cls, datestring):
157 """Return a new object initialized with the argument if it contains a
158 value (otherwise, just return the argument)."""
159 return cls(datestring) if datestring is not None else None