DateTime.TryParseExact не работает с однозначным числом дня или месяца, независимо от строки формата

Я пытаюсь реализовать проверку TextBox для ввода дат пользователем. Короче говоря, я создаю новое приложение, и пользователи привыкли вводить даты таким образом, поэтому я хочу попробовать и проверить ввод, не заставляя их «узнавать» что-то новое. У меня есть следующий код обработчика событий, который я подключаю к полям даты для проверки ввода:

Dim DateText As String = String.Empty
Dim ValidDates As New List(Of Date)
Dim DateFormats() As String = {"Mdyy", "Mddyy", "MMdyy", "MMddyy", "Mdyyyy", "Mddyyyy", "MMdyyyy", "MMddyyyy"}

If TypeOf sender Is System.Windows.Forms.TextBox Then
    Dim CurrentField As System.Windows.Forms.TextBox = CType(sender, System.Windows.Forms.TextBox)

    If CurrentField.Text IsNot Nothing AndAlso Not String.IsNullOrEmpty(CurrentField.Text.Trim) Then
        DateText = CurrentField.Text.Trim.ReplaceCharacters(CharacterType.Punctuation)
    End If

    For Each ValidFormat As String In DateFormats
        Dim DateBuff As Date

        If Date.TryParseExact(DateText, ValidFormat, CultureInfo.InvariantCulture, DateTimeStyles.None, DateBuff) Then
            If Not ValidDates.Contains(DateBuff) Then
                ValidDates.Add(DateBuff)
            End If
        End If
    Next ValidFormat

    If ValidDates.Count > 1 Then
        CurrentField.SelectAll()
        CurrentField.HideSelection = False

        MessageBox.Show("The date you entered is ambiguous." & vbCrLf & vbCrLf &
                        "Please enter two digits for the month, two digits for the day and" & vbCrLf &
                        "two digits for the year." & vbCrLf & vbCrLf &
                        "For example, today's date should be entered as either " & Now.ToString("MMddyy") & vbCrLf &
                        " or " & Now.ToString("MM/dd/yy") & ".",
                        "AMBIGUOUS DATE ENTERED", MessageBoxButtons.OK, MessageBoxIcon.Exclamation)

        CurrentField.HideSelection = True
        e.Cancel = True
    ElseIf ValidDates.Count < 1 Then
        CurrentField.SelectAll()
        CurrentField.HideSelection = False

        MessageBox.Show("The date you entered was not valid." & vbCrLf & vbCrLf &
                        "Please enter two digits for the month, two digits for the day and" & vbCrLf &
                        "two digits for the year." & vbCrLf & vbCrLf &
                        "For example, today's date should be entered as either " & Now.ToString("MMddyy") & vbCrLf &
                        " or " & Now.ToString("MM/dd/yy") & ".",
                        "INVALID INPUT FORMAT", MessageBoxButtons.OK, MessageBoxIcon.Exclamation)

        CurrentField.HideSelection = True
        e.Cancel = True
    Else
        CurrentField.ForeColor = SystemColors.WindowText
        CurrentField.BackColor = SystemColors.Window
    End If
End If

Этот метод проверки работает правильно только в том случае, если формат включает двузначный месяц и двузначный день. Если я пытаюсь использовать любой из однозначных форматов (например, Mddyy, MMdyyyy и т. д.), TryParseExact всегда возвращает False, а дата никогда не добавляется к List(Of Date).

Вот некоторые «жестко закодированные» тесты, через которые я прошел, пытаясь добраться до источника проблемы. Я использовал некоторые преднамеренно неоднозначные даты, а также некоторые однозначно недвусмысленные:

If Date.TryParseExact("1223", "Mdyy", CultureInfo.InvariantCulture, DateTimeStyles.None, TempDate) Then
    Console.WriteLine($"success: 1223 -> {TempDate.ToString("M/d/yyyy")}")
Else
    Console.WriteLine("failed (1223)")
End If
'failed (1223)

If Date.TryParseExact("12123", "Mddyy", CultureInfo.InvariantCulture, DateTimeStyles.None, TempDate) Then
    Console.WriteLine($"success: 12123 -> {TempDate.ToString("M/d/yyyy")}")
Else
    Console.WriteLine("failed (12123)")
End If
'failed (12123)

If Date.TryParseExact("012123", "MMddyy", CultureInfo.InvariantCulture, DateTimeStyles.None, TempDate) Then
    Console.WriteLine($"success: 012123 -> {TempDate.ToString("M/d/yyyy")}")
