1 /**
2 Bindings for the script Date class. This module contains both the D class definition and the script bindings.
3 See https://pillager86.github.io/dmildew/Date.html for more details.
4 
5 ────────────────────────────────────────────────────────────────────────────────
6 
7 Copyright (C) 2021 pillager86.rf.gd
8 
9 This program is free software: you can redistribute it and/or modify it under 
10 the terms of the GNU General Public License as published by the Free Software 
11 Foundation, either version 3 of the License, or (at your option) any later 
12 version.
13 
14 This program is distributed in the hope that it will be useful, but WITHOUT ANY
15 WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
16 PARTICULAR PURPOSE.  See the GNU General Public License for more details.
17 
18 You should have received a copy of the GNU General Public License along with 
19 this program.  If not, see <https://www.gnu.org/licenses/>.
20 */
21 module mildew.stdlib.date;
22 
23 import core.time: TimeException;
24 import std.datetime.systime;
25 import std.datetime.date;
26 import std.datetime.timezone;
27 
28 import mildew.binder;
29 import mildew.environment;
30 import mildew.interpreter;
31 import mildew.types;
32 
33 /** 
34  * Initializes the Date library, which includes the Date() constructor. Documentation for the
35  * library can be found at https://pillager86.github.io/dmildew/Date.html
36  * Params:
37  *  interpreter = The Interpreter object for which to load the library.
38  */
39 void initializeDateLibrary(Interpreter interpreter)
40 {
41     auto Date_ctor = new ScriptFunction("Date", &native_Date_ctor, true);
42     Date_ctor["now"] = new ScriptFunction("Date.now", &native_Date_s_now);
43     Date_ctor["prototype"]["getDate"] = new ScriptFunction("Date.prototype.getDate", &native_Date_getDate);
44     Date_ctor["prototype"]["getDay"] = new ScriptFunction("Date.prototype.getDay", &native_Date_getDay);
45     Date_ctor["prototype"]["getFullYear"] = new ScriptFunction("Date.prototype.getFullYear", 
46             &native_Date_getFullYear);
47     Date_ctor["prototype"]["getHours"] = new ScriptFunction("Date.prototype.getHours", 
48             &native_Date_getHours);
49     Date_ctor["prototype"]["getMilliseconds"] = new ScriptFunction("Date.prototype.getMilliseconds", 
50             &native_Date_getMilliseconds);
51     Date_ctor["prototype"]["getMinutes"] = new ScriptFunction("Date.prototype.getMinutes", 
52             &native_Date_getMinutes);
53     Date_ctor["prototype"]["getMonth"] = new ScriptFunction("Date.prototype.getMonth", 
54             &native_Date_getMonth);
55     Date_ctor["prototype"]["getSeconds"] = new ScriptFunction("Date.prototype.getSeconds", 
56             &native_Date_getSeconds);
57     Date_ctor["prototype"]["getTime"] = new ScriptFunction("Date.prototype.getTime", 
58             &native_Date_getTime);
59     Date_ctor["prototype"]["getTimezone"] = new ScriptFunction("Date.prototype.getTimezone", 
60             &native_Date_getTimezone);
61     Date_ctor["prototype"]["setDate"] = new ScriptFunction("Date.prototype.setDate", 
62             &native_Date_setDate);
63     Date_ctor["prototype"]["setFullYear"] = new ScriptFunction("Date.prototype.setFullYear", 
64             &native_Date_setFullYear);
65     Date_ctor["prototype"]["setHours"] = new ScriptFunction("Date.prototype.setHours", 
66             &native_Date_setHours);
67     Date_ctor["prototype"]["setMilliseconds"] = new ScriptFunction("Date.prototype.setMillseconds", 
68             &native_Date_setMilliseconds);
69     Date_ctor["prototype"]["setMinutes"] = new ScriptFunction("Date.prototype.setMinutes", 
70             &native_Date_setMinutes);
71     Date_ctor["prototype"]["setMonth"] = new ScriptFunction("Date.prototype.setMonth", 
72             &native_Date_setMonth);
73     Date_ctor["prototype"]["setSeconds"] = new ScriptFunction("Date.prototype.setSeconds", 
74             &native_Date_setSeconds);
75     Date_ctor["prototype"]["setTime"] = new ScriptFunction("Date.prototype.setTime", 
76             &native_Date_setTime);
77     Date_ctor["prototype"]["toDateString"] = new ScriptFunction("Date.prototype.toDateString", 
78             &native_Date_toDateString);
79     Date_ctor["prototype"]["toISOString"] = new ScriptFunction("Date.prototype.toISOString", 
80             &native_Date_toISOString);
81     Date_ctor["prototype"]["toUTC"] = new ScriptFunction("Date.prototype.toUTC", 
82             &native_Date_toUTC);
83     interpreter.forceSetGlobal("Date", Date_ctor, false);
84 }
85 
86 package:
87 
88 /**
89  * The Date class
90  */
91 class ScriptDate
92 {
93 public:
94     /**
95      * Creates a Date representing the time and date of object creation
96      */ 
97     this()
98     {
99         _sysTime = Clock.currTime();
100     }
101 
102     this(in long num)
103     {
104         _sysTime = SysTime.fromUnixTime(num);
105     }
106 
107     /// takes month 0-11 like JavaScript
108     this(in int year, in int monthIndex, in int day=1, in int hours=0, in int minutes=0, 
109          in int seconds=0, in int milliseconds=0)
110     {
111         import core.time: msecs;
112         auto dt = DateTime(year, monthIndex+1, day, hours, minutes, seconds);
113         _sysTime = SysTime(dt, msecs(milliseconds), UTC());
114     }
115 
116     /// This string has to be formatted as "2020-Jan-01 00:00:00" for example. Anything different throws an exception
117     this(in string str)
118     {
119         try 
120         {
121             auto dt = DateTime.fromSimpleString(str);
122             _sysTime = SysTime(dt, UTC());
123         }
124         catch(DateTimeException)
125         {
126             auto dt = DateTime.fromISOString(str);
127             _sysTime = SysTime(dt, UTC());
128         }
129     }
130 
131     static long now() 
132     {
133         immutable start = SysTime.fromUnixTime(0);
134         return (Clock.currTime - start).total!"msecs";
135     }
136 
137     /// returns day of month
138     int getDate() const
139     {
140         auto dt = cast(DateTime)_sysTime;
141         return dt.day;
142     }
143 
144     /// returns day of week
145     int getDay() const
146     {
147         auto dt = cast(DateTime)_sysTime;
148         return dt.dayOfWeek;
149     }
150 
151     int getFullYear() const
152     {
153         auto dt = cast(DateTime)_sysTime;
154         return dt.year;
155     }
156 
157     /// get the hour of the date
158     int getHours() const
159     {
160         return _sysTime.hour;
161     }
162 
163     long getMilliseconds() const
164     {
165         return _sysTime.fracSecs.total!"msecs";
166     }
167 
168     int getMinutes() const
169     {
170         return _sysTime.minute;
171     }
172 
173     /// returns month from 0-11
174     int getMonth() const
175     {
176         return cast(int)(_sysTime.month) - 1;
177     }
178 
179     int getSeconds() const
180     {
181         return _sysTime.second;
182     }
183 
184     long getTime() const
185     {
186         return _sysTime.toUnixTime * 1000; // TODO fix
187     }
188 
189     long getTimezone() const
190     {
191         return _sysTime.timezone.utcOffsetAt(_sysTime.stdTime).total!"minutes";
192     }
193 
194     // TODO UTC stuff
195 
196     void setDate(in int d)
197     {
198         _sysTime.day = d;
199     }
200 
201     void setFullYear(in int year)
202     {
203         _sysTime.year = year;
204     }
205 
206     void setHours(in int hours, in int minutes=0, in int seconds=0)
207     {
208         _sysTime.hour = hours;
209         _sysTime.minute = minutes;
210         _sysTime.second = seconds;
211     }
212 
213     void setMilliseconds(in uint ms)
214     {
215         import core.time: msecs, Duration;
216         _sysTime.fracSecs = msecs(ms);
217     }
218 
219     void setMinutes(in uint minutes)
220     {
221         _sysTime.minute = minutes;
222     }
223 
224     void setMonth(in uint month)
225     {
226         _sysTime.month = cast(Month)(month % 12 + 1);
227     }
228 
229     void setSeconds(in uint s)
230     {
231         _sysTime.second = cast(ubyte)(s%60);
232     }
233 
234     void setTime(in long unixTimeMs)
235     {
236         _sysTime = _sysTime.fromUnixTime(unixTimeMs / 1000); // TODO fix
237     }
238 
239     string toISOString() const
240     {
241         auto dt = cast(DateTime)_sysTime;
242         return dt.toISOString();
243     }
244 
245     ScriptDate toUTC() const
246     {
247         auto newSD = new ScriptDate(0);
248         newSD._sysTime = _sysTime.toUTC();
249         return newSD;
250     }
251 
252     override string toString() const
253     {
254         auto tz = _sysTime.timezone.dstName;
255         return (cast(DateTime)_sysTime).toString() ~ " " ~ tz;
256     }
257 
258 private:
259     SysTime _sysTime;
260 }
261 
262 private:
263 
264 ScriptAny native_Date_ctor(Environment env, ScriptAny* thisObj, ScriptAny[] args, ref NativeFunctionError nfe)
265 {
266     if(!thisObj.isObject)
267     {
268         nfe = NativeFunctionError.WRONG_TYPE_OF_ARG;
269         return ScriptAny.UNDEFINED;
270     }
271     auto obj = thisObj.toValue!ScriptObject;
272     try 
273     {
274         if(args.length == 0)
275             obj.nativeObject = new ScriptDate();
276         else if(args.length == 1)
277         {
278             if(args[0].isNumber)
279                 obj.nativeObject = new ScriptDate(args[0].toValue!long);
280             else
281                 obj.nativeObject = new ScriptDate(args[0].toString());
282         }
283         else if(args.length >= 2)
284         {
285             immutable year = args[0].toValue!int;
286             immutable month = args[1].toValue!int;
287             immutable day = args.length > 2? args[2].toValue!int : 1;
288             immutable hours = args.length > 3 ? args[3].toValue!int : 0;
289             immutable minutes = args.length > 4 ? args[4].toValue!int : 0; 
290             immutable seconds = args.length > 5 ? args[5].toValue!int : 0;
291             immutable mseconds = args.length > 6 ? args[6].toValue!int : 0;
292             obj.nativeObject = new ScriptDate(year, month, day, hours, minutes, seconds, mseconds);
293         }
294         else
295             nfe = NativeFunctionError.WRONG_NUMBER_OF_ARGS;
296     }
297     catch(TimeException tex)
298     {
299         nfe = NativeFunctionError.RETURN_VALUE_IS_EXCEPTION;
300         return ScriptAny(tex.msg);
301     }
302     return ScriptAny.UNDEFINED;
303 }
304 
305 ScriptAny native_Date_s_now(Environment environment,
306                                           ScriptAny* thisObj,
307                                           ScriptAny[] args,
308                                           ref NativeFunctionError nfe)
309 {
310     return ScriptAny(ScriptDate.now());
311 }
312 
313 ScriptAny native_Date_getDate(Environment env, ScriptAny* thisObj, ScriptAny[] args, ref NativeFunctionError nfe)
314 {
315     auto date = thisObj.toNativeObject!ScriptDate;
316     if(date is null)
317     {
318         nfe = NativeFunctionError.WRONG_TYPE_OF_ARG;
319         return ScriptAny.UNDEFINED;
320     }
321     return ScriptAny(date.getDate());
322 }
323 
324 ScriptAny native_Date_getDay(Environment env, ScriptAny* thisObj, ScriptAny[] args, ref NativeFunctionError nfe)
325 {
326     auto date = thisObj.toNativeObject!ScriptDate;
327     if(date is null)
328     {
329         nfe = NativeFunctionError.WRONG_TYPE_OF_ARG;
330         return ScriptAny.UNDEFINED;
331     }
332     return ScriptAny(date.getDay());
333 }
334 
335 ScriptAny native_Date_getFullYear(Environment env, ScriptAny* thisObj, ScriptAny[] args, ref NativeFunctionError nfe)
336 {
337     auto date = thisObj.toNativeObject!ScriptDate;
338     if(date is null)
339     {
340         nfe = NativeFunctionError.WRONG_TYPE_OF_ARG;
341         return ScriptAny.UNDEFINED;
342     }
343     return ScriptAny(date.getFullYear());
344 }
345 
346 ScriptAny native_Date_getHours(Environment env, ScriptAny* thisObj, ScriptAny[] args, ref NativeFunctionError nfe)
347 {
348     auto date = thisObj.toNativeObject!ScriptDate;
349     if(date is null)
350     {
351         nfe = NativeFunctionError.WRONG_TYPE_OF_ARG;
352         return ScriptAny.UNDEFINED;
353     }
354     return ScriptAny(date.getHours());
355 }
356 
357 ScriptAny native_Date_getMilliseconds(Environment env, ScriptAny* thisObj, 
358                                       ScriptAny[] args, ref NativeFunctionError nfe)
359 {
360     auto date = thisObj.toNativeObject!ScriptDate;
361     if(date is null)
362     {
363         nfe = NativeFunctionError.WRONG_TYPE_OF_ARG;
364         return ScriptAny.UNDEFINED;
365     }
366     return ScriptAny(date.getMilliseconds());
367 }
368 
369 ScriptAny native_Date_getMinutes(Environment env, ScriptAny* thisObj, ScriptAny[] args, ref NativeFunctionError nfe)
370 {
371     auto date = thisObj.toNativeObject!ScriptDate;
372     if(date is null)
373     {
374         nfe = NativeFunctionError.WRONG_TYPE_OF_ARG;
375         return ScriptAny.UNDEFINED;
376     }
377     return ScriptAny(date.getMinutes());
378 }
379 
380 ScriptAny native_Date_getMonth(Environment env, ScriptAny* thisObj, ScriptAny[] args, ref NativeFunctionError nfe)
381 {
382     if(!thisObj.isObject)
383     {
384         nfe = NativeFunctionError.WRONG_TYPE_OF_ARG;
385         return ScriptAny.UNDEFINED;
386     }
387     auto dateObj = (*thisObj).toValue!ScriptDate;
388     if(dateObj is null)
389     {
390         nfe = NativeFunctionError.WRONG_TYPE_OF_ARG;
391         return ScriptAny.UNDEFINED;
392     }
393     return ScriptAny(dateObj.getMonth);
394 }
395 
396 ScriptAny native_Date_getSeconds(Environment env, ScriptAny* thisObj, ScriptAny[] args, ref NativeFunctionError nfe)
397 {
398     auto date = thisObj.toNativeObject!ScriptDate;
399     if(date is null)
400     {
401         nfe = NativeFunctionError.WRONG_TYPE_OF_ARG;
402         return ScriptAny.UNDEFINED;
403     }
404     return ScriptAny(date.getSeconds());
405 }
406 
407 ScriptAny native_Date_getTime(Environment env, ScriptAny* thisObj, ScriptAny[] args, ref NativeFunctionError nfe)
408 {
409     auto date = thisObj.toNativeObject!ScriptDate;
410     if(date is null)
411     {
412         nfe = NativeFunctionError.WRONG_TYPE_OF_ARG;
413         return ScriptAny.UNDEFINED;
414     }
415     return ScriptAny(date.getTime());
416 }
417 
418 ScriptAny native_Date_getTimezone(Environment env, ScriptAny* thisObj, ScriptAny[] args, ref NativeFunctionError nfe)
419 {
420     auto date = thisObj.toNativeObject!ScriptDate;
421     if(date is null)
422     {
423         nfe = NativeFunctionError.WRONG_TYPE_OF_ARG;
424         return ScriptAny.UNDEFINED;
425     }
426     return ScriptAny(date.getTimezone());
427 }
428 
429 ScriptAny native_Date_setDate(Environment env, ScriptAny* thisObj, ScriptAny[] args, ref NativeFunctionError nfe)
430 {
431     mixin(CHECK_THIS_NATIVE_OBJECT!("date", ScriptDate));
432     mixin(TO_ARG_CHECK_INDEX!("d", 0, int));
433     try 
434     {
435         date.setDate(d);
436     }
437     catch(TimeException ex)
438     {
439         nfe = NativeFunctionError.RETURN_VALUE_IS_EXCEPTION;
440         return ScriptAny(ex.msg);
441     }
442     return ScriptAny.UNDEFINED;
443 }
444 
445 ScriptAny native_Date_setFullYear(Environment env, ScriptAny* thisObj, ScriptAny[] args, ref NativeFunctionError nfe)
446 {
447     mixin(CHECK_THIS_NATIVE_OBJECT!("date", ScriptDate));
448     mixin(TO_ARG_CHECK_INDEX!("year", 0, int));
449     try // this might not be needed here
450     {
451         date.setFullYear(year);
452     }
453     catch(TimeException ex)
454     {
455         nfe = NativeFunctionError.RETURN_VALUE_IS_EXCEPTION;
456         return ScriptAny(ex.msg);
457     }
458     return ScriptAny.UNDEFINED;
459 }
460 
461 ScriptAny native_Date_setHours(Environment env, ScriptAny* thisObj, ScriptAny[] args, ref NativeFunctionError nfe)
462 {
463     mixin(CHECK_THIS_NATIVE_OBJECT!("date", ScriptDate));
464     mixin(TO_ARG_CHECK_INDEX!("hour", 0, int));
465     mixin(TO_ARG_OPT!("minute", 1, 0, int));
466     mixin(TO_ARG_OPT!("second", 2, 0, int));
467     date.setHours(hour%24, minute%60, second%60);
468     return ScriptAny.UNDEFINED;
469 }
470 
471 ScriptAny native_Date_setMilliseconds(Environment env, ScriptAny* thisObj, 
472                                       ScriptAny[] args, ref NativeFunctionError nfe)
473 {
474     mixin(CHECK_THIS_NATIVE_OBJECT!("date", ScriptDate));
475     mixin(TO_ARG_CHECK_INDEX!("ms", 0, uint));
476     date.setMilliseconds(ms % 1000);
477     return ScriptAny.UNDEFINED;
478 }
479 
480 ScriptAny native_Date_setMinutes(Environment env, ScriptAny* thisObj, ScriptAny[] args, ref NativeFunctionError nfe)
481 {
482     mixin(CHECK_THIS_NATIVE_OBJECT!("date", ScriptDate));
483     mixin(TO_ARG_CHECK_INDEX!("minutes", 0, uint));
484     date.setMinutes(minutes % 60);
485     return ScriptAny.UNDEFINED;
486 }
487 
488 ScriptAny native_Date_setMonth(Environment env, ScriptAny* thisObj, ScriptAny[] args, ref NativeFunctionError nfe)
489 {
490     mixin(CHECK_THIS_NATIVE_OBJECT!("date", ScriptDate));
491     mixin(TO_ARG_CHECK_INDEX!("m", 0, uint));
492     try 
493     {
494         date.setMonth(m);
495     }
496     catch(TimeException ex)
497     {
498         nfe = NativeFunctionError.RETURN_VALUE_IS_EXCEPTION;
499         return ScriptAny(ex.msg);
500     }
501     return ScriptAny.UNDEFINED;
502 }
503 
504 ScriptAny native_Date_setSeconds(Environment env, ScriptAny* thisObj, ScriptAny[] args, ref NativeFunctionError nfe)
505 {
506     mixin(CHECK_THIS_NATIVE_OBJECT!("date", ScriptDate));
507     mixin(TO_ARG_CHECK_INDEX!("s", 0, uint));
508     date.setSeconds(s);
509     return ScriptAny.UNDEFINED;
510 }
511 
512 ScriptAny native_Date_setTime(Environment env, ScriptAny* thisObj, ScriptAny[] args, ref NativeFunctionError nfe)
513 {
514     mixin(CHECK_THIS_NATIVE_OBJECT!("date", ScriptDate));
515     mixin(TO_ARG_CHECK_INDEX!("t", 0, long));
516     date.setTime(t);
517     return ScriptAny.UNDEFINED;
518 }
519 
520 ScriptAny native_Date_toDateString(Environment env, ScriptAny* thisObj, ScriptAny[] args, ref NativeFunctionError nfe)
521 {
522     mixin(CHECK_THIS_NATIVE_OBJECT!("date", ScriptDate));
523     return ScriptAny(date.toString());
524 }
525 
526 ScriptAny native_Date_toISOString(Environment env, ScriptAny* thisObj, ScriptAny[] args, ref NativeFunctionError nfe)
527 {
528     mixin(CHECK_THIS_NATIVE_OBJECT!("date", ScriptDate));
529     return ScriptAny(date.toISOString());
530 }
531 
532 ScriptAny native_Date_toUTC(Environment env, ScriptAny* thisObj, ScriptAny[] args, ref NativeFunctionError nfe)
533 {
534     mixin(CHECK_THIS_NATIVE_OBJECT!("date", ScriptDate));
535     auto newDate = date.toUTC();
536     auto newSD = new ScriptObject("Date", thisObj.toValue!ScriptObject.prototype, newDate);
537     return ScriptAny(newSD);
538 }