Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 12 additions & 10 deletions docs/reference/calendar-day.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,16 +38,16 @@ This class can throw:

## Methods

| Method | Description |
| --- | --- |
| `getYear()` | Returns the numeric year. |
| `getMonth()` | Returns a `Month`. |
| `getMonthOfYear()` | Returns a `MonthOfYear`. |
| `getDay()` | Returns a `Day`. |
| `getDayOfWeek()` | Returns a `DayOfWeek`. |
| `toTimestamp()` | Returns a UTC timestamp for the day. |
| `isBefore(date)` | Compares by `YYYY-MM-DD` string value. |
| `isAfter(date)` | Compares by `YYYY-MM-DD` string value. |
| Method | Description |
| ------------------ | -------------------------------------- |
| `getYear()` | Returns the numeric year. |
| `getMonth()` | Returns a `Month`. |
| `getMonthOfYear()` | Returns a `MonthOfYear`. |
| `getDay()` | Returns a `Day`. |
| `getDayOfWeek()` | Returns a `DayOfWeek`. |
| `toTimestamp()` | Returns a UTC timestamp for the day. |
| `isBefore(date)` | Compares by `YYYY-MM-DD` string value. |
| `isAfter(date)` | Compares by `YYYY-MM-DD` string value. |

## Example

Expand All @@ -64,6 +64,8 @@ day.getDayOfWeek().toString(); // depends on date
## Notes

- When no value is provided, it uses the current timestamp.
- Numeric zero represents the Unix epoch (`1970-01-01`).
- Calendar dates that do not exist, such as `2022-02-31`, are rejected instead of normalized.
- Internally it uses UTC date getters through `Timestamp`.

## Related
Expand Down
22 changes: 11 additions & 11 deletions docs/reference/number-value-object.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,17 +37,17 @@ This class can throw:

## Methods

| Method | Description |
| --- | --- |
| `isZero()` | Returns true when the number is exactly 0. |
| `isGreaterThan(other)` | Returns true when the current value is greater than `other`. |
| Method | Description |
| ----------------------------- | ------------------------------------------------------------------------ |
| `isZero()` | Returns true when the number is exactly 0. |
| `isGreaterThan(other)` | Returns true when the current value is greater than `other`. |
| `isGreaterOrEqualThan(other)` | Returns true when the current value is greater than or equal to `other`. |
| `isLessThan(other)` | Returns true when the current value is less than `other`. |
| `isLessOrEqualThan(other)` | Returns true when the current value is less than or equal to `other`. |
| `add(other)` | Returns a new `NumberValueObject` with the sum. |
| `subtract(other)` | Returns a new `NumberValueObject` with the difference. |
| `multiply(other)` | Returns a new `NumberValueObject` with the product. |
| `divide(other)` | Returns a new `NumberValueObject` with the quotient. |
| `isLessThan(other)` | Returns true when the current value is less than `other`. |
| `isLessOrEqualThan(other)` | Returns true when the current value is less than or equal to `other`. |
| `add(other)` | Returns a new `NumberValueObject` with the sum. |
| `subtract(other)` | Returns a new `NumberValueObject` with the difference. |
| `multiply(other)` | Returns a new `NumberValueObject` with the product. |
| `divide(other)` | Returns a new `NumberValueObject` with the quotient. |

## Example

Expand All @@ -65,7 +65,7 @@ a.isGreaterThan(b); // true
## Notes

- Arithmetic helpers return new instances.
- `Infinity` is accepted because the current validation only rejects `NaN`.
- `NaN`, `Infinity`, and `-Infinity` are rejected.

## Related

Expand Down
28 changes: 13 additions & 15 deletions docs/reference/timestamp-interval.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ constructor(start: Timestamp, end: Timestamp)

## Validation

`start` must be before or equal to `end` in the constructor. `modifyStart()` and `modifyEnd()` require strict before.
`start` must be before or equal to `end` in the constructor. Intervals are immutable; create a new interval when either boundary changes.

## Throws

Expand All @@ -37,19 +37,17 @@ This class can throw:

## Methods

