February 24, 2021

DateTime String Format Extensions

Bear in mind that a DateTime should generally be stored in Universal format and converted to local format when be displayed in a GUI or used in a report. This handy extension for date time format strings saves having to remember their obscure format codes or look them up every time.

// See here for more info 
// https://docs.microsoft.com/en-us/dotnet/standard/base-types/standard-date-and-time-format-strings
public static class DateTimeToStringExtender
{
    public static bool ParseGeneralShortDateTime(this string dateTimeStr, out DateTime dateTime)
    {
        var format = "g"; // Standard short date time format: "dd/MM/yyyy HH:mm:ss" for UK
        bool result = false;
        CultureInfo provider = CultureInfo.CurrentCulture;
        result = DateTime.TryParseExact(dateTimeStr.Trim(), format, provider, DateTimeStyles.None, out dateTime);
        return result;
    }

    /// <summary>
    /// Convert a DateTime to a YearMonthDay string of the form yyyy/MM/dd
    /// </summary>
    /// <param name="dt">target</param>
    /// <returns>YearMonthDay string of the form yyyyMMdd</returns>
    public static string ToYyyyMmDdString(this DateTime dt, char separator = char.MinValue)
    {
        string format = "yyyyMMdd";
        if (!char.IsControl(separator))
        {
            format = "yyyy" + separator + "MM" + separator + "dd";
        }
        var res = dt.ToString(format);
        return res;
    }

    /// <summary>
    /// Convert a DateTime to a YearMonthDay string of the form DdMmYyyy
    /// </summary>
    /// <param name="dt">target</param>
    /// <returns>YearMonthDay string of the form DdMmYyyy</returns>
    public static string ToDdMmYyyyString(this DateTime dt, char separator = char.MinValue)
    {
        string format = "ddMMyyyy";
        if (!char.IsControl(separator))
        {
            format = "dd" + separator + "MM" + separator + "yyyy";
        }
        return dt.ToString(format);
    }


    /// <summary>
    /// Convert a DateTime to a YearMonthDay string of the form yyyyMMdd_HHmmss where the hour is in the 24 hour format
    /// </summary>
    /// <param name="dt">target</param>
    /// <returns>YearMonthDay string of the form yyyyMMdd_HHmmss</returns>
    public static string ToYyyyMmDd_HhMmSsString(this DateTime dt)
    {
        return dt.ToString("yyyyMMdd_HHmmss");
    }

    /// <summary>
    /// Convert a DateTime to a YearMonthDay string of the form yyyyMMdd_HHmmss_ffffff where the hour is in the 24 hour format
    /// Useful for creating randomised filenames based upon the date
    /// </summary>
    /// <param name="dt">target</param>
    /// <returns>YearMonthDay string of the form yyyyMMdd_HHmmss_ffffff</returns>
    public static string To_YyyyMmDd_HhMmSs_ffffff(this DateTime dt)
    {
        return dt.ToString("yyyyMMdd_HHmmss_ffffff");
    }

    /// <summary>
    /// Convert a DateTime to a YearMonthDay string of the form yyyyMMdd_HHmmss where the hour is in the 24 hour format
    /// </summary>
    /// <param name="dt">target</param>
    /// <returns>YearMonthDay string of the form yyyyMMdd_HHmmss</returns>
    public static string ToYyyyMmDd_HhMmSsString(this DateTime dt, char dateSeparator = char.MinValue, char timeSeparator = char.MinValue)
    {
        string format = "yyyyMMdd_HHmmss";
        if (!char.IsLetterOrDigit(dateSeparator) && !char.IsControl(dateSeparator))
        {
            format = "yyyy" + dateSeparator + "MM" + dateSeparator + "dd" + "_" + "HH" + timeSeparator + "mm" + timeSeparator + "ss";
        }
        return dt.ToString(format);
    }


    /// <summary>
    /// Emits a date time (culture independent) string of the form 
    /// "ddd, dd MMM yyyy HH':'mm':'ss 'GMT'"
    /// (eg.: "Sun, 09 Mar 2008 16:05:07 GMT"). 
    /// Works on universal date times only
    /// </summary>
    /// <param name="dateTime">target date time to convert to a string</param>
    /// <returns>dateTime formatted as a string</returns>
    public static string ToRfc1123FormatString(this DateTime dateTime)
    {
        string result = dateTime.ToUniversalTime().ToString("r");
        return result;
    }

    /// <summary>
    /// Convert a DateTime to a string of the form "2008-03-09 16:05:07Z"
    /// where the '-' character is culture dependant
    /// </summary>
    /// <param name="dt">target</param>
    /// <returns>string of the form "2008-03-09 16:05:07Z" where '-' character is culture dependant</returns>
    public static string ToUniversalSorta­bleString(this DateTime dt)
    {
        return dt.ToUniversalTime().ToString("u");
    }

