Click here to Skip to main content
15,881,852 members
Articles / Programming Languages / C#

W3CDateTime Structure in C#

Rate me:
Please Sign up or sign in to vote.
4.25/5 (5 votes)
3 Nov 2009CPOL3 min read 40.6K   374   14   8
Simple W3CDateTime Structure when you are using atom feed like Gmail

Introduction

I'm pleased to post this article, my very first one. Please understand my bad English. My English is not as good as my programming skills.

This article is about a simple W3CDateTime structure in C#. I made it for the purpose of parsing W3C datetime of Gmail atom feed, but I think you can use it in a common way if you have W3C date time string.

Gmail Atom Feed

Image 1

<modified>2009-1103T16:52:16:Z</modified>
"2009-1103T16:52:16:Z" - this is w3c date time string.
"Z" means this time is expressed in universal time

There are many libraries to parse Gmail atom feed, because there is no official Gmail API like atom.net. But I can't find a simple class just for handling W3CDateTime.

W3C Date

http://www.w3.org/TR/NOTE-datetime

Image 2

TZD is used for time zone offset for issuer. (+09:00, -05:00, or Z means 0)
hh = 00 through 23, but I think Gmail atom feed uses 01 through 24.

"2009-11-02T24:39:06Z" - This time was logged in my application.

