Exemplo n.º 1
0
        static void Main(string[] args)
        {
            try
            {
                foreach (var testCase in s_testCases)
                {
                    Console.WriteLine(testCase.ToString());
                    testCase.PerformTest();
                }

                Console.WriteLine();
                Console.WriteLine("Parse failure cases:");
                foreach (var testCase in s_parseFailureCases)
                {
                    Console.WriteLine(testCase);
                    TimeZoneTag tag;
                    if (TimeZoneTag.TryParse(testCase, out tag))
                    {
                        throw new ApplicationException("Failed parse failure test");
                    }
                }

                Console.WriteLine();
                Console.WriteLine("All tests passed.");
            }
            catch (Exception err)
            {
                Console.WriteLine(err.ToString());
            }

            Console.WriteLine();
            Console.WriteLine("Press any key to exit.");
            Console.ReadKey();
        }
Exemplo n.º 2
0
        public bool Equals(TimeZoneTag other)
        {
            if (other == null)
            {
                return(false);
            }

            return(Kind == other.Kind && m_offset == other.m_offset);
        }
Exemplo n.º 3
0
        /// <summary>
        /// Constructs a DateTag from constituent values
        /// </summary>
        /// <param name="date">A <see cref="DateTime"/> value.</param>
        /// <param name="timeZone">A <see cref="TimeZoneTag"/> value or null if unknown.</param>
        /// <param name="precision">Precision in terms of significant digits. If zero
        /// then set to maximum (<see cref="PrecisionMax"/>).</param>
        /// <remarks>
        /// <para>If timeZone is null, the timezone will be set to <see cref="TimeZoneTag.ForceLocal"/>
        /// if the <paramref name="date"/> <see cref="DateTime.Kind"/> is <see cref="DateTimeKind.Local"/>
        /// or <see cref="TimeZoneTag.Unknown"/>, and to <see cref="TimeZoneTag.ForceUtc"/> if
        /// <see cref="DateTime.Kind"/> is <see cref="DateTimeKind.Utc"/>.
        /// </para>
        /// <para>If precision is zero, the precision is detected by the number of trailing zeros
        /// after the seconds decimal point. The lowest precision detected is <see cref="PrecisionSecond"/>.
        /// See <see cref="DetectPrecision(DateTime)"/>.
        /// </para>
        /// </remarks>
        public DateTag(DateTime date, TimeZoneTag timeZone = null, int precision = 0)
        {
            // Default the timezone value if needed.
            if (timeZone == null)
            {
                switch (date.Kind)
                {
                case DateTimeKind.Local:
                    timeZone = TimeZoneTag.ForceLocal;
                    break;

                case DateTimeKind.Utc:
                    timeZone = TimeZoneTag.ForceUtc;
                    break;

                default:
                    timeZone = TimeZoneTag.Zero;
                    break;
                }
            }

            // Change date to a local timezone if needed
            if (date.Kind == DateTimeKind.Utc)
            {
                date = timeZone.ToLocal(date);
            }

            // Limit precision to compatible range
            if (precision > PrecisionMax)
            {
                precision = PrecisionMax;
            }
            if (precision < PrecisionMin)
            {
                precision = DetectPrecision(date);
            }

            m_dateTicks = date.Ticks;
            TimeZone    = timeZone;
            Precision   = precision;
        }
