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