Лучший CDate для VB6

У нас есть приложение VB6 (в компоненте COM), которое использует CDate (), чтобы взять строку и преобразовать ее в Date для хранения в базе данных.

В зависимости от того, хотим ли мы, чтобы приложение разговаривало в дд / ММ / гг или ММ / дд / гг, например, мы должны изменить региональные настройки пользователя идентификации для приложение COM. (Сейчас у нас есть только один вариант - мерзкий взлом.)

У нас есть строка формата даты, которая используется для форматирования всех выходных дат, и предполагается, что дата

Если бы это была .NET, мы бы использовали DateTime.ParseExact И подальше смеяться. Вызов COM-объекта, написанного на .NET для этой единственной цели, является вариантом. Есть ли другой или лучший вариант, включающий некоторую черную магию вокруг команды Format или длинную многоразовую функцию, которая токенизует дату в зависимости от строки формата и т. Д.?


person crb    schedule 29.06.2009    source источник
comment
Я уверен, что когда я писал этот вопрос, у третьего предложения было окончание.   -  person crb    schedule 23.07.2010


Ответы (8)


Это должно быть близко, хотя он жестко закодирует разделитель как "/" и окна YY лет на 50:

Private Function ParseDate(ByVal DateString As String, _
                           ByVal DatePattern As String) As Date
    'DateString:  i/j/k formatting.
    'DatePattern: i/j/k formatting, each to be:
    '               M or MM for month position.
    '               D or DD for day position.
    '               YY or YYYY for year position, if YY
    '                 then century windowed at 50.
    Dim strStringParts() As String
    Dim strPatternParts() As String
    Dim intPart As Integer, intScore As Integer
    Dim intMonth As Integer, intDay As Integer, intYear As Integer
    Const DELIM As String = "/"
    Const YYWINDOW As Integer = 50

    strStringParts = Split(DateString, DELIM)
    strPatternParts = Split(UCase$(DatePattern), DELIM)
    For intPart = 0 To UBound(strStringParts)
        If intPart > UBound(strPatternParts) Then
            Err.Raise 5, "ParseDate"
        End If
        Select Case strPatternParts(intPart)
            Case "M", "MM"
                intMonth = CInt(strStringParts(intPart))
                intScore = intScore Or &H1
            Case "D", "DD"
                intDay = CInt(strStringParts(intPart))
                intScore = intScore Or &H2
            Case "YY"
                intYear = CInt(strStringParts(intPart))
                If 0 > intYear Or intYear > 99 Then
                    Err.Raise 5, "ParseDate"
                End If
                intYear = intYear + IIf(intYear < YYWINDOW, 2000, 1900)
                intScore = intScore Or &H4
            Case "YYYY"
                intYear = CInt(strStringParts(intPart))
                If 100 > intYear Or intYear > 9999 Then
                    Err.Raise 5, "ParseDate"
                End If
                intScore = intScore Or &H4
            Case Else
                Err.Raise 5, "ParseDate"
        End Select
    Next
    If intScore = &H7 Then
        ParseDate = DateSerial(intYear, intMonth, intDay)
    Else
        Err.Raise 5, "ParseDate"
    End If
End Function

Валидация может быть не идеальной, но должна быть близкой. Он выдает «Недопустимый вызов процедуры или аргумент (Ошибка 5)» при неверных входных данных.

person Bob77    schedule 30.06.2009
comment
+1. Я только читал, не тестировал, но выглядит идеально. Возможно, StackOverflow нужен способ публикации модульных тестов. - person MarkJ; 30.06.2009
comment
@MarkJ: Ха. Иногда я думал то же самое. Кто хочет начать писать SOUnit? ;) - person Mike Spross; 30.06.2009
comment
Я полагаю, что части строки даты могут быть протестированы и на числовые значения. Возможно, другие проверки также будут важны при работе с реальным вводом от пользователей. Удивительно, как добавление ошибок проверяет любой код. - person Bob77; 30.06.2009

Послушайте, нет простого способа сказать это - вы облажались. Если вы принимаете ввод произвольной формы из Интернета, вам придется смириться с тем фактом, что люди во всем мире датируют формат по-разному. Вот почему так много веб-сайтов используют всплывающие календари и тому подобное, чтобы получать информацию от пользователя. Никакой двусмысленности. Что бы вы ни думали, библиотечные процедуры .NET не могут понять намерения вашего пользователя лучше, чем любая другая библиотека.

