Numeric Date Validation

R

Richard Cornford

Lasse Reichstein Nielsen wrote:
function DateOK2(Y, M, D) {
var ML = __months[M];
return D>0 && ML && (D<=ML ||
D==29 && Y%4==0 && (Y%100!=0 || Y%400==0) ) }
<snip>

The comparison operation on - ML - reminded me that a - <= - comparison
of an object with a number will call the object's - valueOf - method,
and so the possibility of putting an object with an overridden -
valueOf - method into index 2 of the months length array and have that
method return 28 or 29 based on the year:-

var DateOK3 = (function(){
var _y;
var __months = [
NaN,31,
{valueOf:function(){
return (((_y&3)||((_y&15)&&(!(_y%25))))?28:29);
}},
31,30,31,30,31,31,30,31,30,31
];
return (function(Y, M, D){
_y = Y;
return (D > 0) && (D <= __months[M]);
});
})();

Which takes advantage of undefined values from - __months[M] - type
converting to NaN for the comparison and so returns false from - (D <=
__months[M]) - when - M - is out of the appropriate range and has the
merit of always returning a boolean value.

Richard.
 
C

Csaba Gabor

isLeapYear = !( ayear & 3 ) && (( ayear % 25 != 0 ) || !( ayear & 15 ))
!( ayear & 3 || !( ayear & 15 ) && ayear % 25 )

Hmmm, that's very interesting. My first inclination was to say that
swapping those last two arguments was going to lead the last one being
evaluated (almost) all the time (since it's checking for divisibility by
400,
only true 1/400 of the time) but that's not correct since the 25 is doing
much of the work. That being the case, the situation becomes murkier
and depends much more on the efficiency of the various operations.
In particular, the last operation (the presumed expensive Mod 25)
is only going to be done 1/16th of the time as opposed to the 1/4 on
the top line. Now I'm real curious as to which wins out on time, if either.

Thanks for that post,

Csaba,
a bit toddler
 
D

Dr John Stockton

JRS: In article <[email protected]>, seen in
Hmmm, that's very interesting. My first inclination was to say that
swapping those last two arguments was going to lead the last one being
evaluated (almost) all the time (since it's checking for divisibility by
400,
only true 1/400 of the time) but that's not correct since the 25 is doing
much of the work. That being the case, the situation becomes murkier
and depends much more on the efficiency of the various operations.
In particular, the last operation (the presumed expensive Mod 25)
is only going to be done 1/16th of the time as opposed to the 1/4 on
the top line. Now I'm real curious as to which wins out on time, if either.


The tests will be performed on order (assumption). The most effective
tests are those where the two possible results are most nearly equally
probable (general rule). 4 < 16 < 25.


PII/300, MSIE4 :-


1000 // ; var D0, D1, D2, J, isLeapYear, ayear

D0 = new Date()
for (J=0 ; J<K ; J++) for (ayear=2000 ; ayear<2400 ; ayear++)
isLeapYear = !( ayear & 3 ) && (( ayear % 25 != 0 ) || !( ayear & 15 ))
D1 = new Date()
for (J=0 ; J<K ; J++) for (ayear=2000 ; ayear<2400 ; ayear++)
isLeapYear = !( ayear & 3 || !( ayear & 15 ) && ayear % 25 )
D2 = new Date()
for (J=0 ; J<K ; J++) for (ayear=2000 ; ayear<2400 ; ayear++)
isLeapYear = !( ayear & 3 )
D3 = new Date()

X = [D1-D0, D2-D1, D3-D2]


Executing in <URL:http://www.merlyn.demon.co.uk/js-quick.htm>, typical
results are 3680,3620,3240 3740,3620,3190. About half of the time is
taken by loop overhead; the difference is a few percent, with mine being
faster.

Unveiling the var makes things faster.

The RmEv button, used above, gives much shorter times that the Eval
button - so thanks to Richard, who can possibly explain why!

....

<URL:http://www.merlyn.demon.co.uk/js-date4.htm#MaYL> now includes speed
testing (LeapSloth). It was designed on a system with time resolution
about 55 ms x 300 MHz CPU cycles; those with relatively faster CPUs may
need, and should be willing, to increase the year range tested by a
multiple of 400. Routines not using Objects are *much* quicker.
 
R

Richard Cornford

Dr John Stockton wrote:
The RmEv button, used above, gives much shorter times that the Eval
button - so thanks to Richard, who can possibly explain why!
<snip>

As I recall the point of the - remoteEval - was to avoid evaling the
expression in a scope derived from the event handling function's
execution context because the event handling function is provided with a
custom scope chain that may modify the interpretation of identifiers
used in the code that is evaled. It is probably this custom scope chain
that is influencing the execution speed of the code in the two contexts.

The execution context of the - remoteEval - function will only include
its Activation/Variable object and the global object, so any identifier
resolution will only involve the examination of these two objects.

