@kensio/yulin 1.21.21 → 1.21.22
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/service/athena/engine/sim-athena-engine-result.d.ts +9 -1
- package/dist/service/athena/engine/sim-athena-engine-result.js +15 -4
- package/dist/service/athena/engine/sim-athena-engine-run.d.ts +2 -0
- package/dist/service/athena/engine/sim-athena-engine-run.js +6 -1
- package/dist/service/athena/engine/sim-athena-query-engine.js +1 -0
- package/dist/service/athena/engine/sim-athena-result-columns.d.ts +1 -1
- package/dist/service/athena/engine/sim-athena-result-columns.js +11 -4
- package/dist/service/athena/engine/sim-athena-round-scales.d.ts +19 -0
- package/dist/service/athena/engine/sim-athena-round-scales.js +80 -0
- package/dist/service/athena/engine/sim-athena-sql-translation.d.ts +9 -0
- package/dist/service/athena/engine/sim-athena-sql-translation.js +17 -2
- package/dist/service/lambda/cfn/event-source-mapping/sim-cfn-lambda-event-source-mapping-properties.js +2 -0
- package/dist/service/lambda/cfn/event-source-mapping/sim-cfn-lambda-event-source-mapping-property-names.js +1 -5
- package/dist/service/lambda/command/event-source-mapping/create-event-source-mapping-input.d.ts +5 -0
- package/dist/service/lambda/command/event-source-mapping/create-event-source-mapping-input.js +7 -0
- package/dist/service/lambda/command/event-source-mapping/create-event-source-mapping-refusals.js +0 -1
- package/dist/service/lambda/command/event-source-mapping/create-event-source-mapping.handler.js +1 -0
- package/dist/service/lambda/command/event-source-mapping/event-source-mapping.command.d.ts +2 -1
- package/dist/service/lambda/command/event-source-mapping/event-source-mapping.command.js +0 -6
- package/dist/service/lambda/event-source/filter/sim-lambda-filter-criteria.d.ts +48 -0
- package/dist/service/lambda/event-source/filter/sim-lambda-filter-criteria.js +129 -0
- package/dist/service/lambda/event-source/filter/sim-lambda-filtered-delivery.d.ts +27 -0
- package/dist/service/lambda/event-source/filter/sim-lambda-filtered-delivery.js +15 -0
- package/dist/service/lambda/event-source/filter/sim-lambda-filtered-records.d.ts +24 -0
- package/dist/service/lambda/event-source/filter/sim-lambda-filtered-records.js +35 -0
- package/dist/service/lambda/event-source/poll/kinesis/sim-lambda-kinesis-shard-poller.d.ts +7 -4
- package/dist/service/lambda/event-source/poll/kinesis/sim-lambda-kinesis-shard-poller.js +9 -6
- package/dist/service/lambda/event-source/poll/kinesis/sim-lambda-kinesis-stream-delivery.d.ts +16 -4
- package/dist/service/lambda/event-source/poll/kinesis/sim-lambda-kinesis-stream-delivery.js +36 -5
- package/dist/service/lambda/event-source/poll/kinesis/sim-lambda-kinesis-stream-event-source-poller.d.ts +4 -3
- package/dist/service/lambda/event-source/poll/kinesis/sim-lambda-kinesis-stream-event-source-poller.js +6 -7
- package/dist/service/lambda/event-source/poll/kinesis/sim-lambda-kinesis-stream-event.d.ts +14 -2
- package/dist/service/lambda/event-source/poll/kinesis/sim-lambda-kinesis-stream-event.js +21 -2
- package/dist/service/lambda/event-source/poll/sim-lambda-dynamodb-stream-delivery.d.ts +19 -6
- package/dist/service/lambda/event-source/poll/sim-lambda-dynamodb-stream-delivery.js +41 -7
- package/dist/service/lambda/event-source/poll/sim-lambda-dynamodb-stream-event-source-poller.d.ts +7 -3
- package/dist/service/lambda/event-source/poll/sim-lambda-dynamodb-stream-event-source-poller.js +10 -9
- package/dist/service/lambda/event-source/poll/sim-lambda-dynamodb-stream-event.d.ts +6 -2
- package/dist/service/lambda/event-source/poll/sim-lambda-dynamodb-stream-event.js +6 -2
- package/dist/service/lambda/event-source/poll/sim-lambda-event-source-delivery.d.ts +21 -1
- package/dist/service/lambda/event-source/poll/sim-lambda-event-source-delivery.js +32 -2
- package/dist/service/lambda/event-source/poll/sim-lambda-sqs-delivery.js +1 -0
- package/dist/service/lambda/event-source/poll/sim-lambda-sqs-event.d.ts +8 -0
- package/dist/service/lambda/event-source/poll/sim-lambda-sqs-event.js +11 -0
- package/dist/service/lambda/event-source/sim-lambda-event-source-mapping.d.ts +10 -0
- package/dist/service/lambda/event-source/sim-lambda-event-source-mapping.js +11 -0
- package/dist/service/lambda/event-source/stream/sim-lambda-stream-cascade-guard.d.ts +35 -8
- package/dist/service/lambda/event-source/stream/sim-lambda-stream-cascade-guard.js +35 -12
- package/dist/service/lambda/event-source/stream/sim-lambda-stream-cascade.error.d.ts +13 -4
- package/dist/service/lambda/event-source/stream/sim-lambda-stream-cascade.error.js +12 -6
- package/dist/service/lambda/event-source/stream/sim-lambda-stream-halt.d.ts +18 -0
- package/dist/service/lambda/event-source/stream/sim-lambda-stream-halt.js +22 -0
- package/dist/service/scheduler/command/schedule/schedule.command.d.ts +1 -0
- package/dist/service/scheduler/command/schedule/sim-scheduler-described-schedule.js +1 -0
- package/dist/service/scheduler/command/schedule/sim-scheduler-schedule-writer.js +2 -1
- package/dist/service/scheduler/command/schedule/sim-scheduler-unsimulated-input.d.ts +3 -3
- package/dist/service/scheduler/command/schedule/sim-scheduler-unsimulated-input.js +19 -11
- package/dist/service/scheduler/schedule/sim-scheduler-schedule-expression.d.ts +5 -3
- package/dist/service/scheduler/schedule/sim-scheduler-schedule-expression.js +6 -4
- package/dist/service/scheduler/schedule/sim-scheduler-schedule.d.ts +9 -0
- package/dist/service/scheduler/schedule/sim-scheduler-schedule.js +8 -0
- package/dist/util/schedule/at/sim-at-expression.d.ts +2 -1
- package/dist/util/schedule/at/sim-at-expression.js +7 -3
- package/dist/util/schedule/cron/sim-cron-expression.d.ts +6 -3
- package/dist/util/schedule/cron/sim-cron-expression.js +24 -21
- package/dist/util/schedule/sim-schedule-zone-clock.d.ts +26 -0
- package/dist/util/schedule/sim-schedule-zone-clock.js +56 -0
- package/dist/util/schedule/sim-schedule-zone.d.ts +58 -0
- package/dist/util/schedule/sim-schedule-zone.js +83 -0
- package/dist/util/schedule/sim-schedule.d.ts +1 -1
- package/dist/util/schedule/sim-schedule.js +6 -5
- package/docs/services/athena/README.md +5 -0
- package/docs/services/lambda/README.md +159 -15
- package/docs/services/scheduler/README.md +35 -6
- package/package.json +1 -1
|
@@ -21,6 +21,8 @@ interface SimSchedulerScheduleProperties {
|
|
|
21
21
|
readonly state: SimSchedulerScheduleState;
|
|
22
22
|
readonly actionAfterCompletion: SimSchedulerActionAfterCompletion;
|
|
23
23
|
readonly description?: string | undefined;
|
|
24
|
+
/** The timezone the request named, which a describe reports back. */
|
|
25
|
+
readonly timeZone?: string | undefined;
|
|
24
26
|
readonly createdAt: Date;
|
|
25
27
|
}
|
|
26
28
|
/**
|
|
@@ -51,6 +53,13 @@ export declare class SimSchedulerSchedule {
|
|
|
51
53
|
readonly target: SimSchedulerTarget;
|
|
52
54
|
readonly actionAfterCompletion: SimSchedulerActionAfterCompletion;
|
|
53
55
|
readonly description: string | undefined;
|
|
56
|
+
/**
|
|
57
|
+
* The timezone the schedule was created with, if it named one.
|
|
58
|
+
*
|
|
59
|
+
* A schedule created without one runs in UTC, and reports no timezone rather
|
|
60
|
+
* than reporting the one it fell back to.
|
|
61
|
+
*/
|
|
62
|
+
readonly timeZone: string | undefined;
|
|
54
63
|
readonly creationDate: Date;
|
|
55
64
|
/**
|
|
56
65
|
* When this schedule falls due, which is read as simulated time advances.
|
|
@@ -28,6 +28,13 @@ export class SimSchedulerSchedule {
|
|
|
28
28
|
target;
|
|
29
29
|
actionAfterCompletion;
|
|
30
30
|
description;
|
|
31
|
+
/**
|
|
32
|
+
* The timezone the schedule was created with, if it named one.
|
|
33
|
+
*
|
|
34
|
+
* A schedule created without one runs in UTC, and reports no timezone rather
|
|
35
|
+
* than reporting the one it fell back to.
|
|
36
|
+
*/
|
|
37
|
+
timeZone;
|
|
31
38
|
creationDate;
|
|
32
39
|
/**
|
|
33
40
|
* When this schedule falls due, which is read as simulated time advances.
|
|
@@ -45,6 +52,7 @@ export class SimSchedulerSchedule {
|
|
|
45
52
|
this.target = properties.target;
|
|
46
53
|
this.actionAfterCompletion = properties.actionAfterCompletion;
|
|
47
54
|
this.description = properties.description;
|
|
55
|
+
this.timeZone = properties.timeZone;
|
|
48
56
|
this.creationDate = properties.createdAt;
|
|
49
57
|
this.modified = properties.createdAt;
|
|
50
58
|
this.held = properties.state;
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { SimScheduleZone } from "../sim-schedule-zone.js";
|
|
1
2
|
/**
|
|
2
3
|
* One `at()` expression: a single instant, and never another.
|
|
3
4
|
*
|
|
@@ -12,7 +13,7 @@ export declare class SimAtExpression {
|
|
|
12
13
|
/**
|
|
13
14
|
* Read the instant inside an `at(...)`.
|
|
14
15
|
*/
|
|
15
|
-
static of(source: string): SimAtExpression;
|
|
16
|
+
static of(source: string, zone: SimScheduleZone): SimAtExpression;
|
|
16
17
|
/**
|
|
17
18
|
* The instant this expression names, once, and nothing after that.
|
|
18
19
|
*
|
|
@@ -7,6 +7,7 @@ import { SimScheduleExpressionError } from "../sim-schedule.error.js";
|
|
|
7
7
|
* schedule rather than part of the expression, which is why one written here
|
|
8
8
|
* would be ambiguous rather than helpful.
|
|
9
9
|
*/
|
|
10
|
+
const millisecondsPerSecond = 1000;
|
|
10
11
|
const atInstant = /^(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})T(?<hour>\d{2}):(?<minute>\d{2}):(?<second>\d{2})$/u;
|
|
11
12
|
/**
|
|
12
13
|
* One `at()` expression: a single instant, and never another.
|
|
@@ -25,7 +26,7 @@ export class SimAtExpression {
|
|
|
25
26
|
/**
|
|
26
27
|
* Read the instant inside an `at(...)`.
|
|
27
28
|
*/
|
|
28
|
-
static of(source) {
|
|
29
|
+
static of(source, zone) {
|
|
29
30
|
const read = atInstant.exec(source.trim())?.groups;
|
|
30
31
|
if (read === undefined) {
|
|
31
32
|
throw new SimScheduleExpressionError(`an at expression is an instant written 'yyyy-mm-ddThh:mm:ss', with ` +
|
|
@@ -37,11 +38,14 @@ export class SimAtExpression {
|
|
|
37
38
|
// JavaScript rolls an impossible date over rather than refusing it, so the
|
|
38
39
|
// thirtieth of February quietly becomes the second of March and half past
|
|
39
40
|
// twenty-five becomes tomorrow morning. Writing the instant back out is
|
|
40
|
-
// what catches every one of those in one comparison.
|
|
41
|
+
// what catches every one of those in one comparison. The calendar is read
|
|
42
|
+
// in UTC for this, since which instant the wall-clock time is depends on
|
|
43
|
+
// the zone and whether it is a real time of day does not.
|
|
41
44
|
if (!at.toISOString().startsWith(written)) {
|
|
42
45
|
throw new SimScheduleExpressionError(`an at expression names a real instant, and '${source}' is not one`);
|
|
43
46
|
}
|
|
44
|
-
|
|
47
|
+
const minuteStart = zone.instantOf(Number(year), Number(month) - 1, Number(day), Number(hour), Number(minute));
|
|
48
|
+
return new this(source, new Date(minuteStart + Number(second) * millisecondsPerSecond));
|
|
45
49
|
}
|
|
46
50
|
/**
|
|
47
51
|
* The instant this expression names, once, and nothing after that.
|
|
@@ -1,13 +1,16 @@
|
|
|
1
|
+
import type { SimScheduleZone } from "../sim-schedule-zone.js";
|
|
1
2
|
import type { SimCronFieldSpec } from "./sim-cron-field-spec.js";
|
|
2
3
|
/**
|
|
3
4
|
* One cron expression, read into the instants it falls due at.
|
|
4
5
|
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
6
|
+
* The fields name a wall-clock time, and the schedule's zone is what says which
|
|
7
|
+
* instants those are. An EventBridge rule has no timezone setting and gets the
|
|
8
|
+
* UTC zone, which reads exactly as the UTC calendar does.
|
|
7
9
|
*/
|
|
8
10
|
export declare class SimCronExpression {
|
|
9
11
|
readonly source: string;
|
|
10
12
|
private readonly fields;
|
|
13
|
+
private readonly zone;
|
|
11
14
|
private constructor();
|
|
12
15
|
/**
|
|
13
16
|
* Read a cron expression against a dialect's fields.
|
|
@@ -16,7 +19,7 @@ export declare class SimCronExpression {
|
|
|
16
19
|
* is refused naming the form expected rather than read as though the missing
|
|
17
20
|
* one were at the end.
|
|
18
21
|
*/
|
|
19
|
-
static of(specs: readonly SimCronFieldSpec[], source: string): SimCronExpression;
|
|
22
|
+
static of(specs: readonly SimCronFieldSpec[], source: string, zone: SimScheduleZone): SimCronExpression;
|
|
20
23
|
/**
|
|
21
24
|
* The next instant this expression falls due after an instant.
|
|
22
25
|
*
|
|
@@ -17,15 +17,18 @@ function startOfNextMinute(instant) {
|
|
|
17
17
|
/**
|
|
18
18
|
* One cron expression, read into the instants it falls due at.
|
|
19
19
|
*
|
|
20
|
-
*
|
|
21
|
-
*
|
|
20
|
+
* The fields name a wall-clock time, and the schedule's zone is what says which
|
|
21
|
+
* instants those are. An EventBridge rule has no timezone setting and gets the
|
|
22
|
+
* UTC zone, which reads exactly as the UTC calendar does.
|
|
22
23
|
*/
|
|
23
24
|
export class SimCronExpression {
|
|
24
25
|
source;
|
|
25
26
|
fields;
|
|
26
|
-
|
|
27
|
+
zone;
|
|
28
|
+
constructor(source, fields, zone) {
|
|
27
29
|
this.source = source;
|
|
28
30
|
this.fields = fields;
|
|
31
|
+
this.zone = zone;
|
|
29
32
|
}
|
|
30
33
|
/**
|
|
31
34
|
* Read a cron expression against a dialect's fields.
|
|
@@ -34,14 +37,14 @@ export class SimCronExpression {
|
|
|
34
37
|
* is refused naming the form expected rather than read as though the missing
|
|
35
38
|
* one were at the end.
|
|
36
39
|
*/
|
|
37
|
-
static of(specs, source) {
|
|
40
|
+
static of(specs, source, zone) {
|
|
38
41
|
const written = source.trim().split(whitespace);
|
|
39
42
|
if (written.length !== specs.length) {
|
|
40
43
|
throw new SimScheduleExpressionError(`a cron expression has ${String(specs.length)} fields separated by ` +
|
|
41
44
|
`spaces, ${specs.map((spec) => spec.name).join(" ")}, and this one ` +
|
|
42
45
|
`has ${String(written.length)}`);
|
|
43
46
|
}
|
|
44
|
-
return new this(source, readFields(specs, written));
|
|
47
|
+
return new this(source, readFields(specs, written), zone);
|
|
45
48
|
}
|
|
46
49
|
/**
|
|
47
50
|
* The next instant this expression falls due after an instant.
|
|
@@ -50,7 +53,7 @@ export class SimCronExpression {
|
|
|
50
53
|
* has, since a cron expression naming only years in the past falls due never.
|
|
51
54
|
*/
|
|
52
55
|
nextAfter(instant) {
|
|
53
|
-
const end =
|
|
56
|
+
const end = this.zone.instantOf(this.fields.year.maximum + 1, 0, 1);
|
|
54
57
|
let candidate = startOfNextMinute(instant);
|
|
55
58
|
while (candidate < end) {
|
|
56
59
|
const skipTo = this.skipUnmatched(candidate);
|
|
@@ -71,27 +74,27 @@ export class SimCronExpression {
|
|
|
71
74
|
* steps rather than the million minutes they hold.
|
|
72
75
|
*/
|
|
73
76
|
skipUnmatched(at) {
|
|
74
|
-
const
|
|
75
|
-
const year =
|
|
77
|
+
const read = this.zone.partsAt(at);
|
|
78
|
+
const { year, month, day, hour } = read;
|
|
76
79
|
if (!this.fields.year.allows(year)) {
|
|
77
|
-
return
|
|
80
|
+
return this.zone.instantOf(year + 1, 0, 1);
|
|
78
81
|
}
|
|
79
|
-
const month = date.getUTCMonth();
|
|
80
82
|
if (!this.fields.month.allows(month + 1)) {
|
|
81
|
-
return
|
|
83
|
+
return this.zone.instantOf(year, month + 1, 1);
|
|
82
84
|
}
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
return Date.UTC(year, month, day + 1);
|
|
85
|
+
if (!this.allowsDay(read)) {
|
|
86
|
+
return this.zone.instantOf(year, month, day + 1);
|
|
86
87
|
}
|
|
87
|
-
const hour = date.getUTCHours();
|
|
88
88
|
if (!this.fields.hours.allows(hour)) {
|
|
89
|
-
return
|
|
89
|
+
return this.zone.instantOf(year, month, day, hour + 1);
|
|
90
90
|
}
|
|
91
|
-
if (!this.fields.minutes.allows(
|
|
91
|
+
if (!this.fields.minutes.allows(read.minute)) {
|
|
92
92
|
return at + millisecondsPerMinute;
|
|
93
93
|
}
|
|
94
|
-
|
|
94
|
+
// The second of a repeated hour reads the same wall clock as the first and
|
|
95
|
+
// has already been answered with. Real Scheduler fires once when the clocks
|
|
96
|
+
// go back, rather than twice.
|
|
97
|
+
return this.zone.isCanonical(at) ? undefined : at + millisecondsPerMinute;
|
|
95
98
|
}
|
|
96
99
|
/**
|
|
97
100
|
* Whether this expression names a day.
|
|
@@ -99,12 +102,12 @@ export class SimCronExpression {
|
|
|
99
102
|
* Whichever of the two day fields is not `?` is the one that decides, which
|
|
100
103
|
* is why they cannot both say something.
|
|
101
104
|
*/
|
|
102
|
-
allowsDay(
|
|
105
|
+
allowsDay(read) {
|
|
103
106
|
if (this.fields.dayOfMonth.isAny) {
|
|
104
107
|
// AWS numbers the week from Sunday as one, and JavaScript from Sunday as
|
|
105
108
|
// zero.
|
|
106
|
-
return this.fields.dayOfWeek.allows(
|
|
109
|
+
return this.fields.dayOfWeek.allows(read.weekday + 1);
|
|
107
110
|
}
|
|
108
|
-
return this.fields.dayOfMonth.allows(
|
|
111
|
+
return this.fields.dayOfMonth.allows(read.day);
|
|
109
112
|
}
|
|
110
113
|
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The wall-clock reading of one instant, as a schedule expression names it.
|
|
3
|
+
*/
|
|
4
|
+
export interface SimScheduleZoneParts {
|
|
5
|
+
readonly year: number;
|
|
6
|
+
/** The month as a JavaScript `Date` numbers it, January being zero. */
|
|
7
|
+
readonly month: number;
|
|
8
|
+
readonly day: number;
|
|
9
|
+
readonly hour: number;
|
|
10
|
+
readonly minute: number;
|
|
11
|
+
/** The day of the week, Sunday being zero. */
|
|
12
|
+
readonly weekday: number;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Reads what a clock in one zone says at an instant.
|
|
16
|
+
*/
|
|
17
|
+
export type SimScheduleZoneClock = (instant: number) => SimScheduleZoneParts;
|
|
18
|
+
/**
|
|
19
|
+
* A clock for one zone, refusing a name no zone answers to.
|
|
20
|
+
*
|
|
21
|
+
* `Intl.DateTimeFormat` is what knows the offsets, including the two days a
|
|
22
|
+
* year a zone changes one, so no timezone database travels with this. Which
|
|
23
|
+
* names there are is therefore the host's own, and a name it has never heard
|
|
24
|
+
* of is refused here.
|
|
25
|
+
*/
|
|
26
|
+
export declare function simScheduleZoneClock(name: string): SimScheduleZoneClock;
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { SimScheduleExpressionError } from "./sim-schedule.error.js";
|
|
2
|
+
const weekdays = [
|
|
3
|
+
"Sun",
|
|
4
|
+
"Mon",
|
|
5
|
+
"Tue",
|
|
6
|
+
"Wed",
|
|
7
|
+
"Thu",
|
|
8
|
+
"Fri",
|
|
9
|
+
"Sat",
|
|
10
|
+
];
|
|
11
|
+
/**
|
|
12
|
+
* A clock for one zone, refusing a name no zone answers to.
|
|
13
|
+
*
|
|
14
|
+
* `Intl.DateTimeFormat` is what knows the offsets, including the two days a
|
|
15
|
+
* year a zone changes one, so no timezone database travels with this. Which
|
|
16
|
+
* names there are is therefore the host's own, and a name it has never heard
|
|
17
|
+
* of is refused here.
|
|
18
|
+
*/
|
|
19
|
+
export function simScheduleZoneClock(name) {
|
|
20
|
+
const format = zoneFormat(name);
|
|
21
|
+
return (instant) => {
|
|
22
|
+
const parts = new Map(format
|
|
23
|
+
.formatToParts(new Date(instant))
|
|
24
|
+
.map((part) => [part.type, part.value]));
|
|
25
|
+
return {
|
|
26
|
+
year: Number(parts.get("year")),
|
|
27
|
+
month: Number(parts.get("month")) - 1,
|
|
28
|
+
day: Number(parts.get("day")),
|
|
29
|
+
// Midnight reads as 24 under some locales rather than as zero.
|
|
30
|
+
hour: Number(parts.get("hour")) % 24,
|
|
31
|
+
minute: Number(parts.get("minute")),
|
|
32
|
+
weekday: weekdays.indexOf(parts.get("weekday") ?? ""),
|
|
33
|
+
};
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* The formatter for a zone, which is also what decides the name is one.
|
|
38
|
+
*/
|
|
39
|
+
function zoneFormat(name) {
|
|
40
|
+
try {
|
|
41
|
+
return new Intl.DateTimeFormat("en-US", {
|
|
42
|
+
timeZone: name,
|
|
43
|
+
hourCycle: "h23",
|
|
44
|
+
year: "numeric",
|
|
45
|
+
month: "2-digit",
|
|
46
|
+
day: "2-digit",
|
|
47
|
+
hour: "2-digit",
|
|
48
|
+
minute: "2-digit",
|
|
49
|
+
weekday: "short",
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
catch {
|
|
53
|
+
throw new SimScheduleExpressionError(`'${name}' is not a timezone. A timezone is an IANA name such as ` +
|
|
54
|
+
`'Europe/London', or 'UTC'`);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { type SimScheduleZoneParts } from "./sim-schedule-zone-clock.js";
|
|
2
|
+
/**
|
|
3
|
+
* The zone a schedule expression is read in.
|
|
4
|
+
*
|
|
5
|
+
* A cron expression names wall-clock fields, and which instants those are
|
|
6
|
+
* depends on the zone the schedule was created with. Everything a schedule does
|
|
7
|
+
* with a calendar therefore goes through one of these, and a schedule created
|
|
8
|
+
* without a timezone gets the UTC one, which is what every schedule used to
|
|
9
|
+
* get.
|
|
10
|
+
*/
|
|
11
|
+
export declare class SimScheduleZone {
|
|
12
|
+
/**
|
|
13
|
+
* The zone name as the request wrote it, which a describe reports back.
|
|
14
|
+
*/
|
|
15
|
+
readonly name: string;
|
|
16
|
+
private readonly clock;
|
|
17
|
+
private constructor();
|
|
18
|
+
/**
|
|
19
|
+
* The zone a name calls for, refusing a name no zone answers to.
|
|
20
|
+
*/
|
|
21
|
+
static of(name?: string): SimScheduleZone;
|
|
22
|
+
/**
|
|
23
|
+
* What a clock in this zone reads at an instant.
|
|
24
|
+
*/
|
|
25
|
+
partsAt(instant: number): SimScheduleZoneParts;
|
|
26
|
+
/**
|
|
27
|
+
* The instant a clock in this zone reads a wall-clock time at.
|
|
28
|
+
*
|
|
29
|
+
* Two offsets are in play on the day a zone changes one, so both are tried
|
|
30
|
+
* and the answer is whichever of them the zone actually reads back. A time
|
|
31
|
+
* the clocks read twice has two, and the earlier is the answer, which is the
|
|
32
|
+
* one real Scheduler fires at. A time the clocks skip over has neither, and
|
|
33
|
+
* the answer is the later of the two, which is the first instant after the
|
|
34
|
+
* hour that never happened.
|
|
35
|
+
*/
|
|
36
|
+
instantOf(year: number, month: number, day: number, hour?: number, minute?: number): number;
|
|
37
|
+
/**
|
|
38
|
+
* Whether an instant is the one this zone puts its own wall-clock reading at.
|
|
39
|
+
*
|
|
40
|
+
* The second of a repeated hour reads the same as the first and is not the
|
|
41
|
+
* instant that reading resolves to, which is what tells the two apart.
|
|
42
|
+
*/
|
|
43
|
+
isCanonical(instant: number): boolean;
|
|
44
|
+
/**
|
|
45
|
+
* The instants a wall-clock time could be, under the offsets either side of
|
|
46
|
+
* a change.
|
|
47
|
+
*/
|
|
48
|
+
private candidatesFor;
|
|
49
|
+
/**
|
|
50
|
+
* How far ahead of UTC this zone is at an instant, in milliseconds.
|
|
51
|
+
*/
|
|
52
|
+
private offsetAt;
|
|
53
|
+
/**
|
|
54
|
+
* What a clock in this zone reads at an instant, as a UTC instant of the same
|
|
55
|
+
* wall-clock fields, which is what makes two readings comparable.
|
|
56
|
+
*/
|
|
57
|
+
private wallClockOf;
|
|
58
|
+
}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import { simScheduleZoneClock, } from "./sim-schedule-zone-clock.js";
|
|
2
|
+
const minuteMs = 60_000;
|
|
3
|
+
/**
|
|
4
|
+
* The zone a schedule expression is read in.
|
|
5
|
+
*
|
|
6
|
+
* A cron expression names wall-clock fields, and which instants those are
|
|
7
|
+
* depends on the zone the schedule was created with. Everything a schedule does
|
|
8
|
+
* with a calendar therefore goes through one of these, and a schedule created
|
|
9
|
+
* without a timezone gets the UTC one, which is what every schedule used to
|
|
10
|
+
* get.
|
|
11
|
+
*/
|
|
12
|
+
export class SimScheduleZone {
|
|
13
|
+
/**
|
|
14
|
+
* The zone name as the request wrote it, which a describe reports back.
|
|
15
|
+
*/
|
|
16
|
+
name;
|
|
17
|
+
clock;
|
|
18
|
+
constructor(name, clock) {
|
|
19
|
+
this.name = name;
|
|
20
|
+
this.clock = clock;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* The zone a name calls for, refusing a name no zone answers to.
|
|
24
|
+
*/
|
|
25
|
+
static of(name = "UTC") {
|
|
26
|
+
return new this(name, simScheduleZoneClock(name));
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* What a clock in this zone reads at an instant.
|
|
30
|
+
*/
|
|
31
|
+
partsAt(instant) {
|
|
32
|
+
return this.clock(instant);
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* The instant a clock in this zone reads a wall-clock time at.
|
|
36
|
+
*
|
|
37
|
+
* Two offsets are in play on the day a zone changes one, so both are tried
|
|
38
|
+
* and the answer is whichever of them the zone actually reads back. A time
|
|
39
|
+
* the clocks read twice has two, and the earlier is the answer, which is the
|
|
40
|
+
* one real Scheduler fires at. A time the clocks skip over has neither, and
|
|
41
|
+
* the answer is the later of the two, which is the first instant after the
|
|
42
|
+
* hour that never happened.
|
|
43
|
+
*/
|
|
44
|
+
instantOf(year, month, day, hour = 0, minute = 0) {
|
|
45
|
+
const wanted = Date.UTC(year, month, day, hour, minute);
|
|
46
|
+
const candidates = this.candidatesFor(wanted);
|
|
47
|
+
const read = candidates.filter((candidate) => this.wallClockOf(candidate) === wanted);
|
|
48
|
+
return read.length === 0 ? Math.max(...candidates) : Math.min(...read);
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Whether an instant is the one this zone puts its own wall-clock reading at.
|
|
52
|
+
*
|
|
53
|
+
* The second of a repeated hour reads the same as the first and is not the
|
|
54
|
+
* instant that reading resolves to, which is what tells the two apart.
|
|
55
|
+
*/
|
|
56
|
+
isCanonical(instant) {
|
|
57
|
+
const read = this.clock(instant);
|
|
58
|
+
return (this.instantOf(read.year, read.month, read.day, read.hour, read.minute) === instant);
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* The instants a wall-clock time could be, under the offsets either side of
|
|
62
|
+
* a change.
|
|
63
|
+
*/
|
|
64
|
+
candidatesFor(wanted) {
|
|
65
|
+
const first = wanted - this.offsetAt(wanted);
|
|
66
|
+
const second = wanted - this.offsetAt(first);
|
|
67
|
+
return first === second ? [first] : [first, second];
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* How far ahead of UTC this zone is at an instant, in milliseconds.
|
|
71
|
+
*/
|
|
72
|
+
offsetAt(instant) {
|
|
73
|
+
return (this.wallClockOf(instant) - Math.floor(instant / minuteMs) * minuteMs);
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* What a clock in this zone reads at an instant, as a UTC instant of the same
|
|
77
|
+
* wall-clock fields, which is what makes two readings comparable.
|
|
78
|
+
*/
|
|
79
|
+
wallClockOf(instant) {
|
|
80
|
+
const read = this.clock(instant);
|
|
81
|
+
return Date.UTC(read.year, read.month, read.day, read.hour, read.minute);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
@@ -16,7 +16,7 @@ export declare class SimSchedule {
|
|
|
16
16
|
/**
|
|
17
17
|
* Read a schedule expression under a dialect's rules.
|
|
18
18
|
*/
|
|
19
|
-
static of(source: string, dialect: SimScheduleDialect): SimSchedule;
|
|
19
|
+
static of(source: string, dialect: SimScheduleDialect, timeZone?: string): SimSchedule;
|
|
20
20
|
/**
|
|
21
21
|
* Read the body of whichever form the expression was written in.
|
|
22
22
|
*/
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { SimAtExpression } from "./at/sim-at-expression.js";
|
|
2
2
|
import { SimCronExpression } from "./cron/sim-cron-expression.js";
|
|
3
3
|
import { SimRateExpression } from "./rate/sim-rate-expression.js";
|
|
4
|
+
import { SimScheduleZone } from "./sim-schedule-zone.js";
|
|
4
5
|
import { SimScheduleExpressionError } from "./sim-schedule.error.js";
|
|
5
6
|
const expression = /^(?<kind>[a-z]+)\((?<body>.*)\)$/su;
|
|
6
7
|
/**
|
|
@@ -23,27 +24,27 @@ export class SimSchedule {
|
|
|
23
24
|
/**
|
|
24
25
|
* Read a schedule expression under a dialect's rules.
|
|
25
26
|
*/
|
|
26
|
-
static of(source, dialect) {
|
|
27
|
+
static of(source, dialect, timeZone) {
|
|
27
28
|
const read = expression.exec(source.trim())?.groups;
|
|
28
29
|
if (read === undefined) {
|
|
29
30
|
throw new SimScheduleExpressionError(`a schedule expression is 'rate(<value> <unit>)' or ` +
|
|
30
31
|
`'cron(<fields>)', and this one is '${source}'`);
|
|
31
32
|
}
|
|
32
|
-
return new this(source, this.occurrencesOf(read, dialect));
|
|
33
|
+
return new this(source, this.occurrencesOf(read, dialect, SimScheduleZone.of(timeZone)));
|
|
33
34
|
}
|
|
34
35
|
/**
|
|
35
36
|
* Read the body of whichever form the expression was written in.
|
|
36
37
|
*/
|
|
37
|
-
static occurrencesOf(read, dialect) {
|
|
38
|
+
static occurrencesOf(read, dialect, zone) {
|
|
38
39
|
const body = read["body"] ?? "";
|
|
39
40
|
if (read["kind"] === "rate") {
|
|
40
41
|
return SimRateExpression.of(body, dialect.requiresRateAgreement);
|
|
41
42
|
}
|
|
42
43
|
if (read["kind"] === "cron") {
|
|
43
|
-
return SimCronExpression.of(dialect.cronFields, body);
|
|
44
|
+
return SimCronExpression.of(dialect.cronFields, body, zone);
|
|
44
45
|
}
|
|
45
46
|
if (read["kind"] === "at" && dialect.allowsOneTime) {
|
|
46
|
-
return SimAtExpression.of(body);
|
|
47
|
+
return SimAtExpression.of(body, zone);
|
|
47
48
|
}
|
|
48
49
|
const forms = dialect.allowsOneTime
|
|
49
50
|
? "an 'at(...)', a 'rate(...)' or a 'cron(...)'"
|
|
@@ -1107,6 +1107,11 @@ Current documented limitations:
|
|
|
1107
1107
|
- A null in a result row reads as an empty string. Real Athena leaves the value out of the row.
|
|
1108
1108
|
- A computed boolean reads as `1` and `0`. The Glue column type is what makes a boolean column read
|
|
1109
1109
|
as `true` and `false`, and an expression has no column type behind it.
|
|
1110
|
+
- `round(x, 1)` keeps the one decimal place it was asked for, so `25` reads as `25.0` the way Trino
|
|
1111
|
+
renders it, and the column reports `double`. The scale is read off the statement, so a `round`
|
|
1112
|
+
whose scale is an expression rather than a number keeps none of it. Real Athena reports `decimal`
|
|
1113
|
+
where the value being rounded came from a decimal literal, and nothing here tracks decimal
|
|
1114
|
+
precision.
|
|
1110
1115
|
- An expression nobody named is called `_col0` upward, as Athena calls one. An alias that needed
|
|
1111
1116
|
quotes around it is renamed the same way.
|
|
1112
1117
|
- The engine reads every object under the prefixes a query reaches and holds the rows in memory.
|