1from ..base import *
2from .time_helpers import *
3
4TZ_OFFSET = (time.altzone // 3600)
5ABS_OFFSET = abs(TZ_OFFSET)
6TZ_NAME = time.tzname[1]
7ISO_FORMAT = '%s-%s-%sT%s:%s:%s.%sZ'
8
9
10@Js
11def Date(year, month, date, hours, minutes, seconds, ms):
12    return now().to_string()
13
14
15Date.Class = 'Date'
16
17
18def now():
19    return PyJsDate(int(time.time() * 1000), prototype=DatePrototype)
20
21
22@Js
23def UTC(year, month, date, hours, minutes, seconds, ms):  # todo complete this
24    args = arguments
25    y = args[0].to_number()
26    m = args[1].to_number()
27    l = len(args)
28    dt = args[2].to_number() if l > 2 else Js(1)
29    h = args[3].to_number() if l > 3 else Js(0)
30    mi = args[4].to_number() if l > 4 else Js(0)
31    sec = args[5].to_number() if l > 5 else Js(0)
32    mili = args[6].to_number() if l > 6 else Js(0)
33    if not y.is_nan() and 0 <= y.value <= 99:
34        y = y + Js(1900)
35    t = TimeClip(MakeDate(MakeDay(y, m, dt), MakeTime(h, mi, sec, mili)))
36    return PyJsDate(t, prototype=DatePrototype)
37
38
39@Js
40def parse(string):
41    return PyJsDate(
42        TimeClip(parse_date(string.to_string().value)),
43        prototype=DatePrototype)
44
45
46Date.define_own_property('now', {
47    'value': Js(now),
48    'enumerable': False,
49    'writable': True,
50    'configurable': True
51})
52
53Date.define_own_property('parse', {
54    'value': parse,
55    'enumerable': False,
56    'writable': True,
57    'configurable': True
58})
59
60Date.define_own_property('UTC', {
61    'value': UTC,
62    'enumerable': False,
63    'writable': True,
64    'configurable': True
65})
66
67
68class PyJsDate(PyJs):
69    Class = 'Date'
70    extensible = True
71
72    def __init__(self, value, prototype=None):
73        self.value = value
74        self.own = {}
75        self.prototype = prototype
76
77    # todo fix this problematic datetime part
78    def to_local_dt(self):
79        return datetime.datetime.utcfromtimestamp(
80            UTCToLocal(self.value) // 1000)
81
82    def to_utc_dt(self):
83        return datetime.datetime.utcfromtimestamp(self.value // 1000)
84
85    def local_strftime(self, pattern):
86        if self.value is NaN:
87            return 'Invalid Date'
88        try:
89            dt = self.to_local_dt()
90        except:
91            raise MakeError(
92                'TypeError',
93                'unsupported date range. Will fix in future versions')
94        try:
95            return dt.strftime(pattern)
96        except:
97            raise MakeError(
98                'TypeError',
99                'Could not generate date string from this date (limitations of python.datetime)'
100            )
101
102    def utc_strftime(self, pattern):
103        if self.value is NaN:
104            return 'Invalid Date'
105        try:
106            dt = self.to_utc_dt()
107        except:
108            raise MakeError(
109                'TypeError',
110                'unsupported date range. Will fix in future versions')
111        try:
112            return dt.strftime(pattern)
113        except:
114            raise MakeError(
115                'TypeError',
116                'Could not generate date string from this date (limitations of python.datetime)'
117            )
118
119
120def parse_date(py_string):  # todo support all date string formats
121    try:
122        try:
123            dt = datetime.datetime.strptime(py_string, "%Y-%m-%dT%H:%M:%S.%fZ")
124        except:
125            dt = datetime.datetime.strptime(py_string, "%Y-%m-%dT%H:%M:%SZ")
126        return MakeDate(
127            MakeDay(Js(dt.year), Js(dt.month - 1), Js(dt.day)),
128            MakeTime(
129                Js(dt.hour), Js(dt.minute), Js(dt.second),
130                Js(dt.microsecond // 1000)))
131    except:
132        raise MakeError(
133            'TypeError',
134            'Could not parse date %s - unsupported date format. Currently only supported format is RFC3339 utc. Sorry!'
135            % py_string)
136
137
138def date_constructor(*args):
139    if len(args) >= 2:
140        return date_constructor2(*args)
141    elif len(args) == 1:
142        return date_constructor1(args[0])
143    else:
144        return date_constructor0()
145
146
147def date_constructor0():
148    return now()
149
150
151def date_constructor1(value):
152    v = value.to_primitive()
153    if v._type() == 'String':
154        v = parse_date(v.value)
155    else:
156        v = v.to_int()
157    return PyJsDate(TimeClip(v), prototype=DatePrototype)
158
159
160def date_constructor2(*args):
161    y = args[0].to_number()
162    m = args[1].to_number()
163    l = len(args)
164    dt = args[2].to_number() if l > 2 else Js(1)
165    h = args[3].to_number() if l > 3 else Js(0)
166    mi = args[4].to_number() if l > 4 else Js(0)
167    sec = args[5].to_number() if l > 5 else Js(0)
168    mili = args[6].to_number() if l > 6 else Js(0)
169    if not y.is_nan() and 0 <= y.value <= 99:
170        y = y + Js(1900)
171    t = TimeClip(
172        LocalToUTC(MakeDate(MakeDay(y, m, dt), MakeTime(h, mi, sec, mili))))
173    return PyJsDate(t, prototype=DatePrototype)
174
175
176Date.create = date_constructor
177
178DatePrototype = PyJsDate(float('nan'), prototype=ObjectPrototype)
179
180
181def check_date(obj):
182    if obj.Class != 'Date':
183        raise MakeError('TypeError', 'this is not a Date object')
184
185
186class DateProto:
187    def toString():
188        check_date(this)
189        if this.value is NaN:
190            return 'Invalid Date'
191        offset = (UTCToLocal(this.value) - this.value) // msPerHour
192        return this.local_strftime(
193            '%a %b %d %Y %H:%M:%S GMT') + '%s00 (%s)' % (pad(
194                offset, 2, True), GetTimeZoneName(this.value))
195
196    def toDateString():
197        check_date(this)
198        return this.local_strftime('%d %B %Y')
199
200    def toTimeString():
201        check_date(this)
202        return this.local_strftime('%H:%M:%S')
203
204    def toLocaleString():
205        check_date(this)
206        return this.local_strftime('%d %B %Y %H:%M:%S')
207
208    def toLocaleDateString():
209        check_date(this)
210        return this.local_strftime('%d %B %Y')
211
212    def toLocaleTimeString():
213        check_date(this)
214        return this.local_strftime('%H:%M:%S')
215
216    def valueOf():
217        check_date(this)
218        return this.value
219
220    def getTime():
221        check_date(this)
222        return this.value
223
224    def getFullYear():
225        check_date(this)
226        if this.value is NaN:
227            return NaN
228        return YearFromTime(UTCToLocal(this.value))
229
230    def getUTCFullYear():
231        check_date(this)
232        if this.value is NaN:
233            return NaN
234        return YearFromTime(this.value)
235
236    def getMonth():
237        check_date(this)
238        if this.value is NaN:
239            return NaN
240        return MonthFromTime(UTCToLocal(this.value))
241
242    def getDate():
243        check_date(this)
244        if this.value is NaN:
245            return NaN
246        return DateFromTime(UTCToLocal(this.value))
247
248    def getUTCMonth():
249        check_date(this)
250        if this.value is NaN:
251            return NaN
252        return MonthFromTime(this.value)
253
254    def getUTCDate():
255        check_date(this)
256        if this.value is NaN:
257            return NaN
258        return DateFromTime(this.value)
259
260    def getDay():
261        check_date(this)
262        if this.value is NaN:
263            return NaN
264        return WeekDay(UTCToLocal(this.value))
265
266    def getUTCDay():
267        check_date(this)
268        if this.value is NaN:
269            return NaN
270        return WeekDay(this.value)
271
272    def getHours():
273        check_date(this)
274        if this.value is NaN:
275            return NaN
276        return HourFromTime(UTCToLocal(this.value))
277
278    def getUTCHours():
279        check_date(this)
280        if this.value is NaN:
281            return NaN
282        return HourFromTime(this.value)
283
284    def getMinutes():
285        check_date(this)
286        if this.value is NaN:
287            return NaN
288        return MinFromTime(UTCToLocal(this.value))
289
290    def getUTCMinutes():
291        check_date(this)
292        if this.value is NaN:
293            return NaN
294        return MinFromTime(this.value)
295
296    def getSeconds():
297        check_date(this)
298        if this.value is NaN:
299            return NaN
300        return SecFromTime(UTCToLocal(this.value))
301
302    def getUTCSeconds():
303        check_date(this)
304        if this.value is NaN:
305            return NaN
306        return SecFromTime(this.value)
307
308    def getMilliseconds():
309        check_date(this)
310        if this.value is NaN:
311            return NaN
312        return msFromTime(UTCToLocal(this.value))
313
314    def getUTCMilliseconds():
315        check_date(this)
316        if this.value is NaN:
317            return NaN
318        return msFromTime(this.value)
319
320    def getTimezoneOffset():
321        check_date(this)
322        if this.value is NaN:
323            return NaN
324        return (this.value - UTCToLocal(this.value)) // 60000
325
326    def setTime(time):
327        check_date(this)
328        this.value = TimeClip(time.to_number().to_int())
329        return this.value
330
331    def setMilliseconds(ms):
332        check_date(this)
333        t = UTCToLocal(this.value)
334        tim = MakeTime(
335            HourFromTime(t), MinFromTime(t), SecFromTime(t), ms.to_int())
336        u = TimeClip(LocalToUTC(MakeDate(Day(t), tim)))
337        this.value = u
338        return u
339
340    def setUTCMilliseconds(ms):
341        check_date(this)
342        t = this.value
343        tim = MakeTime(
344            HourFromTime(t), MinFromTime(t), SecFromTime(t), ms.to_int())
345        u = TimeClip(MakeDate(Day(t), tim))
346        this.value = u
347        return u
348
349    # todo Complete all setters!
350
351    def toUTCString():
352        check_date(this)
353        return this.utc_strftime('%d %B %Y %H:%M:%S')
354
355    def toISOString():
356        check_date(this)
357        t = this.value
358        year = YearFromTime(t)
359        month, day, hour, minute, second, milli = pad(
360            MonthFromTime(t) + 1), pad(DateFromTime(t)), pad(
361                HourFromTime(t)), pad(MinFromTime(t)), pad(
362                    SecFromTime(t)), pad(msFromTime(t))
363        return ISO_FORMAT % (unicode(year) if 0 <= year <= 9999 else pad(
364            year, 6, True), month, day, hour, minute, second, milli)
365
366    def toJSON(key):
367        o = this.to_object()
368        tv = o.to_primitive('Number')
369        if tv.Class == 'Number' and not tv.is_finite():
370            return this.null
371        toISO = o.get('toISOString')
372        if not toISO.is_callable():
373            raise this.MakeError('TypeError', 'toISOString is not callable')
374        return toISO.call(o, ())
375
376
377def pad(num, n=2, sign=False):
378    '''returns n digit string representation of the num'''
379    s = unicode(abs(num))
380    if len(s) < n:
381        s = '0' * (n - len(s)) + s
382    if not sign:
383        return s
384    if num >= 0:
385        return '+' + s
386    else:
387        return '-' + s
388
389
390fill_prototype(DatePrototype, DateProto, default_attrs)
391
392Date.define_own_property(
393    'prototype', {
394        'value': DatePrototype,
395        'enumerable': False,
396        'writable': False,
397        'configurable': False
398    })
399
400DatePrototype.define_own_property('constructor', {
401    'value': Date,
402    'enumerable': False,
403    'writable': True,
404    'configurable': True
405})
406