Else
    Console.WriteLine("failed (012123)")
End If
'success: 012123 -> 1/21/2023

If Date.TryParseExact("1122023", "MMdyyyy", CultureInfo.InvariantCulture, DateTimeStyles.None, TempDate) Then
    Console.WriteLine($"success: 1122023 -> {TempDate.ToString("M/d/yyyy")}")
Else
    Console.WriteLine("failed (1122023)")
End If
'failed (1122023)

If Date.TryParseExact("72521", "Mddyy", CultureInfo.InvariantCulture, DateTimeStyles.None, TempDate) Then
    Console.WriteLine($"success: 72521 -> {TempDate.ToString("M/d/yyyy")}")
Else
    Console.WriteLine("failed (72521)")
End If
'failed (72521)

If Date.TryParseExact("072521", "MMddyy", CultureInfo.InvariantCulture, DateTimeStyles.None, TempDate) Then
    Console.WriteLine($"success: 072521 -> {TempDate.ToString("M/d/yyyy")}")
Else
    Console.WriteLine("failed (072521)")
End If
'success: 072521 -> 7/25/2021

If Date.TryParseExact("3312019", "Mddyyyy", CultureInfo.InvariantCulture, DateTimeStyles.None, TempDate) Then
    Console.WriteLine($"success: 3312019 -> {TempDate.ToString("M/d/yyyy")}")
Else
    Console.WriteLine("failed (3312019)")
End If
'failed (3312019)

If Date.TryParseExact("05201975", "MMddyyyy", CultureInfo.InvariantCulture, DateTimeStyles.None, TempDate) Then
    Console.WriteLine($"success: 05201975 -> {TempDate.ToString("M/d/yyyy")}")
Else
    Console.WriteLine("failed (05201975)")
End If
'success: 05201975 -> 5/20/1975

If Date.TryParseExact("432013", "Mdyyyy", CultureInfo.InvariantCulture, DateTimeStyles.None, TempDate) Then
    Console.WriteLine($"success: 432013 -> {TempDate.ToString("M/d/yyyy")}")
Else
    Console.WriteLine("failed (432013)")
End If
'failed (432013)

Я видел несколько сообщений с жалобами на «необычное поведение» метода TryParseExact(), но я не смог найти ничего, что объясняло бы, почему это происходит на самом деле. Я знаю, что использовал некоторые из этих методов синтаксического анализа в прошлом, но я не припомню, чтобы у меня когда-либо возникали такие большие проблемы с запуском простого синтаксического анализа.

Я думал, что весь смысл использования метода TryParseExact() заключался в том, чтобы я мог указать синтаксическому анализатору, где конкретно находятся элементы данных в строке, и получить обратно действительное значение. Я что-то упускаю или пропускаю здесь?


МОЕ "РЕШЕНИЕ":

На основании объяснения из принятого ответа, а также дополнительных деталей в принятом ответе от Как преобразовать строку даты и времени в формате MMMdyyyyhhmmtt в объект даты и времени?, я считаю, что придумал своего рода «обходное» решение, которое позволяет мне достичь моей цели, позволяя моим пользователям продолжать делать вещи так, как они привыкли, при этом обеспечивая проверку, которую я ищу .

  • Добавлена ​​новая переменная List(Of String), в которой я храню возможные форматы для данной входной строки (я уже ограничиваю ввод только числовыми, - или /)
  • Добавлен Select Case для вставки разделителей (/) в строку в определенных позициях в зависимости от длины строки.
  • Изменен массив DateFormats(), чтобы использовать строки формата для использования в TryParseExact(), которые включают разделители (/)

Благодаря этому я могу проверить каждое из этих значений на действительные даты и сделать вывод оттуда.

Вот обновленный метод:

