Repository: sjbarag/brs
Pull request #277 (2019-07-27 20:43:56)

Implemented roDateTime object #129
The implementation is limited to en-US localization, as brs do not emulate Roku unique localization, I opened an issue to have this addressed later.

PS. Sorry about the mess of my latest 2 pull requests, I made a confusion on my fork, working on the master, and that mixed my implementations. Will make sure it will be cleaner next time.


Patch:
diff --git a/src/brsTypes/components/BrsObjects.ts b/src/brsTypes/components/BrsObjects.ts
index 958239e..60429eb 100644
--- a/src/brsTypes/components/BrsObjects.ts
+++ b/src/brsTypes/components/BrsObjects.ts
@@ -1,5 +1,6 @@
 import { RoAssociativeArray } from "./RoAssociativeArray";
 import { RoArray } from "./RoArray";
+import { RoDateTime } from "./RoDateTime";
 import { Timespan } from "./Timespan";
 import { RoSGNode, createNodeByType } from "./RoSGNode";
 import { RoRegex } from "./RoRegex";
@@ -10,6 +11,7 @@ import { RoString } from "./RoString";
 export const BrsObjects = new Map<string, Function>([
     ["roassociativearray", () => new RoAssociativeArray([])],
     ["roarray", () => new RoArray([])],
+    ["rodatetime", () => new RoDateTime()],
     ["rotimespan", () => new Timespan()],
     ["rosgnode", (nodeType: BrsString) => createNodeByType(nodeType)],
     ["roregex", (expression: BrsString, flags: BrsString) => new RoRegex(expression, flags)],
diff --git a/src/brsTypes/components/RoArray.ts b/src/brsTypes/components/RoArray.ts
index 2a3fbb2..577c5ee 100644
--- a/src/brsTypes/components/RoArray.ts
+++ b/src/brsTypes/components/RoArray.ts
@@ -1,9 +1,10 @@
-import { BrsValue, ValueKind, BrsBoolean, BrsInvalid } from "../BrsType";
-import { BrsType } from "..";
+import { BrsValue, ValueKind, BrsString, BrsBoolean, BrsInvalid } from "../BrsType";
+import { BrsType, isBrsString, isBrsNumber } from "..";
 import { BrsComponent, BrsIterable } from "./BrsComponent";
 import { Callable, StdlibArgument } from "../Callable";
 import { Interpreter } from "../../interpreter";
 import { Int32 } from "../Int32";
+import { RoAssociativeArray } from "./RoAssociativeArray";
 
 export class RoArray extends BrsComponent implements BrsValue, BrsIterable {
     readonly kind = ValueKind.Object;
@@ -22,6 +23,10 @@ export class RoArray extends BrsComponent implements BrsValue, BrsIterable {
             this.count,
             this.clear,
             this.append,
+            this.join,
+            this.sort,
+            this.sortBy,
+            this.reverse,
         ]);
     }
 
@@ -180,4 +185,147 @@ export class RoArray extends BrsComponent implements BrsValue, BrsIterable {
             return BrsInvalid.Instance;
         },
     });
+    // ifArrayJoin
+    private join = new Callable("join", {
+        signature: {
+            args: [new StdlibArgument("separator", ValueKind.String)],
+            returns: ValueKind.String,
+        },
+        impl: (interpreter: Interpreter, separator: BrsString) => {
+            if (
+                this.elements.some(function(element) {
+                    return !(element instanceof BrsString);
+                })
+            ) {
+                interpreter.stderr.write("roArray.Join: Array contains non-string value(s).\n");
+                return new BrsString("");
+            }
+            return new BrsString(this.elements.join(separator.value));
+        },
+    });
+    // ifArraySort
+    private sort = new Callable("sort", {
+        signature: {
+            args: [new StdlibArgument("flags", ValueKind.String, new BrsString(""))],
+            returns: ValueKind.Void,
+        },
+        impl: (interpreter: Interpreter, flags: BrsString) => {
+            if (flags.toString().match(/([^ir])/g) != null) {
+                interpreter.stderr.write("roArray.Sort: Flags contains invalid option(s).\n");
+            } else {
+                this.elements = this.elements.sort(function(a, b) {
+                    var compare = 0;
+                    if (a !== undefined && b !== undefined) {
+                        if (a instanceof RoArray && b instanceof RoAssociativeArray) {
+                            compare = 1;
+                        } else if (a instanceof RoAssociativeArray && b instanceof RoArray) {
+                            compare = -1;
+                        } else if (
+                            !(isBrsString(a) || isBrsNumber(a)) &&
+                            (isBrsString(b) || isBrsNumber(b))
+                        ) {
+                            compare = 1;
+                        } else if (
+                            (isBrsString(a) || isBrsNumber(a)) &&
+                            !(isBrsString(b) || isBrsNumber(b))
+                        ) {
+                            compare = -1;
+                        } else if (
+                            flags.toString().indexOf("i") > -1 &&
+                            isBrsString(a) &&
+                            isBrsString(b)
+                        ) {
+                            compare = a
+                                .toString()
+                                .toLowerCase()
+                                .localeCompare(b.toString().toLowerCase());
+                        } else {
+                            compare = a > b ? 1 : -1;
+                        }
+                    }
+                    if (flags.toString().indexOf("r") > -1) {
+                        compare = -compare;
+                    }
+                    return compare;
+                });
+            }
+            return BrsInvalid.Instance;
+        },
+    });
+
+    private sortBy = new Callable("sortBy", {
+        signature: {
+            args: [
+                new StdlibArgument("fieldName", ValueKind.String),
+                new StdlibArgument("flags", ValueKind.String, new BrsString("")),
+            ],
+            returns: ValueKind.Void,
+        },
+        impl: (interpreter: Interpreter, fieldName: BrsString, flags: BrsString) => {
+            if (flags.toString().match(/([^ir])/g) != null) {
+                interpreter.stderr.write("roArray.SortBy: Flags contains invalid option(s).\n");
+            } else {
+                this.elements = this.elements.sort(function(a, b) {
+                    var compare = 0;
+                    if (a instanceof RoAssociativeArray && b instanceof RoAssociativeArray) {
+                        if (
+                            a.elements.has(fieldName.toString().toLowerCase()) &&
+                            b.elements.has(fieldName.toString().toLowerCase())
+                        ) {
+                            var valueA = a.get(fieldName);
+                            var valueB = b.get(fieldName);
+                            if (
+                                flags.toString().indexOf("i") > -1 &&
+                                isBrsString(valueA) &&
+                                isBrsString(valueB)
+                            ) {
+                                compare = valueA
+                                    .toString()
+                                    .toLowerCase()
+                                    .localeCompare(valueB.toString().toLowerCase());
+                            } else if (valueA !== undefined && valueB !== undefined) {
+                                compare = valueA > valueB ? 1 : -1;
+                            }
+                        }
+                    } else if (a !== undefined && b !== undefined) {
+                        if (a instanceof RoArray && b instanceof RoAssociativeArray) {
+                            compare = 1;
+                        } else if (a instanceof RoAssociativeArray && b instanceof RoArray) {
+                            compare = -1;
+                        } else if (isBrsString(a) && isBrsNumber(b)) {
+                            compare = 1;
+                        } else if (isBrsNumber(a) && isBrsString(b)) {
+                            compare = -1;
+                        } else if (
+                            !(isBrsString(a) || isBrsNumber(a)) &&
+                            (isBrsString(b) || isBrsNumber(b))
+                        ) {
+                            compare = 1;
+                        } else if (
+                            (isBrsString(a) || isBrsNumber(a)) &&
+                            !(isBrsString(b) || isBrsNumber(b))
+                        ) {
+                            compare = -1;
+                        }
+                    }
+                    return compare;
+                });
+                if (flags.toString().indexOf("r") > -1) {
+                    this.elements = this.elements.reverse();
+                }
+            }
+            return BrsInvalid.Instance;
+        },
+    });
+
+    private reverse = new Callable("reverse", {
+        signature: {
+            args: [],
+            returns: ValueKind.Void,
+        },
+        impl: (interpreter: Interpreter, separator: BrsString) => {
+            this.elements = this.elements.reverse();
+            return BrsInvalid.Instance;
+        },
+    });
 }
