TZDB — time zone database

In the previous post I offered some details on the new TTimeZone class in Delphi XE. In Today’s post I will present a new project that is based on TTimeZone and offers support for most known (if not all) time zones in wild.

All the time zone information used by the project is provided by tz database project. This database is used in quite a few systems out there including BSDs, GNU LibC, Java, Oracle, MacOS, Linux and etc.

TZDB is made of three modules:

  1. TZUpdate, a small command line utility that takes as input the path to the tz rule files and generates a pascal source file filled with pre-packed constants.
  2. TZDB, a package that contain only one unit: TimeZones. This unit implements a new class, TBundledTimeZone, derived from TTimeZone. This package is basically all you need.
  3. TZVisualizer, a small VCL project that extracts time zone information from a given time zone. It writes all time intervals in a given year, their offset and etc.

First, I would like to detail a bit more about TimeZones unit. It exports only only class but internally it implements all the required logic required to process time zone information generated by the TZUpdate tool. TZUpdate is normally configured to emit a TimeZone.inc file (included by TimeZone.pas unit). What this meas is that all this information gets compiled into your application if you are including this unit directly (and increases the application size by about 700Kb right now). But that was one of my choices — allow anyone to copy/paste two files into their project and have the full database at their hands. This is not the recommended way though, as I will explain later on.

Why use TZDB?

Not many people will find this database useful. The normal use cases are for application that actually need to do time conversions between multiple time zones (usually server side). Or maybe you want to implement a “world clock”-like application.

In some cases you might even find it useful as a replacement to the TTimeZone.Local implementation. These cases are related to the fact that Windows will not offer you time zone information for years that passed (in most cases DST rules are different from one year to another) thus making time calculations with past date/times error-prone. TZ database stores all known periods and DST rules that were used in a region, thus you can be sure that any time related calculation is correct for the given year.

Timezone updates

A bitter aspect of using a bundled time zone database is the fact that DST rules change between years. Hopefully not that much, but they do change. Operating system cope with that by issuing updates. In case of a 3rd party application, the vendor should also issue update to its application when some DST rules have change (otherwise you risk having outdated data and rick big date/time problems).

For one, I strongly recommend that applications rely on TTimeZone.Local if they only need local time zone information. TTimeZone.Local uses OS provided information thus is safe when updates intervene.

If you need TZDB in your application, you should rely on packages and specifically – run time packages. This means that you include TZDB as a package that your application relies upon and mark this package as run time. This ensures that the database is external in a BPL and that BPL can be simply replaced within an update to your application.

An example!

uses
  SysUtils, DateUtils, TimeZones;

var
  LSydneyTZ: TTimeZone;
  LLocal, LUtc,
    LSydneyLocal: TDateTime;

begin
  { Create a new time zone for Sydney, Australia }
  LSydneyTZ := TBundledTimeZone.Create('Australia/Sydney');

  { Get the local time for my region/computer }
  LLocal := Now;

  { Convert this to UTC }
  LUtc := TTimeZone.Local.ToUniversalTime(LLocal);

  { And now convert the UTC to Sydney's local time }
  LSydneyLocal := LSydneyTZ.ToLocalTime(LUtc);

  { Write results }
  WriteLn('Current local time is: ', DateTimeToStr(LLocal));
  WriteLn('Current Sydney time is: ', DateTimeToStr(LSydneyLocal));

  { ... }
  LSydneyTZ.Free;
end.

Visualizing the time zones

Disclaimer: I am very bad with drawing, be it pen, mouse or code :) That’s why I chose to go with something I can do better: fonts, color and text.

The following picture shows information about the local time zone on my machine (by using TTimeZone.Local):

The application allows visualizing bundled time zones (by selecting one from the combo box):

For now I do not have a package/download for TZDB. But you can checkout it from here. Note that there might be some bugs lurking around, so be careful.

TimeZone in XE

