// ==================================================================================================== // YAML Parser for the .NET Framework // ==================================================================================================== // // Copyright (c) 2006 // Christophe Lambrechts // Jonathan Slenders // // ==================================================================================================== // This file is part of the .NET YAML Parser. // // This .NET YAML parser is free software; you can redistribute it and/or modify // it under the terms of the GNU Lesser General Public License as published by // the Free Software Foundation; either version 2.1 of the License, or // (at your option) any later version. // // The .NET YAML parser is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public License // along with Foobar; if not, write to the Free Software // Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USAusing System.Reflection; // ==================================================================================================== using System; namespace Yaml { /// /// Yaml Timestamp node /// uri: tag:yaml.org,2002:timestamp /// public class Timestamp : Scalar { private System.DateTime content; /// /// Represents the offset from the UTC time in hours /// /// /// We use this extra variable for compatibility with Mono /// and .NET 1.0. .NET 2.0 has an extra property for /// System.Datetime for the timezone. /// private double timezone = 0; /// /// Basic constructor that takes a given datetime /// /// A .NET 1.0 datetime public Timestamp (DateTime datetime) : base ("tag:yaml.org,2002:timestamp", NodeType.Timestamp) { this.content = datetime; this.timezone = 0; } /// /// Basic constructor, that also gives the posibility to set a timezone /// /// A .NET 1.0 datetime /// The offset, in hours,r to UTC that determine the timezone public Timestamp (DateTime datetime, double timezone) : base ("tag:yaml.org,2002:timestamp", NodeType.Timestamp) { this.content = datetime; this.timezone = timezone; } /// Content property public System.DateTime Content { get { return content; } set { content = value; } } /// Timezone, an offset in hours public double Timezone { get { return timezone; } set { timezone = value; } } /// To String public override string ToString () { return "[TIMESTAMP]" + YamlString () + "[/TIMESTAMP]"; } /// Parse a DateTime public Timestamp (ParseStream stream) : base ("tag:yaml.org,2002:timestamp", NodeType.Timestamp) { int year = 0; int month = 0; int day = 0; int hour = 0; int minutes = 0; int seconds = 0; int ms = 0; try { // Parse year year = ParseNumber (stream, 4); SkipChar (stream, '-'); // Parse month month = ParseNumber (stream, 2); SkipChar (stream, '-'); // Parse day day = ParseNumber (stream, 2); // Additional, the time if ( ! stream.EOF) ParseTime (stream, out hour, out minutes, out seconds); // Additional, milliseconds if ( ! stream.EOF) ms = ParseMilliSeconds (stream); // Additional, the timezone if ( ! stream.EOF) timezone = ParseTimezone (stream); // If there is more, then a format exception if ( ! stream.EOF) throw new Exception ("More data then excepted"); content = new DateTime (year, month, day, hour, minutes, seconds, ms); } catch (Exception ex) { throw new ParseException (stream, ex.ToString ()); } } /// /// Parse the time (hours, minutes, seconds) /// private void ParseTime (ParseStream stream, out int hour, out int minutes, out int seconds) { if (stream.Char == 't' || stream.Char == 'T') stream.Next (true); else SkipWhitespace (stream); // Parse hour // Note: A hour can be represented by one or two digits. string hulp = ""; while (stream.Char >= '0' && stream.Char <= '9' && ! stream.EOF && hulp.Length <= 2) { hulp += stream.Char; stream.Next (true); } hour = Int32.Parse (hulp); SkipChar (stream, ':'); // Parse minutes minutes = ParseNumber (stream, 2); SkipChar (stream, ':'); // Parse seconds seconds = ParseNumber (stream, 2); } /// /// Parse the milliseconds /// private int ParseMilliSeconds (ParseStream stream) { int ms = 0; // Look for fraction if (stream.Char == '.') { stream.Next (true); // Parse fraction, can consists of an // unlimited sequence of numbers, we only // look to the first three (max 1000) int count = 0; while (stream.Char >= '0' && stream.Char <= '9' && count < 3 && ! stream.EOF) { ms *= 10; ms += stream.Char - '0'; stream.Next (true); count ++; } if (count == 1) ms *= 100; if (count == 2) ms *= 10; if (count == 3) ms *= 1; // Ignore the rest while (stream.Char >= '0' && stream.Char <= '9' && ! stream.EOF) stream.Next (true); } return ms; } /// /// Parse the time zone /// private double ParseTimezone (ParseStream stream) { double timezone = 0; SkipWhitespace (stream); // Timezone = UTC, use by default 0 if (stream.Char == 'Z') stream.Next (true); else { // Find the sign of the offset int sign = 0; if (stream.Char == '-') sign = -1; else if (stream.Char == '+') sign = +1; else throw new Exception ("Invalid time zone: " + "unexpected character"); // Read next char and test for more chars stream.Next (true); if (stream.EOF) throw new Exception ("Invalid time zone"); // Parse hour offset // Note: A hour can be represented by one or two digits. string hulp = ""; while (stream.Char >= '0' && stream.Char <= '9' && !stream.EOF && hulp.Length <= 2) { hulp += (stream.Char); stream.Next (true); } timezone = sign * Double.Parse (hulp); // Parse the minutes of the timezone // when there is still more to parse if ( ! stream.EOF) { SkipChar (stream, ':'); int temp = ParseNumber (stream, 2); timezone += (temp / 60.0); } } return timezone; } /// /// Parse an integer /// /// /// The number of characters that the integer is expected to be. /// /// The stream that will be parsed private int ParseNumber (ParseStream stream, int length) { System.Text.StringBuilder hulp = new System.Text.StringBuilder (); int i; for (i = 0; i < length && !stream.EOF; i++) { hulp.Append (stream.Char); stream.Next (true); } if (i == length) return Int32.Parse (hulp.ToString ()); else throw new Exception ("Can't parse number"); } /// /// Skips a specified char, and throws an exception when /// another char was found. /// private void SkipChar (ParseStream stream, char toSkip) { if (stream.Char == toSkip) stream.Next (true); else throw new Exception ("Unexpected character"); } /// /// Skips the spaces * and tabs * in the current stream /// private void SkipWhitespace (ParseStream stream) { while ((stream.Char == ' ' || stream.Char == '\t') && ! stream.EOF) stream.Next (true); } /// Yaml notation for this datetime private string YamlString () { string date = content.ToString ("yyyy-MM-ddTHH:mm:ss"); int ms = content.Millisecond; if (ms != 0) { string hulp = "" + (ms / 1000.0); hulp = hulp.Substring(2); // Cut of the '0,', first 2 digits date += "." + hulp; } string zone = ""; if (timezone != 0) { int timezoneHour = (int) Math.Floor (timezone); int timezoneMinutes = (int) (60 * (timezone - timezoneHour)); // if positif offset add '+', a '-' is default added if (timezone > 0) zone = "+"; zone += timezoneHour.ToString (); if (timezoneMinutes != 0) zone += ":" + timezoneMinutes; } else zone = "Z"; // UTC timezone as default and if offset == 0 return date + zone; } /// Write to YAML protected internal override void Write (WriteStream stream) { stream.Append (YamlString ()); } } }