
Parse an ISO 8601 date/time string.
| Type | Intent | Optional | Attributes | Name | ||
|---|---|---|---|---|---|---|
| character(len=*), | intent(in) | :: | str | |||
| integer, | intent(out), | optional | :: | stat |
function parse_datetime(str, stat) result(dt) !! version: experimental !! !! Parse an ISO 8601 date/time string. character(len=*), intent(in) :: str integer, intent(out), optional :: stat type(datetime_type) :: dt integer :: slen, ios, off_h, off_m, ms_end integer :: max_day character(len=1) :: sign_ch character(len=32) :: tmp_str real(dp) :: ms_frac if (present(stat)) stat = 0 dt = datetime_type() slen = len_trim(str) ! Require at least YYYY-MM-DD (10 characters) if (slen < 10) then if (present(stat)) stat = 1 return end if ! Check required date separators for ISO 8601 (YYYY-MM-DD) if (str(5:5) /= '-' .or. str(8:8) /= '-') then if (present(stat)) stat = 1 return end if read(str(1:4), '(I4)', iostat=ios) dt%year if (ios /= 0) then if (present(stat)) stat = 1 return end if read(str(6:7), '(I2)', iostat=ios) dt%month if (ios /= 0) then if (present(stat)) stat = 1 return end if ! Validate month range [1,12] if (dt%month < 1 .or. dt%month > 12) then if (present(stat)) stat = 1 return end if read(str(9:10), '(I2)', iostat=ios) dt%day if (ios /= 0) then if (present(stat)) stat = 1 return end if ! Validate day range [1, days_in_month] max_day = days_in_month(dt%month, dt%year) if (dt%day < 1 .or. dt%day > max_day) then if (present(stat)) stat = 1 return end if if (slen == 10) return if (str(11:11) /= 'T' .and. & str(11:11) /= 't' .and. & str(11:11) /= ' ') then if (present(stat)) stat = 1 return end if if (slen < 19) then if (present(stat)) stat = 1 return end if ! Validate required time separators (HH:MM:SS) if (str(14:14) /= ':' .or. str(17:17) /= ':') then if (present(stat)) stat = 1 return end if read(str(12:13), '(I2)', iostat=ios) dt%hour if (ios /= 0) then if (present(stat)) stat = 1 return end if ! Validate hour range [0,23] if (dt%hour < 0 .or. dt%hour > 23) then if (present(stat)) stat = 1 return end if read(str(15:16), '(I2)', iostat=ios) dt%minute if (ios /= 0) then if (present(stat)) stat = 1 return end if ! Validate minute range [0,59] if (dt%minute < 0 .or. dt%minute > 59) then if (present(stat)) stat = 1 return end if read(str(18:19), '(I2)', iostat=ios) dt%second if (ios /= 0) then if (present(stat)) stat = 1 return end if ! Validate second range [0,59] if (dt%second < 0 .or. dt%second > 59) then if (present(stat)) stat = 1 return end if if (slen == 19) return ms_end = 19 if (str(20:20) == '.') then ms_end = 20 do while (ms_end < slen) sign_ch = str(ms_end+1:ms_end+1) if (sign_ch >= '0' .and. sign_ch <= '9') then ms_end = ms_end + 1 else exit end if end do if (ms_end == 20) then ! "." without following digits if (present(stat)) stat = 1 return end if tmp_str = '0' // str(20:ms_end) read(tmp_str, *, iostat=ios) ms_frac if (ios /= 0) then if (present(stat)) stat = 1 return end if dt%millisecond = nint(ms_frac * 1000.0_dp) end if if (slen <= ms_end) return sign_ch = str(ms_end+1:ms_end+1) if (sign_ch == 'Z' .or. sign_ch == 'z') then dt%utc_offset_minutes = 0 else if (sign_ch == '+' .or. sign_ch == '-') then if (slen < ms_end + 6) then if (present(stat)) stat = 1 return end if read(str(ms_end+2:ms_end+3), '(I2)', & iostat=ios) off_h if (ios /= 0) then if (present(stat)) stat = 1 return end if ! Require ':' between offset hours and minutes if (str(ms_end+4:ms_end+4) /= ':') then if (present(stat)) stat = 1 return end if read(str(ms_end+5:ms_end+6), '(I2)', & iostat=ios) off_m if (ios /= 0) then if (present(stat)) stat = 1 return end if ! Validate timezone offset ranges if (off_h < 0 .or. off_h > 23 .or. & off_m < 0 .or. off_m > 59) then if (present(stat)) stat = 1 return end if dt%utc_offset_minutes = off_h * 60 + off_m if (sign_ch == '-') & dt%utc_offset_minutes = & -dt%utc_offset_minutes else if (present(stat)) stat = 1 return end if end function parse_datetime