momentjs calculates date difference incorrectly - momentjs

In my angular web application, I want to compare two dates to see if a person is less than 18 years old when she/he entered the company. Here is the code I use to do this:
const dayOfBirth = moment(formControl.value, this.dateFormat, true).startOf('day');
const entranceDateControl = this.wizardFormGroup.get('entranceDate');
const entranceDate = moment(entranceDateControl.value, this.dateFormat, true).startOf('day');
// Check validation rule R3: Age is less than 18 compared to entrance date
const difference = moment.duration(Math.abs(entranceDate.diff(dayOfBirth)));
if (difference.years() < 18) {
const validationMessage = this.getValidationMessage('R3', formControlName);
return validationMessage ? validationMessage.message : null;
}
As you can see, I am using startOf('day') to get rid of any time component so that I only handle dates. I use diff() to get the difference between two dates and then duration() to convert the difference to years, months, days, etc. Using this code, the validation message should NOT show when the person is turning 18 years old on the day when she/he entered the company.
Upon testing this, I came across what is, in my opinion, strange behavior. Depending on months and years used, it gave different results. For instance, for these dates it was Ok:
dayOfBirth = 1998-03-01, 1998-04-01, ..., 2000-02-01
entranceDate = 2016-03-01, 2016-04-01, ..., 2018-02-01
But the following dates returned the validation message:
dayOfBirth = 2000-03-01, 2000-04-01, ..., 2002-02-01
entranceDate = 2018-03-01, 2000-04-01, ..., 2020-02-01
After these dates, i.e. using 2002-03-01 and onward, it works again. I also got wrong result for the dates preceding 1998-03-01.
Now, I had a closer look at the Duration object and I noticed that for the times where it was less than 18 years, it had calculated 864 milliseconds less then when it came to the right conclusion that it was 18 years between the dates.
Correct duration
----------------
dayOfBirth = 1998-03-01, 1998-04-01, ..., 2000-02-01
entranceDate = 2016-03-01, 2016-04-01, ..., 2018-02-01
Duration = 568080000000 ms
Wrong duration
--------------
dayOfBirth = 2000-03-01, 2000-04-01, ..., 2002-02-01
entranceDate = 2018-03-01, 2000-04-01, ..., 2020-02-01
Duration = 567993600000 ms
Duration difference
-------------------
568080000000 - 567993600000 = 86400000 ms = 24 hours = 1 day
Has anyone an explanation for this? Can it be considered a bug in momentjs? Any viable workaround for this?

I didn't go into details in moment source code but it seems duration() is playing tricks with you. Simplify the code and rely only on diffas follow and you should be good (at least it seems to work for the samples you provided). And it's easier on the eyes :)
const moment = require('moment')
const dayOfBirth = moment('2000-03-01').startOf('day');
const entranceDate = moment('2018-03-01').startOf('day');
const difference = entranceDate.diff(dayOfBirth, 'years')
if (difference < 18) {
console.log( '<18')
} else {
console.log( '>=18')
}
will output >=18

Related

What does NNN mean in date format <YYMMDDhhmmssNNN><C|D|G|H>?