Since the inclusion of TTimeZone in Delphi XE’s RTL, I was trying to write a small introductory article into how to get started with the class but never got the time. Now, I got some free time on my hands, so here it goes:

The new class provides support for:

  • Converting date/time values representing local time (wall clock) to universal time (UTC) and back.
  • Obtaining the abbreviation and display names that can be attached to a date/time string to properly identify it in its time zone.
  • Obtaining the UTC offset of a given local time.
  • Checking the “type” of a local time (ex. Standard, Daylight, Ambiguous or Invalid).
  • Extensible for new implementations (other than the standard one provided by the RTL).

The first thing you might notice is that TTimeZone is declared abstract. The intention was to give the class only the necessary conversion methods and related code while forcing the derived classes to provide the mechanics to retrieve the actual date/time information. Now, since TTimeZone is itself abstract, that means that there is a derived class somewhere that plugs in the necessary plumbing. Indeed there is, and it’s declared and implemented in the implementation section of DateUtils unit. This class provides support for the “local time zone” by querying the current operating system for time zone related details. To access this local time zone you need to use the TTimeZone.Local property.

The rest of the operations are quite simple to understand, but in any case here’s a small list of provided functionality:

  • GetUtcOffset returns the UTC offset of a given local date/time.
  • ToUniversalTime converts a local date/time to an universal date/time.
  • ToLocalTime converts an universal date/time to a local date/time.
  • GetDisplayName obtainins a display name for the given local date/time (for example “GTB Standard Time” if the local date/time was in the standard period of the year and “GTB Daylight Time” if the local date/time was in the summer period).
  • GetAbbreviation obtainins a “GMT+XX” string also based on DST rules.
  • GetLocalTimeType obtains an enumeration that specifies the type of the provided local date/time (ex. Standard, Daylight, Ambiguous or Invalid).
  • IsStandardTime checks if the local date/time is in standard period.
  • IsInvalidTime checks if the local date/time is invalid.
  • IsAmbiguousTime checks if the local date/time is ambiguous.
  • IsDaylightTime checks if the local date/time is in daylight period.
  • ID returns an ID of the time zone (useful to uniquely identify a time zone).
  • DisplayName specifies the display name of the time zone for current date. The value of this property can change based on the period the computer time is in.
  • Abbreviation specifies the abbreviation of the time zone for current date. The value of this property can change based on the period the computer time is in.
  • UtcOffset specifies the UTC offset of the time zone for current date. The value of this property can change based on the period the computer time is in.

You might notice that some methods also accept an optional “ForceDaylight” parameter. This parameter is only used by the time zone class when the provided local time is ambiguous (usually an hour at the end of the DST period when you need to get your clock an hour back). Since that period may be treated as being daylight or standard, the class allows you to control which one is it.

The other thing to note is that TTimeZone checks for invalid local date/times. Usually when switching between standard and daylight hours, there is an hour that is “missing” (you need to adjust your wall clock to one hour ahead). For example, 03:00 AM is adjusted to 04:00 AM. This means that the hour between 03:00 AM and 04:00 AM is basically … invalid — there is no corresponding universal time for that hour. If a date/time value situated in that period is passed to TTimeZone an exception will be raised. Normally you can use IsInvalidTime to detect this problem ahead of time.

What would be life without examples?

{$APPTYPE CONSOLE}
uses
  SysUtils, DateUtils;

begin
  { Writes the current universal time }
  WriteLn('Current universal time is:',
  DateTimeToStr(TTimeZone.Local.ToUniversalTime(Now)));

  { Write the current date/time along with the abbreviation }
  WriteLn(
    Format('%s (%s)', [DateTimeToStr(Now),
      TTimeZone.Local.GetAbbreviation(Now, true)])
  );

  { Simply write the type of the current time }
  case TTimeZone.Local.GetLocalTimeType(Now) of
    lttStandard:
      WriteLn('Current time is Standard.');

    lttDaylight:
      WriteLn('Current time is Daylight.');

    lttInvalid:
      WriteLn('This should never happen when the time is coming from the system.');

    lttAmbiguous:
      WriteLn('Current time is ambiguous.');
  end;
