Skip to content

Instantly share code, notes, and snippets.

@n1215
Last active September 8, 2025 15:03
Show Gist options
  • Save n1215/b4eb3954a31201dff35afbd9c9565061 to your computer and use it in GitHub Desktop.
Save n1215/b4eb3954a31201dff35afbd9c9565061 to your computer and use it in GitHub Desktop.
TypeScript and abstract data type implementations. combinations of object creation methods and oop/functional styles.
// oop style (class based) / class instantiation
class LocalTime {
constructor(
private readonly _hour: number,
private readonly _minutes: number,
private readonly _seconds: number
) {
if (_hour < 0 || _hour > 23 || !Number.isInteger(_hour)) {
throw new Error("hour must be an integer between 0 and 23");
}
if (_minutes < 0 || _minutes > 59 || !Number.isInteger(_minutes)) {
throw new Error("minutes must be an integer between 0 and 59");
}
if (_seconds < 0 || _seconds > 59 || !Number.isInteger(_seconds)) {
throw new Error("seconds must be an integer between 0 and 59");
}
}
get hour(): number {
return this._hour;
}
get minutes(): number {
return this._minutes;
}
get seconds(): number {
return this._seconds;
}
isAfter(another: LocalTime): boolean {
if (this.hour > another.hour) {
return true;
}
if (this.hour < another.hour) {
return false;
}
if (this.minutes > another.minutes) {
return true;
}
return this.seconds > another.seconds;
}
}
const localTime = new LocalTime(10, 0, 1);
const localTime2 = new LocalTime(10, 0, 0);
console.log(localTime.hour, localTime.minutes, localTime.seconds);
console.log(localTime.isAfter(localTime2));
// compile time type error
const invalidLocalTime: LocalTime = {
hour: 25,
minutes: 0,
seconds: 0,
isAfter(another: LocalTime): boolean {
return true;
}
};
// oop style (prototype based) / constructor function
type LocalTime = {
readonly hour: number;
readonly minutes: number;
readonly seconds: number;
isAfter(other: LocalTime): boolean;
}
/**
* @constructor
* @returns {LocalTime}
*/
function LocalTime(hour: number, minutes: number, seconds: number) {
if (hour < 0 || hour > 23 || !Number.isInteger(hour)) {
throw new Error("hour must be an integer between 0 and 23");
}
if (minutes < 0 || minutes > 59 || !Number.isInteger(minutes)) {
throw new Error("minutes must be an integer between 0 and 59");
}
if (seconds < 0 || seconds > 59 || !Number.isInteger(seconds)) {
throw new Error("seconds must be an integer between 0 and 59");
}
this.hour = hour;
this.minutes = minutes;
this.seconds = seconds;
}
LocalTime.prototype.isAfter = function(another: LocalTime): boolean {
if (this.hour > another.hour) {
return true;
}
if (this.hour < another.hour) {
return false;
}
if (this.minutes > another.minutes) {
return true;
}
return this.seconds > another.seconds;
};
const localTime = new LocalTime(10, 0, 1);
const localTime2 = new LocalTime(10, 0, 0);
console.log(localTime.hour, localTime.minutes, localTime.seconds);
console.log(localTime.isAfter(localTime2));
// no type error
const invalidLocalTime: LocalTime = {
hour: 25,
minutes: 0,
seconds: 0,
isAfter(another: LocalTime): boolean {
return true;
}
};
// oop style / factory function
type LocalTime = {
readonly hour: number;
readonly minutes: number;
readonly seconds: number;
isAfter(other: LocalTime): boolean;
};
function createLocalTime(
hour: number,
minutes: number,
seconds: number
): LocalTime {
if (hour < 0 || hour > 23 || !Number.isInteger(hour)) {
throw new Error("hour must be a integer between 0 and 23");
}
if (minutes < 0 || minutes > 59 || !Number.isInteger(minutes)) {
throw new Error("minutes must be a integer between 0 and 59");
}
if (seconds < 0 || seconds > 59 || !Number.isInteger(seconds)) {
throw new Error("seconds must be a integer between 0 and 59");
}
const self = {
hour,
minutes,
seconds,
isAfter(another: LocalTime): boolean {
if (self.hour > another.hour) {
return true;
}
if (self.hour < another.hour) {
return false;
}
if (self.minutes > another.minutes) {
return true;
}
return self.seconds > another.seconds;
}
};
return self;
}
const localTime = createLocalTime(10, 0, 1);
const localTime2 = createLocalTime(10, 0, 0);
console.log(localTime.hour, localTime.minutes, localTime.seconds);
console.log(localTime.isAfter(localTime2));
// no type error
const invalidLocalTime: LocalTime = {
hour: 25,
minutes: 0,
seconds: 0,
isAfter(another: LocalTime): boolean {
return true;
}
};
// oop style / factory method by companion object
type LocalTime = {
readonly hour: number;
readonly minutes: number;
readonly seconds: number;
isAfter(other: LocalTime): boolean;
};
const LocalTime = {
of(hour: number, minutes: number, seconds: number): LocalTime {
if (hour < 0 || hour > 23 || !Number.isInteger(hour)) {
throw new Error("hour must be a integer between 0 and 23");
}
if (minutes < 0 || minutes > 59 || !Number.isInteger(minutes)) {
throw new Error("minutes must be a integer between 0 and 59");
}
if (seconds < 0 || seconds > 59 || !Number.isInteger(seconds)) {
throw new Error("seconds must be a integer between 0 and 59");
}
const self: LocalTime = {
hour,
minutes,
seconds,
isAfter(another: LocalTime): boolean {
if (self.hour > another.hour) {
return true;
}
if (self.hour < another.hour) {
return false;
}
if (self.minutes > another.minutes) {
return true;
}
return self.seconds > another.seconds;
}
};
return self;
}
};
const localTime = LocalTime.of(10, 0, 1);
const localTime2 = LocalTime.of(10, 0, 0);
console.log(localTime.hour, localTime.minutes, localTime.seconds);
console.log(localTime.isAfter(localTime2));
// no type error
const invalidLocalTime: LocalTime = {
hour: 25,
minutes: 0,
seconds: 0,
isAfter(another: LocalTime): boolean {
return true;
}
};
// fp style(type has no method)
type LocalTime = {
readonly hour: number;
readonly minutes: number;
readonly seconds: number;
};
function createLocalTime(
hour: number,
minutes: number,
seconds: number
): LocalTime {
if (hour < 0 || hour > 23 || !Number.isInteger(hour)) {
throw new Error("hour must be an integer between 0 and 23");
}
if (minutes < 0 || minutes > 59 || !Number.isInteger(minutes)) {
throw new Error("minutes must be an integer between 0 and 59");
}
if (seconds < 0 || seconds > 59 || !Number.isInteger(seconds)) {
throw new Error("seconds must be an integer between 0 and 59");
}
return {
hour,
minutes,
seconds
};
}
function isAfter(one: LocalTime, another: LocalTime) {
if (one.hour > another.hour) {
return true;
}
if (one.hour < another.hour) {
return false;
}
if (one.minutes > another.minutes) {
return true;
}
return one.seconds > another.seconds;
}
const localTime = createLocalTime(10, 0, 1);
const localTime2 = createLocalTime(10, 0, 0);
console.log(localTime.hour, localTime.minutes, localTime.seconds);
console.log(isAfter(localTime, localTime2));
// no type error
const invalidLocalTime: LocalTime = {
hour: 25,
minutes: 0,
seconds: 0,
};
// fp style + strictly typed
type Hour = 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23;
type Minutes = 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9
| 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19
| 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29
| 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39
| 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49
| 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59;
type Seconds = 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9
| 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19
| 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29
| 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39
| 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49
| 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59;
type LocalTime = {
readonly hour: Hour;
readonly minutes: Minutes;
readonly seconds: Seconds;
};
function createLocalTime(
hour: Hour,
minutes: Minutes,
seconds: Seconds
): LocalTime {
return {
hour,
minutes,
seconds
};
}
function isAfter(one: LocalTime, another: LocalTime) {
if (one.hour > another.hour) {
return true;
}
if (one.hour < another.hour) {
return false;
}
if (one.minutes > another.minutes) {
return true;
}
return one.seconds > another.seconds;
}
const localTime = createLocalTime(10, 0, 1);
const localTime2 = createLocalTime(10, 0, 0);
console.log(localTime.hour, localTime.minutes, localTime.seconds);
console.log(isAfter(localTime, localTime2));
const invalidLocalTime: LocalTime = {
hour: 25, // compile time type error
minutes: 0,
seconds: 0,
};
// fp style + branded type
export const BrandTypeId: unique symbol = Symbol.for("mylib/Brand")
export interface Brand<in out K extends string | symbol> {
readonly [BrandTypeId]: {
readonly [k in K]: K
}
}
type LocalTime = {
readonly hour: number;
readonly minutes: number;
readonly seconds: number;
} & Brand<"LocalTime">;
function createLocalTime(
hour: number,
minutes: number,
seconds: number
): LocalTime {
if (hour < 0 || hour > 23 || !Number.isInteger(hour)) {
throw new Error("hour must be an integer between 0 and 23");
}
if (minutes < 0 || minutes > 59 || !Number.isInteger(minutes)) {
throw new Error("minutes must be an integer between 0 and 59");
}
if (seconds < 0 || seconds > 59 || !Number.isInteger(seconds)) {
throw new Error("seconds must be an integer between 0 and 59");
}
return {
hour,
minutes,
seconds
} as LocalTime;
}
function isAfter(one: LocalTime, another: LocalTime) {
if (one.hour > another.hour) {
return true;
}
if (one.hour < another.hour) {
return false;
}
if (one.minutes > another.minutes) {
return true;
}
return one.seconds > another.seconds;
}
const localTime = createLocalTime(10, 0, 1);
const localTime2 = createLocalTime(10, 0, 0);
console.log(localTime.hour, localTime.minutes, localTime.seconds);
console.log(isAfter(localTime, localTime2));
// compile time type error
const invalidLocalTime: LocalTime = {
hour: 25,
minutes: 0,
seconds: 0,
};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment