Skip to content

Converting any string to date and considering "no date"

Converting strings to dates never seemed to be straightforward. First, specifying the source for all the different date component was difficult. Second, if the conversion is unsuccessful and "no date" has been defined, it could not be expressed with TDate or TDateTime.

Possible solutions for both issues will be presented in this article.

Format specifiers

You define the source of date components using format specifiers. Think of the like placeholders in your string. Delphi initializes the format settings depending on the system your application runs on. If you run on a US-based system, the short date format string will be initialized with mm/dd/yy. In Germany, it will be dd.mm.yy.

Warning

Whenever working with dates, you should try using TDateTime as long as possible. String is used quite often, but it is definitely not the best solution. String should only be used if a date is persisted in a file, for example, or output on screen. There is no need for internal storage of dates as String. The same holds true for databases. They also have dedicated data types for date/time values.

Example

Let us assume we have a string with the following numbers 230623. This is supposed to be interpret as a date. Each part of the consists of two digits:

  • Year
  • Month
  • Day

In order to convert this safely, we can use the following code snippet:

var
  LString: String;
  LPaidOn: TDateTime;
  LFormat: TFormatSettings;

begin
  // initialize format
  LFormat.ShortDateFormat := 'yymmdd';

  // try to convert...
  if TryStrToDate( LString, LPaidOn, LFormat ) then
  begin
    Result.PaidOn := LPaidOn;
  end
  else
  begin
    // what should we assign?
  end;

TryStrToDate will return true if the conversion was successful. The date will be stored in LPaidOn. Otherwise, LPaidOn will remain unchanged.

There is one big issue. How can we assign a value to our variable if the conversion was not successful and we want to indicate that the variable "has no value"? A lot of implementations use a dedicated date that they define as "No value" (for example, a date far in the past or future). However, there is a far better solution!

Introducing Nullable data types

In database programming, we can assign "no value" to a field. Sadly, Object Pascal so far has no "official" support for this in the language. However, 3rd party libraries come to the rescue.

Warning

Sadly, when using a certain framework, you are mostly stuck with that specific implementation for Nullable Types. For example, if you use TMS Aurelius or TMS XData, you cannot use the Nullable Type implementation from Spring4D with these. You have to use the Nullable Type from TMS BCL in that case.

In this case, I use the TMS Business Core Library (BCL).

Import Bcl.Types.Nullable.pas:

uses
  System.Generics.Collections
  , Bcl.Types.Nullable
Declare your date time value as Nullable. This can also be a field variable of your class. It is not restricted to local variables, for example.
FPaidOn: Nullable<TDateTime>;
Assign values as before or set it to "no value". This is done in the BCL using the variable SNull.
  if TryStrToDate( LString, LPaidOn, LFormat ) then
  begin
    FPaidOn := LPaidOn;
  end
  else
  begin
    FPaidOn := SNull;
  end;

In order to query if a value is assigned or not, you can use IsNull.

  if FPaidOn.IsNull then 
  begin
    // value is null
  end
  else
  begin
    // use value
  end;

You might be hesitant to use this fearing it might break IDE functionality like Code Completion. However, it works perfectly fine as at its core the Nullable implementation uses nothing that would confuse the IDE.

Screenshot

List of format specifiers

I always struggle to remember which specifiers can be used with TFormatSettings. Here is the table based on the Embarcadero documentation at https://docwiki.embarcadero.com/Libraries/Alexandria/en/System.SysUtils.FormatDateTime.

Specifier Displays
c Displays the date using the format given by the ShortDateFormat global variable, followed by the time using the format given by the LongTimeFormat global variable. The time is not displayed if the date-time value indicates midnight precisely.
d Displays the day as a number without a leading zero (1-31).
dd Displays the day as a number with a leading zero (01-31).
ddd Displays the day as an abbreviation (Sun-Sat) using the strings given by the ShortDayNames global variable.
dddd Displays the day as a full name (Sunday-Saturday) using the strings given by the LongDayNames global variable.
ddddd Displays the date using the format given by the ShortDateFormat global variable.
dddddd Displays the date using the format given by the LongDateFormat global variable.
e (Windows only) Displays the year in the current period/era as a number without a leading zero (Japanese, Korean, and Taiwanese locales only).
ee (Windows only) Displays the year in the current period/era as a number with a leading zero (Japanese, Korean, and Taiwanese locales only).
g (Windows only) Displays the period/era as an abbreviation (Japanese and Taiwanese locales only).
gg (Windows only) Displays the period/era as a full name (Japanese and Taiwanese locales only).
m Displays the month as a number without a leading zero (1-12). If the m specifier immediately follows an h or hh specifier, the minute rather than the month is displayed.
mm Displays the month as a number with a leading zero (01-12). If the mm specifier immediately follows an h or hh specifier, the minute rather than the month is displayed.
mmm Displays the month as an abbreviation (Jan-Dec) using the strings given by the ShortMonthNames global variable.
mmmm Displays the month as a full name (January-December) using the strings given by the LongMonthNames global variable.
yy Displays the year as a two-digit number (00-99).
yyyy Displays the year as a four-digit number (0000-9999).
h Displays the hour without a leading zero (0-23).
hh Displays the hour with a leading zero (00-23).
n Displays the minute without a leading zero (0-59).
nn Displays the minute with a leading zero (00-59).
s Displays the second without a leading zero (0-59).
ss Displays the second with a leading zero (00-59).
z Displays the millisecond without a leading zero (0-999).
zzz Displays the millisecond with a leading zero (000-999).
t Displays the time using the format given by the ShortTimeFormat global variable.
tt Displays the time using the format given by the LongTimeFormat global variable.
am/pm Uses the 12-hour clock for the preceding h or hh specifier, and displays 'am' for any hour before noon, and 'pm' for any hour after noon. The am/pm specifier can use lower, upper, or mixed case, and the result is displayed accordingly.
a/p Uses the 12-hour clock for the preceding h or hh specifier, and displays 'a' for any hour before noon, and 'p' for any hour after noon. The a/p specifier can use lower, upper, or mixed case, and the result is displayed accordingly.
ampm Uses the 12-hour clock for the preceding h or hh specifier, and displays the contents of the TimeAMString global variable for any hour before noon, and the contents of the TimePMString global variable for any hour after noon.
/ Displays the date separator character given by the DateSeparator global variable.
: Displays the time separator character given by the TimeSeparator global variable.
'xx'/"xx" Characters enclosed in single or double quotation marks are displayed as such, and do not affect formatting.