@@ -637,9 +637,9 @@ var Utilities_Date = new function(){
637637 }
638638
639639 // A date, optionally followed by a slash and a second date, where each date is a
640- // 4-digit year with an optional minus sign followed by month/day/time/ qualifier
640+ // 4-digit year with an optional minus sign followed by month/day/qualifier
641641 // characters
642- var _edtfShapeRE = / ^ - ? [ 0 - 9 ] { 4 } [ - 0 - 9 T : + Z ~ ? % ] * ( \/ - ? [ 0 - 9 ] { 4 } [ - 0 - 9 T : + Z ~ ? % ] * ) ? $ / i ;
642+ var _edtfShapeRE = / ^ - ? [ 0 - 9 ] { 4 } [ - 0 - 9 ~ ? % ] * ( \/ - ? [ 0 - 9 ] { 4 } [ - 0 - 9 ~ ? % ] * ) ? $ / ;
643643
644644 // Era markers, with optional periods and spaces
645645 var _bceMarker = 'B\\.?\\s?C\\.?(?:\\s?E\\.?)?' ;
@@ -714,6 +714,14 @@ var Utilities_Date = new function(){
714714 return str . replace ( / [ \u2013 \u2014 \u2212 ] / g, '-' ) . replace ( / - - + / g, '-' ) ;
715715 }
716716
717+ // Days in a 1-indexed month, in the proleptic Gregorian calendar
718+ function _daysInMonth ( year , month ) {
719+ if ( month == 2 ) {
720+ return ( year % 4 == 0 && ( year % 100 != 0 || year % 400 == 0 ) ) ? 29 : 28 ;
721+ }
722+ return [ 4 , 6 , 9 , 11 ] . includes ( month ) ? 30 : 31 ;
723+ }
724+
717725 // Check whether a string gives a year in a notation strToDate() misreads, either a
718726 // negative year or an era marker. An era marker counts only if the rest of the
719727 // string is EDTF punctuation, so that spelled-out dates ("January 10 200 BCE") are
@@ -785,26 +793,14 @@ var Utilities_Date = new function(){
785793 * @param {String } str
786794 * @return {Object|false } - Object with 'begin' ({year, month, day} -- month 0-indexed,
787795 * as in strToDate()), 'end' (same, for intervals), and 'circa' (true if any part
788- * is uncertain or approximate); false if the string isn't EDTF, uses unsupported
789- * features, or the EDTF library isn't loaded
796+ * is uncertain or approximate); false if the string isn't EDTF or uses unsupported
797+ * features
790798 */
791799 this . parseEDTF = function ( str ) {
792- if ( typeof EDTF == 'undefined' || typeof str != 'string' ) {
800+ if ( typeof str != 'string' ) {
793801 return false ;
794802 }
795803 str = _normalizeDashes ( str . trim ( ) ) ;
796- // Fast path for plain ISO dates, which don't need the full EDTF parser
797- var iso = str . match ( / ^ ( [ 0 - 9 ] { 4 } ) (?: - ( 0 [ 1 - 9 ] | 1 [ 0 - 2 ] ) (?: - ( 0 [ 1 - 9 ] | [ 1 2 ] [ 0 - 9 ] | 3 [ 0 1 ] ) ) ? ) ? $ / ) ;
798- if ( iso ) {
799- let begin = { year : parseInt ( iso [ 1 ] , 10 ) } ;
800- if ( iso [ 2 ] ) {
801- begin . month = parseInt ( iso [ 2 ] , 10 ) - 1 ;
802- if ( iso [ 3 ] ) {
803- begin . day = parseInt ( iso [ 3 ] , 10 ) ;
804- }
805- }
806- return { begin } ;
807- }
808804 // Convert a circa prefix to an approximate qualifier below
809805 var circa = _circaPrefixRE . test ( str ) ;
810806 if ( circa ) {
@@ -841,65 +837,64 @@ var Utilities_Date = new function(){
841837 if ( circa && ! / [ ~ ? % ] $ / . test ( str ) ) {
842838 str += '~' ;
843839 }
844- // Skip the parser for strings that can't be in the supported EDTF subset,
840+ // Reject strings that can't be in the supported EDTF subset,
845841 // e.g., other numeric date formats like "5/13/2021" and "13.5.2021"
846842 if ( ! _edtfShapeRE . test ( str ) ) {
847843 return false ;
848844 }
849- var parsed ;
850- try {
851- parsed = EDTF . parse ( str ) ;
852- }
853- catch ( e ) {
854- return false ;
855- }
856845
857- // Only plain dates at level 0-1, without unspecified digits (e.g., "196X")
858- function convertDate ( date ) {
859- if ( ! date || date . type != 'Date' || date . level > 1 || date . unspecified ) {
846+ // A single date: a signed 4-digit year, an optional month and day, and an
847+ // optional trailing uncertain/approximate qualifier ("2004-06~", "-0429?").
848+ // A qualifier anywhere else ("2004-06~-11") is rejected, as are month codes
849+ // above 12, including seasons, and invalid calendar days.
850+ function parseDate ( dateStr ) {
851+ var m = dateStr . match ( / ^ ( - ? [ 0 - 9 ] { 4 } ) (?: - ( [ 0 - 9 ] { 2 } ) (?: - ( [ 0 - 9 ] { 2 } ) ) ? ) ? ( [ ~ ? % ] ) ? $ / ) ;
852+ if ( ! m ) {
860853 return false ;
861854 }
862- let converted = { year : date . values [ 0 ] } ;
863- if ( date . values . length > 1 ) {
864- converted . month = date . values [ 1 ] ;
865- }
866- if ( date . values . length > 2 ) {
867- converted . day = date . values [ 2 ] ;
855+ let date = { year : parseInt ( m [ 1 ] , 10 ) } ;
856+ if ( m [ 2 ] ) {
857+ let month = parseInt ( m [ 2 ] , 10 ) ;
858+ if ( month < 1 || month > 12 ) {
859+ return false ;
860+ }
861+ date . month = month - 1 ;
862+ if ( m [ 3 ] ) {
863+ let day = parseInt ( m [ 3 ] , 10 ) ;
864+ if ( day < 1 || day > _daysInMonth ( date . year , month ) ) {
865+ return false ;
866+ }
867+ date . day = day ;
868+ }
868869 }
869- return converted ;
870+ return { date , circa : ! ! m [ 4 ] } ;
870871 }
871872
872- let result = { } ;
873- if ( parsed . type == 'Date' ) {
874- result . begin = convertDate ( parsed ) ;
875- if ( ! result . begin ) {
873+ var [ beginStr , endStr ] = str . split ( '/' ) ;
874+ var begin = parseDate ( beginStr ) ;
875+ if ( ! begin ) {
876+ return false ;
877+ }
878+ let result = { begin : begin . date } ;
879+ if ( begin . circa ) {
880+ result . circa = true ;
881+ }
882+ if ( endStr !== undefined ) {
883+ let end = parseDate ( endStr ) ;
884+ if ( ! end ) {
876885 return false ;
877886 }
878- if ( parsed . uncertain || parsed . approximate ) {
887+ result . end = end . date ;
888+ if ( end . circa ) {
879889 result . circa = true ;
880890 }
881- }
882- else if ( parsed . type == 'Interval' ) {
883- result . begin = convertDate ( parsed . values [ 0 ] ) ;
884- result . end = convertDate ( parsed . values [ 1 ] ) ;
885- if ( ! result . begin || ! result . end ) {
886- return false ;
887- }
888891 // Reject reversed and degenerate intervals
889892 let toComparable = date => date . year * 10000
890893 + ( date . month !== undefined ? ( date . month + 1 ) * 100 : 0 )
891894 + ( date . day || 0 ) ;
892895 if ( toComparable ( result . end ) <= toComparable ( result . begin ) ) {
893896 return false ;
894897 }
895- for ( let value of parsed . values ) {
896- if ( value . uncertain || value . approximate ) {
897- result . circa = true ;
898- }
899- }
900- }
901- else {
902- return false ;
903898 }
904899 return result ;
905900 } ;
0 commit comments