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 // TODO separate the prototype so that Dates can be constructed with Date(). This will become the 44 // sole use case as to why thisObj is passed by pointer rather than value. 45 Date_ctor["prototype"]["getDate"] = new ScriptFunction("Date.prototype.getDate", &native_Date_getDate); 46 Date_ctor["prototype"]["getDay"] = new ScriptFunction("Date.prototype.getDay", &native_Date_getDay); 47 Date_ctor["prototype"]["getFullYear"] = new ScriptFunction("Date.prototype.getFullYear", 48 &native_Date_getFullYear); 49 Date_ctor["prototype"]["getHours"] = new ScriptFunction("Date.prototype.getHours", 50 &native_Date_getHours); 51 Date_ctor["prototype"]["getMilliseconds"] = new ScriptFunction("Date.prototype.getMilliseconds", 52 &native_Date_getMilliseconds); 53 Date_ctor["prototype"]["getMinutes"] = new ScriptFunction("Date.prototype.getMinutes", 54 &native_Date_getMinutes); 55 Date_ctor["prototype"]["getMonth"] = new ScriptFunction("Date.prototype.getMonth", 56 &native_Date_getMonth); 57 Date_ctor["prototype"]["getSeconds"] = new ScriptFunction("Date.prototype.getSeconds", 58 &native_Date_getSeconds); 59 Date_ctor["prototype"]["getTime"] = new ScriptFunction("Date.prototype.getTime", 60 &native_Date_getTime); 61 Date_ctor["prototype"]["getTimezone"] = new ScriptFunction("Date.prototype.getTimezone", 62 &native_Date_getTimezone); 63 Date_ctor["prototype"]["setDate"] = new ScriptFunction("Date.prototype.setDate", 64 &native_Date_setDate); 65 Date_ctor["prototype"]["setFullYear"] = new ScriptFunction("Date.prototype.setFullYear", 66 &native_Date_setFullYear); 67 Date_ctor["prototype"]["setHours"] = new ScriptFunction("Date.prototype.setHours", 68 &native_Date_setHours); 69 Date_ctor["prototype"]["setMilliseconds"] = new ScriptFunction("Date.prototype.setMillseconds", 70 &native_Date_setMilliseconds); 71 Date_ctor["prototype"]["setMinutes"] = new ScriptFunction("Date.prototype.setMinutes", 72 &native_Date_setMinutes); 73 Date_ctor["prototype"]["setMonth"] = new ScriptFunction("Date.prototype.setMonth", 74 &native_Date_setMonth); 75 Date_ctor["prototype"]["setSeconds"] = new ScriptFunction("Date.prototype.setSeconds", 76 &native_Date_setSeconds); 77 Date_ctor["prototype"]["setTime"] = new ScriptFunction("Date.prototype.setTime", 78 &native_Date_setTime); 79 Date_ctor["prototype"]["toDateString"] = new ScriptFunction("Date.prototype.toDateString", 80 &native_Date_toDateString); 81 Date_ctor["prototype"]["toISOString"] = new ScriptFunction("Date.prototype.toISOString", 82 &native_Date_toISOString); 83 Date_ctor["prototype"]["toUTC"] = new ScriptFunction("Date.prototype.toUTC", 84 &native_Date_toUTC); 85 interpreter.forceSetGlobal("Date", Date_ctor, false); 86 } 87 88 private: 89 90 /** 91 * The Date class 92 */ 93 class ScriptDate 94 { 95 public: 96 /** 97 * Creates a Date representing the time and date of object creation 98 */ 99 this() 100 { 101 _sysTime = Clock.currTime(); 102 } 103 104 this(in long num) 105 { 106 _sysTime = SysTime.fromUnixTime(num); 107 } 108 109 /// takes month 0-11 like JavaScript 110 this(in int year, in int monthIndex, in int day=1, in int hours=0, in int minutes=0, 111 in int seconds=0, in int milliseconds=0) 112 { 113 import core.time: msecs; 114 auto dt = DateTime(year, monthIndex+1, day, hours, minutes, seconds); 115 _sysTime = SysTime(dt, msecs(milliseconds), UTC()); 116 } 117 118 /// This string has to be formatted as "2020-Jan-01 00:00:00" for example. Anything different throws an exception 119 this(in string str) 120 { 121 try 122 { 123 auto dt = DateTime.fromSimpleString(str); 124 _sysTime = SysTime(dt, UTC()); 125 } 126 catch(DateTimeException) 127 { 128 auto dt = DateTime.fromISOString(str); 129 _sysTime = SysTime(dt, UTC()); 130 } 131 } 132 133 static long now() 134 { 135 immutable start = SysTime.fromUnixTime(0); 136 return (Clock.currTime - start).total!"msecs"; 137 } 138 139 /// returns day of month 140 int getDate() const 141 { 142 auto dt = cast(DateTime)_sysTime; 143 return dt.day; 144 } 145 146 /// returns day of week 147 int getDay() const 148 { 149 auto dt = cast(DateTime)_sysTime; 150 return dt.dayOfWeek; 151 } 152 153 int getFullYear() const 154 { 155 auto dt = cast(DateTime)_sysTime; 156 return dt.year; 157 } 158 159 /// get the hour of the date 160 int getHours() const 161 { 162 return _sysTime.hour; 163 } 164 165 long getMilliseconds() const 166 { 167 return _sysTime.fracSecs.total!"msecs"; 168 } 169 170 int getMinutes() const 171 { 172 return _sysTime.minute; 173 } 174 175 /// returns month from 0-11 176 int getMonth() const 177 { 178 return cast(int)(_sysTime.month) - 1; 179 } 180 181 int getSeconds() const 182 { 183 return _sysTime.second; 184 } 185 186 long getTime() const 187 { 188 return _sysTime.toUnixTime * 1000; // TODO fix 189 } 190 191 long getTimezone() const 192 { 193 return _sysTime.timezone.utcOffsetAt(_sysTime.stdTime).total!"minutes"; 194 } 195 196 // TODO UTC stuff 197 198 void setDate(in int d) 199 { 200 _sysTime.day = d; 201 } 202 203 void setFullYear(in int year) 204 { 205 _sysTime.year = year; 206 } 207 208 void setHours(in int hours, in int minutes=0, in int seconds=0) 209 { 210 _sysTime.hour = hours; 211 _sysTime.minute = minutes; 212 _sysTime.second = seconds; 213 } 214 215 void setMilliseconds(in uint ms) 216 { 217 import core.time: msecs, Duration; 218 _sysTime.fracSecs = msecs(ms); 219 } 220 221 void setMinutes(in uint minutes) 222 { 223 _sysTime.minute = minutes; 224 } 225 226 void setMonth(in uint month) 227 { 228 _sysTime.month = cast(Month)(month % 12 + 1); 229 } 230 231 void setSeconds(in uint s) 232 { 233 _sysTime.second = cast(ubyte)(s%60); 234 } 235 236 void setTime(in long unixTimeMs) 237 { 238 _sysTime = _sysTime.fromUnixTime(unixTimeMs / 1000); // TODO fix 239 } 240 241 string toISOString() const 242 { 243 auto dt = cast(DateTime)_sysTime; 244 return dt.toISOString(); 245 } 246 247 ScriptDate toUTC() const 248 { 249 auto newSD = new ScriptDate(0); 250 newSD._sysTime = _sysTime.toUTC(); 251 return newSD; 252 } 253 254 override string toString() const 255 { 256 auto tz = _sysTime.timezone.dstName; 257 return (cast(DateTime)_sysTime).toString() ~ " " ~ tz; 258 } 259 260 private: 261 SysTime _sysTime; 262 } 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 }