Friday, November 18, 2005

I've pulled out "The Rules" from this article that I found referenced by Stuart:

"The Rules

  1. Calculations and comparisons of DateTime instances are only meaningful when the instances being compared or used are representations of points in time from the same time-zone perspective.
  2. A developer is responsible for keeping track of time-zone information associated with a DateTime value via some external mechanism. Typically this is accomplished by defining another field or variable that you use to record time-zone information when you store a DateTime value type. This approach (storing the time-zone sense alongside the DateTime value) is the most accurate and allows different developers at different points in a program's lifecycle to always have a clear understanding of the meaning of a DateTime value. Another common approach is to make it a "rule" in your design that all time values are stored in a specific time-zone context. This approach does not require additional storage to save a user's view of the time-zone context, but introduces the risk that a time value will be misinterpreted or stored incorrectly down the road by a developer that isn't aware of the rule.
  3. Performing date and time calculations on values that represent machine local time may not always yield the correct result. When performing calculations on time values in time-zone contexts that practice daylight savings time, you should convert values to universal time representations before performing date arithmetic calculations. For a specific list of operations and proper time-zone contexts, see the table in the Sorting out DateTime Methods section.
  4. A calculation on an instance of a DateTime value does not modify the value of the instance, thus a call to MyDateTime.ToLocalTime() does not modify the value of the instance of the DateTime. The methods associated with the Date (in Visual Basic®) and DateTime (in the .NET CLR) classes return new instances that represent the result of a calculation or operation.
  5. When using the .NET Framework version 1.0 and 1.1, DO NOT send a DateTime value that represents UCT time thru System.XML.Serialization. This goes for Date, Time and DateTime values. For Web services and other forms of serialization to XML involving System.DateTime, always make sure that the value in the DateTime value represents current machine local time. The serializer will properly decode an XML Schema-defined DateTime value that is encoded in GMT (offset value = 0), but it will decode it to the local machine time viewpoint.
  6. In general, if you are dealing with absolute elapsed time, such as measuring a timeout, performing arithmetic, or doing comparisons of different DateTime values, you should try and use a Universal time value if possible so that you get the best possible accuracy without effects of time zone and/or daylight savings having an impact.
  7. When dealing with high-level, user-facing concepts such as scheduling, and you can safely assume that each day has 24 hours from a user's perspective, it may be okay to counter Rule #6 by performing arithmetic, et cetera, on local times."


Stuart references this article from which I took the above quote.

Stuart Celarier : Quickie: it's about time

Saturday, November 5, 2005

NAnt - The solution task and setup/merge module projects in VS.NET 2003

At my current employer we use CruiseControl.net. Before we had CruiseControl.net in place we used NAnt build files. I'm sure that if you done much more than a "hello world" solution using NAnt you would soon find that there are some project types that do not work well with NAnt (at least 0.85 RC2).

I've given up on getting solutions that contain C++ in them to compile correctly with NAnt. It is just much easier to use an exec task. All that being said, I really dislike seeing warning messages of any kind in the build results:

Warnings: (22)

Only C#, VB.NET and C++ projects are supported. Skipping project 'FirstModule\FirstModule\Module.vdproj'.

Only C#, VB.NET and C++ projects are supported. Skipping project 'AnotherModule\AnotherModule.vdproj'.

Only C#, VB.NET and C++ projects are supported. Skipping project 'AnnoyMeModule\AnnoyMeModule.vdproj'.


The above is a sample with the module names changed. I knew from hacking solution files in the past that it would be possible to simply find the references to the MergeModule projects and remove them and then load the solution.

What I needed to do was remove the lines below:

Project("{54435603-DBB4-11D2-8724-00A0C9A8B90C}") = "FirstModule", "FirstModule\FirstModule.vdproj", "{675E01F7-2494-4F95-A271-B2F6F6FD02D4}"
ProjectSection(ProjectDependencies) = postProject
EndProjectSection
EndProject


I thought about three possible solutions to removing these lines:

  1. Write an EXE Console App that used Regex's to find the 'offending' lines and remove them from the output (an "old school" Unix filter!)

  2. Take a similar approach but implement a new NAnt task. I've had to do that in the past to override broken CVS tasks, so this wasn't a bad option.

  3. Write a script task and embed the C# code directly into the build file. Not as elegant, but extremely portable and you don't need to load a DLL to get it working.

Here is the script task that I wrote. It’s not pretty, and certainly could be cleaned up a little, but it does the job:

<!-- Utility Scripts -->

<script language="C#" prefix="filter">

    <code>

    <![CDATA[

    [Function("remove-vdproj")]

    public static string RemoveVdprojFilter(string path)

    {

        string beginPattern = @"^Project\(""\{54435603-DBB4-11D2-8724-00A0C9A8B90C\}""\) = "".+"", "".+\.vdproj"", ""\{[A-F0-9\\-]+\}""$";

        string endPattern = @"^EndProject$";

 

        using (StreamReader reader = File.OpenText(path))

        using (StringWriter writer = new StringWriter())

        {

            bool inside = false;

            string line;

            while ((line = reader.ReadLine()) != null)

            {

                if (inside)

                {

                    Match match = Regex.Match(line, endPattern, RegexOptions.IgnoreCase);

                    inside = match.Success == false;

                }

                else

                {

                    Match match = Regex.Match(line, beginPattern, RegexOptions.IgnoreCase);

                    inside = match.Success;

                    if (inside == false)

                    {

                        writer.WriteLine(line);

                    }

                }

            }

 

            return writer.ToString();

        }

    }

    ]]>

    </code>

</script>



The workhorse of this script is the beginPattern. It reads in the solution file line by line until it finds the project definition that matches a Setup/MergeModule project. It then changes the state to 'inside' and reads until it finds the EndProject line. This will continue until it processes the entire file. The filtered solution file is returned as a string.

I decided to use an 'echo' task to write the filtered solution file back to the disk. It is then ready for consumption in a solution task.

NOTE: You might run into problems if you call this NAnt task from another NAnt task. The script code assumes the working directory of the original Nant project, not the currently executing task's project's directory. To work around this, make sure to surround your relative paths to your solution file with a path::get-full-path() function.

See example below:

<target name="ds.build" description="Builds DirectoryServices">

    <echo file="DirectoryServices/DirectoryServices-NoMerge.sln" message="${filter::remove-vdproj(path::get-full-path('DirectoryServices/DirectoryServices.sln'))}" />

    <solution configuration="${configuration}" solutionfile="DirectoryServices/DirectoryServices-NoMerge.sln" />

    <delete file="DirectoryServices/DirectoryServices-NoMerge.sln" />

</target>



You can easily expand on this concept to alter solution and project files prior to compiling them in your favorite default.build!

Scott pointed out to me that I should cross-link to his article, "Building MSI files from NAnt and Updating the VDProj's version information and other sins on Tuesday". Thanks Scott!

-- John