Public Sub ValidateDateField(ByVal sender As Object, ByVal e As CancelEventArgs)
    Dim DateText As String = String.Empty
    Dim ValidDates As New List(Of Date)
    Dim DateFormats() As String = {"M/d/yy", "M/dd/yy", "MM/d/yy", "MM/dd/yy", "M/d/yyyy", "M/dd/yyyy", "MM/d/yyyy", "MM/dd/yyyy"}
    Dim FormattedDates As New List(Of String)

    If TypeOf sender Is System.Windows.Forms.TextBox Then
        Dim CurrentField As System.Windows.Forms.TextBox = CType(sender, System.Windows.Forms.TextBox)

        If CurrentField.Text IsNot Nothing AndAlso Not String.IsNullOrEmpty(CurrentField.Text.Trim) Then
            'ReplaceCharacters() is a custom extension method
            DateText = CurrentField.Text.Trim.ReplaceCharacters(CharacterType.Punctuation)

            Select Case DateText.Length
                Case < 4
                    CurrentField.SelectAll()
                    CurrentField.HideSelection = False

                    MessageBox.Show("The date you entered was not valid." & vbCrLf & vbCrLf &
                                    "Please enter two digits for the month, two digits for the day and" & vbCrLf &
                                    "two digits for the year." & vbCrLf & vbCrLf &
                                    "For example, today's date should be entered as either " & Now.ToString("MMddyy") & vbCrLf &
                                    " or " & Now.ToString("MM/dd/yy") & ".",
                                    "INVALID INPUT FORMAT", MessageBoxButtons.OK, MessageBoxIcon.Exclamation)

                    CurrentField.HideSelection = True
                    e.Cancel = True
                    Exit Sub
                Case 4
                    FormattedDates.Add(DateText.Insert(1, "/"c).Insert(3, "/"c))
                Case 5
                    FormattedDates.Add(DateText.Insert(1, "/"c).Insert(4, "/"c))
                    FormattedDates.Add(DateText.Insert(2, "/"c).Insert(4, "/"c))
                Case 6
                    FormattedDates.Add(DateText.Insert(1, "/"c).Insert(3, "/"c))
                    FormattedDates.Add(DateText.Insert(2, "/"c).Insert(5, "/"c))
                Case 7
                    FormattedDates.Add(DateText.Insert(1, "/"c).Insert(4, "/"c))
                    FormattedDates.Add(DateText.Insert(2, "/"c).Insert(4, "/"c))
                Case 8
                    FormattedDates.Add(DateText.Insert(2, "/"c).Insert(5, "/"c))
                Case Else
                    CurrentField.SelectAll()
                    CurrentField.HideSelection = False

                    MessageBox.Show("The date you entered was not valid." & vbCrLf & vbCrLf &
                                    "Please enter two digits for the month, two digits for the day and" & vbCrLf &
                                    "two digits for the year." & vbCrLf & vbCrLf &
                                    "For example, today's date should be entered as either " & Now.ToString("MMddyy") & vbCrLf &
                                    " or " & Now.ToString("MM/dd/yy") & ".",
                                    "INVALID INPUT FORMAT", MessageBoxButtons.OK, MessageBoxIcon.Exclamation)

                    CurrentField.HideSelection = True
                    e.Cancel = True
                    Exit Sub
            End Select

            For Each TempDate As String In FormattedDates
                For Each ValidFormat As String In DateFormats
                    Dim DateBuff As Date

                    If DateTime.TryParseExact(TempDate, ValidFormat, System.Globalization.CultureInfo.CurrentCulture, DateTimeStyles.None, DateBuff) Then
                        If Not ValidDates.Contains(DateBuff) Then
                            ValidDates.Add(DateBuff)
                        End If
                    End If
                Next ValidFormat
            Next TempDate

            If DateText.Trim.Length > 0 Then
                If ValidDates.Count > 1 Then
                    CurrentField.SelectAll()
                    CurrentField.HideSelection = False

                    MessageBox.Show("The date you entered is ambiguous." & vbCrLf & vbCrLf &
                                    "Please enter two digits for the month, two digits for the day and" & vbCrLf &
                                    "two digits for the year." & vbCrLf & vbCrLf &
                                    "For example, today's date should be entered as either " & Now.ToString("MMddyy") & vbCrLf &
                                    " or " & Now.ToString("MM/dd/yy") & ".",
                                    "AMBIGUOUS DATE ENTERED", MessageBoxButtons.OK, MessageBoxIcon.Exclamation)

                    CurrentField.HideSelection = True
                    e.Cancel = True
                ElseIf ValidDates.Count < 1 Then
                    CurrentField.SelectAll()
                    CurrentField.HideSelection = False

                    MessageBox.Show("The date you entered was not valid." & vbCrLf & vbCrLf &
                                    "Please enter two digits for the month, two digits for the day and" & vbCrLf &
                                    "two digits for the year." & vbCrLf & vbCrLf &
                                    "For example, today's date should be entered as either " & Now.ToString("MMddyy") & vbCrLf &
                                    " or " & Now.ToString("MM/dd/yy") & ".",
                                    "INVALID INPUT FORMAT", MessageBoxButtons.OK, MessageBoxIcon.Exclamation)

                    CurrentField.HideSelection = True
                    e.Cancel = True
                Else
                    CurrentField.ForeColor = SystemColors.WindowText
                    CurrentField.BackColor = SystemColors.Window
                End If
            End If
        End If
    End If
