66package kotlinx.datetime.internal.format.parser
77
88import kotlinx.datetime.internal.isAsciiDigit
9+ import kotlinx.datetime.internal.isAsciiLetter
910
1011internal interface ParserOperation <in Output > {
1112 fun consume (storage : Output , input : CharSequence , startIndex : Int ): ParseResult
@@ -142,9 +143,8 @@ internal class UnconditionalModification<Output>(
142143internal class TimeZoneParserOperation <Output >(
143144 private val setter : AssignableField <Output , String >
144145) : ParserOperation<Output> {
145-
146146 override fun consume (storage : Output , input : CharSequence , startIndex : Int ): ParseResult {
147- val lastMatch = validateTimezone (input, startIndex)
147+ val lastMatch = validateTimeZone (input, startIndex)
148148 return if (lastMatch > startIndex) {
149149 setter.setWithoutReassigning(storage, input.substring(startIndex, lastMatch), startIndex, lastMatch)
150150 ParseResult .Ok (lastMatch)
@@ -158,95 +158,106 @@ internal class TimeZoneParserOperation<Output>(
158158 START ,
159159 AFTER_PREFIX ,
160160 AFTER_SIGN ,
161+ AFTER_INIT_SIGN ,
161162 AFTER_HOUR ,
163+ AFTER_INIT_HOUR ,
162164 AFTER_MINUTE ,
163165 AFTER_COLON_MINUTE ,
164- END ,
165- INVALID
166+ IN_PART ,
167+ AFTER_SLASH ,
168+ END
166169 }
167170
168- private fun validateTimezone (input : CharSequence , startIndex : Int ): Int {
171+ private inline fun Boolean.onTrue (action : () -> Unit ): Boolean = if (this ) { action(); true } else false
172+
173+ private inline fun Boolean.onFalse (action : () -> Unit ): Boolean = if (this ) true else { action(); false }
174+
175+ private fun validateTimeZone (input : CharSequence , startIndex : Int ): Int {
169176 var index = startIndex
170- var lastValidIndex = startIndex
171177
172178 fun validatePrefix (validValues : List <String >): Boolean =
173- validValues.firstOrNull { input.startsWith(it) }?.let {
174- index + = it.length
175- lastValidIndex = index
176- true
177- } ? : false
178-
179- fun validateTimeComponent (length : Int ): Boolean {
180- if ((index.. < (index + length)).all { input.getOrNull(it)?.isAsciiDigit() ? : false }) {
181- index + = length
182- lastValidIndex = index
183- return true
184- }
185- return false
186- }
179+ validValues.firstOrNull { input.startsWith(it, index) }?.also { index + = it.length } != null
180+
181+ fun validateSign (): Boolean = (input[index] in listOf (' +' , ' -' )).onTrue { index++ }
182+
183+ fun validateTimeComponent (length : Int ): Boolean =
184+ (index.. < (index + length))
185+ .all { input.getOrNull(it)?.isAsciiDigit() ? : false }
186+ .onTrue { index + = length }
187+
188+ fun validateTimeComponentWithColon (): Boolean =
189+ (input[index] == ' :' ).onTrue { index++ } && validateTimeComponent(2 ).onFalse { index-- }
190+
191+ fun Char.isTimeZoneInitial (): Boolean = isAsciiLetter() || this == ' .' || this == ' _'
192+ fun Char.isTimeZoneChar (): Boolean = isTimeZoneInitial() || isAsciiDigit() || this == ' -' || this == ' +'
193+
194+ fun validateTimeZoneInitial (): Boolean = input[index].isTimeZoneInitial().onTrue { index++ }
195+ fun validateTimeZoneChar (): Boolean = input[index].isTimeZoneChar().onTrue { index++ }
196+ fun validateSlash (): Boolean = (input[index] == ' /' ).onTrue { index++ }
187197
188198 var state = State .START
189199 while (index < input.length) {
190200 state = when (state) {
191201 State .START -> when {
192- input[index] == ' Z' || input[index] == ' z' -> {
193- index++
194- State .END
195- }
196-
197- input[index] in listOf (' +' , ' -' ) -> {
198- index++
199- State .AFTER_SIGN
200- }
201-
202202 validatePrefix(listOf (" UTC" , " GMT" , " UT" )) -> State .AFTER_PREFIX
203- else -> State .INVALID
203+ validateSign() -> State .AFTER_INIT_SIGN
204+ validateTimeZoneInitial() -> State .IN_PART
205+ else -> break
204206 }
205207
206208 State .AFTER_PREFIX -> when {
207- input[index] in listOf (' +' , ' -' ) -> {
208- index++
209- State .AFTER_SIGN
210- }
211-
212- else -> State .INVALID
209+ validateSign() -> State .AFTER_SIGN
210+ else -> State .IN_PART
213211 }
214212
215213 State .AFTER_SIGN -> when {
216214 validateTimeComponent(2 ) -> State .AFTER_HOUR
215+ else -> State .IN_PART
216+ }
217+
218+ State .AFTER_INIT_SIGN -> when {
219+ validateTimeComponent(2 ) -> State .AFTER_INIT_HOUR
217220 validateTimeComponent(1 ) -> State .END
218- else -> State . INVALID
221+ else -> break
219222 }
220223
221224 State .AFTER_HOUR -> when {
222- input[index] == ' :' -> {
223- index++
224- if (validateTimeComponent(2 )) State .AFTER_COLON_MINUTE else State .INVALID
225- }
225+ validateTimeComponentWithColon() -> State .AFTER_COLON_MINUTE
226+ else -> State .IN_PART
227+ }
226228
229+ State .AFTER_INIT_HOUR -> when {
230+ validateTimeComponentWithColon() -> State .AFTER_COLON_MINUTE
227231 validateTimeComponent(2 ) -> State .AFTER_MINUTE
228- else -> State . INVALID
232+ else -> break
229233 }
230234
231235 State .AFTER_MINUTE -> when {
232236 validateTimeComponent(2 ) -> State .END
233- else -> State . INVALID
237+ else -> break
234238 }
235239
236240 State .AFTER_COLON_MINUTE -> when {
237- input[index] == ' :' -> {
238- index++
239- if (validateTimeComponent(2 )) State .END else State .INVALID
240- }
241+ validateTimeComponentWithColon() -> State .END
242+ else -> break
243+ }
244+
245+ State .IN_PART -> when {
246+ validateTimeZoneChar() -> State .IN_PART
247+ validateSlash() -> State .AFTER_SLASH
248+ else -> break
249+ }
241250
242- else -> State .INVALID
251+ State .AFTER_SLASH -> when {
252+ validateTimeZoneInitial() -> State .IN_PART
253+ else -> break
243254 }
244255
245- State .END , State . INVALID -> break
256+ State .END -> break
246257 }
247258 }
248259
249- return if (state == State .END ) index else lastValidIndex
260+ return index - if (state == State .AFTER_SLASH || state == State . AFTER_INIT_SIGN ) 1 else 0
250261 }
251262 }
252263}
0 commit comments