diff --git a/client/components/cards/cardCustomFields.js b/client/components/cards/cardCustomFields.js index 23647ce53..5d02091ca 100644 --- a/client/components/cards/cardCustomFields.js +++ b/client/components/cards/cardCustomFields.js @@ -1,6 +1,25 @@ -import moment from 'moment/min/moment-with-locales'; import { TAPi18n } from '/imports/i18n'; import { DatePicker } from '/client/lib/datepicker'; +import { + formatDateTime, + formatDate, + formatTime, + getISOWeek, + isValidDate, + isBefore, + isAfter, + isSame, + add, + subtract, + startOf, + endOf, + format, + parseDate, + now, + createDate, + fromNow, + calendar +} from '/imports/lib/dateUtils'; import Cards from '/models/cards'; import { CustomFieldStringTemplate } from '/client/lib/customFields' @@ -134,18 +153,18 @@ CardCustomField.register('cardCustomField'); super.onCreated(); const self = this; self.date = ReactiveVar(); - self.now = ReactiveVar(moment()); + self.now = ReactiveVar(now()); window.setInterval(() => { - self.now.set(moment()); + self.now.set(now()); }, 60000); self.autorun(() => { - self.date.set(moment(self.data().value)); + self.date.set(new Date(self.data().value)); }); } showWeek() { - return this.date.get().week().toString(); + return getISOWeek(this.date.get()).toString(); } showWeekOfYear() { @@ -153,12 +172,7 @@ CardCustomField.register('cardCustomField'); } showDate() { - // this will start working once mquandalle:moment - // is updated to at least moment.js 2.10.5 - // until then, the date is displayed in the "L" format - return this.date.get().calendar(null, { - sameElse: 'llll', - }); + return calendar(this.date.get()); } showISODate() { @@ -167,8 +181,8 @@ CardCustomField.register('cardCustomField'); classes() { if ( - this.date.get().isBefore(this.now.get(), 'minute') && - this.now.get().isBefore(this.data().value) + isBefore(this.date.get(), this.now.get(), 'minute') && + isBefore(this.now.get(), this.data().value, 'minute') ) { return 'current'; } @@ -176,7 +190,7 @@ CardCustomField.register('cardCustomField'); } showTitle() { - return `${TAPi18n.__('card-start-on')} ${this.date.get().format('LLLL')}`; + return `${TAPi18n.__('card-start-on')} ${this.date.get().toLocaleString()}`; } events() { @@ -195,7 +209,7 @@ CardCustomField.register('cardCustomField'); const self = this; self.card = Utils.getCurrentCard(); self.customFieldId = this.data()._id; - this.data().value && this.date.set(moment(this.data().value)); + this.data().value && this.date.set(new Date(this.data().value)); } _storeDate(date) { diff --git a/client/components/cards/cardDate.js b/client/components/cards/cardDate.js index 5d255614d..cc5498fe3 100644 --- a/client/components/cards/cardDate.js +++ b/client/components/cards/cardDate.js @@ -1,17 +1,36 @@ -import moment from 'moment/min/moment-with-locales'; import { TAPi18n } from '/imports/i18n'; import { DatePicker } from '/client/lib/datepicker'; +import { + formatDateTime, + formatDate, + formatTime, + getISOWeek, + isValidDate, + isBefore, + isAfter, + isSame, + add, + subtract, + startOf, + endOf, + format, + parseDate, + now, + createDate, + fromNow, + calendar +} from '/imports/lib/dateUtils'; // editCardReceivedDatePopup (class extends DatePicker { onCreated() { - super.onCreated(moment().format('YYYY-MM-DD HH:mm')); + super.onCreated(formatDateTime(now())); this.data().getReceived() && - this.date.set(moment(this.data().getReceived())); + this.date.set(new Date(this.data().getReceived())); } _storeDate(date) { - this.card.setReceived(moment(date).format('YYYY-MM-DD HH:mm')); + this.card.setReceived(formatDateTime(date)); } _deleteDate() { @@ -22,8 +41,8 @@ import { DatePicker } from '/client/lib/datepicker'; // editCardStartDatePopup (class extends DatePicker { onCreated() { - super.onCreated(moment().format('YYYY-MM-DD HH:mm')); - this.data().getStart() && this.date.set(moment(this.data().getStart())); + super.onCreated(formatDateTime(now())); + this.data().getStart() && this.date.set(new Date(this.data().getStart())); } onRendered() { @@ -37,7 +56,7 @@ import { DatePicker } from '/client/lib/datepicker'; } _storeDate(date) { - this.card.setStart(moment(date).format('YYYY-MM-DD HH:mm')); + this.card.setStart(formatDateTime(date)); } _deleteDate() { @@ -49,7 +68,7 @@ import { DatePicker } from '/client/lib/datepicker'; (class extends DatePicker { onCreated() { super.onCreated('1970-01-01 17:00:00'); - this.data().getDue() && this.date.set(moment(this.data().getDue())); + this.data().getDue() && this.date.set(new Date(this.data().getDue())); } onRendered() { @@ -60,7 +79,7 @@ import { DatePicker } from '/client/lib/datepicker'; } _storeDate(date) { - this.card.setDue(moment(date).format('YYYY-MM-DD HH:mm')); + this.card.setDue(formatDateTime(date)); } _deleteDate() { @@ -71,8 +90,8 @@ import { DatePicker } from '/client/lib/datepicker'; // editCardEndDatePopup (class extends DatePicker { onCreated() { - super.onCreated(moment().format('YYYY-MM-DD HH:mm')); - this.data().getEnd() && this.date.set(moment(this.data().getEnd())); + super.onCreated(formatDateTime(now())); + this.data().getEnd() && this.date.set(new Date(this.data().getEnd())); } onRendered() { @@ -83,7 +102,7 @@ import { DatePicker } from '/client/lib/datepicker'; } _storeDate(date) { - this.card.setEnd(moment(date).format('YYYY-MM-DD HH:mm')); + this.card.setEnd(formatDateTime(date)); } _deleteDate() { @@ -100,14 +119,14 @@ const CardDate = BlazeComponent.extendComponent({ onCreated() { const self = this; self.date = ReactiveVar(); - self.now = ReactiveVar(moment()); + self.now = ReactiveVar(now()); window.setInterval(() => { - self.now.set(moment()); + self.now.set(now()); }, 60000); }, showWeek() { - return this.date.get().week().toString(); + return getISOWeek(this.date.get()).toString(); }, showWeekOfYear() { @@ -115,12 +134,7 @@ const CardDate = BlazeComponent.extendComponent({ }, showDate() { - // this will start working once mquandalle:moment - // is updated to at least moment.js 2.10.5 - // until then, the date is displayed in the "L" format - return this.date.get().calendar(null, { - sameElse: 'llll', - }); + return calendar(this.date.get()); }, showISODate() { @@ -133,7 +147,7 @@ class CardReceivedDate extends CardDate { super.onCreated(); const self = this; self.autorun(() => { - self.date.set(moment(self.data().getReceived())); + self.date.set(new Date(self.data().getReceived())); }); } @@ -173,7 +187,7 @@ class CardStartDate extends CardDate { super.onCreated(); const self = this; self.autorun(() => { - self.date.set(moment(self.data().getStart())); + self.date.set(new Date(self.data().getStart())); }); } @@ -208,7 +222,7 @@ class CardDueDate extends CardDate { super.onCreated(); const self = this; self.autorun(() => { - self.date.set(moment(self.data().getDue())); + self.date.set(new Date(self.data().getDue())); }); } @@ -244,7 +258,7 @@ class CardEndDate extends CardDate { super.onCreated(); const self = this; self.autorun(() => { - self.date.set(moment(self.data().getEnd())); + self.date.set(new Date(self.data().getEnd())); }); } @@ -279,12 +293,12 @@ class CardCustomFieldDate extends CardDate { super.onCreated(); const self = this; self.autorun(() => { - self.date.set(moment(self.data().value)); + self.date.set(new Date(self.data().value)); }); } showWeek() { - return this.date.get().week().toString(); + return getISOWeek(this.date.get()).toString(); } showWeekOfYear() { @@ -316,31 +330,31 @@ CardCustomFieldDate.register('cardCustomFieldDate'); (class extends CardReceivedDate { showDate() { - return this.date.get().format('L'); + return format(this.date.get(), 'L'); } }.register('minicardReceivedDate')); (class extends CardStartDate { showDate() { - return this.date.get().format('YYYY-MM-DD HH:mm'); + return format(this.date.get(), 'YYYY-MM-DD HH:mm'); } }.register('minicardStartDate')); (class extends CardDueDate { showDate() { - return this.date.get().format('YYYY-MM-DD HH:mm'); + return format(this.date.get(), 'YYYY-MM-DD HH:mm'); } }.register('minicardDueDate')); (class extends CardEndDate { showDate() { - return this.date.get().format('YYYY-MM-DD HH:mm'); + return format(this.date.get(), 'YYYY-MM-DD HH:mm'); } }.register('minicardEndDate')); (class extends CardCustomFieldDate { showDate() { - return this.date.get().format('L'); + return format(this.date.get(), 'L'); } }.register('minicardCustomFieldDate')); @@ -349,7 +363,7 @@ class VoteEndDate extends CardDate { super.onCreated(); const self = this; self.autorun(() => { - self.date.set(moment(self.data().getVoteEnd())); + self.date.set(new Date(self.data().getVoteEnd())); }); } classes() { @@ -357,10 +371,10 @@ class VoteEndDate extends CardDate { return classes; } showDate() { - return this.date.get().format('L LT'); + return format(this.date.get(), 'L') + ' ' + format(this.date.get(), 'HH:mm'); } showTitle() { - return `${TAPi18n.__('card-end-on')} ${this.date.get().format('LLLL')}`; + return `${TAPi18n.__('card-end-on')} ${this.date.get().toLocaleString()}`; } events() { @@ -376,7 +390,7 @@ class PokerEndDate extends CardDate { super.onCreated(); const self = this; self.autorun(() => { - self.date.set(moment(self.data().getPokerEnd())); + self.date.set(new Date(self.data().getPokerEnd())); }); } classes() { diff --git a/client/components/cards/cardDetails.js b/client/components/cards/cardDetails.js index b6e62c740..da95765ba 100644 --- a/client/components/cards/cardDetails.js +++ b/client/components/cards/cardDetails.js @@ -1,7 +1,26 @@ import { ReactiveCache } from '/imports/reactiveCache'; -import moment from 'moment/min/moment-with-locales'; import { TAPi18n } from '/imports/i18n'; import { DatePicker } from '/client/lib/datepicker'; +import { + formatDateTime, + formatDate, + formatTime, + getISOWeek, + isValidDate, + isBefore, + isAfter, + isSame, + add, + subtract, + startOf, + endOf, + format, + parseDate, + now, + createDate, + fromNow, + calendar +} from '/imports/lib/dateUtils'; import Cards from '/models/cards'; import Boards from '/models/boards'; import Checklists from '/models/checklists'; @@ -455,7 +474,7 @@ BlazeComponent.extendComponent({ 'click .js-poker-finish'(e) { if ($(e.target).hasClass('js-poker-finish')) { e.preventDefault(); - const now = moment().format('YYYY-MM-DD HH:mm'); + const now = formatDateTime(new Date()); this.data().setPokerEnd(now); } }, @@ -1106,8 +1125,8 @@ BlazeComponent.extendComponent({ // editVoteEndDatePopup (class extends DatePicker { onCreated() { - super.onCreated(moment().format('YYYY-MM-DD HH:mm')); - this.data().getVoteEnd() && this.date.set(moment(this.data().getVoteEnd())); + super.onCreated(formatDateTime(now())); + this.data().getVoteEnd() && this.date.set(new Date(this.data().getVoteEnd())); } events() { return [ @@ -1118,12 +1137,12 @@ BlazeComponent.extendComponent({ // if no time was given, init with 12:00 const time = evt.target.time.value || - moment(new Date().setHours(12, 0, 0)).format('LT'); + formatTime(new Date().setHours(12, 0, 0)); const dateString = `${evt.target.date.value} ${time}`; /* - const newDate = moment(dateString, 'L LT', true); + const newDate = parseDate(dateString, ['L LT'], true); if (newDate.isValid()) { // if active vote - store it if (this.currentData().getVoteQuestion()) { @@ -1137,28 +1156,27 @@ BlazeComponent.extendComponent({ */ - // Try to parse different date formats of all languages. - // This code is same for vote and planning poker. - const usaDate = moment(dateString, 'L LT', true); - const euroAmDate = moment(dateString, 'DD.MM.YYYY LT', true); - const euro24hDate = moment(dateString, 'DD.MM.YYYY HH.mm', true); - const eurodotDate = moment(dateString, 'DD.MM.YYYY HH:mm', true); - const minusDate = moment(dateString, 'YYYY-MM-DD HH:mm', true); - const slashDate = moment(dateString, 'DD/MM/YYYY HH.mm', true); - const dotDate = moment(dateString, 'DD/MM/YYYY HH:mm', true); - const brezhonegDate = moment(dateString, 'DD/MM/YYYY h[e]mm A', true); - const hrvatskiDate = moment(dateString, 'DD. MM. YYYY H:mm', true); - const latviaDate = moment(dateString, 'YYYY.MM.DD. H:mm', true); - const nederlandsDate = moment(dateString, 'DD-MM-YYYY HH:mm', true); - // greekDate does not work: el Greek Ελληνικά , - // it has date format DD/MM/YYYY h:mm MM like 20/06/2021 11:15 MM - // where MM is maybe some text like AM/PM ? - // Also some other languages that have non-ascii characters in dates - // do not work. - const greekDate = moment(dateString, 'DD/MM/YYYY h:mm A', true); - const macedonianDate = moment(dateString, 'D.MM.YYYY H:mm', true); + // Try to parse different date formats using native Date parsing + const formats = [ + 'YYYY-MM-DD HH:mm', + 'MM/DD/YYYY HH:mm', + 'DD.MM.YYYY HH:mm', + 'DD/MM/YYYY HH:mm', + 'DD-MM-YYYY HH:mm' + ]; + + let parsedDate = null; + for (const format of formats) { + parsedDate = parseDate(dateString, [format], true); + if (parsedDate) break; + } + + // Fallback to native Date parsing + if (!parsedDate) { + parsedDate = new Date(dateString); + } - if (usaDate.isValid()) { + if (isValidDate(parsedDate)) { // if active poker - store it if (this.currentData().getPokerQuestion()) { this._storeDate(usaDate.toDate()); @@ -1337,9 +1355,9 @@ BlazeComponent.extendComponent({ // editPokerEndDatePopup (class extends DatePicker { onCreated() { - super.onCreated(moment().format('YYYY-MM-DD HH:mm')); + super.onCreated(formatDateTime(now())); this.data().getPokerEnd() && - this.date.set(moment(this.data().getPokerEnd())); + this.date.set(new Date(this.data().getPokerEnd())); } /* @@ -1357,7 +1375,7 @@ BlazeComponent.extendComponent({ return moment.localeData().longDateFormat('LT'); } - const newDate = moment(dateString, dateformat() + ' ' + timeformat(), true); + const newDate = parseDate(dateString, [dateformat() + ' ' + timeformat()], true); */ events() { @@ -1369,7 +1387,7 @@ BlazeComponent.extendComponent({ // if no time was given, init with 12:00 const time = evt.target.time.value || - moment(new Date().setHours(12, 0, 0)).format('LT'); + formatTime(new Date().setHours(12, 0, 0)); const dateString = `${evt.target.date.value} ${time}`; @@ -1380,7 +1398,7 @@ BlazeComponent.extendComponent({ Maybe client/components/lib/datepicker.jade could have hidden input field for datepicker format that could be used to detect date format? - const newDate = moment(dateString, dateformat() + ' ' + timeformat(), true); + const newDate = parseDate(dateString, [dateformat() + ' ' + timeformat()], true); if (newDate.isValid()) { // if active poker - store it @@ -1393,28 +1411,27 @@ BlazeComponent.extendComponent({ } */ - // Try to parse different date formats of all languages. - // This code is same for vote and planning poker. - const usaDate = moment(dateString, 'L LT', true); - const euroAmDate = moment(dateString, 'DD.MM.YYYY LT', true); - const euro24hDate = moment(dateString, 'DD.MM.YYYY HH.mm', true); - const eurodotDate = moment(dateString, 'DD.MM.YYYY HH:mm', true); - const minusDate = moment(dateString, 'YYYY-MM-DD HH:mm', true); - const slashDate = moment(dateString, 'DD/MM/YYYY HH.mm', true); - const dotDate = moment(dateString, 'DD/MM/YYYY HH:mm', true); - const brezhonegDate = moment(dateString, 'DD/MM/YYYY h[e]mm A', true); - const hrvatskiDate = moment(dateString, 'DD. MM. YYYY H:mm', true); - const latviaDate = moment(dateString, 'YYYY.MM.DD. H:mm', true); - const nederlandsDate = moment(dateString, 'DD-MM-YYYY HH:mm', true); - // greekDate does not work: el Greek Ελληνικά , - // it has date format DD/MM/YYYY h:mm MM like 20/06/2021 11:15 MM - // where MM is maybe some text like AM/PM ? - // Also some other languages that have non-ascii characters in dates - // do not work. - const greekDate = moment(dateString, 'DD/MM/YYYY h:mm A', true); - const macedonianDate = moment(dateString, 'D.MM.YYYY H:mm', true); + // Try to parse different date formats using native Date parsing + const formats = [ + 'YYYY-MM-DD HH:mm', + 'MM/DD/YYYY HH:mm', + 'DD.MM.YYYY HH:mm', + 'DD/MM/YYYY HH:mm', + 'DD-MM-YYYY HH:mm' + ]; + + let parsedDate = null; + for (const format of formats) { + parsedDate = parseDate(dateString, [format], true); + if (parsedDate) break; + } + + // Fallback to native Date parsing + if (!parsedDate) { + parsedDate = new Date(dateString); + } - if (usaDate.isValid()) { + if (isValidDate(parsedDate)) { // if active poker - store it if (this.currentData().getPokerQuestion()) { this._storeDate(usaDate.toDate()); diff --git a/client/config/blazeHelpers.js b/client/config/blazeHelpers.js index 10e558b37..f42dfed94 100644 --- a/client/config/blazeHelpers.js +++ b/client/config/blazeHelpers.js @@ -1,7 +1,26 @@ import { ReactiveCache } from '/imports/reactiveCache'; import { Blaze } from 'meteor/blaze'; import { Session } from 'meteor/session'; -import moment from 'moment/min/moment-with-locales'; +import { + formatDateTime, + formatDate, + formatTime, + getISOWeek, + isValidDate, + isBefore, + isAfter, + isSame, + add, + subtract, + startOf, + endOf, + format, + parseDate, + now, + createDate, + fromNow, + calendar +} from '/imports/lib/dateUtils'; Blaze.registerHelper('currentBoard', () => { const ret = Utils.getCurrentBoard(); @@ -47,7 +66,7 @@ Blaze.registerHelper('isTouchScreenOrShowDesktopDragHandles', () => Blaze.registerHelper('moment', (...args) => { args.pop(); // hash const [date, format] = args; - return moment(date).format(format ?? 'LLLL'); + return format(new Date(date), format ?? 'LLLL'); }); Blaze.registerHelper('canModifyCard', () => diff --git a/client/lib/datepicker.js b/client/lib/datepicker.js index fb216d65e..08b15b2eb 100644 --- a/client/lib/datepicker.js +++ b/client/lib/datepicker.js @@ -1,12 +1,29 @@ import { ReactiveCache } from '/imports/reactiveCache'; import { TAPi18n } from '/imports/i18n'; -import moment from 'moment/min/moment-with-locales'; +import { + formatDateTime, + formatDate, + formatTime, + getISOWeek, + isValidDate, + isBefore, + isAfter, + isSame, + add, + subtract, + startOf, + endOf, + format, + parseDate, + now, + createDate, + fromNow, + calendar +} from '/imports/lib/dateUtils'; -// Helper function to replace HH with H for 24 hours format, because H allows also single-digit hours +// Helper function to get time format for 24 hours function adjustedTimeFormat() { - return moment - .localeData() - .longDateFormat('LT'); + return 'HH:mm'; } // .replace(/HH/i, 'H'); @@ -19,7 +36,7 @@ export class DatePicker extends BlazeComponent { onCreated(defaultTime = '1970-01-01 08:00:00') { this.error = new ReactiveVar(''); this.card = this.data(); - this.date = new ReactiveVar(moment.invalid()); + this.date = new ReactiveVar(new Date('invalid')); this.defaultTime = defaultTime; } @@ -34,35 +51,35 @@ export class DatePicker extends BlazeComponent { onRendered() { // Set initial values for native HTML inputs - if (this.date.get().isValid()) { + if (isValidDate(this.date.get())) { const dateInput = this.find('#date'); const timeInput = this.find('#time'); if (dateInput) { - dateInput.value = this.date.get().format('YYYY-MM-DD'); + dateInput.value = formatDate(this.date.get()); } if (timeInput && !timeInput.value && this.defaultTime) { - const defaultMoment = moment(this.defaultTime); - timeInput.value = defaultMoment.format('HH:mm'); - } else if (timeInput && this.date.get().isValid()) { - timeInput.value = this.date.get().format('HH:mm'); + const defaultDate = new Date(this.defaultTime); + timeInput.value = formatTime(defaultDate); + } else if (timeInput && isValidDate(this.date.get())) { + timeInput.value = formatTime(this.date.get()); } } } showDate() { - if (this.date.get().isValid()) return this.date.get().format('YYYY-MM-DD'); + if (isValidDate(this.date.get())) return formatDate(this.date.get()); return ''; } showTime() { - if (this.date.get().isValid()) return this.date.get().format('HH:mm'); + if (isValidDate(this.date.get())) return formatTime(this.date.get()); return ''; } dateFormat() { - return moment.localeData().longDateFormat('L'); + return 'L'; } timeFormat() { - return moment.localeData().longDateFormat('LT'); + return 'LT'; } events() { @@ -72,8 +89,8 @@ export class DatePicker extends BlazeComponent { // Native HTML date input validation const dateValue = this.find('#date').value; if (dateValue) { - const dateMoment = moment(dateValue, 'YYYY-MM-DD', true); - if (dateMoment.isValid()) { + const dateObj = new Date(dateValue); + if (isValidDate(dateObj)) { this.error.set(''); } else { this.error.set('invalid-date'); @@ -84,8 +101,8 @@ export class DatePicker extends BlazeComponent { // Native HTML time input validation const timeValue = this.find('#time').value; if (timeValue) { - const timeMoment = moment(timeValue, 'HH:mm', true); - if (timeMoment.isValid()) { + const timeObj = new Date(`1970-01-01T${timeValue}`); + if (isValidDate(timeObj)) { this.error.set(''); } else { this.error.set('invalid-time'); @@ -104,14 +121,14 @@ export class DatePicker extends BlazeComponent { return; } - const newCompleteDate = moment(`${dateValue} ${timeValue}`, 'YYYY-MM-DD HH:mm', true); + const newCompleteDate = new Date(`${dateValue}T${timeValue}`); - if (!newCompleteDate.isValid()) { + if (!isValidDate(newCompleteDate)) { this.error.set('invalid'); return; } - this._storeDate(newCompleteDate.toDate()); + this._storeDate(newCompleteDate); Popup.back(); }, 'click .js-delete-date'(evt) { diff --git a/client/lib/filter.js b/client/lib/filter.js index 66ed01a36..463bd4535 100644 --- a/client/lib/filter.js +++ b/client/lib/filter.js @@ -1,5 +1,24 @@ import { ReactiveCache } from '/imports/reactiveCache'; -import moment from 'moment/min/moment-with-locales'; +import { + formatDateTime, + formatDate, + formatTime, + getISOWeek, + isValidDate, + isBefore, + isAfter, + isSame, + add, + subtract, + startOf, + endOf, + format, + parseDate, + now, + createDate, + fromNow, + calendar +} from '/imports/lib/dateUtils'; // Filtered view manager // We define local filter objects for each different type of field (SetFilter, @@ -30,7 +49,7 @@ class DateFilter { this.reset(); return; } - this._filter = { $lte: moment().toDate() }; + this._filter = { $lte: now() }; this._updateState('past'); } @@ -72,13 +91,8 @@ class DateFilter { return; } - var startDay = moment() - .startOf('day') - .toDate(), - endDay = moment() - .endOf('day') - .add(offset, 'day') - .toDate(); + var startDay = startOf(now(), 'day'), + endDay = endOf(add(now(), offset, 'day'), 'day'); if (offset >= 0) { this._filter = { $gte: startDay, $lte: endDay }; @@ -112,33 +126,21 @@ class DateFilter { const weekStartDay = currentUser ? currentUser.getStartDayOfWeek() : 1; if (week === 'this') { - // Moments are mutable so they must be cloned before modification - var WeekStart = moment() - .startOf('day') - .startOf('week') - .add(weekStartDay, 'days'); - var WeekEnd = WeekStart - .clone() - .add(6, 'days') - .endOf('day'); + // Create week start and end dates + var WeekStart = startOf(add(startOf(now(), 'week'), weekStartDay, 'days'), 'day'); + var WeekEnd = endOf(add(WeekStart, 6, 'days'), 'day'); this._updateState('thisweek'); } else if (week === 'next') { - // Moments are mutable so they must be cloned before modification - var WeekStart = moment() - .startOf('day') - .startOf('week') - .add(weekStartDay + 7, 'days'); - var WeekEnd = WeekStart - .clone() - .add(6, 'days') - .endOf('day'); + // Create next week start and end dates + var WeekStart = startOf(add(startOf(now(), 'week'), weekStartDay + 7, 'days'), 'day'); + var WeekEnd = endOf(add(WeekStart, 6, 'days'), 'day'); this._updateState('nextweek'); } - var startDate = WeekStart.toDate(); - var endDate = WeekEnd.toDate(); + var startDate = WeekStart; + var endDate = WeekEnd; if (offset >= 0) { this._filter = { $gte: startDate, $lte: endDate }; diff --git a/config/query-classes.js b/config/query-classes.js index 4f0ef66a7..7ef4eb5fa 100644 --- a/config/query-classes.js +++ b/config/query-classes.js @@ -1,5 +1,24 @@ -import moment from 'moment/min/moment-with-locales'; import { TAPi18n } from '/imports/i18n'; +import { + formatDateTime, + formatDate, + formatTime, + getISOWeek, + isValidDate, + isBefore, + isAfter, + isSame, + add, + subtract, + startOf, + endOf, + format, + parseDate, + now, + createDate, + fromNow, + calendar +} from '/imports/lib/dateUtils'; import { OPERATOR_ASSIGNEE, OPERATOR_BOARD, @@ -421,43 +440,44 @@ export class Query { switch (duration) { case PREDICATE_WEEK: // eslint-disable-next-line no-case-declarations - const week = moment().week(); + const week = getISOWeek(now()); if (week === 52) { - date = moment(1, 'W'); - date.set('year', date.year() + 1); + date = new Date(now().getFullYear() + 1, 0, 1); // January 1st of next year } else { - date = moment(week + 1, 'W'); + // Calculate the date for the next week + const currentDate = now(); + const daysToAdd = (week + 1) * 7 - (currentDate.getDay() + 6) % 7; + date = add(currentDate, daysToAdd, 'days'); } break; case PREDICATE_MONTH: // eslint-disable-next-line no-case-declarations - const month = moment().month(); - // .month() is zero indexed + const month = now().getMonth(); + // .getMonth() is zero indexed if (month === 11) { - date = moment(1, 'M'); - date.set('year', date.year() + 1); + date = new Date(now().getFullYear() + 1, 0, 1); // January 1st of next year } else { - date = moment(month + 2, 'M'); + date = new Date(now().getFullYear(), month + 1, 1); // First day of next month } break; case PREDICATE_QUARTER: // eslint-disable-next-line no-case-declarations - const quarter = moment().quarter(); + const quarter = Math.floor(now().getMonth() / 3) + 1; if (quarter === 4) { - date = moment(1, 'Q'); - date.set('year', date.year() + 1); + date = new Date(now().getFullYear() + 1, 0, 1); // January 1st of next year } else { - date = moment(quarter + 1, 'Q'); + const nextQuarterMonth = quarter * 3; // 3, 6, 9 for quarters 2, 3, 4 + date = new Date(now().getFullYear(), nextQuarterMonth, 1); // First day of next quarter } break; case PREDICATE_YEAR: - date = moment(moment().year() + 1, 'YYYY'); + date = new Date(now().getFullYear() + 1, 0, 1); // January 1st of next year break; } if (date) { value = { operator: '$lt', - value: date.format('YYYY-MM-DD'), + value: formatDate(date), }; } } else if ( @@ -466,7 +486,7 @@ export class Query { ) { value = { operator: '$lt', - value: moment().format('YYYY-MM-DD'), + value: formatDate(now()), }; } else { this.addError(OPERATOR_DUE, { @@ -478,16 +498,12 @@ export class Query { } else if (operator === OPERATOR_DUE) { value = { operator: '$lt', - value: moment(moment().format('YYYY-MM-DD')) - .add(days + 1, duration ? duration : 'days') - .format(), + value: formatDate(add(add(now(), 1, 'days'), days + 1, duration ? duration : 'days')), }; } else { value = { operator: '$gte', - value: moment(moment().format('YYYY-MM-DD')) - .subtract(days, duration ? duration : 'days') - .format(), + value: formatDate(subtract(now(), days, duration ? duration : 'days')), }; } } else if (operator === OPERATOR_SORT) { diff --git a/imports/i18n/index.js b/imports/i18n/index.js index c8e39e358..b18a217cc 100644 --- a/imports/i18n/index.js +++ b/imports/i18n/index.js @@ -1,6 +1,5 @@ import { TAPi18n } from './tap'; import './accounts'; -import './moment'; if (Meteor.isClient) { import './blaze'; diff --git a/imports/i18n/moment.js b/imports/i18n/moment.js index 998e45f86..a2a378eee 100644 --- a/imports/i18n/moment.js +++ b/imports/i18n/moment.js @@ -1,13 +1,5 @@ import { Tracker } from 'meteor/tracker'; -import moment from 'moment/min/moment-with-locales'; import { TAPi18n } from './tap'; -// Reactively adjust Moment.js translations -Tracker.autorun(() => { - const language = TAPi18n.getLanguage(); - try { - moment.locale(language); - } catch (err) { - console.error(err); - } -}); +// Note: moment.js has been removed and replaced with native JavaScript Date functions +// Locale handling is now done through native Date.toLocaleString() methods diff --git a/imports/lib/dateUtils.js b/imports/lib/dateUtils.js new file mode 100644 index 000000000..de7d95ea7 --- /dev/null +++ b/imports/lib/dateUtils.js @@ -0,0 +1,492 @@ +/** + * Date utility functions to replace moment.js with native JavaScript Date + */ + +/** + * Format a date to YYYY-MM-DD HH:mm format + * @param {Date|string} date - Date to format + * @returns {string} Formatted date string + */ +export function formatDateTime(date) { + const d = new Date(date); + if (isNaN(d.getTime())) return ''; + + const year = d.getFullYear(); + const month = String(d.getMonth() + 1).padStart(2, '0'); + const day = String(d.getDate()).padStart(2, '0'); + const hours = String(d.getHours()).padStart(2, '0'); + const minutes = String(d.getMinutes()).padStart(2, '0'); + + return `${year}-${month}-${day} ${hours}:${minutes}`; +} + +/** + * Format a date to YYYY-MM-DD format + * @param {Date|string} date - Date to format + * @returns {string} Formatted date string + */ +export function formatDate(date) { + const d = new Date(date); + if (isNaN(d.getTime())) return ''; + + const year = d.getFullYear(); + const month = String(d.getMonth() + 1).padStart(2, '0'); + const day = String(d.getDate()).padStart(2, '0'); + + return `${year}-${month}-${day}`; +} + +/** + * Format a time to HH:mm format + * @param {Date|string} date - Date to format + * @returns {string} Formatted time string + */ +export function formatTime(date) { + const d = new Date(date); + if (isNaN(d.getTime())) return ''; + + const hours = String(d.getHours()).padStart(2, '0'); + const minutes = String(d.getMinutes()).padStart(2, '0'); + + return `${hours}:${minutes}`; +} + +/** + * Get ISO week number (ISO 8601) + * @param {Date|string} date - Date to get week number for + * @returns {number} ISO week number + */ +export function getISOWeek(date) { + const d = new Date(date); + if (isNaN(d.getTime())) return 0; + + // Set to nearest Thursday: current date + 4 - current day number + // Make Sunday's day number 7 + const target = new Date(d); + const dayNr = (d.getDay() + 6) % 7; + target.setDate(target.getDate() - dayNr + 3); + + // ISO week date weeks start on monday, so correct the day number + const firstThursday = target.valueOf(); + target.setMonth(0, 1); + if (target.getDay() !== 4) { + target.setMonth(0, 1 + ((4 - target.getDay()) + 7) % 7); + } + + return 1 + Math.ceil((firstThursday - target) / 604800000); // 604800000 = 7 * 24 * 3600 * 1000 +} + +/** + * Check if a date is valid + * @param {Date|string} date - Date to check + * @returns {boolean} True if date is valid + */ +export function isValidDate(date) { + const d = new Date(date); + return !isNaN(d.getTime()); +} + +/** + * Check if a date is before another date + * @param {Date|string} date1 - First date + * @param {Date|string} date2 - Second date + * @param {string} unit - Unit of comparison ('minute', 'hour', 'day', etc.) + * @returns {boolean} True if date1 is before date2 + */ +export function isBefore(date1, date2, unit = 'millisecond') { + const d1 = new Date(date1); + const d2 = new Date(date2); + + if (isNaN(d1.getTime()) || isNaN(d2.getTime())) return false; + + switch (unit) { + case 'year': + return d1.getFullYear() < d2.getFullYear(); + case 'month': + return d1.getFullYear() < d2.getFullYear() || + (d1.getFullYear() === d2.getFullYear() && d1.getMonth() < d2.getMonth()); + case 'day': + return d1.getFullYear() < d2.getFullYear() || + (d1.getFullYear() === d2.getFullYear() && d1.getMonth() < d2.getMonth()) || + (d1.getFullYear() === d2.getFullYear() && d1.getMonth() === d2.getMonth() && d1.getDate() < d2.getDate()); + case 'hour': + return d1.getTime() < d2.getTime() && Math.floor(d1.getTime() / (1000 * 60 * 60)) < Math.floor(d2.getTime() / (1000 * 60 * 60)); + case 'minute': + return d1.getTime() < d2.getTime() && Math.floor(d1.getTime() / (1000 * 60)) < Math.floor(d2.getTime() / (1000 * 60)); + default: + return d1.getTime() < d2.getTime(); + } +} + +/** + * Check if a date is after another date + * @param {Date|string} date1 - First date + * @param {Date|string} date2 - Second date + * @param {string} unit - Unit of comparison ('minute', 'hour', 'day', etc.) + * @returns {boolean} True if date1 is after date2 + */ +export function isAfter(date1, date2, unit = 'millisecond') { + return isBefore(date2, date1, unit); +} + +/** + * Check if a date is the same as another date + * @param {Date|string} date1 - First date + * @param {Date|string} date2 - Second date + * @param {string} unit - Unit of comparison ('minute', 'hour', 'day', etc.) + * @returns {boolean} True if dates are the same + */ +export function isSame(date1, date2, unit = 'millisecond') { + const d1 = new Date(date1); + const d2 = new Date(date2); + + if (isNaN(d1.getTime()) || isNaN(d2.getTime())) return false; + + switch (unit) { + case 'year': + return d1.getFullYear() === d2.getFullYear(); + case 'month': + return d1.getFullYear() === d2.getFullYear() && d1.getMonth() === d2.getMonth(); + case 'day': + return d1.getFullYear() === d2.getFullYear() && d1.getMonth() === d2.getMonth() && d1.getDate() === d2.getDate(); + case 'hour': + return Math.floor(d1.getTime() / (1000 * 60 * 60)) === Math.floor(d2.getTime() / (1000 * 60 * 60)); + case 'minute': + return Math.floor(d1.getTime() / (1000 * 60)) === Math.floor(d2.getTime() / (1000 * 60)); + default: + return d1.getTime() === d2.getTime(); + } +} + +/** + * Add time to a date + * @param {Date|string} date - Base date + * @param {number} amount - Amount to add + * @param {string} unit - Unit ('years', 'months', 'days', 'hours', 'minutes', 'seconds') + * @returns {Date} New date + */ +export function add(date, amount, unit) { + const d = new Date(date); + if (isNaN(d.getTime())) return new Date(); + + switch (unit) { + case 'years': + d.setFullYear(d.getFullYear() + amount); + break; + case 'months': + d.setMonth(d.getMonth() + amount); + break; + case 'days': + d.setDate(d.getDate() + amount); + break; + case 'hours': + d.setHours(d.getHours() + amount); + break; + case 'minutes': + d.setMinutes(d.getMinutes() + amount); + break; + case 'seconds': + d.setSeconds(d.getSeconds() + amount); + break; + default: + d.setTime(d.getTime() + amount); + } + + return d; +} + +/** + * Subtract time from a date + * @param {Date|string} date - Base date + * @param {number} amount - Amount to subtract + * @param {string} unit - Unit ('years', 'months', 'days', 'hours', 'minutes', 'seconds') + * @returns {Date} New date + */ +export function subtract(date, amount, unit) { + return add(date, -amount, unit); +} + +/** + * Get start of a time unit + * @param {Date|string} date - Base date + * @param {string} unit - Unit ('year', 'month', 'day', 'hour', 'minute', 'second') + * @returns {Date} Start of unit + */ +export function startOf(date, unit) { + const d = new Date(date); + if (isNaN(d.getTime())) return new Date(); + + switch (unit) { + case 'year': + d.setMonth(0, 1); + d.setHours(0, 0, 0, 0); + break; + case 'month': + d.setDate(1); + d.setHours(0, 0, 0, 0); + break; + case 'day': + d.setHours(0, 0, 0, 0); + break; + case 'hour': + d.setMinutes(0, 0, 0); + break; + case 'minute': + d.setSeconds(0, 0); + break; + case 'second': + d.setMilliseconds(0); + break; + } + + return d; +} + +/** + * Get end of a time unit + * @param {Date|string} date - Base date + * @param {string} unit - Unit ('year', 'month', 'day', 'hour', 'minute', 'second') + * @returns {Date} End of unit + */ +export function endOf(date, unit) { + const d = new Date(date); + if (isNaN(d.getTime())) return new Date(); + + switch (unit) { + case 'year': + d.setMonth(11, 31); + d.setHours(23, 59, 59, 999); + break; + case 'month': + d.setMonth(d.getMonth() + 1, 0); + d.setHours(23, 59, 59, 999); + break; + case 'day': + d.setHours(23, 59, 59, 999); + break; + case 'hour': + d.setMinutes(59, 59, 999); + break; + case 'minute': + d.setSeconds(59, 999); + break; + case 'second': + d.setMilliseconds(999); + break; + } + + return d; +} + +/** + * Format date for display with locale + * @param {Date|string} date - Date to format + * @param {string} format - Format string (simplified) + * @returns {string} Formatted date string + */ +export function format(date, format = 'L') { + const d = new Date(date); + if (isNaN(d.getTime())) return ''; + + const year = d.getFullYear(); + const month = String(d.getMonth() + 1).padStart(2, '0'); + const day = String(d.getDate()).padStart(2, '0'); + const hours = String(d.getHours()).padStart(2, '0'); + const minutes = String(d.getMinutes()).padStart(2, '0'); + const seconds = String(d.getSeconds()).padStart(2, '0'); + + switch (format) { + case 'L': + return `${month}/${day}/${year}`; + case 'LL': + return d.toLocaleDateString(); + case 'LLL': + return d.toLocaleString(); + case 'llll': + return d.toLocaleString(); + case 'YYYY-MM-DD': + return `${year}-${month}-${day}`; + case 'YYYY-MM-DD HH:mm': + return `${year}-${month}-${day} ${hours}:${minutes}`; + case 'HH:mm': + return `${hours}:${minutes}`; + default: + return d.toLocaleString(); + } +} + +/** + * Parse a date string with multiple formats + * @param {string} dateString - Date string to parse + * @param {string[]} formats - Array of format strings to try + * @param {boolean} strict - Whether to use strict parsing + * @returns {Date|null} Parsed date or null if invalid + */ +export function parseDate(dateString, formats = [], strict = true) { + if (!dateString) return null; + + // Try native Date parsing first + const nativeDate = new Date(dateString); + if (!isNaN(nativeDate.getTime())) { + return nativeDate; + } + + // Try common formats + const commonFormats = [ + 'YYYY-MM-DD HH:mm', + 'YYYY-MM-DD', + 'MM/DD/YYYY HH:mm', + 'MM/DD/YYYY', + 'DD.MM.YYYY HH:mm', + 'DD.MM.YYYY', + 'DD/MM/YYYY HH:mm', + 'DD/MM/YYYY', + 'DD-MM-YYYY HH:mm', + 'DD-MM-YYYY' + ]; + + const allFormats = [...formats, ...commonFormats]; + + for (const format of allFormats) { + const parsed = parseWithFormat(dateString, format); + if (parsed && isValidDate(parsed)) { + return parsed; + } + } + + return null; +} + +/** + * Parse date with specific format + * @param {string} dateString - Date string to parse + * @param {string} format - Format string + * @returns {Date|null} Parsed date or null + */ +function parseWithFormat(dateString, format) { + // Simple format parsing - can be extended as needed + const formatMap = { + 'YYYY': '\\d{4}', + 'MM': '\\d{2}', + 'DD': '\\d{2}', + 'HH': '\\d{2}', + 'mm': '\\d{2}', + 'ss': '\\d{2}' + }; + + let regex = format; + for (const [key, value] of Object.entries(formatMap)) { + regex = regex.replace(new RegExp(key, 'g'), `(${value})`); + } + + const match = dateString.match(new RegExp(regex)); + if (!match) return null; + + const groups = match.slice(1); + let year, month, day, hour = 0, minute = 0, second = 0; + + let groupIndex = 0; + for (let i = 0; i < format.length; i++) { + if (format[i] === 'Y' && format[i + 1] === 'Y' && format[i + 2] === 'Y' && format[i + 3] === 'Y') { + year = parseInt(groups[groupIndex++]); + i += 3; + } else if (format[i] === 'M' && format[i + 1] === 'M') { + month = parseInt(groups[groupIndex++]) - 1; + i += 1; + } else if (format[i] === 'D' && format[i + 1] === 'D') { + day = parseInt(groups[groupIndex++]); + i += 1; + } else if (format[i] === 'H' && format[i + 1] === 'H') { + hour = parseInt(groups[groupIndex++]); + i += 1; + } else if (format[i] === 'm' && format[i + 1] === 'm') { + minute = parseInt(groups[groupIndex++]); + i += 1; + } else if (format[i] === 's' && format[i + 1] === 's') { + second = parseInt(groups[groupIndex++]); + i += 1; + } + } + + if (year === undefined || month === undefined || day === undefined) { + return null; + } + + return new Date(year, month, day, hour, minute, second); +} + +/** + * Get current date and time + * @returns {Date} Current date + */ +export function now() { + return new Date(); +} + +/** + * Create a date from components + * @param {number} year - Year + * @param {number} month - Month (0-based) + * @param {number} day - Day + * @param {number} hour - Hour (optional) + * @param {number} minute - Minute (optional) + * @param {number} second - Second (optional) + * @returns {Date} Created date + */ +export function createDate(year, month, day, hour = 0, minute = 0, second = 0) { + return new Date(year, month, day, hour, minute, second); +} + +/** + * Get relative time string (e.g., "2 hours ago") + * @param {Date|string} date - Date to compare + * @param {Date|string} now - Current date (optional) + * @returns {string} Relative time string + */ +export function fromNow(date, now = new Date()) { + const d = new Date(date); + const n = new Date(now); + + if (isNaN(d.getTime()) || isNaN(n.getTime())) return ''; + + const diffMs = n.getTime() - d.getTime(); + const diffSeconds = Math.floor(diffMs / 1000); + const diffMinutes = Math.floor(diffSeconds / 60); + const diffHours = Math.floor(diffMinutes / 60); + const diffDays = Math.floor(diffHours / 24); + const diffWeeks = Math.floor(diffDays / 7); + const diffMonths = Math.floor(diffDays / 30); + const diffYears = Math.floor(diffDays / 365); + + if (diffSeconds < 60) return 'a few seconds ago'; + if (diffMinutes < 60) return `${diffMinutes} minute${diffMinutes !== 1 ? 's' : ''} ago`; + if (diffHours < 24) return `${diffHours} hour${diffHours !== 1 ? 's' : ''} ago`; + if (diffDays < 7) return `${diffDays} day${diffDays !== 1 ? 's' : ''} ago`; + if (diffWeeks < 4) return `${diffWeeks} week${diffWeeks !== 1 ? 's' : ''} ago`; + if (diffMonths < 12) return `${diffMonths} month${diffMonths !== 1 ? 's' : ''} ago`; + return `${diffYears} year${diffYears !== 1 ? 's' : ''} ago`; +} + +/** + * Get calendar format (e.g., "Today", "Yesterday", "Tomorrow") + * @param {Date|string} date - Date to format + * @param {Date|string} now - Current date (optional) + * @returns {string} Calendar format string + */ +export function calendar(date, now = new Date()) { + const d = new Date(date); + const n = new Date(now); + + if (isNaN(d.getTime()) || isNaN(n.getTime())) return format(d); + + const diffMs = d.getTime() - n.getTime(); + const diffDays = Math.floor(diffMs / (1000 * 60 * 60 * 24)); + + if (diffDays === 0) return 'Today'; + if (diffDays === 1) return 'Tomorrow'; + if (diffDays === -1) return 'Yesterday'; + if (diffDays > 1 && diffDays < 7) return `In ${diffDays} days`; + if (diffDays < -1 && diffDays > -7) return `${Math.abs(diffDays)} days ago`; + + return format(d, 'L'); +} diff --git a/models/cards.js b/models/cards.js index 548990a3c..1959e5de3 100644 --- a/models/cards.js +++ b/models/cards.js @@ -1,5 +1,24 @@ import { ReactiveCache, ReactiveMiniMongoIndex } from '/imports/reactiveCache'; -import moment from 'moment/min/moment-with-locales'; +import { + formatDateTime, + formatDate, + formatTime, + getISOWeek, + isValidDate, + isBefore, + isAfter, + isSame, + add, + subtract, + startOf, + endOf, + format, + parseDate, + now, + createDate, + fromNow, + calendar +} from '/imports/lib/dateUtils'; import { ALLOWED_COLORS, TYPE_CARD, @@ -1492,8 +1511,8 @@ Cards.helpers({ expiredVote() { let end = this.getVoteEnd(); if (end) { - end = moment(end); - return end.isBefore(new Date()); + end = new Date(end); + return isBefore(end, new Date()); } return false; }, @@ -1586,8 +1605,8 @@ Cards.helpers({ expiredPoker() { let end = this.getPokerEnd(); if (end) { - end = moment(end); - return end.isBefore(new Date()); + end = new Date(end); + return isBefore(end, new Date()); } return false; }, @@ -3201,9 +3220,7 @@ if (Meteor.isServer) { // change list modifiedAt, when user modified the key values in // timingaction array, if it's endAt, put the modifiedAt of list // back to one year ago for sorting purpose - const modifiedAt = moment() - .subtract(1, 'year') - .toISOString(); + const modifiedAt = add(now(), -1, 'year').toISOString(); const boardId = list.boardId; Lists.direct.update( { diff --git a/models/exporter.js b/models/exporter.js index b8fe68ba4..a42dc0f7a 100644 --- a/models/exporter.js +++ b/models/exporter.js @@ -1,7 +1,26 @@ import { ReactiveCache } from '/imports/reactiveCache'; -import moment from 'moment/min/moment-with-locales'; const Papa = require('papaparse'); import { TAPi18n } from '/imports/i18n'; +import { + formatDateTime, + formatDate, + formatTime, + getISOWeek, + isValidDate, + isBefore, + isAfter, + isSame, + add, + subtract, + startOf, + endOf, + format, + parseDate, + now, + createDate, + fromNow, + calendar +} from '/imports/lib/dateUtils'; //const stringify = require('csv-stringify'); @@ -302,15 +321,15 @@ export class Exporter { labels = `${labels + label.name}-${label.color} `; }); currentRow.push(labels.trim()); - currentRow.push(card.startAt ? moment(card.startAt).format() : ' '); - currentRow.push(card.dueAt ? moment(card.dueAt).format() : ' '); - currentRow.push(card.endAt ? moment(card.endAt).format() : ' '); + currentRow.push(card.startAt ? new Date(card.startAt).toISOString() : ' '); + currentRow.push(card.dueAt ? new Date(card.dueAt).toISOString() : ' '); + currentRow.push(card.endAt ? new Date(card.endAt).toISOString() : ' '); currentRow.push(card.isOvertime ? 'true' : 'false'); currentRow.push(card.spentTime); - currentRow.push(card.createdAt ? moment(card.createdAt).format() : ' '); - currentRow.push(card.modifiedAt ? moment(card.modifiedAt).format() : ' '); + currentRow.push(card.createdAt ? new Date(card.createdAt).toISOString() : ' '); + currentRow.push(card.modifiedAt ? new Date(card.modifiedAt).toISOString() : ' '); currentRow.push( - card.dateLastActivity ? moment(card.dateLastActivity).format() : ' ', + card.dateLastActivity ? new Date(card.dateLastActivity).toISOString() : ' ', ); if (card.vote && card.vote.question !== '') { let positiveVoters = ''; @@ -343,7 +362,7 @@ export class Exporter { if (field.value !== null) { if (customFieldMap[field._id].type === 'date') { customFieldValuesToPush[customFieldMap[field._id].position] = - moment(field.value).format(); + new Date(field.value).toISOString(); } else if (customFieldMap[field._id].type === 'dropdown') { const dropdownOptions = result.customFields.find( ({ _id }) => _id === field._id, diff --git a/models/server/ExporterCardPDF.js b/models/server/ExporterCardPDF.js index f3fde0f85..37551e6cb 100644 --- a/models/server/ExporterCardPDF.js +++ b/models/server/ExporterCardPDF.js @@ -1,6 +1,26 @@ import { ReactiveCache } from '/imports/reactiveCache'; // exporter maybe is broken since Gridfs introduced, add fs and path import { createWorkbook } from './createWorkbook'; +import { + formatDateTime, + formatDate, + formatTime, + getISOWeek, + isValidDate, + isBefore, + isAfter, + isSame, + add, + subtract, + startOf, + endOf, + format, + parseDate, + now, + createDate, + fromNow, + calendar +} from '/imports/lib/dateUtils'; class ExporterCardPDF { constructor(boardId) { @@ -353,8 +373,8 @@ class ExporterCardPDF { //add data +8 hours function addTZhours(jdate) { const curdate = new Date(jdate); - const checkCorrectDate = moment(curdate); - if (checkCorrectDate.isValid()) { + const checkCorrectDate = new Date(curdate); + if (isValidDate(checkCorrectDate)) { return curdate; } else { return ' '; diff --git a/models/server/ExporterExcel.js b/models/server/ExporterExcel.js index 72120dddb..e9775baff 100644 --- a/models/server/ExporterExcel.js +++ b/models/server/ExporterExcel.js @@ -1,7 +1,26 @@ import { ReactiveCache } from '/imports/reactiveCache'; -import moment from 'moment/min/moment-with-locales'; import { TAPi18n } from '/imports/i18n'; import { createWorkbook } from './createWorkbook'; +import { + formatDateTime, + formatDate, + formatTime, + getISOWeek, + isValidDate, + isBefore, + isAfter, + isSame, + add, + subtract, + startOf, + endOf, + format, + parseDate, + now, + createDate, + fromNow, + calendar +} from '/imports/lib/dateUtils'; // exporter maybe is broken since Gridfs introduced, add fs and path @@ -379,8 +398,8 @@ class ExporterExcel { function addTZhours(jdate) { if (!jdate) { return ' '; } const curdate = new Date(jdate); - const checkCorrectDate = moment(curdate); - if (checkCorrectDate.isValid()) { + const checkCorrectDate = new Date(curdate); + if (isValidDate(checkCorrectDate)) { return curdate; } else { return ' '; diff --git a/models/trelloCreator.js b/models/trelloCreator.js index 2d445bc46..5dde821c0 100644 --- a/models/trelloCreator.js +++ b/models/trelloCreator.js @@ -1,10 +1,29 @@ import { ReactiveCache } from '/imports/reactiveCache'; -import moment from 'moment/min/moment-with-locales'; import { TAPi18n } from '/imports/i18n'; +import { + formatDateTime, + formatDate, + formatTime, + getISOWeek, + isValidDate, + isBefore, + isAfter, + isSame, + add, + subtract, + startOf, + endOf, + format, + parseDate, + now, + createDate, + fromNow, + calendar +} from '/imports/lib/dateUtils'; const DateString = Match.Where(function(dateAsString) { check(dateAsString, String); - return moment(dateAsString, moment.ISO_8601).isValid(); + return isValidDate(new Date(dateAsString)); }); export class TrelloCreator { diff --git a/models/wekanCreator.js b/models/wekanCreator.js index b429a9427..4d7f4425d 100644 --- a/models/wekanCreator.js +++ b/models/wekanCreator.js @@ -1,9 +1,28 @@ import { ReactiveCache } from '/imports/reactiveCache'; -import moment from 'moment/min/moment-with-locales'; +import { + formatDateTime, + formatDate, + formatTime, + getISOWeek, + isValidDate, + isBefore, + isAfter, + isSame, + add, + subtract, + startOf, + endOf, + format, + parseDate, + now, + createDate, + fromNow, + calendar +} from '/imports/lib/dateUtils'; const DateString = Match.Where(function(dateAsString) { check(dateAsString, String); - return moment(dateAsString, moment.ISO_8601).isValid(); + return isValidDate(new Date(dateAsString)); }); export class WekanCreator { diff --git a/package.json b/package.json index b5a3bb113..7d8019699 100644 --- a/package.json +++ b/package.json @@ -47,7 +47,6 @@ "markdown-it-mathjax3": "^4.3.2", "meteor-accounts-t9n": "^2.6.0", "meteor-node-stubs": "^1.2.24", - "moment": "^2.30.1", "os": "^0.1.2", "papaparse": "^5.5.3", "pretty-ms": "^7.0.1", diff --git a/server/publications/cards.js b/server/publications/cards.js index 877c77d85..8db1a542b 100644 --- a/server/publications/cards.js +++ b/server/publications/cards.js @@ -1,7 +1,26 @@ import { ReactiveCache } from '/imports/reactiveCache'; -import moment from 'moment/min/moment-with-locales'; import escapeForRegex from 'escape-string-regexp'; import Users from '../../models/users'; +import { + formatDateTime, + formatDate, + formatTime, + getISOWeek, + isValidDate, + isBefore, + isAfter, + isSame, + add, + subtract, + startOf, + endOf, + format, + parseDate, + now, + createDate, + fromNow, + calendar +} from '/imports/lib/dateUtils'; import Boards from '../../models/boards'; import Lists from '../../models/lists'; import Swimlanes from '../../models/swimlanes'; @@ -730,9 +749,7 @@ function findCards(sessionId, query) { userId, modifiedAt: { $lt: new Date( - moment() - .subtract(1, 'day') - .format(), + subtract(now(), 1, 'day').toISOString(), ), }, });