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    return TimeClip(MakeDate(MakeDay(y, m, dt), MakeTime(h, mi, sec, mili)))
36
37
38@Js
39def parse(string):
40    return PyJsDate(
41        TimeClip(parse_date(string.to_string().value)),
42        prototype=DatePrototype)
43
44
45Date.define_own_property('now', {
46    'value': Js(now),
47    'enumerable': False,
48    'writable': True,
49    'configurable': True
50})
51
52Date.define_own_property('parse', {
53    'value': parse,
54    'enumerable': False,
55    'writable': True,
56    'configurable': True
57})
58
59Date.define_own_property('UTC', {
60    'value': UTC,
61    'enumerable': False,
62    'writable': True,
63    'configurable': True
64})
65
66
67class PyJsDate(PyJs):
68    Class = 'Date'
69    extensible = True
70
71    def __init__(self, value, prototype=None):
72        self.value = value
73        self.own = {}
74        self.prototype = prototype
75
76    # todo fix this problematic datetime part
77    def to_local_dt(self):
78        return datetime.datetime(1970, 1, 1) + datetime.timedelta(
79            seconds=UTCToLocal(self.value) // 1000)
80
81    def to_utc_dt(self):
82        return datetime.datetime(1970, 1, 1) + datetime.timedelta(
83            seconds=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    date_formats = (
122        "%Y-%m-%d",
123        "%m/%d/%Y",
124        "%b %d %Y",
125    )
126    # Supports these hour formats and with or hour.
127    hour_formats = (
128        "T%H:%M:%S.%f",
129        "T%H:%M:%S",
130    ) + ('',)
131    # Supports with or without Z indicator.
132    z_formats = ("Z",) + ('',)
133    supported_formats = [
134        d + t + z
135        for d in date_formats
136        for t in hour_formats
137        for z in z_formats
138    ]
139    for date_format in supported_formats:
140        try:
141            dt = datetime.datetime.strptime(py_string, date_format)
142        except ValueError:
143            continue
144        else:
145            return MakeDate(
146                MakeDay(Js(dt.year), Js(dt.month - 1), Js(dt.day)),
147                MakeTime(
148                    Js(dt.hour), Js(dt.minute), Js(dt.second),
149                    Js(dt.microsecond // 1000)))
150
151    raise MakeError(
152        'TypeError',
153        'Could not parse date %s - unsupported date format. Currently only supported formats are RFC3339 utc, ISO Date, Short Date, and Long Date. Sorry!'
154        % py_string)
155
156
157def date_constructor(*args):
158    if len(args) >= 2:
159        return date_constructor2(*args)
160    elif len(args) == 1:
161        return date_constructor1(args[0])
162    else:
163        return date_constructor0()
164
165
166def date_constructor0():
167    return now()
168
169
170def date_constructor1(value):
171    v = value.to_primitive()
172    if v._type() == 'String':
173        v = parse_date(v.value)
174    else:
175        v = v.to_int()
176    return PyJsDate(TimeClip(v), prototype=DatePrototype)
177
178
179def date_constructor2(*args):
180    y = args[0].to_number()
181    m = args[1].to_number()
182    l = len(args)
183    dt = args[2].to_number() if l > 2 else Js(1)
184    h = args[3].to_number() if l > 3 else Js(0)
185    mi = args[4].to_number() if l > 4 else Js(0)
186    sec = args[5].to_number() if l > 5 else Js(0)
187    mili = args[6].to_number() if l > 6 else Js(0)
188    if not y.is_nan() and 0 <= y.value <= 99:
189        y = y + Js(1900)
190    t = TimeClip(
191        LocalToUTC(MakeDate(MakeDay(y, m, dt), MakeTime(h, mi, sec, mili))))
192    return PyJsDate(t, prototype=DatePrototype)
193
194
195Date.create = date_constructor
196
197DatePrototype = PyJsDate(float('nan'), prototype=ObjectPrototype)
198
199
200def check_date(obj):
201    if obj.Class != 'Date':
202        raise MakeError('TypeError', 'this is not a Date object')
203
204
205class DateProto:
206    def toString():
207        check_date(this)
208        if this.value is NaN:
209            return 'Invalid Date'
210        offset = (UTCToLocal(this.value) - this.value) // msPerHour
211        return this.local_strftime(
212            '%a %b %d %Y %H:%M:%S GMT') + '%s00 (%s)' % (pad(
213                offset, 2, True), GetTimeZoneName(this.value))
214
215    def toDateString():
216        check_date(this)
217        return this.local_strftime('%d %B %Y')
218
219    def toTimeString():
220        check_date(this)
221        return this.local_strftime('%H:%M:%S')
222
223    def toLocaleString():
224        check_date(this)
225        return this.local_strftime('%d %B %Y %H:%M:%S')
226
227    def toLocaleDateString():
228        check_date(this)
229        return this.local_strftime('%d %B %Y')
230
231    def toLocaleTimeString():
232        check_date(this)
233        return this.local_strftime('%H:%M:%S')
234
235    def valueOf():
236        check_date(this)
237        return this.value
238
239    def getTime():
240        check_date(this)
241        return this.value
242
243    def getFullYear():
244        check_date(this)
245        if this.value is NaN:
246            return NaN
247        return YearFromTime(UTCToLocal(this.value))
248
249    def getUTCFullYear():
250        check_date(this)
251        if this.value is NaN:
252            return NaN
253        return YearFromTime(this.value)
254
255    def getMonth():
256        check_date(this)
257        if this.value is NaN:
258            return NaN
259        return MonthFromTime(UTCToLocal(this.value))
260
261    def getDate():
262        check_date(this)
263        if this.value is NaN:
264            return NaN
265        return DateFromTime(UTCToLocal(this.value))
266
267    def getUTCMonth():
268        check_date(this)
269        if this.value is NaN:
270            return NaN
271        return MonthFromTime(this.value)
272
273    def getUTCDate():
274        check_date(this)
275        if this.value is NaN:
276            return NaN
277        return DateFromTime(this.value)
278
279    def getDay():
280        check_date(this)
281        if this.value is NaN:
282            return NaN
283        return WeekDay(UTCToLocal(this.value))
284
285    def getUTCDay():
286        check_date(this)
287        if this.value is NaN:
288            return NaN
289        return WeekDay(this.value)
290
291    def getHours():
292        check_date(this)
293        if this.value is NaN:
294            return NaN
295        return HourFromTime(UTCToLocal(this.value))
296
297    def getUTCHours():
298        check_date(this)
299        if this.value is NaN:
300            return NaN
301        return HourFromTime(this.value)
302
303    def getMinutes():
304        check_date(this)
305        if this.value is NaN:
306            return NaN
307        return MinFromTime(UTCToLocal(this.value))
308
309    def getUTCMinutes():
310        check_date(this)
311        if this.value is NaN:
312            return NaN
313        return MinFromTime(this.value)
314
315    def getSeconds():
316        check_date(this)
317        if this.value is NaN:
318            return NaN
319        return SecFromTime(UTCToLocal(this.value))
320
321    def getUTCSeconds():
322        check_date(this)
323        if this.value is NaN:
324            return NaN
325        return SecFromTime(this.value)
326
327    def getMilliseconds():
328        check_date(this)
329        if this.value is NaN:
330            return NaN
331        return msFromTime(UTCToLocal(this.value))
332
333    def getUTCMilliseconds():
334        check_date(this)
335        if this.value is NaN:
336            return NaN
337        return msFromTime(this.value)
338
339    def getTimezoneOffset():
340        check_date(this)
341        if this.value is NaN:
342            return NaN
343        return (this.value - UTCToLocal(this.value)) // 60000
344
345    def setTime(time):
346        check_date(this)
347        this.value = TimeClip(time.to_number().to_int())
348        return this.value
349
350    def setMilliseconds(ms):
351        check_date(this)
352        t = UTCToLocal(this.value)
353        tim = MakeTime(
354            Js(HourFromTime(t)), Js(MinFromTime(t)), Js(SecFromTime(t)), ms)
355        u = TimeClip(LocalToUTC(MakeDate(Day(t), tim)))
356        this.value = u
357        return u
358
359    def setUTCMilliseconds(ms):
360        check_date(this)
361        t = this.value
362        tim = MakeTime(
363            Js(HourFromTime(t)), Js(MinFromTime(t)), Js(SecFromTime(t)), ms)
364        u = TimeClip(MakeDate(Day(t), tim))
365        this.value = u
366        return u
367
368    def setSeconds(sec, ms=None):
369        check_date(this)
370        t = UTCToLocal(this.value)
371        s = sec.to_number()
372        if not ms is None: milli = Js(msFromTime(t))
373        else: milli = ms.to_number()
374        date = MakeDate(
375            Day(t), MakeTime(Js(HourFromTime(t)), Js(MinFromTime(t)), s, milli))
376        u = TimeClip(LocalToUTC(date))
377        this.value = u
378        return u
379
380    def setUTCSeconds(sec, ms=None):
381        check_date(this)
382        t = this.value
383        s = sec.to_number()
384        if not ms is None: milli = Js(msFromTime(t))
385        else: milli = ms.to_number()
386        date = MakeDate(
387            Day(t), MakeTime(Js(HourFromTime(t)), Js(MinFromTime(t)), s, milli))
388        v = TimeClip(date)
389        this.value = v
390        return v
391
392    def setMinutes(min, sec=None, ms=None):
393        check_date(this)
394        t = UTCToLocal(this.value)
395        m = min.to_number()
396        if not sec is None: s = Js(SecFromTime(t))
397        else: s = sec.to_number()
398        if not ms is None: milli = Js(msFromTime(t))
399        else: milli = ms.to_number()
400        date = MakeDate(Day(t), MakeTime(Js(HourFromTime(t)), m, s, milli))
401        u = TimeClip(LocalToUTC(date))
402        this.value = u
403        return u
404
405    def setUTCMinutes(min, sec=None, ms=None):
406        check_date(this)
407        t = this.value
408        m = min.to_number()
409        if not sec is None: s = Js(SecFromTime(t))
410        else: s = sec.to_number()
411        if not ms is None: milli = Js(msFromTime(t))
412        else: milli = ms.to_number()
413        date = MakeDate(Day(t), MakeTime(Js(HourFromTime(t)), m, s, milli))
414        v = TimeClip(date)
415        this.value = v
416        return v
417
418    def setHours(hour, min=None, sec=None, ms=None):
419        check_date(this)
420        t = UTCToLocal(this.value)
421        h = hour.to_number()
422        if not min is None: m = Js(MinFromTime(t))
423        else: m = min.to_number()
424        if not sec is None: s = Js(SecFromTime(t))
425        else: s = sec.to_number()
426        if not ms is None: milli = Js(msFromTime(t))
427        else: milli = ms.to_number()
428        date = MakeDate(Day(t), MakeTime(h, m, s, milli))
429        u = TimeClip(LocalToUTC(date))
430        this.value = u
431        return u
432
433    def setUTCHours(hour, min=None, sec=None, ms=None):
434        check_date(this)
435        t = this.value
436        h = hour.to_number()
437        if not min is None: m = Js(MinFromTime(t))
438        else: m = min.to_number()
439        if not sec is None: s = Js(SecFromTime(t))
440        else: s = sec.to_number()
441        if not ms is None: milli = Js(msFromTime(t))
442        else: milli = ms.to_number()
443        date = MakeDate(Day(t), MakeTime(h, m, s, milli))
444        v = TimeClip(date)
445        this.value = v
446        return v
447
448    def setDate(date):
449        check_date(this)
450        t = UTCToLocal(this.value)
451        dt = date.to_number()
452        newDate = MakeDate(
453            MakeDay(Js(YearFromTime(t)), Js(MonthFromTime(t)), dt), TimeWithinDay(t))
454        u = TimeClip(LocalToUTC(newDate))
455        this.value = u
456        return u
457
458    def setUTCDate(date):
459        check_date(this)
460        t = this.value
461        dt = date.to_number()
462        newDate = MakeDate(
463            MakeDay(Js(YearFromTime(t)), Js(MonthFromTime(t)), dt), TimeWithinDay(t))
464        v = TimeClip(newDate)
465        this.value = v
466        return v
467
468    def setMonth(month, date=None):
469        check_date(this)
470        t = UTCToLocal(this.value)
471        m = month.to_number()
472        if not date is None: dt = Js(DateFromTime(t))
473        else: dt = date.to_number()
474        newDate = MakeDate(
475            MakeDay(Js(YearFromTime(t)), m, dt), TimeWithinDay(t))
476        u = TimeClip(LocalToUTC(newDate))
477        this.value = u
478        return u
479
480    def setUTCMonth(month, date=None):
481        check_date(this)
482        t = this.value
483        m = month.to_number()
484        if not date is None: dt = Js(DateFromTime(t))
485        else: dt = date.to_number()
486        newDate = MakeDate(
487            MakeDay(Js(YearFromTime(t)), m, dt), TimeWithinDay(t))
488        v = TimeClip(newDate)
489        this.value = v
490        return v
491
492    def setFullYear(year, month=None, date=None):
493        check_date(this)
494        if not this.value is NaN: t = UTCToLocal(this.value)
495        else: t = 0
496        y = year.to_number()
497        if not month is None: m = Js(MonthFromTime(t))
498        else: m = month.to_number()
499        if not date is None: dt = Js(DateFromTime(t))
500        else: dt = date.to_number()
501        newDate = MakeDate(
502            MakeDay(y, m, dt), TimeWithinDay(t))
503        u = TimeClip(LocalToUTC(newDate))
504        this.value = u
505        return u
506
507    def setUTCFullYear(year, month=None, date=None):
508        check_date(this)
509        if not this.value is NaN: t = UTCToLocal(this.value)
510        else: t = 0
511        y = year.to_number()
512        if not month is None: m = Js(MonthFromTime(t))
513        else: m = month.to_number()
514        if not date is None: dt = Js(DateFromTime(t))
515        else: dt = date.to_number()
516        newDate = MakeDate(
517            MakeDay(y, m, dt), TimeWithinDay(t))
518        v = TimeClip(newDate)
519        this.value = v
520        return v
521
522    def toUTCString():
523        check_date(this)
524        return this.utc_strftime('%d %B %Y %H:%M:%S')
525
526    def toISOString():
527        check_date(this)
528        t = this.value
529        year = YearFromTime(t)
530        month, day, hour, minute, second, milli = pad(
531            MonthFromTime(t) + 1), pad(DateFromTime(t)), pad(
532                HourFromTime(t)), pad(MinFromTime(t)), pad(
533                    SecFromTime(t)), pad(msFromTime(t))
534        return ISO_FORMAT % (unicode(year) if 0 <= year <= 9999 else pad(
535            year, 6, True), month, day, hour, minute, second, milli)
536
537    def toJSON(key):
538        o = this.to_object()
539        tv = o.to_primitive('Number')
540        if tv.Class == 'Number' and not tv.is_finite():
541            return this.null
542        toISO = o.get('toISOString')
543        if not toISO.is_callable():
544            raise this.MakeError('TypeError', 'toISOString is not callable')
545        return toISO.call(o, ())
546
547
548def pad(num, n=2, sign=False):
549    '''returns n digit string representation of the num'''
550    s = unicode(abs(num))
551    if len(s) < n:
552        s = '0' * (n - len(s)) + s
553    if not sign:
554        return s
555    if num >= 0:
556        return '+' + s
557    else:
558        return '-' + s
559
560
561fill_prototype(DatePrototype, DateProto, default_attrs)
562
563Date.define_own_property(
564    'prototype', {
565        'value': DatePrototype,
566        'enumerable': False,
567        'writable': False,
568        'configurable': False
569    })
570
571DatePrototype.define_own_property('constructor', {
572    'value': Date,
573    'enumerable': False,
574    'writable': True,
575    'configurable': True
576})
577