Posts tagged RTL

Collections 1.0.1 soon

I will get an 1.0.1 update soon addressing XE compatibility issues some people are experiencing and some documentation fixes. Stay tuned.

Collections 1.0

Last week I have finally got some free time on my hands to do something I was planning for a long time now: create a new package that will only provide a number of collections. What this means is that I took DeHL and stripped out everything that was not collection related; remodeled the classes to rely on Generics.Collections and Generics.Defaults standard units; and finished all the documentation.

What remained is a pure collection orientated package called (very unimaginatively)  Collections.  If you ever used DeHL’s collections you will most certainly recognize the code. But, while stripping out the bloat I was forced to re move some things that relied on that bloat:

  • No more reliance on IType/TType. Collections now use IComparer and IEqualityComparer provided by Generics.Defaults.
  • All associative collections use TPair now.
  • Because IType was designed to provide cleanup for the enclosed values in the collections some reworking was necessary to make collections more Delphi-like. You still get the OwnsObjects, OwnsKeys and OwnsValues properties on the object variants.
  • No more Op.Cast<> support. This Enex operation relied on TConverter which relied on IType and so on and so forth. Instead use a Op.Select with a casting predicate.
  • Using NativeInt instead of NativeUInt in some places. This was a menace for a lot of people.

Note that Collections is not base-class or interface compatible with Generics.Collections. I could not make the proper compatibility because of interface support.
So what would you expect in the package:

  • Full inline documentation. All APIs are properly documented. If you find any typos or errors please fill a bug!
  • Test cases for mostly everything provided.
  • A wide range of collections (much more than just stack, queue, list and dictionary).
  • Enumerable extensions.
  • Easy extensibility.
  • Based on NativeInt instead of Integer. Ready at interface level for 64bit support.

Warning: This is a 1.0 release. There may still be some problems. If you find any please fill in a bug report. If you feel that you can help and implement something that is not included in the package feel free to contact me!

TZDB 1.6, oh man!

It seems that changes keep piling up. I decided it is time to get these announcements out of my blog and into their own discussion group. No more TZDB release related news will be written on this blog.

Anyway, TZDB 1.6 1.7 introduces a better Delphi/FreePascal version detection and should work on Delphi 6 and FreePascal 2.0. Let me emphasize: should work ! (tested on Delphi 7). It’s a real pain to keep a project compatible with so many version of Delphi and FreePascal even for such a small project. Also, the embedded time zone database was upgraded to the latest version.

Grab the latest version here, and view the latest changes here.

TZDB 1.5, now with FreePascal support.

TZDB 1.5 was released today. If you are wondering where did 1.3 and 1.4 go … well, those were transitional releases. This version of TZDB is selectively dependent on Delphi features giving you the opportunity to actually compile TZDB on more versions of Delphi (should be compilable starting with 7th version) and even FreePascal 2.4.0 (I tested both the 32bit and 64bit versions).

Note: TZDB does not employ the “least common denominator” technique. If a certain feature is present in a Delphi or FreePascal version — it is used.

Grab the latest version here, and view the latest changes here.

TZDB 1.2, now with Delphi 2009 and 2010 support

TZDB 1.2 was released today. You can grab it here, and view the latest changes here.



The most notable changes are:

  • Latest tz database (2010m). Also removed a few less then useless time zones from the official distribution.
  • Bug fixes, and a new testing module that actually highlighted those bugs in the first place.
  • Support for Delphi 2009 and 2010. If compiled for these versions, TZDB provides some features of Delphi XE’s TTimeZone class (for API compatibility).

TZDB — new location

I have moved the TZDB to a new Google code project. There are two wiki pages explaining some basics and a download with the latest TZDB.

Cheers.

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!