Jump to content

Recommended Posts

Hi Team,

I needed a routine that would tell me if either of two dates from a database table were valid before being tested for other characteristics.

I came up with the following..

function AreValidDates(const DateString1, DateString2: string): Boolean;
var
  DT: TDateTime; // unused date time value
begin
  //
  Result := TryStrToDate(DateString1, DT);
  //
  if Result then
    Result := TryStrToDate(DateString2, DT);
  //
end;

The Function is called by "if AreValidDates(MyTable.FieldByName('Date1').AsString, MyTable.FieldByName('Date2').AsString) then..."

The function is used in many places in my Apps.

Is there a better/more efficient way to do this?

 

Regards & TIA,

Ian

Share this post


Link to post
Guest

First, the best validator for your dates is the Date type field where they reside. Otherwise, the field would not accept such dates or generate a value of "null".

 

Second, I would recommend that you check your IDE's HELP SYSTEM for support for DateTime. You could find an interesting answer to your question, since the best for one would not necessarily be the best for another.

 

A well-planned database would have tables with fields suitable for native types, and, in a more general case, most likely, you would use other types to archive data, the "string" type being one of the most used. However, a type prone to result in errors, if not properly handled on the "Client" side.
You better detail your programming environment: IDE, Database, how the dates are archived, there is some way of validation for such archiving, etc ... more information pertinent to what you want to use on your system.

 

In a more conservative case, I would advise you, first, to emphasize the care in data entry in your tables. That is, if a field needs to have a date (or date-and-time), then this field must in no way accept a null date, for example. This way, you are already guaranteeing 99.99% of the success in your task, the rest, 0.01% I would leave due to some bug in the system. What is not uncommon!

 

I think that, "TryStrToDate( 'Date Time to test', DateTimeValid_IfSomeError)" already it's great to valid any datetime value!

 

hug

Share this post


Link to post

As you suggest, one solution is not necessarily the answer for all circumstances.

 

I had considered various options and this was what I considered an appropriate solution. 😉

I had two considerations..

1.  Are either of the date fields NULL; and

2.  If they weren't, are they properly constituted date(s).

This mechanism gave me coverage for both.

It is a little of 'Belts & Braces' as the date entries are via TDateTimePickers.

From here, if all good, various 'validity/comparison' checks are done, if they fail the User is sent back to correct the date(s) as appropriate.

Thank you for your input.

 

Regards,

Ian

Share this post


Link to post
Guest

you're wellcome!

NOTE: if the entry is verifyed, on the "out" it should be good!

Share this post


Link to post
Guest
6 hours ago, Ian Branch said:

Is there a better/more efficient way to do this?

I would suggest a helper here, so your "if ..." will be this clean

if MyTable.AreValidDates('Date1', 'Date2') then

But if you are talking about performance, then i would to depend on SQL and the DB engine itself to validate and invalidate dates.

Share this post


Link to post

Hi Kas,

Now that's an intriguing idea. 

Perhaps not for this case but I would be interested in how it is done.

Can you point me to appropriate material?  A Tutorial or similar..

 

Regards & TIA,

Ian

Share this post


Link to post
Guest
23 minutes ago, Ian Branch said:

Can you point me to appropriate material?

Start here to see the power and usefulness of the helpers 

http://docwiki.embarcadero.com/RADStudio/Sydney/en/Class_and_Record_Helpers_(Delphi)

 

and here what it will look like

type
  TUniTableHelper = class helper for TUniTable
    function AreValidDBDates(const DateString1, DateString2: string): Boolean;
  end;

{ TUniTableHelper }

// Parameters are FieldName in this case
function TUniTableHelper.AreValidDBDates(const DateString1, DateString2: string): Boolean;
var
  DT1, DT2: TDateTime;
begin
  Result := TryStrToDate(FieldByName(DateString1).AsString, DT1) and TryStrToDate(FieldByName(DateString2).AsString, DT2);
  if Result then
  begin
    // Do your extra checks, like DT2 should be after DT1, the time span between them should be 5 years at max .. etc
  end;