end.

Why should you start using it? Well for a number of reasons:

  • It is there. An unified method of working with time zones. Some parts of the RTL started using it already. And it is pretty simple.
  • It is cross-platform. Your application need not depend on Windows API anymore for this sort of things.
  • For Windows it uses either GetTimeZoneInformation or the newer GetTimeZoneInformationForYear depending on what is available.
  • It uses an internal caching mechanic to minimize the number of calls to the underlying OS.
  • It is extensible. A well designed application can take advantage of that (more about that in the next post).
  • And finally it gives better control over how you treat ambiguity or invalid date/times.

Well, that is all I have to say about TTimeZone for now, hope you found this useful. In the next post I will continue on the same subject and introduce a time zone class that obtains its information from a bundled database rather than OS itself.

DeHL 0.8.3

DeHL

I will be brief as usual — version 0.8.3 of DeHL is out. The downloads can be found on this page and changelog on this page. This release “fixes” some of the things I wanted fixed for a long time, so it seemed this is the perfect moment for this to happen. A new unit is introduced — DeHL.Tuples — which brings seven generic Tuple<…> types. I have also finished moving away from Integer and Cardinal to NativeInt and NativeUInt through all DeHL.

Breaking changes are:

  1. DeHL.Converter was renamed to DeHL.Conversion. This name change was done mostly to reflect the nature of the unit. It does not contain one simple class anymore. Now there is a fully featured conversion system.
  2. TBinaryTree<T> (in DeHL.Collections.BinaryTree) is no more. It was written way in the beginnings of the project and was buggy, incomplete and utterly useless.
  3. And the most visible and breaking of all changes is the removal of TKeyValuePair<TKey, TValue> (in DeHL.KeyValuePair). It was replaced with KVPair<TKey, TValue> (in DeHL.Tuples). The easiet way to get over this change is to find and replace all TKeyValuePair instances with KVPair and all DeHL.KeyValuePair uses with DeHL.Tuples.

Now, obviously an example using Tuples:

uses
  SysUtils, DeHL.Tuples;

{ If you are lazy and do not wish to declare a new record type
  to be used as result. Use Tuple<..> do to that. }
function GiveMeSomeData(): Tuple<String, Integer, Integer>;
begin
  { ... Do some processing ... }
  Result := Tuple.Create('Some data', 100, -99);
end;

var
  LResult: Tuple<String, Integer, Integer>;
begin
  { Obtain the result }
  LResult := GiveMeSomeData();

  { And write the results to the console }
  with LResult do
    WriteLn('Something was done with result: ', Value1, ', ', 
      Value2, ', ', Value3, '!');
end.

The new conversion engine handles most of the possible conversions and also allows registering custom ones:

uses
  SysUtils,
  DeHL.Conversion,
  DeHL.Collections.List;

