Skip to content

Commit

Permalink
Merge branch 'master' of github.com:urish/ngx-moment
Browse files Browse the repository at this point in the history
  • Loading branch information
urish committed Mar 7, 2019
2 parents 061593c + 430add1 commit e0f1768
Show file tree
Hide file tree
Showing 13 changed files with 256 additions and 11 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,4 @@ node_modules
.ng_pkg_build
*.log
dist
/package-lock.json
50 changes: 50 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,22 @@ import { MomentModule } from 'ngx-moment';
})
```

If you would like to supply any `NgxMomentOptions` that will be made available to the pipes you can also use:

```typescript
import { MomentModule } from 'ngx-moment';

@NgModule({
imports: [
MomentModule.forRoot({
relativeTimeThresholdOptions: {
'm': 59
}
})
]
})
```

This makes all the `ngx-moment` pipes available for use in your app components.

Available pipes
Expand Down Expand Up @@ -305,6 +321,40 @@ Parses a string but keeps the resulting Moment object in a fixed-offset timezone

Prints `Last updated: Saturday, December 31, 2016 11:00 PM (-03:00)`

## amIsBefore and amIsAfter pipe

Check if a moment is before another moment. Supports limiting granularity to a unit other than milliseconds, pass the units as second parameter

```typescript
@Component({
selector: 'app',
template: `
Today is before tomorrow: {{ today | amIsBefore:tomorrow:'day' }}
`
})
```

Prints `Today is before tomorrow: true`

```typescript
@Component({
selector: 'app',
template: `
Tomorrow is after today: {{ tomorrow | amIsAfter:today:'day' }}
`
})
```

Prints `Tomorrow is after today: true`

NgxMomentOptions
----------------
An `NgxMomentOptions` object can be provided to the module using the `forRoot` convention and will provide options for the pipes to use with the `moment` instance, these options are detailed in the table below:

| prop | type | description |
| --- |:---:| --- |
| relativeTimeThresholdOptions | Dictionary<br>key: string<br>value: number | Provides the `relativeTimeThreshold` units allowing a pipe to set the `moment.relativeTimeThreshold` values. <br><br>The `key` is a unit defined as one of `ss`, `s`, `m`, `h`, `d`, `M`.<br><br>See [Relative Time Thresholds](https://momentjs.com/docs/#/customization/relative-time-threshold/) documentation for more details. |

Complete Example
----------------

Expand Down
21 changes: 20 additions & 1 deletion src/duration.pipe.spec.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import {DurationPipe} from './duration.pipe';
import { DurationPipe } from './duration.pipe';
import { NgxMomentOptions } from './moment-options';

describe('DurationPipe', () => {
let pipe: DurationPipe;
Expand All @@ -17,5 +18,23 @@ describe('DurationPipe', () => {
expect(pipe.transform(365, 'days')).toEqual('a year');
expect(pipe.transform(86400, 'seconds')).toEqual('a day');
});

it(`should convert '50 minutes' to 'an hour' with default 'relativeTimeThreshold'`, () => {
expect(pipe.transform(50, 'minutes')).toEqual('an hour');
});
});

describe('ctor with NgxMomentOptions', () => {
const momentOptions: NgxMomentOptions = {
relativeTimeThresholdOptions: {
'm': 59
}
};

beforeEach(() => pipe = new DurationPipe(momentOptions));

it(`should convert '50 minutes' to '50 minutes' when relativeTimeThreshold for 'm' unit is set to 59`, () => {
expect(pipe.transform(50, 'minutes')).toEqual('50 minutes');
});
});
});
26 changes: 25 additions & 1 deletion src/duration.pipe.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,36 @@
import {Pipe, PipeTransform} from '@angular/core';
import * as moment from 'moment';

import { Inject, Optional, Pipe, PipeTransform } from '@angular/core';
import { NGX_MOMENT_OPTIONS, NgxMomentOptions } from './moment-options';

@Pipe({ name: 'amDuration' })
export class DurationPipe implements PipeTransform {

allowedUnits: Array<string> = ['ss', 's', 'm', 'h', 'd', 'M'];

constructor(@Optional() @Inject(NGX_MOMENT_OPTIONS) momentOptions?: NgxMomentOptions) {
this._applyOptions(momentOptions);
}

transform(value: any, ...args: string[]): string {
if (typeof args === 'undefined' || args.length !== 1) {
throw new Error('DurationPipe: missing required time unit argument');
}
return moment.duration(value, args[0] as moment.unitOfTime.DurationConstructor).humanize();
}

private _applyOptions(momentOptions: NgxMomentOptions): void {
if (!momentOptions) {
return;
}

if (!!momentOptions.relativeTimeThresholdOptions) {
const units: Array<string> = Object.keys(momentOptions.relativeTimeThresholdOptions);
const filteredUnits: Array<string> = units.filter(unit => this.allowedUnits.indexOf(unit) !== -1);
filteredUnits.forEach(unit => {
moment.relativeTimeThreshold(unit, momentOptions.relativeTimeThresholdOptions[unit]);
});
}
}

}
4 changes: 4 additions & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,7 @@ export { FromUtcPipe } from './from-utc.pipe';
export { LocalTimePipe } from './local.pipe';
export { LocalePipe } from './locale.pipe';
export { ParseZonePipe } from './parse-zone.pipe';
export { IsBeforePipe } from './is-before.pipe';
export { IsAfterPipe } from './is-after.pipe';

export { NgxMomentOptions, NGX_MOMENT_OPTIONS } from './moment-options';
33 changes: 33 additions & 0 deletions src/is-after.pipe.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
/* tslint:disable:no-unused-variable */
import { IsAfterPipe } from './is-after.pipe';

describe('IsAfterPipe', () => {
let pipe: IsAfterPipe;

beforeEach(() => pipe = new IsAfterPipe());

describe('#transform', () => {

it('should return true if value is after otherValue', () => {
const test = new Date(2018, 11, 15, 0, 0, 0);
const testDate1 = new Date(2018, 11, 13, 0, 0, 0);
expect(pipe.transform(test, testDate1)).toBeTruthy();
});

it('should support passing "year", "month", "week", "day", etc as a unit parameter', () => {
const test = new Date(2019, 11, 13, 12, 45, 45);
const testDate1 = new Date(2018, 0, 13, 12, 45, 45);
expect(pipe.transform(test, testDate1, 'year')).toBe(true);
const testDate2 = new Date(2018, 1, 13, 12, 45, 45);
expect(pipe.transform(test, testDate2, 'month')).toBe(true);
const testDate3 = new Date(2018, 1, 10, 12, 45, 45);
expect(pipe.transform(test, testDate3, 'day')).toBe(true);
});

it('should return false if value is before otherValue', () => {
const test = new Date(2018, 11, 13, 0, 0, 0);
const testDate1 = new Date(2018, 11, 15, 0, 0, 0);
expect(pipe.transform(test, testDate1)).toBeFalsy();
});
});
});
18 changes: 18 additions & 0 deletions src/is-after.pipe.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import * as moment from 'moment';

import { Pipe, PipeTransform } from '@angular/core';

const momentConstructor = moment;

@Pipe({
name: 'amIsAfter'
})
export class IsAfterPipe implements PipeTransform {

transform(value: Date | moment.Moment,
otherValue: Date | moment.Moment,
unit?: moment.unitOfTime.StartOf): boolean {
return momentConstructor(value).isAfter(momentConstructor(otherValue), unit);
}

}
32 changes: 32 additions & 0 deletions src/is-before.pipe.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import { IsBeforePipe } from './is-before.pipe';

describe('IsBeforePipe', () => {
let pipe: IsBeforePipe;

beforeEach(() => pipe = new IsBeforePipe());

describe('#transform', () => {

it('should return true if value is before otherValue', () => {
const test = new Date(2018, 11, 13, 0, 0, 0);
const testDate1 = new Date(2018, 11, 15, 0, 0, 0);
expect(pipe.transform(test, testDate1)).toBeTruthy();
});

it('should support passing "year", "month", "week", "day", etc as a unit parameter', () => {
const test = new Date(2018, 0, 13, 12, 45, 45);
const testDate1 = new Date(2019, 0, 13, 12, 45, 45);
expect(pipe.transform(test, testDate1, 'year')).toBe(true);
const testDate2 = new Date(2018, 1, 13, 12, 45, 45);
expect(pipe.transform(test, testDate2, 'month')).toBe(true);
const testDate3 = new Date(2018, 1, 13, 12, 45, 45);
expect(pipe.transform(test, testDate3, 'day')).toBe(true);
});

it('should return false if value is after otherValue', () => {
const test = new Date(2018, 11, 15, 0, 0, 0);
const testDate1 = new Date(2018, 11, 13, 0, 0, 0);
expect(pipe.transform(test, testDate1)).toBeFalsy();
});
});
});
18 changes: 18 additions & 0 deletions src/is-before.pipe.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import * as moment from 'moment';

import { Pipe, PipeTransform } from '@angular/core';

const momentConstructor = moment;

@Pipe({
name: 'amIsBefore'
})
export class IsBeforePipe implements PipeTransform {

transform(value: Date | moment.Moment,
otherValue: Date | moment.Moment,
unit?: moment.unitOfTime.StartOf): boolean {
return momentConstructor(value).isBefore(momentConstructor(otherValue), unit);
}

}
16 changes: 16 additions & 0 deletions src/moment-options.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { InjectionToken } from '@angular/core';

export const NGX_MOMENT_OPTIONS: InjectionToken<NgxMomentOptions> = new InjectionToken<NgxMomentOptions>('NGX_MOMENT_OPTIONS');

export interface NgxMomentOptions {
/**
* relativeTimeThresholdOptions
* @description Provides the `relativeTimeThreshold` units allowing a pipe to set the `moment.relativeTimeThreshold` values.
* The `key` is a unit defined as one of `ss`, `s`, `m`, `h`, `d`, `M`.
* @see https://momentjs.com/docs/#/customization/relative-time-threshold/
* @example by default more than 45 seconds is considered a minute, more than 22 hours is considered a day and so on.
* So settings the unit 'm' to `59` will adjust the `relativeTimeThreshold` and consider more than 59 minutes
* to be an hour (default is `45 minutes`)
*/
relativeTimeThresholdOptions: { [key: string]: number };
}
32 changes: 25 additions & 7 deletions src/moment.module.ts
Original file line number Diff line number Diff line change
@@ -1,19 +1,22 @@
import { NgModule } from '@angular/core';
import { ModuleWithProviders, NgModule } from '@angular/core';
import { NGX_MOMENT_OPTIONS, NgxMomentOptions } from './moment-options';

import { AddPipe } from './add.pipe';
import { CalendarPipe } from './calendar.pipe';
import { DateFormatPipe } from './date-format.pipe';
import { DifferencePipe } from './difference.pipe';
import { DurationPipe } from './duration.pipe';
import { FromUnixPipe } from './from-unix.pipe';
import { ParsePipe } from './parse.pipe';
import { SubtractPipe } from './subtract.pipe';
import { TimeAgoPipe } from './time-ago.pipe';
import { UtcPipe } from './utc.pipe';
import { FromUtcPipe } from './from-utc.pipe';
import { IsAfterPipe } from './is-after.pipe';
import { IsBeforePipe } from './is-before.pipe';
import { LocalTimePipe } from './local.pipe';
import { LocalePipe } from './locale.pipe';
import { ParsePipe } from './parse.pipe';
import { ParseZonePipe } from './parse-zone.pipe';
import { SubtractPipe } from './subtract.pipe';
import { TimeAgoPipe } from './time-ago.pipe';
import { UtcPipe } from './utc.pipe';

const ANGULAR_MOMENT_PIPES = [
AddPipe,
Expand All @@ -29,11 +32,26 @@ const ANGULAR_MOMENT_PIPES = [
FromUtcPipe,
LocalTimePipe,
LocalePipe,
ParseZonePipe
ParseZonePipe,
IsBeforePipe,
IsAfterPipe
];

@NgModule({
declarations: ANGULAR_MOMENT_PIPES,
exports: ANGULAR_MOMENT_PIPES
})
export class MomentModule { }
export class MomentModule {
static forRoot(options?: NgxMomentOptions): ModuleWithProviders {
return {
ngModule: MomentModule,
providers: [
{
provide: NGX_MOMENT_OPTIONS, useValue: {
...options
}
}
]
};
}
}
14 changes: 13 additions & 1 deletion src/time-ago.pipe.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,10 @@ function fakeDate(defaultDate: string | number) {
describe('TimeAgoPipe', () => {
describe('#transform', () => {

beforeEach(() => jest.useFakeTimers());
beforeEach(() => {
moment.locale('en-gb');
jest.useFakeTimers();
});

afterEach(() => {
global.Date = _Date;
Expand Down Expand Up @@ -83,6 +86,15 @@ describe('TimeAgoPipe', () => {
expect(pipe.transform(new Date(0))).toBe('46 years ago');
});

it('should update the text when using Date Objects and locale changes', () => {
const changeDetectorMock = { markForCheck: jest.fn() };
const pipe = new TimeAgoPipe(changeDetectorMock as any, new NgZoneMock() as NgZone);
fakeDate('2016-05-01');
expect(pipe.transform(new Date(0))).toBe('46 years ago');
moment.locale('de');
expect(pipe.transform(new Date(0))).toBe('vor 46 Jahren');
});

it('should update the text when the date instance time is updated', () => {
const changeDetectorMock = { markForCheck: jest.fn() };
const pipe = new TimeAgoPipe(changeDetectorMock as any, new NgZoneMock() as NgZone);
Expand Down
2 changes: 1 addition & 1 deletion src/time-ago.pipe.ts
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,6 @@ export class TimeAgoPipe implements PipeTransform, OnDestroy {
}

private getLocale(value: moment.MomentInput): string | null {
return moment.isMoment(value) ? value.locale() : null;
return moment.isMoment(value) ? value.locale() : moment.locale();
}
}

0 comments on commit e0f1768

Please sign in to comment.