Skip to content

Commit ea00bda

Browse files
authored
Merge pull request DSpace#1903 from tdonohue/momentjs_to_date-fns
Replace Moment.js with date-fns
2 parents 142dcf5 + 0d2be94 commit ea00bda

7 files changed

Lines changed: 139 additions & 56 deletions

File tree

package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,8 @@
8989
"compression": "^1.7.4",
9090
"cookie-parser": "1.4.5",
9191
"core-js": "^3.7.0",
92+
"date-fns": "^2.29.3",
93+
"date-fns-tz": "^1.3.7",
9294
"deepmerge": "^4.2.2",
9395
"express": "^4.17.1",
9496
"express-rate-limit": "^5.1.3",
@@ -110,13 +112,11 @@
110112
"mirador": "^3.3.0",
111113
"mirador-dl-plugin": "^0.13.0",
112114
"mirador-share-plugin": "^0.11.0",
113-
"moment": "^2.29.4",
114115
"morgan": "^1.10.0",
115116
"ng-mocks": "^13.1.1",
116117
"ng2-file-upload": "1.4.0",
117118
"ng2-nouislider": "^1.8.3",
118119
"ngx-infinite-scroll": "^10.0.1",
119-
"ngx-moment": "^5.0.0",
120120
"ngx-pagination": "5.0.0",
121121
"ngx-sortablejs": "^11.1.0",
122122
"ngx-ui-switch": "^11.0.1",

src/app/shared/date.util.spec.ts

Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
import { dateToString, dateToNgbDateStruct, dateToISOFormat, isValidDate, yearFromString } from './date.util';
2+
3+
describe('Date Utils', () => {
4+
5+
describe('dateToISOFormat', () => {
6+
it('should convert Date to YYYY-MM-DDThh:mm:ssZ string', () => {
7+
// NOTE: month is zero indexed which is why it increases by one
8+
expect(dateToISOFormat(new Date(Date.UTC(2022, 5, 3)))).toEqual('2022-06-03T00:00:00Z');
9+
});
10+
it('should convert Date string to YYYY-MM-DDThh:mm:ssZ string', () => {
11+
expect(dateToISOFormat('2022-06-03')).toEqual('2022-06-03T00:00:00Z');
12+
});
13+
it('should convert Month string to YYYY-MM-DDThh:mm:ssZ string', () => {
14+
expect(dateToISOFormat('2022-06')).toEqual('2022-06-01T00:00:00Z');
15+
});
16+
it('should convert Year string to YYYY-MM-DDThh:mm:ssZ string', () => {
17+
expect(dateToISOFormat('2022')).toEqual('2022-01-01T00:00:00Z');
18+
});
19+
it('should convert ISO Date string to YYYY-MM-DDThh:mm:ssZ string', () => {
20+
// NOTE: Time is always zeroed out as proven by this test.
21+
expect(dateToISOFormat('2022-06-03T03:24:04Z')).toEqual('2022-06-03T00:00:00Z');
22+
});
23+
it('should convert NgbDateStruct to YYYY-MM-DDThh:mm:ssZ string', () => {
24+
// NOTE: month is zero indexed which is why it increases by one
25+
const date = new Date(2022, 5, 3);
26+
expect(dateToISOFormat(dateToNgbDateStruct(date))).toEqual('2022-06-03T00:00:00Z');
27+
});
28+
});
29+
30+
describe('dateToString', () => {
31+
it('should convert Date to YYYY-MM-DD string', () => {
32+
// NOTE: month is zero indexed which is why it increases by one
33+
expect(dateToString(new Date(2022, 5, 3))).toEqual('2022-06-03');
34+
});
35+
it('should convert Date with time to YYYY-MM-DD string', () => {
36+
// NOTE: month is zero indexed which is why it increases by one
37+
expect(dateToString(new Date(2022, 5, 3, 3, 24, 0))).toEqual('2022-06-03');
38+
});
39+
it('should convert Month only to YYYY-MM-DD string', () => {
40+
// NOTE: month is zero indexed which is why it increases by one
41+
expect(dateToString(new Date(2022, 5))).toEqual('2022-06-01');
42+
});
43+
it('should convert ISO Date to YYYY-MM-DD string', () => {
44+
expect(dateToString(new Date('2022-06-03T03:24:00Z'))).toEqual('2022-06-03');
45+
});
46+
it('should convert NgbDateStruct to YYYY-MM-DD string', () => {
47+
// NOTE: month is zero indexed which is why it increases by one
48+
const date = new Date(2022, 5, 3);
49+
expect(dateToString(dateToNgbDateStruct(date))).toEqual('2022-06-03');
50+
});
51+
});
52+
53+
54+
describe('isValidDate', () => {
55+
it('should return false for null', () => {
56+
expect(isValidDate(null)).toBe(false);
57+
});
58+
it('should return false for empty string', () => {
59+
expect(isValidDate('')).toBe(false);
60+
});
61+
it('should return false for text', () => {
62+
expect(isValidDate('test')).toBe(false);
63+
});
64+
it('should return true for YYYY', () => {
65+
expect(isValidDate('2022')).toBe(true);
66+
});
67+
it('should return true for YYYY-MM', () => {
68+
expect(isValidDate('2022-12')).toBe(true);
69+
});
70+
it('should return true for YYYY-MM-DD', () => {
71+
expect(isValidDate('2022-06-03')).toBe(true);
72+
});
73+
it('should return true for YYYY-MM-DDTHH:MM:SS', () => {
74+
expect(isValidDate('2022-06-03T10:20:30')).toBe(true);
75+
});
76+
it('should return true for YYYY-MM-DDTHH:MM:SSZ', () => {
77+
expect(isValidDate('2022-06-03T10:20:30Z')).toBe(true);
78+
});
79+
it('should return false for a month that does not exist', () => {
80+
expect(isValidDate('2022-13')).toBe(false);
81+
});
82+
it('should return false for a day that does not exist', () => {
83+
expect(isValidDate('2022-02-60')).toBe(false);
84+
});
85+
it('should return false for a time that does not exist', () => {
86+
expect(isValidDate('2022-02-60T10:60:20')).toBe(false);
87+
});
88+
});
89+
90+
describe('yearFromString', () => {
91+
it('should return year from YYYY string', () => {
92+
expect(yearFromString('2022')).toEqual(2022);
93+
});
94+
it('should return year from YYYY-MM string', () => {
95+
expect(yearFromString('1970-06')).toEqual(1970);
96+
});
97+
it('should return year from YYYY-MM-DD string', () => {
98+
expect(yearFromString('1914-10-23')).toEqual(1914);
99+
});
100+
it('should return year from YYYY-MM-DDTHH:MM:SSZ string', () => {
101+
expect(yearFromString('1914-10-23T10:20:30Z')).toEqual(1914);
102+
});
103+
it('should return null if invalid date', () => {
104+
expect(yearFromString('test')).toBeNull();
105+
});
106+
});
107+
});