    // 2009-06-15T13:45:30 --> Monday, June 15, 2009 8:45:30 PM (for en-US)
    public static string ToUniversalFull(this DateTime dateTime)
    {
        var res = dateTime.ToUniversalTime().ToString("U");
        return res;
    }

    /// <summary>
    /// Emits a sortable date time (culture independent) string of the form "yyyy'-'MM'-'dd'T'HH':'mm':'ss" (eg.: "2008-03-09T16:05:07"")
    /// </summary>
    /// <param name="dateTime">target date time to convert to a string</param>
    /// <returns>dateTime formatted as a string</returns>
    public static string ToSortableFormatString(this DateTime dateTime)
    {
        string result = dateTime.ToString("s");
        return result;
    }

    /// <summary>
    /// Emits a result date time format string using a pattern that preserves
    /// time zone information and emits a result string that complies with
    /// ISO 8601 ("yyyy'-'MM'-'dd'T'HH':'mm':'ss'.'fffffffK"), preserves 
    /// the DateTime kind.
    /// </summary>
    /// <param name="dateTime">target date time to convert to a string</param>
    /// <returns>dateTime formatted as a string</returns>
    // 2009-06-15T13:45:30 (DateTimeKind.Utc) -->; 2009-06-15T13:45:30.0000000Z
    // 2009-06-15T13:45:30 (DateTimeKind.Local) --> 2009-06-15T13:45:30.0000000-07:00 (depending on the time zone)
    // 2009-06-15T13:45:30 (DateTimeKind.Unspecified) --> 2009-06-15T13:45:30.0000000
    public static string ToRoundTripFormatString(this DateTime dateTime)
    {
        string result = dateTime.ToString("o");
        return result;
    }

    static readonly string[] formats = { 
    // Extended formats
    "o",
    "yyyy-MM-ddTHH:mm:ss.ffffffZ",
    "yyyy-MM-ddTHH:mm:ss.fffffZ",
    "yyyy-MM-ddTHH:mm:ss.ffffZ",
    "yyyy-MM-ddTHH:mm:ss.fffZ",
    "yyyy-MM-ddTHH:mm:ss.ffZ",
    "yyyy-MM-ddTHH:mm:ss.fZ",
    "yyyy-MM-ddTHH:mm:ssZ"
  };

    public static DateTime ParseToIso8601DateTime(this string str)
    {
        return DateTime.ParseExact(str, formats,
            CultureInfo.InvariantCulture, DateTimeStyles.RoundtripKind);
    }

}

February 16, 2021

Compare Files Using Visual Studio

Go to the command window:
Tools.DiffFiles {file1path} {file2path}

You can get the file paths using Windows Explorer, select the file with the right mouse button whilst holding down shift and then select the menu option "Copy as path"

Extract MSI files (without installing)

Extract MSI files (without installing) With “Admin” permission use the following command line:
msiexec /a drive:\filepath\to\MSI\file /qb TARGETDIR=drive:\filepath\to\target\folder        
For example to extract “Product.Setup.msi” files:
msiexec /a Product.Setup.msi /qb TARGETDIR=C:\Downloads\Product.Setup\Extracted

Using Test Files in a Unit Test with NUnit

Insert the files into the project under a new directory say “TestData”. Include them in the project and change the build action on each file such that the “Copy to Output Directory” option should be “Copy if newer”.

Somewhere in your test:
// Create a path to the TestData directory using the 
// NUnit “TestContext.CurrentContext.TestDirectory” property
private string TestFilePath = Path.Combine(TestContext.CurrentContext.TestDirectory, "TestData");
// Now you have access to the files
string testFilePath = Path.Combine(TestFilePath, @"TestFile1.mib");

Example of using Moq's MockSequence in a Unit Test