Exemplo n.º 4
0
        // Throws an exception if any test fails.
        // Exceptions are convenient indicators because they can include a message and the location of the error.
        public void PerformTest()
        {
            // Parse test
            TimeZoneTag tag;

            if (!TimeZoneTag.TryParse(m_srcTag, out tag))
            {
                throw new ApplicationException("Failed TryParse test.");
            }

            if (!tag.ToString().Equals(m_normalizedTag, StringComparison.Ordinal))
            {
                throw new ApplicationException("Failed ToString test.");
            }

            if (tag.Kind != m_kind)
            {
                throw new ApplicationException("Failed Kind test.");
            }

            int utcOffset = (tag.Kind == TimeZoneKind.Normal) ? m_utcOffset : 0;

            if (tag.UtcOffset != new TimeSpan(0, utcOffset, 0))
            {
                throw new ApplicationException("Failed UtcOffset test.");
            }

            if (tag.UtcOffsetMinutes != utcOffset)
            {
                throw new ApplicationException("Failed UtcOffsetMinutes test.");
            }

            if (tag.UtcOffsetTicks != utcOffset * c_ticksPerMinute)
            {
                throw new ApplicationException("Failed UtcOffsetTicks test.");
            }

            TimeZoneTag tag2 = new TimeZoneTag(m_utcOffset, m_kind);

            if (tag2.GetHashCode() != tag.GetHashCode())
            {
                throw new ApplicationException("Failed GetHashCode test.");
            }

            if (!tag2.Equals(tag))
            {
                throw new ApplicationException("Failed Equals test.");
            }

            tag2 = new TimeZoneTag(new TimeSpan(0, utcOffset, 0), m_kind);
            if (!tag2.Equals(tag))
            {
                throw new ApplicationException("Failed TimeSpan Constructor test");
            }

            tag2 = new TimeZoneTag(utcOffset * c_ticksPerMinute, m_kind);
            if (!tag2.Equals(tag))
            {
                throw new ApplicationException("Failed Ticks Constructor test");
            }

            if (m_kind == TimeZoneKind.Normal)
            {
                tag2 = new TimeZoneTag(m_utcOffset + 1, m_kind);

                if (tag2.GetHashCode() == tag.GetHashCode())
                {
                    throw new ApplicationException("Failed GetHashCode no match test");
                }

                if (tag2.Equals(tag))
                {
                    throw new ApplicationException("Failed Not Equals test");
                }

                tag2 = new TimeZoneTag(m_utcOffset, TimeZoneKind.ForceLocal);
                if (tag2.Equals(tag))
                {
                    throw new ApplicationException("Failed ForceLocal test");
                }

                tag2 = new TimeZoneTag(m_utcOffset, TimeZoneKind.ForceLocal);
                if (tag2.Equals(tag))
                {
                    throw new ApplicationException("Failed ForceUtc test");
                }

                if (utcOffset == 0 && !tag.Equals(TimeZoneTag.Zero))
                {
                    throw new ApplicationException("Failed Zero test");
                }
            }
            else if (m_kind == TimeZoneKind.ForceLocal)
            {
                if (!tag.Equals(TimeZoneTag.ForceLocal))
                {
                    throw new ApplicationException("Failed ForceLocal test");
                }

                if (tag.Equals(TimeZoneTag.ForceUtc))
                {
                    throw new ApplicationException("Failed ForceUtc test");
                }
            }
            else // m_kind == TimeZoneKind.ForceUtc
            {
                if (!tag.Equals(TimeZoneTag.ForceUtc))
                {
                    throw new ApplicationException("Failed ForceUtc test");
                }

                if (tag.Equals(TimeZoneTag.ForceLocal))
                {
                    throw new ApplicationException("Failed ForceLocal test");
                }
            }

            tag2 = TimeZoneTag.Parse(m_srcTag);
            if (!tag2.Equals(tag))
            {
                throw new ApplicationException("Failed Parse test");
            }

            tag2 = TimeZoneTag.Parse(m_normalizedTag);
            if (!tag2.Equals(tag))
            {
                throw new ApplicationException("Failed Parse Normalized test");
            }

            DateTime       dtLocal = new DateTime(1968, 7, 23, 8, 24, 46, 22, DateTimeKind.Local);
            DateTime       dtUtc   = new DateTime(dtLocal.Ticks - (utcOffset * c_ticksPerMinute), DateTimeKind.Utc);
            DateTimeOffset dto     = new DateTimeOffset(dtLocal.Ticks, TimeSpan.FromMinutes(utcOffset));

            if (!tag.ToLocal(dtUtc).Equals(dtLocal))
            {
                throw new ApplicationException("Failed ToLocal test");
            }

            if (!tag.ToUtc(dtLocal).Equals(dtUtc))
            {
                throw new ApplicationException("Failed ToUtc test");
            }

            if (!tag.ToLocal(dtLocal).Equals(dtLocal))
            {
                throw new ApplicationException("Failed ToLocal already local test");
            }

            if (!tag.ToUtc(dtUtc).Equals(dtUtc))
            {
                throw new ApplicationException("Failed ToUtc already utc test");
            }

            if (!tag.ToLocal(DateTime.SpecifyKind(dtUtc, DateTimeKind.Unspecified)).Equals(dtLocal))
            {
                throw new ApplicationException("Failed ToLocal Unspecified test");
            }

            if (!tag.ToUtc(DateTime.SpecifyKind(dtLocal, DateTimeKind.Unspecified)).Equals(dtUtc))
            {
                throw new ApplicationException("Failed ToUtc Unspecified test");
            }

            if (!tag.ToDateTimeOffset(dtUtc).Equals(dto))
            {
                throw new ApplicationException("Failed ToDateTimeOffset UTC test");
            }

            if (!tag.ToDateTimeOffset(dtLocal).Equals(dto))
            {
                throw new ApplicationException("Failed ToDateTimeOffset Local test");
            }
        }
