@@ -185,7 +185,7 @@ const monthsDesignator = character('Mm');
185185const durationDesignator = character ( 'Pp' ) ;
186186const secondsDesignator = character ( 'Ss' ) ;
187187const dateTimeSeparator = character ( ' Tt' ) ;
188- const durationTimeDesignator = character ( 'Tt' ) ;
188+ const timeDesignator = character ( 'Tt' ) ;
189189const weeksDesignator = character ( 'Ww' ) ;
190190const yearsDesignator = character ( 'Yy' ) ;
191191const utcDesignator = withCode ( character ( 'Zz' ) , ( data ) => {
@@ -204,12 +204,26 @@ const dateYear = withCode(
204204const dateMonth = withCode ( zeroPaddedInclusive ( 1 , 12 , 2 ) , ( data , result ) => ( data . month = + result ) ) ;
205205const dateDay = withCode ( zeroPaddedInclusive ( 1 , 31 , 2 ) , ( data , result ) => ( data . day = + result ) ) ;
206206
207- const timeHour = withCode ( hour , ( data , result ) => ( data . hour = + result ) ) ;
208- const timeMinute = withCode ( minuteSecond , ( data , result ) => ( data . minute = + result ) ) ;
209- const timeSecond = withCode ( choice ( minuteSecond , '60' ) , ( data , result ) => {
207+ function saveHour ( data , result ) {
208+ data . hour = + result ;
209+ }
210+ function saveMinute ( data , result ) {
211+ data . minute = + result ;
212+ }
213+ function saveSecond ( data , result ) {
210214 data . second = + result ;
211215 if ( data . second === 60 ) data . second = 59 ;
212- } ) ;
216+ }
217+ const timeHour = withCode ( hour , saveHour ) ;
218+ const timeMinute = withCode ( minuteSecond , saveMinute ) ;
219+ const timeSecond = withCode ( choice ( minuteSecond , '60' ) , saveSecond ) ;
220+ const timeHourNotValidMonth = withCode ( choice ( '00' , zeroPaddedInclusive ( 13 , 23 , 2 ) ) , saveHour ) ;
221+ const timeHourNot31DayMonth = withCode ( choice ( '02' , '04' , '06' , '09' , '11' ) , saveHour ) ;
222+ const timeHour2Only = withCode ( '02' , saveHour ) ;
223+ const timeMinuteNotValidDay = withCode ( choice ( '00' , zeroPaddedInclusive ( 32 , 59 , 2 ) ) , saveMinute ) ;
224+ const timeMinute30Only = withCode ( '30' , saveMinute ) ;
225+ const timeMinute31Only = withCode ( '31' , saveMinute ) ;
226+ const timeSecondNotValidMonth = withCode ( choice ( '00' , zeroPaddedInclusive ( 13 , 60 , 2 ) ) , saveSecond ) ;
213227const timeFraction = withCode ( fraction , ( data , result ) => {
214228 result = result . slice ( 1 ) ;
215229 const fraction = result . padEnd ( 9 , '0' ) ;
@@ -221,14 +235,34 @@ const timeZoneUTCOffsetSign = withCode(
221235 sign ,
222236 ( data , result ) => ( data . offsetSign = result === '-' || result === '\u2212' ? '-' : '+' )
223237) ;
224- const timeZoneUTCOffsetHour = withCode ( hour , ( data , result ) => ( data . offsetHour = + result ) ) ;
238+ function saveOffsetHour ( data , result ) {
239+ data . offsetHour = + result ;
240+ }
241+ const timeZoneUTCOffsetHour = withCode ( hour , saveOffsetHour ) ;
242+ const timeZoneUTCOffsetHourNotValidMonth = withCode ( zeroPaddedInclusive ( 13 , 23 , 2 ) , saveOffsetHour ) ;
225243const timeZoneUTCOffsetMinute = withCode ( minuteSecond , ( data , result ) => ( data . offsetMinute = + result ) ) ;
226244const timeZoneUTCOffsetSecond = withCode ( minuteSecond , ( data , result ) => ( data . offsetSecond = + result ) ) ;
227245const timeZoneUTCOffsetFraction = withCode ( fraction , ( data , result ) => {
228246 result = result . slice ( 1 ) ;
229247 const fraction = result . padEnd ( 9 , '0' ) ;
230248 data . offsetFraction = + fraction ;
231249} ) ;
250+ function saveOffset ( data ) {
251+ if ( data . offsetSign !== undefined && data . offsetHour !== undefined ) {
252+ const h = `${ data . offsetHour } ` . padStart ( 2 , '0' ) ;
253+ const m = `${ data . offsetMinute || 0 } ` . padStart ( 2 , '0' ) ;
254+ const s = `${ data . offsetSecond || 0 } ` . padStart ( 2 , '0' ) ;
255+ data . offset = `${ data . offsetSign } ${ h } :${ m } ` ;
256+ if ( data . offsetFraction ) {
257+ let fraction = `${ data . offsetFraction } ` . padStart ( 9 , '0' ) ;
258+ while ( fraction . endsWith ( '0' ) ) fraction = fraction . slice ( 0 , - 1 ) ;
259+ data . offset += `:${ s } .${ fraction } ` ;
260+ } else if ( data . offsetSecond ) {
261+ data . offset += `:${ s } ` ;
262+ }
263+ if ( data . offset === '-00:00' ) data . offset = '+00:00' ;
264+ }
265+ }
232266const timeZoneNumericUTCOffset = withCode (
233267 seq (
234268 timeZoneUTCOffsetSign ,
@@ -238,22 +272,25 @@ const timeZoneNumericUTCOffset = withCode(
238272 seq ( ':' , timeZoneUTCOffsetMinute , [ ':' , timeZoneUTCOffsetSecond , [ timeZoneUTCOffsetFraction ] ] )
239273 )
240274 ) ,
241- ( data ) => {
242- if ( data . offsetSign !== undefined && data . offsetHour !== undefined ) {
243- const h = `${ data . offsetHour } ` . padStart ( 2 , '0' ) ;
244- const m = `${ data . offsetMinute || 0 } ` . padStart ( 2 , '0' ) ;
245- const s = `${ data . offsetSecond || 0 } ` . padStart ( 2 , '0' ) ;
246- data . offset = `${ data . offsetSign } ${ h } :${ m } ` ;
247- if ( data . offsetFraction ) {
248- let fraction = `${ data . offsetFraction } ` . padStart ( 9 , '0' ) ;
249- while ( fraction . endsWith ( '0' ) ) fraction = fraction . slice ( 0 , - 1 ) ;
250- data . offset += `:${ s } .${ fraction } ` ;
251- } else if ( data . offsetSecond ) {
252- data . offset += `:${ s } ` ;
253- }
254- if ( data . offset === '-00:00' ) data . offset = '+00:00' ;
255- }
256- }
275+ saveOffset
276+ ) ;
277+ const timeZoneNumericUTCOffsetNotAmbiguous = withCode (
278+ choice (
279+ seq ( character ( '+\u2212' ) , timeZoneUTCOffsetHour ) ,
280+ seq (
281+ timeZoneUTCOffsetSign ,
282+ timeZoneUTCOffsetHour ,
283+ choice (
284+ seq ( timeZoneUTCOffsetMinute , [ timeZoneUTCOffsetSecond , [ timeZoneUTCOffsetFraction ] ] ) ,
285+ seq ( ':' , timeZoneUTCOffsetMinute , [ ':' , timeZoneUTCOffsetSecond , [ timeZoneUTCOffsetFraction ] ] )
286+ )
287+ )
288+ ) ,
289+ saveOffset
290+ ) ;
291+ const timeZoneNumericUTCOffsetNotAmbiguousAllowedNegativeHour = withCode (
292+ choice ( timeZoneNumericUTCOffsetNotAmbiguous , seq ( '-' , timeZoneUTCOffsetHourNotValidMonth ) ) ,
293+ saveOffset
257294) ;
258295const timeZoneUTCOffset = choice ( utcDesignator , timeZoneNumericUTCOffset ) ;
259296const timeZoneUTCOffsetName = seq (
@@ -286,14 +323,42 @@ const timeSpec = seq(
286323 timeHour ,
287324 choice ( [ ':' , timeMinute , [ ':' , timeSecond , [ timeFraction ] ] ] , seq ( timeMinute , [ timeSecond , [ timeFraction ] ] ) )
288325) ;
326+ const timeSpecWithOptionalTimeZoneNotAmbiguous = choice (
327+ seq ( timeHour , [ timeZoneNumericUTCOffsetNotAmbiguous ] , [ timeZoneBracketedAnnotation ] ) ,
328+ seq ( timeHourNotValidMonth , timeZone ) ,
329+ seq (
330+ choice (
331+ seq ( timeHourNotValidMonth , timeMinute ) ,
332+ seq ( timeHour , timeMinuteNotValidDay ) ,
333+ seq ( timeHourNot31DayMonth , timeMinute31Only ) ,
334+ seq ( timeHour2Only , timeMinute30Only )
335+ ) ,
336+ [ timeZoneBracketedAnnotation ]
337+ ) ,
338+ seq (
339+ timeHour ,
340+ timeMinute ,
341+ choice (
342+ seq ( timeZoneNumericUTCOffsetNotAmbiguousAllowedNegativeHour , [ timeZoneBracketedAnnotation ] ) ,
343+ seq ( timeSecondNotValidMonth , [ timeZone ] ) ,
344+ seq ( timeSecond , timeFraction , [ timeZone ] )
345+ )
346+ ) ,
347+ seq ( timeHour , ':' , timeMinute , [ ':' , timeSecond , [ timeFraction ] ] , [ timeZone ] )
348+ ) ;
289349const timeSpecSeparator = seq ( dateTimeSeparator , timeSpec ) ;
290350
291351const dateSpecMonthDay = seq ( [ '--' ] , dateMonth , [ '-' ] , dateDay ) ;
292352const dateSpecYearMonth = seq ( dateYear , [ '-' ] , dateMonth ) ;
293353const date = choice ( seq ( dateYear , '-' , dateMonth , '-' , dateDay ) , seq ( dateYear , dateMonth , dateDay ) ) ;
294354const dateTime = seq ( date , [ timeSpecSeparator ] , [ timeZone ] ) ;
295355const calendarDateTime = seq ( dateTime , [ calendar ] ) ;
296- const calendarTime = seq ( timeSpec , [ timeZone ] , [ calendar ] ) ;
356+ const calendarDateTimeTimeRequired = seq ( date , timeSpecSeparator , [ timeZone ] , [ calendar ] ) ;
357+ const calendarTime = choice (
358+ seq ( timeDesignator , timeSpec , [ timeZone ] , [ calendar ] ) ,
359+ seq ( timeSpec , [ timeZone ] , calendar ) ,
360+ seq ( timeSpecWithOptionalTimeZoneNotAmbiguous )
361+ ) ;
297362
298363const durationFractionalPart = withCode ( between ( 1 , 9 , digit ( ) ) , ( data , result ) => {
299364 const fraction = result . padEnd ( 9 , '0' ) ;
@@ -317,7 +382,7 @@ const durationHours = seq(
317382 hoursDesignator ,
318383 [ choice ( durationMinutes , durationSeconds ) ]
319384) ;
320- const durationTime = seq ( durationTimeDesignator , choice ( durationHours , durationMinutes , durationSeconds ) ) ;
385+ const durationTime = seq ( timeDesignator , choice ( durationHours , durationMinutes , durationSeconds ) ) ;
321386const durationDays = seq (
322387 withCode ( oneOrMore ( digit ( ) ) , ( data , result ) => ( data . days = + result * data . factor ) ) ,
323388 daysDesignator
@@ -354,7 +419,7 @@ const goals = {
354419 DateTime : calendarDateTime ,
355420 Duration : duration ,
356421 MonthDay : choice ( dateSpecMonthDay , calendarDateTime ) ,
357- Time : choice ( calendarTime , calendarDateTime ) ,
422+ Time : choice ( calendarTime , calendarDateTimeTimeRequired ) ,
358423 TimeZone : choice ( temporalTimeZoneIdentifier , seq ( date , [ timeSpecSeparator ] , timeZone , [ calendar ] ) ) ,
359424 YearMonth : choice ( dateSpecYearMonth , calendarDateTime ) ,
360425 ZonedDateTime : zonedDateTime
0 commit comments