From 04dc23d4753e968565bf0ad22692f7fe5228e1ae Mon Sep 17 00:00:00 2001
From: Tsuf Cohen <39455181+tsuf239@users.noreply.github.com>
Date: Tue, 1 Aug 2023 11:35:06 +0300
Subject: [PATCH] feat(sdk): adding `datetime` type (#3570)
## Description
closes #2102
(besides adding the type mapping, opened a separate issue: https://github.com/winglang/wing/issues/3569)
a wing test added, checked both preflight and inflight both in tf-aws and sim.
## Checklist
- [x] Title matches [Winglang's style guide](https://www.winglang.io/contributing/start-here/pull_requests#how-are-pull-request-titles-formatted)
- [x] Description explains motivation and solution
- [x] Tests added (always)
- [x] Docs updated (only required for features)
- [ ] Added `pr/e2e-full` label if this feature requires end-to-end testing
*By submitting this pull request, I confirm that my contribution is made under the terms of the [Wing Cloud Contribution License](https://github.com/winglang/wing/blob/main/CONTRIBUTION_LICENSE.md)*.
---
docs/docs/03-language-reference.md | 57 ++-
.../02-std/api-reference.md | 361 ++++++++++++++++++
examples/tests/sdk_tests/std/datetime.w | 75 ++++
libs/wingc/src/type_check.rs | 5 +
libs/wingc/src/type_check/jsii_importer.rs | 2 +-
libs/wingsdk/src/std/datetime.ts | 227 +++++++++++
libs/wingsdk/src/std/index.ts | 1 +
.../std/datetime.w_compile_tf-aws.md | 249 ++++++++++++
.../sdk_tests/std/datetime.w_test_sim.md | 12 +
9 files changed, 985 insertions(+), 4 deletions(-)
create mode 100644 examples/tests/sdk_tests/std/datetime.w
create mode 100644 libs/wingsdk/src/std/datetime.ts
create mode 100644 tools/hangar/__snapshots__/test_corpus/sdk_tests/std/datetime.w_compile_tf-aws.md
create mode 100644 tools/hangar/__snapshots__/test_corpus/sdk_tests/std/datetime.w_test_sim.md
diff --git a/docs/docs/03-language-reference.md b/docs/docs/03-language-reference.md
index b8a3c5bc25b..082fe73ab99 100644
--- a/docs/docs/03-language-reference.md
+++ b/docs/docs/03-language-reference.md
@@ -413,11 +413,62 @@ assert(threeHours.minutes == 180);
Duration objects are immutable and can be referenced across inflight context.
-#### 1.1.5.1 Roadmap
+#### 1.1.6 `Datetime`
-An additional built-in `datetime` type is planned and not yet implemented. `datetime` represents a single moment in time in a platform-independent
+The `Datetime` (alias `datetime`) type represents a single moment in time in a platform-independent
format.
-See https://github.com/winglang/wing/issues/2102 to track.
+Datetime objects are immutable and can be referenced across inflight context.
+Here is the initial API for the Datetime type:
+
+```TS
+struct DatetimeComponents {
+ year: num;
+ month: num;
+ day: num;
+ hour: num;
+ min: num;
+ sec: num;
+ ms: num;
+ tz: num; // timezone offset in minutes from UTC
+}
+
+class Datetime {
+ static utcNow(): Datetime; // returns the current time in UTC timezone
+ static systemNow(): Datetime; // returns the current time in system timezone
+ static fromIso(iso: str): Datetime; // creates an instance from an ISO-8601 string, represented in UTC timezone
+ static fromComponents(c: DatetimeComponents): Datetime;
+
+ timestamp: num; // Date.valueOf()/1000 (non-leap seconds since epoch)
+ timestampMs: num; // Date.valueOf() (non-leap milliseconds since epoch)
+
+ hours: num; // Date.getHours()
+ min: num; // Date.getMinutes()
+ sec: num; // Date.getSeconds()
+ ms: num; // Date.getMilliseconds()
+ dayOfMonth: num; // Date.getDate()
+ dayOfWeek: num; // Date.getDay()
+ month: num; // Date.getMonth()
+ year: num; // Date.getFullYear()
+
+ timezone: num; // (offset in minutes from UTC)
+ utc: Datetime; // returns the same time in UTC timezone
+
+ toIso(): str; // returns ISO-8601 string
+}
+```
+
+A few examples:
+
+```TS
+let now = Datetime.utcNow();
+log("It is now ${now.month}/${now.dayOfMonth}/${now.year} at ${now.hours}:${now.min}:${now.sec})");
+assert(now.timezone == 0); // UTC
+
+let t1 = DateTime.fromIso("2023-02-09T06:20:17.573Z");
+log("Timezone is GMT${d.timezone() / 60}"); // output: Timezone is GMT-2
+log("UTC: ${t1.utc.toIso())}"); // output: 2023-02-09T06:21:03.000Z
+```
+
### 1.2 Utility Functions
diff --git a/docs/docs/04-standard-library/02-std/api-reference.md b/docs/docs/04-standard-library/02-std/api-reference.md
index f630cf4c144..13f2a8e9452 100644
--- a/docs/docs/04-standard-library/02-std/api-reference.md
+++ b/docs/docs/04-standard-library/02-std/api-reference.md
@@ -190,6 +190,245 @@ to parse boolean from.
+### Datetime
+
+Represents a local or UTC date object.
+
+#### Methods
+
+| **Name** | **Description** |
+| --- | --- |
+| toIso
| Returns ISO-8601 string. |
+| toUtc
| Returns a Datetime represents the same date in utc. |
+
+---
+
+##### `toIso`
+
+```wing
+toIso(): str
+```
+
+Returns ISO-8601 string.
+
+##### `toUtc`
+
+```wing
+toUtc(): datetime
+```
+
+Returns a Datetime represents the same date in utc.
+
+#### Static Functions
+
+| **Name** | **Description** |
+| --- | --- |
+| fromComponents
| Create a Datetime from Datetime components. |
+| fromIso
| Create a Datetime from an ISO-8601 string. |
+| systemNow
| Create a Datetime from local system timezone. |
+| utcNow
| Create a Datetime from UTC timezone. |
+
+---
+
+##### `fromComponents`
+
+```wing
+datetime.fromComponents(c: DatetimeComponents);
+```
+
+Create a Datetime from Datetime components.
+
+###### `c`Required
+
+- *Type:* DatetimeComponents
+
+DatetimeComponents.
+
+---
+
+##### `fromIso`
+
+```wing
+datetime.fromIso(iso: str);
+```
+
+Create a Datetime from an ISO-8601 string.
+
+###### `iso`Required
+
+- *Type:* str
+
+ISO-8601 string.
+
+---
+
+##### `systemNow`
+
+```wing
+datetime.systemNow();
+```
+
+Create a Datetime from local system timezone.
+
+##### `utcNow`
+
+```wing
+datetime.utcNow();
+```
+
+Create a Datetime from UTC timezone.
+
+#### Properties
+
+| **Name** | **Type** | **Description** |
+| --- | --- | --- |
+| dayOfMonth
| num
| Returns the day of month in the local machine time or in utc (1 - 31). |
+| dayOfWeek
| num
| Returns the day in month of the local machine time or in utc (0 - 6). |
+| hours
| num
| Returns the hour of the local machine time or in utc. |
+| min
| num
| Returns the minute of the local machine time or in utc. |
+| month
| num
| Returns the month of the local machine time or in utc (0 - 11). |
+| ms
| num
| Returns the milliseconds of the local machine time or in utc *. |
+| sec
| num
| Returns the seconds of the local machine time or in utc. |
+| timestamp
| num
| Return a timestamp of non-leap year seconds since epoch. |
+| timestampMs
| num
| Return a timestamp of non-leap year milliseconds since epoch. |
+| timezone
| num
| returns the offset in minutes from UTC. |
+| year
| num
| Returns the year of the local machine time or in utc. |
+
+---
+
+##### `dayOfMonth`Required
+
+```wing
+dayOfMonth: num;
+```
+
+- *Type:* num
+
+Returns the day of month in the local machine time or in utc (1 - 31).
+
+---
+
+##### `dayOfWeek`Required
+
+```wing
+dayOfWeek: num;
+```
+
+- *Type:* num
+
+Returns the day in month of the local machine time or in utc (0 - 6).
+
+---
+
+##### `hours`Required
+
+```wing
+hours: num;
+```
+
+- *Type:* num
+
+Returns the hour of the local machine time or in utc.
+
+---
+
+##### `min`Required
+
+```wing
+min: num;
+```
+
+- *Type:* num
+
+Returns the minute of the local machine time or in utc.
+
+---
+
+##### `month`Required
+
+```wing
+month: num;
+```
+
+- *Type:* num
+
+Returns the month of the local machine time or in utc (0 - 11).
+
+---
+
+##### `ms`Required
+
+```wing
+ms: num;
+```
+
+- *Type:* num
+
+Returns the milliseconds of the local machine time or in utc *.
+
+---
+
+##### `sec`Required
+
+```wing
+sec: num;
+```
+
+- *Type:* num
+
+Returns the seconds of the local machine time or in utc.
+
+---
+
+##### `timestamp`Required
+
+```wing
+timestamp: num;
+```
+
+- *Type:* num
+
+Return a timestamp of non-leap year seconds since epoch.
+
+---
+
+##### `timestampMs`Required
+
+```wing
+timestampMs: num;
+```
+
+- *Type:* num
+
+Return a timestamp of non-leap year milliseconds since epoch.
+
+---
+
+##### `timezone`Required
+
+```wing
+timezone: num;
+```
+
+- *Type:* num
+
+returns the offset in minutes from UTC.
+
+---
+
+##### `year`Required
+
+```wing
+year: num;
+```
+
+- *Type:* num
+
+Returns the year of the local machine time or in utc.
+
+---
+
+
### Duration
Represents a length of time.
@@ -1812,5 +2051,127 @@ The length of the string.
---
+## Structs
+
+### DatetimeComponents
+
+interface that is used for setting Datetime date.
+
+#### Initializer
+
+```wing
+let DatetimeComponents = DatetimeComponents{ ... };
+```
+
+#### Properties
+
+| **Name** | **Type** | **Description** |
+| --- | --- | --- |
+| day
| num
| day. |
+| hour
| num
| hours. |
+| min
| num
| minutes. |
+| month
| num
| month. |
+| ms
| num
| milliseconds. |
+| sec
| num
| seconds. |
+| tz
| num
| timezone offset in minutes from UTC. |
+| year
| num
| year. |
+
+---
+
+##### `day`Required
+
+```wing
+day: num;
+```
+
+- *Type:* num
+
+day.
+
+---
+
+##### `hour`Required
+
+```wing
+hour: num;
+```
+
+- *Type:* num
+
+hours.
+
+---
+
+##### `min`Required
+
+```wing
+min: num;
+```
+
+- *Type:* num
+
+minutes.
+
+---
+
+##### `month`Required
+
+```wing
+month: num;
+```
+
+- *Type:* num
+
+month.
+
+---
+
+##### `ms`Required
+
+```wing
+ms: num;
+```
+
+- *Type:* num
+
+milliseconds.
+
+---
+
+##### `sec`Required
+
+```wing
+sec: num;
+```
+
+- *Type:* num
+
+seconds.
+
+---
+
+##### `tz`Required
+
+```wing
+tz: num;
+```
+
+- *Type:* num
+
+timezone offset in minutes from UTC.
+
+---
+
+##### `year`Required
+
+```wing
+year: num;
+```
+
+- *Type:* num
+
+year.
+
+---
diff --git a/examples/tests/sdk_tests/std/datetime.w b/examples/tests/sdk_tests/std/datetime.w
new file mode 100644
index 00000000000..65f95260bd6
--- /dev/null
+++ b/examples/tests/sdk_tests/std/datetime.w
@@ -0,0 +1,75 @@
+bring cloud;
+bring util;
+bring math;
+
+let d1 = datetime.utcNow();
+let d2 = datetime.systemNow();
+
+assert(math.floor(d2.timestamp) == math.floor(d1.timestamp));
+
+let d3 = datetime.fromIso("2023-07-18T20:18:25.177+03:00");
+
+assert(d3.timestampMs == 1689700705177);
+assert(d3.hours == 17);
+assert(d3.min == 18);
+assert(d3.sec == 25);
+assert(d3.ms == 177);
+assert(d3.dayOfMonth == 18);
+assert(d3.dayOfWeek == 2);
+assert(d3.month == 6);
+assert(d3.year == 2023);
+
+
+let d4 = datetime.fromComponents(year: 2023, month: 6, day: 18, hour: 19, min: 18, sec: 25, ms: 177, tz: -120);
+
+assert(d4.timezone == -120);
+assert(d4.timestampMs == 1689700705177);
+assert(d4.hours == 19);
+assert(d4.min == 18);
+assert(d4.sec == 25);
+assert(d4.ms == 177);
+assert(d4.dayOfMonth == 18);
+assert(d4.dayOfWeek == 2);
+assert(d4.month == 6);
+assert(d4.year == 2023);
+
+assert(d4.toUtc().hours == (d4.hours + (d4.timezone / 60)));
+
+test "inflight datetime" {
+ let d5 = datetime.utcNow();
+ let d6 = datetime.systemNow();
+
+ assert(d2.timestamp == d1.timestamp);
+
+ let d7 = datetime.fromIso("2023-07-18T20:18:25.177-03:00");
+ let d8 = datetime.fromComponents(year: 2023, month: 6, day: 18, hour: 20, min: 18, sec: 25, ms: 177, tz: 180); // UTC-3:00
+
+
+ assert(d7.timestampMs == 1689722305177);
+ assert(d7.hours == 23);
+ assert(d7.min == 18);
+ assert(d7.sec == 25);
+ assert(d7.ms == 177);
+ assert(d7.dayOfMonth == 18);
+ assert(d7.dayOfWeek == 2);
+ assert(d7.month == 6);
+ assert(d7.year == 2023);
+ assert(d8.hours == 20);
+
+ assert(math.floor(d7.timestamp) == math.floor(d8.timestamp));
+ assert(d4.toUtc().hours == (d4.hours + (d4.timezone / 60)));
+ assert(d8.toUtc().hours == (d8.hours + (d8.timezone / 60)));
+
+
+ let beforeSleep = datetime.systemNow();
+ util.sleep(1s);
+ assert(math.floor(datetime.systemNow().timestamp - beforeSleep.timestamp) == 1);
+
+}
+
+
+
+
+
+
+
diff --git a/libs/wingc/src/type_check.rs b/libs/wingc/src/type_check.rs
index 0fc972d24bb..36a1b5b63c7 100644
--- a/libs/wingc/src/type_check.rs
+++ b/libs/wingc/src/type_check.rs
@@ -3669,6 +3669,10 @@ impl<'a> TypeChecker<'a> {
name: "Duration".to_string(),
span: symbol.span.clone(),
}),
+ "datetime" => Some(Symbol {
+ name: "Datetime".to_string(),
+ span: symbol.span.clone(),
+ }),
"str" => Some(Symbol {
name: "String".to_string(),
span: symbol.span.clone(),
@@ -4422,6 +4426,7 @@ pub fn fully_qualify_std_type(type_: &str) -> std::string::String {
let type_name = match type_name {
"str" => "String",
"duration" => "Duration",
+ "datetime" => "Datetime",
"bool" => "Boolean",
"num" => "Number",
_ => type_name,
diff --git a/libs/wingc/src/type_check/jsii_importer.rs b/libs/wingc/src/type_check/jsii_importer.rs
index a6abe2d29a2..bea9848fd17 100644
--- a/libs/wingc/src/type_check/jsii_importer.rs
+++ b/libs/wingc/src/type_check/jsii_importer.rs
@@ -90,7 +90,7 @@ impl<'a> JsiiImporter<'a> {
PrimitiveType::Boolean => self.wing_types.bool(),
PrimitiveType::Any => self.wing_types.anything(),
PrimitiveType::Json => self.wing_types.json(),
- PrimitiveType::Date => self.wing_types.anything(), // TODO: https://github.com/winglang/wing/issues/2102
+ PrimitiveType::Date => self.wing_types.anything(), // TODO: https://github.com/winglang/wing/issues/3569
},
TypeReference::NamedTypeReference(named_ref) => {
let type_fqn = &named_ref.fqn;
diff --git a/libs/wingsdk/src/std/datetime.ts b/libs/wingsdk/src/std/datetime.ts
new file mode 100644
index 00000000000..e88ff0d1bea
--- /dev/null
+++ b/libs/wingsdk/src/std/datetime.ts
@@ -0,0 +1,227 @@
+import { Code, InflightClient } from "../core";
+
+/**
+ * interface that is used for setting Datetime date
+ */
+export interface DatetimeComponents {
+ /**
+ * year
+ */
+ readonly year: number;
+ /**
+ * month
+ */
+ readonly month: number;
+ /**
+ * day
+ */
+ readonly day: number;
+ /**
+ * hours
+ */
+ readonly hour: number;
+ /**
+ * minutes
+ */
+ readonly min: number;
+ /**
+ * seconds
+ */
+ readonly sec: number;
+ /**
+ * milliseconds
+ */
+ readonly ms: number;
+ /**
+ * timezone offset in minutes from UTC
+ */
+ readonly tz: number;
+}
+
+/**
+ * Represents a local or UTC date object
+ * @wingType datetime
+ */
+export class Datetime {
+ /**
+ * @internal
+ */
+ public static _toInflightType(): Code {
+ return InflightClient.forType(__filename, this.name);
+ }
+ /**
+ * Create a Datetime from UTC timezone
+ *
+ * @returns a new `Datetime` from current time in UTC timezone
+ */
+ public static utcNow(): Datetime {
+ return new Datetime();
+ }
+
+ /**
+ * Create a Datetime from local system timezone
+ *
+ * @returns a new `Datetime` from current time in system timezone
+ */
+ public static systemNow(): Datetime {
+ const date = new Date();
+ date.setTime(date.getTime() - date.getTimezoneOffset() * 60 * 1000);
+
+ return new Datetime(date, date.getTimezoneOffset());
+ }
+
+ /**
+ * Create a Datetime from an ISO-8601 string
+ *
+ * @returns a new `Datetime` in UTC timezone
+ * @param iso ISO-8601 string
+ */
+ public static fromIso(iso: string): Datetime {
+ return new Datetime(new Date(iso));
+ }
+
+ /**
+ * Create a Datetime from Datetime components
+ *
+ * @param c DatetimeComponents
+ * @returns a new `Datetime`
+ */
+ public static fromComponents(c: DatetimeComponents): Datetime {
+ const date = new Date(
+ Date.UTC(c.year, c.month, c.day, c.hour, c.min, c.sec, c.ms)
+ );
+
+ return new Datetime(date, c.tz);
+ }
+
+ /** @internal */
+ private readonly _date: Date;
+ /** @internal */
+ private readonly _timezoneOffset: number = 0;
+
+ private constructor(date: Date = new Date(), timezoneOffset = 0) {
+ this._date = date;
+ this._timezoneOffset = timezoneOffset;
+ }
+
+ /**
+ * Return a timestamp of non-leap year seconds since epoch
+ *
+ * @returns a number representing the current timestamp in seconds
+ */
+ public get timestamp(): number {
+ return this.timestampMs / 1000;
+ }
+
+ /**
+ * Return a timestamp of non-leap year milliseconds since epoch
+ *
+ * @returns a number representing the current timestamp in milliseconds
+ */
+ public get timestampMs(): number {
+ // since converting between timezones/ declaring a date in a timezone other than the local or UTC
+ // isn't native to js, we keep the date in a UTC time, then retrieving back the the original timestamp,
+ // this way the date components (hours, month, day, minutes, etc..) are persistent
+ // and retrieved in the same order for all of the different constructing methods and the timestamp is correct.
+ return this._date.valueOf() + this._timezoneOffset * 60 * 1000;
+ }
+
+ /**
+ * Returns the hour of the local machine time or in utc
+ *
+ * @returns a number representing the datetime's hour
+ */
+ public get hours(): number {
+ return this._date.getUTCHours();
+ }
+
+ /**
+ * Returns the minute of the local machine time or in utc
+ *
+ * @returns a number representing the datetime's minute
+ */
+ public get min(): number {
+ return this._date.getUTCMinutes();
+ }
+
+ /**
+ * Returns the seconds of the local machine time or in utc
+ *
+ * @returns a number representing the datetime's seconds
+ */
+ public get sec(): number {
+ return this._date.getUTCSeconds();
+ }
+
+ /**
+ * Returns the milliseconds of the local machine time or in utc
+ * *
+ * @returns a number representing the datetime's milliseconds
+ */
+ public get ms(): number {
+ return this._date.getUTCMilliseconds();
+ }
+
+ /**
+ * Returns the day of month in the local machine time or in utc (1 - 31)
+ *
+ * @returns a number representing the datetime's day of month
+ */
+ public get dayOfMonth(): number {
+ return this._date.getUTCDate();
+ }
+
+ /**
+ * Returns the day in month of the local machine time or in utc (0 - 6)
+ *
+ * @returns a number representing the datetime's day of week
+ */
+ public get dayOfWeek(): number {
+ return this._date.getUTCDay();
+ }
+
+ /**
+ * Returns the month of the local machine time or in utc (0 - 11)
+ *
+ * @returns a number representing the datetime's month
+ */
+ public get month(): number {
+ return this._date.getUTCMonth();
+ }
+
+ /**
+ * Returns the year of the local machine time or in utc
+ *
+ * @returns a number representing the datetime's year
+ */
+ public get year(): number {
+ return this._date.getUTCFullYear();
+ }
+
+ /**
+ * returns the offset in minutes from UTC
+ *
+ * @returns a number representing the datetime's offset in minutes from UTC
+ */
+ public get timezone(): number {
+ return this._timezoneOffset;
+ }
+
+ /**
+ * Returns a Datetime represents the same date in utc
+ *
+ * @returns a datetime representing the datetime's date in UTC
+ */
+ public toUtc(): Datetime {
+ return new Datetime(new Date(this.timestampMs));
+ }
+
+ /**
+ * Returns ISO-8601 string
+ *
+ * @returns a ISO-8601 string representation of the datetime
+ */
+ public toIso(): string {
+ return new Date(this.timestampMs).toISOString();
+ }
+}
diff --git a/libs/wingsdk/src/std/index.ts b/libs/wingsdk/src/std/index.ts
index fe6ed2b4460..f135327b652 100644
--- a/libs/wingsdk/src/std/index.ts
+++ b/libs/wingsdk/src/std/index.ts
@@ -1,5 +1,6 @@
export * from "./array";
export * from "./bool";
+export * from "./datetime";
export * from "./duration";
export * from "./generics";
export * from "./json";
diff --git a/tools/hangar/__snapshots__/test_corpus/sdk_tests/std/datetime.w_compile_tf-aws.md b/tools/hangar/__snapshots__/test_corpus/sdk_tests/std/datetime.w_compile_tf-aws.md
new file mode 100644
index 00000000000..9bf11bbe3f1
--- /dev/null
+++ b/tools/hangar/__snapshots__/test_corpus/sdk_tests/std/datetime.w_compile_tf-aws.md
@@ -0,0 +1,249 @@
+# [datetime.w](../../../../../../examples/tests/sdk_tests/std/datetime.w) | compile | tf-aws
+
+## inflight.$Closure1.js
+```js
+module.exports = function({ $_d4_toUtc____hours, $d1_timestamp, $d2_timestamp, $d4_hours, $d4_timezone, $math_Util, $std_Datetime, $std_Duration, $util_Util }) {
+ class $Closure1 {
+ constructor({ }) {
+ const $obj = (...args) => this.handle(...args);
+ Object.setPrototypeOf($obj, this);
+ return $obj;
+ }
+ async handle() {
+ const d5 = (await $std_Datetime.utcNow());
+ const d6 = (await $std_Datetime.systemNow());
+ {((cond) => {if (!cond) throw new Error("assertion failed: d2.timestamp == d1.timestamp")})((((a,b) => { try { return require('assert').deepStrictEqual(a,b) === undefined; } catch { return false; } })($d2_timestamp,$d1_timestamp)))};
+ const d7 = (await $std_Datetime.fromIso("2023-07-18T20:18:25.177-03:00"));
+ const d8 = (await $std_Datetime.fromComponents({ year: 2023, month: 6, day: 18, hour: 20, min: 18, sec: 25, ms: 177, tz: 180 }));
+ {((cond) => {if (!cond) throw new Error("assertion failed: d7.timestampMs == 1689722305177")})((((a,b) => { try { return require('assert').deepStrictEqual(a,b) === undefined; } catch { return false; } })(d7.timestampMs,1689722305177)))};
+ {((cond) => {if (!cond) throw new Error("assertion failed: d7.hours == 23")})((((a,b) => { try { return require('assert').deepStrictEqual(a,b) === undefined; } catch { return false; } })(d7.hours,23)))};
+ {((cond) => {if (!cond) throw new Error("assertion failed: d7.min == 18")})((((a,b) => { try { return require('assert').deepStrictEqual(a,b) === undefined; } catch { return false; } })(d7.min,18)))};
+ {((cond) => {if (!cond) throw new Error("assertion failed: d7.sec == 25")})((((a,b) => { try { return require('assert').deepStrictEqual(a,b) === undefined; } catch { return false; } })(d7.sec,25)))};
+ {((cond) => {if (!cond) throw new Error("assertion failed: d7.ms == 177")})((((a,b) => { try { return require('assert').deepStrictEqual(a,b) === undefined; } catch { return false; } })(d7.ms,177)))};
+ {((cond) => {if (!cond) throw new Error("assertion failed: d7.dayOfMonth == 18")})((((a,b) => { try { return require('assert').deepStrictEqual(a,b) === undefined; } catch { return false; } })(d7.dayOfMonth,18)))};
+ {((cond) => {if (!cond) throw new Error("assertion failed: d7.dayOfWeek == 2")})((((a,b) => { try { return require('assert').deepStrictEqual(a,b) === undefined; } catch { return false; } })(d7.dayOfWeek,2)))};
+ {((cond) => {if (!cond) throw new Error("assertion failed: d7.month == 6")})((((a,b) => { try { return require('assert').deepStrictEqual(a,b) === undefined; } catch { return false; } })(d7.month,6)))};
+ {((cond) => {if (!cond) throw new Error("assertion failed: d7.year == 2023")})((((a,b) => { try { return require('assert').deepStrictEqual(a,b) === undefined; } catch { return false; } })(d7.year,2023)))};
+ {((cond) => {if (!cond) throw new Error("assertion failed: d8.hours == 20")})((((a,b) => { try { return require('assert').deepStrictEqual(a,b) === undefined; } catch { return false; } })(d8.hours,20)))};
+ {((cond) => {if (!cond) throw new Error("assertion failed: math.floor(d7.timestamp) == math.floor(d8.timestamp)")})((((a,b) => { try { return require('assert').deepStrictEqual(a,b) === undefined; } catch { return false; } })((await $math_Util.floor(d7.timestamp)),(await $math_Util.floor(d8.timestamp)))))};
+ {((cond) => {if (!cond) throw new Error("assertion failed: d4.toUtc().hours == (d4.hours + (d4.timezone / 60))")})((((a,b) => { try { return require('assert').deepStrictEqual(a,b) === undefined; } catch { return false; } })($_d4_toUtc____hours,($d4_hours + ($d4_timezone / 60)))))};
+ {((cond) => {if (!cond) throw new Error("assertion failed: d8.toUtc().hours == (d8.hours + (d8.timezone / 60))")})((((a,b) => { try { return require('assert').deepStrictEqual(a,b) === undefined; } catch { return false; } })((await d8.toUtc()).hours,(d8.hours + (d8.timezone / 60)))))};
+ const beforeSleep = (await $std_Datetime.systemNow());
+ (await $util_Util.sleep((await $std_Duration.fromSeconds(1))));
+ {((cond) => {if (!cond) throw new Error("assertion failed: math.floor(datetime.systemNow().timestamp - beforeSleep.timestamp) == 1")})((((a,b) => { try { return require('assert').deepStrictEqual(a,b) === undefined; } catch { return false; } })((await $math_Util.floor(((await $std_Datetime.systemNow()).timestamp - beforeSleep.timestamp))),1)))};
+ }
+ }
+ return $Closure1;
+}
+
+```
+
+## main.tf.json
+```json
+{
+ "//": {
+ "metadata": {
+ "backend": "local",
+ "stackName": "root",
+ "version": "0.17.0"
+ },
+ "outputs": {
+ "root": {
+ "Default": {
+ "cloud.TestRunner": {
+ "TestFunctionArns": "WING_TEST_RUNNER_FUNCTION_ARNS"
+ }
+ }
+ }
+ }
+ },
+ "output": {
+ "WING_TEST_RUNNER_FUNCTION_ARNS": {
+ "value": "[[\"root/Default/Default/test:inflight datetime\",\"${aws_lambda_function.testinflightdatetime_Handler_CCA19CA1.arn}\"]]"
+ }
+ },
+ "provider": {
+ "aws": [
+ {}
+ ]
+ },
+ "resource": {
+ "aws_iam_role": {
+ "testinflightdatetime_Handler_IamRole_F29772B9": {
+ "//": {
+ "metadata": {
+ "path": "root/Default/Default/test:inflight datetime/Handler/IamRole",
+ "uniqueId": "testinflightdatetime_Handler_IamRole_F29772B9"
+ }
+ },
+ "assume_role_policy": "{\"Version\":\"2012-10-17\",\"Statement\":[{\"Action\":\"sts:AssumeRole\",\"Principal\":{\"Service\":\"lambda.amazonaws.com\"},\"Effect\":\"Allow\"}]}"
+ }
+ },
+ "aws_iam_role_policy": {
+ "testinflightdatetime_Handler_IamRolePolicy_BA52AB3F": {
+ "//": {
+ "metadata": {
+ "path": "root/Default/Default/test:inflight datetime/Handler/IamRolePolicy",
+ "uniqueId": "testinflightdatetime_Handler_IamRolePolicy_BA52AB3F"
+ }
+ },
+ "policy": "{\"Version\":\"2012-10-17\",\"Statement\":[{\"Effect\":\"Allow\",\"Action\":\"none:null\",\"Resource\":\"*\"}]}",
+ "role": "${aws_iam_role.testinflightdatetime_Handler_IamRole_F29772B9.name}"
+ }
+ },
+ "aws_iam_role_policy_attachment": {
+ "testinflightdatetime_Handler_IamRolePolicyAttachment_BBFA8051": {
+ "//": {
+ "metadata": {
+ "path": "root/Default/Default/test:inflight datetime/Handler/IamRolePolicyAttachment",
+ "uniqueId": "testinflightdatetime_Handler_IamRolePolicyAttachment_BBFA8051"
+ }
+ },
+ "policy_arn": "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole",
+ "role": "${aws_iam_role.testinflightdatetime_Handler_IamRole_F29772B9.name}"
+ }
+ },
+ "aws_lambda_function": {
+ "testinflightdatetime_Handler_CCA19CA1": {
+ "//": {
+ "metadata": {
+ "path": "root/Default/Default/test:inflight datetime/Handler/Default",
+ "uniqueId": "testinflightdatetime_Handler_CCA19CA1"
+ }
+ },
+ "environment": {
+ "variables": {
+ "WING_FUNCTION_NAME": "Handler-c8211bab",
+ "WING_TARGET": "tf-aws"
+ }
+ },
+ "function_name": "Handler-c8211bab",
+ "handler": "index.handler",
+ "publish": true,
+ "role": "${aws_iam_role.testinflightdatetime_Handler_IamRole_F29772B9.arn}",
+ "runtime": "nodejs18.x",
+ "s3_bucket": "${aws_s3_bucket.Code.bucket}",
+ "s3_key": "${aws_s3_object.testinflightdatetime_Handler_S3Object_972D133E.key}",
+ "timeout": 30,
+ "vpc_config": {
+ "security_group_ids": [],
+ "subnet_ids": []
+ }
+ }
+ },
+ "aws_s3_bucket": {
+ "Code": {
+ "//": {
+ "metadata": {
+ "path": "root/Default/Code",
+ "uniqueId": "Code"
+ }
+ },
+ "bucket_prefix": "code-c84a50b1-"
+ }
+ },
+ "aws_s3_object": {
+ "testinflightdatetime_Handler_S3Object_972D133E": {
+ "//": {
+ "metadata": {
+ "path": "root/Default/Default/test:inflight datetime/Handler/S3Object",
+ "uniqueId": "testinflightdatetime_Handler_S3Object_972D133E"
+ }
+ },
+ "bucket": "${aws_s3_bucket.Code.bucket}",
+ "key": "",
+ "source": ""
+ }
+ }
+ }
+}
+```
+
+## preflight.js
+```js
+const $stdlib = require('@winglang/sdk');
+const $outdir = process.env.WING_SYNTH_DIR ?? ".";
+const std = $stdlib.std;
+const $wing_is_test = process.env.WING_IS_TEST === "true";
+const cloud = require('@winglang/sdk').cloud;
+const util = require('@winglang/sdk').util;
+const math = require('@winglang/sdk').math;
+class $Root extends $stdlib.std.Resource {
+ constructor(scope, id) {
+ super(scope, id);
+ class $Closure1 extends $stdlib.std.Resource {
+ constructor(scope, id, ) {
+ super(scope, id);
+ this._addInflightOps("handle", "$inflight_init");
+ this.display.hidden = true;
+ }
+ static _toInflightType(context) {
+ return $stdlib.core.NodeJsCode.fromInline(`
+ require("./inflight.$Closure1.js")({
+ $_d4_toUtc____hours: ${context._lift((d4.toUtc()).hours)},
+ $d1_timestamp: ${context._lift(d1.timestamp)},
+ $d2_timestamp: ${context._lift(d2.timestamp)},
+ $d4_hours: ${context._lift(d4.hours)},
+ $d4_timezone: ${context._lift(d4.timezone)},
+ $math_Util: ${context._lift(math.Util)},
+ $std_Datetime: ${context._lift(std.Datetime)},
+ $std_Duration: ${context._lift(std.Duration)},
+ $util_Util: ${context._lift(util.Util)},
+ })
+ `);
+ }
+ _toInflight() {
+ return $stdlib.core.NodeJsCode.fromInline(`
+ (await (async () => {
+ const $Closure1Client = ${$Closure1._toInflightType(this).text};
+ const client = new $Closure1Client({
+ });
+ if (client.$inflight_init) { await client.$inflight_init(); }
+ return client;
+ })())
+ `);
+ }
+ _registerBind(host, ops) {
+ if (ops.includes("handle")) {
+ $Closure1._registerBindObject((d4.toUtc()).hours, host, []);
+ $Closure1._registerBindObject(d1.timestamp, host, []);
+ $Closure1._registerBindObject(d2.timestamp, host, []);
+ $Closure1._registerBindObject(d4.hours, host, []);
+ $Closure1._registerBindObject(d4.timezone, host, []);
+ }
+ super._registerBind(host, ops);
+ }
+ }
+ const d1 = (std.Datetime.utcNow());
+ const d2 = (std.Datetime.systemNow());
+ {((cond) => {if (!cond) throw new Error("assertion failed: math.floor(d2.timestamp) == math.floor(d1.timestamp)")})((((a,b) => { try { return require('assert').deepStrictEqual(a,b) === undefined; } catch { return false; } })((math.Util.floor(d2.timestamp)),(math.Util.floor(d1.timestamp)))))};
+ const d3 = (std.Datetime.fromIso("2023-07-18T20:18:25.177+03:00"));
+ {((cond) => {if (!cond) throw new Error("assertion failed: d3.timestampMs == 1689700705177")})((((a,b) => { try { return require('assert').deepStrictEqual(a,b) === undefined; } catch { return false; } })(d3.timestampMs,1689700705177)))};
+ {((cond) => {if (!cond) throw new Error("assertion failed: d3.hours == 17")})((((a,b) => { try { return require('assert').deepStrictEqual(a,b) === undefined; } catch { return false; } })(d3.hours,17)))};
+ {((cond) => {if (!cond) throw new Error("assertion failed: d3.min == 18")})((((a,b) => { try { return require('assert').deepStrictEqual(a,b) === undefined; } catch { return false; } })(d3.min,18)))};
+ {((cond) => {if (!cond) throw new Error("assertion failed: d3.sec == 25")})((((a,b) => { try { return require('assert').deepStrictEqual(a,b) === undefined; } catch { return false; } })(d3.sec,25)))};
+ {((cond) => {if (!cond) throw new Error("assertion failed: d3.ms == 177")})((((a,b) => { try { return require('assert').deepStrictEqual(a,b) === undefined; } catch { return false; } })(d3.ms,177)))};
+ {((cond) => {if (!cond) throw new Error("assertion failed: d3.dayOfMonth == 18")})((((a,b) => { try { return require('assert').deepStrictEqual(a,b) === undefined; } catch { return false; } })(d3.dayOfMonth,18)))};
+ {((cond) => {if (!cond) throw new Error("assertion failed: d3.dayOfWeek == 2")})((((a,b) => { try { return require('assert').deepStrictEqual(a,b) === undefined; } catch { return false; } })(d3.dayOfWeek,2)))};
+ {((cond) => {if (!cond) throw new Error("assertion failed: d3.month == 6")})((((a,b) => { try { return require('assert').deepStrictEqual(a,b) === undefined; } catch { return false; } })(d3.month,6)))};
+ {((cond) => {if (!cond) throw new Error("assertion failed: d3.year == 2023")})((((a,b) => { try { return require('assert').deepStrictEqual(a,b) === undefined; } catch { return false; } })(d3.year,2023)))};
+ const d4 = (std.Datetime.fromComponents({ year: 2023, month: 6, day: 18, hour: 19, min: 18, sec: 25, ms: 177, tz: (-120) }));
+ {((cond) => {if (!cond) throw new Error("assertion failed: d4.timezone == -120")})((((a,b) => { try { return require('assert').deepStrictEqual(a,b) === undefined; } catch { return false; } })(d4.timezone,(-120))))};
+ {((cond) => {if (!cond) throw new Error("assertion failed: d4.timestampMs == 1689700705177")})((((a,b) => { try { return require('assert').deepStrictEqual(a,b) === undefined; } catch { return false; } })(d4.timestampMs,1689700705177)))};
+ {((cond) => {if (!cond) throw new Error("assertion failed: d4.hours == 19")})((((a,b) => { try { return require('assert').deepStrictEqual(a,b) === undefined; } catch { return false; } })(d4.hours,19)))};
+ {((cond) => {if (!cond) throw new Error("assertion failed: d4.min == 18")})((((a,b) => { try { return require('assert').deepStrictEqual(a,b) === undefined; } catch { return false; } })(d4.min,18)))};
+ {((cond) => {if (!cond) throw new Error("assertion failed: d4.sec == 25")})((((a,b) => { try { return require('assert').deepStrictEqual(a,b) === undefined; } catch { return false; } })(d4.sec,25)))};
+ {((cond) => {if (!cond) throw new Error("assertion failed: d4.ms == 177")})((((a,b) => { try { return require('assert').deepStrictEqual(a,b) === undefined; } catch { return false; } })(d4.ms,177)))};
+ {((cond) => {if (!cond) throw new Error("assertion failed: d4.dayOfMonth == 18")})((((a,b) => { try { return require('assert').deepStrictEqual(a,b) === undefined; } catch { return false; } })(d4.dayOfMonth,18)))};
+ {((cond) => {if (!cond) throw new Error("assertion failed: d4.dayOfWeek == 2")})((((a,b) => { try { return require('assert').deepStrictEqual(a,b) === undefined; } catch { return false; } })(d4.dayOfWeek,2)))};
+ {((cond) => {if (!cond) throw new Error("assertion failed: d4.month == 6")})((((a,b) => { try { return require('assert').deepStrictEqual(a,b) === undefined; } catch { return false; } })(d4.month,6)))};
+ {((cond) => {if (!cond) throw new Error("assertion failed: d4.year == 2023")})((((a,b) => { try { return require('assert').deepStrictEqual(a,b) === undefined; } catch { return false; } })(d4.year,2023)))};
+ {((cond) => {if (!cond) throw new Error("assertion failed: d4.toUtc().hours == (d4.hours + (d4.timezone / 60))")})((((a,b) => { try { return require('assert').deepStrictEqual(a,b) === undefined; } catch { return false; } })((d4.toUtc()).hours,(d4.hours + (d4.timezone / 60)))))};
+ this.node.root.new("@winglang/sdk.std.Test",std.Test,this,"test:inflight datetime",new $Closure1(this,"$Closure1"));
+ }
+}
+const $App = $stdlib.core.App.for(process.env.WING_TARGET);
+new $App({ outdir: $outdir, name: "datetime", rootConstruct: $Root, plugins: $plugins, isTestEnvironment: $wing_is_test }).synth();
+
+```
+
diff --git a/tools/hangar/__snapshots__/test_corpus/sdk_tests/std/datetime.w_test_sim.md b/tools/hangar/__snapshots__/test_corpus/sdk_tests/std/datetime.w_test_sim.md
new file mode 100644
index 00000000000..f97bc041428
--- /dev/null
+++ b/tools/hangar/__snapshots__/test_corpus/sdk_tests/std/datetime.w_test_sim.md
@@ -0,0 +1,12 @@
+# [datetime.w](../../../../../../examples/tests/sdk_tests/std/datetime.w) | test | sim
+
+## stdout.log
+```log
+pass ─ datetime.wsim » root/env0/test:inflight datetime
+
+
+Tests 1 passed (1)
+Test Files 1 passed (1)
+Duration
+```
+