src/app/shared/date.util.ts

Lines changed: 17 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,8 @@
11
import { NgbDateStruct } from '@ng-bootstrap/ng-bootstrap';
2-
2+
import { formatInTimeZone } from 'date-fns-tz';
3+
import { isValid } from 'date-fns';
34
import isObject from 'lodash/isObject';
4-
import * as moment from 'moment';
5-
6-
import { isNull, isUndefined } from './empty.util';
5+
import { hasNoValue } from './empty.util';
76

87
/**
98
* Returns true if the passed value is a NgbDateStruct.
@@ -31,21 +30,7 @@ export function dateToISOFormat(date: Date | NgbDateStruct | string): string {
3130
const dateObj: Date = (date instanceof Date) ? date :
3231
((typeof date === 'string') ? ngbDateStructToDate(stringToNgbDateStruct(date)) : ngbDateStructToDate(date));
3332

34-
let year = dateObj.getUTCFullYear().toString();
35-
let month = (dateObj.getUTCMonth() + 1).toString();
36-
let day = dateObj.getUTCDate().toString();
37-
let hour = dateObj.getHours().toString();
38-
let min = dateObj.getMinutes().toString();
39-
let sec = dateObj.getSeconds().toString();
40-
41-
year = (year.length === 1) ? '0' + year : year;
42-
month = (month.length === 1) ? '0' + month : month;
43-
day = (day.length === 1) ? '0' + day : day;
44-
hour = (hour.length === 1) ? '0' + hour : hour;
45-
min = (min.length === 1) ? '0' + min : min;
46-
sec = (sec.length === 1) ? '0' + sec : sec;
47-
const dateStr = `${year}${month}${day}${hour}${min}${sec}`;
48-
return moment.utc(dateStr, 'YYYYMMDDhhmmss').format();
33+
return formatInTimeZone(dateObj, 'UTC', "yyyy-MM-dd'T'HH:mm:ss'Z'");
4934
}
5035

5136
/**
@@ -81,7 +66,7 @@ export function stringToNgbDateStruct(date: string): NgbDateStruct {
8166
* the NgbDateStruct object
8267
*/
8368
export function dateToNgbDateStruct(date?: Date): NgbDateStruct {
84-
if (isNull(date) || isUndefined(date)) {
69+
if (hasNoValue(date)) {
8570
date = new Date();
8671
}
8772

@@ -102,22 +87,23 @@ export function dateToNgbDateStruct(date?: Date): NgbDateStruct {
10287
*/
10388
export function dateToString(date: Date | NgbDateStruct): string {
10489
const dateObj: Date = (date instanceof Date) ? date : ngbDateStructToDate(date);
105-
106-
let year = dateObj.getUTCFullYear().toString();
107-
let month = (dateObj.getUTCMonth() + 1).toString();
108-
let day = dateObj.getUTCDate().toString();
109-
110-
year = (year.length === 1) ? '0' + year : year;
111-
month = (month.length === 1) ? '0' + month : month;
112-
day = (day.length === 1) ? '0' + day : day;
113-
const dateStr = `${year}-${month}-${day}`;
114-
return moment.utc(dateStr, 'YYYYMMDD').format('YYYY-MM-DD');
90+
return formatInTimeZone(dateObj, 'UTC', 'yyyy-MM-dd');
11591
}
11692

11793
/**
11894
* Checks if the given string represents a valid date
11995
* @param date the string to be checked
12096
*/
12197
export function isValidDate(date: string) {
122-
return moment(date).isValid();
98+
return (hasNoValue(date)) ? false : isValid(new Date(date));
12399
}
100+
101+
/**
102+
* Parse given date string to a year number based on expected formats
103+
* @param date the string to be parsed
104+
* @param formats possible formats the string may align with. MUST be valid date-fns formats
105+
*/
106+
export function yearFromString(date: string) {
107+
return isValidDate(date) ? new Date(date).getUTCFullYear() : null;
108+
}
109+

src/app/shared/search/search-filters/search-filter/search-range-filter/search-range-filter.component.spec.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,6 @@ describe('SearchRangeFilterComponent', () => {
3131
let fixture: ComponentFixture<SearchRangeFilterComponent>;
3232
const minSuffix = '.min';
3333
const maxSuffix = '.max';
34-
const dateFormats = ['YYYY', 'YYYY-MM', 'YYYY-MM-DD'];
3534
const filterName1 = 'test name';
3635
const value1 = '2000 - 2012';
3736
const value2 = '1992 - 2000';

src/app/shared/search/search-filters/search-filter/search-range-filter/search-range-filter.component.ts

Lines changed: 3 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -15,11 +15,11 @@ import {
1515
} from '../../../../../core/shared/search/search-filter.service';
1616
import { SearchService } from '../../../../../core/shared/search/search.service';
1717
import { Router } from '@angular/router';
18-
import * as moment from 'moment';
1918
import { SEARCH_CONFIG_SERVICE } from '../../../../../my-dspace-page/my-dspace-page.component';
2019
import { SearchConfigurationService } from '../../../../../core/shared/search/search-configuration.service';
2120
import { RouteService } from '../../../../../core/services/route.service';
2221
import { hasValue } from '../../../../empty.util';
22+
import { yearFromString } from 'src/app/shared/date.util';
2323

2424
/**
2525
* The suffix for a range filters' minimum in the frontend URL
@@ -31,11 +31,6 @@ export const RANGE_FILTER_MIN_SUFFIX = '.min';
3131
*/
3232
export const RANGE_FILTER_MAX_SUFFIX = '.max';
3333

34-
/**
35-
* The date formats that are possible to appear in a date filter
36-
*/
37-
const dateFormats = ['YYYY', 'YYYY-MM', 'YYYY-MM-DD'];
38-
3934
/**
4035
* This component renders a simple item page.
4136
* The route parameter 'id' is used to request the item it represents.
@@ -99,8 +94,8 @@ export class SearchRangeFilterComponent extends SearchFacetFilterComponent imple
9994
*/
10095
ngOnInit(): void {
10196
super.ngOnInit();
102-
this.min = moment(this.filterConfig.minValue, dateFormats).year() || this.min;
103-
this.max = moment(this.filterConfig.maxValue, dateFormats).year() || this.max;
97+
this.min = yearFromString(this.filterConfig.minValue) || this.min;
98+
this.max = yearFromString(this.filterConfig.maxValue) || this.max;
10499
const iniMin = this.route.getQueryParameterValue(this.filterConfig.paramName + RANGE_FILTER_MIN_SUFFIX).pipe(startWith(undefined));
105100
const iniMax = this.route.getQueryParameterValue(this.filterConfig.paramName + RANGE_FILTER_MAX_SUFFIX).pipe(startWith(undefined));
106101
this.sub = observableCombineLatest(iniMin, iniMax).pipe(

src/app/shared/shared.module.ts

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,6 @@ import { MissingTranslationHandler, TranslateModule } from '@ngx-translate/core'
1919
import { NgxPaginationModule } from 'ngx-pagination';
2020
import { FileUploadModule } from 'ng2-file-upload';
2121
import { InfiniteScrollModule } from 'ngx-infinite-scroll';
22-
import { MomentModule } from 'ngx-moment';
2322
import { ConfirmationModalComponent } from './confirmation-modal/confirmation-modal.component';
2423
import {
2524
ExportMetadataSelectorComponent
@@ -342,7 +341,6 @@ const MODULES = [
342341
ReactiveFormsModule,
343342
RouterModule,
344343
NouisliderModule,
345-
MomentModule,
346344
DragDropModule,
347345
CdkTreeModule,
348346
GoogleRecaptchaModule,

yarn.lock

Lines changed: 10 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -4759,6 +4759,16 @@ data-urls@^3.0.1:
47594759
whatwg-mimetype "^3.0.0"
47604760
whatwg-url "^10.0.0"
47614761

4762+
date-fns-tz@^1.3.7:
4763+
version "1.3.7"
4764+
resolved "https://registry.yarnpkg.com/date-fns-tz/-/date-fns-tz-1.3.7.tgz#e8e9d2aaceba5f1cc0e677631563081fdcb0e69a"
4765+
integrity sha512-1t1b8zyJo+UI8aR+g3iqr5fkUHWpd58VBx8J/ZSQ+w7YrGlw80Ag4sA86qkfCXRBLmMc4I2US+aPMd4uKvwj5g==
4766+
4767+
date-fns@^2.29.3:
4768+
version "2.29.3"
4769+
resolved "https://registry.yarnpkg.com/date-fns/-/date-fns-2.29.3.tgz#27402d2fc67eb442b511b70bbdf98e6411cd68a8"
4770+
integrity sha512-dDCnyH2WnnKusqvZZ6+jA1O51Ibt8ZMRNkDZdyAyK4YfbDwa/cEmuztzG5pk6hqlp9aSBPYcjOlktquahGwGeA==
4771+
47624772
date-format@^4.0.4:
47634773
version "4.0.4"
47644774
resolved "https://registry.yarnpkg.com/date-format/-/date-format-4.0.4.tgz#b58036e29e74121fca3e1b3e0dc4a62c65faa233"
@@ -8933,11 +8943,6 @@ mkdirp@^1.0.3, mkdirp@^1.0.4:
89338943
resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-1.0.4.tgz#3eb5ed62622756d79a5f0e2a221dfebad75c2f7e"
89348944
integrity sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==
89358945

8936-
moment@^2.29.4:
8937-
version "2.29.4"
8938-
resolved "https://registry.yarnpkg.com/moment/-/moment-2.29.4.tgz#3dbe052889fe7c1b2ed966fcb3a77328964ef108"
8939-
integrity sha512-5LC9SOxjSc2HF6vO2CyuTDNivEdoz2IvyJJGj6X8DJ0eFyfszE0QiEd+iXmBvUP3WHxSjFH/vIsA0EN00cgr8w==
8940-
89418946
morgan@^1.10.0:
89428947
version "1.10.0"
89438948
resolved "https://registry.yarnpkg.com/morgan/-/morgan-1.10.0.tgz#091778abc1fc47cd3509824653dae1faab6b17d7"
@@ -9082,13 +9087,6 @@ ngx-mask@^13.1.7:
90829087
dependencies:
90839088
tslib "^2.3.0"
90849089

9085-
ngx-moment@^5.0.0:
9086-
version "5.0.0"
9087-
resolved "https://registry.yarnpkg.com/ngx-moment/-/ngx-moment-5.0.0.tgz#6500432a2fcda75fb236a632850e599db23c8177"
9088-
integrity sha512-LPpGPo4ccdh8RWnDbJdLTLGGGcwbRYMbn/j4PXM24754J7MZ0tgnBM+ncaVbwefUSSEMme8yMkNIxFiVxgOOvQ==
9089-
dependencies:
9090-
tslib "^2.0.0"
9091-
90929090
ngx-pagination@5.0.0:
90939091
version "5.0.0"
90949092
resolved "https://registry.yarnpkg.com/ngx-pagination/-/ngx-pagination-5.0.0.tgz#a4b4c150a70aef17ccd825e4543e174a974bbd14"

0 commit comments

Comments
 (0)