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