@everwhen/temporal
Version:
_description_
156 lines (155 loc) • 4.7 kB
JavaScript
import { Duration, } from "./duration.js";
import { invariant } from "./fn/misc.js";
import { isPlainTime, isPlainYearMonth } from "./is.js";
export class Sequence {
start;
end;
step;
#duration;
constructor(start, end, step) {
// Excluding PlainTime as time is circular, it is simultaneously before and after midnight.
if (!isPlainTime(start)) {
invariant(start.compare(end) <= 0, `Invalid bounds. start: ${start.toString()} - end: ${end.toString()} `);
}
this.start = start;
this.end = end;
this.step = step;
this.#duration = this.start.until(this.end);
}
static from(bounds) {
let stepDefault;
if (isPlainTime(bounds.start)) {
stepDefault = { hours: 1 };
}
else if (isPlainYearMonth(bounds.start)) {
stepDefault = { months: 1 };
}
else {
stepDefault = { days: 1 };
}
return new Sequence(bounds.start, bounds.end, bounds.step ?? stepDefault);
}
*[Symbol.iterator]() {
let current = this.start;
// Special handling for PlainTime as time is circular, incrementing `current` will eventually circle back to 00:00, causing a infinite loop
let timeAccumulated = Duration.from({ seconds: 0 });
yield current;
while (current.compare(this.end) < 0) {
if (isPlainTime(this.start)) {
timeAccumulated = timeAccumulated.add(this.step);
if (timeAccumulated.compare(this.#duration) > 0) {
break;
}
}
current = current.add(this.step);
yield current;
}
}
get bounds() {
return { start: this.start, end: this.end };
}
*items() {
let timeAccumulated = Duration.from({ seconds: 0 });
const item = {
value: this.start,
next: this.start.add(this.step),
};
yield item;
while (item.value.compare(this.end) < 0) {
if (isPlainTime(this.start)) {
timeAccumulated = timeAccumulated.add(this.step);
if (timeAccumulated.compare(this.#duration) > 0) {
break;
}
}
item.previous = item.value;
item.value = item.value.add(this.step);
const next = item.value.add(this.step);
if (next.compare(this.end) <= 0) {
item.next = next;
}
else {
delete item.next;
}
yield item;
}
}
forEach(callbackfn) {
let index = 0;
for (const val of this) {
callbackfn(val, index);
index += 1;
}
}
map(mapper) {
const items = [];
for (const tem of this) {
items.push(mapper(tem));
}
return items;
}
group(keyFn) {
const groups = new Map();
for (const item of this) {
const key = keyFn(item);
if (!groups.has(key)) {
groups.set(key, []);
}
groups.get(key).push(item);
}
return groups;
}
select(predicate, mapper) {
const values = [];
for (const item of this) {
if (predicate(item)) {
values.push(mapper(item));
}
}
return values;
}
filter(predicate) {
return this.select(predicate, (t) => t);
}
with(bounds) {
return new Sequence(bounds.start ?? this.start, bounds.end ?? this.end, bounds.step ?? this.step);
}
get length() {
return Array.from(this).length;
}
contains(target) {
return this.start.compare(target) <= 0 && this.end.compare(target) >= 0;
}
toMap(mapper) {
const result = new Map();
for (const item of this) {
const key = item.toString();
const value = mapper(item);
result.set(key, value);
}
return result;
}
toJSON() {
return {
start: this.start.toString(),
end: this.end.toString(),
step: Duration.from(this.step).toString(),
};
}
indexOf(target) {
if (!this.contains(target)) {
return -1;
}
let index = 0;
for (const point of this) {
if (point.equals(target)) {
return index;
}
index++;
}
return index;
}
toString() {
return `[${this.start.toString()}, ${this.end.toString()}, ${Duration.from(this.step).toString()}]`;
}
}