End Sub

Я не знаю ответа, но не было бы разумнее использовать элемент управления редактированием даты над обычным текстовым полем? Таким образом, убедитесь, что дата действительна, в первую очередь, пропуская всю эту сложность.

Hursey 15.02.2023 00:14

Отвечает ли это на ваш вопрос? .Net DateTime.ParseExact не работает

GSerg 15.02.2023 01:24

Вероятно, стоит обратить внимание на что-то вроде маскирующего текстового поля. Проблема в том, что если они вводят день 12 января 2022 года как 1122022, как компилятор узнает, что это 12 января 2022 года, а не 2 ноября 2022 года. Текстовое поле с маской может справиться с этим, добавляя правильные отсутствующие значения, пока пользователь их вводит. Все это говорит о том, что контроль даты был бы лучшим решением, которое по-прежнему позволяло бы вручную вводить дату для ваших пользователей. Иногда дополнительное нажатие клавиши не так уж и много.

ClearlyClueless 15.02.2023 04:53

@Hursey - Будет ли это иметь больше смысла? Да. Но, как я уже говорил, я пытаюсь включить в дизайн уже укоренившиеся привычки моих пользователей. Однако, судя по ссылке на документацию, похоже, что мне, возможно, придется немного вытолкнуть их из их зоны комфорта.

G_Hosa_Phat 15.02.2023 16:01

@GSerg - Принятый ответ в вашей первой ссылке не соответствует, но один из других ответов на этот вопрос предоставляет ту же документацию MSDN, которая «объясняет» это немного подробнее. Спасибо за эту ссылку, потому что я не наткнулся на нее, когда искал. Однако принятый ответ в вашей второй ссылке содержит более подробную информацию, которая полезна, хотя и немного разочаровывает. Видимо, этот вопрос возникает МНОГО.

G_Hosa_Phat 15.02.2023 16:05

@ClearlyClueless - Да, я рассмотрел здесь несколько различных вариантов ввода, в том числе с использованием элемента управления MonthCalendar, но, как я изначально сказал, я пытаюсь учесть привычки и модели использования моих пользователей в дизайне этой формы. Похоже, я не смогу полностью добиться этого так, как планировал, но посмотрим.

G_Hosa_Phat 15.02.2023 16:10
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
1
7
57
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

В документации об этом ясно сказано,

Если вы не используете разделители даты или времени в пользовательском шаблоне формата, использовать инвариантную культуру для параметра провайдера и самого широкого форма каждого описателя пользовательского формата. Например, если вы хотите укажите часы в шаблоне, укажите более широкую форму, "ЧЧ", вместо более узкая форма «Н».

Хотя для меня не имеет смысла, что синтаксический анализатор не может определить дату при использовании ParseExact и явном определении формата с однозначной цифрой день/месяц, по крайней мере, это отвечает на мой вопрос. Я имею в виду, что я говорю синтаксическому анализатору, что формат имеет только одну цифру в любой позиции, поэтому я думаю, что он, по крайней мере, попытается использовать этот формат и, когда ему это удастся, двинется дальше. В любом случае, спасибо за ссылку на документацию. Я, должно быть, совершенно упустил это из виду.

G_Hosa_Phat 15.02.2023 15:59

Чтобы расширить эту мысль, если бы я рассказал об этом TryParse(), я бы понял, что у него проблемы с двусмысленностью. Однако, поскольку я использую TryParseExact(), я явно заявляю, что я «знаю», в каком формате находится строка, и я хочу, чтобы синтаксический анализатор соответствовал именно этому формату. Если в формате указана одна цифра для дня, я бы не ожидал, что синтаксический анализатор попытается втиснуть две цифры в это поле под версией TryParseExact(). В противном случае я бы использовал более простую версию TryParse() и сэкономил бы несколько нажатий клавиш.

G_Hosa_Phat 15.02.2023 16:18

@GSerg - этот ответ содержит основную информацию, отвечающую на вопрос, но если мой вопрос будет помечен как дубликат, могу ли я предложить альтернативный дубликат, я, вероятно, предпочел бы принятый ответ на Как преобразовать строку даты и времени в форматировать MMMdyyyyhhmmtt для объекта datetime? (что само по себе является «дубликатом» предложенного дубликата моего вопроса). Этот ответ содержит более подробную информацию о причинах существования этой документации.

G_Hosa_Phat 15.02.2023 16:48

Другие вопросы по теме