| Method | Description |
| --- | --- |
| `static fromPrimitives(primitives)` | Restores from `{ start, end }` milliseconds. |
| `toPrimitives()` | Returns `{ start, end }`. |
| `getStart()` | Returns the current start timestamp. |
| `getEnd()` | Returns the current end timestamp. |
| `getDuration()` | Returns a `Duration`. |
| `getTotalDaysOfWeek(dayOfWeek)` | Counts occurrences of a weekday within the interval. |
| `modifyStart(start)` | Mutates the start timestamp after validation. |
| `modifyEnd(end)` | Mutates the end timestamp after validation. |
| `getDaysBetweenInterval()` | Returns all `CalendarDay` values between start and end, inclusive. |
| `getOverlappingInterval(searchInterval)` | Returns overlapping interval or `null`. |
| `includes(timestamp)` | Checks whether a timestamp is inside the interval, inclusive. |
| Method | Description |
| ---------------------------------------- | ------------------------------------------------------------------ |
| `static fromPrimitives(primitives)` | Restores from `{ start, end }` milliseconds. |
| `toPrimitives()` | Returns `{ start, end }`. |
| `getStart()` | Returns the current start timestamp. |
| `getEnd()` | Returns the current end timestamp. |
| `getDuration()` | Returns a `Duration`. |
| `getTotalDaysOfWeek(dayOfWeek)` | Counts occurrences of a weekday within the interval. |
| `getDaysBetweenInterval()` | Returns all `CalendarDay` values between start and end, inclusive. |
| `getOverlappingInterval(searchInterval)` | Returns overlapping interval or `null`. |
| `includes(timestamp)` | Checks whether a timestamp is inside the interval, inclusive. |

## Example

Expand All @@ -66,7 +64,7 @@ interval.includes(start); // true

## Notes

- Unlike most value objects, `modifyStart()` and `modifyEnd()` mutate internal state. Use deliberately, because apparently time was not annoying enough already.
- Interval boundaries cannot be changed after construction.

## Related

Expand Down
72 changes: 36 additions & 36 deletions docs/reference/timestamp.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,45 +27,45 @@ constructor(value?: number | Date | Timestamp | string)

## Validation

String values are parsed with `new Date(value)`. Invalid dates produce `NaN`; this class itself does not reject `NaN`.
String values are parsed with `new Date(value)`. Inputs that produce an invalid ECMAScript `Date`, including non-finite and out-of-range millisecond values, are rejected with `InvalidNumberError`.

## Methods

