kairos-lang 1.0.0
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/LICENSE +202 -0
- package/NOTICE +2 -0
- package/README.md +260 -0
- package/dist/ast.d.ts +142 -0
- package/dist/ast.js +1 -0
- package/dist/cli.d.ts +55 -0
- package/dist/cli.js +309 -0
- package/dist/eval.d.ts +438 -0
- package/dist/eval.js +3198 -0
- package/dist/index.d.ts +9 -0
- package/dist/index.js +64 -0
- package/dist/lexer.d.ts +33 -0
- package/dist/lexer.js +171 -0
- package/dist/parser.d.ts +11 -0
- package/dist/parser.js +644 -0
- package/dist/tz.d.ts +57 -0
- package/dist/tz.js +216 -0
- package/package.json +63 -0
- package/stdlib/fiscal.kairos +12 -0
- package/stdlib/gregorian.kairos +36 -0
- package/stdlib/isoweek.kairos +16 -0
package/dist/tz.js
ADDED
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
// IANA タイムゾーン(依存ゼロ・Intl ベース)
|
|
2
|
+
//
|
|
3
|
+
// ADR-33 の射影パラメータモデルの幾何側: tz は「chronos → 市民ラベル」の写像で、
|
|
4
|
+
// - オフセットの遷移点列を Intl.DateTimeFormat の探査+二分法で一度だけ構築しキャッシュする
|
|
5
|
+
// - 市民日は「その日付になる最初の瞬間」からの半開区間(ADR-33 判断 4——DST の隙間・重複も同規則)
|
|
6
|
+
// - DST 切替日の市民日は 23/25 時間(幅規約 ADR-11/12: 1d は市民日であって 86400s ではない)
|
|
7
|
+
import { KairosError } from "./eval.js";
|
|
8
|
+
const MIN_MS = 60_000;
|
|
9
|
+
const DAY_MS = 86_400_000;
|
|
10
|
+
/** 遷移タイムラインの構築範囲(プロトタイプの実体化はこの中に収まる) */
|
|
11
|
+
const RANGE_LO = Date.UTC(1969, 0, 1);
|
|
12
|
+
const RANGE_HI = Date.UTC(2101, 0, 1);
|
|
13
|
+
export class Tz {
|
|
14
|
+
name;
|
|
15
|
+
/** 区間 [t[i], t[i+1]) のオフセット off[i](分)。t[0] = RANGE_LO */
|
|
16
|
+
t = [];
|
|
17
|
+
off = [];
|
|
18
|
+
/** 市民日開始の実体化キャッシュ(1970-01-01 の市民日が添字 0) */
|
|
19
|
+
days = [];
|
|
20
|
+
daysHi = 0;
|
|
21
|
+
constructor(name) {
|
|
22
|
+
this.name = name;
|
|
23
|
+
const dtf = new Intl.DateTimeFormat('en-CA', {
|
|
24
|
+
timeZone: name, hourCycle: 'h23',
|
|
25
|
+
year: 'numeric', month: '2-digit', day: '2-digit',
|
|
26
|
+
hour: '2-digit', minute: '2-digit', second: '2-digit',
|
|
27
|
+
});
|
|
28
|
+
const offAt = (ms) => {
|
|
29
|
+
const p = {};
|
|
30
|
+
for (const part of dtf.formatToParts(ms)) {
|
|
31
|
+
if (part.type !== 'literal')
|
|
32
|
+
p[part.type] = +part.value;
|
|
33
|
+
}
|
|
34
|
+
const local = Date.UTC(p.year, p.month - 1, p.day, p.hour, p.minute, p.second);
|
|
35
|
+
return Math.round((local - Math.floor(ms / 1000) * 1000) / MIN_MS);
|
|
36
|
+
};
|
|
37
|
+
// 探査(14 日刻み)+二分法で遷移点列を構築(分精度)
|
|
38
|
+
let cur = RANGE_LO;
|
|
39
|
+
let curOff = offAt(cur);
|
|
40
|
+
this.t.push(RANGE_LO);
|
|
41
|
+
this.off.push(curOff);
|
|
42
|
+
const STEP = 14 * DAY_MS;
|
|
43
|
+
while (cur < RANGE_HI) {
|
|
44
|
+
const next = Math.min(cur + STEP, RANGE_HI);
|
|
45
|
+
const nextOff = offAt(next);
|
|
46
|
+
if (nextOff !== curOff) {
|
|
47
|
+
let lo = cur, hi = next; // 遷移は (lo, hi] のどこか
|
|
48
|
+
while (hi - lo > MIN_MS) {
|
|
49
|
+
const mid = lo + Math.floor((hi - lo) / 2 / MIN_MS) * MIN_MS;
|
|
50
|
+
if (offAt(mid) === curOff)
|
|
51
|
+
lo = mid;
|
|
52
|
+
else
|
|
53
|
+
hi = mid;
|
|
54
|
+
}
|
|
55
|
+
this.t.push(hi);
|
|
56
|
+
this.off.push(nextOff);
|
|
57
|
+
curOff = nextOff;
|
|
58
|
+
}
|
|
59
|
+
cur = next;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
/** 瞬間のオフセット(分) */
|
|
63
|
+
offsetAt(ms) {
|
|
64
|
+
let lo = 0, hi = this.t.length - 1;
|
|
65
|
+
while (lo < hi) {
|
|
66
|
+
const mid = (lo + hi + 1) >> 1;
|
|
67
|
+
if (this.t[mid] <= ms)
|
|
68
|
+
lo = mid;
|
|
69
|
+
else
|
|
70
|
+
hi = mid - 1;
|
|
71
|
+
}
|
|
72
|
+
return this.off[lo];
|
|
73
|
+
}
|
|
74
|
+
/** 瞬間の市民フィールド */
|
|
75
|
+
localFields(ms) {
|
|
76
|
+
const d = new Date(ms + this.offsetAt(ms) * MIN_MS);
|
|
77
|
+
return { y: d.getUTCFullYear(), mo: d.getUTCMonth() + 1, d: d.getUTCDate(),
|
|
78
|
+
h: d.getUTCHours(), mi: d.getUTCMinutes(), s: d.getUTCSeconds(), ms: d.getUTCMilliseconds() };
|
|
79
|
+
}
|
|
80
|
+
/** 市民フィールド(local-as-UTC ms)の錨打ち。DST の隙間・重複を検出する(ADR-33) */
|
|
81
|
+
anchor(localAsUtc) {
|
|
82
|
+
// 近傍の区間のオフセット候補で逆写像を試す
|
|
83
|
+
const cands = [];
|
|
84
|
+
const seen = new Set();
|
|
85
|
+
let lo = 0, hi = this.t.length - 1;
|
|
86
|
+
while (lo < hi) { // localAsUtc 近傍の区間添字(±1 日で足りる)
|
|
87
|
+
const mid = (lo + hi + 1) >> 1;
|
|
88
|
+
if (this.t[mid] <= localAsUtc - DAY_MS)
|
|
89
|
+
lo = mid;
|
|
90
|
+
else
|
|
91
|
+
hi = mid - 1;
|
|
92
|
+
}
|
|
93
|
+
for (let i = lo; i < this.t.length && this.t[i] <= localAsUtc + DAY_MS; i++) {
|
|
94
|
+
const o = this.off[i];
|
|
95
|
+
if (seen.has(o))
|
|
96
|
+
continue;
|
|
97
|
+
seen.add(o);
|
|
98
|
+
const u = localAsUtc - o * MIN_MS;
|
|
99
|
+
if (this.offsetAt(u) === o)
|
|
100
|
+
cands.push(u);
|
|
101
|
+
}
|
|
102
|
+
cands.sort((a, b) => a - b);
|
|
103
|
+
if (cands.length === 1)
|
|
104
|
+
return { kind: 'unique', ms: cands[0] };
|
|
105
|
+
if (cands.length >= 2)
|
|
106
|
+
return { kind: 'overlap', ms: cands[0], alt: cands[cands.length - 1] };
|
|
107
|
+
// 隙間: 時計が飛んだ——「その時刻以後の最初の瞬間」=直近の遷移点(ADR-33 判断 4)
|
|
108
|
+
for (let i = 1; i < this.t.length; i++) {
|
|
109
|
+
const tr = this.t[i];
|
|
110
|
+
const before = tr - 1 + this.off[i - 1] * MIN_MS; // 遷移直前の市民時刻
|
|
111
|
+
const after = tr + this.off[i] * MIN_MS; // 遷移直後の市民時刻
|
|
112
|
+
if (before < localAsUtc && localAsUtc <= after)
|
|
113
|
+
return { kind: 'gap', ms: tr };
|
|
114
|
+
}
|
|
115
|
+
// ここに来るのは構築範囲外など——保守的にオフセット即値で写す
|
|
116
|
+
return { kind: 'unique', ms: localAsUtc - this.offsetAt(localAsUtc) * MIN_MS };
|
|
117
|
+
}
|
|
118
|
+
/** 市民日の開始=「その日付になる最初の瞬間」(ADR-33 判断 4。真夜中が隙間なら遷移点) */
|
|
119
|
+
civilDayStart(y, mo, d) {
|
|
120
|
+
const a = this.anchor(Date.UTC(y, mo - 1, d));
|
|
121
|
+
return a.ms; // gap→遷移点・overlap→最初の候補・unique→そのまま
|
|
122
|
+
}
|
|
123
|
+
/** 市民日開始 ms を n 市民日ぶん歩進(フィールド演算=壁時計保存) */
|
|
124
|
+
addCivilDays(dayStartMs, n) {
|
|
125
|
+
const f = this.localFields(dayStartMs);
|
|
126
|
+
return this.civilDayStart(f.y, f.mo, f.d + n);
|
|
127
|
+
}
|
|
128
|
+
/** ms の属する市民日の開始 */
|
|
129
|
+
floorToDay(ms) {
|
|
130
|
+
const f = this.localFields(ms);
|
|
131
|
+
const s = this.civilDayStart(f.y, f.mo, f.d);
|
|
132
|
+
return s <= ms ? s : this.civilDayStart(f.y, f.mo, f.d - 1); // 稀な端の防御
|
|
133
|
+
}
|
|
134
|
+
/** 瞬間の壁時計の日内ラベル(h:m:s.ms の ms 換算)——日開始からの経過ではなくラベル読み。
|
|
135
|
+
* 時刻付き anchor/from: の窓境界の正(ADR-31 改訂 2・F87): 切替日の anchor でも
|
|
136
|
+
* atTimeOfDay と往復して「各市民日でその壁時計を最初に読む瞬間」が立つ */
|
|
137
|
+
timeOfDayLabel(ms) {
|
|
138
|
+
const f = this.localFields(ms);
|
|
139
|
+
return ((f.h * 60 + f.mi) * 60 + f.s) * 1000 + f.ms;
|
|
140
|
+
}
|
|
141
|
+
/** 市民日 D(開始 ms)で壁時計 (h,m,s) = todMs を最初に読む瞬間(ADR-31 改訂 2):
|
|
142
|
+
* 隙間(存在しない時刻)なら隙間明けの最初の瞬間・重複(二度ある時刻)なら最初の出現 */
|
|
143
|
+
atTimeOfDay(dayStartMs, todMs) {
|
|
144
|
+
if (todMs === 0)
|
|
145
|
+
return dayStartMs;
|
|
146
|
+
const f = this.localFields(dayStartMs);
|
|
147
|
+
const a = this.anchor(Date.UTC(f.y, f.mo - 1, f.d) + todMs);
|
|
148
|
+
return a.ms;
|
|
149
|
+
}
|
|
150
|
+
/** [1970-01-01 の市民日, hi) の市民日開始列(添字 0 =紀元の市民日。キャッシュ・追記拡張) */
|
|
151
|
+
dayStarts(hi) {
|
|
152
|
+
if (hi > this.daysHi) {
|
|
153
|
+
if (this.days.length === 0)
|
|
154
|
+
this.days.push(this.civilDayStart(1970, 1, 1));
|
|
155
|
+
let last = this.days[this.days.length - 1];
|
|
156
|
+
while (last < hi + DAY_MS) {
|
|
157
|
+
last = this.addCivilDays(last, 1);
|
|
158
|
+
this.days.push(last);
|
|
159
|
+
}
|
|
160
|
+
this.daysHi = hi;
|
|
161
|
+
}
|
|
162
|
+
return this.days;
|
|
163
|
+
}
|
|
164
|
+
/** 市民日開始の紀元(1970-01-01 の市民日)からの序数。dayStarts の添字 */
|
|
165
|
+
dayIndex(dayStartMs) {
|
|
166
|
+
// 概算(24h 割り)から局所補正——市民日は 23〜25h なので誤差は小さい
|
|
167
|
+
const days = this.dayStarts(Math.max(dayStartMs + 2 * DAY_MS, this.daysHi));
|
|
168
|
+
let i = Math.min(Math.max(Math.round((dayStartMs - days[0]) / DAY_MS), 0), days.length - 1);
|
|
169
|
+
while (days[i] > dayStartMs && i > 0)
|
|
170
|
+
i--;
|
|
171
|
+
while (i + 1 < days.length && days[i + 1] <= dayStartMs)
|
|
172
|
+
i++;
|
|
173
|
+
return i;
|
|
174
|
+
}
|
|
175
|
+
/** 表示形(YYYY-MM-DD[Thh:mm[:ss]])——この tz の市民ラベル */
|
|
176
|
+
format(ms) {
|
|
177
|
+
const f = this.localFields(ms);
|
|
178
|
+
const p = (n) => String(n).padStart(2, '0');
|
|
179
|
+
const date = `${f.y}-${p(f.mo)}-${p(f.d)}`;
|
|
180
|
+
if (f.h || f.mi || f.s || f.ms) {
|
|
181
|
+
return `${date}T${p(f.h)}:${p(f.mi)}` + (f.s ? `:${p(f.s)}` : '');
|
|
182
|
+
}
|
|
183
|
+
return date;
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
const TZ_CACHE = new Map();
|
|
187
|
+
/** 固定オフセット表記の厳格一意形(F66 (b)・ADR-43): ^[+-]HH:MM$(ゼロ埋め必須・HH 00–14・
|
|
188
|
+
* MM 00–59)のみ。Intl が受ける別綴り("+0900"・"+15:00" 等)もここで封じる——同じオフセットの
|
|
189
|
+
* 二綴りは tz 名の文字列等値(ADR-36 判断 6・defCache キー)を黙って割る。Intl に渡す前の字句検査 */
|
|
190
|
+
function checkTzLexical(name) {
|
|
191
|
+
if (/^[+-]/.test(name) || name === 'Z' || /^UTC.+/.test(name)) {
|
|
192
|
+
const m = /^([+-])(\d{2}):(\d{2})$/.exec(name);
|
|
193
|
+
if (!m || +m[2] > 14 || +m[3] > 59) {
|
|
194
|
+
throw new KairosError(`不正な固定オフセット tz: "${name}"——正準形は "±HH:MM"(ゼロ埋め必須・`
|
|
195
|
+
+ `HH 00..14・MM 00..59。例 "+09:00")か IANA 名("UTC" は IANA 名として合法。F66/ADR-43)`);
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
/** tz 名から Tz を得る(プロセス内キャッシュ)。不正な名前は静的エラー */
|
|
200
|
+
export function getTz(name) {
|
|
201
|
+
const hit = TZ_CACHE.get(name);
|
|
202
|
+
if (hit)
|
|
203
|
+
return hit;
|
|
204
|
+
checkTzLexical(name); // 固定オフセットの厳格一意形(F66 (b))——Intl の受理より狭い
|
|
205
|
+
try {
|
|
206
|
+
const tz = new Tz(name);
|
|
207
|
+
TZ_CACHE.set(name, tz);
|
|
208
|
+
return tz;
|
|
209
|
+
}
|
|
210
|
+
catch (e) {
|
|
211
|
+
if (e instanceof RangeError) {
|
|
212
|
+
throw new KairosError(`不正な tz 名: "${name}"(IANA 名か固定オフセット正準形 "±HH:MM"。ADR-33/36/43)`);
|
|
213
|
+
}
|
|
214
|
+
throw e;
|
|
215
|
+
}
|
|
216
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "kairos-lang",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Kairos schedule definition language — reference implementation (CLI + evaluator, zero runtime dependencies)",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"schedule",
|
|
7
|
+
"scheduling",
|
|
8
|
+
"cron",
|
|
9
|
+
"calendar",
|
|
10
|
+
"business-days",
|
|
11
|
+
"holidays",
|
|
12
|
+
"dsl",
|
|
13
|
+
"timezone"
|
|
14
|
+
],
|
|
15
|
+
"homepage": "https://kairos-lang.org/",
|
|
16
|
+
"repository": {
|
|
17
|
+
"type": "git",
|
|
18
|
+
"url": "git+https://github.com/azathothx/kairos-lang.git",
|
|
19
|
+
"directory": "impl"
|
|
20
|
+
},
|
|
21
|
+
"bugs": {
|
|
22
|
+
"url": "https://github.com/azathothx/kairos-lang/issues"
|
|
23
|
+
},
|
|
24
|
+
"license": "Apache-2.0",
|
|
25
|
+
"author": "azathothx",
|
|
26
|
+
"type": "module",
|
|
27
|
+
"engines": {
|
|
28
|
+
"node": ">=20"
|
|
29
|
+
},
|
|
30
|
+
"bin": {
|
|
31
|
+
"kairos": "dist/cli.js"
|
|
32
|
+
},
|
|
33
|
+
"exports": {
|
|
34
|
+
".": {
|
|
35
|
+
"types": "./dist/index.d.ts",
|
|
36
|
+
"default": "./dist/index.js"
|
|
37
|
+
}
|
|
38
|
+
},
|
|
39
|
+
"types": "dist/index.d.ts",
|
|
40
|
+
"files": [
|
|
41
|
+
"dist",
|
|
42
|
+
"stdlib",
|
|
43
|
+
"README.md",
|
|
44
|
+
"LICENSE",
|
|
45
|
+
"NOTICE"
|
|
46
|
+
],
|
|
47
|
+
"directories": {
|
|
48
|
+
"test": "test"
|
|
49
|
+
},
|
|
50
|
+
"scripts": {
|
|
51
|
+
"build": "tsc -p tsconfig.build.json",
|
|
52
|
+
"prepack": "npm run build",
|
|
53
|
+
"test": "vitest run",
|
|
54
|
+
"typecheck": "tsc --noEmit"
|
|
55
|
+
},
|
|
56
|
+
"devDependencies": {
|
|
57
|
+
"@types/node": "^26.1.0",
|
|
58
|
+
"esbuild": "^0.28.1",
|
|
59
|
+
"postject": "^1.0.0-alpha.6",
|
|
60
|
+
"typescript": "^6.0.3",
|
|
61
|
+
"vitest": "^4.1.10"
|
|
62
|
+
}
|
|
63
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
# 標準 premise: Fiscal(会計暦・4 月始まり。stdlib/fiscal.md §1 の完全定義)
|
|
2
|
+
# Gregorian の派生的定義。上書きは year 一行だけで、quarter・yearStart は機構 A で自動追従(spec §3.7)。
|
|
3
|
+
# 糖衣形 `Gregorian |> rephase(+3, on: year, unit: month)` と同じ展開(正式名=2026-07-26 裁定)。
|
|
4
|
+
|
|
5
|
+
premise Fiscal = Gregorian with {
|
|
6
|
+
year = month span (_ => 12) phase: 3 label: (p => yearNo(p)) # 暦月を 4 月始まりで 12 ずつ束ね直す。
|
|
7
|
+
# ラベル=開始暦年(窓先頭 4/1 の暦年=年度番号。
|
|
8
|
+
# 上書きはラベルを継承しない——同時付与が統治。ADR-42/F96)
|
|
9
|
+
|
|
10
|
+
fiscalYearNo = d => yearOf(epochOrdinal(month, d) - 3) # 年度番号(開始暦年の規約。「2026 年度」= 2026)
|
|
11
|
+
fiscalMonthNo = d => ordinalIn(month, year, d) # 会計月番号(4 月 = 1 … 3 月 = 12)
|
|
12
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# 標準 premise: Gregorian(stdlib/gregorian.md §1 の完全定義を Kairos ソースにしたもの)
|
|
2
|
+
# 注: 紀元(span 序数の起点)は spec 未規定のため、プロトタイプは 1970-01-01 に固定する(FINDINGS)。
|
|
3
|
+
# stdlib/gregorian.md の補助値関数の「紀元年」もこれに合わせ epochYear = 1970。
|
|
4
|
+
|
|
5
|
+
premise Gregorian {
|
|
6
|
+
day = chronos grid 1d # 原子(連続基底 Chronos を市民日で分割)
|
|
7
|
+
hour = chronos grid 1h # 経過 1 時間のタイル(市民時ではない。ADR-50)
|
|
8
|
+
weekday = day cycle [Mon, Tue, Wed, Thu, Fri, Sat, Sun] anchor: 2000-01-03
|
|
9
|
+
|
|
10
|
+
epochYear = 1970
|
|
11
|
+
monthOf = m => m mod 12
|
|
12
|
+
yearOf = m => epochYear + m div 12
|
|
13
|
+
monthLengths = leap => [31, leap ? 29 : 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]
|
|
14
|
+
|
|
15
|
+
isLeap = y => y mod 4 == 0 and not(y mod 100 == 0 and y mod 400 != 0)
|
|
16
|
+
daysInMonth = m => monthLengths(isLeap(yearOf(m)))[monthOf(m)]
|
|
17
|
+
daysInMonthOf = d => daysInMonth(epochOrdinal(month, d)) # 点→その月の日数(§4.9 暦座標糖衣ファミリ。ADR-46 期・還流第 3 便 F101)
|
|
18
|
+
month = day span daysInMonth label: (p => monthNo(p)) # 基本の括り:day を束ねる(year 非依存)
|
|
19
|
+
year = month span (_ => 12) phase: 0 label: (p => yearNo(p)) # month を 12 ずつ束ねる
|
|
20
|
+
quarter = year split (_ => [3, 3, 3, 3]) by: month # year の従属窓
|
|
21
|
+
|
|
22
|
+
weekStart = day |> filter(d => weekday(d) == wkst) # wkst は利用側前文の宣言を遅延解決
|
|
23
|
+
week = day |> segmentBy(weekStart, edges: clip, empties: error)
|
|
24
|
+
|
|
25
|
+
monthStart = month |> first # 公開境界語(選択子の再利用)
|
|
26
|
+
monthEnd = month |> last
|
|
27
|
+
yearStart = year |> first
|
|
28
|
+
|
|
29
|
+
# 日時刻の付与(ADR-51 糖衣・変換): 日集合 → その日の壁時計 a 時(a は単独時刻リテラル Thh:mm)
|
|
30
|
+
at(a) = s => (everyInstant |> strideBy(1d, from: a)) |> filter(t => coincides(s, day, t))
|
|
31
|
+
|
|
32
|
+
# 暦座標糖衣(spec §4.9: epochOrdinal+ordinalIn+既存値関数の糖衣)
|
|
33
|
+
yearNo = d => yearOf(epochOrdinal(month, d)) # 暦年(2026 など)
|
|
34
|
+
monthNo = d => monthOf(epochOrdinal(month, d)) + 1 # 暦月(1..12)
|
|
35
|
+
dayNo = d => ordinalIn(day, month, d) # 暦日(1..31)
|
|
36
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# 標準 premise: ISOWeek(ISO 8601 週暦。stdlib/iso-week.md §1 の完全定義)
|
|
2
|
+
# 週は月曜始まり・W01 はその年の最初の木曜を含む週(= 1/4 を含む週)。
|
|
3
|
+
# 等価変形「W01 の月曜は必ず 12/29〜1/4 に落ちる」(任意の連続 7 日は月曜をちょうど 1 つ含む)により
|
|
4
|
+
# label: 付与式なしの確定語彙だけで立つ(設計注記は stdlib/iso-week.md)。wkst には依存しない(月曜固定)。
|
|
5
|
+
|
|
6
|
+
premise ISOWeek = Gregorian with {
|
|
7
|
+
isoWeekStart = day |> filter(x => weekday(x) == Mon)
|
|
8
|
+
isoWeek = day |> segmentBy(isoWeekStart, edges: clip, empties: error)
|
|
9
|
+
|
|
10
|
+
isoYearStart = isoWeekStart |> filter(x => (monthNo(x) == 12 and dayNo(x) >= 29) or (monthNo(x) == 1 and dayNo(x) <= 4))
|
|
11
|
+
isoYear = day |> segmentBy(isoYearStart, edges: clip, empties: error)
|
|
12
|
+
|
|
13
|
+
isoWeekNo = d => ordinalIn(isoWeek, isoYear, d) # 1..52/53
|
|
14
|
+
isoWeekday = d => ordinalIn(day, isoWeek, d) # 月 = 1 … 日 = 7
|
|
15
|
+
isoYearNo = d => monthNo(d) == 1 and isoWeekNo(d) >= 52 ? yearNo(d) - 1 : (monthNo(d) == 12 and isoWeekNo(d) == 1 ? yearNo(d) + 1 : yearNo(d))
|
|
16
|
+
}
|