Subtract extract months between two javascript dates

Asked

Viewed 514 times

1

I’m struggling to extract the total of months between two dates ex: end date (29/03/2018) - start date (29/01/2018) = 2 months, however the dates are provided by the jQuery datepiker and if it could inform the command to change the result value when changed one or both dates in javascript.

Follow the code below:

<div class="control-form">
   <label for="">Data Inicial:</label>
   <input name="datainicial" type="text" id="calendario" value="29/01/2018"  class="form-control" >
</div>
<div class="control-form">
   <label for="">Data Final:</label>
   <input name="datafinal" type="text" id="calendario2" class="form-control" >
</div>
<div class="control-form">
   <label for="">Numero de Parcelas:</label>
   <input name="numeroparcelas" type="text" class="form-control" id="numeroparcelas">
</div>
  • Your question is very wide, include your code with a verifiable example. Ps.: Avoid greetings, such as summoning the script gods ;)

  • OK, I’ll do it, I appreciate it.

  • has a lib called Moment.js a quick search and will never have problems with dates and javascript again

2 answers

2

To another answer already explains how to get the dates of the date Picker whenever one is selected.

But I would like to mention one detail in the calculation. Getting the difference in months is not as simple as it seems, as there are many factors to consider. As explained here:

Months have varied sizes, can be 28, 29, 30 or 31 days, and the bill is not always that simple.

For example, between 01/01/2019 and 01/02/2019 there are 31 days. Dividing by 30 and rounding, gives 1 month.

But between 01/02/2019 and 01/03/2019 there are 28 days. Dividing by 30, gives 0,93: if round down, gives zero months. But between February 1 and March 1 the difference is not one month? Then we should round up in this case?

But if it was between 01/01/2019 and 29/01/2019, the difference is also 28 days. Only between January 1st and January 29th it hasn’t been 1 month, so I can’t round up in this case. " Ah, so I just jump up if I’m not in the same month".

Then you see that between 01/01/2019 and 27/02/2019 the difference is 57 days, divided by 30 is 1,9. If you round it up, it’s 2, but between January 1st and February 27th it hasn’t been two months. And now?

That is, the way of calculating varies according to what you want to consider. Will the day be taken into account or not? Between January 15 and March 10 it has 2 months (because March is "two months after January"), or only 1 month (because only after March 15 it is 2 months)?

That is the first problem. Another is the very Date Javascript, which hides some traps. In the other response string "01/29/2018" is transformed into "2018-01-29" (which is the ISO 8601 format). But when you only have the date (only day, month and year), and without the time, it creates the date with the time equal to midnight at UTC. This is the first problem, because when using the getters, use the environment Timezone (which may not be the same as UTC). See this example:

const data = new Date("2021-01-01");
// mostrar data no timezone do browser (a saída abaixo ocorre se estiver no Horário de Brasília)
console.log(data.toString()); // Thu Dec 31 2020 21:00:00 GMT-0300 (Horário Padrão de Brasília)
// mostrar data em UTC
console.log(data.toISOString()); // 2021-01-01T00:00:00.000Z

// getters usam o timezone do browser
console.log(data.getDate(), data.getMonth(), data.getFullYear()); // 31 11 2020

In my case, my browser is using Brasilia Time (Chrome picks up the Operating System configuration) - if yours is using a different zone, the output will not be the same. So in my case, I created the date referring to "2021-01-01" (January 1, 2021), but as I did not inform the time, he arrow "midnight in UTC". Only that midnight in UTC corresponds to 21h the day before in Brazilian Time (since this zone is 3 hours behind UTC). So when using getMonth() and getDate(), it returns the values according to the Time of Brasilia (remembering that 11 is equivalent to December, as in Date javascript, january is zero).

That is, this can cause some problems in the calculation made in the other answer. Test, for example, from 01/01/2021 to 10/02/2021, and the result will be 2 (but I think it should be 1, because it has not been 2 months). This occurs because of the problem reported above, getMonth() returns December and getFullYear() returns 2020.

This all happens because the Date Javascript actually represents a timestamp (a point in the timeline), not a specific date (a single value of day, month, year, hour, minute and second).
For more details, read here, here, here, here and here.


One way to circumvent is to use the constructor that receives the numeric values, because then it considers midnight in the browser spindle (which is the same spindle used in getters) and the above problem no longer occurs.

But still you have to decide whether you will consider the day or not: for example, between January 15 and March 10, it is 2 months (because I consider only "January" and "March") or 1 month (because only after March 15 is it 2 months). Something like that:

function ajustaMesAno(data) {
    return data.getFullYear() * 12 + data.getMonth() + 1;
}

function diffMeses(inicio, fim, consideraDia = true) {
    let meses = ajustaMesAno(fim) - ajustaMesAno(inicio);
    if (consideraDia && inicio.getDate() > fim.getDate()) {
        meses--;
    }
    return meses;
}

// lembrando que janeiro é zero, fevereiro é 1, etc
const inicio = new Date(2021, 0, 15); // 15 de janeiro
const fim = new Date(2021, 2, 10); // 10 de março

// considera o dia
console.log(diffMeses(inicio, fim)); // 1

// não considera o dia
console.log(diffMeses(inicio, fim, false)); // 2

That is, if I consider the day, the difference between January 15 and March 10 is 1 month (only from March 15 will it be 2 months).


The whole example looks like this:

function construirData(str) {
    let [dia, mes, ano] = str.split("/").map(v => parseInt(v));
    return new Date(ano, mes - 1, dia);
}