diff --git a/src/brsTypes/components/RoDateTime.ts b/src/brsTypes/components/RoDateTime.ts
new file mode 100644
index 0000000..ac35f3b
--- /dev/null
+++ b/src/brsTypes/components/RoDateTime.ts
@@ -0,0 +1,390 @@
+import { BrsValue, ValueKind, BrsString, BrsInvalid, BrsBoolean } from "../BrsType";
+import { BrsComponent } from "./BrsComponent";
+import { BrsType } from "..";
+import { Callable, StdlibArgument } from "../Callable";
+import { Interpreter } from "../../interpreter";
+import { Int32 } from "../Int32";
+import * as luxon from "luxon";
+
+export class RoDateTime extends BrsComponent implements BrsValue {
+    readonly kind = ValueKind.Object;
+    private markTime = Date.now();
+
+    constructor() {
+        super("roDateTime");
+        this.registerMethods([
+            this.mark,
+            this.asDateString,
+            this.asDateStringNoParam,
+            this.asSeconds,
+            this.fromISO8601String,
+            this.fromSeconds,
+            this.getDayOfMonth,
+            this.getDayOfWeek,
+            this.getHours,
+            this.getLastDayOfMonth,
+            this.getMilliseconds,
+            this.getMinutes,
+            this.getMonth,
+            this.getSeconds,
+            this.getTimeZoneOffset,
+            this.getWeekday,
+            this.getYear,
+            this.toISOString,
+            this.toLocalTime,
+        ]);
+
+        this.resetTime();
+    }
+
+    resetTime() {
+        this.markTime = Date.now();
+    }
+
+    toString(parent?: BrsType): string {
+        return "<Component: roDateTime>";
+    }
+
+    equalTo(other: BrsType) {
+        return BrsBoolean.False;
+    }
+
+    /** Set the date/time value to the current UTC date and time */
+    private mark = new Callable("mark", {
+        signature: {
+            args: [],
+            returns: ValueKind.Void,
+        },
+        impl: (_: Interpreter) => {
+            this.resetTime();
+            return BrsInvalid.Instance;
+        },
+    });
+
+    /** Returns the date/time as a formatted string */
+    private asDateString = new Callable("asDateString", {
+        signature: {
+            args: [new StdlibArgument("format", ValueKind.String)],
+            returns: ValueKind.String,
+        },
+        impl: (_: Interpreter, format: BrsString) => {
+            var date = new Date(this.markTime);
+            var dateString = "";
+            switch (format.toString()) {
+                case "short-weekday": {
+                    dateString = date
+                        .toLocaleDateString("en-US", {
+                            weekday: "short",
+                            year: "numeric",
+                            month: "long",
+                            day: "numeric",
+                            timeZone: "UTC",
+                        })
+                        .replace(",", "");
+                    break;
+                }
+                case "no-weekday": {
+                    dateString = date.toLocaleDateString("en-US", {
+                        year: "numeric",
+                        month: "long",
+                        day: "numeric",
+                        timeZone: "UTC",
+                    });
+                    break;
+                }
+                case "short-month": {
+                    dateString = date
+                        .toLocaleDateString("en-US", {
+                            weekday: "long",
+                            year: "numeric",
+                            month: "short",
+                            day: "numeric",
+                            timeZone: "UTC",
+                        })
+                        .replace(",", "");
+                    break;
+                }
+                case "short-month-short-weekday": {
+                    dateString = date
+                        .toLocaleDateString("en-US", {
+                            weekday: "short",
+                            year: "numeric",
+                            month: "short",
+                            day: "numeric",
+                            timeZone: "UTC",
+                        })
+                        .replace(",", "");
+                    break;
+                }
+                case "short-month-no-weekday": {
+                    dateString = date.toLocaleDateString("en-US", {
+                        year: "numeric",
+                        month: "short",
+                        day: "numeric",
+                        timeZone: "UTC",
+                    });
+                    break;
+                }
+                case "short-date": {
+                    var dateArray = date
+                        .toLocaleDateString("en-US", {
+                            year: "2-digit",
+                            month: "numeric",
+                            day: "numeric",
+                            timeZone: "UTC",
+                        })
+                        .split("/");
+                    dateString =
+                        dateArray[0] + "/" + dateArray[1] + "/" + parseInt(dateArray[2]).toString();
+                    break;
+                }
+                case "short-date-dashes": {
+                    var dateArray = date
+                        .toLocaleDateString("en-US", {
+                            year: "2-digit",
+                            month: "numeric",
+                            day: "numeric",
+                            timeZone: "UTC",
+                        })
+                        .split("/");
+                    dateString =
+                        dateArray[0] + "-" + dateArray[1] + "-" + parseInt(dateArray[2]).toString();
+                    break;
+                }
+                default: {
+                    // default format: long-date
+                    dateString = date
+                        .toLocaleDateString("en-US", {
+                            weekday: "long",
+                            year: "numeric",
+                            month: "long",
+                            day: "numeric",
+                            timeZone: "UTC",
+                        })
+                        .replace(",", "");
+                    break;
+                }
+            }
+            return new BrsString(dateString);
+        },
+    });
+
+    /** Same as AsDateString("long-date") */
+    private asDateStringNoParam = new Callable("asDateStringNoParam", {
+        signature: {
+            args: [],
+            returns: ValueKind.String,
+        },
+        impl: (_: Interpreter) => {
+            var date = new Date(this.markTime);
+            var options = {
+                weekday: "long",
+                year: "numeric",
+                month: "long",
+                day: "numeric",
+                timeZone: "UTC",
+            };
+            return new BrsString(date.toLocaleDateString("en-US", options).replace(",", ""));
+        },
+    });
+
+    /** Returns the date/time as the number of seconds from the Unix epoch (00:00:00 1/1/1970 GMT) */
+    private asSeconds = new Callable("asSeconds", {
+        signature: {
+            args: [],
+            returns: ValueKind.Int32,
+        },
+        impl: (_: Interpreter) => {
+            return new Int32(this.markTime / 1000);
+        },
+    });
+
+    /** Set the date/time using a string in the ISO 8601 format */
+    private fromISO8601String = new Callable("fromISO8601String", {
+        signature: {
+            args: [new StdlibArgument("dateString", ValueKind.String)],
+            returns: ValueKind.Void,
+        },
+        impl: (_: Interpreter, dateString: BrsString) => {
+            let dateToParse = luxon.DateTime.fromISO(dateString.value, { zone: "utc" });
+            if (dateToParse.isValid) {
+                this.markTime = Date.parse(dateToParse.toISO());
+            } else {
+                this.markTime = 0;
+            }
+            return BrsInvalid.Instance;
+        },
+    });
+
+    /** Set the date/time value using the number of seconds from the Unix epoch */
+    private fromSeconds = new Callable("fromSeconds", {
+        signature: {
+            args: [new StdlibArgument("numSeconds", ValueKind.Int32)],
+            returns: ValueKind.Void,
+        },
+        impl: (_: Interpreter, numSeconds: Int32) => {
+            this.markTime = numSeconds.getValue() * 1000;
+            return BrsInvalid.Instance;
+        },
+    });
+
+    /** Returns the date/time value's day of the month */
+    private getDayOfMonth = new Callable("getDayOfMonth", {
+        signature: {
+            args: [],
+            returns: ValueKind.Int32,
+        },
+        impl: (_: Interpreter) => {
+            var date = new Date(this.markTime);
+            return new Int32(date.getUTCDate());
+        },
+    });
+
+    /** Returns the date/time value's day of the week */
+    private getDayOfWeek = new Callable("getDayOfWeek", {
+        signature: {
+            args: [],
+            returns: ValueKind.Int32,
+        },
+        impl: (_: Interpreter) => {
+            var date = new Date(this.markTime);
+            return new Int32(date.getUTCDay());
+        },
+    });
+
+    /** Returns the date/time value's hour within the day */
+    private getHours = new Callable("getHours", {
+        signature: {
+            args: [],
+            returns: ValueKind.Int32,
+        },
+        impl: (_: Interpreter) => {
+            var date = new Date(this.markTime);
+            return new Int32(date.getUTCHours());
+        },
+    });
+
+    /** Returns the date/time value's last day of the month */
+    private getLastDayOfMonth = new Callable("getLastDayOfMonth", {
+        signature: {
+            args: [],
+            returns: ValueKind.Int32,
+        },
+        impl: (_: Interpreter) => {
+            var date = new Date(this.markTime);
+            return new Int32(
+                new Date(date.getUTCFullYear(), date.getUTCMonth() + 1, 0).getUTCDate()
+            );
+        },
+    });
+
+    /** Returns the date/time value's millisecond within the second */
+    private getMilliseconds = new Callable("getMilliseconds", {
+        signature: {
+            args: [],
+            returns: ValueKind.Int32,
+        },
+        impl: (_: Interpreter) => {
+            var date = new Date(this.markTime);
+            return new Int32(date.getUTCMilliseconds());
+        },
+    });
+
+    /** Returns the date/time value's minute within the hour */
+    private getMinutes = new Callable("getMinutes", {
+        signature: {
+            args: [],
+            returns: ValueKind.Int32,
+        },
+        impl: (_: Interpreter) => {
+            var date = new Date(this.markTime);
+            return new Int32(date.getUTCMinutes());
+        },
+    });
+
+    /** Returns the date/time value's month */
+    private getMonth = new Callable("getMonth", {
+        signature: {
+            args: [],
+            returns: ValueKind.Int32,
+        },
+        impl: (_: Interpreter) => {
+            var date = new Date(this.markTime);
+            return new Int32(date.getUTCMonth() + 1);
+        },
+    });
+
+    /** Returns the date/time value's second within the minute */
+    private getSeconds = new Callable("getSeconds", {
+        signature: {
+            args: [],
+            returns: ValueKind.Int32,
+        },
+        impl: (_: Interpreter) => {
+            var date = new Date(this.markTime);
+            return new Int32(date.getUTCSeconds());
+        },
+    });
+
+    /** Returns the offset in minutes from the system time zone to UTC */
+    private getTimeZoneOffset = new Callable("getTimeZoneOffset", {
+        signature: {
+            args: [],
+            returns: ValueKind.Int32,
+        },
+        impl: (_: Interpreter) => {
+            var date = new Date(this.markTime);
+            return new Int32(date.getTimezoneOffset());
+        },
+    });
+
+    /** Returns the day of the week */
+    private getWeekday = new Callable("getWeekday", {
+        signature: {
+            args: [],
+            returns: ValueKind.String,
+        },
+        impl: (_: Interpreter) => {
+            var date = new Date(this.markTime);
+            return new BrsString(
+                date.toLocaleDateString("en-US", { weekday: "long", timeZone: "UTC" })
+            );
+        },
+    });
+
+    /** Returns the date/time value's year */
+    private getYear = new Callable("getYear", {
+        signature: {
+            args: [],
+            returns: ValueKind.Int32,
+        },
+        impl: (_: Interpreter) => {
+            var date = new Date(this.markTime);
+            return new Int32(date.getUTCFullYear());
+        },
+    });
+
+    /** Return an ISO 8601 representation of the date/time value */
+    private toISOString = new Callable("toISOString", {
+        signature: {
+            args: [],
+            returns: ValueKind.String,
+        },
+        impl: (_: Interpreter) => {
+            var date = new Date(this.markTime);
+            return new BrsString(date.toISOString().split(".")[0] + "Z");
+        },
+    });
+
+    /** Offsets the date/time value from an assumed UTC date/time to a local date/time using the system time zone setting. */
+    private toLocalTime = new Callable("toLocalTime", {
+        signature: {
+            args: [],
+            returns: ValueKind.Void,
+        },
+        impl: (_: Interpreter) => {
+            this.markTime -= new Date(this.markTime).getTimezoneOffset() * 60 * 1000;
+            return BrsInvalid.Instance;
+        },
+    });
+}
diff --git a/src/brsTypes/index.ts b/src/brsTypes/index.ts
index 2a341bf..2d9ab19 100644
--- a/src/brsTypes/index.ts
+++ b/src/brsTypes/index.ts
@@ -23,6 +23,7 @@ export * from "./Int64";
 export * from "./Float";
 export * from "./Double";
 export * from "./components/RoArray";
