diff --git a/package.json b/package.json index 347b83811d..fe203c5899 100644 --- a/package.json +++ b/package.json @@ -89,6 +89,8 @@ "compression": "^1.7.4", "cookie-parser": "1.4.5", "core-js": "^3.7.0", + "date-fns": "^2.29.3", + "date-fns-tz": "^1.3.7", "deepmerge": "^4.2.2", "express": "^4.17.1", "express-rate-limit": "^5.1.3", @@ -110,13 +112,11 @@ "mirador": "^3.3.0", "mirador-dl-plugin": "^0.13.0", "mirador-share-plugin": "^0.11.0", - "moment": "^2.29.4", "morgan": "^1.10.0", "ng-mocks": "^13.1.1", "ng2-file-upload": "1.4.0", "ng2-nouislider": "^1.8.3", "ngx-infinite-scroll": "^10.0.1", - "ngx-moment": "^5.0.0", "ngx-pagination": "5.0.0", "ngx-sortablejs": "^11.1.0", "ngx-ui-switch": "^11.0.1", diff --git a/src/app/shared/date.util.spec.ts b/src/app/shared/date.util.spec.ts index fbc8d60e3f..d7c418e8ae 100644 --- a/src/app/shared/date.util.spec.ts +++ b/src/app/shared/date.util.spec.ts @@ -1,4 +1,4 @@ -import { dateToString, dateToNgbDateStruct, dateToISOFormat, isValidDate } from './date.util'; +import { dateToString, dateToNgbDateStruct, dateToISOFormat, isValidDate, yearFromString } from './date.util'; describe('Date Utils', () => { @@ -86,4 +86,22 @@ describe('Date Utils', () => { expect(isValidDate('2022-02-60T10:60:20')).toBe(false); }); }); -}); \ No newline at end of file + + describe('yearFromString', () => { + it('should return year from YYYY string', () => { + expect(yearFromString('2022')).toEqual(2022); + }); + it('should return year from YYYY-MM string', () => { + expect(yearFromString('1970-06')).toEqual(1970); + }); + it('should return year from YYYY-MM-DD string', () => { + expect(yearFromString('1914-10-23')).toEqual(1914); + }); + it('should return year from YYYY-MM-DDTHH:MM:SSZ string', () => { + expect(yearFromString('1914-10-23T10:20:30Z')).toEqual(1914); + }); + it('should return null if invalid date', () => { + expect(yearFromString('test')).toBeNull(); + }); + }); +}); diff --git a/src/app/shared/date.util.ts b/src/app/shared/date.util.ts index 5f7ccb2438..a6f55c10dc 100644 --- a/src/app/shared/date.util.ts +++ b/src/app/shared/date.util.ts @@ -1,7 +1,8 @@ import { NgbDateStruct } from '@ng-bootstrap/ng-bootstrap'; - +import * as formatInTimeZone from 'date-fns-tz/formatInTimeZone'; +import isMatch from 'date-fns/isMatch'; +import isValid from 'date-fns/isValid'; import { isObject } from 'lodash'; -import * as moment from 'moment'; import { isNull, isUndefined } from './empty.util'; @@ -31,21 +32,7 @@ export function dateToISOFormat(date: Date | NgbDateStruct | string): string { const dateObj: Date = (date instanceof Date) ? date : ((typeof date === 'string') ? ngbDateStructToDate(stringToNgbDateStruct(date)) : ngbDateStructToDate(date)); - let year = dateObj.getUTCFullYear().toString(); - let month = (dateObj.getUTCMonth() + 1).toString(); - let day = dateObj.getUTCDate().toString(); - let hour = dateObj.getHours().toString(); - let min = dateObj.getMinutes().toString(); - let sec = dateObj.getSeconds().toString(); - - year = (year.length === 1) ? '0' + year : year; - month = (month.length === 1) ? '0' + month : month; - day = (day.length === 1) ? '0' + day : day; - hour = (hour.length === 1) ? '0' + hour : hour; - min = (min.length === 1) ? '0' + min : min; - sec = (sec.length === 1) ? '0' + sec : sec; - const dateStr = `${year}${month}${day}${hour}${min}${sec}`; - return moment.utc(dateStr, 'YYYYMMDDhhmmss').format(); + return formatInTimeZone(dateObj, 'UTC', "yyyy-MM-dd'T'HH:mm:ss'Z'"); } /** @@ -102,16 +89,7 @@ export function dateToNgbDateStruct(date?: Date): NgbDateStruct { */ export function dateToString(date: Date | NgbDateStruct): string { const dateObj: Date = (date instanceof Date) ? date : ngbDateStructToDate(date); - - let year = dateObj.getUTCFullYear().toString(); - let month = (dateObj.getUTCMonth() + 1).toString(); - let day = dateObj.getUTCDate().toString(); - - year = (year.length === 1) ? '0' + year : year; - month = (month.length === 1) ? '0' + month : month; - day = (day.length === 1) ? '0' + day : day; - const dateStr = `${year}-${month}-${day}`; - return moment.utc(dateStr, 'YYYYMMDD').format('YYYY-MM-DD'); + return formatInTimeZone(dateObj, 'UTC', 'yyyy-MM-dd'); } /** @@ -119,5 +97,15 @@ export function dateToString(date: Date | NgbDateStruct): string { * @param date the string to be checked */ export function isValidDate(date: string) { - return moment(date).isValid(); + return (isNull(date) || isUndefined(date)) ? false : isValid(new Date(date)); } + +/** + * Parse given date string to a year number based on expected formats + * @param date the string to be parsed + * @param formats possible formats the string may align with. MUST be valid date-fns formats + */ +export function yearFromString(date: string) { + return isValidDate(date) ? new Date(date).getUTCFullYear() : null; +} + diff --git a/src/app/shared/search/search-filters/search-filter/search-range-filter/search-range-filter.component.spec.ts b/src/app/shared/search/search-filters/search-filter/search-range-filter/search-range-filter.component.spec.ts index 44dda40d15..3a146f5059 100644 --- a/src/app/shared/search/search-filters/search-filter/search-range-filter/search-range-filter.component.spec.ts +++ b/src/app/shared/search/search-filters/search-filter/search-range-filter/search-range-filter.component.spec.ts @@ -31,7 +31,6 @@ describe('SearchRangeFilterComponent', () => { let fixture: ComponentFixture; const minSuffix = '.min'; const maxSuffix = '.max'; - const dateFormats = ['YYYY', 'YYYY-MM', 'YYYY-MM-DD']; const filterName1 = 'test name'; const value1 = '2000 - 2012'; const value2 = '1992 - 2000'; diff --git a/src/app/shared/search/search-filters/search-filter/search-range-filter/search-range-filter.component.ts b/src/app/shared/search/search-filters/search-filter/search-range-filter/search-range-filter.component.ts index fbd767284f..938f67412e 100644 --- a/src/app/shared/search/search-filters/search-filter/search-range-filter/search-range-filter.component.ts +++ b/src/app/shared/search/search-filters/search-filter/search-range-filter/search-range-filter.component.ts @@ -15,11 +15,11 @@ import { } from '../../../../../core/shared/search/search-filter.service'; import { SearchService } from '../../../../../core/shared/search/search.service'; import { Router } from '@angular/router'; -import * as moment from 'moment'; import { SEARCH_CONFIG_SERVICE } from '../../../../../my-dspace-page/my-dspace-page.component'; import { SearchConfigurationService } from '../../../../../core/shared/search/search-configuration.service'; import { RouteService } from '../../../../../core/services/route.service'; import { hasValue } from '../../../../empty.util'; +import { yearFromString } from 'src/app/shared/date.util'; /** * The suffix for a range filters' minimum in the frontend URL @@ -31,11 +31,6 @@ export const RANGE_FILTER_MIN_SUFFIX = '.min'; */ export const RANGE_FILTER_MAX_SUFFIX = '.max'; -/** - * The date formats that are possible to appear in a date filter - */ -const dateFormats = ['YYYY', 'YYYY-MM', 'YYYY-MM-DD']; - /** * This component renders a simple item page. * The route parameter 'id' is used to request the item it represents. @@ -99,8 +94,8 @@ export class SearchRangeFilterComponent extends SearchFacetFilterComponent imple */ ngOnInit(): void { super.ngOnInit(); - this.min = moment(this.filterConfig.minValue, dateFormats).year() || this.min; - this.max = moment(this.filterConfig.maxValue, dateFormats).year() || this.max; + this.min = yearFromString(this.filterConfig.minValue) || this.min; + this.max = yearFromString(this.filterConfig.maxValue) || this.max; const iniMin = this.route.getQueryParameterValue(this.filterConfig.paramName + RANGE_FILTER_MIN_SUFFIX).pipe(startWith(undefined)); const iniMax = this.route.getQueryParameterValue(this.filterConfig.paramName + RANGE_FILTER_MAX_SUFFIX).pipe(startWith(undefined)); this.sub = observableCombineLatest(iniMin, iniMax).pipe( diff --git a/src/app/shared/shared.module.ts b/src/app/shared/shared.module.ts index 45e9764151..f723f081d3 100644 --- a/src/app/shared/shared.module.ts +++ b/src/app/shared/shared.module.ts @@ -19,7 +19,6 @@ import { MissingTranslationHandler, TranslateModule } from '@ngx-translate/core' import { NgxPaginationModule } from 'ngx-pagination'; import { FileUploadModule } from 'ng2-file-upload'; import { InfiniteScrollModule } from 'ngx-infinite-scroll'; -import { MomentModule } from 'ngx-moment'; import { ConfirmationModalComponent } from './confirmation-modal/confirmation-modal.component'; import { ExportMetadataSelectorComponent @@ -342,7 +341,6 @@ const MODULES = [ ReactiveFormsModule, RouterModule, NouisliderModule, - MomentModule, DragDropModule, CdkTreeModule, GoogleRecaptchaModule, diff --git a/yarn.lock b/yarn.lock index 9542bfdbe1..308d592b17 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4759,6 +4759,16 @@ data-urls@^3.0.1: whatwg-mimetype "^3.0.0" whatwg-url "^10.0.0" +date-fns-tz@^1.3.7: + version "1.3.7" + resolved "https://registry.yarnpkg.com/date-fns-tz/-/date-fns-tz-1.3.7.tgz#e8e9d2aaceba5f1cc0e677631563081fdcb0e69a" + integrity sha512-1t1b8zyJo+UI8aR+g3iqr5fkUHWpd58VBx8J/ZSQ+w7YrGlw80Ag4sA86qkfCXRBLmMc4I2US+aPMd4uKvwj5g== + +date-fns@^2.29.3: + version "2.29.3" + resolved "https://registry.yarnpkg.com/date-fns/-/date-fns-2.29.3.tgz#27402d2fc67eb442b511b70bbdf98e6411cd68a8" + integrity sha512-dDCnyH2WnnKusqvZZ6+jA1O51Ibt8ZMRNkDZdyAyK4YfbDwa/cEmuztzG5pk6hqlp9aSBPYcjOlktquahGwGeA== + date-format@^4.0.4: version "4.0.4" resolved "https://registry.yarnpkg.com/date-format/-/date-format-4.0.4.tgz#b58036e29e74121fca3e1b3e0dc4a62c65faa233" @@ -8926,11 +8936,6 @@ mkdirp@^1.0.3, mkdirp@^1.0.4: resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-1.0.4.tgz#3eb5ed62622756d79a5f0e2a221dfebad75c2f7e" integrity sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw== -moment@^2.29.4: - version "2.29.4" - resolved "https://registry.yarnpkg.com/moment/-/moment-2.29.4.tgz#3dbe052889fe7c1b2ed966fcb3a77328964ef108" - integrity sha512-5LC9SOxjSc2HF6vO2CyuTDNivEdoz2IvyJJGj6X8DJ0eFyfszE0QiEd+iXmBvUP3WHxSjFH/vIsA0EN00cgr8w== - morgan@^1.10.0: version "1.10.0" resolved "https://registry.yarnpkg.com/morgan/-/morgan-1.10.0.tgz#091778abc1fc47cd3509824653dae1faab6b17d7" @@ -9075,13 +9080,6 @@ ngx-mask@^13.1.7: dependencies: tslib "^2.3.0" -ngx-moment@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/ngx-moment/-/ngx-moment-5.0.0.tgz#6500432a2fcda75fb236a632850e599db23c8177" - integrity sha512-LPpGPo4ccdh8RWnDbJdLTLGGGcwbRYMbn/j4PXM24754J7MZ0tgnBM+ncaVbwefUSSEMme8yMkNIxFiVxgOOvQ== - dependencies: - tslib "^2.0.0" - ngx-pagination@5.0.0: version "5.0.0" resolved "https://registry.yarnpkg.com/ngx-pagination/-/ngx-pagination-5.0.0.tgz#a4b4c150a70aef17ccd825e4543e174a974bbd14"