hi I has date format and I want converted to correct GMT date :
<YYMMDDhhmmssNNN><C|D|G|H>
Sample value on that date:
210204215026000C
I get this explanation for part NNN :
NNN If flag is C or D then NNN is the number of hours relativeto GMT,
if flag is G or H, NNN is the number of quarter hours relative to GMT
C|D|G|H C and G = Ahead of GMT, D and H = Behind GMT
but I did not get how number of hours relative to GMT can present on 3 digits ? it should be in 2 digit as i knew the offset for hours related to GMT is from 0 to 23 , and also what quarter hours relative to GMT mean ?
I want to use Scala or Java.
I don’t know why they set 3 digits aside for the offset. I agree with you that 2 digits suffice for all cases. Maybe they just wanted to be very sure they would never run of out space, and maybe they even overdid this a bit. 3 digits is not a problem as long as the actual values are within the range that java.time.ZoneOffset can handle, +/-18 hours. In your example NNN is 000, so 0 hours from GMT, which certainly is OK and trivial to handle.
A quarter hour is a quarter of an hour. As Salman A mentioned in a comment, 22 quarter hours ahead of Greenwich means an offset of +05:30, currently used in Sri Lanka and India. If the producer of the string wants to use this option, they can give numbers up to 72 (still comfortably within 2 digits). 18 * 4 = 72, so 18 hours equals 72 quarter hours. To imagine a situation where 2 digits would be too little, think an offset of 25 hours. I wouldn’t think it realistic, on the other hand no one can guarantee that it will never happen.
Java solution: how to parse and convert to GMT time
I am using these constants:
private static final Pattern DATE_PATTERN
= Pattern.compile("(\\d{12})(\\d{3})(\\w)");
private static final DateTimeFormatter FORMATTER
= DateTimeFormatter.ofPattern("uuMMddHHmmss");
private static final int SECONDS_IN_A_QUARTER_HOUR
= Math.toIntExact(Duration.ofHours(1).dividedBy(4).getSeconds());
Parse and convert like this:
String sampleValue = "210204215026000C";
Matcher matcher = DATE_PATTERN.matcher(sampleValue);
if (matcher.matches()) {
LocalDateTime ldt = LocalDateTime.parse(matcher.group(1), FORMATTER);
int offsetAmount = Integer.parseInt(matcher.group(2));
char flag = matcher.group(3).charAt(0);
// offset amount denotes either hours or quarter hours
boolean quarterHours = flag == 'G' || flag == 'H';
boolean negative = flag == 'D' || flag == 'H';
if (negative) {
offsetAmount = -offsetAmount;
}
ZoneOffset offset = quarterHours
? ZoneOffset.ofTotalSeconds(offsetAmount * SECONDS_IN_A_QUARTER_HOUR)
: ZoneOffset.ofHours(offsetAmount);
OffsetDateTime dateTime = ldt.atOffset(offset);
OffsetDateTime gmtDateTime = dateTime.withOffsetSameInstant(ZoneOffset.UTC);
System.out.println("GMT time: " + gmtDateTime);
}
else {
System.out.println("Invalid value: " + sampleValue);
}
Output is:
GMT time: 2021-02-04T21:50:26Z
I think my code covers all valid cases. You will probably want to validate that the flag is indeed C, D, G or H, and also handle the potential DateTimeException and NumberFormatException from the parsing and creating the ZoneOffset (NumberFormatException should not happen).

MomentJS comparing Unix time with minutes and get difference

I need to find if currentDate time (unix) and lastFetchedTime(unix) is greater than 30 minutes in moment.js.
How can compare the subtracted value from 30 minutes in moment?
lastFetchedTime(unix) is equivalent to the previous Date.now()..
const now = moment(Date.now());
const lastFetched = 1598578706;
const checkTime = now.diff(lastFetched, 'minutes') > 30 ;
You can use momentJS duration function to get the difference between two times which are in unix format.
Firstly, you need to convert the unix format to human readable time and then get the difference of current time and lastFetched time using asMinutes function of duration
If the difference is greater then 30 then do something else or do something else.
Live Demo:
const now = moment().unix()
const lastFetched = 1598597404;
const duration = moment.duration(moment.unix(now).diff(moment.unix(lastFetched)));
const getMinutes = duration.asMinutes();
if (getMinutes > 30) {
console.log('Minutes are GREATER then 30 minutes - from now')
} else {
console.log('Minutes are LESS then 30 minutes - from now')
}
<script src="//cdnjs.cloudflare.com/ajax/libs/moment.js/2.8.4/moment.js"></script>

Delphi displays strange results for operations with negative values to TDateTime

