@nestjstools/clock
Version:
A lightweight, test-friendly clock abstraction for NestJS apps enabling flexible time management via dependency injection
195 lines (140 loc) • 5.64 kB
Markdown
<p align="center">
<image src="nestjstools-logo.png" width="400">
</p>
# /clock
## Introduction
In most applications, time plays a crucial role-whether it's timestamps, expiration checks, scheduling, or logging. However, directly calling `new Date()` throughout your code can make testing difficult and introduce unpredictable behavior in your services.
`/clock` provides a clean abstraction over system time using the `IClock` interface. It enables dependency injection of time providers (`SystemClock`, `FixedClock`) in your NestJS app, improving testability, readability, and flexibility.
This utility is especially useful in domain-driven design and hexagonal architecture, where you want infrastructure concerns (like the system clock) abstracted from your core business logic.
## Features
- **Abstraction of time handling** via the `IClock` interface.
- **Swappable implementations**: `SystemClock` for real-time usage, `FixedClock` for predictable testing.
- **Perfect for unit testing** with consistent and controlled time behavior.
- **Seamless NestJS integration** via dependency injection.
## Installation
```bash
npm install /clock
#or
yarn add /clock
```
## Usage
### SystemClock
Returns the actual current system time.
```ts
import { SystemClock } from '@nestjstools/clock';
const clock = new SystemClock();
console.log(clock.now()); // → current system date/time
```
### FixedClock
Returns a fixed date/time—ideal for deterministic tests.
```ts
import { FixedClock } from '@nestjstools/clock';
const fixedDate = new Date('2023-01-01T00:00:00Z');
const clock = new FixedClock(fixedDate);
console.log(clock.now()); // → always returns 2023-01-01T00:00:00Z - helpful in tests
```
## NestJS Integration
You can easily register the clock as a provider in your modules:
```ts
import { Module } from '@nestjs/common';
import { ClockModule } from '@nestjstools/clock';
({
imports: [
ClockModule.forRoot(), //By default global - .forFeature() also available
]
})
export class AppModule {}
```
Inject the clock into your services (example):
```ts
import { Injectable } from '@nestjs/common';
import { IClock, Clock } from '@nestjstools/clock';
()
export class SubscriptionService {
constructor(() private readonly clock: IClock) {}
isSubscriptionActive(startDate: Date, durationDays: number): boolean {
const now = this.clock.now();
const endDate = new Date(startDate);
endDate.setDate(endDate.getDate() + durationDays);
return now < endDate;
}
}
```
### Testing Example
Swap out the system clock with a fixed one in your test setup:
```ts
import { Test } from '@nestjs/testing';
import { SubscriptionService } from './subscription.service';
import { FixedClock, Service } from '@nestjstools/clock';
describe('SubscriptionService', () => {
let service: SubscriptionService;
beforeEach(async () => {
const moduleRef = await Test.createTestingModule({
providers: [
SubscriptionService,
],
})
.overrideProvider(Service.CLOCK_SERVICE)
.useValue(new FixedClock(new Date('2020-10-10T00:00:00Z')))
.compile();
service = moduleRef.get(SubscriptionService);
});
it('returns true when subscription is still active', () => {
const start = new Date('2020-10-01T00:00:00Z');
const active = service.isSubscriptionActive(start, 20); // expires 2020-10-21
expect(active).toBe(true);
});
it('returns false when subscription has expired', () => {
const start = new Date('2020-09-01T00:00:00Z');
const active = service.isSubscriptionActive(start, 20); // expires 2020-09-21
expect(active).toBe(false);
});
});
```
---
## CalendarDate Value Object
**CalendarDate** is a simple and immutable value object representing a calendar date without time or timezone information, storing only the year, month, and day. It ensures valid date creation and provides convenient methods for manipulation and comparison.
* Immutable representation of a date in `YYYY-MM-DD` format.
* Creation from string (`YYYY-MM-DD`) or native `Date` objects.
* Validation to prevent invalid dates.
* Comparison methods (`equals`, `isBefore`, `isAfter`, etc.).
* Methods to add or subtract days safely.
* Conversion back to native `Date` objects (with time zeroed).
* Useful for date-only domain logic where time is irrelevant.
### Usage as value object
```ts
import { CalendarDate } from '../value-object/calendar-date';
// Create from string
const date1 = CalendarDate.fromString('2025-06-14');
// Create from native Date
const date2 = CalendarDate.fromDate(new Date());
// Get today's date as CalendarDate
const today = CalendarDate.today();
// Manipulate dates
const nextWeek = today.addDays(7);
const yesterday = today.subtractDays(1);
// Compare dates
if (date1.isBefore(nextWeek)) {
console.log(`${date1.toString()} is before ${nextWeek.toString()}`);
}
// Convert back to native Date
const nativeDate = date1.toDate();
```
### In NestJS Dependency Injection
```ts
import { Injectable } from '@nestjs/common';
import { IClock, Clock } from '@nestjstools/clock';
()
export class ReturnToday {
constructor(() private readonly clock: IClock) {}
todayIs(): string {
const today = this.clock.today();
return today.toString(); // output in format YYYY-MM-DD
}
}
```
## Benefits
* Avoid scattered use of `new Date()` in your business logic
* Lightweight and dependency-free
* Improve the testability and maintainability of your time-dependent logic
* Fits well in clean architecture and DDD practices