Exemplo n.º 5
0
        /// <summary>
        /// Parses a metadata date tag into a <see cref="DateTag"/> including <see cref="DateTime"/>, <see cref="TimeZoneTag"/>, and significant digits.
        /// </summary>
        /// <param name="dateTag">The value to be parsed in <see cref="https://www.w3.org/TR/NOTE-datetime">W3CDTF</see> format.</param>
        /// <param name="result">The result of the parsing.</param>
        /// <returns>True if successful, else false.</returns>
        /// <remarks>
        /// <para>The <see cref="https://www.w3.org/TR/NOTE-datetime">W3CDTF</see> format has date and timezone portions.
        /// This method parses both.</para>
        /// <para>If the timezone portion is not included in the input string then the resulting <paramref name="timezone"/>
        /// will have <see cref="Kind"/> set to <see cref="TimeZoneKind.ForceLocal"/>.
        /// </para>
        /// <para>If the timezone portion is set to "Z" indicating UTC, then the resulting <paramref name="timezone"/>
        /// will have <see cref="Kind"/> set to <see cref="TimeZoneKind.ForceUtc"/> and the <see cref="UtcOffset"/>
        /// will be zero.
        /// </para>
        /// <para>The W2CDTF format permits partial date-time values. For example "2018" is just a year with no
        /// other information. The <paramref name="precision"/> value indicates how much detail is included
        /// as follows: 4 = year, 6 = month, 8 = day, 10 = hour, 12 = minute, 14 = second, 17 = millisecond, 20 = microsecond,
        /// 21 = tick (100 nanoseconds).
        /// </para>
        /// </remarks>
        public static bool TryParse(string dateTag, out DateTag result)
        {
            // Init values for failure case
            result = new DateTag(ZeroDate, TimeZoneTag.Zero, 0);

            // Init parts
            int  year   = 0;
            int  month  = 1;
            int  day    = 1;
            int  hour   = 12; // Noon
            int  minute = 0;
            int  second = 0;
            long ticks  = 0;

            // Track position
            int pos = 0;

            if (dateTag.Length < 4)
            {
                return(false);
            }

            if (!int.TryParse(dateTag.Substring(0, 4), out year) ||
                year < 1 || year > 9999)
            {
                return(false);
            }
            int precision = PrecisionYear;

            pos = 4;
            if (dateTag.Length > 5 && dateTag[4] == '-')
            {
                if (!int.TryParse(dateTag.Substring(5, 2), out month) ||
                    month < 1 || month > 12)
                {
                    return(false);
                }
                precision = PrecisionMonth;
                pos       = 7;
                if (dateTag.Length > 8 && dateTag[7] == '-')
                {
                    if (!int.TryParse(dateTag.Substring(8, 2), out day) ||
                        day < 1 || day > DateTime.DaysInMonth(year, month))
                    {
                        return(false);
                    }
                    precision = PrecisionDay;
                    pos       = 10;
                    if (dateTag.Length > 11 && (dateTag[10] == 'T' || dateTag[10] == ' ')) // Even though W3CDTF and ISO 8601 specify 'T' separating date and time, tolerate a space as an alternative.
                    {
                        if (!int.TryParse(dateTag.Substring(11, 2), out hour) ||
                            hour < 0 || hour > 23)
                        {
                            return(false);
                        }
                        precision = PrecisionHour;
                        pos       = 13;
                        if (dateTag.Length > 14 && dateTag[13] == ':')
                        {
                            if (!int.TryParse(dateTag.Substring(14, 2), out minute) ||
                                minute < 0 || minute > 59)
                            {
                                return(false);
                            }
                            precision = PrecisionMinute;
                            pos       = 16;
                            if (dateTag.Length > 17 && dateTag[16] == ':')
                            {
                                if (!int.TryParse(dateTag.Substring(17, 2), out second) ||
                                    second < 0 || second > 59)
                                {
                                    return(false);
                                }
                                precision = PrecisionSecond;
                                pos       = 19;
                                if (dateTag.Length > 20 && dateTag[19] == '.')
                                {
                                    ++pos;
                                    int anchor = pos;
                                    while (pos < dateTag.Length && char.IsDigit(dateTag[pos]))
                                    {
                                        ++pos;
                                    }

                                    precision = PrecisionSecond + (pos - anchor);
                                    if (precision > PrecisionMax)
                                    {
                                        precision = PrecisionMax;
                                    }

                                    double d;
                                    if (!double.TryParse(dateTag.Substring(anchor, pos - anchor), out d))
                                    {
                                        return(false);
                                    }
                                    ticks = (long)(d * Math.Pow(10.0, 7.0 - (pos - anchor)));
                                }
                            }
                        }
                    }
                }
            }

            // Attempt to parse the timezone
            TimeZoneTag  timezone;
            DateTimeKind dtk = DateTimeKind.Unspecified;

            if (pos < dateTag.Length)
            {
                if (!TimeZoneTag.TryParse(dateTag.Substring(pos), out timezone))
                {
                    return(false);
                }
                dtk = (timezone.Kind == TimeZoneKind.ForceUtc) ? DateTimeKind.Utc : DateTimeKind.Local;
            }
            else
            {
                timezone = TimeZoneTag.ForceLocal;
                dtk      = DateTimeKind.Local;
            }

            result = new DateTag(new DateTime(year, month, day, hour, minute, second, dtk).AddTicks(ticks),
                                 timezone, precision);
            return(true);
        }
