rapydscript-ns 0.9.0 → 0.9.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (100) hide show
  1. package/.agignore +1 -1
  2. package/.github/workflows/ci.yml +38 -38
  3. package/=template.pyj +5 -5
  4. package/CHANGELOG.md +10 -0
  5. package/HACKING.md +103 -103
  6. package/LICENSE +24 -24
  7. package/README.md +7 -6
  8. package/TODO.md +116 -1
  9. package/add-toc-to-readme +2 -2
  10. package/bin/export +75 -75
  11. package/bin/rapydscript +70 -70
  12. package/bin/web-repl-export +102 -102
  13. package/build +2 -2
  14. package/language-service/index.js +9 -9
  15. package/language-service/language-service.d.ts +1 -1
  16. package/package.json +6 -2
  17. package/publish.py +37 -37
  18. package/release/compiler.js +246 -231
  19. package/release/signatures.json +23 -23
  20. package/session.vim +4 -4
  21. package/setup.cfg +2 -2
  22. package/src/compiler.pyj +36 -36
  23. package/src/errors.pyj +30 -30
  24. package/src/lib/aes.pyj +646 -646
  25. package/src/lib/contextlib.pyj +379 -0
  26. package/src/lib/copy.pyj +120 -120
  27. package/src/lib/datetime.pyj +712 -0
  28. package/src/lib/elementmaker.pyj +83 -83
  29. package/src/lib/encodings.pyj +126 -126
  30. package/src/lib/gettext.pyj +569 -569
  31. package/src/lib/io.pyj +500 -0
  32. package/src/lib/itertools.pyj +580 -580
  33. package/src/lib/json.pyj +227 -0
  34. package/src/lib/math.pyj +193 -193
  35. package/src/lib/operator.pyj +11 -11
  36. package/src/lib/pythonize.pyj +20 -20
  37. package/src/lib/random.pyj +118 -118
  38. package/src/lib/react.pyj +74 -74
  39. package/src/lib/traceback.pyj +63 -63
  40. package/src/lib/uuid.pyj +77 -77
  41. package/src/monaco-language-service/diagnostics.js +4 -4
  42. package/src/monaco-language-service/dts.js +550 -550
  43. package/src/monaco-language-service/index.js +2 -2
  44. package/src/output/comments.pyj +45 -45
  45. package/src/output/exceptions.pyj +201 -201
  46. package/src/output/jsx.pyj +164 -164
  47. package/src/output/loops.pyj +9 -0
  48. package/src/output/treeshake.pyj +182 -182
  49. package/src/output/utils.pyj +72 -72
  50. package/src/string_interpolation.pyj +72 -72
  51. package/src/tokenizer.pyj +1 -1
  52. package/src/unicode_aliases.pyj +576 -576
  53. package/src/utils.pyj +192 -192
  54. package/test/_import_one.pyj +37 -37
  55. package/test/_import_two/__init__.pyj +11 -11
  56. package/test/_import_two/level2/deep.pyj +4 -4
  57. package/test/_import_two/other.pyj +6 -6
  58. package/test/_import_two/sub.pyj +13 -13
  59. package/test/aes_vectors.pyj +421 -421
  60. package/test/annotations.pyj +80 -80
  61. package/test/contextlib.pyj +362 -0
  62. package/test/datetime.pyj +500 -0
  63. package/test/debugger_stmt.pyj +41 -0
  64. package/test/decorators.pyj +77 -77
  65. package/test/docstrings.pyj +39 -39
  66. package/test/elementmaker_test.pyj +45 -45
  67. package/test/functions.pyj +151 -151
  68. package/test/generators.pyj +41 -41
  69. package/test/generic.pyj +370 -370
  70. package/test/imports.pyj +72 -72
  71. package/test/internationalization.pyj +73 -73
  72. package/test/io.pyj +316 -0
  73. package/test/json.pyj +196 -0
  74. package/test/lint.pyj +164 -164
  75. package/test/loops.pyj +85 -85
  76. package/test/numpy.pyj +734 -734
  77. package/test/omit_function_metadata.pyj +20 -20
  78. package/test/repl.pyj +121 -121
  79. package/test/scoped_flags.pyj +76 -76
  80. package/test/unit/index.js +66 -0
  81. package/test/unit/language-service-dts.js +543 -543
  82. package/test/unit/language-service-hover.js +455 -455
  83. package/test/unit/language-service.js +1 -1
  84. package/test/unit/web-repl.js +533 -0
  85. package/tools/compiler.d.ts +367 -0
  86. package/tools/completer.js +131 -131
  87. package/tools/gettext.js +185 -185
  88. package/tools/ini.js +65 -65
  89. package/tools/msgfmt.js +187 -187
  90. package/tools/repl.js +223 -223
  91. package/tools/test.js +118 -118
  92. package/tools/utils.js +128 -128
  93. package/tools/web_repl.js +95 -95
  94. package/try +41 -41
  95. package/web-repl/env.js +196 -196
  96. package/web-repl/index.html +163 -163
  97. package/web-repl/prism.css +139 -139
  98. package/web-repl/prism.js +113 -113
  99. package/web-repl/rapydscript.js +224 -224
  100. package/web-repl/sha1.js +25 -25