Fwiw, код, который опубликовал Майк, абсолютно VB6. Я не уверен, что это похоже на VB.NET? Как только вы введете дату / время в переменную Date, вы можете отображать ее, как хотите, с помощью Format (). Это легкая часть.

Я настоятельно рекомендую вам либо A) найти способ однозначно собрать ваши данные, либо B) сообщить вашим пользователям, какой формат вы ожидаете, и жить с тем, что они вводят. С учетом всего сказанного, возможно ли, что я неправильно понял вопрос, а вы действительно знаете, в каком формате пользователь предоставляет данные? (Потому что, если это так, мне действительно трудно понять, в чем проблема с его интерпретацией в ClassicVB - извините.)

person Karl E. Peterson    schedule 30.06.2009

DateAdd принимает большое количество входов и выходов в правильном формате.

ThisLine =  "Tuesday, September 04, 2012 2:02 PM"

i = InStr(ThisLine, ",")  ' get rid of the leading day

If i > 0 Then
     TempResult = Trim(Right$(ThisLine, Len(ThisLine) - i))
end if

TempResult = DateAdd("s", 0, TempResult)
person Old Dog Learning    schedule 28.09.2012

В комментариях к одному из других ответов вы упоминаете, что вводите информацию из Интернета.

В этом случае вы можете управлять форматом отправляемой даты, ограничивая ввод данных пользователем выпадающими списками ‹select›. Сделайте список в поле месяца краткими или длинными названиями месяцев январь / январь, февраль / февраль и т. Д., А затем создайте строку даты в формате «1 января 2009 года».

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

person Gareth Simpson    schedule 30.06.2009

Вы можете использовать встроенную функцию Format, чтобы сделать это за вас.

Вот простой тест, чтобы подтвердить это:

Public Sub TestDateParsing()

   'On my computer, the date format is U.S. (mm/dd/yyyy)'
   'This test creates a date string in dd/mm/yyyy format to'
   'simulate user input in a different format'

    Const TEST_DATE As Date = #6/1/2009#

    Dim inputDate As String
    inputDate = Format(TEST_DATE, "dd/mm/yyyy")
    'inputDate is "1/6/2009" (June 1 in dd/mm/yyyy format)'

    Debug.Print Format(inputDate, "dd/mm/yyyy")
    'It`s magic! The above line will print 6/1/2009'
    'which is the correct format for my Regional Settings'

End Sub

Это может показаться волшебством, но это не так. Он использует то, как функция Format работает в сочетании с текущими региональными настройками.

Например, предположим, что ваши региональные настройки настроены на использование формата "mm/dd/yyyy" для дат.

Теперь вы получаете строку даты от пользователя в формате "dd/mm/yyyy". Если вы Format эту строку даты и укажете Format также использовать "dd/mm/yyy", она поменяет местами части дня и месяца, поскольку в ваших настройках указано, что даты находятся в формате "mm/dd/yyyy".

Другими словами, Format всегда предполагает, что строка даты от пользователя отформатирована в соответствии с вашими текущими региональными настройками (в данном случае "mm/dd/yyyy"), поэтому, когда вы укажете ему форматировать дату с помощью "dd/mm/yyyy", он заставит его поменять местами месяц и дневные части. Если в ваших региональных настройках используется тот же формат, что и для даты, указанной пользователем, этот код все равно будет работать: Format просто вернет дату пользователя без изменений. Еще не запутались? ;)

То же самое произойдет, если ваши региональные настройки установлены на "dd/mm/yyyy", и пользователь отправит дату в формате "mm/dd/yyyy".

Загвоздка в том, что вы должны заранее знать, в каком формате пользователь отправляет даты. Они не могут начать смешивать и сопоставлять форматы даты (да и не должно быть).


РЕДАКТИРОВАТЬ (от MarkJ) - просто чтобы доказать, что код Майка может преобразовывать строку в Date. Майк, пожалуйста, откатитесь или измените это редактирование, если хотите.

Public Sub Test()
  Dim dte As Date
  For dte = #1/1/2009# To #12/31/2009#
    Call TestDateParsing(dte)
  Next dte
End Sub

