• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1/*
2 * Copyright (c) 2021-2024 Huawei Device Co., Ltd.
3 * Licensed under the Apache License, Version 2.0 (the "License");
4 * you may not use this file except in compliance with the License.
5 * You may obtain a copy of the License at
6 *
7 * http://www.apache.org/licenses/LICENSE-2.0
8 *
9 * Unless required by applicable law or agreed to in writing, software
10 * distributed under the License is distributed on an "AS IS" BASIS,
11 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 * See the License for the specific language governing permissions and
13 * limitations under the License.
14 */
15
16package escompat;
17
18// TODO To offer protection against timing attacks and fingerprinting, the precision of `Date.getTime()`,
19// `Date.now()` might get rounded depending on browser (target) settings.
20// In Firefox, the `privacy.reduceTimerPrecision` preference is enabled by default
21// and defaults to 20µs in Firefox 59; in 60 it will be 2ms.
22
23/** Hours in a day. */
24const hoursPerDay: int = 24;
25
26/** Minutes in an hour. */
27const minutesPerHour: int = 60;
28
29/** Seconds in a minute. */
30const secondsPerMinute: long = 60;
31
32/** Milliseconds in a second. */
33const msPerSecond: long = 1000;
34
35/** msPerMinute == 60000 */
36const msPerMinute: long = msPerSecond * secondsPerMinute;
37
38/** msPerHour == 3600000 */
39const msPerHour: long = msPerMinute * minutesPerHour;
40
41/** msPerDay == 86400000 */
42const msPerDay: long = msPerHour * hoursPerDay;
43
44/** Days in a year */
45const dayCountInYear: int = 365;
46
47/** Days in a leap year */
48const dayCountInLeapYear: int = 366;
49
50/** Months in a year */
51const monthCountPerYear: int = 12;
52
53/** Max possible count of days in month */
54const maxDaysInMonth = 31;
55
56/**
57 * This gives a range of 8,640,000,000,000,000 milliseconds
58 * to either side of 01 January, 1970 UTC.
59 */
60const maxDateOffset: long = 8.64e15;
61
62/** Day names */
63const dayNames: String[] = [
64    "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"
65];
66
67/** Month names */
68const monthNames: String[] = [
69    "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
70];
71
72/** First day of a month in a year */
73const firstDayInMonthNormal: int[] = [0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334];
74/** First day of a month in a leap year */
75const firstDayInMonthLeap: int[]   = [0, 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335];
76
77/**
78 * Returns first day with respect to year and month
79 *
80 * @param year
81 *
82 * @param month
83 *
84 * @returns first day
85 */
86function getFirstDayInMonth(year: int, month: int): int {
87    assert (year == 0 || year == 1) && (month >= 0 && month <= 11) : "Invalid year/month pair";
88    if (year == 0) {
89        return firstDayInMonthNormal[month];
90    } else {
91        return firstDayInMonthLeap[month];
92    }
93}
94
95/**
96 * Calculate the elapsed days since Unix Epoch.
97 *
98 * @returns elapsed days since Unix Epoch.
99 */
100function ecmaDayFromTime(time: long): int {
101    if (time < 0) {
102        time -= msPerDay - 1;
103    }
104    return (time / msPerDay) as int;
105}
106
107/**
108 *  @see ECMA-262, 20.4.1.3
109 *
110 * @returns number of days in the given year.
111 */
112function ecmaDaysInYear(year: int): int {
113    if ((year % 4 != 0) || ((year % 100 == 0) && (year % 400 != 0))) {
114        return dayCountInYear;
115    }
116
117    return dayCountInLeapYear;
118}
119
120/**
121 * @see ECMA-262, 21.4.1.3
122 *
123 * @returns Year, corresponding to the given time.
124 */
125function ecmaYearFromTime(time: long): int {
126    let approx: int = floor(time / msPerDay / 365.2425) as int + 1970 as int;
127    let yearMs: long = ecmaDayFromYear(approx) * msPerDay;
128
129    if (yearMs > time) {
130        approx--;
131    }
132
133    if ((yearMs + ecmaDaysInYear(approx) * msPerDay) <= time) {
134        approx++;
135    }
136
137    return approx;
138}
139
140/**
141 * @see ECMA-262, 21.4.1.4
142 *
143 * @returns Month number (within year).
144 */
145function ecmaMonthFromTime(time: long): byte {
146    let year = ecmaYearFromTime(time);
147    let dayWithinYear = ecmaDayFromTime(time) - ecmaDayFromYear(year);
148
149    assert dayWithinYear >= 0 && dayWithinYear < dayCountInLeapYear;
150
151    let inLeapYear = ecmaInLeapYear(year);
152
153    for (let i = 1; i < 12; i++) {
154        if (dayWithinYear < getFirstDayInMonth(inLeapYear, i)) {
155            return (i - 1) as byte;
156        }
157    }
158
159    return 11;
160}
161
162/**
163 * @see ECMA-262, 21.4.1.5
164 *
165 * @returns Date number (within month).
166 */
167function ecmaDateFromTime(time: long): byte {
168    let year = ecmaYearFromTime(time);
169    let dayWithinYear = ecmaDayFromTime(time) - ecmaDayFromYear(year);
170
171    assert dayWithinYear >= 0 && dayWithinYear < dayCountInLeapYear;
172
173    let inLeapYear = ecmaInLeapYear(year);
174
175    let month: byte = 11;
176
177    for (let i = 1; i < 12; i++) {
178        if (dayWithinYear < getFirstDayInMonth(inLeapYear, i)) {
179            month = (i - 1) as byte;
180            break;
181        }
182    }
183
184    return (dayWithinYear + 1 - getFirstDayInMonth(inLeapYear, month)) as byte;
185}
186
187/**
188 * @see ECMA-262, 21.4.1.5
189 *
190 * @param time
191 *
192 * @returns Week day.
193 */
194function ecmaWeekDay(time: long): byte {
195    let day = ecmaDayFromTime (time);
196    let weekDay = ((day + 4) % 7) as byte;
197
198    return weekDay >= 0 ? weekDay : (weekDay + 7) as byte;
199}
200
201/**
202 * @see ECMA-262, 21.4.1.5
203 *
204 * @param time
205 *
206 * @returns Week day.
207 */
208function ecmaTimeInDayFromTime(time: long): long {
209    let day = ecmaDayFromTime(time);
210    return (time - (day * msPerDay));
211}
212
213/**
214 * @see ECMA-262, 21.4.1.13
215 *
216 * @returns Hour component of a given time.
217 */
218function ecmaHourFromTime(time: long): byte {
219    let timeInDay = ecmaTimeInDayFromTime(time);
220    return (timeInDay / msPerHour) as byte;
221}
222
223/**
224 * @see ECMA-262, 21.4.1.13
225 *
226 * @returns Minute component of a given time.
227 */
228function ecmaMinFromTime(time: long): byte {
229    let timeInDay = ecmaTimeInDayFromTime(time);
230    return ((timeInDay / msPerMinute) % minutesPerHour) as byte;
231}
232
233/**
234 * @see ECMA-262, 21.4.1.13
235 *
236 * @returns Seconds component of a given time.
237 */
238function ecmaSecFromTime(time: long): byte {
239    let timeInDay = ecmaTimeInDayFromTime(time);
240    return ((timeInDay / msPerSecond) % secondsPerMinute) as byte;
241}
242
243/**
244 * @see ECMA-262, 21.4.1.13
245 *
246 * @returns Milliseconds component of a given time.
247 */
248function ecmaMsFromTime(time: long): short {
249    let timeInDay = ecmaTimeInDayFromTime(time);
250    return (timeInDay % msPerSecond) as short;
251}
252
253/**
254 * @see ECMA-262, 21.4.1.14
255 *
256 * @param hr
257 *
258 * @param min
259 *
260 * @param sec
261 *
262 * @param ms
263 *
264 * @returns Constructed time in milliseconds.
265 */
266function ecmaMakeTime(hr: long, min: long, sec: long, ms: long): long {
267    return hr * msPerHour + min * msPerMinute + sec * msPerSecond + ms;
268}
269
270/**
271 * @see ECMA-262, 21.4.1.14
272 *
273 * @param hr
274 *
275 * @param min
276 *
277 * @param sec
278 *
279 * @param ms
280 *
281 * @returns Constructed time in milliseconds.
282 */
283function ecmaMakeTime(hr: double, min: double, sec: double, ms: double): long {
284    let hh = trunc(hr)  as long;
285    let mm = trunc(min) as long;
286    let ss = trunc(sec) as long;
287    let mi = trunc(ms)  as long;
288
289    return ecmaMakeTime(hh, mm, ss, mi);
290}
291
292
293/**
294 * @see ECMA-262, 21.4.1.15
295 *
296 * @param year
297 *
298 * @param month
299 *
300 * @param date
301 *
302 * @returns Elapsed number of days since Unix Epoch.
303 */
304function ecmaMakeDay(year: long, month: long, date: long): long {
305    let ym = (year + floor(month as double / 12.0 as double)) as int;
306    let mn = (month % 12) as byte;
307
308    if (mn < 0) {
309        mn += 12;
310    }
311
312    let days: long = (ecmaDayFromYear(ym) as long + getFirstDayInMonth(ecmaInLeapYear(ym), mn) as long + (date as long) - 1);
313
314    return days;
315}
316
317/**
318 * @see ECMA-262, 21.4.1.15
319 *
320 * @param year
321 *
322 * @param month
323 *
324 * @param day
325 *
326 * @returns Elapsed number of days since Unix Epoch.
327 */
328function ecmaMakeDay(year: double, month: double, day: double): long {
329    let y = trunc(year)  as long;
330    let m = trunc(month) as long;
331    let d = trunc(day)   as long;
332
333    return ecmaMakeDay(y, m, d);
334}
335
336/**
337 * @see ECMA-262, 21.4.1.16
338 *
339 * @returns Elapsed number of milliseconds since Unix Epoch.
340 *
341 */
342function ecmaMakeDate(day:long, time: long): long {
343    return day * msPerDay + time;
344}
345
346/**
347 * @see ECMA-262, 21.4.1.17
348 *
349 * @returns Elapsed number of milliceconds since Unix Epoch.
350 *
351 */
352function ecmaTimeClip(time: long): long throws {
353    if ((time > maxDateOffset) || (time < -maxDateOffset)) {
354        throw new InvalidDate();
355    }
356
357    return time;
358}
359
360/**
361 * Returns first day of a given year since epoch.
362 *
363 * @param year
364 *
365 * @returns day from year
366 *
367 * @see ECMA-262, 21.4.1.3, DayFromYear
368 */
369function ecmaDayFromYear(year: int): int {
370    return (365 * (year - 1970) +
371        floor((year - 1969) / 4.0) -
372        floor((year - 1901) / 100.0) +
373        floor((year - 1601) / 400.0)) as int;
374}
375
376/**
377 * Returns first day of a given year since epoch.
378 *
379 * @param year
380 *
381 * @returns day from year
382 *
383 * @see ECMA-262, 21.4.1.3, DayFromYear
384 */
385function ecmaTimeFromYear(year: int): long {
386    return msPerDay as long * ecmaDayFromYear(year) as long;
387}
388
389/**
390 * The leap-year function is 1 for a time within a leap year and otherwise is 0.
391 *
392 * @see ECMA-262, 21.4.1.3
393 *
394 * @returns 1 - if the year is leap
395 *          0 - otherwise
396 */
397function ecmaInLeapYear(year: int): byte {
398    return (ecmaDaysInYear(year) - dayCountInYear) as byte;
399}
400
401/**
402 * Date JS API-compatible class
403 */
404export final class Date {
405    /** Stored value of Date, milliseconds */
406    private ms: long;
407    private TZOffset: long;
408
409    /**
410     * The `parseDateFromString()` static method parses a string representation of a date,
411     * and returns the number of milliseconds since January 1, 1970, 00:00:00 UTC
412     * or raises `InvalidDate` if the string is unrecognized or, in some cases,
413     * contains illegal date values (e.g. 2015-02-31).
414     *
415     * Only the @link{https://tc39.es/ecma262/#sec-date-time-string-format|ISO 8601 format}
416     * (YYYY-MM-DDTHH:mm:ss.sssZ) is explicitly specified to be supported.
417     * Other formats are implementation-defined and may not work across all browsers (targets).
418     * A library can help if many different formats are to be accommodated.
419     *
420     * @see ECMA-262, 21.4.3.2
421     *
422     * @returns A number representing the milliseconds elapsed since January 1, 1970, 00:00:00 UTC
423     * and the date obtained by parsing the given string representation of a date.
424     * If the argument doesn't represent a valid date, `InvalidDate` error is thrown.
425     */
426    private static parseDateFromString(dateStr: String): number {
427        const dateStrLen = dateStr.length as int;
428        if (dateStrLen < 4) {
429            return 0;
430        }
431        if (dateStrLen > 35) {
432            throw new InvalidDate();
433        }
434
435        let date = new Date(0);
436
437        let timeZone: long = 0;
438        const years = Date.parseIfContains(dateStr, 0, 4);
439        const months = Date.parseIfContains(dateStr, 5, 7);
440        const days = Date.parseIfContains(dateStr, 8, 10);
441        const hours = Date.parseIfContains(dateStr, 11, 13);
442        const minutes = Date.parseIfContains(dateStr, 14, 16);
443        const seconds = Date.parseIfContains(dateStr, 17, 19);
444        const milliseconds = Date.parseIfContains(dateStr, 20, 23);
445
446        date.setUTCFullYear(years);
447        if (months != Date.UNSUCCESSFUL_PARSING) {
448            if (months > monthCountPerYear || months <= 0) {
449                throw new InvalidDate();
450            }
451            date.setUTCMonth((months - 1) as int);
452        } else {
453            date.setUTCMonth(0 as int);
454        }
455        if (days != Date.UNSUCCESSFUL_PARSING) {
456            if (days > maxDaysInMonth || days <= 0) {
457                throw new InvalidDate();
458            }
459            date.setUTCDate((days) as byte);
460        } else {
461            date.setUTCDate(1 as byte);
462        }
463        if (hours != Date.UNSUCCESSFUL_PARSING) {
464            if (hours > hoursPerDay || hours < 0) {
465                throw new InvalidDate();
466            }
467            date.setUTCHours(hours as byte);
468        } else {
469            date.setUTCHours(0 as byte);
470        }
471        if (minutes != Date.UNSUCCESSFUL_PARSING) {
472            if (minutes >= minutesPerHour || minutes < 0) {
473                throw new InvalidDate();
474            }
475            date.setUTCMinutes(minutes as byte);
476        } else {
477            date.setUTCMinutes(0 as byte);
478        }
479        if (seconds != Date.UNSUCCESSFUL_PARSING) {
480            if (seconds >= secondsPerMinute || seconds < 0) {
481                throw new InvalidDate();
482            }
483            date.setUTCSeconds(seconds as byte);
484        } else {
485            date.setUTCSeconds(0 as byte);
486        }
487        if (milliseconds != Date.UNSUCCESSFUL_PARSING) {
488            if (milliseconds >= msPerSecond || milliseconds < 0) {
489                throw new InvalidDate();
490            }
491            date.setUTCMilliseconds(milliseconds as short);
492        } else {
493            date.setUTCMilliseconds(0 as short);
494        }
495        if (dateStrLen > 23) {
496            timeZone = 0;
497            let timeZoneSign = 0;
498            let timeZoneSignChar = c'+';
499            try {
500                timeZoneSignChar = dateStr.charAt(23);
501            } catch (e) {}
502            if (timeZoneSignChar == c'-') {
503                timeZoneSign = 1;
504            } else if (timeZoneSignChar == c'Z') {
505                timeZoneSign = 0;
506            } else if (timeZoneSignChar == c'+') {
507                timeZoneSign = -1;
508            } else {
509                throw new InvalidDate();
510            }
511            const timeZoneHours = Date.parseIfContains(dateStr, 24, 26);
512            const timeZoneMinutes = Date.parseIfContains(dateStr, 27, 29);
513            const timeZoneSeconds = Date.parseIfContains(dateStr, 30, 32);
514            const timeZoneSecondsFraction = Date.parseIfContains(dateStr, 33, min(36, dateStrLen) as int);
515            if (timeZoneHours != Date.UNSUCCESSFUL_PARSING) {
516                if (timeZoneHours < 0 || timeZoneHours >= hoursPerDay) {
517                    throw new InvalidDate();
518                }
519                timeZone += timeZoneHours * msPerHour;
520            }
521            if (timeZoneMinutes != Date.UNSUCCESSFUL_PARSING) {
522                if (timeZoneMinutes < 0 || timeZoneMinutes >= minutesPerHour) {
523                    throw new InvalidDate();
524                }
525                timeZone += timeZoneMinutes * msPerMinute;
526            }
527            if (timeZoneSeconds != Date.UNSUCCESSFUL_PARSING) {
528                if (timeZoneSeconds < 0 || timeZoneSeconds >= secondsPerMinute) {
529                    throw new InvalidDate();
530                }
531                timeZone += timeZoneSeconds * msPerSecond;
532            }
533            if (timeZoneSecondsFraction != Date.UNSUCCESSFUL_PARSING) {
534                let timeZoneMillis = timeZoneSecondsFraction;
535                if (dateStrLen == 32) {
536                    timeZoneMillis *= 100;
537                } else if (dateStrLen == 33) {
538                    timeZoneMillis *= 10;
539                }
540                if (timeZoneSecondsFraction < 0) {
541                    throw new InvalidDate();
542                }
543                timeZone += timeZoneSecondsFraction;
544            }
545
546            timeZone *= timeZoneSign;
547        }
548        if (dateStrLen == 23) {
549            timeZone = Date.getLocalTimezoneOffset(date.valueOf() as long) *  msPerMinute;
550        }
551        return date.valueOf() + timeZone ;
552    }
553    /**
554     * Default constructor.
555     *
556     * @see ECMA-262, 21.4.2.1
557     *
558     * @description Initializes Date instance with current time.
559     */
560    constructor() {
561        this.ms = Date.now() as long;
562        this.TZOffset = Date.getLocalTimezoneOffset(this.ms as long)
563    }
564
565    /**
566     * `Date` constructor.
567     *
568     * @param value
569     *
570     * @see ECMA-262, 21.4.2.1
571     *
572     * @description Initializes `Date` instance with `number` or `string` or another `Date` instance.
573     * NOTE: Dates before `1921-01-01T00:00:00 GMT` can be represented as UTC milliseconds in different ways then TS do.
574     */
575
576    constructor(value: number | string | Date) {
577
578        if (value instanceof Date) {
579            this.ms = trunc((value as Date).valueOf() as number) as long
580            this.TZOffset = (value as Date).getTimezoneOffset() as long
581        }
582        else if (typeof value == "string") {
583            this.ms = (Date.parseDateFromString(value as string) as long);
584        }
585        else {
586            this.ms = trunc(value as number) as long;
587        }
588        this.TZOffset = Date.getLocalTimezoneOffset(this.ms as long);
589    }
590
591    /**
592     * `Date` constructor.
593     *
594     * @param year
595     *
596     * @param month
597     *
598     * @param day
599     *
600     * @param hours
601     *
602     * @param minutes
603     *
604     * @param seconds
605     *
606     * @param ms
607     *
608     * @see ECMA-262, 21.4.2.1
609     *
610     * @description Initialize `Date` instance with year, month, day, hours, minutes, seconds and milliseconds given.
611     * NOTE: Dates before `1921-01-01T00:00:00 GMT` can be represented as UTC milliseconds in different ways then TS do.
612     */
613    constructor(year: number, monthIndex: number, day?: number, hours?: number, minutes?: number, seconds?: number, ms?: number) {
614        this.ms = ecmaMakeDate(
615            ecmaMakeDay(
616                year,
617                monthIndex,
618                day != undefined ? day as number: 1
619            ),
620            ecmaMakeTime(
621                hours != undefined ? hours as number : 0,
622                minutes != undefined ? minutes as number : 0,
623                seconds != undefined ? seconds as number : 0,
624                ms != undefined ? ms as number : 0
625            )
626        );
627        this.TZOffset = Date.getLocalTimezoneOffset(this.ms as long);
628        this.ms += this.TZOffset * msPerMinute
629    }
630
631    /**
632     * Creates a new instance of a Date as a string
633     *
634     * @returns A new Date instance as a string
635     */
636    static invoke(): string {
637        return new Date().toString();
638    }
639
640    /**
641     * The `isDateValid()` method checks if given date `ms` is maximum of ±100,000,000
642     * (one hundred million) days relative to January 1, 1970 UTC
643     * (that is, April 20, 271821 BCE ~ September 13, 275760 CE) can be represented
644     * by the standard `Date` object (equivalent to ±8,640,000,000,000,000 milliseconds).
645     */
646    private isDateValid(ms: long): boolean {
647        return ms >= -maxDateOffset && ms <= maxDateOffset;
648    }
649
650    /**
651     * The `isDateValid()` method checks if constructed date is maximum of ±100,000,000
652     * (one hundred million) days relative to January 1, 1970 UTC
653     * (that is, April 20, 271821 BCE ~ September 13, 275760 CE) can be represented
654     * by the standard Date object (equivalent to ±8,640,000,000,000,000 milliseconds).
655     *
656     */
657    public isDateValid(): boolean {
658        return this.isDateValid(this.ms);
659    }
660
661    /**
662     * The `valueOf()` method returns the primitive value of a `Date` object.
663     *
664     * @see ECMA-262, 21.4.4.44
665     *
666     * @returns The number of milliseconds between 1 January 1970 00:00:00 UTC and the given date.
667     *
668     * @throws InvalidDate - Throws if Date object is invalid (@link{isDateValid} is `false`).
669     */
670    public valueOf(): number {
671        if (!this.isDateValid()) {
672            return NaN;
673        }
674        return this.ms as number;
675    }
676
677    /**
678     * The `now()` static method returns the number of milliseconds elapsed since the epoch,
679     * which is defined as the midnight at the beginning of January 1, 1970, UTC.
680     *
681     * @see ECMA-262, 21.4.3.1
682     *
683     * @returns A number representing the number of milliseconds elapsed since the epoch,
684     * which is defined as the midnight at the beginning of January 1, 1970, UTC.
685     */
686    public static native now(): number;
687
688    /**
689     * Gets local time offset.
690     *
691     * @returns local time offset.
692     */
693    public static native getLocalTimezoneOffset(ms: long): long;
694
695    /**
696     * Gets time zone name.
697     *
698     * @returns time zone name.
699     */
700    public static native getTimezoneName(ms: long): String;
701
702    /**
703     * Gets locale string representation according to format.
704     *
705     * @param format
706     *
707     * @param locale
708     *
709     * @param ms
710     *
711     * @param isUTC
712     *
713     * @returns locale string in the specified format.
714     */
715    public static native getLocaleString(format: String, locale: String, ms: long, isUTC: boolean): String;
716
717    /**
718     * Gets a string with a language-sensitive representation of the time portion of the date.
719     *
720     * @returns a language-sensitive representation of the time portion of the date.
721     */
722    public toLocaleTimeString(): string {
723        return Date.getLocaleString("%EX", "", this.ms, false);
724    }
725
726    /**
727     * Gets a string with a language-sensitive representation of the time portion of the date with respect to locale.
728     *
729     * @param locale
730     *
731     * @returns a language-sensitive representation of the time portion of the date with respect to locale.
732     */
733    public toLocaleTimeString(locale: string): string {
734        return Date.getLocaleString("%EX", locale, this.ms, false);
735    }
736
737    /**
738     * Gets a string with a language-sensitive representation of this date.
739     *
740     * @returns a language-sensitive representation of this date.
741     */
742    public toLocaleString(): string {
743        return Date.getLocaleString("%Ex %EX", "", this.ms, false);
744    }
745
746    /**
747     * Gets a string with a language-sensitive representation of this date with respect to locale.
748     *
749     * @param locale
750     *
751     * @returns a language-sensitive representation of this date.
752     */
753    public toLocaleString(locale: string): string {
754        return Date.getLocaleString("%Ex %EX", locale, this.ms, false);
755    }
756
757    /**
758     * Gets a string with a language-sensitive representation
759     * of the date portion of the specified date in the user agent's timezone.
760     *
761     * @returns a string with a language-sensitive representation
762     * of the date portion of the specified date in the user agent's timezone.
763     */
764    public toLocaleDateString(): string {
765        return Date.getLocaleString("%Ex", "", this.ms, false);
766    }
767
768    /**
769     * Returns a string with a language-sensitive representation
770     * of the date portion of the specified date in the user agent's timezone.
771     *
772     * @returns a string with a language-sensitive representation
773     * of the date portion of the specified date in the user agent's timezone.
774     */
775    public toLocaleDateString(locale: string): string {
776        return Date.getLocaleString("%Ex", locale, this.ms, false);
777    }
778
779    /**
780     * The `getDate()` method returns the day of the month for the specified date according to local time.
781     *
782     * @see ECMA-262, 21.4.4.2
783     *
784     * @returns An integer number, between 1 and 31, representing the day of the month for the given date according to local time.
785     */
786    public getDate(): number {
787        let localTime = this.ms - this.TZOffset * 60 * msPerSecond;
788        let day: int = ecmaDayFromTime(localTime) - ecmaDayFromYear(ecmaYearFromTime(localTime));
789        let inLeapYear = ecmaInLeapYear(this.getFullYear() as int);
790        let firstDay: int = getFirstDayInMonth(inLeapYear, ecmaMonthFromTime(localTime));
791        return (day - firstDay + 1) as number;
792    }
793
794    /**
795     * Changes the day of the month of a given Date instance, based on local time.
796     *
797     * @param value new day.
798     */
799    public setDate(value: byte): void {
800        let day = this.getDate();
801        this.ms -= day * msPerDay;
802        this.ms += value * msPerDay;
803    }
804
805    /**
806     * Changes the day of the month of a given Date instance, based on local time.
807     *
808     * @param value new day.
809     */
810    public setDate(value: number): number {
811        this.setDate(value as byte);
812        return this.ms as number;
813    }
814
815    /**
816     * Alias to @link{setDate} and left for compatibility with ECMA-262.
817     *
818     * @param value new day.
819     */
820    public setDay(value: byte): void {
821        this.setDate(value);
822    }
823
824    /**
825     * Returns the day of the month (from 1 to 31) in the specified date according to universal time.
826     *
827     * @returns An integer number, between 1 and 31, representing the day of the month for the given date according to local time.
828     */
829    public getUTCDate(): number {
830        let localTime = this.ms;
831        let day: int = ecmaDayFromTime(localTime) - ecmaDayFromYear(ecmaYearFromTime(localTime));
832        let inLeapYear = ecmaInLeapYear(this.getFullYear() as int);
833        let firstDay: int = getFirstDayInMonth(inLeapYear, ecmaMonthFromTime(localTime));
834        return (day - firstDay + 1) as number;
835    }
836
837    /**
838     * Changes the day of the month of a given Date instance, based on UTC time.
839     *
840     * @param value new day.
841     */
842    public setUTCDate(value: byte): void {
843        let day = this.getUTCDate() as byte;
844        this.ms -= day * msPerDay;
845        this.ms += value * msPerDay;
846    }
847
848    /**
849     * Changes the day of the month of a given Date instance, based on UTC time.
850     *
851     * @param value new day.
852     */
853    public setUTCDate(value: number): number {
854        this.setUTCDate(value as byte);
855        return this.ms as number;
856    }
857
858    /**
859     * Changes the day of the month of a given Date instance, based on UTC time.
860     *
861     * @param value new day.
862     */
863    public setUTCDay(value: byte): void {
864        this.setUTCDate(value);
865    }
866
867    /**
868     * Changes the day of the month of a given Date instance, based on UTC time.
869     *
870     * @param value new day.
871     */
872    public setUTCDay(value: number): number {
873        this.setUTCDate(value);
874        return this.ms as number;
875    }
876
877    /**
878     * Returns the day of the week for the specified date according to local time,
879     * where 0 represents Sunday. For the day of the month, see @link{getDayOfMonth}.
880     *
881     * @see ECMA-262, 21.4.4.3
882     *
883     * @returns An integer number, between 0 and 6, corresponding to the day of the week
884     * for the given date, according to local time: 0 for Sunday, 1 for Monday, 2 for Tuesday, and so on.
885     */
886    public getDay(): number {
887        return ecmaWeekDay(this.ms - this.TZOffset * 60 * msPerSecond) as number;
888    }
889
890    /**
891     * Returns the day of the week in the specified date according to universal time, where 0 represents Sunday.
892     *
893     * @returns An integer number, between 0 and 6, corresponding to the day of the week
894     * for the given date, according to local time: 0 for Sunday, 1 for Monday, 2 for Tuesday, and so on.
895     */
896    public getUTCDay(): number {
897        return ecmaWeekDay(this.ms) as number;
898    }
899
900    /**
901     * Returns the year of the specified date according to local time.
902     *
903     * @see ECMA-262, 21.4.4.4
904     * deprecated
905     * @note This function is an alias to @link{getFullYear} and left for compatibility with ECMA-262.
906     *
907     * @returns year
908     */
909    public getYear(): int {
910        return this.getFullYear() as int;
911    }
912
913    /**
914     * Returns the year of the specified date according to local time.
915     *
916     * @returns A year of the specified date according to local time.
917     *
918     * @description The value returned by `getUTCFullYear()` is an absolute number.
919     * For dates between the years 1000 and 9999, `getUTCFullYear()` returns a four-digit number,
920     * for example, 1995. Use this function to make sure a year is compliant with years after 2000.
921     *
922     * @returns year
923     */
924    public getUTCFullYear(): number {
925        return ecmaYearFromTime(this.ms) as number;
926    }
927
928    /**
929     * Returns the year of the specified date according to local time.
930     *
931     * @see ECMA-262, 21.4.4.4
932     *
933     * @returns A number corresponding to the year of the given date, according to local time.
934     *
935     * @description The value returned by `getFullYear()` is an absolute number.
936     * For dates between the years 1000 and 9999, `getFullYear()` returns a four-digit number,
937     * for example, 1995. Use this function to make sure a year is compliant with years after 2000.
938     *
939     * @example
940     * ```sts
941     * const today = new Date();
942     * const year = today.getYearFull();
943     * ```
944     */
945    public getFullYear(): number {
946        let localTime = this.ms - this.TZOffset * 60 * msPerSecond;
947        return ecmaYearFromTime(localTime) as number;
948    }
949
950    /**
951     * Sets the full year for a specified date according to universal time.
952     *
953     * @param value new year
954     */
955    public setUTCFullYear(value: number, month?: number, date?: number): number {
956        this.setUTCFullYear(value as int);
957        if (month !== undefined) {
958            this.setUTCMonth(month.intValue());
959        }
960        if (date !== undefined) {
961            this.setUTCDate(date.byteValue());
962        }
963        return this.ms as number;
964    }
965
966    /**
967     * Sets the full year for a specified date according to universal time.
968     *
969     * @param value new year
970     */
971    public setUTCFullYear(value: int): void {
972        let year = ecmaYearFromTime(this.ms);
973        this.ms -= ecmaDayFromYear(year) * msPerDay as long;
974        this.ms += ecmaDayFromYear(value) * msPerDay as long;
975    }
976
977    /**
978     * This function is an alias to @link{setFullYear} and left for compatibility with ECMA-262.
979     *
980     * @param value new year
981     */
982    public setYear(value: number): void {
983        this.setYear(value as int)
984    }
985
986    /**
987     * This function is an alias to @link{setFullYear} and left for compatibility with ECMA-262.
988     *
989     * @param value new year
990     */
991    public setYear(value: int): void {
992        this.setFullYear(value);
993    }
994
995    /**
996     * Sets the full year for a specified date according to local time.
997     *
998     * @param value new year
999     */
1000    public setFullYear(value: number, month?: Number, date?: Number): number {
1001        this.setFullYear(value as int);
1002        if (month !== undefined) {
1003            this.setMonth(month.intValue());
1004        }
1005        if (date !== undefined) {
1006            this.setDate(date.byteValue());
1007        }
1008        return this.ms as number;
1009    }
1010
1011    /**
1012     * Sets the full year for a specified date according to local time.
1013     *
1014     * @param value new year
1015     */
1016    public setFullYear(value: int): void {
1017        let year = ecmaYearFromTime(this.ms - this.TZOffset * 60 * msPerSecond);
1018        this.ms -= ecmaDayFromYear(year) * msPerDay as long;
1019        this.ms += ecmaDayFromYear(value) * msPerDay as long;
1020    }
1021
1022    /**
1023     * Returns the hour for the specified date, according to local time.
1024     *
1025     * @see ECMA-262, 21.4.4.5
1026     *
1027     * @returns An integer number, between 0 and 23, representing the hour for the given date according to local time.
1028     *
1029     * @example
1030     * ```sts
1031     * const today = new Date();
1032     * const hours = today.getHour();
1033     * ```
1034     */
1035    public getHours(): number {
1036        let localTime = this.ms - this.TZOffset * 60 * msPerSecond;
1037        return ecmaHourFromTime(localTime) as number;
1038    }
1039
1040    /**
1041     * Returns the hours in the specified date according to universal time.
1042     *
1043     * @returns An integer number, between 0 and 23, representing the hour for the given date according to UTC time.
1044     */
1045    public getUTCHours(): number {
1046        return ecmaHourFromTime(this.ms) as number;
1047    }
1048
1049    /**
1050     * Sets the hours for a specified date according to local time.
1051     *
1052     * @param value new hours
1053     */
1054    public setHours(value: byte): void {
1055        assert value >= 0 && value <= 23;
1056        let hours = ecmaHourFromTime(this.ms - this.TZOffset * 60 * msPerSecond);
1057        this.ms -= msPerHour * hours;
1058        this.ms += msPerHour * value;
1059    }
1060
1061    /**
1062     * Sets the hours for a specified date according to local time.
1063     *
1064     * @param value new hours
1065     */
1066    public setHours(value: number, min?: number, sec?: number, ms?: number): number {
1067        this.setHours(value as byte);
1068        if (min !== undefined) {
1069            this.setMinutes(min.byteValue());
1070        }
1071        if (sec !== undefined) {
1072            this.setSeconds(sec.byteValue());
1073        }
1074        if (ms !== undefined) {
1075            this.setMilliseconds(ms.shortValue());
1076        }
1077        return this.ms as number;
1078    }
1079
1080    /**
1081     * Sets the hour for a specified date according to universal time.
1082     *
1083     * @param value new hours
1084     */
1085    public setUTCHours(value: byte): void {
1086        assert value >= 0 && value <= 23;
1087        let hours = ecmaHourFromTime(this.ms);
1088        this.ms -= msPerHour * hours;
1089        this.ms += msPerHour * value;
1090    }
1091
1092    /**
1093     * Sets the hour for a specified date according to universal time.
1094     *
1095     * @param value new hours
1096     */
1097    public setUTCHours(value: number, min?: number, sec?: number, ms?: number): number {
1098        this.setUTCHours(value as byte);
1099        if (min !== undefined) {
1100            this.setUTCMinutes(min.byteValue());
1101        }
1102        if (sec !== undefined) {
1103            this.setUTCSeconds(sec.byteValue());
1104        }
1105        if (ms !== undefined) {
1106            this.setUTCMilliseconds(ms.shortValue());
1107        }
1108        return this.ms as number;
1109    }
1110
1111    /**
1112     * Returns the milliseconds in the specified date according to local time.
1113     *
1114     * @see ECMA-262, 21.4.4.6
1115     *
1116     * @returns A number between 0 and 999 representing the milliseconds for the given date according to local time.
1117     *
1118     * @example
1119     * ```sts
1120     * const today = new Date();
1121     * const milliseconds = today.getMilliseconds();
1122     * ```
1123     */
1124    public getMilliseconds(): number {
1125        let localTime = this.ms - this.TZOffset * 60 * msPerSecond;
1126        return ecmaMsFromTime(localTime) as number;
1127    }
1128
1129    /**
1130     * Returns the number of milliseconds elapsed since the epoch,
1131     * which is defined as the midnight at the beginning of January 1, 1970, UTC.
1132     *
1133     * @param d to be converted to milliseconds.
1134     *
1135     * @see ECMA-262, 21.4.3.1
1136     *
1137     * @returns A number representing the number of milliseconds elapsed since the epoch,
1138     * which is defined as the midnight at the beginning of January 1, 1970, UTC.
1139     */
1140    public static UTC(d: Date): long {
1141        return d.valueOf() as long;
1142    }
1143
1144    /**
1145     * Returns the number of milliseconds elapsed since the epoch,
1146     * which is defined as the midnight at the beginning of January 1, 1970, UTC.
1147     *
1148     * @param year to be converted to milliseconds.
1149     *
1150     * @returns A number representing the number of milliseconds elapsed since the epoch,
1151     * which is defined as the midnight at the beginning of January 1, 1970, UTC.
1152     */
1153    public static UTC(year: number): number {
1154        return ecmaMakeDate(ecmaMakeDay(year, 0.0, 1.0), ecmaMakeTime(0.0, 0.0, 0.0, 0.0)) as number
1155    }
1156
1157    /**
1158     * Returns the number of milliseconds elapsed since the epoch,
1159     * which is defined as the midnight at the beginning of January 1, 1970, UTC.
1160     *
1161     * @param year to be converted to milliseconds.
1162     *
1163     * @param month to be converted to milliseconds.
1164     *
1165     * @param day to be converted to milliseconds.
1166     *
1167     * @param hours to be converted to milliseconds.
1168     *
1169     * @param minutes to be converted to milliseconds.
1170     *
1171     * @param seconds to be converted to milliseconds.
1172     *
1173     * @param ms to be converted to milliseconds.
1174     *
1175     * @returns A number representing the number of milliseconds elapsed since the epoch,
1176     * which is defined as the midnight at the beginning of January 1, 1970, UTC.
1177     */
1178
1179    public static UTC(year: number, month: number, day?: number, hours?: number, minutes?: number, seconds?: number, ms?: number): number {
1180        return ecmaMakeDate(
1181            ecmaMakeDay(
1182                year,
1183                month,
1184                day != undefined ? day as number: 1.0
1185            ),
1186            ecmaMakeTime(
1187                hours != undefined ? hours as number : 0,
1188                minutes != undefined ? minutes as number : 0,
1189                seconds != undefined ? seconds as number : 0,
1190                ms != undefined ? ms as number : 0
1191            )
1192        ) as number;
1193    }
1194
1195    /**
1196     * Returns the milliseconds portion of the time object's value according to universal time.
1197     *
1198     * @returns the milliseconds portion of the time object's value according to universal time.
1199     */
1200    public getUTCMilliseconds(): number {
1201        let localTime = this.ms;
1202        return ecmaMsFromTime(localTime) as number;
1203    }
1204
1205    /**
1206     * Sets the milliseconds for a specified date according to local time.
1207     *
1208     * @param value new ms
1209     */
1210    public setMilliseconds(value: short): void {
1211        let ms = ecmaMsFromTime(this.ms - this.TZOffset * 60 * msPerSecond);
1212        this.ms -= ms;
1213        this.ms += value;
1214    }
1215
1216    /**
1217     * Sets the milliseconds for a specified date according to local time.
1218     *
1219     * @param value new ms
1220     */
1221    public setMilliseconds(value: number): number {
1222        this.setMilliseconds(value as short);
1223        return this.ms as number;
1224    }
1225
1226    /**
1227     * Sets the milliseconds for a specified date according to universal time.
1228     *
1229     * @param value new ms
1230     */
1231    public setUTCMilliseconds(value: short): void {
1232        let ms = ecmaMsFromTime(this.ms);
1233        this.ms -= ms;
1234        this.ms += value;
1235    }
1236
1237    /**
1238     * Sets the milliseconds for a specified date according to universal time.
1239     *
1240     * @param value new ms
1241     */
1242    public setUTCMilliseconds(value: number): number {
1243        this.setUTCMilliseconds(value as short);
1244        return this.ms as number;
1245    }
1246
1247    /**
1248     * Returns the seconds in the specified date according to local time.
1249     *
1250     * @see ECMA-262, 21.4.4.9
1251     *
1252     * @returns An integer number, between 0 and 59, representing the seconds in the given date according to local time.
1253     *
1254     * @example
1255     * ```sts
1256     * const today = new Date();
1257     * const seconds = today.getSeconds();
1258     * ```
1259     */
1260    public getSeconds(): number {
1261        let localTime = this.ms - this.TZOffset * 60 * msPerSecond;
1262        return ecmaSecFromTime(localTime) as number;
1263    }
1264
1265    /**
1266     * Returns the seconds in the specified date according to universal time.
1267     *
1268     * @returns the seconds in the specified date according to universal time.
1269     */
1270    public getUTCSeconds(): number {
1271        return ecmaSecFromTime(this.ms) as number;
1272    }
1273
1274    /**
1275     * Sets the seconds for a specified date according to local time.
1276     *
1277     * @param value new seconds
1278     */
1279    public setSeconds(value: number, ms?: number): number {
1280        this.setSeconds(value as byte);
1281        if (ms !== undefined) {
1282            this.setMilliseconds(ms.shortValue());
1283        }
1284        return this.ms as number;
1285    }
1286
1287    /**
1288     * Sets the seconds for a specified date according to local time.
1289     *
1290     * @param value new seconds
1291     */
1292    public setSeconds(value: byte): void {
1293        assert value >= 0 && value < 60;
1294        let sec = ecmaSecFromTime(this.ms - this.TZOffset * 60 * msPerSecond);
1295        this.ms -= sec * msPerSecond;
1296        this.ms += value * msPerSecond;
1297    }
1298
1299    /**
1300     * Sets the seconds for a specified date according to universal time.
1301     *
1302     * @param value new seconds
1303     */
1304    public setUTCSeconds(value: byte): void {
1305        assert value >= 0 && value < 60;
1306        let sec = ecmaSecFromTime(this.ms);
1307        this.ms -= sec * msPerSecond;
1308        this.ms += value * msPerSecond;
1309    }
1310
1311    /**
1312     * Sets the seconds for a specified date according to universal time.
1313     *
1314     * @param value new seconds
1315     */
1316    public setUTCSeconds(value: number, ms?: number): number {
1317        this.setUTCSeconds(value as byte);
1318        if (ms !== undefined) {
1319            this.setUTCMilliseconds(ms.shortValue());
1320        }
1321        return this.ms as number;
1322    }
1323
1324    /**
1325     * Returns the minutes in the specified date according to local time.
1326     *
1327     * @see ECMA-262, 21.4.4.7
1328     *
1329     * @returns An integer number, between 0 and 59, representing the minutes in the given date according to local time.
1330     *
1331     * @example
1332     * ```sts
1333     * const today = new Date();
1334     * const minutes = today.getMinutes();
1335     * ```
1336     */
1337    public getMinutes(): number {
1338        let localTime = this.ms - this.TZOffset * 60 * msPerSecond;
1339        return ecmaMinFromTime(localTime) as number;
1340    }
1341
1342    /**
1343     * Sets the minutes for a specified date according to universal time.
1344     *
1345     * @param value new minutes
1346     */
1347    public setUTCMinutes(value: byte): void {
1348        assert value >= 0 && value < 60;
1349        let min = ecmaMinFromTime(this.ms);
1350        this.ms -= min * msPerMinute;
1351        this.ms += value * msPerMinute;
1352    }
1353
1354    /**
1355     * Sets the minutes for a specified date according to universal time.
1356     *
1357     * @param value new minutes
1358     */
1359    public setUTCMinutes(value: number, sec?: Number, ms?: Number): number {
1360        this.setUTCMinutes(value as byte);
1361        if (sec !== undefined) {
1362            this.setUTCSeconds(sec.byteValue());
1363        }
1364        if (ms !== undefined) {
1365            this.setUTCMilliseconds(ms.shortValue());
1366        }
1367        return this.ms as number;
1368    }
1369
1370    /**
1371     * Returns the minutes in the specified date according to universal time.
1372     *
1373     * @returns the minutes in the specified date according to universal time.
1374     */
1375    public getUTCMinutes(): number {
1376        return ecmaMinFromTime(this.ms) as number;
1377    }
1378
1379    /**
1380     * Sets the minutes for a specified date according to local time.
1381     *
1382     * @param value new minutes
1383     */
1384    public setMinutes(value: byte): void {
1385        assert value >= 0 && value < 60;
1386        let min = ecmaMinFromTime(this.ms - this.TZOffset * 60 * msPerSecond);
1387        this.ms -= min * msPerMinute;
1388        this.ms += value * msPerMinute;
1389    }
1390
1391    /**
1392     * Sets the minutes for a specified date according to local time.
1393     *
1394     * @param value new minutes
1395     */
1396    public setMinutes(value: number, sec?: number, ms?: number): number {
1397        this.setMinutes(value as byte);
1398        if (sec !== undefined) {
1399            this.setSeconds(sec.byteValue());
1400        }
1401        if (ms !== undefined) {
1402            this.setMilliseconds(ms.shortValue());
1403        }
1404        return this.ms as number;
1405    }
1406
1407    /**
1408     * Returns the month in the specified date according to local time,
1409     * as a zero-based value (where zero indicates the first month of the year).
1410     *
1411     * @see ECMA-262, 21.4.4.8
1412     *
1413     * @returns  An integer number, between 0 and 11, representing the month in the given date according to local time.
1414     * 0 corresponds to January, 1 to February, and so on.
1415     *
1416     * @example
1417     * ```sts
1418     * const today = new Date();
1419     * const month = today.getMonth();
1420     * ```
1421     */
1422    public getMonth(): number {
1423        let localTime = this.ms - this.TZOffset * 60 * msPerSecond;
1424        return ecmaMonthFromTime(localTime) as number;
1425    }
1426
1427    /**
1428     * Returns the month of the specified date according to universal time, as a zero-based value (where zero indicates the first month of the year).
1429     *
1430     * @returns  An integer number, between 0 and 11, representing the month in the given date according to UTC time.
1431     * 0 corresponds to January, 1 to February, and so on.
1432     */
1433    public getUTCMonth(): number {
1434        return ecmaMonthFromTime(this.ms) as number;
1435    }
1436
1437    /**
1438     * Sets the month for a specified date according to the currently set year.
1439     *
1440     * @param month new month
1441     */
1442    public setMonth(month: number, date?: number): number {
1443        this.setMonth(month as int);
1444        if (date !== undefined) {
1445            this.setDate(date.byteValue());
1446        }
1447        return this.ms as number;
1448    }
1449
1450    /**
1451     * Sets the month for a specified date according to the currently set year.
1452     *
1453     * @param month new month
1454     */
1455    public setMonth(month: int): void {
1456        let inLeapYear = ecmaInLeapYear(this.getFullYear() as int);
1457        let daysNow: int = getFirstDayInMonth(inLeapYear, ecmaMonthFromTime(this.ms - this.TZOffset * 60 * msPerSecond));
1458        let daysNew: int = getFirstDayInMonth(inLeapYear, month);
1459        this.ms -= daysNow * msPerDay;
1460        this.ms += daysNew * msPerDay;
1461    }
1462
1463    /**
1464     * Sets the month for a specified date according to universal time.
1465     *
1466     * @param month new month
1467     */
1468    public setUTCMonth(month: number, date?: number): number {
1469        this.setUTCMonth(month as int);
1470        if (date !== undefined) {
1471            this.setUTCDate(date.byteValue());
1472        }
1473        return this.ms as number;
1474    }
1475
1476    /**
1477     * Sets the month for a specified date according to universal time.
1478     *
1479     * @param month new month
1480     */
1481    public setUTCMonth(month: int): void {
1482        let inLeapYear = ecmaInLeapYear(this.getFullYear() as int);
1483        let daysNow: int = getFirstDayInMonth(inLeapYear, ecmaMonthFromTime(this.ms));
1484        let daysNew: int = getFirstDayInMonth(inLeapYear, month);
1485        this.ms -= daysNow * msPerDay;
1486        this.ms += daysNew * msPerDay;
1487    }
1488
1489    /**
1490     * Returns the number of milliseconds since the epoch,
1491     * which is defined as the midnight at the beginning of January 1, 1970, UTC.
1492     *
1493     * @see ECMA-262, 21.4.4.10
1494     *
1495     * @returns A number representing the milliseconds elapsed between 1 January 1970 00:00:00 UTC and the given date.
1496     */
1497    public getTime(): number {
1498        return this.valueOf();
1499    }
1500
1501    /**
1502     * Sets the number of milliseconds since the epoch,
1503     * which is defined as the midnight at the beginning of January 1, 1970, UTC.
1504     *
1505     * @param value new ms
1506     *
1507     * @see ECMA-262, 21.4.4.10
1508     *
1509     * @returns A number representing the milliseconds elapsed between 1 January 1970 00:00:00 UTC and the given date.
1510     */
1511    public setTime(value: long): void {
1512        this.ms = value;
1513    }
1514
1515    /**
1516     * Sets the number of milliseconds since the epoch,
1517     * which is defined as the midnight at the beginning of January 1, 1970, UTC.
1518     *
1519     * @param value new ms
1520     *
1521     * @see ECMA-262, 21.4.4.10
1522     *
1523     * @returns A number representing the milliseconds elapsed between 1 January 1970 00:00:00 UTC and the given date.
1524     */
1525    public setTime(value: number): number {
1526        this.setTime(value as long);
1527        return this.ms as number;
1528    }
1529
1530    /**
1531     * Parses a string representation of a date,
1532     * and returns the number of milliseconds since January 1, 1970, 00:00:00 UTC
1533     * or raises `InvalidDate` if the string is unrecognized or, in some cases,
1534     * contains illegal date values (e.g. 2015-02-31).
1535     *
1536     * Only the @link{https://tc39.es/ecma262/#sec-date-time-string-format|ISO 8601 format}
1537     * (YYYY-MM-DDTHH:mm:ss.sssZ) is explicitly specified to be supported.
1538     * Other formats are implementation-defined and may not work across all browsers (targets).
1539     * A library can help if many different formats are to be accommodated.
1540     *
1541     * @param dateStr to be parsed
1542     *
1543     * @see ECMA-262, 21.4.3.2
1544     *
1545     * @returns A number representing the milliseconds elapsed since January 1, 1970, 00:00:00 UTC
1546     * and the date obtained by parsing the given string representation of a date.
1547     * If the argument doesn't represent a valid date, `InvalidDate` exception is thrown.
1548     */
1549    public static parse(dateStr: string): number {
1550        return Date.parseDateFromString(dateStr) as number;
1551    }
1552
1553    /**
1554     * Returns the difference, in minutes, between a date as
1555     * evaluated in the UTC time zone, and the same date as evaluated in the local time zone.
1556     *
1557     * @returns the difference, in minutes, between a date as
1558     * evaluated in the UTC time zone, and the same date as evaluated in the local time zone.
1559     */
1560    public getTimezoneOffset(): number {
1561        return this.TZOffset as number;
1562    }
1563
1564    /**
1565     * Sets the difference, in minutes, between a date as
1566     * evaluated in the UTC time zone, and the same date as evaluated in the local time zone.
1567     *
1568     * @param value new timezone offset
1569     */
1570    public setTimezoneOffset(value: number): number {
1571        return this.setTimezoneOffset(value as int) as number
1572    }
1573
1574    /**
1575     * Sets the difference, in minutes, between a date as
1576     * evaluated in the UTC time zone, and the same date as evaluated in the local time zone.
1577     *
1578     * @param value new timezone offset
1579     */
1580    public setTimezoneOffset(value: int): long {
1581        this.TZOffset = value;
1582    }
1583
1584    /**
1585     * Returns a string in simplified extended ISO format (ISO 8601),
1586     * which is always 24 or 27 characters long (YYYY-MM-DDTHH:mm:ss.sssZ or
1587     * ±YYYYYY-MM-DDTHH:mm:ss.sssZ, respectively). The timezone is always zero UTC offset,
1588     * as denoted by the suffix Z.
1589     *
1590     * @see ECMA-262, 21.4.4.36
1591     *
1592     * @returns A string representing the given date in the ISO 8601 format according to universal time.
1593     * It's the same format as the one required to be recognized by @link{parse()}.
1594     *
1595     * @example
1596     * ```sts
1597     * const today = new Date();
1598     * console.println(today.toISOString()); // Returns 2023-02-05T14:48:00.000Z
1599     * ```
1600     */
1601    public toISOString(): string {
1602        let sb = new StringBuilder();
1603        let y = this.getUTCFullYear();
1604        if (y < 0) {
1605            sb.append("-");
1606        }
1607        if (abs(y) < 1000) {
1608            sb.append("0");
1609        }
1610        if (abs(y) < 100) {
1611            sb.append("0");
1612        }
1613        if (abs(y) < 10) {
1614            sb.append("0");
1615        }
1616        sb.append(abs(y) as long);
1617        sb.append("-");
1618        let month = this.getUTCMonth() + 1;
1619        if(month < 10){
1620            sb.append("0");
1621        }
1622        sb.append(month);
1623        sb.append("-");
1624        let day = this.getUTCDate();
1625        if(day < 10){
1626            sb.append("0");
1627        }
1628        sb.append(day);
1629        sb.append("T");
1630        let h = this.getUTCHours();
1631        if(h < 10) {
1632            sb.append("0");
1633        }
1634        sb.append(h);
1635        sb.append(":");
1636        let m = this.getUTCMinutes();
1637        if(m < 10) {
1638            sb.append("0");
1639        }
1640        sb.append(m);
1641        sb.append(":");
1642        let s = this.getUTCSeconds();
1643        if(s < 10) {
1644            sb.append("0");
1645        }
1646        sb.append(s);
1647        sb.append(".");
1648        let ms = this.getUTCMilliseconds();
1649        if (ms < 10) {
1650           sb.append("00")
1651        }
1652        else if (ms < 100) {
1653            sb.append("0")
1654        }
1655        // sb.append(ms / 100);
1656        // sb.append((ms / 10) % 10);
1657        // sb.append(ms % 10);
1658        sb.append(trunc(ms))
1659        sb.append("Z");
1660        return sb.toString();
1661    }
1662
1663    /**
1664     * Returns a string representation of the Date object.
1665     *
1666     * @returns JSON representation of the current instance
1667     */
1668    public toJSON(): string {
1669        return this.toISOString();
1670    }
1671
1672    /**
1673     * Returns the time portion of a `Date` object interpreted in the local timezone in English.
1674     *
1675     * @see ECMA-262, 21.4.4.42
1676     *
1677     * @returns A string representing the time portion of the given date in human-readable form in English.
1678     *
1679     * @example
1680     * ```
1681     * let d = new Date(1979.0, 9.0, 27.0, 13.0, 12.8, 57.0, 444.1);
1682     * console.println(d.toTimeString()); // 13:12:57 GMT
1683     * ```
1684     */
1685    public toTimeString(): string {
1686        return this.timeString() + " " + this.timeZoneString();
1687    }
1688
1689    /**
1690     * Returns string representation
1691     */
1692    private timeString(): String {
1693        if (!this.isDateValid()) {
1694            throw new Error("Invalid Date");
1695        }
1696
1697        let sb = new StringBuilder();
1698        let h = this.getHours();
1699        if (h < 10) {
1700            sb.append("0");
1701        }
1702        sb.append(h);
1703        sb.append(":");
1704        let m = this.getMinutes();
1705        if (m < 10) {
1706            sb.append("0");
1707        }
1708        sb.append(m);
1709        sb.append(":");
1710        let s = this.getSeconds();
1711        if (s < 10) {
1712            sb.append("0");
1713        }
1714        sb.append(s);
1715        return sb.toString();
1716    }
1717
1718    /**
1719     * Returns the date portion of a `Date` object interpreted in the local timezone in English.
1720     *
1721     * @see ECMA-262, 21.4.4.35
1722     *
1723     * @returns A string representing the date portion of the given Date object in human-readable form in English.
1724     *
1725     * @example
1726     * ```
1727     * let d = new Date(1979.0, 9.0, 27.0, 13.0, 12.8, 57.0, 444.1);
1728     * console.println(d.toDateString()); // Sat Oct 27 1979
1729     * ```
1730     */
1731    public toDateString(): string {
1732        return this.dateString();
1733    }
1734
1735    /**
1736     * Returns a string representation
1737     */
1738    private dateString(): String {
1739        if (!this.isDateValid()) {
1740            throw new Error("Invalid Date");
1741        }
1742
1743        let sb = new StringBuilder();
1744        sb.append(dayNames[this.getDay() as int]);
1745        sb.append(" ");
1746        sb.append(monthNames[this.getMonth() as int]);
1747        sb.append(" ");
1748        let d = this.getDate();
1749        if (d < 10) {
1750            sb.append("0");
1751        }
1752        sb.append(d);
1753        sb.append(" ");
1754        let y = this.getFullYear();
1755        if (y < 0) {
1756            sb.append("-");
1757        }
1758        if (abs(y) < 1000) {
1759            sb.append("0");
1760        }
1761        if (abs(y) < 100) {
1762            sb.append("0");
1763        }
1764        if (abs(y) < 10) {
1765            sb.append("0");
1766        }
1767        sb.append(abs(y) as long);
1768        return sb.toString();
1769    }
1770
1771    /**
1772     * TODO Until TZ support implemented, the time is calculated in UTC+0 and TZ string is hardcoded
1773     */
1774    private timeZoneString(): String {
1775        let sb = new StringBuilder();
1776        sb.append("GMT");
1777        let offset = -this.TZOffset;
1778        let hours = offset / 60;
1779        if(hours < 0) {
1780            sb.append("-");
1781        } else {
1782            sb.append("+")
1783        }
1784        if (abs(hours) < 10) {
1785            sb.append("0");
1786        }
1787        sb.append(abs(hours));
1788        let minutes = abs(offset % 60);
1789        if (minutes < 10) {
1790            sb.append("0");
1791        }
1792        sb.append(minutes);
1793        sb.append(" (");
1794        sb.append(Date.getTimezoneName(this.ms));
1795        sb.append(")");
1796
1797        return sb.toString();
1798    }
1799    /**
1800     * Returns a string representing the specified `Date` object interpreted in the local timezone.
1801     *
1802     * @see ECMA-262, 21.4.4.41
1803     *
1804     * @returns A string representing the given date.
1805     *
1806     * @example
1807     * ```
1808     * let d = new Date(1979.0, 9.0, 27.0, 13.0, 12.8, 57.0, 444.1);
1809     * console.println(d.toString()); // Sat Oct 27 1979 13:12:57 GMT
1810     * ```
1811     */
1812    public override toString(): string {
1813        let res: String = "Invalid date";
1814        try {
1815            res = this.toDateString() + " " + this.toTimeString();
1816        }
1817        catch (e) {}
1818        return res;
1819    }
1820
1821    /**
1822     * Returns a string representing the specified `Date` object interpreted in UTC.
1823     *
1824     * @see ECMA-262, 21.4.4.41
1825     *
1826     * @returns A string representing the given date.
1827     *
1828     * @example
1829     * ```
1830     * let d = new Date(1979.0, 9.0, 27.0, 13.0, 12.8, 57.0, 444.1);
1831     * console.println(d.toUTCString()); // Sat Oct 27 1979 13:12:57 GMT
1832     * ```
1833     */
1834    public toUTCString(): string {
1835        if (!this.isDateValid()) {
1836            throw new Error("Invalid Date");
1837        }
1838        let s: String = this.toUTCDateString() + " " + this.toUTCTimeString();
1839        return s;
1840    }
1841
1842    private toUTCDateString(): string {
1843        let sb = new StringBuilder();
1844        sb.append(dayNames[this.getUTCDay() as int]);
1845        sb.append(", ");
1846        let d = this.getUTCDate();
1847        if (d < 10) {
1848            sb.append("0");
1849        }
1850        sb.append(d);
1851        sb.append(" ");
1852        sb.append(monthNames[this.getUTCMonth() as int]);
1853        sb.append(" ");
1854        let y = this.getUTCFullYear();
1855        if (y < 0) {
1856            sb.append("-");
1857        }
1858        y = abs(y);
1859        if (y < 1000) {
1860            sb.append("0");
1861        }
1862        if (y < 100) {
1863            sb.append("0");
1864        }
1865        if (y < 10) {
1866            sb.append("0");
1867        }
1868        sb.append(y as long);
1869        return sb.toString();
1870    }
1871
1872    private toUTCTimeString(): string {
1873        let sb = new StringBuilder();
1874        let h = this.getUTCHours();
1875        if (h < 10) {
1876            sb.append("0");
1877        }
1878        sb.append(h);
1879        sb.append(":");
1880        let m = this.getUTCMinutes();
1881        if (m < 10) {
1882            sb.append("0");
1883        }
1884        sb.append(m);
1885        sb.append(":");
1886        let s = this.getUTCSeconds();
1887        if (s < 10) {
1888            sb.append("0");
1889        }
1890        sb.append(s);
1891        sb.append(" GMT")
1892        return sb.toString();
1893    }
1894
1895    /**
1896     * Parses int from substring if indexes are in string
1897     */
1898    private static parseIfContains(dateStr: String, begin: int, end: int): int {
1899        if (dateStr.length >= end && begin < end) {
1900            return Double.parseInt(dateStr.substring(begin, end)) as int;
1901        }
1902        return Date.UNSUCCESSFUL_PARSING;
1903    }
1904
1905    private static readonly UNSUCCESSFUL_PARSING: int = -1;
1906}
1907