IE adds the - document -, the form element and the - this - object to
the scope chain of its event handlers (Mozilla adds more objects), so
the resolution of an identifier that refers to a global variable will
necessitate a search of all of these objects (in addition to the
Activation/Variable object from the event handler's execution context
and the global object). The speed of resolution of local variables will
depend on whether the added objects are above the event handler's
Activation/Variable object in the scope chain (as would be the case if
they had been added using the - with - statement) or whether they are
below it.

On the other hand each test seems to be doing about as much identifier
resolution (except the last) so the relative speed results are still
meaningful in either test.

Richard.
 
D

Dr John Stockton

JRS: In article <[email protected]>, seen in
Richard Cornford
Lasse Reichstein Nielsen wrote:
function DateOK2(Y, M, D) {
var ML = __months[M];
return D>0 && ML && (D<=ML ||
D==29 && Y%4==0 && (Y%100!=0 || Y%400==0) ) }

To avoid getting a result which is neither true nor false (but is
equivalent to false), I put !! before ML.
<snip>

The comparison operation on - ML - reminded me that a - <= - comparison
of an object with a number will call the object's - valueOf - method,
and so the possibility of putting an object with an overridden -
valueOf - method into index 2 of the months length array and have that
method return 28 or 29 based on the year:-

var DateOK3 = (function(){
var _y;
var __months = [
NaN,31,
{valueOf:function(){
return (((_y&3)||((_y&15)&&(!(_y%25))))?28:29);
}},
31,30,31,30,31,31,30,31,30,31
];
return (function(Y, M, D){
_y = Y;
return (D > 0) && (D <= __months[M]);
});
})();

Which takes advantage of undefined values from - __months[M] - type
converting to NaN for the comparison and so returns false from - (D <=
__months[M]) - when - M - is out of the appropriate range and has the
merit of always returning a boolean value.

Remarkable code. But it appears to work (though seems, tested with
2002-02-29, *marginally* slower than the best of the others). It's
testable in <URL:http://www.merlyn.demon.co.uk/js-dates4.htm#DVal>.

Alas, unless I wrap the above in a "normal" function, it's not amenable
to my usual code display technique.


Thanks for your RmEv explanation.
 
R

Richard Cornford

Dr John Stockton said:
Richard Cornford posted:
Lasse Reichstein Nielsen wrote:
function DateOK2(Y, M, D) {
var ML = __months[M];
return D>0 && ML && (D<=ML ||
D==29 && Y%4==0 && (Y%100!=0 || Y%400==0) ) }

To avoid getting a result which is neither true nor false (but is
equivalent to false), I put !! before ML.
The comparison operation on - ML - reminded me that a - <= -
comparison of an object with a number will call the object's
- valueOf - method, and so the possibility of putting an object
with an overridden - valueOf - method into index 2 of the months
length array and have that method return 28 or 29 based on the
year:-
var DateOK3 = (function(){
var _y;
var __months = [
NaN,31,
{valueOf:function(){
return (((_y&3)||((_y&15)&&(!(_y%25))))?28:29);
}},
31,30,31,30,31,31,30,31,30,31
];
return (function(Y, M, D){
_y = Y;
return (D > 0) && (D <= __months[M]);
});
})();

Which takes advantage of undefined values from - __months[M] - type
converting to NaN for the comparison and so returns false from -
(D <= __months[M]) - when - M - is out of the appropriate range
and has the merit of always returning a boolean value.

Remarkable code. But it appears to work (though seems, tested with
2002-02-29, *marginally* slower than the best of the others). It's
testable in <URL:http://www.merlyn.demon.co.uk/js-dates4.htm#DVal>.

Yes, my speed tests give Lasse's the edge (4% or so, with a range of
dates some of which are invalid).

On the other hand, I was looking a Lasse's logic and I think it can be
faster still:-

return (D <= __months[M])? (D > 0) :
(D==29) && (M==2) && !( ((Y&3)||((Y&15)&&(!(Y%25)))) );

As out of range - M - values return undefined from - __months[M] -,
which type-convert to NaN for the comparison, only in range - M - values
can produce true results from - (D <= __months[M]) - so the - (D > 0) -
test can be moved so that it only applies when the comparison returns
true.

The only condition where a false value for - (D <= __months[M]) - will
eventually leave the function returning true is when D == 29, M ==2 and
the year makes February 29 days long. If - D - was less than zero is
would not equal 29. There is also no longer any need to test the value
returned from - __months[M] - separately by type converting it as it
would only type-convert to true if - M == 2 -, removing the need for the
ML local variable and the assignment operation..

Finally, using the expression that I used to positively select day
lengths of 28 for February, and applying the NOT operator allows that
final expression to return a boolean value even though two of its
intermediate values are numeric.

I make it 6-10% faster than Lasse's original (with !!ML). The version I
was using for testing was:-

var DateOK2b = (function(){
var __months = [NaN,31,28,31,30,31,30,31,31,30,31,30,31]
return (function(Y, M, D){
return (D <= __months[M])? (D > 0) :
(D==29) &&
(M==2) &&
!( ((Y&3)||((Y&15)&&(!(Y%25)))) );
});
})();
Alas, unless I wrap the above in a "normal" function, it's not
amenable to my usual code display technique.
<snip>

It isn't strictly necessary that the __months array be held in a
closure. I like the idea of bundling the two together and keeping
__months free from external influences. It might also be slightly faster
to resolve the identifier - __months - in the closure as the global
object has many more properties to be considered. But that isn't going
to help when it comes to displaying the string value of the resulting
(inner) function object.

Richard.
 
D

Dr John Stockton

JRS: In article <[email protected]>, seen in
news:comp.lang.javascript said:
I make it 6-10% faster than Lasse's original (with !!ML). The version I
was using for testing was:-

var DateOK2b = (function(){
var __months = [NaN,31,28,31,30,31,30,31,31,30,31,30,31]
return (function(Y, M, D){
return (D <= __months[M])? (D > 0) :
(D==29) &&
(M==2) &&
!( ((Y&3)||((Y&15)&&(!(Y%25)))) );
});
})();

OK. But && M==2 seems superfluous. Should NaN (instead of blank) be
faster?

It's not obvious how best to speed-test - one should cover all 1461 good
dates in 4 years, or 146097 in 400, but how many & which bad ones?

I wonder why the using Date Object is so slow; possibly in order to
accommodate out-of-range fields.


I've been re-looking at Zeller's Concordance (and have been in
communication with one of his great-grandsons). In converting Y M D to
day-of-week, Zeller separated out the century, which seems simpler for
manual arithmetic; but, at least on a computer, ISTM better not to :-
<URL:http://www.merlyn.demon.co.uk/zeller-c.htm#DDCC> boxes 1 & 3
Box 3 gives a much faster way of converting Y M D to a day count than
using a Date Object (at least, in my system).

That function is repeated at
<URL:http://www.merlyn.demon.co.uk/dayscale.htm#CDD> Box 1
and Box 2 gives an inverse. ISTM unlikely that one can do much better
for the forward function; but the inverse might be improvable ....
 
R

Richard Cornford

Dr said:
Richard Cornford posted:
I make it 6-10% faster than Lasse's original (with !!ML). The version
I was using for testing was:-

var DateOK2b = (function(){
var __months = [NaN,31,28,31,30,31,30,31,31,30,31,30,31]
return (function(Y, M, D){
return (D <= __months[M])? (D > 0) :
(D==29) &&
(M==2) &&
!( ((Y&3)||((Y&15)&&(!(Y%25)))) );
});
})();

OK. But && M==2 seems superfluous.

It is necessary as M <= 0, or M >= 13 will refer to undefined (or NaN at
index zero) values and - D <= __months[M]) - will type convert those
undefined values into NaN for the comparison. All comparisons with NaN
return false, entering the branch where M == 2 is tested in order to
eliminate those out-of-range possibilities (which may still coincide
with D == 29, tested first as that would literally be a coincidence).
But I thought that testing M was better than using a local variable -
var ML = __months[M] - and applying a type-converting test to ML instead
(as it produces a boolean result in one operation and avoids the
assignment and additional work in variable instantiation).
Should NaN (instead of
blank) be faster?

I assume you mean starting the array - [NaN,31,28 - instead of -
[,31,28, -. It should be very fractionally faster when M == 0 as it
avoids a type-conversion to NaN from undefined, but I used it because I
don't trust javascript implementations to handle sparse array literals
consistently, so I avoid them when I can.
It's not obvious how best to speed-test - one should cover all 1461
good dates in 4 years, or 146097 in 400, but how many & which bad
ones?

I thought it only fair to use some month values outside of the
acceptable range along with some dates of zero or less and greater than
31 while comparing the various functions. But optimisation should be the
fast verification of correct date combinations as users should get those
right more often than not.
I wonder why the using Date Object is so slow; possibly in order to
accommodate out-of-range fields.

If you think IE's Date object is slow you haven't seen Opera 7.11
struggling through my first test script. :)
I've been re-looking at Zeller's Concordance (and have been in
communication with one of his great-grandsons). In converting Y M D
to day-of-week, Zeller separated out the century, which seems simpler
for manual arithmetic; but, at least on a computer, ISTM better not
to :- <URL:http://www.merlyn.demon.co.uk/zeller-c.htm#DDCC>
boxes 1 & 3
Box 3 gives a much faster way of converting Y M D to a day count than
using a Date Object (at least, in my system).

That function is repeated at
<URL:http://www.merlyn.demon.co.uk/dayscale.htm#CDD> Box 1
and Box 2 gives an inverse. ISTM unlikely that one can do much better
for the forward function; but the inverse might be improvable ....

I am going to be very pressed for time over the next couple of days but
I will have a look at that when I get the chance.

Richard.
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

Forum statistics

Threads
473,774
Messages
2,569,599
Members
45,175
Latest member
Vinay Kumar_ Nevatia
Top