Public Sub TestDateParsing(ByVal dteIn As Date)

  'On my computer, the date format is U.S. (mm/dd/yyyy)'
  'This test creates a date string in dd/mm/yyyy format to'
  'simulate user input in a different format'

  Dim sExpected As String
  sExpected = Day(dteIn) & " / " & Month(dteIn) & " / " & Year(dteIn)
  Dim inputDate As String
  Dim dte As Date
  inputDate = Format(dteIn, "dd/mm/yyyy")
  dte = Format(inputDate, "dd/mm/yyyy")

  Debug.Assert sExpected = Day(dte) & " / " & Month(dte) & " / " & Year(dte)
  Debug.Print sExpected

End Sub
person Mike Spross    schedule 29.06.2009
comment
Вы правы, в этом нет никакого смысла. :) У меня есть дата в строке, а у меня есть строка формата в строке, и я хочу получить объект DateTime. Кроме того, ваш код больше похож на VB.NET, чем на VB6? - person crb; 30.06.2009
comment
Ага, это сложно объяснить;). Это действительно работает, но будет работать только для вашего конкретного случая использования. К сожалению, более общее решение означало бы развертывание вашей собственной функции DateParseExact в VB6. В VB6 нет встроенного эквивалента DateParseExact. - person Mike Spross; 30.06.2009
comment
Я постараюсь объяснить лучше: если принимающий компьютер использует формат мм / дд / гг, и вы получаете строку в формате дд / мм / гг, то вызов Format с дд / мм / гг для второго параметра приведет к форматированию формата дд / мм / гг в формате мм / дд / гг, потому что формат предполагает, что строка дд / мм / гг на самом деле уже находится в формате мм / дд / гг (это соответствует текущим региональным настройкам), поэтому для преобразования ее в дд / mm / yy, Формат заменит компоненты даты mm и dd, что даст вам результат в формате mm / dd / yyy. Да, я не уверен, что лучше это объясняю;) - person Mike Spross; 30.06.2009
comment
Это действительно работает, хотя я не уверен, почему. Я бы подумал, что ему не нужны числа дней ›12, но это так. Майк. Я отредактировал ваш ответ, добавив измененную версию, которая преобразует строку на текущий момент. Пожалуйста, измените / откатите мою правку, если хотите. - person MarkJ; 30.06.2009
comment
@MarkJ: Он работает с номерами дней ›12, потому что Format не проверяет ввод. Форматирование на самом деле не является функцией форматирования даты как таковой. Это обычная функция форматирования строк, которая знает о форматах даты. - person Mike Spross; 30.06.2009
comment
Работает ли это для местного времени ПК ГГ / ММ / ДД (например, в Южной Африке)? - person Clay Nichols; 14.09.2012

Я не знаю простого решения. Вы можете разделить входную строку на под- строки по разделителю, а затем используйте DateSerial чтобы рекомбинировать номера года, месяца и часа в родной VB6 Дата переменная. Примерно так ниже. Если вам нужно поддерживать много локалей, это может быть сложно (см. ответ Боба). Имейте в виду, что можно использовать DateTime.ParseExact.

sInput = "1/3/71"
Dim splt() As String
splt = Split(sInput, "/")
dte = DateSerial(splt(2) + 1900, splt(1), splt(0))  ' dd/mm/yy'
person MarkJ    schedule 29.06.2009

Другой способ пойти:

Public Enum abDateType
    abMDY
    abDMY
End Enum

Public Function MakeDate(ByVal dateString As String, ByVal dateType As abDateType, Optional delimiter As String = "/") As Date
    Dim strVals() As String
    Dim dtRtnVal As Date
    strVals = Split(dateString, delimiter)
    Select Case dateType
    Case abMDY
        dtRtnVal = DateSerial(strVals(2), strVals(0), strVals(1))
    Case abDMY
        dtRtnVal = DateSerial(strVals(2), strVals(1), strVals(0))
    Case Else
        Err.Raise vbObjectError, , "Unexpected date format."
    End Select
    MakeDate = dtRtnVal
End Function
person Oorang    schedule 30.06.2009

Вы должны поговорить с переменной даты (а не с форматированной строкой), а затем отобразить форматированное значение в форме, используя региональные настройки пользователя, или принять ввод от пользователя в переменную даты.

person Eduardo Molteni    schedule 29.06.2009
comment
Мы принимаем данные от пользователя через Интернет. - person crb; 29.06.2009