We have a solution in Delphi that calculates a travel's duration of a given vehicle, for example, 20 minutes, 25 minutes and so on. However, sometimes we have to antecipate the travel's start time, from a specific datetime, for example 09:00 to 08:40. Then, we need to substract a negative value from a TDateTime variable (travel's start), in this case, something like "-00:20". To do this, we multiply the datetime value by -1 (for example MyDiffDateTimeVariable * -1). The output we got is very strange, sometimes we obtain the exactly opposite behavior. In other case, an operation to extract 20 minutes results in a difference of two days from the original datetime.
Here is a sample console application that simulate our situation, with the current outputs, and what we will expected:
program DateTimeSample;
uses
System.SysUtils, System.DateUtils;
var
LDate1: TDateTime;
LDate2: TDateTime;
begin
LDate1 := IncMinute(0, 20);
LDate2 := IncMinute(0, -20);
WriteLn('Date1: ' + DateTimeToStr(LDate1));
// Output = Date1: 30/12/1899 00:20:00 [OK]
WriteLn('Date2: ' + DateTimeToStr(LDate2));
// Output = Date2: 29/12/1899 23:40:00 [OK]
WriteLn('-----');
WriteLn('Date1: ' + DateTimeToStr(LDate1 * -1));
// Output = Date1: 30/12/1899 00:20:00 [Expected 29/12/1899 23:40:00]
WriteLn('Date2: ' + DateTimeToStr(LDate2 * -1));
// Output = Date2: 31/12/1899 23:40:00 [Expected 30/12/1899 00:20:00]
ReadLn;
end.
When you inspect the value casted to double, you can see:
double(LDate1) = 0.0138888888888889
double(LDate2) = -1.98611111111111
Seems like a bug to me, because with today it returns:
double(LDate1) = 43168,0138888889
double(LDate2) = 43167,9861111111
Edit: Hmm, according the documentation, it is not a bug, it is a feature :-)
When working with negative TDateTime values, computations must handle time portion separately. The fractional part reflects the fraction of a 24-hour day without regard to the sign of the TDateTime value. For example, 6:00 A.M. on December 29, 1899 is –1.25, not –1 + 0.25, which would equal –0.75. There are no TDateTime values from –1 through 0.
Karel's answer explains what's happening. Basically, TDateTime is represented as a Double, but that doesn't mean you can work with it in the same way as you normally would a Double value. It's internal structure carries particular semantics that if you don't handle them correctly, you're bound to get some peculiar behaviour.
The key mistake you're making is in taking the negative of a date-time value. This concept doesn't really make sense. Not even if you look at dates in BC, because the calendar system has changed a number of times over the years.
This is the main reason you should favour library routines that deal with the nuances of the internal structure (whatever your platform). In Delphi that means you should use the SysUtils and DateUtils routines for working with dates and times.
You seem to be trying to hold duration as a TDateTime value. You'd be much better off determining your preferred unit of measure and using Integer (perhaps Int64) or Double (if you need support for fractions of a unit). Then you can add or subtract, preferably using library routines, the duration from your start or end times.
The following code demonstrates some examples.
var
LStartTime, LEndTime: TDateTime;
LDuration_Mins: Integer;
begin
{ Init sample values for each calculation }
LStartTime := EncodeDateTime(2018, 3, 9, 8, 40, 0, 0);
LEndTime := EncodeDateTime(2018, 3, 9, 9, 0, 0, 0);
LDuration_Mins := 20;
{ Output result of each calculation }
Writeln(Format('Whole Duration: %d', [MinutesBetween(LStartTime, LEndTime)]));
Writeln(Format('Frac Duration: %.6f', [MinuteSpan(LStartTime, LEndTime)]));
Writeln(Format('Start Time: %s', [FormatDateTime('yyyy-mm-dd hh:nn:ss', IncMinute(LEndTime, -LDuration_Mins))]));
Writeln(Format('End Time: %s', [FormatDateTime('yyyy-mm-dd hh:nn:ss', IncMinute(LStartTime, LDuration_Mins))]));
end;
Additional Considerations
You said you're dealing with vehicle travel times. If you're dealing with long-haul travel you might have some other things to think about.
Daylight saving: If a vehicle starts its journey shortly before DST changes and ends after, you need to take this into account when calculating a missing value. Perhaps easiest would be to convert date-time values to UTC for the calculation. Which leads to...
Time zone changes: Again, unless your code is time-zone aware you're bound to make mistakes.
Compiler always appears to treat TDateTime as positive when doing numerical operations on it. Try this:
uses
System.SysUtils, System.DateUtils;
function InvertDate(ADateTime: TDateTime): TDateTime;
var
LMsec: Int64;
begin
LMsec := MillisecondsBetween(ADateTime, 0); //Always Positive
if ADateTime > 0 then
LMsec := 0 - LMsec;
Result := IncMillisecond(0, LMsec);
end;
var
LDate1: TDateTime;
LDate1Negative: TDateTime;
LDate2: TDateTime;
begin
try
LDate1 := IncMinute(0, 20);
LDate2 := IncMinute(0, -20);
WriteLn('Date1: ' + DateTimeToStr(LDate1));
// Output = Date1: 30/12/1899 00:20:00 [OK]
WriteLn('Date2: ' + DateTimeToStr(LDate2));
// Output = Date2: 29/12/1899 23:40:00 [OK]
WriteLn('-----');
WriteLn('Date1: ' + DateTimeToStr( InvertDate(LDate1) ));
// Output = Date1: Expected 29/12/1899 23:40:00
WriteLn('Date2: ' + DateTimeToStr( InvertDate(LDate2) ));
// Output = Date2: 30/12/1899 00:20:00
ReadLn;
except
on E: Exception do
Writeln(E.ClassName, ': ', E.Message);
end;
end.

How do I work out how many days ago date1 was compared to date2, using datejs?

var d1 = Date.parse(date); // eg, 7th jan
var d2 = Date.today(); // eg, 2nd jan
I want to do:
(d2 - d1).days
which with the above data, should return 5.
Is this possible with datejs?
The optional TimeSpan module includes functionality to easily get various value differences between two date objects.
http://code.google.com/p/datejs/source/browse/trunk/src/time.js
Just include after your main date.js include, or combine.
In the "time.js" file, there's also a TimePeriod class. You might not require, so just crop what you need if weight is an issue.
The following sample demonstrates the full scenario.
Example
var d1 = Date.parse("jan 7"); // eg, 7th jan
var d2 = Date.parse("jan 2"); // eg, 2nd jan
console.log(new TimeSpan(d1 - d2).days); // 5
Hope this helps.

Weird flex date issue

Flex is driving me CRAZY and I think it's some weird gotcha with how it handles leap years and none leap years. So here's my example. I have the below dateDiff method that finds the number of days or milliseconds between two dates. If I run the following three statements I get some weird issues.
dateDiff("date", new Date(2010, 0,1), new Date(2010, 0, 31));
dateDiff("date", new Date(2010, 1,1), new Date(2010, 1, 28));
dateDiff("date", new Date(2010, 2,1), new Date(2010, 2, 31));
dateDiff("date", new Date(2010, 3,1), new Date(2010, 3, 30));
If you were to look at the date comparisons above you would expect to get 30, 27, 30, 29 as the number of days between the dates. There weird part is that I get 29 when comparing March 1 to March 31. Why is that? Is it something to do with February only having 28 days? If anyone has ANY input on this that would be greatly appreciated.
public static function dateDiff( datePart:String, startDate:Date, endDate:Date ):Number
{
var _returnValue:Number = 0;
switch (datePart) {
case "milliseconds":
_returnValue = endDate.time - startDate.time;
break;
case "date":
// TODO: Need to figure out DST problem i.e. 23 hours at DST start, 25 at end.
// Math.floor causes rounding down error with DST start at dayOfYear
_returnValue = Math.floor(dateDiff("milliseconds", startDate, endDate)/(1000 * 60 * 60 * 24));
break;
}
return _returnValue;
}
This is not a leap year problem, but rather a daylight savings time problem.
To correct the code to account for DST, you need to look at the timezoneOffset of both dates to determine if the date range is spanning a DST boundary.
var adjustment:Number = ( startDate.timezoneOffset - endDate.timezoneOffset ) * 60 * 1000;
_returnValue = endDate.time - startDate.time + adjustment;
This will get the difference between the two time zones (in minutes), convert that value to milliseconds, and then apply the timezone difference to the millisecond difference to "cancel out" the DST boundary.
Naturally, when both numbers are in the same time zone, the adjustment value becomes 0 and the time values are not adjusted.
You have part of the answer in your comment: 2010-Mar-01 0:00 until 2010-Mar-31 0:00 is thirty (!) days minus one hour (because Mar 14 is DST start in 2010). Since you floor the result of your division, you get 29.
Edit: This answer is of course based on the assumption that the time property of Date takes DST into account. This would explain your problem; I didn't check it, however.

Resources