type
  { Declare some type that cannot be converted into integer directly.
    We're making up an "int". }
  TRecordInt = packed record
    FValueAsString: string;
    FSign: Boolean;

    constructor Create(const AInt: Integer);
  end;

{ TRecordInt }

constructor TRecordInt.Create(const AInt: Integer);
begin
  { Decompose an int }
  FValueAsString := IntToStr(Abs(AInt));
  FSign := (AInt < 0);
end;

var
  LInputList: TList<TRecordInt>;
  S: String;
  I: Integer;
begin
  { Create a list of TRecordInt }
  LInputList := TList<TRecordInt>.Create();

  { Fill it with some random values (positive and negative) }
  for I := 0 to 10 do
    LInputList.Add(TRecordInt.Create(Random(MaxInt) - (MaxInt div 2)));

  { Now comes the interesting part ... register a custom converter
    from TRecordInt to Integer }
  TConverter<TRecordInt, Integer>.Method :=
    function(const AIn: TRecordInt; out AOut: Integer): Boolean
    begin
      { Convert the TRecordInt back to an integer }
      Result := TryStrToInt(AIn.FValueAsString, AOut);
      if Result and AIn.FSign then
        AOut := -AOut;
    end;

  { Now print the values to the console. Convert them from TRecordInt to
    Integer then to String }
  for S in LInputList.Op.Cast<Integer>.Op.Cast<String> do
    WriteLn(S);
end.

TConverter is also smart enough to figure out that “type MyInt = type Integer” is actually equivalent to Integer. If there is no explicit custom conversion method registered for it the converter for the standard type will be selected is possible. In the worst case, when TConverter cannot convert directly between the given types, it falls back to Variant conversion (using TType.TryConvertToVariant and TType.TryConvertFromVariant) which all types registered with DeHL’s type system, if possible, should implement.

Well, that’s all for today,
Have Fun!

DeHL 0.8.2 is out

DeHL

I’ve just released the version 0.8.2 of DeHL. The downloads can be found on this page and changelog on this page.
Again, this is a minor release with a few bugs fixed and a new feature: TString (as asked in this comment).
As you might have guessed already, TString is a wrapper record modeled on .NET’s System.String class. Unfortunately I was unable to use most of the RTL’s string functionality so the “wrapper” grew quite a bit from my original expectations.

But … enough talk, here are some usage scenarios that someone may find useful:

var
  LStr: TString;
begin
  { Overloaded operators and a special function "U" }
  LStr := U('Hello World for the ') + 10 + 'th time!';

  { Do some random operations }
  if (LStr.ToUpper().Contains('HELLO')) and
     (LStr.Contains('HeLLo', scLocaleIgnoreCase)) then
  WriteLn(LStr.ToString);

  { Now let's select all the distinct chars from the string }
  WriteLn(
    LStr.Concat(LStr.AsCollection.Distinct.Op.Cast<string>).ToString
  );
end.

TString overloads all sane operators: Equality, Inequality, Implicit conversions, Addition, Subtraction and offers functions to convert to and from UTF8 and UCS4 (via RTL of course). I also need to iron a few things about about Enex integration for the next minor release.

The other small improvement that I added relates to the collection package. All simple collections (not the Key/Value pair ones) implement a sort of “where T is the_class, select it as such” operation. Check out this example:

var
  LList: TList<TObject>;
  LBuilder: TStringBuilder;
  LObject: TObject;
begin
  LList := TList<TObject>.Create;

  { Populate the list with some random objects }
  LList.Add(TInterfacedObject.Create);
  LList.Add(TStringBuilder.Create);
  LList.Add(TStringBuilder.Create);
  LList.Add(TObject.Create);

  { Now select the objects we're interested in (string builders) }
  for LBuilder in LList.Op.Select<TStringBuilder> do
    WriteLn(LBuilder.ClassName); // Do stuff

  { Or select everything (not actually required - an example) }
  for LObject in LList.Op.Select<TObject> do
    WriteLn(LObject.ClassName); // Do stuff
end.

If it’s still not clear what this operations does, let me explain. It basically consists of two operations: Where and Select. First, each object is checked to be of a given class and then this object is cast to that class so you can iterate directly using a FOR .. IN loop only over the objects you want to. Of course doing that for TObject makes no sense (as in example) … but well … that was an example.

Well, that’s all for today,
Have Fun!

DeHL 0.8.1

DeHL

I’ve just released the version 0.8.1 of DeHL. The downloads can be found on this page and changelog on this page.

This is mostly a fix release with only one major feature – Cloning (in DeHL.Cloning). The rest of the changes are either bug fixes or janitorial changes.

Have Fun!

Replication

I must say, I am still pretty exited by the extended RTTI in Delphi 2010. It makes life so much easier in many circumstances. “Cloning” (I call it replication) is one of those. Say hello to TReplicator<T> (in DeHL.Replication). It can take any type and create an exact copy (including deep cloning for complex types).

The following example should pretty much explain what and how:

uses
  SysUtils,
  DeHL.Replication,
  DeHL.Collections.List;

type
  { Define a test class }
  TData = class
    { Pointer to self }
    FSelf: TObject;

    { A chunck of bytes }
    FSomeData: TBytes;

    { Some dummy string }
    FName: string;

    { An internal pointer. Marked as non-replicable -- will not be copied }
    [NonReplicable]
    FInternalPtr: Pointer;

    { This field will be copied by reference. It means that the new
      object will have the reference to the same TList<String> and not
      a copy of it. }
    [ByReference]
    FList: TList<String>;
  end;

var
  LInput, LOutput: TData;
  LReplicator: TReplicator<TData>;
begin
  { Create the input }
  LInput := TData.Create;
  LInput.FSelf := LInput;
  LInput.FSomeData := TBytes.Create(1, 2, 3, 4, 5, 6);
  LInput.FName := 'Some Name';
  LInput.FInternalPtr := Ptr($DEADBABE);
  LInput.FList := TList<String>.Create(['Hello', 'World']);

  { Create the replicator }
  LReplicator := TReplicator<TData>.Create;

  { Create a copy of LInput }
  LReplicator.Replicate(LInput, LOutput);

  { ... }
end.

As you can see we take LInput and create a copy into LOutput. Also note the use of [NonReplicable] and [ByReference] attributes on two fields of TData class. NonReplicable forces the replication process to skip the field and ByReference forces a shallow copy of reference fields (objects, pointers to arrays and dynamic arrays).

For the ease of use, a TCloneableObject is defined in the same unit (DeHL.Replication). TCloneableObject provides a method called Clone() which returns a copy of the object:

uses
  SysUtils,
  DeHL.Replication;

type
  { Define a test class }
  TTestMe = class(TCloneableObject)
    { ... }
  end;

var
  LInput, LOutput: TTestMe;
begin
  { ... }
  LOutput := LInput.Clone() as TTestMe;
  { ... }
end.

Note: This is just a preview of a new feature finding it’s way into DeHL. It’s currently only in the SVN repository. As usual, I need to write unit tests and so on to make sure it actually has no bugs (or at leas not that many bugs ;).

Here you go — DeHL 0.8

DeHL

This is going to be a short one. After months of no releases, here it is: DeHL 0.8 (see changelog for the list of changes on this release). As I mentioned previously, this release will only work on Delphi 2010, since the number of changes required to support serialization was quite big.

I must confess, this is the first time in my programming career that I had to interact with serialization (excluding the times when the frameworks do that for you). I had to learn quite a bit and changed my internal design three times.

So, here’s an example how to serialize a TFormatSettings structure into binary format:

var
  LBinFile: TFileStream;
  LBinSerializer: TBinarySerializer<TFormatSettings>;
  LSettings: TFormatSettings;
begin
  { Obtain current thread's format settings }
  GetFormatSettings(GetThreadLocale(), LSettings);

  { Create a serializer and a file stream }
  LBinSerializer := TBinarySerializer<TFormatSettings>.Create();
  LBinFile := TFileStream.Create('dump_obj.bin', fmCreate);

  { Serialize the structure }
  try
    LBinSerializer.Serialize(LSettings, LBinFile);
  finally
    LBinFile.Free;
    LBinSerializer.Free;
  end;
end;

… and here’s an example how to deserialize it:

var
  LBinFile: TFileStream;
  LBinSerializer: TBinarySerializer<TFormatSettings>;
  LSettings: TFormatSettings;
begin
  { Create a serializer and a file stream }
  LBinSerializer := TBinarySerializer<TFormatSettings>.Create();
  LBinFile := TFileStream.Create('dump_obj.bin', fmOpenRead);

  { Deserialize the structure }
  try
    LBinSerializer.Deserialize(LSettings, LBinFile);
  finally
    LBinFile.Free;
    LBinSerializer.Free;
  end;
end;

Note: I have written quite a few unit tests to support the new changes, but most certainly there are hidden bugs. If you find one please report it here.

Other Note: I exhausted my idea jar regarding new features. If you have an idea please do not hesitate to drop me a comment or an email.

Have Fun!

DeHL, Delphi 2010 and Serialization

DeHLA few months have passed and I did not release a new version of DeHL yet. No, it’s not dead. I’ve just been busy with a delicate new feature — Serialization. This post will demonstrate the new capabilities of DeHL it’s advantages and and shortcomings.

But first — since the new releases will focus mostly on serialization and related stuff, I decided to drop Delphi 2009 support. It made no sense to support 2009 for future versions since no essential changes are made to the prior code. You can still use 0.7 release in Delphi 2009.

Back to serialization. The following list describes the changes that went into the new version:

  • In order to support serialization, DeHL’s type system was extended to support Serialize and Deserialize methods. Each type class (that describes a type in Delphi) now knows how to serialize values of the type it manages.
  • A new unit named DeHL.Serialization was added. It contains the base definitions of types used by the type system for serialization.
  • TPointerType, TRecordType<T>, TArrayType<T>, etc. were added for simplified type handling. The old method was a mix-up of Delphi 2009 and Delphi 2010 RTTI specifics (which have some essential differences in my case).
  • All classes can now implement ISerializable interface. The TClassType<T> detects whether this interface is implemented by the object and uses it for serialization (no, reference counting is not touched).
  • DeHL.Serialization.Abstract contains the semi-implementation of a “serializer” and it’s context. It is used by specific serializers.
  • DeHL.Serialization.XML defines the TXMLSerializer<T> which can be used to serialize/deserialize into XML nodes (uses TXMLDocument). Supports it’s own set of attributes (such as XmlRoot, XmlElement, etc.).
  • DeHL.Serialization.Ini defines the TIniSerializer<T> that you can use to serialize/deserialize type into Ini files or registry (through RTL’s TRegIniFile).
  • Most DeHL types (such as Nullable<T>TFixedArray<T>, BigInteger, etc.) provide their own serialization and deserialization methods.
  • All Enex collections (except a few that can’t actually) can be serialized and deserialized. They implement a custom serialization and deserialization technique through ISerializable.

Enough talk, a mandatory example:

type
  [XmlRoot('Testing', 'http://test.namespace.com')]
  TTest = class
    { Pointer to self }
    [XmlElement('PointerToSelf')]
    FSelf: TObject; 
    {A set of format settings }
    FFormatSettings: TFormatSettings;

    { And internal record }
    FInternal: record
      { Force the field to be an attribute of FInternal }
      [XmlAttribute('Value')]
      FOne: Integer;

      { Force this element to have same name but other namespace }
      [XmlElement('Value', 'http://other.namespace.com')]
      FTwo: String;
    end;

    FListOfDoubles: TList<Double>;
  end;
var
  LDocument: IXMLDocument;
  LXMLSerializer: TXMLSerializer<TTest>;
  LOutInst, LInInst: TTest;
begin
  CoInitializeEx(nil, 0);

  { Initialize the test object }
  LOutInst := TTest.Create;
  LOutInst.FSelf := LOutInst;
  GetLocaleFormatSettings(GetThreadLocale(), LOutInst.FFormatSettings);
  LOutInst.FInternal.FOne := 1;
  LOutInst.FInternal.FTwo := '2 - Two';
  LOutInst.FListOfDoubles := TList<Double>.Create();
  LOutInst.FListOfDoubles.Add(0.55);
  LOutInst.FListOfDoubles.Add(0.122);
  LOutInst.FListOfDoubles.Add(122.23);

  { Create the serializer and an XML document }
  LXMLSerializer := TXMLSerializer<TTest>.Create();
  LDocument := TXMLDocument.Create(nil);

  { Set the options }
  LDocument.Active := true;
  LDocument.Options := LDocument.Options + [doNodeAutoIndent];

  { Force fields to elements by default }
  LXMLSerializer.DefaultFieldsToTags := true;

  { Serialize the structure }
  LXMLSerializer.Serialize(LOutInst, LDocument.Node);

  { Serialize the structure }
  LXMLSerializer.Deserialize(LInInst, LDocument.Node);

  { Cleanup }
  LDocument.SaveToFile('c:\test.xml');
  LXMLSerializer.Free;
end.

The XML file generated by this code looks like this (INI looks uglier):

<Testing xmlns="http://test.namespace.com" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:DeHL="http://alex.ciobanu.org/DeHL.Serialization.XML" xmlns:NS1="http://other.namespace.com">
  <PointerToSelf DeHL:ref="Testing"/>
  <FFormatSettings>
    <CurrencyString>$</CurrencyString>
    <CurrencyFormat>0</CurrencyFormat>
    <CurrencyDecimals>2</CurrencyDecimals>
    <DateSeparator>/</DateSeparator>
    <TimeSeparator>:</TimeSeparator>
    <ListSeparator>,</ListSeparator>
    <ShortDateFormat>M/d/yyyy</ShortDateFormat>
    <LongDateFormat>dddd, MMMM dd, yyyy</LongDateFormat>
    <TimeAMString>AM</TimeAMString>
    <TimePMString>PM</TimePMString>
    <ShortTimeFormat>h:mm AMPM</ShortTimeFormat>
    <LongTimeFormat>h:mm:ss AMPM</LongTimeFormat>
    <ShortMonthNames>
      <string>Jan</string>
      <string>Feb</string>
      <string>Mar</string>
      <string>Apr</string>
      <string>May</string>
      <string>Jun</string>
      <string>Jul</string>
      <string>Aug</string>
      <string>Sep</string>
      <string>Oct</string>
      <string>Nov</string>
      <string>Dec</string>
    </ShortMonthNames>
    <LongMonthNames>
      <string>January</string>
      <string>February</string>
      <string>March</string>
      <string>April</string>
      <string>May</string>
      <string>June</string>
      <string>July</string>
      <string>August</string>
      <string>September</string>
      <string>October</string>
      <string>November</string>
      <string>December</string>
    </LongMonthNames>
    <ShortDayNames>
      <string>Sun</string>
      <string>Mon</string>
      <string>Tue</string>
      <string>Wed</string>
      <string>Thu</string>
      <string>Fri</string>
      <string>Sat</string>
    </ShortDayNames>
    <LongDayNames>
      <string>Sunday</string>
      <string>Monday</string>
      <string>Tuesday</string>
      <string>Wednesday</string>
      <string>Thursday</string>
      <string>Friday</string>
      <string>Saturday</string>
    </LongDayNames>
    <ThousandSeparator>,</ThousandSeparator>
    <DecimalSeparator>.</DecimalSeparator>
    <TwoDigitYearCenturyWindow>50</TwoDigitYearCenturyWindow>
    <NegCurrFormat>0</NegCurrFormat>
  </FFormatSettings>
  <FInternal Value="1">
    <NS1:Value>2 - Two</NS1:Value>
  </FInternal>
  <FListOfDoubles>
    <Elements>
      <Double>0.55</Double>
      <Double>0.122</Double>
      <Double>122.23</Double>
    </Elements>
  </FListOfDoubles>
</Testing>

On the first serialized/deserialized value, serializers build up a sort of an internal “object graph” and gathers all information about the data being serialized. The next uses of the same serializer instance yield an 10x performance gain since there is no need to rebuild all the information from scratch. I am still working on more optimizations that could give greater speed boost.

P.S. I can’t show the contents of the deserialized object here so you’ll have to take my word for it.

Note. This is just a preview of what is going on in the trunk. No version is released since I have to iron out the last problems and write the missing unit tests.



TypeInfo workaround

This is going to be a short one. Just wanted to share a simple and elegant work-around for this QC issue:

type
  TypeOf<T> = record
    class function TypeInfo: PTypeInfo; static;
    class function Name: string; static;
    class function Kind: TTypeKind; static;
  end;

{ TypeOf<T> }

class function TypeOf<T>.Kind: TTypeKind;
var
  LTypeInfo: PTypeInfo;
begin
  LTypeInfo := TypeInfo;

  if LTypeInfo <> nil then
    Result := LTypeInfo^.Kind
  else
    Result := tkUnknown;
end;

class function TypeOf<T>.Name: string;
var
  LTypeInfo: PTypeInfo;
begin
  LTypeInfo := TypeInfo;

  if LTypeInfo <> nil then
    Result := GetTypeName(LTypeInfo)
  else
    Result := '';
end;

class function TypeOf<T>.TypeInfo: PTypeInfo;
begin
  Result := System.TypeInfo(T);
end;

Now, you can obtain the type information for any type by simply using TypeOf<T>.TypeInfo:

type
  TRecord<T> = record
    FVal: T;
  end;

  TIntRecord = TRecord<Integer>;

begin
  // TypeInfo(TIntRecord); // Fails with compile-time error;
  WriteLn(GetTypeName(TypeOf<TIntRecord>.TypeInfo));

  ReadLn;
end.

You can also use this construct to obtain the type information for generic types from within themselves:

type
  TSomeRec<T> = record
    function GetMyName: String;
  end;

function TSomeRec<T>.GetMyName(): String;
begin
  // Result := GetTypeName(TypeInfo(TSomeRec<T>)); // Fails
  Result := TypeOf<TSomeRec<T>>.Name;
end;

The only thing to notice is that compile time [DCC Error] E2134 Type ‘XXX’ has no type info errors will not be triggered for types having no type info. Instead, the TypeOf<T>.TypeInfo method will return nil.

DeHL 0.7 is up

DeHL

After a few months of no releases, I finally decided to throw one out — so here it is, DeHL 0.7. This release is adding three more collection, new types and fixes some internal limitations of the library.

For the people that never tried DeHL – it is a collection of types and classes that use (and even abuse) the newer generation Delphi compilers into obtaining some features that were impossible in the past releases.

The newly added collection classes are:

  • TPriorityQueue<TPriority, TValue> which implements the priority queue.
  • TDistinctMultiMap<TKey, TValue> that is similar to TMultiMap but uses sets instead of lists to store the values. This ensures that all values for a key are distinct. As usual 2 more flavors of this class exist – TSortedDistinctMultiMap<TKey, TValue> and TDoubleSortedDistinctMultiMap<TKey, TValue>.
  • TBidiMap<TKey, TValue> implements the concept of bi-directional map in which there is a bi-directional relation between keys and values (both are actually keys). TSortedBidiMap<TKey, TValue> and TDoubleSortedBidiMap<TKey, TValue> are the other two flavors of this class.
  • TStringList<T> and TWideStringList<T> collections are actually generic variants of the normal TStringList and TWideStringList (which are used as base classes).

Other enhancements include:

  • Singleton<T> class. Which can be used to access the same instance of a class across all application.
  • TWideCharSet (this was inspired on other implementations out there but use dynamic arrays to save memory and speed in certain circumstances).
  • TType speed and reliability increases.
  • A global DeHL.Defines.inc files for all $IFDEF needs.
  • Types such as Nullable<T>, Scoped<T> and TKeyValuePair<TKey, TValue> now have their own type classes.

For a full list of changes see the changelog.

This release also makes use of some new improvements of the Delphi 2010 compiler (such as class constructors, or some fixed problems in the generics handling) which makes some features unavailable on Delphi 2009. I plan to keep Delphi 2009 supported for as long as possible, but many new planned features are not going to be available on that platform.

Have Fun!