end;

// to be used like this
  if MyTable.AreValidDBDates('Date1', 'Date2') then
  begin

  end;

 

Share this post


Link to post

Now that's neat.

I did have a look at that link but my knowledge depth wasn't up to it and I got confused. 😞

I can follow your example though and can see how it could be extended/used in other areas.

 

Regards & Tks,

Ian

Share this post


Link to post
Guest

RAD have a function to validate a String transformed in TDateTime:

 

http://docwiki.embarcadero.com/Libraries/Sydney/en/System.DateUtils.IsValidDate

 

or using my sample below, you can decode a string transformed in TDateTime and verify Year, Month and Day, if any value = 0, then the date is "invalid", the time can be 0 = then we have 0hrs

 

another thing, verifying the Millisecond TOTAL in a DateTime, if = 0 or 0.0 (using Double function) then we have a DateTime invalid too!

Edited by Guest

Share this post


Link to post

Why are these dates stored as strings in the first place? If they are in a database they should be in a date field. If they were entered with a date picker, they should be TDate or TDateTime variables.

 

If you want to store them as strings in a database for whatever reason, store them in a standard format: ISO 8601, and document that format. Don't rely on your users having Windows configured for any particular date format, you will find somebody who didn't, especially if your program is used in more than one country.

 

And keep in mind, that date formats can be ambiguous: 01/12/15 can be 12 January 1915 or 12 January 2015 (American format), 1 December 2015 (British format), 15 December 2001 (some MSSQL format that looks like they got ISO 8601 wing). The only one I haven't seen so far is using the middle term for the year, but I'm sure some idiot will come up with that too.

 

TryStringToDate by default uses the Windows settings, so that's out in this case.

 

I wrote my own conversion and checking code for ISO 8601. It's in my dzlib if you are interested.

Edited by dummzeuch
  • Like 2

Share this post


Link to post

+1 to the post above.

Storing dates as strings is bad idea in most cases. Just don't do it. If you really REALLY have to do it, then use carved in stone format that won't depend on current locale settings. And don't forget the timestamps could contain timezones

  • Like 2

Share this post


Link to post

Hi Thomas,

Thank you for your input.

They are stored as Dates.

My original code at the start of this thread was simply where I ended up after considering some alternatives.

I have now implemented the Helper per Kas' suggestion.  Something new I have learnt today. 🙂

I will have a look in dzLib, I have it here.

 

Ian

Share this post


Link to post
Guest
13 minutes ago, Ian Branch said:

Something new I have learnt today. 🙂

Somehow i can feel your excitement and happiness with the use of helpers, and i miss that feeling very much !

 

Last time Embarcadero tried to introduce something similar in excitement, they reviewed FreeAndNil.

Share this post


Link to post

Hi Team,

Just to close this one off.

I revisited all the various Date data entry points, 7 different dates in all, and confirmed they can only be either Null or a valid date.

Then using my new Helper knowledge I created the following Helper based on Kas' suggestion..

{ TQueryHelper }
// Parameters are FieldNames in this case
function TQueryHelper.AreDBDatesNull(const DateString1, DateString2: string): Boolean;
begin
  //
  Result := (not FieldByName(DateString1).IsNull) and (not FieldByName(DateString2).IsNull);
  //
end;

//
//  Implemented by..
    if MyQuery.AreDBDatesNull('Date1', 'Date2') then .....

Simpler, and testing the data fields directly rather than 'converting' them.

 

Thank you all for your input & contributions.

Have a Great 2021.

 

Regards,

Ian

  • Like 1

Share this post


Link to post
9 hours ago, Fr0sT.Brutal said:

Storing dates as strings is bad idea in most cases

Time without time zones are like numbers without units. Like the numbers on the street. (30) I always interpret them as weight limits.

Share this post


Link to post
Guest

maybe can be used:

