Skip to content

Commit

Permalink
Updated to 1.0.0
Browse files Browse the repository at this point in the history
  • Loading branch information
zaccomode committed Oct 25, 2023
1 parent 5a8ca89 commit 9a416ac
Show file tree
Hide file tree
Showing 15 changed files with 1,214 additions and 847 deletions.
159 changes: 127 additions & 32 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,50 +2,145 @@
Chiral is a timekeeping library built for JavaScript and TypeScript projects.
It sits as a layer of abstraction on top of JavaScript's `Date` class, aiming to effectively replace it for most uses.

> **Note:** Chiral is currently in pre-release, so does not currently support its planned implementation of `Date`. For now, it only supports the `Time` and `DeltaTime` objects.
The primary purpose of Chiral is to separate Date and Time. There are a number of preferential reasons for doing this, most of which are outlined in [this Thread](https://www.threads.net/@zaccomode/post/CykLglzr4Ff). Although exact implementation details are different, the general idea of separating the two remains constant.

> **Chiral does not support Timezones**, due to the inherent complexity of the topic. This is by design, and will almost certainly never be changed.
## Installation
[npm](https://www.npmjs.com/package/chiral)
```bash
npm install chiral
```

## Usage
## `RealTime`
The `RealTime` class represents date-agnostic time. It only has four properties; `hours`, `minutes`, `seconds` and `milliseconds`. It's epoch is `00:00:00 AM`, or midnight. `RealTime` objects are generally immutable, except when their properties are accessed via setters.
```ts
import { RealTime } from "chiral";

const time = new RealTime(9, 35, 40);
console.log(time.toDigital());
// 09:35:40 AM
```

Individual increments can be set (this is a mutable transformation!):
```ts
import { Time } from "chiral";
time.minutes = 41;
console.log(time.toDigital());
// 09:41:40 AM
```

// Create a new time, 9:30:15:000 AM
const time1 = new Time(9, 30, 15, 0);
console.log(time1.toDigital({ p: "s" }));
// > 09:30:15 AM
Rounding to a particular precision `p` is also possible, and will by default **round down**. This can be set manually using the `r` parameter, which accepts `"down"`, `"up"` or `"nearest"`:
```ts
console.log(time.round({ p: "m" }).toDigital());
// 09:41:00 AM
console.log(time.round({ p: "m", r: "nearest"}).toDigital());
// 09:42:00 AM
```

Milliseconds are also supported, but occluded by default:
```ts
const time = new RealTime(9, 41, 40, 325);
console.log(time.toDigital());
// 09:41:40 AM
console.log(time.toDigital({ p: "ms" }));
// 09:41:40:325 AM
```

// Add 15 seconds to the time
time1.seconds += 15;
console.log(time1.toDigital({ p: "s" }));
// > 09:30:30 AM
---
Arithmetic can also be performed on times, like so:
```ts
const time = new RealTime(9, 35, 40);
console.log(time.add({ u: "m", v: 3 }).toDigital());
// 09:38:40 AM

```

And with lists of actions, including double-ups, like so:
```ts
const time = new RealTime(9, 35, 40);
console.log(time.add([
{ u: "m", v: 3 },
{ u: "m", v: 5 },
{ u: "h", v: 1 },
]).toDigital());
// 10:43:40 AM
```

The same goes for subtraction.


---
Equality and comparison checks can also be performed to certain precisions. Rounding to a precision will accept and obey the same default parameters as the `.round()` function:
```ts
const firstTime = new RealTime(9, 35, 40); // 09:35:40 AM
const secondTime = new RealTime(9); // 09:00:00 AM

// Round to the nearest minute
console.log(time1.round({ p: "m" }).toDigital({ p: "s" }))
// > 09:30:00 AM
console.log(firstTime.equals(secondTime));
// false
console.log(firstTime.equals(secondTime, { p: "h" }));
// true - round down by default!
console.log(firstTime.equals(secondTime, { p: "h", r: "nearest" }));
// false

// Create a DeltaTime, 3 hours & 15 minutes
const deltaTime = new DeltaTime(3, 15);
console.log(deltaTime.toDigital({ p: "s" }));
// > 03:15:00
console.log(firstTime.isBefore(secondTime));
// false
console.log(secondTime.isBefore(firstTime));
// true
console.log(firstTime.isBefore(secondTime, { p: "h" }));
// false - exact equality is not before

// Create a DeltaTime using the time between two Times
const deltaTime2 = Time.calcDelta(
new Time(10, 30, 30, 0),
time1
);
console.log(deltaTime2.toDigital({ p: "s" }));
// > 01:00:00
// Same behaviour for .isAfter

// Add two DeltaTimes together
console.log(deltaTime.add(deltaTime2).toDigital({ p: "s" }));
// > 4:15:00
const thirdTime = new RealTime(9, 41, 40); // 09:41:40 AM
console.log(thirdTime.isBetween(firstTime, secondTime));
// true
console.log(firstTime.isBetween(firstTime, secondTime));
// true - here, exact equality is counted
console.log(firstTime.isBetween(secondTime, thirdTime));
// false
console.log(firstTime.isBetween(secondTime, thirdTime, { p: "h" }));
// true - again, we round down by default!
```


## `RealDate`
Chiral's `RealDate` is largely the converse of the `RealTime` class; it is a time-agnostic date that has the normal JavaScript epoch. Rather than a complete re-implementation (like `RealTime` is), `RealDate` is a simple wrapper around JavaScript's `Date` class that provides a similar API to the `RealTime`.

```ts
import { RealDate } from "chiral";

const date = new RealDate(2023, 10, 25);
console.log(date.toReadable());
// 25/10/2023
```

The `.toReadable()` method is the functional equivalent of `RealTime`'s `.toDigital()` method, but offers the `format` (`f`) parameters to format the date as you wish. This defaults to DD/MM/YYYY, because I'm not American and read dates like a normal person.

```ts
console.log(date.toReadable({ f: "MM/DD/YYYY" }));
// 10/25/2023 - For all the Americans out there

// Other supported formats are:
// - YYYY/MM/DD - 2023/10/25
// - Month DD, YYYY - October 25, 2023
```

Other methods and parameters are almost identical to those defined `RealTime`, so they won't be re-explained here. They scale as one would expect to a date object.


## Building your own
Chiral also exports the `Chiral` namespace, which contains all the underlying types used by the `RealDate` and `RealTime` classes. This contains parameter types, and the class interfaces for both.

```ts
import { Chiral } from "chiral";

class RealTime implements Chiral.Time.I_Time<RealTime> {
// ...
}

class RealDate implements Chiral.Date.I_Time<RealDate> {
// ...
}
```

// Add a DeltaTime to time1
console.log(time1.add(deltaTime2).toDigital({ p: "s" }));
// > 10:30:30 AM
```
This is handy if you want to re-implement the classes for your own use, or build extensions or wrappers.
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "chiral",
"version": "0.5.0",
"version": "1.0.0",
"description": "A library to simplify timekeeping.",
"author": "Isaac Shea <[email protected]>",
"license": "MIT",
Expand All @@ -15,7 +15,7 @@
},
"scripts": {
"build": "tsc -p tsconfig.json && tsc -p tsconfig-cjs.json",
"test": "jest"
"test": "jest ./src"
},
"devDependencies": {
"@types/jest": "^29.5.6",
Expand Down
145 changes: 145 additions & 0 deletions src/models/date.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
import RealDate from "./date";

test("RealDate", () => {
const baseDate = new RealDate(2021, 1, 1);

/** Test getters */
expect(baseDate.year).toBe(2021);
expect(baseDate.month).toBe(1);
expect(baseDate.monthName).toBe("January");
expect(baseDate.day).toBe(1);

/** Test setters */
const setDate = new RealDate();
setDate.year = 2021;
setDate.month = 1;
setDate.day = 1;
expect(setDate.year).toBe(2021);
expect(setDate.month).toBe(1);
expect(setDate.monthName).toBe("January");
expect(setDate.day).toBe(1);

/** Setter arithmetic */
const strangeDate = new RealDate();
strangeDate.year += 1;
expect(strangeDate.year).toBe(1901);
strangeDate.year -= 2;
expect(strangeDate.year).toBe(1899);
strangeDate.month += 3;
expect(strangeDate.year).toBe(1899);


/** Test toReadable */
const readableDate = new RealDate(2021, 3, 1);
expect(readableDate.toReadable()).toBe("01/03/2021");
expect(readableDate.toReadable({ f: "DD/MM/YYYY" })).toBe("01/03/2021");
expect(readableDate.toReadable({ f: "MM/DD/YYYY" })).toBe("03/01/2021");
expect(readableDate.toReadable({ f: "YYYY/MM/DD" })).toBe("2021/03/01");
expect(readableDate.toReadable({ f: "Month DD, YYYY" })).toBe("March 01, 2021");


/** Test round */
const roundDate = new RealDate(2021, 3, 23);
expect(roundDate.round().toReadable()).toBe("23/03/2021");
expect(roundDate.round({ p: "d" }).toReadable()).toBe("23/03/2021");
expect(roundDate.round({ p: "m" }).toReadable()).toBe("01/03/2021");
expect(roundDate.round({ p: "y" }).toReadable()).toBe("01/01/2021");

expect(roundDate.round({ p: "d", r: "up" }).toReadable()).toBe("23/03/2021");
expect(roundDate.round({ p: "m", r: "up" }).toReadable()).toBe("01/04/2021");
expect(roundDate.round({ p: "y", r: "up" }).toReadable()).toBe("01/01/2022");

expect(roundDate.round({ p: "d", r: "down" }).toReadable()).toBe("23/03/2021");
expect(roundDate.round({ p: "m", r: "down" }).toReadable()).toBe("01/03/2021");
expect(roundDate.round({ p: "y", r: "down" }).toReadable()).toBe("01/01/2021");


/** Test arithmetic */
const arithmeticDate = new RealDate(2021, 3, 23);
expect(arithmeticDate.toReadable()).toBe("23/03/2021");
expect(arithmeticDate.add().toReadable()).toBe("24/03/2021");
expect(arithmeticDate.add({ u: "d", v: 1 }).toReadable()).toBe("24/03/2021");
expect(arithmeticDate.add({ u: "m", v: 1 }).toReadable()).toBe("23/04/2021");
expect(arithmeticDate.add({ u: "y", v: 1 }).toReadable()).toBe("23/03/2022");

expect(arithmeticDate.add([
{ u: "d", v: 1 },
{ u: "m", v: 1 }
]).toReadable()).toBe("24/04/2021");
expect(arithmeticDate.add([
{ u: "d", v: 1 },
{ u: "m", v: 1 },
{ u: "y", v: 1 }
]).toReadable()).toBe("24/04/2022");

// Subtract
expect(arithmeticDate.subtract().toReadable()).toBe("22/03/2021");
expect(arithmeticDate.subtract({ u: "d", v: 1 }).toReadable()).toBe("22/03/2021");
expect(arithmeticDate.subtract({ u: "m", v: 1 }).toReadable()).toBe("23/02/2021");
expect(arithmeticDate.subtract({ u: "y", v: 1 }).toReadable()).toBe("23/03/2020");

expect(arithmeticDate.subtract([
{ u: "d", v: 1 },
{ u: "m", v: 1 }
]).toReadable()).toBe("22/02/2021");
expect(arithmeticDate.subtract([
{ u: "d", v: 1 },
{ u: "m", v: 1 },
{ u: "y", v: 1 }
]).toReadable()).toBe("22/02/2020");


/** Test comparison */
const equalDate1 = new RealDate(2021, 3, 23);
const equalDate2 = new RealDate(2021, 3, 23);
const equalDate3 = new RealDate(2021, 3, 24);

// Equals
expect(equalDate1.equals(equalDate2)).toBe(true);
expect(equalDate1.equals(equalDate3)).toBe(false);

expect(equalDate1.equals(equalDate2, { p: "d" })).toBe(true);
expect(equalDate1.equals(equalDate3, { p: "d" })).toBe(false);

expect(equalDate1.equals(equalDate2, { p: "m" })).toBe(true);
expect(equalDate1.equals(equalDate3, { p: "m" })).toBe(true);

expect(equalDate1.equals(equalDate2, { p: "y" })).toBe(true);
expect(equalDate1.equals(equalDate3, { p: "y" })).toBe(true);

// isBefore
expect(equalDate1.isBefore(equalDate2)).toBe(false);
expect(equalDate1.isBefore(equalDate3)).toBe(true);

expect(equalDate1.isBefore(equalDate2, { p: "d" })).toBe(false);
expect(equalDate1.isBefore(equalDate3, { p: "d" })).toBe(true);

expect(equalDate1.isBefore(equalDate2, { p: "m" })).toBe(false);
expect(equalDate1.isBefore(equalDate3, { p: "m" })).toBe(false);

expect(equalDate1.isBefore(equalDate2, { p: "y" })).toBe(false);
expect(equalDate1.isBefore(equalDate3, { p: "y" })).toBe(false);

// isAfter
expect(equalDate1.isAfter(equalDate2)).toBe(false);
expect(equalDate3.isAfter(equalDate1)).toBe(true);

expect(equalDate1.isAfter(equalDate2, { p: "d" })).toBe(false);
expect(equalDate3.isAfter(equalDate1, { p: "d" })).toBe(true);

expect(equalDate1.isAfter(equalDate2, { p: "m" })).toBe(false);
expect(equalDate3.isAfter(equalDate1, { p: "m" })).toBe(false);

expect(equalDate1.isAfter(equalDate2, { p: "y" })).toBe(false);
expect(equalDate3.isAfter(equalDate1, { p: "y" })).toBe(false);

// isBetween
expect(equalDate1.isBetween(equalDate2, equalDate3)).toBe(true);
expect(equalDate1.isBetween(equalDate3, equalDate2)).toBe(true);

expect(equalDate3.isBetween(equalDate1, equalDate2)).toBe(false);
expect(equalDate3.isBetween(equalDate2, equalDate1)).toBe(false);

expect(equalDate3.isBetween(equalDate1, equalDate2, { p: "m" })).toBe(true);
expect(equalDate3.isBetween(equalDate2, equalDate1, { p: "m" })).toBe(true);
});
Loading

0 comments on commit 9a416ac

Please sign in to comment.