| Method | Description |
| --- | --- |
| `static new(value)` | Creates a new `Timestamp`. |
| `static now()` | Creates a timestamp for the current time. |
| `static fromSeconds(seconds)` | Creates from seconds. |
| `toExactHour()` | Returns a timestamp rounded down to the exact UTC hour. |
| `toMilliseconds()` | Returns milliseconds. |
| `toSeconds()` | Returns rounded seconds. |
| `toDate()` | Returns a `Date`. |
| `isBefore(other)` | Compares milliseconds. |
| `isAfter(other)` | Compares milliseconds. |
| `isBeforeOrEqual(other)` | Compares milliseconds. |
| `isAfterOrEqual(other)` | Compares milliseconds. |
| `addMilliseconds(value)` | Returns a new timestamp. |
| `addSeconds(value)` | Returns a new timestamp. |
| `addMinutes(value)` | Returns a new timestamp. |
| `addHours(value)` | Returns a new timestamp. |
| `addDays(value)` | Returns a new timestamp. |
| `addWeeks(value)` | Returns a new timestamp. |
| `addMonths(value)` | Uses a fixed 30-day month factor. |
| `addYears(value)` | Uses a fixed 365-day year factor. |
| `addDuration(duration)` | Adds a `Duration`. |
| `isSameDay(other)` | Compares `CalendarDay`. |
| `isSameMonth(other)` | Compares `MonthOfYear`. |
| `isSameYear(other)` | Compares `Year`. |
| `getCalendarDay()` | Returns a `CalendarDay`. |
| `getDay()` | Returns a UTC `Day`. |
| `getMonth()` | Returns a UTC `Month`. |
| `getYear()` | Returns a UTC `Year`. |
| `getHours()` | Returns UTC hours. |
| `getMinutes()` | Returns UTC minutes. |
| `getSeconds()` | Returns UTC seconds. |
| `getMilliseconds()` | Returns UTC milliseconds. |
| `getDayOfWeek()` | Returns JavaScript UTC day number. |
| `getMonthOfYear()` | Returns `MonthOfYear`. |
| Method | Description |
| ----------------------------- | ------------------------------------------------------- |
| `static new(value)` | Creates a new `Timestamp`. |
| `static now()` | Creates a timestamp for the current time. |
| `static fromSeconds(seconds)` | Creates from seconds. |
| `toExactHour()` | Returns a timestamp rounded down to the exact UTC hour. |
| `toMilliseconds()` | Returns milliseconds. |
| `toSeconds()` | Returns rounded seconds. |
| `toDate()` | Returns a `Date`. |
| `isBefore(other)` | Compares milliseconds. |
| `isAfter(other)` | Compares milliseconds. |
| `isBeforeOrEqual(other)` | Compares milliseconds. |
| `isAfterOrEqual(other)` | Compares milliseconds. |
| `addMilliseconds(value)` | Returns a new timestamp. |
| `addSeconds(value)` | Returns a new timestamp. |
| `addMinutes(value)` | Returns a new timestamp. |
| `addHours(value)` | Returns a new timestamp. |
| `addDays(value)` | Returns a new timestamp. |
| `addWeeks(value)` | Returns a new timestamp. |
| `addMonths(value)` | Uses a fixed 30-day month factor. |
| `addYears(value)` | Uses a fixed 365-day year factor. |
| `addDuration(duration)` | Adds a `Duration`. |
| `isSameDay(other)` | Compares `CalendarDay`. |
| `isSameMonth(other)` | Compares `MonthOfYear`. |
| `isSameYear(other)` | Compares `Year`. |
| `getCalendarDay()` | Returns a `CalendarDay`. |
| `getDay()` | Returns a UTC `Day`. |
| `getMonth()` | Returns a UTC `Month`. |
| `getYear()` | Returns a UTC `Year`. |
| `getHours()` | Returns UTC hours. |
| `getMinutes()` | Returns UTC minutes. |
| `getSeconds()` | Returns UTC seconds. |
| `getMilliseconds()` | Returns UTC milliseconds. |
| `getDayOfWeek()` | Returns JavaScript UTC day number. |
| `getMonthOfYear()` | Returns `MonthOfYear`. |

## Example

Expand Down
8 changes: 4 additions & 4 deletions docs/reference/uuid.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ constructor(value: string | StringValueObject)

## Validation

Must be exactly 36 characters and match the current lowercase alphanumeric/hyphen pattern.
Must use the standard `8-4-4-4-12` hexadecimal structure with a supported UUID version and RFC variant. The Nil and Max sentinel UUIDs defined by RFC 9562 are also accepted.

## Throws

Expand All @@ -38,9 +38,9 @@ This class can throw:

## Methods

| Method | Description |
| --- | --- |
| `static generate()` | Generates a random UUID-like v4 value. |
| Method | Description |
| ------------------- | --------------------------------- |
| `static generate()` | Generates a random UUID v4 value. |

## Example

Expand Down
2 changes: 1 addition & 1 deletion src/value-objects/NumberValueObject.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ export class NumberValueObject extends ValueObject<number> {
}

private isValidNumber(value: number): boolean {
return !isNaN(value.valueOf());
return Number.isFinite(value.valueOf());
}

public isZero(): boolean {
Expand Down
9 changes: 5 additions & 4 deletions src/value-objects/coordinates/Latitude.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,16 +7,17 @@ export class Latitude extends NumberValueObject {
private static MAX_VALUE = new NumberValueObject(90);

private static isValid(value: number | NumberValueObject): boolean {
const numberValue = new NumberValueObject(value.valueOf());
const primitive = value.valueOf();

return (
numberValue.isGreaterOrEqualThan(this.MIN_VALUE) &&
numberValue.isLessOrEqualThan(this.MAX_VALUE)
Number.isFinite(primitive) &&
primitive >= this.MIN_VALUE.valueOf() &&
primitive <= this.MAX_VALUE.valueOf()
);
}

constructor(value: number | NumberValueObject) {
super(value.valueOf());
assert(Latitude.isValid(value), new InvalidLatitudeError(value.valueOf()));
super(value.valueOf());
}
}
9 changes: 5 additions & 4 deletions src/value-objects/coordinates/Longitude.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,19 +7,20 @@ export class Longitude extends NumberValueObject {
private static MAX_VALUE = new NumberValueObject(180);

private static isValid(value: number | NumberValueObject): boolean {
const numberValue = new NumberValueObject(value.valueOf());
const primitive = value.valueOf();

return (
numberValue.isGreaterOrEqualThan(this.MIN_VALUE) &&
numberValue.isLessOrEqualThan(this.MAX_VALUE)
Number.isFinite(primitive) &&
primitive >= this.MIN_VALUE.valueOf() &&
primitive <= this.MAX_VALUE.valueOf()
);
}

constructor(value: number | NumberValueObject) {
super(value.valueOf());
assert(
Longitude.isValid(value),
new InvalidLongitudeError(value.valueOf()),
);
super(value.valueOf());
}
}
11 changes: 9 additions & 2 deletions src/value-objects/ids/UUID.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,11 @@ import { ValueObject } from '../ValueObject';

export class UUID extends ValueObject<string> {
private static readonly LENGTH = 36;
private static readonly PATTERN = new RegExp(`^[a-z0-9-]{${this.LENGTH}}$`);
private static readonly PATTERN =
/^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
Comment thread
haskou marked this conversation as resolved.
Resolved

private static readonly SENTINEL_PATTERN =
/^(?:00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$/i;

private static fromRandomBytes(bytes: Buffer): string {
const uuidBytes = Buffer.from(bytes);
Expand Down Expand Up @@ -44,6 +48,9 @@ export class UUID extends ValueObject<string> {
value.length === UUID.LENGTH,
new InvalidLengthError(this.value, UUID.LENGTH),
);
assert(UUID.PATTERN.test(value), new InvalidFormatError(this.value));
assert(
UUID.PATTERN.test(value) || UUID.SENTINEL_PATTERN.test(value),
new InvalidFormatError(this.value),
);
}
}
52 changes: 38 additions & 14 deletions src/value-objects/time/CalendarDay.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,15 +13,6 @@ export class CalendarDay extends ValueObject<string> {
private readonly month: number;
private readonly day: number;

private static ensureIsValidValue(
value: string | Date | number | Timestamp,
): void {
if (typeof value == 'string') {
this.ensureIsValidString(value);
}
this.ensureIsValidDate(new Date(value.valueOf()));
}

private static ensureIsValidString(value: string): void {
assert(
value.match(/^\d{4}-\d{1,2}-\d{1,2}$/),
Expand All @@ -37,15 +28,44 @@ export class CalendarDay extends ValueObject<string> {
}

private static ensureIsValidDate(value: Date | number | Timestamp): void {
assert(!isNaN(value.valueOf()), new InvalidDayError(value));
assert(Number.isFinite(value.valueOf()), new InvalidDayError(value));
}

private static timestampFromString(value: string): Timestamp {
CalendarDay.ensureIsValidString(value);

const [year, month, day] = value.split('-').map(Number);
const date = new Date(0);
date.setUTCHours(0, 0, 0, 0);
date.setUTCFullYear(year, month - 1, day);

assert(
date.getUTCFullYear() === year &&
date.getUTCMonth() === month - 1 &&
date.getUTCDate() === day,
new InvalidDayError(value),
);

return new Timestamp(date);
}

private static timestampFromValue(
value: string | Date | number | Timestamp,
): Timestamp {
if (typeof value === 'string') {
return CalendarDay.timestampFromString(value);
}

CalendarDay.ensureIsValidDate(value);

return new Timestamp(value);
}

constructor(value?: string | Date | number | Timestamp) {
let timestamp: Timestamp;

if (value) {
CalendarDay.ensureIsValidValue(value);
timestamp = new Timestamp(value);
if (value !== undefined) {
timestamp = CalendarDay.timestampFromValue(value);
} else {
timestamp = new Timestamp();
}
Expand Down Expand Up @@ -86,6 +106,10 @@ export class CalendarDay extends ValueObject<string> {
}

public toTimestamp(): Timestamp {
return new Timestamp(Date.UTC(this.year, this.month - 1, this.day));
const date = new Date(0);
date.setUTCHours(0, 0, 0, 0);
date.setUTCFullYear(this.year, this.month - 1, this.day);

return new Timestamp(date);
}
}
Loading
Loading