+export * from "./components/RoDateTime";
 export * from "./components/RoAssociativeArray";
 export * from "./components/Timespan";
 export * from "./components/RoSGNode";
diff --git a/src/lexer/Token.ts b/src/lexer/Token.ts
index d3d4a42..f9993f9 100644
--- a/src/lexer/Token.ts
+++ b/src/lexer/Token.ts
@@ -53,8 +53,8 @@ export interface Identifier extends Token {
 /**
  * Determines whether or not `obj` is a `Token`.
  * @param obj the object to check for `Token`-ness
- * @returns `true` is `obj` is a `Token`, otherwise `false`
+ * @returns `true` if `obj` is a `Token`, otherwise `false`
  */
 export function isToken(obj: Record<string, any>): obj is Token {
-    return obj.kind && obj.text && obj.location;
+    return !!(obj.kind && obj.text && obj.location);
 }
diff --git a/src/stdlib/String.ts b/src/stdlib/String.ts
index 1a94e4a..1895fa2 100644
--- a/src/stdlib/String.ts
+++ b/src/stdlib/String.ts
@@ -182,6 +182,36 @@ export const StrI = new Callable("StrI", {
     },
 });
 
+/**
+ * Returns a string composed of n copies of the second argument concatenated together.
+ */
+export const STRING = new Callable("String", {
+    //Defined in uppercase to avoid conflict with javascript String class
+    signature: {
+        args: [
+            new StdlibArgument("n", ValueKind.Int32),
+            new StdlibArgument("str", ValueKind.String),
+        ],
+        returns: ValueKind.String,
+    },
+    impl: (interpreter: Interpreter, n: Int32, str: BrsString): BrsString => {
+        return new BrsString(str.value.repeat(n.getValue()));
+    },
+});
+
+/**
+ * Returns a string composed of n copies of the character whose Unicode value is the second argument.
+ */
+export const StringI = new Callable("StringI", {
+    signature: {
+        args: [new StdlibArgument("n", ValueKind.Int32), new StdlibArgument("ch", ValueKind.Int32)],
+        returns: ValueKind.String,
+    },
+    impl: (interpreter: Interpreter, n: Int32, ch: Int32): BrsString => {
+        return new BrsString(String.fromCharCode(ch.getValue()).repeat(n.getValue()));
+    },
+});
+
 /**
  * Return a string from another string replacing instances of {index} with the
  * respective parameter.


Test patch:
diff --git a/test/brsTypes/components/RoArray.test.js b/test/brsTypes/components/RoArray.test.js
index aabe110..76a62f4 100644
--- a/test/brsTypes/components/RoArray.test.js
+++ b/test/brsTypes/components/RoArray.test.js
@@ -1,5 +1,5 @@
 const brs = require("brs");
-const { RoArray, BrsBoolean, BrsString, Int32, BrsInvalid } = brs.types;
+const { RoArray, RoAssociativeArray, BrsBoolean, BrsString, Int32, BrsInvalid } = brs.types;
 const BrsError = require("../../../lib/Error");
 const { Interpreter } = require("../../../lib/interpreter");
 
@@ -262,5 +262,555 @@ describe("RoArray", () => {
                 expect(src.elements).toEqual([a, b, c, d, e, f]);
             });
         });
+
+        describe("join", () => {
+            it("joins the string elements of the current array", () => {
+                let a = new BrsString("a");
+                let b = new BrsString("b");
+                let c = new BrsString("c");
+                let src = new RoArray([a, b, c]);
+
+                let join = src.getMethod("join");
+                expect(join).toBeTruthy();
+                expect(join.call(interpreter, new BrsString(","))).toEqual(new BrsString("a,b,c"));
+            });
+
+            it("return empty string when any element is a non string value", () => {
+                let a = new BrsString("a");
+                let b = new Int32(1);
+                let c = new BrsString("c");
+                let src = new RoArray([a, b, c]);
+
+                let join = src.getMethod("join");
+                expect(join).toBeTruthy();
+                expect(join.call(interpreter, new BrsString(","))).toEqual(new BrsString(""));
+            });
+            it("return empty string when any element is invalid", () => {
+                let a = new BrsString("a");
+                let b = new BrsString("b");
+                let c = BrsInvalid.Instance;
+                let src = new RoArray([a, b, c]);
+
+                let join = src.getMethod("join");
+                expect(join).toBeTruthy();
+                expect(join.call(interpreter, new BrsString(","))).toEqual(new BrsString(""));
+            });
+        });
+
+        describe("sort", () => {
+            it("sorts the elements of the array with no flags", () => {
+                let a = new BrsString("a");
+                let b = new BrsString("B");
+                let c = new BrsString("c");
+                let i3 = new Int32(3);
+                let i1 = new Int32(1);
+                let i2 = new Int32(2);
+                let src = new RoArray([a, b, c, i3, i1, i2]);
+
+                let sort = src.getMethod("sort");
+                expect(sort).toBeTruthy();
+                expect(sort.call(interpreter)).toBe(BrsInvalid.Instance);
+                expect(src.elements).toEqual([i1, i2, i3, b, a, c]);
+            });
+
+            it("sorts the elements of the array with empty flags", () => {
+                let a = new BrsString("a");
+                let b = new BrsString("B");
+                let c = new BrsString("c");
+                let i3 = new Int32(3);
+                let i1 = new Int32(1);
+                let i2 = new Int32(2);
+                let src = new RoArray([a, b, c, i3, i1, i2]);
+
+                let sort = src.getMethod("sort");
+                expect(sort).toBeTruthy();
+                expect(sort.call(interpreter, new BrsString(""))).toBe(BrsInvalid.Instance);
+                expect(src.elements).toEqual([i1, i2, i3, b, a, c]);
+            });
+
+            it("sorts the elements of the array with flags = i", () => {
+                let a = new BrsString("a");
+                let b = new BrsString("B");
+                let c = new BrsString("c");
+                let i3 = new Int32(3);
+                let i1 = new Int32(1);
+                let i2 = new Int32(2);
+                let src = new RoArray([a, b, c, i3, i1, i2]);
+
+                let sort = src.getMethod("sort");
+                expect(sort).toBeTruthy();
+                expect(sort.call(interpreter, new BrsString("i"))).toBe(BrsInvalid.Instance);
+                expect(src.elements).toEqual([i1, i2, i3, a, b, c]);
+            });
+
+            it("sorts the elements of the array with flags = ii", () => {
+                let a = new BrsString("a");
+                let b = new BrsString("B");
+                let c = new BrsString("c");
+                let i3 = new Int32(3);
+                let i1 = new Int32(1);
+                let i2 = new Int32(2);
+                let src = new RoArray([a, b, c, i3, i1, i2]);
+
+                let sort = src.getMethod("sort");
+                expect(sort).toBeTruthy();
+                expect(sort.call(interpreter, new BrsString("ii"))).toBe(BrsInvalid.Instance);
+                expect(src.elements).toEqual([i1, i2, i3, a, b, c]);
+            });
+
+            it("sorts the elements of the array with flag = r", () => {
+                let a = new BrsString("a");
+                let b = new BrsString("B");
+                let c = new BrsString("c");
+                let i3 = new Int32(3);
+                let i1 = new Int32(1);
+                let i2 = new Int32(2);
+                let src = new RoArray([a, b, c, i3, i1, i2]);
+
+                let sort = src.getMethod("sort");
+                expect(sort).toBeTruthy();
+                expect(sort.call(interpreter, new BrsString("r"))).toBe(BrsInvalid.Instance);
+                expect(src.elements).toEqual([c, a, b, i3, i2, i1]);
+            });
+
+            it("sorts the elements of the array with flag = ir", () => {
+                let a = new BrsString("a");
+                let b = new BrsString("B");
+                let c = new BrsString("c");
+                let i3 = new Int32(3);
+                let i1 = new Int32(1);
+                let i2 = new Int32(2);
+                let src = new RoArray([a, b, c, i3, i1, i2]);
+
+                let sort = src.getMethod("sort");
+                expect(sort).toBeTruthy();
+                expect(sort.call(interpreter, new BrsString("ir"))).toBe(BrsInvalid.Instance);
+                expect(src.elements).toEqual([c, b, a, i3, i2, i1]);
+            });
+
+            it("sorts the elements of the array with flag = ri", () => {
+                let a = new BrsString("a");
+                let b = new BrsString("B");
+                let c = new BrsString("c");
+                let i3 = new Int32(3);
+                let i1 = new Int32(1);
+                let i2 = new Int32(2);
+                let src = new RoArray([a, b, c, i3, i1, i2]);
+
+                let sort = src.getMethod("sort");
+                expect(sort).toBeTruthy();
+                expect(sort.call(interpreter, new BrsString("ri"))).toBe(BrsInvalid.Instance);
+                expect(src.elements).toEqual([c, b, a, i3, i2, i1]);
+            });
+
+            it("sorts the elements of the array with flag = rir", () => {
+                let a = new BrsString("a");
+                let b = new BrsString("B");
+                let c = new BrsString("c");
+                let i3 = new Int32(3);
+                let i1 = new Int32(1);
+                let i2 = new Int32(2);
+                let src = new RoArray([a, b, c, i3, i1, i2]);
+
+                let sort = src.getMethod("sort");
+                expect(sort).toBeTruthy();
+                expect(sort.call(interpreter, new BrsString("rir"))).toBe(BrsInvalid.Instance);
+                expect(src.elements).toEqual([c, b, a, i3, i2, i1]);
+            });
+
+            it("sorts the elements of the array with array elements and no flags", () => {
+                let a = new BrsString("a");
+                let b = new BrsString("B");
+                let c = new BrsString("c");
+                let ar = new RoArray([]);
+                let aa = new RoAssociativeArray([]);
+                let i3 = new Int32(3);
+                let i1 = new Int32(1);
+                let i2 = new Int32(2);
+                let src = new RoArray([a, b, c, ar, aa, i3, i1, i2]);
+
+                let sort = src.getMethod("sort");
+                expect(sort).toBeTruthy();
+                expect(sort.call(interpreter)).toBe(BrsInvalid.Instance);
+                expect(src.elements).toEqual([i1, i2, i3, b, a, c, aa, ar]);
+            });
+
+            it("sorts the elements of the array with array elements and flag = ir", () => {
+                let a = new BrsString("a");
+                let b = new BrsString("B");
+                let c = new BrsString("c");
+                let ar = new RoArray([]);
+                let aa = new RoAssociativeArray([]);
+                let i3 = new Int32(3);
+                let i1 = new Int32(1);
+                let i2 = new Int32(2);
+                let src = new RoArray([a, b, c, ar, aa, i3, i1, i2]);
+
+                let sort = src.getMethod("sort");
+                expect(sort).toBeTruthy();
+                expect(sort.call(interpreter, new BrsString("ir"))).toBe(BrsInvalid.Instance);
+                expect(src.elements).toEqual([ar, aa, c, b, a, i3, i2, i1]);
+            });
+
+            it("sorts the elements of the array with invalid flag", () => {
+                let a = new BrsString("a");
+                let b = new BrsString("B");
+                let c = new BrsString("c");
+                let i3 = new Int32(3);
+                let i1 = new Int32(1);
+                let i2 = new Int32(2);
+                let src = new RoArray([a, b, c, i3, i1, i2]);
+
+                let sort = src.getMethod("sort");
+                expect(sort).toBeTruthy();
+                expect(sort.call(interpreter, new BrsString("ari"))).toBe(BrsInvalid.Instance);
+                expect(src.elements).toEqual([a, b, c, i3, i1, i2]);
+            });
+        });
+
+        describe("sortBy", () => {
+            it("sorts the array based on AA case matching valid string field with no flags", () => {
+                let a1 = new RoAssociativeArray([
+                    { name: new BrsString("id"), value: new Int32(1) },
+                    { name: new BrsString("name"), value: new BrsString("Carol") },
+                ]);
+                let a3 = new RoAssociativeArray([
+                    { name: new BrsString("id"), value: new Int32(3) },
+                    { name: new BrsString("name"), value: new BrsString("Betty") },
+                ]);
+                let a2 = new RoAssociativeArray([
+                    { name: new BrsString("id"), value: new Int32(2) },
+                    { name: new BrsString("name"), value: new BrsString("anne") },
+                ]);
+                let a = new BrsString("a");
+                let b = new BrsString("B");
+                let ar = new RoArray([]);
+                let i2 = new Int32(2);
+                let i1 = new Int32(1);
+                let src = new RoArray([a1, a3, a2, a, b, ar, i2, i1]);
+
+                let sortBy = src.getMethod("sortBy");
+                expect(sortBy).toBeTruthy();
+                expect(sortBy.call(interpreter, new BrsString("name"))).toBe(BrsInvalid.Instance);
+                expect(src.elements).toEqual([i2, i1, a, b, a3, a1, a2, ar]);
+            });
+
+            it("sorts the array based on AA non-matching case valid field with no flags", () => {
+                let a1 = new RoAssociativeArray([
+                    { name: new BrsString("id"), value: new Int32(1) },
+                    { name: new BrsString("name"), value: new BrsString("Carol") },
+                ]);
+                let a3 = new RoAssociativeArray([
+                    { name: new BrsString("id"), value: new Int32(3) },
+                    { name: new BrsString("name"), value: new BrsString("Betty") },
+                ]);
+                let a2 = new RoAssociativeArray([
+                    { name: new BrsString("id"), value: new Int32(2) },
+                    { name: new BrsString("name"), value: new BrsString("anne") },
+                ]);
+                let a = new BrsString("a");
+                let b = new BrsString("B");
+                let ar = new RoArray([]);
+                let i2 = new Int32(2);
+                let i1 = new Int32(1);
+                let src = new RoArray([a1, a3, a2, a, b, ar, i2, i1]);
+
+                let sortBy = src.getMethod("sortBy");
+                expect(sortBy).toBeTruthy();
+                expect(sortBy.call(interpreter, new BrsString("nAme"))).toBe(BrsInvalid.Instance);
+                expect(src.elements).toEqual([i2, i1, a, b, a3, a1, a2, ar]);
+            });
+
+            it("sorts the array based on AA valid string field with flags = i", () => {
+                let a1 = new RoAssociativeArray([
+                    { name: new BrsString("id"), value: new Int32(1) },
+                    { name: new BrsString("name"), value: new BrsString("Carol") },
+                ]);
+                let a3 = new RoAssociativeArray([
+                    { name: new BrsString("id"), value: new Int32(3) },
+                    { name: new BrsString("name"), value: new BrsString("Betty") },
+                ]);
+                let a2 = new RoAssociativeArray([
+                    { name: new BrsString("id"), value: new Int32(2) },
+                    { name: new BrsString("name"), value: new BrsString("anne") },
+                ]);
+                let a = new BrsString("a");
+                let b = new BrsString("B");
+                let ar = new RoArray([]);
+                let i2 = new Int32(2);
+                let i1 = new Int32(1);
+                let src = new RoArray([a1, a3, a2, a, b, ar, i2, i1]);
+
+                let sortBy = src.getMethod("sortBy");
+                expect(sortBy).toBeTruthy();
+                expect(sortBy.call(interpreter, new BrsString("name"), new BrsString("i"))).toBe(
+                    BrsInvalid.Instance
+                );
+                expect(src.elements).toEqual([i2, i1, a, b, a2, a3, a1, ar]);
+            });
+
+            it("sorts the array based on AA valid string field with flags = r", () => {
+                let a1 = new RoAssociativeArray([
+                    { name: new BrsString("id"), value: new Int32(1) },
+                    { name: new BrsString("name"), value: new BrsString("Carol") },
+                ]);
+                let a3 = new RoAssociativeArray([
+                    { name: new BrsString("id"), value: new Int32(3) },
+                    { name: new BrsString("name"), value: new BrsString("Betty") },
+                ]);
+                let a2 = new RoAssociativeArray([
+                    { name: new BrsString("id"), value: new Int32(2) },
+                    { name: new BrsString("name"), value: new BrsString("anne") },
+                ]);
+                let a = new BrsString("a");
+                let b = new BrsString("B");
+                let ar = new RoArray([]);
+                let i2 = new Int32(2);
+                let i1 = new Int32(1);
+                let src = new RoArray([a1, a3, a2, a, b, ar, i2, i1]);
+
+                let sortBy = src.getMethod("sortBy");
+                expect(sortBy).toBeTruthy();
+                expect(sortBy.call(interpreter, new BrsString("name"), new BrsString("r"))).toBe(
+                    BrsInvalid.Instance
+                );
+                expect(src.elements).toEqual([ar, a2, a1, a3, b, a, i1, i2]);
+            });
+
+            it("sorts the array based on AA valid string field with flags = ir", () => {
+                let a1 = new RoAssociativeArray([
+                    { name: new BrsString("id"), value: new Int32(1) },
+                    { name: new BrsString("name"), value: new BrsString("Carol") },
+                ]);
+                let a3 = new RoAssociativeArray([
+                    { name: new BrsString("id"), value: new Int32(3) },
+                    { name: new BrsString("name"), value: new BrsString("Betty") },
+                ]);
+                let a2 = new RoAssociativeArray([
+                    { name: new BrsString("id"), value: new Int32(2) },
+                    { name: new BrsString("name"), value: new BrsString("anne") },
+                ]);
+                let a = new BrsString("a");
+                let b = new BrsString("B");
+                let ar = new RoArray([]);
+                let i2 = new Int32(2);
+                let i1 = new Int32(1);
+                let src = new RoArray([a1, a3, a2, a, b, ar, i2, i1]);
+
+                let sortBy = src.getMethod("sortBy");
+                expect(sortBy).toBeTruthy();
+                expect(sortBy.call(interpreter, new BrsString("name"), new BrsString("ir"))).toBe(
+                    BrsInvalid.Instance
+                );
+                expect(src.elements).toEqual([ar, a1, a3, a2, b, a, i1, i2]);
+            });
+
+            it("sorts the array based on AA valid string field with flags = riir", () => {
+                let a1 = new RoAssociativeArray([
+                    { name: new BrsString("id"), value: new Int32(1) },
+                    { name: new BrsString("name"), value: new BrsString("Carol") },
+                ]);
+                let a3 = new RoAssociativeArray([
+                    { name: new BrsString("id"), value: new Int32(3) },
+                    { name: new BrsString("name"), value: new BrsString("Betty") },
+                ]);
+                let a2 = new RoAssociativeArray([
+                    { name: new BrsString("id"), value: new Int32(2) },
+                    { name: new BrsString("name"), value: new BrsString("anne") },
+                ]);
+                let a = new BrsString("a");
+                let b = new BrsString("B");
+                let ar = new RoArray([]);
+                let i2 = new Int32(2);
+                let i1 = new Int32(1);
+                let src = new RoArray([a1, a3, a2, a, b, ar, i2, i1]);
+
+                let sortBy = src.getMethod("sortBy");
+                expect(sortBy).toBeTruthy();
+                expect(sortBy.call(interpreter, new BrsString("name"), new BrsString("riir"))).toBe(
+                    BrsInvalid.Instance
+                );
+                expect(src.elements).toEqual([ar, a1, a3, a2, b, a, i1, i2]);
+            });
+
+            it("sorts the array based on AA valid numeric field with no flags", () => {
+                let a3 = new RoAssociativeArray([
+                    { name: new BrsString("id"), value: new Int32(3) },
+                    { name: new BrsString("name"), value: new BrsString("Betty") },
+                ]);
+                let a1 = new RoAssociativeArray([
+                    { name: new BrsString("id"), value: new Int32(1) },
+                    { name: new BrsString("name"), value: new BrsString("Carol") },
+                ]);
+                let a2 = new RoAssociativeArray([
+                    { name: new BrsString("id"), value: new Int32(2) },
+                    { name: new BrsString("name"), value: new BrsString("anne") },
+                ]);
+                let a = new BrsString("a");
+                let b = new BrsString("B");
+                let ar = new RoArray([]);
+                let i2 = new Int32(2);
+                let i1 = new Int32(1);
+                let src = new RoArray([a3, a1, a2, a, b, ar, i2, i1]);
+
+                let sortBy = src.getMethod("sortBy");
+                expect(sortBy).toBeTruthy();
+                expect(sortBy.call(interpreter, new BrsString("id"))).toBe(BrsInvalid.Instance);
+                expect(src.elements).toEqual([i2, i1, a, b, a1, a2, a3, ar]);
+            });
+
+            it("sorts the array based on AA valid numeric field with flags = r", () => {
+                let a1 = new RoAssociativeArray([
+                    { name: new BrsString("id"), value: new Int32(1) },
+                    { name: new BrsString("name"), value: new BrsString("Carol") },
+                ]);
+                let a3 = new RoAssociativeArray([
+                    { name: new BrsString("id"), value: new Int32(3) },
+                    { name: new BrsString("name"), value: new BrsString("Betty") },
+                ]);
+                let a2 = new RoAssociativeArray([
+                    { name: new BrsString("id"), value: new Int32(2) },
+                    { name: new BrsString("name"), value: new BrsString("anne") },
+                ]);
+                let a = new BrsString("a");
+                let b = new BrsString("B");
+                let ar = new RoArray([]);
+                let i2 = new Int32(2);
+                let i1 = new Int32(1);
+                let src = new RoArray([a1, a3, a2, a, b, ar, i2, i1]);
+
+                let sortBy = src.getMethod("sortBy");
+                expect(sortBy).toBeTruthy();
+                expect(sortBy.call(interpreter, new BrsString("ID"), new BrsString("r"))).toBe(
+                    BrsInvalid.Instance
+                );
+                expect(src.elements).toEqual([ar, a3, a2, a1, b, a, i1, i2]);
+            });
+
+            it("sorts the array based on AA inexistent field with no flags", () => {
+                let a3 = new RoAssociativeArray([
+                    { name: new BrsString("id"), value: new Int32(3) },
+                    { name: new BrsString("name"), value: new BrsString("Betty") },
+                ]);
+                let a1 = new RoAssociativeArray([
+                    { name: new BrsString("id"), value: new Int32(1) },
+                    { name: new BrsString("name"), value: new BrsString("Carol") },
+                ]);
+                let a2 = new RoAssociativeArray([
+                    { name: new BrsString("id"), value: new Int32(2) },
+                    { name: new BrsString("name"), value: new BrsString("anne") },
+                ]);
+                let a = new BrsString("a");
+                let b = new BrsString("B");
+                let ar = new RoArray([]);
+                let i2 = new Int32(2);
+                let i1 = new Int32(1);
+                let src = new RoArray([a3, a1, a2, a, b, ar, i2, i1]);
+
+                let sortBy = src.getMethod("sortBy");
+                expect(sortBy).toBeTruthy();
+                expect(sortBy.call(interpreter, new BrsString("xyz"))).toBe(BrsInvalid.Instance);
+                expect(src.elements).toEqual([i2, i1, a, b, a3, a1, a2, ar]);
+            });
+
+            it("sorts the array based on AA empty field with no flags", () => {
+                let a3 = new RoAssociativeArray([
+                    { name: new BrsString("id"), value: new Int32(3) },
+                    { name: new BrsString("name"), value: new BrsString("Betty") },
+                ]);
+                let a1 = new RoAssociativeArray([
+                    { name: new BrsString("id"), value: new Int32(1) },
+                    { name: new BrsString("name"), value: new BrsString("Carol") },
+                ]);
+                let a2 = new RoAssociativeArray([
+                    { name: new BrsString("id"), value: new Int32(2) },
+                    { name: new BrsString("name"), value: new BrsString("anne") },
+                ]);
+                let a = new BrsString("a");
+                let b = new BrsString("B");
+                let ar = new RoArray([]);
+                let i2 = new Int32(2);
+                let i1 = new Int32(1);
+                let src = new RoArray([a3, a1, a2, a, b, ar, i2, i1]);
+
+                let sortBy = src.getMethod("sortBy");
+                expect(sortBy).toBeTruthy();
+                expect(sortBy.call(interpreter, new BrsString(""))).toBe(BrsInvalid.Instance);
+                expect(src.elements).toEqual([i2, i1, a, b, a3, a1, a2, ar]);
+            });
+
+            it("sorts the array based on AA inexistent field with flags = r", () => {
+                let a1 = new RoAssociativeArray([
+                    { name: new BrsString("id"), value: new Int32(1) },
+                    { name: new BrsString("name"), value: new BrsString("Carol") },
+                ]);
+                let a3 = new RoAssociativeArray([
+                    { name: new BrsString("id"), value: new Int32(3) },
+                    { name: new BrsString("name"), value: new BrsString("Betty") },
+                ]);
+                let a2 = new RoAssociativeArray([
+                    { name: new BrsString("id"), value: new Int32(2) },
+                    { name: new BrsString("name"), value: new BrsString("anne") },
+                ]);
+                let a = new BrsString("a");
+                let b = new BrsString("B");
+                let ar = new RoArray([]);
+                let i2 = new Int32(2);
+                let i1 = new Int32(1);
+                let src = new RoArray([a3, a1, a2, a, b, ar, i2, i1]);
+
+                let sortBy = src.getMethod("sortBy");
+                expect(sortBy).toBeTruthy();
+                expect(sortBy.call(interpreter, new BrsString("xyz"), new BrsString("r"))).toBe(
+                    BrsInvalid.Instance
+                );
+                expect(src.elements).toEqual([ar, a2, a1, a3, b, a, i1, i2]);
+            });
+
+            it("sorts the array based on AA valid field with invalid flags", () => {
+                let a1 = new RoAssociativeArray([
+                    { name: new BrsString("id"), value: new Int32(1) },
+                    { name: new BrsString("name"), value: new BrsString("Carol") },
+                ]);
+                let a3 = new RoAssociativeArray([
+                    { name: new BrsString("id"), value: new Int32(3) },
+                    { name: new BrsString("name"), value: new BrsString("Betty") },
+                ]);
+                let a2 = new RoAssociativeArray([
+                    { name: new BrsString("id"), value: new Int32(2) },
+                    { name: new BrsString("name"), value: new BrsString("anne") },
+                ]);
+                let a = new BrsString("a");
+                let b = new BrsString("B");
+                let ar = new RoArray([]);
+                let i2 = new Int32(2);
+                let i1 = new Int32(1);
+                let src = new RoArray([a1, a3, a2, a, b, ar, i2, i1]);
+
+                let sortBy = src.getMethod("sortBy");
+                expect(sortBy).toBeTruthy();
+                expect(sortBy.call(interpreter, new BrsString("id"), new BrsString("ia"))).toBe(
+                    BrsInvalid.Instance
+                );
+                expect(src.elements).toEqual([a1, a3, a2, a, b, ar, i2, i1]);
+            });
+        });
+
+        describe("reverse", () => {
+            it("reverts the sequence of array elements", () => {
+                let a = new BrsString("a");
+                let b = new BrsString("b");
+                let c = new BrsString("c");
+                let i1 = new Int32(1);
+                let d = new BrsString("d");
+                let e = new BrsString("e");
+                let src = new RoArray([a, b, c, i1, d, e]);
+
+                let reverse = src.getMethod("reverse");
+                expect(reverse).toBeTruthy();
+                expect(reverse.call(interpreter)).toBe(BrsInvalid.Instance);
+                expect(src.elements).toEqual([e, d, i1, c, b, a]);
+            });
+        });
     });
 });
diff --git a/test/brsTypes/components/RoDateTime.test.js b/test/brsTypes/components/RoDateTime.test.js
new file mode 100644
index 0000000..be83105
--- /dev/null
+++ b/test/brsTypes/components/RoDateTime.test.js
@@ -0,0 +1,299 @@
+const brs = require("brs");
+const { RoDateTime, Int32, BrsString, BrsInvalid } = brs.types;
+const { Interpreter } = require("../../../lib/interpreter");
+const lolex = require("lolex");
+
+describe("RoDateTime", () => {
+    let dt;
+    let clock;
+
+    beforeEach(() => {
+        clock = lolex.install({ now: 1230768000123 });
+        dt = new RoDateTime();
+    });
+
+    afterAll(() => {
+        clock.uninstall();
+    });
+
+    describe("stringification", () => {
+        it("inits a new brs roDateTime", () => {
+            expect(dt.toString()).toEqual("<Component: roDateTime>");
+        });
+    });
+
+    describe("markTime", () => {
+        it("inits a new timespan with the current time", () => {
+            expect(dt.markTime).toEqual(clock.now);
+        });
+    });
+
+    describe("methods", () => {
+        let interpreter;
+
+        beforeEach(() => {
+            interpreter = new Interpreter();
+        });
+
+        describe("mark", () => {
+            it("resets mark time to current time", () => {
+                let mark = dt.getMethod("mark");
+                clock.tick(10000);
+
+                let result = mark.call(interpreter);
+                expect(mark).toBeTruthy();
+                expect(dt.markTime).toEqual(clock.now);
+                expect(result).toBe(BrsInvalid.Instance);
+            });
+        });
+
+        describe("asDateString", () => {
+            it("returns date string with empty param", () => {
+                let asDateString = dt.getMethod("asDateString");
+
+                let result = asDateString.call(interpreter, new BrsString(""));
+                expect(asDateString).toBeTruthy();
+                expect(result).toEqual(new BrsString("Thursday January 1, 2009"));
+            });
+
+            it("returns date string with invalid param", () => {
+                let asDateString = dt.getMethod("asDateString");
+
+                let result = asDateString.call(interpreter, new BrsString("anything"));
+                expect(asDateString).toBeTruthy();
+                expect(result).toEqual(new BrsString("Thursday January 1, 2009"));
+            });
+
+            it("returns date string with param: full-date", () => {
+                let asDateString = dt.getMethod("asDateString");
+
+                let result = asDateString.call(interpreter, new BrsString("full-date"));
+                expect(asDateString).toBeTruthy();
+                expect(result).toEqual(new BrsString("Thursday January 1, 2009"));
+            });
+
+            it("returns date string with param: short-weekday", () => {
+                let asDateString = dt.getMethod("asDateString");
+
+                let result = asDateString.call(interpreter, new BrsString("short-weekday"));
+                expect(asDateString).toBeTruthy();
+                expect(result).toEqual(new BrsString("Thu January 1, 2009"));
+            });
+
+            it("returns date string with param: no-weekday", () => {
+                let asDateString = dt.getMethod("asDateString");
+
+                let result = asDateString.call(interpreter, new BrsString("no-weekday"));
+                expect(asDateString).toBeTruthy();
+                expect(result).toEqual(new BrsString("January 1, 2009"));
+            });
+
+            it("returns date string with param: short-month-short-weekday", () => {
+                let asDateString = dt.getMethod("asDateString");
+
+                let result = asDateString.call(
+                    interpreter,
+                    new BrsString("short-month-short-weekday")
+                );
+                expect(asDateString).toBeTruthy();
+                expect(result).toEqual(new BrsString("Thu Jan 1, 2009"));
+            });
+
+            it("returns date string with param: short-month-no-weekday", () => {
+                let asDateString = dt.getMethod("asDateString");
+
+                let result = asDateString.call(
+                    interpreter,
+                    new BrsString("short-month-no-weekday")
+                );
+                expect(asDateString).toBeTruthy();
+                expect(result).toEqual(new BrsString("Jan 1, 2009"));
+            });
+
+            it("returns date string with param: short-date", () => {
+                let asDateString = dt.getMethod("asDateString");
+
+                let result = asDateString.call(interpreter, new BrsString("short-date"));
+                expect(asDateString).toBeTruthy();
+                expect(result).toEqual(new BrsString("1/1/9"));
+            });
+
+            it("returns date string with param: short-date-dashes", () => {
+                let asDateString = dt.getMethod("asDateString");
+
+                let result = asDateString.call(interpreter, new BrsString("short-date-dashes"));
+                expect(asDateString).toBeTruthy();
+                expect(result).toEqual(new BrsString("1-1-9"));
+            });
+        });
+
+        describe("asDateStringNoParam", () => {
+            it("returns date string with empty param", () => {
+                let asDateStringNoParam = dt.getMethod("asDateStringNoParam");
+
+                let result = asDateStringNoParam.call(interpreter);
+                expect(asDateStringNoParam).toBeTruthy();
+                expect(result).toEqual(new BrsString("Thursday January 1, 2009"));
+            });
+        });
+
+        describe("asSeconds", () => {
+            it("returns the date/time as the number of seconds from the Unix epoch", () => {
+                let asSeconds = dt.getMethod("asSeconds");
+                let result = asSeconds.call(interpreter);
+                expect(asSeconds).toBeTruthy();
+                expect(result).toEqual(new Int32(1230768000));
+            });
+        });
+
+        describe("fromISO8601String", () => {
+            it("set the date/time using a string in the ISO 8601 format", () => {
+                let fromISO8601String = dt.getMethod("fromISO8601String");
+                expect(fromISO8601String).toBeTruthy();
+                let result = fromISO8601String.call(
+                    interpreter,
+                    new BrsString("2019-07-27T17:08:41")
+                );
+                expect(new Int32(dt.markTime)).toEqual(new Int32(1564247321000));
+                expect(result).toBe(BrsInvalid.Instance);
+            });
+
+            it("set the date/time using an invalid string", () => {
+                let fromISO8601String = dt.getMethod("fromISO8601String");
+                expect(fromISO8601String).toBeTruthy();
+                let result = fromISO8601String.call(interpreter, new BrsString("garbage"));
+                expect(new Int32(dt.markTime)).toEqual(new Int32(0));
+                expect(result).toBe(BrsInvalid.Instance);
+            });
+        });
+
+        describe("fromSeconds", () => {
+            it("set the date/time value using the number of seconds from the Unix epoch", () => {
+                let fromSeconds = dt.getMethod("fromSeconds");
+                expect(fromSeconds).toBeTruthy();
+                let result = fromSeconds.call(interpreter, new Int32(1564247321));
+                expect(new Int32(dt.markTime)).toEqual(new Int32(1564247321000));
+                expect(result).toBe(BrsInvalid.Instance);
+            });
+        });
+
+        describe("getDayOfMonth", () => {
+            it("returns the date/time value's day of the month", () => {
+                let getDayOfMonth = dt.getMethod("getDayOfMonth");
+                let result = getDayOfMonth.call(interpreter);
+                expect(getDayOfMonth).toBeTruthy();
+                expect(result).toEqual(new Int32(1));
+            });
+        });
+
+        describe("getDayOfWeek", () => {
+            it("returns the date/time value's day of the week", () => {
+                let getDayOfWeek = dt.getMethod("getDayOfWeek");
+                let result = getDayOfWeek.call(interpreter);
+                expect(getDayOfWeek).toBeTruthy();
+                expect(result).toEqual(new Int32(4));
+            });
+        });
+
+        describe("getHours", () => {
+            it("returns the date/time value's hour within the day", () => {
+                let getHours = dt.getMethod("getHours");
+                let result = getHours.call(interpreter);
+                expect(getHours).toBeTruthy();
+                expect(result).toEqual(new Int32(0));
+            });
+        });
+
+        describe("getLastDayOfMonth", () => {
+            it("returns the date/time value's last day of the month", () => {
+                let getLastDayOfMonth = dt.getMethod("getLastDayOfMonth");
+                let result = getLastDayOfMonth.call(interpreter);
+                expect(getLastDayOfMonth).toBeTruthy();
+                expect(result).toEqual(new Int32(31));
+            });
+        });
+
+        describe("getMilliseconds", () => {
+            it("returns the date/time value's millisecond within the second", () => {
+                let getMilliseconds = dt.getMethod("getMilliseconds");
+                let result = getMilliseconds.call(interpreter);
+                expect(getMilliseconds).toBeTruthy();
+                expect(result).toEqual(new Int32(123));
+            });
+        });
+
+        describe("getMinutes", () => {
+            it("returns the date/time value's minute within the hour", () => {
+                let getMinutes = dt.getMethod("getMinutes");
+                let result = getMinutes.call(interpreter);
+                expect(getMinutes).toBeTruthy();
+                expect(result).toEqual(new Int32(0));
+            });
+        });
+
+        describe("getMonth", () => {
+            it("returns the date/time value's month", () => {
+                let getMonth = dt.getMethod("getMonth");
+                let result = getMonth.call(interpreter);
+                expect(getMonth).toBeTruthy();
+                expect(result).toEqual(new Int32(1));
+            });
+        });
+
+        describe("getSeconds", () => {
+            it("returns the date/time value's second within the minute", () => {
+                let getSeconds = dt.getMethod("getSeconds");
+                let result = getSeconds.call(interpreter);
+                expect(getSeconds).toBeTruthy();
+                expect(result).toEqual(new Int32(0));
+            });
+        });
+
+        describe("getTimeZoneOffset", () => {
+            it("returns the offset in minutes from the system time zone to UTC", () => {
+                let getTimeZoneOffset = dt.getMethod("getTimeZoneOffset");
+                let result = getTimeZoneOffset.call(interpreter);
+                expect(getTimeZoneOffset).toBeTruthy();
+                expect(result).toEqual(new Int32(new Date().getTimezoneOffset()));
+            });
+        });
+
+        describe("getWeekday", () => {
+            it("returns the date/time value's second within the minute", () => {
+                let getWeekday = dt.getMethod("getWeekday");
+                let result = getWeekday.call(interpreter);
+                expect(getWeekday).toBeTruthy();
+                expect(result).toEqual(new BrsString("Thursday"));
+            });
+        });
+
+        describe("getYear", () => {
+            it("returns the date/time value's year", () => {
+                let getYear = dt.getMethod("getYear");
+                let result = getYear.call(interpreter);
+                expect(getYear).toBeTruthy();
+                expect(result).toEqual(new Int32(2009));
+            });
+        });
+
+        describe("toISOString", () => {
+            it("return an ISO 8601 representation of the date/time value", () => {
+                let toISOString = dt.getMethod("toISOString");
+                let result = toISOString.call(interpreter);
+                expect(toISOString).toBeTruthy();
+                expect(result).toEqual(new BrsString("2009-01-01T00:00:00Z"));
+            });
+        });
+
+        describe("toLocalTime", () => {
+            it("offsets the date/time from UTC to local time using the system time zone setting", () => {
+                let toLocalTime = dt.getMethod("toLocalTime");
+                let result = toLocalTime.call(interpreter);
+                let local = 1230768000123 - new Date(1230768000123).getTimezoneOffset() * 60 * 1000;
+                expect(toLocalTime).toBeTruthy();
+                expect(new Int32(dt.markTime)).toEqual(new Int32(local));
+                expect(result).toBe(BrsInvalid.Instance);
+            });
+        });
+    });
+});
diff --git a/test/e2e/BrsComponents.test.js b/test/e2e/BrsComponents.test.js
index 6fa7703..984ed00 100644
--- a/test/e2e/BrsComponents.test.js
+++ b/test/e2e/BrsComponents.test.js
@@ -60,6 +60,41 @@ describe("end to end brightscript functions", () => {
         ]);
     });
 
+    test("components/roDateTime.brs", async () => {
+        await execute([resourceFile("components", "roDateTime.brs")], outputStreams);
+
+        expect(allArgs(outputStreams.stdout.write).filter(arg => arg !== "\n")).toEqual([
+            "Full Date: ",
+            "Friday November 12, 2010",
+            "No Week Day: ",
+            "November 12, 2010",
+            "Short Date: ",
+            "11/12/10",
+            "Weekday: ",
+            "Friday",
+            "Day of Week: ",
+            "5",
+            "Day of Month: ",
+            "12",
+            "Month: ",
+            "11",
+            "Year: ",
+            "2010",
+            "Hours: ",
+            "13",
+            "Minutes: ",
+            "14",
+            "Seconds: ",
+            "15",
+            "Last Day of Month: ",
+            "30",
+            "Milliseconds: ",
+            "160",
+            "ISO String UTC: ",
+            "2010-11-12T13:14:15Z",
+        ]);
+    });
+
     test("components/roTimespan.brs", async () => {
         await execute([resourceFile("components", "roTimespan.brs")], outputStreams);
 
diff --git a/test/e2e/StdLib.test.js b/test/e2e/StdLib.test.js
index cbc8e0f..596f443 100644
--- a/test/e2e/StdLib.test.js
+++ b/test/e2e/StdLib.test.js
@@ -54,6 +54,8 @@ describe("end to end standard libary", () => {
             "12.34",
             "Mary and Bob",
             "252",
+            "abababab",
+            "!!!!!!!!",
         ]);
     });
 
diff --git a/test/e2e/resources/components/roDateTime.brs b/test/e2e/resources/components/roDateTime.brs
new file mode 100644
index 0000000..98ac15f
--- /dev/null
+++ b/test/e2e/resources/components/roDateTime.brs
@@ -0,0 +1,18 @@
+sub Main()
+    date = CreateObject("roDateTime")
+    date.fromISO8601String("2010-11-12T13:14:15.160")
+    print "Full Date: " date.AsDateStringNoParam()
+    print "No Week Day: " date.AsDateString("no-weekday")
+    print "Short Date: " date.AsDateString("short-date")
+    print "Weekday: " date.getWeekday()
+    print "Day of Week: " date.getDayOfWeek()
+    print "Day of Month: " date.getDayOfMonth()
+    print "Month: " date.getMonth()
+    print "Year: " date.getYear()
+    print "Hours: " date.getHours()
+    print "Minutes: " date.getMinutes()
+    print "Seconds: " date.getSeconds()
+    print "Last Day of Month: " date.getLastDayOfMonth()
+    print "Milliseconds: " date.getMilliseconds()
+    print "ISO String UTC: " date.toISOString()
+end sub
diff --git a/test/e2e/resources/stdlib/strings.brs b/test/e2e/resources/stdlib/strings.brs
index 812c626..b4186e4 100644
--- a/test/e2e/resources/stdlib/strings.brs
+++ b/test/e2e/resources/stdlib/strings.brs
@@ -17,3 +17,5 @@ print StrI(-3) ' "-3"
 print Val("12.34") ' 12.34
 print Substitute("{0} and {1}", "Mary", "Bob")
 print StrToI("252")
+print String(4, "ab") ' abababab
+print StringI(8, 33)  ' !!!!!!!!
diff --git a/test/lexer/Lexer.test.js b/test/lexer/Lexer.test.js
index 0770043..6b80b8e 100644
--- a/test/lexer/Lexer.test.js
+++ b/test/lexer/Lexer.test.js
@@ -422,4 +422,23 @@ describe("lexer", () => {
             ]);
         });
     });
+
+    describe("isToken", () => {
+        it("returns booleans", () => {
+            let location = {
+                start: {
+                    line: 1,
+                    column: 1,
+                },
+                end: {
+                    line: 1,
+                    column: 2,
+                },
+                file: "SomeFile.brs",
+            };
+
+            expect(lexer.isToken({ kind: Lexeme.And, text: "and", location: location })).toBe(true);
+            expect(lexer.isToken({ text: "and", location: location })).toBe(false);
+        });
+    });
 }); // lexer
diff --git a/test/stdlib/String.test.js b/test/stdlib/String.test.js
index 007a0bf..814cf5c 100644
--- a/test/stdlib/String.test.js
+++ b/test/stdlib/String.test.js
@@ -13,6 +13,8 @@ const {
     Substitute,
     Val,
     StrToI,
+    STRING,
+    StringI,
 } = require("../../lib/stdlib/index");
 const { Interpreter } = require("../../lib/interpreter");
 const { BrsString, BrsBoolean, Int32, Float } = require("../../lib/brsTypes");
@@ -195,6 +197,40 @@ describe("global string functions", () => {
         });
     });
 
+    describe("String", () => {
+        it("returns a string composed of n copies of a character.", () => {
+            expect(STRING.call(interpreter, new Int32(5), new BrsString("*"))).toEqual(
+                new BrsString("*****")
+            );
+        });
+
+        it("returns a string composed of n copies of a string.", () => {
+            expect(STRING.call(interpreter, new Int32(3), new BrsString("brs"))).toEqual(
+                new BrsString("brsbrsbrs")
+            );
+        });
+
+        it("returns a string composed of n copies of an empty string.", () => {
+            expect(STRING.call(interpreter, new Int32(1000), new BrsString(""))).toEqual(
+                new BrsString("")
+            );
+        });
+    });
+
+    describe("StringI", () => {
+        it("returns a string composed of n copies of a character from an ASCII char code.", () => {
+            expect(StringI.call(interpreter, new Int32(7), new Int32(48))).toEqual(
+                new BrsString("0000000")
+            );
+        });
+
+        it("returns a string composed of n copies of a character from a UNICODE char code.", () => {
+            expect(StringI.call(interpreter, new Int32(3), new Int32(936))).toEqual(
+                new BrsString("ΨΨΨ")
+            );
+        });
+    });
+
     describe("Substitute", () => {
         it("returns no substitution", () => {
             expect(