implementation

{$R *.dfm}

uses
  System.DateUtils;

var
  lMyYear       : word = 0;
  lMyMonth      : word = 0;
  lMyDay        : word = 0;
  lMyHour       : word = 0;
  lMyMinute     : word = 0;
  lMySecond     : word = 0;
  lMyMilliSecond: word = 0;

procedure prcZeringValues;
begin
  lMyYear        := 0;
  lMyMonth       := 0;
  lMyDay         := 0;
  lMyHour        := 0;
  lMyMinute      := 0;
  lMySecond      := 0;
  lMyMilliSecond := 0;
end;

function fncTestMyDateTimeInString(lDTinString: string): boolean;
begin
  result := false;
  //
  try
    prcZeringValues;
    //
    // StrToDateTimeDef(lDTinString, now); // always will be valid!
    //
    // DecodeDateTime() for test each part of date in another part of your app, as below in my code!
    // else, use just  "StrToDateTime(lDTinString)"
    //
    DecodeDateTime(StrToDateTime(lDTinString), lMyYear, lMyMonth, lMyDay, lMyHour, lMyMinute, lMySecond, lMyMinute);
    //
    result := true; // for example: result := (lMyYear > 0) ... etc - you can compare another values
  except
    //
    // comment here for have a "False" return... Else, you'll have a "exception" and nothing of return value!
    //
    // if you dont like this way! Sorry, find another!
    //
    // raise; // or rasie Exception.Create('Invalid convertion: string to TDateTime');
  end;
end;

procedure TForm1.btn_Testing_String_as_DateTime_ValidClick(Sender: TObject);
var
  lMyDateTimeInString: string;
begin
  lMyDateTimeInString := '';
  //
  if fncTestMyDateTimeInString(lMyDateTimeInString) then
    ShowMessage('"lMyDateTimeInString" is a valid DateTime value')
  else
    ShowMessage('"lMyDateTimeInString" is NOT a valid DateTime value');
end;

procedure TForm1.Button1Click(Sender: TObject);
var
  lMyDateTimeInString: string;
begin
  lMyDateTimeInString := '';
  //
  fncTestMyDateTimeInString(lMyDateTimeInString); // provoking verification...
  //
  if System.DateUtils.IsValidDateTime(lMyYear, lMyMonth, lMyDay, lMyHour, lMyMinute, lMySecond, lMyMinute) then
    ShowMessage('"lMyDateTimeInString" is a valid DateTime value')
  else
    ShowMessage('"lMyDateTimeInString" is NOT a valid DateTime value');
end;

 

hug

Edited by Guest

Share this post


Link to post
15 hours ago, Attila Kovacs said:

Time without time zones are like numbers without units. Like the numbers on the street. (30) I always interpret them as weight limits.

Generally it's true but timezone could be "global" per-app / per-DB (ideal case is UTC)

11 hours ago, emailx45 said:

function fncTestMyDateTimeInString(lDTinString: string): boolean;

I still can't get what you're checking. Global variables are bad practice anyway and in your case they appear useless. If StrToDateTime haven't risen any exception, you're already sure the date string is valid.

 

Edited by Fr0sT.Brutal
add
  • Like 1

Share this post


Link to post
Guest
5 hours ago, Fr0sT.Brutal said:

Global variables are bad practice anyway and in your case they appear useless.

IF YOU DONT LIKE, DONT USE! PERIOD!

Share this post


Link to post
3 hours ago, emailx45 said:

IF YOU DONT LIKE, DONT USE! PERIOD!

But Frost is right about global variables. I would change this to... use them ONLY if you can not solve your issue without them.

 

Edit: wtf spelling...?!

Edited by aehimself

Share this post


Link to post
Guest

PERIOD! YOU RE RIGHT. PERIOD!

 

Please use the car-brake only if strictly necessary. Otherwise, use your life insurance!

:classic_cheerleader:

Edited by Guest

Share this post


Link to post

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

×