In my application, an exception occurred when DateTime.Parse(innerText);
because the default DateTime class does not support W3C date string and Gmail atom feed do use 1-24hour. :(
After making this structure, I can solve this problem by replacing just small amounts of source
like this:

C#
W3CDateTime.ParseToLocalTime(innerText);

Outline of Structure

C#
using System;
using System.Collections.Generic;
using System.Text;
using System.Text.RegularExpressions;

namespace Bobaman.NewTools
{
    public struct W3CDateTime
    {
        public static TimeSpan CurrentTimeZoneOffset
        {
            get
            {
                return TimeZone.CurrentTimeZone.GetUtcOffset(DateTime.Now);
            }
        }

        private DateTime time;
        private TimeSpan tzd;

        public W3CDateTime(DateTime time, TimeSpan tzd)
        {
            this.time = time;
            this.tzd = tzd;
        }

        public override string ToString()
        {
            string timePart = time.ToString("yyyy-MM-ddTHH:mm:ss");
            string tzdPart = "Z";
            if (tzd != TimeSpan.Zero)
                tzdPart = String.Format("{0}{1:00}:{2:00}", 
		tzd > TimeSpan.Zero ? "+" : "-", tzd.Hours, tzd.Minutes);

            return timePart + tzdPart;
        }

        public DateTime ToLocalTime()
        {
            return ToUniversalTime().ToLocalTime();
        }

        public DateTime ToUniversalTime()
        {
            return DateTime.SpecifyKind(time - tzd, DateTimeKind.Utc);
        }

        public static W3CDateTime Parse(string W3CDateTimeString)
        {
          
        }

        public static DateTime ParseToLocalTime(string W3CDateTimeString)
        {
            return Parse(W3CDateTimeString).ToLocalTime();
        }

        public static DateTime ParseToUniversalTime(string W3CDateTimeString)
        {
            return Parse(W3CDateTimeString).ToUniversalTime();
        }

        public static W3CDateTime FromDateTime(DateTime dateTime)
        {
            if (dateTime.Kind == DateTimeKind.Local || 
		dateTime.Kind == DateTimeKind.Unspecified)
                return new W3CDateTime(dateTime, CurrentTimeZoneOffset);
            else
                return new W3CDateTime(dateTime, TimeSpan.Zero);
        }

        public static string ToString(DateTime dateTime)
        {
            return FromDateTime(dateTime).ToString();
        }
    }
}

This is the outline of the structure.
It's not the full source. I ignored some functions (properties and some TryParse function, etc.)
This structure keeps two parts, the time and tzd (time zone offset). Please remember that ToLocalTime function converts to YOUR local time. but tzd part of W3C date format is not yours. It's THEIR (issuer or publisher) timezone offset.
If you need tzd part of 'their' local time zone, you can call Parse function to parse to W3CDateTime and you can access that property.

Example of Usage

C#
namespace TestConsole
{
    class Program
    {
        static void Main(string[] args)
        {
            W3CDateTime googlefeed = W3CDateTime.Parse("2009-11-02T24:39:06Z");

            W3CDateTime sameTime1 = W3CDateTime.Parse("1994-11-05T08:15:30-05:00");
            W3CDateTime sameTime2 = W3CDateTime.Parse("1994-11-05T13:15:30Z");

            if (sameTime1.ToUniversalTime() == sameTime2.ToUniversalTime())
                Console.WriteLine("good");

            Console.WriteLine(googlefeed.ToLocalTime());
        }
    }
}

This example shows parse W3C date string that contains "24" hour, and proves equality of "1994-11-05T08:15:30-05:00" and "1994-11-05T13:15:30Z".
You can write W3C date string from your time. For example, W3CDateTime.ToString(DateTime.Now). DateTime.Now will be expressed by local time with timezone offset.
When you call ToString if you provide utc DateTime, it will be converted to "Z" format.

Parse Function

C#
public static W3CDateTime Parse(string W3CDateTimeString)
{
    const string W3CDateFormat =
      @"^(?<year>\d\d\d\d)" +
      @"(-(?<month>\d\d)(-(?<day>\d\d)
	(T(?<hour>\d\d):(?<min>\d\d)(:(?<sec>\d\d)(?<ms>\.\d+)?)?" +
      @"(?<tzd>(Z|[+\-]\d\d:\d\d)))?)?)?$";

    Regex regex = new Regex(W3CDateFormat);

    Match match = regex.Match(W3CDateTimeString);
    if (!match.Success)
    {
        // Didn't match either expression. Throw an exception.
        throw new FormatException("String is not a valid date time stamp.");
    }

    int year = int.Parse(match.Groups["year"].Value);
    int month = (match.Groups["month"].Success) ? 
		int.Parse(match.Groups["month"].Value) : 1;
    int day = match.Groups["day"].Success ? int.Parse(match.Groups["day"].Value) : 1;
    int hour = match.Groups["hour"].Success ? int.Parse(match.Groups["hour"].Value) : 0;
    int min = match.Groups["min"].Success ? int.Parse(match.Groups["min"].Value) : 0;
    int sec = match.Groups["sec"].Success ? int.Parse(match.Groups["sec"].Value) : 0;
    int ms = match.Groups["ms"].Success ? 
	(int)Math.Round((1000 * double.Parse(match.Groups["ms"].Value))) : 0;

    //for google mail feed
    if (hour == 24)
        hour = 0;
   
    TimeSpan tzd = TimeSpan.Zero;
    if (match.Groups["tzd"].Success)
        tzd = ParseW3COffset(match.Groups["tzd"].Value);

    DateTime time = new DateTime(year, month, day, hour, min, sec, ms);
   
    return new W3CDateTime(time, tzd);
}

http://www.informit.com/guides/content.aspx?g=dotnet&seqNum=172

I used and modified some source from that article. A regular expression works very well. To avoid an exception from gmail atom feed, code minus one hour when a hour is 24 and add one hour after convert to DateTime structure. so it will use 0~23 hour. it was wrong.
I found that they just use "24" instead of "0". (It's from a lots of time logs of Gmail atom feeds.)
For example, the next of "2009-11-03T23:59:59Z" is "2009-11-04T24:00:00Z",
so I don't need to "plus one hour" and "minus one hour" (it's the cause of "plus one day" problem and it was my mistake).
I don't know why Google uses that weird hour range.
Do... they want to reserve "0" for some reason like a default value or null? - I am not sure.

End of Article

Until now, I can live with the help of codes shared like this (or another way). I hope that this code will help somebody...

History

First version: gone to heaven by Windows crash ............. although this is the second version, this is my first posting :(
Third: I changed the code within the Parse function. there was big bug...OTL

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
CEO GrandMaster Software
Korea (Republic of) Korea (Republic of)
----------------
bobaman
102
heebaek choi
grandmaster software
----------------

Comments and Discussions

 
QuestionBug for cultures with other decimal separators Pin
marc borgers3-Oct-12 20:03
marc borgers3-Oct-12 20:03 
GeneralMy vote of 3 Pin
Hiren solanki12-Aug-10 23:15
Hiren solanki12-Aug-10 23:15 
GeneralOn hour 24 Pin
PIEBALDconsult5-Nov-09 6:32
mvePIEBALDconsult5-Nov-09 6:32 
GeneralRe: On hour 24 Pin
heebaek-choi5-Nov-09 7:07
heebaek-choi5-Nov-09 7:07 
GeneralRe: On hour 24 Pin
PIEBALDconsult5-Nov-09 7:17
mvePIEBALDconsult5-Nov-09 7:17 
Yes, and I noticed that you updated the article; but I wanted to get the word out to others.

You should throw those back at them, not process them. Big Grin | :-D
GeneralInteresting Pin
Argyle4Ever3-Nov-09 22:21
Argyle4Ever3-Nov-09 22:21 
GeneralRe: Interesting Pin
heebaek-choi4-Nov-09 0:02
heebaek-choi4-Nov-09 0:02 
GeneralRe: Interesting Pin
PIEBALDconsult5-Nov-09 6:15
mvePIEBALDconsult5-Nov-09 6:15 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.