Path: csiph.com!v102.xanadu-bbs.net!xanadu-bbs.net!news.albasani.net!weretis.net!feeder1.news.weretis.net!news.solani.org!.POSTED!not-for-mail From: Thomas 'PointedEars' Lahn Newsgroups: comp.lang.javascript Subject: Re: Date arithmetic Date: Sat, 15 Nov 2014 07:13:25 +0100 Organization: PointedEars Software (PES) Lines: 192 Message-ID: <4665324.AV9EZNQcsx@PointedEars.de> References: <111120142237479787%timstreater@greenbee.net> <6814026.8sujcrue1K@PointedEars.de> Reply-To: Thomas 'PointedEars' Lahn Mime-Version: 1.0 Content-Type: text/plain; charset="UTF-8" Content-Transfer-Encoding: 8Bit X-Trace: solani.org 1416032008 6741 eJwFwYEBwDAEBMCVGuExDh/2H6F3dnFAVxjU1japdjnqUtx8dr4qsCaZHXja4etNDUEORX4tCBGj (15 Nov 2014 06:13:28 GMT) X-Complaints-To: abuse@news.solani.org NNTP-Posting-Date: Sat, 15 Nov 2014 06:13:28 +0000 (UTC) User-Agent: KNode/4.14.2 X-User-ID: eJwFwYEBwCAIA7CXANuC5yiO/09YwiVXJ0SBw3nC3PXBtFildx8Vsb05mq/5rEGryGuRx7079kGWo8YH+QNGtBTY Cancel-Lock: sha1:GuMIq4mtVrLRbGVlaC2jVt3KOgA= X-NNTP-Posting-Host: eJwFwQcBwEAIBDBLzKPI4Rn+JTRxBaPD4DA/v9DHmA5Hlk5CvmK6lANbB10TKmcxK89i7QcSvREM Xref: csiph.com comp.lang.javascript:26141 Christoph M. Becker wrote: > Thomas 'PointedEars' Lahn wrote: >> Christoph M. Becker wrote: >>> parts = isoExp.exec(isoDay1); >> >> I find >> >> parts = isoDay1.match(isoExp); >> >> easier to read. I use RegExp.prototype.exec() only in loops, such as >> with parsers, because with global matching there is no other way to get >> at the submatches. > > ACK. Frankly, I had copied the regexp handling from the FAQ[1]. It > might be appropriate to change the code there, or at least adding a > respective comment. > […] > [1] Several things could be and need to be said about that code: ,---- | /**Parses string formatted as YYYY-MM-DD to a Date object. Unusual commenting. The “/**” of a multi-line JSDoc comment should be on its own line: | * If the supplied string does not match the format, an | * invalid Date (value NaN) is returned. | * @param {string} dateStringInRange format YYYY-MM-DD, with year in I made it a rule for myself to refer to primitive values by lowercase type name and object values by first-letter-uppercase type name (but for types, I write, for example, “String type”, as it is in the ECMAScript Language Specification). For formal parameters, when the object variant of a value of a primitive type is supported, I use the latter (a future revision of the ECMAScript Support Matrix [1] will explain the type system and this use of type names in the Matrix). The function as it is written is capable of handling String instances as well (however useless they may usually seem; but you can derive a prototype from String.prototype, see jsx.string.unicode.WideString [2]), so I would write “{String}” instead to indicate that possibility. | * range of 0000-9999, inclusive. | * @return {Date} Date object representing the string. | */ | function parseISO8601(dateStringInRange) { | var isoExp = /^\s*(\d{4})-(\d\d)-(\d\d)\s*$/, | date = new Date(NaN), month, Here a Date instance is being created even before it as been ascertained that there is a date to represent. This is apparently done to ensure that the return value always is a reference to a Date instance. This is questionable: What is the additional value of returning a Date instance in that case? If you wanted to check that the value is invalid, you would have to write if (isNaN(parseISO8601(foo))) or if (isNaN(parseISO8601(foo).getTime())) to avoid the rather inefficient [3] roundtrip ToNumber → [[DefaultValue]] (PreferredType=Number) → RegExp.prototype.valueOf() → time value. [4] If instead you simply returned “null” for an invalid date, then if (parseISO8601(foo) === null) or if (parseISO8601(foo) == null) or even if (!parseISO8601(foo)) would suffice. | parts = isoExp.exec(dateStringInRange); This is _not_ wrong, and it might even be more efficient than dateStringInRange.match(isoExp), because String.prototype.match() when passed a reference to a RegExp instance whose global flag is not set, is a wrapper for RegExp.prototype.exec() per Specification. [5a] I just find it less intuitive. YMMV. It could be considered an advantage that this does not break if “dateStringInRange” does not hold a string because RegExp.prototype.exec() does the conversion [5b]. However, from a different point of view that could also be considered a disadvantage – should such code not break to show the caller that they have called the function wrong? The expected type of the parameter is documented after all. And third, parts = String(dateStringInRange).match(isoExp); fixes even that problem if it would be determined that it requires fixing. Fourth, considering compatibility, the ECMAScript Support Matrix shows no significant difference between RegExp.prototype.exec() and String.prototype.match() [5c] but for now one must keep in mind that such might have been be due to the fact that implementations older than JavaScript 1.5 have not been tested in version 2. In this case, neither MDN nor MSDN show difference in support [6a][6b][7], but if for some reason one needed to support older implementations, further research would be necessary. | if(parts) { | month = +parts[2]; The hard-coded type conversion here *could* be considered unnecessary … | date.setFullYear(parts[1], month - 1, parts[3]); ^^^^ … because it would be done here … | if(month != date.getMonth() + 1) { ^^^^^^^^ … and here implicitly anyway. | date.setTime(NaN); So first we set the time value and when it does not fit, we set it to NaN, just to return a reference to a Date instance that we need to convert again to find out something went wrong? That does not look very efficient. | } | } | return date; | } | `---- > Hopefully, that is possible without a formal vote. :) AFAIK, the FAQ first needs to be written that is being voted on. >> However, you may want to skip the Math.abs() to give an indication to the >> caller which date is in the future of the other. Rounding should also be >> considered because those values are internally represented as >> floating-point values. > > Omitting Math.abs() may or may not be desirable, depending on the usage. Code becomes better reusable if you make fewer assumptions. The more it does that could be optional, either the less flexible (hard-coded handling) or the more complex (handling of optional parameters/configuration or different types), and less efficient as a result, it becomes. For example, if you are simply not using Math.abs() here, the caller is provided with the opportunity to handle negative return values as they see fit in each specific use-case. > In pratice I would round to be on the safe side, but for demonstration > purposes I deliberately omitted the rounding to clarify that this > calculation is supposed to be exact, opposed to the Evertjan's suggestion. > >> JFTR, JSX:date.js (see sig) contains jsx.date.dayOfYear() and optionally >> provides Date.prototype.getDayOfYear() which can also be used to compute >> the difference. > > The calculation in jsx.date.dateOfYear() is rather intriguing; I wasn't s/date/day/ deliberately. > aware of that formula. Thanks. It is an implementation of a description in a Wikipedia article, as the JSdoc comment shows (hence “easy-to-be found”). You’re welcome. _______ [1] [2] [3] [4] [5a] [5b] [5c] [6a] [6b] [7] -- PointedEars FAQ: | SVN: Twitter: @PointedEars2 | ES Matrix: Please do not Cc: me. / Bitte keine Kopien per E-Mail.