Я пытаюсь реализовать проверку 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
Отвечает ли это на ваш вопрос? .Net DateTime.ParseExact не работает
Отвечает ли это на ваш вопрос? Как преобразовать строку даты и времени в формате MMMdyyyyhhmmtt в объект даты и времени?
Вероятно, стоит обратить внимание на что-то вроде маскирующего текстового поля. Проблема в том, что если они вводят день 12 января 2022 года как 1122022, как компилятор узнает, что это 12 января 2022 года, а не 2 ноября 2022 года. Текстовое поле с маской может справиться с этим, добавляя правильные отсутствующие значения, пока пользователь их вводит. Все это говорит о том, что контроль даты был бы лучшим решением, которое по-прежнему позволяло бы вручную вводить дату для ваших пользователей. Иногда дополнительное нажатие клавиши не так уж и много.
@Hursey - Будет ли это иметь больше смысла? Да. Но, как я уже говорил, я пытаюсь включить в дизайн уже укоренившиеся привычки моих пользователей. Однако, судя по ссылке на документацию, похоже, что мне, возможно, придется немного вытолкнуть их из их зоны комфорта.
@GSerg - Принятый ответ в вашей первой ссылке не соответствует, но один из других ответов на этот вопрос предоставляет ту же документацию MSDN, которая «объясняет» это немного подробнее. Спасибо за эту ссылку, потому что я не наткнулся на нее, когда искал. Однако принятый ответ в вашей второй ссылке содержит более подробную информацию, которая полезна, хотя и немного разочаровывает. Видимо, этот вопрос возникает МНОГО.
@ClearlyClueless - Да, я рассмотрел здесь несколько различных вариантов ввода, в том числе с использованием элемента управления MonthCalendar
, но, как я изначально сказал, я пытаюсь учесть привычки и модели использования моих пользователей в дизайне этой формы. Похоже, я не смогу полностью добиться этого так, как планировал, но посмотрим.
В документации об этом ясно сказано,
Если вы не используете разделители даты или времени в пользовательском шаблоне формата, использовать инвариантную культуру для параметра провайдера и самого широкого форма каждого описателя пользовательского формата. Например, если вы хотите укажите часы в шаблоне, укажите более широкую форму, "ЧЧ", вместо более узкая форма «Н».
Хотя для меня не имеет смысла, что синтаксический анализатор не может определить дату при использовании ParseExact
и явном определении формата с однозначной цифрой день/месяц, по крайней мере, это отвечает на мой вопрос. Я имею в виду, что я говорю синтаксическому анализатору, что формат имеет только одну цифру в любой позиции, поэтому я думаю, что он, по крайней мере, попытается использовать этот формат и, когда ему это удастся, двинется дальше. В любом случае, спасибо за ссылку на документацию. Я, должно быть, совершенно упустил это из виду.
Чтобы расширить эту мысль, если бы я рассказал об этом TryParse()
, я бы понял, что у него проблемы с двусмысленностью. Однако, поскольку я использую TryParseExact()
, я явно заявляю, что я «знаю», в каком формате находится строка, и я хочу, чтобы синтаксический анализатор соответствовал именно этому формату. Если в формате указана одна цифра для дня, я бы не ожидал, что синтаксический анализатор попытается втиснуть две цифры в это поле под версией TryParseExact()
. В противном случае я бы использовал более простую версию TryParse()
и сэкономил бы несколько нажатий клавиш.
Этот ответ был опубликован 7 лет назад на предложенном дублирующем вопросе.
@GSerg - этот ответ содержит основную информацию, отвечающую на вопрос, но если мой вопрос будет помечен как дубликат, могу ли я предложить альтернативный дубликат, я, вероятно, предпочел бы принятый ответ на Как преобразовать строку даты и времени в форматировать MMMdyyyyhhmmtt для объекта datetime? (что само по себе является «дубликатом» предложенного дубликата моего вопроса). Этот ответ содержит более подробную информацию о причинах существования этой документации.
Я не знаю ответа, но не было бы разумнее использовать элемент управления редактированием даты над обычным текстовым полем? Таким образом, убедитесь, что дата действительна, в первую очередь, пропуская всю эту сложность.