Exemplo n.º 6
0
        /// <summary>
        /// Parses a timezone string into a TimeZoneTag instance.
        /// </summary>
        /// <param name="s">The timezone string to parse.</param>
        /// <param name="result">The parsed timezone.</param>
        /// <returns>True if successful, else false.</returns>
        /// <remarks>
        /// <para>See <see cref="TimeZoneTag"/> for details about valid values.
        /// </para>
        /// <para>Example timezone values:</para>
        /// <para>  "-05:00" (UTC minus 5 hours)</para>
        /// <para>  "+06:00" (UTC plus 6 hours)</para>
        /// <para>  "+09:30" (UTC plus 9 1/2 hours)</para>
        /// <para>  "Z"      (UTC. Offset to local is unknown.)</para>
        /// <para>  "0"      (Local. Offset to UTC is unknwon.)</para>
        /// <para>Tolerable timezone values:</para>
        /// <para>  "-5"     (UTC minus 5 hours)</para>
        /// <para>  "+6      (UTC plus 6 hours)</para>
        /// </remarks>
        public static bool TryParse(string timezoneTag, out TimeZoneTag result)
        {
            if (string.IsNullOrEmpty(timezoneTag))
            {
                result = Zero;
                return(false);
            }
            if (timezoneTag.Equals(c_local, StringComparison.Ordinal))
            {
                result = ForceLocal;
                return(true);
            }
            if (timezoneTag.Equals(c_utc, StringComparison.Ordinal))
            {
                result = ForceUtc;
                return(true);
            }

            result = Zero;
            if (timezoneTag.Length < 2)
            {
                return(false);
            }

            bool negative;

            if (timezoneTag[0] == '+')
            {
                negative = false;
            }
            else if (timezoneTag[0] == '-')
            {
                negative = true;
            }
            else
            {
                return(false);
            }

            var parts = timezoneTag.Substring(1).Split(':');

            if (parts.Length < 1 || parts.Length > 2)
            {
                return(false);
            }
            int hours;

            if (!int.TryParse(parts[0], out hours))
            {
                return(false);
            }
            if (hours < 0)
            {
                return(false);
            }

            int minutes = 0;

            if (parts.Length > 1)
            {
                if (!int.TryParse(parts[1], out minutes))
                {
                    return(false);
                }
                if (minutes < 0 || minutes > 59)
                {
                    return(false);
                }
            }

            int totalMinutes = hours * 60 + minutes;

            if (negative)
            {
                totalMinutes = -totalMinutes;
            }
            if (totalMinutes < c_minOffset || totalMinutes > c_maxOffset)
            {
                return(false);
            }
            result = new TimeZoneTag(totalMinutes, TimeZoneKind.Normal);
            return(true);
        }