rocklobster
rocklobster

Reputation: 465

moment().add() only works with literal values

I'm using Moment.js in TypeScript (under Angular 2 if that matters). When I use the add() method with literal values as arguments, it works fine:

moment().add(1, 'month');

However, if I try to replace the units with a string, it fails:

let units:string = 'month';
moment().add(1, units);

with this error:

Argument of type '1' is not assignable to parameter of type 'DurationConstructor'.

What am I doing wrong here?

Upvotes: 32

Views: 15595

Answers (8)

Ammar Ismaeel
Ammar Ismaeel

Reputation: 803

Unfortunately, none of the above answers worked with me! But this only made the charm! :D

const startTime = moment().subtract(this.time.amount as moment.DurationInputArg1, this.time.unit as moment.DurationInputArg2);

Upvotes: 7

HankCa
HankCa

Reputation: 9649

This answer is building on all the previous answers that specify deprecated reverse syntax of add, subtract, etc functions in moment as the cause of the problem.

I cast the DurationInputArg2 field to unitOfTime.Base since all these methods have this as the ultimate superclass and so it means for add, subtract, startOf, endOf I just need to remember as Base:

 import Base = moment.unitOfTime.Base

 theMoment.subtract(amount, unitString as Base)
 theMoment.startOf(unitString as Base)

The confusing part for most people is that the amount is marked as the error. Fixed when one qualifies the units.

Upvotes: 3

Mohideen bin Mohammed
Mohideen bin Mohammed

Reputation: 20186

Change type definition from let into const

const units = 'month';
moment().add(1, units);

this works for me..

Upvotes: 0

Aleksey L.
Aleksey L.

Reputation: 37986

Deprecated reverse overload add(unit: unitOfTime.DurationConstructor, amount: number|string) creates ambiguity.

You can fix this by defining type of units to be DurationConstructor instead of string:

let units: moment.unitOfTime.DurationConstructor = 'month';
moment().add(1, units);

Another option is just to use const instead of let, so literal type will be inferred:

const units = 'month';
moment().add(1, units);

Upvotes: 46

Nikita
Nikita

Reputation: 1082

There are two declarations of add

  1. add(amount?: DurationInputArg1, unit?: DurationInputArg2): Moment;
  2. Deprecated add(unit: unitOfTime.DurationConstructor, amount: number|string)

When typescript need to match overloads it tries to take closest type. When you've tried to pass units:string, closest match number|string is second overload, because any string will match to this declaration, f.e. 'qwe', or 'rty' of type string, but not DurationArg2 which is expected as second parameter in first declaration.

When you call moment.add(1,'months) it will use first declaration, because it is possible to cast 1 to type of the first argument in first signature, but not in the second.

So to fix this issue and similar, you should say what you exactly want to use.

Example 1. First signature will be used

import * as moment from 'moment'

calculateRangeFromEnd(end: Date, unitsAmount: moment.DurationInputArg1, unitsMeasureMoment: moment.DurationInputArg2): IDateRange {
    return {
      endDate: end,
      startDate: moment(end).subtract(unitsAmount, unitsMeasureMoment).toDate()
    }
  }

Example 2. Second signature will be used

import * as moment from 'moment'

calculateRangeFromEnd(end: Date, unitsAmount: number | string, unitsMeasureMoment: moment.unitOfTimes.DurationConstructor): IDateRange {
    return {
      endDate: end,
      startDate: moment(end).subtract(unitsMeasureMoment, unitsAmount).toDate()
    }
  }

Upvotes: 3

Alex Spera
Alex Spera

Reputation: 821

Another option to the accepted answer is typecasting in the argument. There's really no difference, just figured I'd include this answer as an option. Also unitOfTime can be imported from moment as a module if you want some more brevity.

import { unitOfTime } from 'moment';
import * as moment from 'moment';

option = {val: 30, unit: 'm'}
moment().add( this.querySince.val, <unitOfTime.DurationConstructor>this.querySince.unit )

Upvotes: 9

rahuljain1311
rahuljain1311

Reputation: 2160

This problem is caused by deprecated reverse syntax of add, subtract, etc functions in moment library. Below is an excerpt:

moment.d.ts

add(amount?: DurationInputArg1, unit?: DurationInputArg2): Moment;
/**
 * @deprecated reverse syntax
 */
add(unit: unitOfTime.DurationConstructor, amount: number|string): Moment;

In my case I wanted units and value both to be variables so I had to write a function to convert the general string to the acceptable ones ("hours", "minutes", etc.).

function convertToDuration (unit: string): Moment.unitOfTime.DurationConstructor {

if(unit ==  'seconds' || unit ==  'minutes' || unit ==  'hours' || unit ==  'days' || unit ==  'weeks' || unit ==  'months'){
    return unit;
}
else // Default unit is hours
    return 'hours';
}

Now to call this function:

const time = moment().add(expiryTime.value, convertToDuration(expiryTime.unit));

Definition of expiryTime in TypeScript:

interface ExpiryTimeDef {
value: number,
unit: string
}

Upvotes: 0

Arif Amirani
Arif Amirani

Reputation: 26695

The problem in my case was that I was using mins. The type def for DurationConstructor is

namespace unitOfTime {
type Base = (
  "year" | "years" | "y" |
  "month" | "months" | "M" |
  "week" | "weeks" | "w" |
  "day" | "days" | "d" |
  "hour" | "hours" | "h" |
  "minute" | "minutes" | "m" |
  "second" | "seconds" | "s" |
  "millisecond" | "milliseconds" | "ms"
);

So this works out of the box:

refTime.clone().subtract(15, 'minute')

-k

Upvotes: 2

Related Questions