@@ -0,0 +1,712 @@
1
+ # vim:fileencoding=utf-8
2
+ # License: Apache 2.0
3
+ # RapydScript implementation of Python's datetime standard library.
4
+ #
5
+ # Supported classes : timedelta, date, time, datetime
6
+ # Constants : MINYEAR, MAXYEAR
7
+ #
8
+ # Usage:
9
+ # from datetime import date, time, datetime, timedelta, MINYEAR, MAXYEAR
10
+ #
11
+ # Notes:
12
+ # - Arithmetic operators (+, -, *, etc.) only dispatch to __add__/__sub__
13
+ # etc. when ``from __python__ import overload_operators`` is active.
14
+ # Without that flag, call the dunder methods directly, e.g.
15
+ # ``d.__add__(timedelta(days=1))``.
16
+ # - Timezone-aware datetimes are not supported (no tzinfo / UTC offsets).
17
+ # - strftime supports: %Y %m %d %H %M %S %f %A %a %B %b %j %p %I %%
18
+
19
+ MINYEAR = 1
20
+ MAXYEAR = 9999
21
+
22
+ # ── Internal helpers ──────────────────────────────────────────────────────────
23
+
24
+ _DAYS_IN_MONTH = [0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]
25
+ # Cumulative days before month m in a non-leap year (index 0 unused)
26
+ _DBM = [0, 0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334]
27
+
28
+ def _is_leap(year):
29
+ return (year % 4 == 0 and year % 100 != 0) or year % 400 == 0
30
+
31
+ def _days_in_month(year, month):
32
+ if month == 2 and _is_leap(year):
33
+ return 29
34
+ return _DAYS_IN_MONTH[month]
35
+
36
+ def _check_field(val, name, lo, hi):
37
+ if jstype(val) is not 'number' or not isFinite(val) or Math.floor(val) != val:
38
+ raise TypeError(name + ' must be an integer')
39
+ if val < lo or val > hi:
40
+ raise ValueError(name + ' must be in ' + str(lo) + '..' + str(hi))
41
+
42
+ def _pad2(n):
43
+ if n < 10:
44
+ return '0' + str(n)
45
+ return str(n)
46
+
47
+ def _pad6(n):
48
+ s = str(n)
49
+ while s.length < 6:
50
+ s = '0' + s
51
+ return s
52
+
53
+ def _days_before_year(year):
54
+ y = year - 1
55
+ return y * 365 + Math.floor(y / 4) - Math.floor(y / 100) + Math.floor(y / 400)
56
+
57
+ def _days_before_month(year, month):
58
+ dbm = _DBM[month]
59
+ if month > 2 and _is_leap(year):
60
+ dbm += 1
61
+ return dbm
62
+
63
+ def _ymd_to_ord(year, month, day):
64
+ return _days_before_year(year) + _days_before_month(year, month) + day
65
+
66
+ def _ord_to_ymd(n):
67
+ # Ordinal 719163 == 1970-01-01 (Unix epoch in proleptic Gregorian calendar)
68
+ d = v"new Date((n - 719163) * 86400000)"
69
+ return [v"d.getUTCFullYear()", v"d.getUTCMonth() + 1", v"d.getUTCDate()"]
70
+
71
+ def _yday(year, month, day):
72
+ return _days_before_month(year, month) + day
73
+
74
+ def _strftime(fmt, obj):
75
+ result = ''
76
+ i = 0
77
+ n = fmt.length
78
+ while i < n:
79
+ c = fmt.charAt(i)
80
+ if c is '%' and i + 1 < n:
81
+ directive = fmt.charAt(i + 1)
82
+ if directive is 'Y':
83
+ sy = str(obj.year)
84
+ while sy.length < 4:
85
+ sy = '0' + sy
86
+ result += sy
87
+ elif directive is 'm':
88
+ result += _pad2(obj.month)
89
+ elif directive is 'd':
90
+ result += _pad2(obj.day)
91
+ elif directive is 'H':
92
+ result += _pad2(obj.hour if hasattr(obj, 'hour') else 0)
93
+ elif directive is 'M':
94
+ result += _pad2(obj.minute if hasattr(obj, 'minute') else 0)
95
+ elif directive is 'S':
96
+ result += _pad2(obj.second if hasattr(obj, 'second') else 0)
97
+ elif directive is 'f':
98
+ result += _pad6(obj.microsecond if hasattr(obj, 'microsecond') else 0)
99
+ elif directive is 'A':
100
+ _dn = ['Monday','Tuesday','Wednesday','Thursday','Friday','Saturday','Sunday']
101
+ result += _dn[obj.weekday()]
102
+ elif directive is 'a':
103
+ _dns = ['Mon','Tue','Wed','Thu','Fri','Sat','Sun']
104
+ result += _dns[obj.weekday()]
105
+ elif directive is 'B':
106
+ _mn = ['','January','February','March','April','May','June','July','August','September','October','November','December']
107
+ result += _mn[obj.month]
108
+ elif directive is 'b':
109
+ _mns = ['','Jan','Feb','Mar','Apr','May','Jun','Jul','Aug','Sep','Oct','Nov','Dec']
110
+ result += _mns[obj.month]
111
+ elif directive is 'j':
112
+ jday = str(_yday(obj.year, obj.month, obj.day))
113
+ while jday.length < 3:
114
+ jday = '0' + jday
115
+ result += jday
116
+ elif directive is 'p':
117
+ result += 'AM' if (obj.hour if hasattr(obj, 'hour') else 0) < 12 else 'PM'
118
+ elif directive is 'I':
119
+ h12 = (obj.hour if hasattr(obj, 'hour') else 0) % 12
120
+ result += _pad2(h12 if h12 else 12)
121
+ elif directive is '%':
122
+ result += '%'
123
+ else:
124
+ result += c + directive
125
+ i += 2
126
+ else:
127
+ result += c
128
+ i += 1
129
+ return result
130
+
131
+
132
+ # ── timedelta ─────────────────────────────────────────────────────────────────
133
+
134
+ class timedelta:
135
+ """Represent a duration: the difference between two date or datetime objects."""
136
+
137
+ def __init__(self, days=0, seconds=0, microseconds=0, milliseconds=0, minutes=0, hours=0, weeks=0):
138
+ d = days + weeks * 7
139
+ s = seconds + minutes * 60 + hours * 3600
140
+ us = microseconds + milliseconds * 1000
141
+
142
+ # Push fractional days into seconds, fractional seconds into microseconds
143
+ d_int = Math.trunc(d)
144
+ s += (d - d_int) * 86400
145
+ d = d_int
146
+
147
+ s_int = Math.trunc(s)
148
+ us += (s - s_int) * 1000000
149
+ s = s_int
150
+
151
+ us = Math.round(us)
152
+
153
+ # Normalise microseconds → seconds
154
+ s_carry = Math.floor(us / 1000000)
155
+ us = us - s_carry * 1000000
156
+ if us < 0:
157
+ us += 1000000
158
+ s_carry -= 1
159
+ s += s_carry
160
+
161
+ # Normalise seconds → days
162
+ d_carry = Math.floor(s / 86400)
163
+ s = s - d_carry * 86400
164
+ if s < 0:
165
+ s += 86400
166
+ d_carry -= 1
167
+ d += d_carry
168
+
169
+ self.days = d
170
+ self.seconds = s
171
+ self.microseconds = us
172
+
173
+ def total_seconds(self):
174
+ return self.days * 86400 + self.seconds + self.microseconds / 1000000
175
+
176
+ def __repr__(self):
177
+ parts = []
178
+ if self.days:
179
+ parts.push('days=' + str(self.days))
180
+ if self.seconds:
181
+ parts.push('seconds=' + str(self.seconds))
182
+ if self.microseconds:
183
+ parts.push('microseconds=' + str(self.microseconds))
184
+ if not parts.length:
185
+ return 'datetime.timedelta(0)'
186
+ return 'datetime.timedelta(' + parts.join(', ') + ')'
187
+
188
+ def __str__(self):
189
+ h = Math.floor(self.seconds / 3600)
190
+ rem = self.seconds % 3600
191
+ mins = Math.floor(rem / 60)
192
+ secs = rem % 60
193
+ time_str = str(h) + ':' + _pad2(mins) + ':' + _pad2(secs)
194
+ if self.microseconds:
195
+ time_str += '.' + _pad6(self.microseconds)
196
+ if self.days != 0:
197
+ day_word = ' day, ' if (self.days == 1 or self.days == -1) else ' days, '
198
+ return str(self.days) + day_word + time_str
199
+ return time_str
200
+
201
+ def __add__(self, other):
202
+ if isinstance(other, timedelta):
203
+ return timedelta(self.days + other.days, self.seconds + other.seconds, self.microseconds + other.microseconds)
204
+ raise TypeError('unsupported operand type(s) for +: timedelta and ' + jstype(other))
205
+
206
+ def __sub__(self, other):
207
+ if isinstance(other, timedelta):
208
+ return timedelta(self.days - other.days, self.seconds - other.seconds, self.microseconds - other.microseconds)
209
+ raise TypeError('unsupported operand type(s) for -: timedelta and ' + jstype(other))
210
+
211
+ def __neg__(self):
212
+ return timedelta(-self.days, -self.seconds, -self.microseconds)
213
+
214
+ def __abs__(self):
215
+ if self.days < 0:
216
+ return timedelta(-self.days, -self.seconds, -self.microseconds)
217
+ return self
218
+
219
+ def __mul__(self, other):
220
+ if jstype(other) is 'number':
221
+ return timedelta(0, 0, Math.round(self.total_seconds() * 1000000 * other))
222
+ raise TypeError('unsupported operand type(s) for *')
223
+
224
+ def __eq__(self, other):
225
+ if isinstance(other, timedelta):
226
+ return self.days is other.days and self.seconds is other.seconds and self.microseconds is other.microseconds
227
+ return False
228
+
229
+ def __lt__(self, other):
230
+ if isinstance(other, timedelta):
231
+ if self.days is not other.days:
232
+ return self.days < other.days
233
+ if self.seconds is not other.seconds:
234
+ return self.seconds < other.seconds
235
+ return self.microseconds < other.microseconds
236
+ raise TypeError('cannot compare timedelta with ' + jstype(other))
237
+
238
+ def __le__(self, other):
239
+ return self.__lt__(other) or self.__eq__(other)
240
+
241
+ def __gt__(self, other):
242
+ if isinstance(other, timedelta):
243
+ return other.__lt__(self)
244
+ raise TypeError('cannot compare timedelta with ' + jstype(other))
245
+
246
+ def __ge__(self, other):
247
+ return self.__gt__(other) or self.__eq__(other)
248
+
249
+ def __hash__(self):
250
+ return hash(self.total_seconds())
251
+
252
+ def __bool__(self):
253
+ return self.days is not 0 or self.seconds is not 0 or self.microseconds is not 0
254
+
255
+
256
+ timedelta.min = timedelta(-999999999)
257
+ timedelta.max = timedelta(999999999, 86399, 999999)
258
+ timedelta.resolution = timedelta(0, 0, 1)
259
+ _timedelta_cls = timedelta # capture before subclasses can shadow the name
260
+
261
+
262
+ # ── date ──────────────────────────────────────────────────────────────────────
263
+
264
+ class date:
265
+ """A calendar date (year, month, day) with no time of day."""
266
+
267
+ def __init__(self, year, month, day):
268
+ _check_field(year, 'year', MINYEAR, MAXYEAR)
269
+ _check_field(month, 'month', 1, 12)
270
+ _check_field(day, 'day', 1, _days_in_month(year, month))
271
+ self.year = year
272
+ self.month = month
273
+ self.day = day
274
+
275
+ @classmethod
276
+ def today(cls):
277
+ d = v"new Date()"
278
+ return cls(v"d.getFullYear()", v"d.getMonth() + 1", v"d.getDate()")
279
+
280
+ @classmethod
281
+ def fromordinal(cls, n):
282
+ ymd = _ord_to_ymd(n)
283
+ return cls(ymd[0], ymd[1], ymd[2])
284
+
285
+ @classmethod
286
+ def fromtimestamp(cls, timestamp):
287
+ d = v"new Date(timestamp * 1000)"
288
+ return cls(v"d.getFullYear()", v"d.getMonth() + 1", v"d.getDate()")
289
+
290
+ @classmethod
291
+ def fromisoformat(cls, s):
292
+ parts = s.split('-')
293
+ if parts.length != 3:
294
+ raise ValueError('Invalid ISO date: ' + s)
295
+ return cls(int(parts[0]), int(parts[1]), int(parts[2]))
296
+
297
+ def toordinal(self):
298
+ return _ymd_to_ord(self.year, self.month, self.day)
299
+
300
+ def weekday(self):
301
+ # Monday=0, Sunday=6
302
+ return (self.toordinal() + 6) % 7
303
+
304
+ def isoweekday(self):
305
+ # Monday=1, Sunday=7
306
+ return self.toordinal() % 7 or 7
307
+
308
+ def isocalendar(self):
309
+ # Returns [iso_year, iso_week, iso_weekday]
310
+ thu = self.toordinal() - self.weekday() + 3
311
+ ymd = _ord_to_ymd(thu)
312
+ iso_year = ymd[0]
313
+ week1_thu = _ymd_to_ord(iso_year, 1, 4)
314
+ iso_week = Math.floor((thu - (week1_thu - (week1_thu + 6) % 7)) / 7) + 1
315
+ return [iso_year, iso_week, self.isoweekday()]
316
+
317
+ def isoformat(self):
318
+ sy = str(self.year)
319
+ while sy.length < 4:
320
+ sy = '0' + sy
321
+ return sy + '-' + _pad2(self.month) + '-' + _pad2(self.day)
322
+
323
+ def ctime(self):
324
+ day_names = ['Mon','Tue','Wed','Thu','Fri','Sat','Sun']
325
+ month_names = ['','Jan','Feb','Mar','Apr','May','Jun','Jul','Aug','Sep','Oct','Nov','Dec']
326
+ sy = str(self.year)
327
+ while sy.length < 4:
328
+ sy = '0' + sy
329
+ return day_names[self.weekday()] + ' ' + month_names[self.month] + ' ' + _pad2(self.day) + ' 00:00:00 ' + sy
330
+
331
+ def strftime(self, fmt):
332
+ return _strftime(fmt, self)
333
+
334
+ def timetuple(self):
335
+ yday = _days_before_month(self.year, self.month) + self.day
336
+ return [self.year, self.month, self.day, 0, 0, 0, self.weekday(), yday, -1]
337
+
338
+ def replace(self, year=None, month=None, day=None):
339
+ y = year if year is not None else self.year
340
+ m = month if month is not None else self.month
341
+ d = day if day is not None else self.day
342
+ return date(y, m, d)
343
+
344
+ def __add__(self, other):
345
+ if isinstance(other, timedelta):
346
+ return date.fromordinal(self.toordinal() + other.days)
347
+ raise TypeError('unsupported operand type(s) for +: date and ' + jstype(other))
348
+
349
+ def __radd__(self, other):
350
+ return self.__add__(other)
351
+
352
+ def __sub__(self, other):
353
+ if isinstance(other, timedelta):
354
+ return date.fromordinal(self.toordinal() - other.days)
355
+ if isinstance(other, date) and not isinstance(other, datetime):
356
+ return timedelta(self.toordinal() - other.toordinal())
357
+ raise TypeError('unsupported operand type(s) for -')
358
+
359
+ def __str__(self):
360
+ return self.isoformat()
361
+
362
+ def __repr__(self):
363
+ return 'datetime.date(' + str(self.year) + ', ' + str(self.month) + ', ' + str(self.day) + ')'
364
+
365
+ def __eq__(self, other):
366
+ if isinstance(other, date) and not isinstance(other, datetime):
367
+ return self.year is other.year and self.month is other.month and self.day is other.day
368
+ return False
369
+
370
+ def __lt__(self, other):
371
+ if isinstance(other, date) and not isinstance(other, datetime):
372
+ if self.year is not other.year:
373
+ return self.year < other.year
374
+ if self.month is not other.month:
375
+ return self.month < other.month
376
+ return self.day < other.day
377
+ raise TypeError('cannot compare date with ' + jstype(other))
378
+
379
+ def __le__(self, other):
380
+ return self.__lt__(other) or self.__eq__(other)
381
+
382
+ def __gt__(self, other):
383
+ if isinstance(other, date) and not isinstance(other, datetime):
384
+ return other.__lt__(self)
385
+ raise TypeError('cannot compare date with ' + jstype(other))
386
+
387
+ def __ge__(self, other):
388
+ return self.__gt__(other) or self.__eq__(other)
389
+
390
+ def __hash__(self):
391
+ return hash(self.toordinal())
392
+
393
+
394
+ date.min = date(MINYEAR, 1, 1)
395
+ date.max = date(MAXYEAR, 12, 31)
396
+ date.resolution = timedelta(1)
397
+ _date_cls = date # capture before datetime can shadow the name
398
+
399
+
400
+ # ── time ──────────────────────────────────────────────────────────────────────
401
+
402
+ class time:
403
+ """A time of day (hour, minute, second, microsecond), independent of date."""
404
+
405
+ def __init__(self, hour=0, minute=0, second=0, microsecond=0):
406
+ _check_field(hour, 'hour', 0, 23)
407
+ _check_field(minute, 'minute', 0, 59)
408
+ _check_field(second, 'second', 0, 59)
409
+ _check_field(microsecond, 'microsecond', 0, 999999)
410
+ self.hour = hour
411
+ self.minute = minute
412
+ self.second = second
413
+ self.microsecond = microsecond
414
+
415
+ def replace(self, hour=None, minute=None, second=None, microsecond=None):
416
+ h = hour if hour is not None else self.hour
417
+ m = minute if minute is not None else self.minute
418
+ s = second if second is not None else self.second
419
+ us = microsecond if microsecond is not None else self.microsecond
420
+ return time(h, m, s, us)
421
+
422
+ def isoformat(self):
423
+ s = _pad2(self.hour) + ':' + _pad2(self.minute) + ':' + _pad2(self.second)
424
+ if self.microsecond:
425
+ s += '.' + _pad6(self.microsecond)
426
+ return s
427
+
428
+ def strftime(self, fmt):
429
+ return _strftime(fmt, self)
430
+
431
+ def __str__(self):
432
+ return self.isoformat()
433
+
434
+ def __repr__(self):
435
+ if self.microsecond:
436
+ return 'datetime.time(' + str(self.hour) + ', ' + str(self.minute) + ', ' + str(self.second) + ', ' + str(self.microsecond) + ')'
437
+ if self.second:
438
+ return 'datetime.time(' + str(self.hour) + ', ' + str(self.minute) + ', ' + str(self.second) + ')'
439
+ if self.minute:
440
+ return 'datetime.time(' + str(self.hour) + ', ' + str(self.minute) + ')'
441
+ return 'datetime.time(' + str(self.hour) + ')'
442
+
443
+ def __eq__(self, other):
444
+ if isinstance(other, time):
445
+ return self.hour is other.hour and self.minute is other.minute and self.second is other.second and self.microsecond is other.microsecond
446
+ return False
447
+
448
+ def __lt__(self, other):
449
+ if isinstance(other, time):
450
+ if self.hour is not other.hour:
451
+ return self.hour < other.hour
452
+ if self.minute is not other.minute:
453
+ return self.minute < other.minute
454
+ if self.second is not other.second:
455
+ return self.second < other.second
456
+ return self.microsecond < other.microsecond
457
+ raise TypeError('cannot compare time with ' + jstype(other))
458
+
459
+ def __le__(self, other):
460
+ return self.__lt__(other) or self.__eq__(other)
461
+
462
+ def __gt__(self, other):
463
+ if isinstance(other, time):
464
+ return other.__lt__(self)
465
+ raise TypeError('cannot compare time with ' + jstype(other))
466
+
467
+ def __ge__(self, other):
468
+ return self.__gt__(other) or self.__eq__(other)
469
+
470
+ def __hash__(self):
471
+ return hash(self.hour * 3600 + self.minute * 60 + self.second + self.microsecond / 1000000)
472
+
473
+ def __bool__(self):
474
+ return self.hour is not 0 or self.minute is not 0 or self.second is not 0 or self.microsecond is not 0
475
+
476
+
477
+ time.min = time(0, 0, 0, 0)
478
+ time.max = time(23, 59, 59, 999999)
479
+ time.resolution = timedelta(0, 0, 1)
480
+ _time_cls = time # capture before datetime can shadow the name
481
+
482
+
483
+ # ── datetime ──────────────────────────────────────────────────────────────────
484
+
485
+ class datetime(date):
486
+ """A date combined with a time of day."""
487
+
488
+ def __init__(self, year, month, day, hour=0, minute=0, second=0, microsecond=0):
489
+ date.__init__(self, year, month, day)
490
+ _check_field(hour, 'hour', 0, 23)
491
+ _check_field(minute, 'minute', 0, 59)
492
+ _check_field(second, 'second', 0, 59)
493
+ _check_field(microsecond, 'microsecond', 0, 999999)
494
+ self.hour = hour
495
+ self.minute = minute
496
+ self.second = second
497
+ self.microsecond = microsecond
498
+
499
+ @classmethod
500
+ def now(cls, tz=None):
501
+ d = v"new Date()"
502
+ return cls(v"d.getFullYear()", v"d.getMonth()+1", v"d.getDate()",
503
+ v"d.getHours()", v"d.getMinutes()", v"d.getSeconds()",
504
+ v"d.getMilliseconds() * 1000")
505
+
506
+ @classmethod
507
+ def utcnow(cls):
508
+ d = v"new Date()"
509
+ return cls(v"d.getUTCFullYear()", v"d.getUTCMonth()+1", v"d.getUTCDate()",
510
+ v"d.getUTCHours()", v"d.getUTCMinutes()", v"d.getUTCSeconds()",
511
+ v"d.getUTCMilliseconds() * 1000")
512
+
513
+ @classmethod
514
+ def fromtimestamp(cls, timestamp, tz=None):
515
+ d = v"new Date(timestamp * 1000)"
516
+ return cls(v"d.getFullYear()", v"d.getMonth()+1", v"d.getDate()",
517
+ v"d.getHours()", v"d.getMinutes()", v"d.getSeconds()",
518
+ v"d.getMilliseconds() * 1000")
519
+
520
+ @classmethod
521
+ def utcfromtimestamp(cls, timestamp):
522
+ d = v"new Date(timestamp * 1000)"
523
+ return cls(v"d.getUTCFullYear()", v"d.getUTCMonth()+1", v"d.getUTCDate()",
524
+ v"d.getUTCHours()", v"d.getUTCMinutes()", v"d.getUTCSeconds()",
525
+ v"d.getUTCMilliseconds() * 1000")
526
+
527
+ @classmethod
528
+ def combine(cls, d, t):
529
+ return cls(d.year, d.month, d.day, t.hour, t.minute, t.second, t.microsecond)
530
+
531
+ @classmethod
532
+ def fromisoformat(cls, s):
533
+ sep = s.indexOf('T')
534
+ if sep is -1:
535
+ sep = s.indexOf(' ')
536
+ if sep is -1:
537
+ # date only — produce a datetime at midnight
538
+ parts = s.split('-')
539
+ return cls(int(parts[0]), int(parts[1]), int(parts[2]))
540
+ ds = s.slice(0, sep)
541
+ ts = s.slice(sep + 1)
542
+ dp = ds.split('-')
543
+ us = 0
544
+ dot = ts.indexOf('.')
545
+ if dot is not -1:
546
+ frac = ts.slice(dot + 1)
547
+ while frac.length < 6:
548
+ frac += '0'
549
+ us = int(frac.slice(0, 6))
550
+ ts = ts.slice(0, dot)
551
+ tp = ts.split(':')
552
+ h = int(tp[0]) if tp.length > 0 else 0
553
+ mi = int(tp[1]) if tp.length > 1 else 0
554
+ sc = int(tp[2]) if tp.length > 2 else 0
555
+ return cls(int(dp[0]), int(dp[1]), int(dp[2]), h, mi, sc, us)
556
+
557
+ def date(self):
558
+ # Use _date_cls alias: inside `function date() {...}` the name 'date'
559
+ # would otherwise bind to the function itself (named-function-expression
560
+ # scoping), causing infinite recursion.
561
+ return _date_cls(self.year, self.month, self.day)
562
+
563
+ def time(self):
564
+ # Same rationale as date() above — use _time_cls alias.
565
+ return _time_cls(self.hour, self.minute, self.second, self.microsecond)
566
+
567
+ def timestamp(self):
568
+ v"""var _ts_d = new Date(this.year, this.month - 1, this.day,
569
+ this.hour, this.minute, this.second,
570
+ Math.floor(this.microsecond / 1000));"""
571
+ return v"_ts_d.getTime() / 1000"
572
+
573
+ def replace(self, year=None, month=None, day=None, hour=None, minute=None, second=None, microsecond=None):
574
+ y = year if year is not None else self.year
575
+ mo = month if month is not None else self.month
576
+ d = day if day is not None else self.day
577
+ h = hour if hour is not None else self.hour
578
+ mi = minute if minute is not None else self.minute
579
+ s = second if second is not None else self.second
580
+ us = microsecond if microsecond is not None else self.microsecond
581
+ return datetime(y, mo, d, h, mi, s, us)
582
+
583
+ def isoformat(self, sep='T'):
584
+ sy = str(self.year)
585
+ while sy.length < 4:
586
+ sy = '0' + sy
587
+ date_str = sy + '-' + _pad2(self.month) + '-' + _pad2(self.day)
588
+ time_str = _pad2(self.hour) + ':' + _pad2(self.minute) + ':' + _pad2(self.second)
589
+ if self.microsecond:
590
+ time_str += '.' + _pad6(self.microsecond)
591
+ return date_str + sep + time_str
592
+
593
+ def ctime(self):
594
+ day_names = ['Mon','Tue','Wed','Thu','Fri','Sat','Sun']
595
+ month_names = ['','Jan','Feb','Mar','Apr','May','Jun','Jul','Aug','Sep','Oct','Nov','Dec']
596
+ sy = str(self.year)
597
+ while sy.length < 4:
598
+ sy = '0' + sy
599
+ return day_names[self.weekday()] + ' ' + month_names[self.month] + ' ' + _pad2(self.day) + ' ' + _pad2(self.hour) + ':' + _pad2(self.minute) + ':' + _pad2(self.second) + ' ' + sy
600
+
601
+ def strftime(self, fmt):
602
+ return _strftime(fmt, self)
603
+
604
+ def timetuple(self):
605
+ yday = _days_before_month(self.year, self.month) + self.day
606
+ return [self.year, self.month, self.day, self.hour, self.minute, self.second, self.weekday(), yday, -1]
607
+
608
+ def __str__(self):
609
+ return self.isoformat(' ')
610
+
611
+ def __repr__(self):
612
+ s = 'datetime.datetime(' + str(self.year) + ', ' + str(self.month) + ', ' + str(self.day) + ', ' + str(self.hour) + ', ' + str(self.minute) + ', ' + str(self.second)
613
+ if self.microsecond:
614
+ s += ', ' + str(self.microsecond)
615
+ return s + ')'
616
+
617
+ def __add__(self, other):
618
+ if isinstance(other, timedelta):
619
+ # Operate on day-ordinal + time-of-day components separately to
620
+ # avoid floating-point precision issues with large microsecond counts.
621
+ us = self.microsecond + other.microseconds
622
+ s = self.second + other.seconds
623
+ mi = self.minute
624
+ h = self.hour
625
+ ord_day = self.toordinal() + other.days
626
+
627
+ # Cascade carries
628
+ s_carry = Math.floor(us / 1000000)
629
+ us = us - s_carry * 1000000
630
+ if us < 0:
631
+ us += 1000000
632
+ s_carry -= 1
633
+ s += s_carry
634
+
635
+ mi_carry = Math.floor(s / 60)
636
+ s = s % 60
637
+ if s < 0:
638
+ s += 60
639
+ mi_carry -= 1
640
+ mi += mi_carry
641
+
642
+ h_carry = Math.floor(mi / 60)
643
+ mi = mi % 60
644
+ if mi < 0:
645
+ mi += 60
646
+ h_carry -= 1
647
+ h += h_carry
648
+
649
+ d_carry = Math.floor(h / 24)
650
+ h = h % 24
651
+ if h < 0:
652
+ h += 24
653
+ d_carry -= 1
654
+ ord_day += d_carry
655
+
656
+ ymd = _ord_to_ymd(ord_day)
657
+ return datetime(ymd[0], ymd[1], ymd[2], h, mi, s, us)
658
+ raise TypeError('unsupported operand type(s) for +: datetime and ' + jstype(other))
659
+
660
+ def __radd__(self, other):
661
+ return self.__add__(other)
662
+
663
+ def __sub__(self, other):
664
+ if isinstance(other, datetime):
665
+ delta_days = self.toordinal() - other.toordinal()
666
+ delta_secs = (self.hour - other.hour) * 3600 + (self.minute - other.minute) * 60 + (self.second - other.second)
667
+ delta_us = self.microsecond - other.microsecond
668
+ return timedelta(delta_days, delta_secs, delta_us)
669
+ if isinstance(other, timedelta):
670
+ return self.__add__(timedelta(-other.days, -other.seconds, -other.microseconds))
671
+ raise TypeError('unsupported operand type(s) for -')
672
+
673
+ def __eq__(self, other):
674
+ if isinstance(other, datetime):
675
+ return self.year is other.year and self.month is other.month and self.day is other.day and self.hour is other.hour and self.minute is other.minute and self.second is other.second and self.microsecond is other.microsecond
676
+ return False
677
+
678
+ def __lt__(self, other):
679
+ if isinstance(other, datetime):
680
+ if self.year is not other.year:
681
+ return self.year < other.year
682
+ if self.month is not other.month:
683
+ return self.month < other.month
684
+ if self.day is not other.day:
685
+ return self.day < other.day
686
+ if self.hour is not other.hour:
687
+ return self.hour < other.hour
688
+ if self.minute is not other.minute:
689
+ return self.minute < other.minute
690
+ if self.second is not other.second:
691
+ return self.second < other.second
692
+ return self.microsecond < other.microsecond
693
+ raise TypeError('cannot compare datetime with ' + jstype(other))
694
+
695
+ def __le__(self, other):
696
+ return self.__lt__(other) or self.__eq__(other)
697
+
698
+ def __gt__(self, other):
699
+ if isinstance(other, datetime):
700
+ return other.__lt__(self)
701
+ raise TypeError('cannot compare datetime with ' + jstype(other))
702
+
703
+ def __ge__(self, other):
704
+ return self.__gt__(other) or self.__eq__(other)
705
+
706
+ def __hash__(self):
707
+ return hash(self.isoformat())
708
+
709
+
710
+ datetime.min = datetime(MINYEAR, 1, 1, 0, 0, 0)
711
+ datetime.max = datetime(MAXYEAR, 12, 31, 23, 59, 59, 999999)
712
+ datetime.resolution = timedelta(0, 0, 1)