function ajustaMesAno(data) {
    return data.getFullYear() * 12 + data.getMonth() + 1;
}

function diffMeses(inicio, fim, consideraDia = true) {
    let meses = ajustaMesAno(fim) - ajustaMesAno(inicio);
    if (consideraDia && inicio.getDate() > fim.getDate()) {
        meses--;
    }
    return meses;
}

function isValidDate(d) {
  return d instanceof Date && !isNaN(d);
}

function calcularDiferenca() {
    var inicio = construirData($("#calendario").val());
    if (! isValidDate(inicio)) {
        alert('Data inicial inválida');
        return;
    }
    var fim = construirData($("#calendario2").val());
    if (! isValidDate(fim)) {
        alert('Data final inválida');
        return;
    }
    if (inicio >= fim) {
        alert('Data inicial deve ser anterior à data final');
        return;
    }
    // aqui você decide se vai considerar os dias ou não
    $("#numeroparcelas").val(diffMeses(inicio, fim));
}

$('input[id*="calendario"]').datepicker({
   dateFormat: 'dd/mm/yy',
   onSelect: calcularDiferenca
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<link rel="stylesheet" href="https://ajax.googleapis.com/ajax/libs/jqueryui/1.12.1/themes/smoothness/jquery-ui.css">
<script src="https://ajax.googleapis.com/ajax/libs/jqueryui/1.12.1/jquery-ui.min.js"></script>
<div class="control-form">
   <label for="">Data Inicial:</label>
   <input name="datainicial" type="text" id="calendario" value="01/01/2021"  class="form-control" >
</div>
<div class="control-form">
   <label for="">Data Final:</label>
   <input name="datafinal" type="text" id="calendario2" class="form-control" >
</div>
<div class="control-form">
   <label for="">Numero de Parcelas:</label>
   <input name="numeroparcelas" type="text" class="form-control" id="numeroparcelas">
</div>

I included some validations on the date (if indeed it is a date, and if the initial date is less than the final one), because if you have something invalid it is not worth calculating (the other answer prefers to calculate always and only at the end check if the result makes sense, I prefer to only do the calculation if I am sure that all the data are valid).

In calculating the difference you can choose whether or not to consider the day, as in the previous example. There is no right answer, and it all depends on the requirements (it is not clear whether to consider or not, or whether the dates will always be on the same day, etc).


And of course there’s still corner cases that have no easy answer. For example, between 02/29/2020 and 02/28/2021, is it 11 or 12 months? If you consider the day, it will be 11 (because it is not yet 29, and the problem is that 2021 February does not have 29 days), and if you do not consider the day, it will be 12. But since it’s the last day of the month, shouldn’t it be 12 months apart? Think of someone who was born on 02/29/2020: on 02/28/2021 this person has already completed 1 year? Some languages/API’s say yes, others say no. It is up to you to decide what makes sense to your case and to make the specific treatment if necessary.

Arithmetic of dates that’s how it is, bizarre and counter-intuitive.

-1

The Datepicker jQuery has an event onSelect: when a date is selected. Then you can call a function within that event and calculate:

$('input[id*="calendario"]').datepicker({
   dateFormat: 'dd/mm/yy',
   onSelect: function(){
      var mesIni = $("#calendario").val().split("/");
      var mesFim = $("#calendario2").val().split("/");

      mesIni = new Date(mesIni.pop()+"-"+mesIni[1]+"-"+mesIni.shift());
      mesFim = new Date(mesFim.pop()+"-"+mesFim[1]+"-"+mesFim.shift());

      var meses = mesFim.getMonth() - mesIni.getMonth() 
      + (12 * (mesFim.getFullYear() - mesIni.getFullYear()));

      if(!isNaN(meses)) $("#numeroparcelas").val(meses);
   }
});

See example:

$('input[id*="calendario"]').datepicker({
   dateFormat: 'dd/mm/yy',
   onSelect: function(){
      var mesIni = $("#calendario").val().split("/");
      var mesFim = $("#calendario2").val().split("/");
      
      mesIni = new Date(mesIni.pop()+"-"+mesIni[1]+"-"+mesIni.shift());
      mesFim = new Date(mesFim.pop()+"-"+mesFim[1]+"-"+mesFim.shift());
      
      var meses = mesFim.getMonth() - mesIni.getMonth() 
      + (12 * (mesFim.getFullYear() - mesIni.getFullYear()));
   
      if(!isNaN(meses)) $("#numeroparcelas").val(meses);
   }
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<link rel="stylesheet" href="https://ajax.googleapis.com/ajax/libs/jqueryui/1.12.1/themes/smoothness/jquery-ui.css">
<script src="https://ajax.googleapis.com/ajax/libs/jqueryui/1.12.1/jquery-ui.min.js"></script>
<div class="control-form">
   <label for="">Data Inicial:</label>
   <input name="datainicial" type="text" id="calendario" value="29/01/2018"  class="form-control" >
</div>
<div class="control-form">
   <label for="">Data Final:</label>
   <input name="datafinal" type="text" id="calendario2" class="form-control" >
</div>
<div class="control-form">
   <label for="">Numero de Parcelas:</label>
   <input name="numeroparcelas" type="text" class="form-control" id="numeroparcelas">
</div>

  • It worked Aki the way I expected, I’m new to programming and I thank you for your contribution and humility for your help. Vlw

Browser other questions tagged

You are not signed in. Login or sign up in order to post.