This test shows how to test Properties and Methods
[Test]
[Category("AutomaticTest")]
public void Some_Test()
{
    var mockPublisher = new Mock(MockBehavior.Strict);
    var sequence = new MockSequence();
    mockPublisher.InSequence(sequence).Setup(x => x.Start());
    mockPublisher.InSequence(sequence).SetupSet(x => x.IsSynchronizing = true);
    mockPublisher.InSequence(sequence).Setup(x => x.Start());
    mockPublisher.InSequence(sequence).Setup(x => x.Complete());
    mockPublisher.InSequence(sequence).Setup(x => x.Start());
    mockPublisher.InSequence(sequence).Setup(x => x.Complete());
    mockPublisher.InSequence(sequence).Setup(x => x.Complete());
    mockPublisher.InSequence(sequence).SetupSet(x => x.IsSynchronizing = false);

    var fakeSyncObserver = new FakeSystemReportersSyncObserver();
    var fakeConnectivityObserver = new FakeEventEngineConnectivityObserver();
    Monitor Monitor = new Monitor(mockPublisher.Object, fakeSyncObserver, fakeConnectivityObserver);

    fakeSyncObserver.PublishStateChanged(new SyncEventArgs("1", 
      SyncEnum.ReportSynchronized));    // Start(), IsSynchronizing = true
    fakeSyncObserver.PublishStateChanged(new SyncEventArgs("2", 
      SyncEnum.ReportSynchronized));    // Start() (IsSynchronizing is already true)
    fakeSyncObserver.PublishStateChanged(new SyncEventArgs("2", 
      SyncEnum.Completed)); // Complete() ("1" is still syncing)
    fakeSyncObserver.PublishStateChanged(new SyncEventArgs("2", 
      SyncEnum.ReportSynchronized));    // Start() (IsSynchronizing is already true)
    fakeSyncObserver.PublishStateChanged(new SyncEventArgs("2", 
      SyncEnum.Completed)); // Complete() ("1" is still syncing)
    fakeSyncObserver.PublishStateChanged(new SyncEventArgs("1", 
      SyncEnum.Completed)); // Complete(), IsSynchronizing = false ("1" and "2" are both complete now)

    mockPublisher.VerifyAll();
    mockPublisher.Verify(m => m.Fail(), Times.Never())
    mockPublisher.Verify(m => m.Start(), Times.Exactly(3));
}
MockSequence has a bug, make sure that there is a call of "VerifyAll()" followed by at least one call to "Verify()" otherwise the MockSequence may not actually be checked!
Here is another sample test:
[Test]
[Category("AutomaticTest")]
public void AnotherSampleTest()
{
    var mockSnmpPublisher = new Mock(MockBehavior.Strict);
    // Create the MockSequence to validate the call order
    var sequence = new MockSequence();
    // Create the expectations, notice that the Setup is called via InSequence
    mockSnmpPublisher.InSequence(sequence).Setup(
        x => x.SendTrap(
            It.Is(y => y.Equals(StatusOids.Traps.SynchronizationStarted)),
            It.Is(s => s == true)));
    mockSnmpPublisher.InSequence(sequence).
      Setup(x => x.SetVariable(It.Is(y => y.Equals(StatusOids.Variables.IsSynchronizing)), 
                      It.Is(y => y.Equals(IsSynchronizing))));
    mockSnmpPublisher.InSequence(sequence).Setup(
        x => x.SendTrap(
            It.Is(y => y.Equals(StatusOids.Traps.SynchronizationCompleted)),
            It.Is(s => s == true)));
    mockSnmpPublisher.InSequence(sequence).
           Setup(x => x.SetVariable(It.Is(y => y.Equals(StatusOids.Variables.IsSynchronizing)), 
                    It.Is(y => y.Equals(IsNotSynchronizing))));

    SyncSnmpPublisher SyncSnmpPublisher = new SyncSnmpPublisher(mockSnmpPublisher.Object);
    SyncSnmpPublisher.Start();
    SyncSnmpPublisher.IsSynchronizing = true;
    SyncSnmpPublisher.Complete();
    SyncSnmpPublisher.IsSynchronizing = false;

    mockSnmpPublisher.VerifyAll();
    mockSnmpPublisher.Verify(m =>  // REMEMBER this will ensure the MockSequence will be checked
        m.SendTrap(It.Is(y => y.Equals(StatusOids.Traps.SynchronizationCompleted)), 
            It.Is(s => s == true)), Times.Once());
}

February 15, 2021

Hard Symbolic Links For Developers

Had a problem where the output of a (DEBUG) build went to one directory but the to run it needed to go somewhere else. This was easy to circumvent using hard links. Here is the command line for making a hard link (requires Administrator permissions)
mklink /J {SourceDirectory} {TargetDirectory}

The Source directory must NOT exist and the Target directory must. This is because the Source directory is created as a symbolic link to the hard directory
Here was my script:
mklink /J "C:\Src\repos\XxxRepo\bin\Debug\TheProduct.Plugins\ICMP" 
          "C:\Src\repos\XxxRepo\bin\Debug\TheProduct.Service\Plugins\ICMP"
mklink /J "C:\Src\repos\XxxRepo\bin\Debug\TheProduct.Plugins\RelayBoard" 
          "C:\Src\repos\XxxRepo\bin\Debug\TheProduct.Service\Plugins\RelayBoard"
mklink /J "C:\Src\repos\XxxRepo\bin\Debug\TheProduct.Plugins\SeveritySetter" 
          "C:\Src\repos\XxxRepo\bin\Debug\TheProduct.Service\Plugins\SeveritySetter"
mklink /J "C:\Src\repos\XxxRepo\bin\Debug\TheProduct.Plugins\SMTP" 
          "C:\Src\repos\XxxRepo\bin\Debug\TheProduct.Service\Plugins\SMTP"
mklink /J "C:\Src\repos\XxxRepo\bin\Debug\TheProduct.Plugins\WMI" 
          "C:\Src\repos\XxxRepo\bin\Debug\TheProduct.Service\Plugins\WMI"