/// <summary>
        /// Initializes a new instance of the <see cref="GedcomxDateApproximate"/> class.
        /// </summary>
        /// <param name="date">The formal duration string that describes a GEDCOM X date approximation.</param>
        /// <exception cref="GedcomxDateException">Thrown if the specified date is null, empty, or does not begin with 'A' (as required by a formal date string).</exception>
        public GedcomxDateApproximate(String date)
        {
            if (date == null || date.Length < 1 || date[0] != 'A')
            {
                throw new GedcomxDateException("Invalid Approximate Date: Must start with A");
            }

            simpleDate = new GedcomxDateSimple(date.Substring(1));
        }
        /// <summary>
        /// Initializes a new instance of the <see cref="GedcomxDateApproximate"/> class.
        /// </summary>
        /// <param name="date">The formal duration string that describes a GEDCOM X date approximation.</param>
        /// <exception cref="GedcomxDateException">Thrown if the specified date is null, empty, or does not begin with 'A' (as required by a formal date string).</exception>
        public GedcomxDateApproximate(String date)
        {
            if (date == null || date.Length < 1 || date[0] != 'A')
            {
                throw new GedcomxDateException("Invalid Approximate Date: Must start with A");
            }

            simpleDate = new GedcomxDateSimple(date.Substring(1));

        }
Exemplo n.º 3
0
        /// <summary>
        /// Initializes a new instance of the <see cref="GedcomxDateRecurring"/> class.
        /// </summary>
        /// <param name="date">The recurring date string that describes a recurring GEDCOM X date.</param>
        /// <exception cref="Gedcomx.Date.GedcomxDateException">
        /// Thrown if the formal date string is null, empty, or fails to meet the expected format. The specific reason will be given at runtime.
        /// </exception>
        public GedcomxDateRecurring(String date)
        {
            if (date == null || date.Length < 3)
            {
                throw new GedcomxDateException("Invalid Recurring Date");
            }

            if (date[0] != 'R')
            {
                throw new GedcomxDateException("Invalid Recurring Date: Must start with R");
            }

            String[] parts = date.Split('/');

            if (parts.Length != 3)
            {
                throw new GedcomxDateException("Invalid Recurring Date: Must contain 3 parts");
            }

            // We must have a start and end
            if (parts[1].Equals("") || parts[2].Equals(""))
            {
                throw new GedcomxDateException("Invalid Recurring Date: Range must have a start and an end");
            }

            String countNum = parts[0].Substring(1);

            char[] countNumChars = parts[0].Substring(1).ToCharArray();

            if (countNumChars.Length > 0)
            {
                foreach (char c in countNumChars)
                {
                    if (!Char.IsDigit(c))
                    {
                        throw new GedcomxDateException("Invalid Recurring Date: Malformed Count");
                    }
                }
                count = Int32.Parse(countNum);
            }
            try
            {
                range = new GedcomxDateRange(parts[1] + "/" + parts[2]);
            }
            catch (GedcomxDateException e)
            {
                throw new GedcomxDateException(e.Message + " in Recurring Range");
            }

            // If we have a count set end
            if (count != null)
            {
                end = GetNth(count.Value);
            }
        }
        /// <summary>
        /// Initializes a new instance of the <see cref="GedcomxDateRecurring"/> class.
        /// </summary>
        /// <param name="date">The recurring date string that describes a recurring GEDCOM X date.</param>
        /// <exception cref="Gedcomx.Date.GedcomxDateException">
        /// Thrown if the formal date string is null, empty, or fails to meet the expected format. The specific reason will be given at runtime.
        /// </exception>
        public GedcomxDateRecurring(String date)
        {
            if (date == null || date.Length < 3)
            {
                throw new GedcomxDateException("Invalid Recurring Date");
            }

            if (date[0] != 'R')
            {
                throw new GedcomxDateException("Invalid Recurring Date: Must start with R");
            }

            String[] parts = date.Split('/');

            if (parts.Length != 3)
            {
                throw new GedcomxDateException("Invalid Recurring Date: Must contain 3 parts");
            }

            // We must have a start and end
            if (parts[1].Equals("") || parts[2].Equals(""))
            {
                throw new GedcomxDateException("Invalid Recurring Date: Range must have a start and an end");
            }

            String countNum = parts[0].Substring(1);
            char[] countNumChars = parts[0].Substring(1).ToCharArray();

            if (countNumChars.Length > 0)
            {
                foreach (char c in countNumChars)
                {
                    if (!Char.IsDigit(c))
                    {
                        throw new GedcomxDateException("Invalid Recurring Date: Malformed Count");
                    }
                }
                count = Int32.Parse(countNum);
            }
            try
            {
                range = new GedcomxDateRange(parts[1] + "/" + parts[2]);
            }
            catch (GedcomxDateException e)
            {
                throw new GedcomxDateException(e.Message + " in Recurring Range");
            }

            // If we have a count set end
            if (count != null)
            {
                end = GetNth(count.Value);
            }
        }
Exemplo n.º 5
0
            /// <summary>
            /// Initializes a new instance of the <see cref="Date"/> class.
            /// </summary>
            /// <param name="simple">The simple GEDCOM X date.</param>
            /// <param name="adjustTimezone">
            /// If set to <c>true</c> the resulting date hours and minutes will have the timezone hours and minutes added. See remarks for more information.
            /// </param>
            /// <remarks>
            /// The timezone adjustment is only applied when the adjustTimezone parameter is <c>true</c> and the incoming simple date hours and minutes are set. Thus,
            /// if the simple date hours are null, no adjustment will be made to the hours. Likewise, if the simple date minutes are null, no adjustment will be made
            /// to minutes.
            /// </remarks>
            public Date(GedcomxDateSimple simple, bool adjustTimezone)
            {
                year    = simple.Year;
                month   = simple.Month;
                day     = simple.Day;
                hours   = simple.Hours;
                minutes = simple.Minutes;
                seconds = simple.Seconds;

                if (adjustTimezone)
                {
                    if (hours != null && simple.TzHours != null)
                    {
                        hours += simple.TzHours;
                    }

                    if (minutes != null && simple.TzMinutes != null)
                    {
                        minutes += simple.TzMinutes;
                    }
                }
            }
Exemplo n.º 6
0
        /// <summary>
        /// Initializes a new instance of the <see cref="GedcomxDateRange"/> class.
        /// </summary>
        /// <param name="date">The formal date string that describes a GEDCOM X date range.</param>
        /// <exception cref="Gedcomx.Date.GedcomxDateException">
        /// Thrown if the formal date is null, empty, or does not meet the expected format.
        /// </exception>
        public GedcomxDateRange(String date)
        {

            if (date == null || date.Length < 1)
            {
                throw new GedcomxDateException("Invalid Range");
            }

            String range = date;

            // If range starts with A it is recurring
            if (date[0] == 'A')
            {
                approximate = true;
                range = date.Substring(1);
            }

            // / is required
            if (!date.Contains("/"))
            {
                throw new GedcomxDateException("Invalid Range: / is required");
            }

            /*
             * range -> parts
             * / -> []
             * +1000/ -> ["+1000"]
             * /+1000 -> ["","+1000"]
             * +1000/+2000 -> ["+1000","+2000"]
             */
            String[] parts = range.Split('/');

            if (parts.Length < 1 || parts.Length > 2)
            {
                throw new GedcomxDateException("Invalid Range: One or two parts are required");
            }

            if (!parts[0].Equals(""))
            {
                try
                {
                    start = new GedcomxDateSimple(parts[0]);
                }
                catch (GedcomxDateException e)
                {
                    throw new GedcomxDateException(e.Message + " in Range Start Date");
                }
            }

            if (parts.Length == 2)
            {
                if (parts[1][0] == 'P')
                {
                    if (start == null)
                    {
                        throw new GedcomxDateException("Invalid Range: A range may not end with a duration if missing a start date");
                    }
                    try
                    {
                        duration = new GedcomxDateDuration(parts[1]);
                    }
                    catch (GedcomxDateException e)
                    {
                        throw new GedcomxDateException(e.Message + " in Range End Duration");
                    }
                    // Use the duration to calculate the end date
                    end = GedcomxDateUtil.AddDuration(start, duration);
                }
                else
                {
                    try
                    {
                        end = new GedcomxDateSimple(parts[1]);
                    }
                    catch (GedcomxDateException e)
                    {
                        throw new GedcomxDateException(e.Message + " in Range End Date");
                    }
                    if (start != null)
                    {
                        duration = GedcomxDateUtil.GetDuration(start, end);
                    }
                }
            }
        }
Exemplo n.º 7
0
        /// <summary>
        /// Initializes a new instance of the <see cref="GedcomxDateRange"/> class.
        /// </summary>
        /// <param name="date">The formal date string that describes a GEDCOM X date range.</param>
        /// <exception cref="Gedcomx.Date.GedcomxDateException">
        /// Thrown if the formal date is null, empty, or does not meet the expected format.
        /// </exception>
        public GedcomxDateRange(String date)
        {
            if (date == null || date.Length < 1)
            {
                throw new GedcomxDateException("Invalid Range");
            }

            String range = date;

            // If range starts with A it is recurring
            if (date[0] == 'A')
            {
                approximate = true;
                range       = date.Substring(1);
            }

            // / is required
            if (!date.Contains("/"))
            {
                throw new GedcomxDateException("Invalid Range: / is required");
            }

            /*
             * range -> parts
             * / -> []
             * +1000/ -> ["+1000"]
             * /+1000 -> ["","+1000"]
             * +1000/+2000 -> ["+1000","+2000"]
             */
            String[] parts = range.Split('/');

            if (parts.Length < 1 || parts.Length > 2)
            {
                throw new GedcomxDateException("Invalid Range: One or two parts are required");
            }

            if (!parts[0].Equals(""))
            {
                try
                {
                    start = new GedcomxDateSimple(parts[0]);
                }
                catch (GedcomxDateException e)
                {
                    throw new GedcomxDateException(e.Message + " in Range Start Date");
                }
            }

            if (parts.Length == 2)
            {
                if (parts[1][0] == 'P')
                {
                    if (start == null)
                    {
                        throw new GedcomxDateException("Invalid Range: A range may not end with a duration if missing a start date");
                    }
                    try
                    {
                        duration = new GedcomxDateDuration(parts[1]);
                    }
                    catch (GedcomxDateException e)
                    {
                        throw new GedcomxDateException(e.Message + " in Range End Duration");
                    }
                    // Use the duration to calculate the end date
                    end = GedcomxDateUtil.AddDuration(start, duration);
                }
                else
                {
                    try
                    {
                        end = new GedcomxDateSimple(parts[1]);
                    }
                    catch (GedcomxDateException e)
                    {
                        throw new GedcomxDateException(e.Message + " in Range End Date");
                    }
                    if (start != null)
                    {
                        duration = GedcomxDateUtil.GetDuration(start, end);
                    }
                }
            }
        }
Exemplo n.º 8
0
        /// <summary>
        /// Gets the duration between the two specified GEDCOM X dates.
        /// </summary>
        /// <param name="startDate">The simple start date.</param>
        /// <param name="endDate">The simple end date.</param>
        /// <returns>A <see cref="GedcomxDateDuration"/> representing the duration between the two specified dates.</returns>
        /// <exception cref="Gedcomx.Date.GedcomxDateException">
        /// Thrown if one of the input dates is null
        /// or
        /// Thrown if the start date occurs after the end date, or is equal to the end time.
        /// </exception>
        public static GedcomxDateDuration GetDuration(GedcomxDateSimple startDate, GedcomxDateSimple endDate)
        {

            if (startDate == null || endDate == null)
            {
                throw new GedcomxDateException("Start and End must be simple dates");
            }

            Date start = new Date(startDate, true);
            Date end = new Date(endDate, true);
            bool hasTime = false;
            StringBuilder duration = new StringBuilder();

            ZipDates(start, end);

            // Build the duration backwards so we can grab the correct diff
            // Also we need to roll everything up so we don't generate an invalid max year
            if (end.seconds != null)
            {
                while (end.seconds - start.seconds < 0)
                {
                    end.minutes -= 1;
                    end.seconds += 60;
                }
                if (end.seconds - start.seconds > 0)
                {
                    hasTime = true;
                    duration.Insert(0, 'S').Insert(0, String.Format("{0:00}", end.seconds - start.seconds));
                }
            }

            if (end.minutes != null)
            {
                while (end.minutes - start.minutes < 0)
                {
                    end.hours -= 1;
                    end.minutes += 60;
                }
                if (end.minutes - start.minutes > 0)
                {
                    hasTime = true;
                    duration.Insert(0, 'M').Insert(0, String.Format("{0:00}", end.minutes - start.minutes));
                }
            }

            if (end.hours != null)
            {
                while (end.hours - start.hours < 0)
                {
                    end.day -= 1;
                    end.hours += 24;
                }
                if (end.hours - start.hours > 0)
                {
                    hasTime = true;
                    duration.Insert(0, 'H').Insert(0, String.Format("{0:00}", end.hours - start.hours));
                }
            }

            if (hasTime)
            {
                duration.Insert(0, 'T');
            }

            if (end.day != null)
            {
                while (end.day - start.day < 0)
                {
                    end.day += DateTime.DaysInMonth(end.year.Value, end.month.Value);
                    end.month -= 1;
                    if (end.month < 1)
                    {
                        end.year -= 1;
                        end.month += 12;
                    }
                }
                if (end.day - start.day > 0)
                {
                    duration.Insert(0, 'D').Insert(0, String.Format("{0:00}", end.day - start.day));
                }
            }

            if (end.month != null)
            {
                while (end.month - start.month < 0)
                {
                    end.year -= 1;
                    end.month += 12;
                }
                if (end.month - start.month > 0)
                {
                    duration.Insert(0, 'M').Insert(0, String.Format("{0:00}", end.month - start.month));
                }
            }

            if (end.year - start.year > 0)
            {
                duration.Insert(0, 'Y').Insert(0, String.Format("%04d", end.year - start.year));
            }

            String finalDuration = duration.ToString();

            if (end.year - start.year < 0 || duration.Equals(""))
            {
                throw new GedcomxDateException("Start Date must be less than End Date");
            }

            return new GedcomxDateDuration("P" + finalDuration);
        }
Exemplo n.º 9
0
            /// <summary>
            /// Initializes a new instance of the <see cref="Date"/> class.
            /// </summary>
            /// <param name="simple">The simple GEDCOM X date.</param>
            /// <param name="adjustTimezone">
            /// If set to <c>true</c> the resulting date hours and minutes will have the timezone hours and minutes added. See remarks for more information.
            /// </param>
            /// <remarks>
            /// The timezone adjustment is only applied when the adjustTimezone parameter is <c>true</c> and the incoming simple date hours and minutes are set. Thus,
            /// if the simple date hours are null, no adjustment will be made to the hours. Likewise, if the simple date minutes are null, no adjustment will be made
            /// to minutes.
            /// </remarks>
            public Date(GedcomxDateSimple simple, bool adjustTimezone)
            {
                year = simple.Year;
                month = simple.Month;
                day = simple.Day;
                hours = simple.Hours;
                minutes = simple.Minutes;
                seconds = simple.Seconds;

                if (adjustTimezone)
                {
                    if (hours != null && simple.TzHours != null)
                    {
                        hours += simple.TzHours;
                    }

                    if (minutes != null && simple.TzMinutes != null)
                    {
                        minutes += simple.TzMinutes;
                    }
                }
            }
Exemplo n.º 10
0
        /// <summary>
        /// Adds a duration to the specified simple date and returns the resulting simple date.
        /// </summary>
        /// <param name="startDate">The start date that will have the specified duration added.</param>
        /// <param name="duration">The duration to add to the specified simple date.</param>
        /// <returns>The <see cref="GedcomxDateSimple"/> date resulting from adding the duration to the specified date. </returns>
        /// <exception cref="Gedcomx.Date.GedcomxDateException">
        /// Throw if the start date is null
        /// or
        /// Thrown if the duration is null
        /// or
        /// Thrown if the resulting end year is beyond 9999.
        /// </exception>
        public static GedcomxDateSimple AddDuration(GedcomxDateSimple startDate, GedcomxDateDuration duration)
        {

            if (startDate == null)
            {
                throw new GedcomxDateException("Invalid Start Date");
            }

            if (duration == null)
            {
                throw new GedcomxDateException("Invalid Duration");
            }

            Date end = new Date(startDate, false);
            StringBuilder endString = new StringBuilder();

            // Initialize all the values we need in end based on the duration
            ZipDuration(end, duration);

            // Add Timezone offset to endString
            if (startDate.TzHours != null)
            {
                endString.Append(startDate.TzHours >= 0 ? "+" : "-").Append(String.Format("{0:00}", Math.Abs(startDate.TzHours.Value)));
                endString.Append(":").Append(String.Format("{0:00}", startDate.TzMinutes));
            }

            if (end.seconds != null)
            {
                if (duration.Seconds != null)
                {
                    end.seconds += duration.Seconds;
                }
                while (end.seconds >= 60)
                {
                    end.seconds -= 60;
                    end.minutes += 1;
                }
                endString.Insert(0, String.Format("{0:00}", end.seconds)).Insert(0, ":");
            }

            if (end.minutes != null)
            {
                if (duration.Minutes != null)
                {
                    end.minutes += duration.Minutes;
                }
                while (end.minutes >= 60)
                {
                    end.minutes -= 60;
                    end.hours += 1;
                }
                endString.Insert(0, String.Format("{0:00}", end.minutes)).Insert(0, ":");
            }

            if (end.hours != null)
            {
                if (duration.Hours != null)
                {
                    end.hours += duration.Hours;
                }
                while (end.hours >= 24)
                {
                    end.hours -= 24;
                    end.day += 1;
                }
                endString.Insert(0, String.Format("{0:00}", end.hours)).Insert(0, "T");
            }

            if (end.day != null)
            {
                if (duration.Days != null)
                {
                    end.day += duration.Days;
                }
                while (end.day >= DateTime.DaysInMonth(end.year.Value, end.month.Value))
                {
                    end.month += 1;
                    if (end.month > 12)
                    {
                        end.month -= 12;
                        end.year += 1;
                    }
                    end.day -= DateTime.DaysInMonth(end.year.Value, end.month.Value);
                }
                endString.Insert(0, String.Format("{0:00}", end.day)).Insert(0, "-");
            }

            if (end.month != null)
            {
                if (duration.Months != null)
                {
                    end.month += duration.Months;
                }
                while (end.month > 12)
                {
                    end.month -= 12;
                    end.year += 1;
                }
                endString.Insert(0, String.Format("{0:00}", end.month)).Insert(0, "-");
            }

            if (duration.Years != null)
            {
                end.year += duration.Years;
            }

            // After adding months to this year we could have bumped into or out of a non leap year
            // TODO fix this

            if (end.year > 9999)
            {
                throw new GedcomxDateException("New date out of range");
            }

            if (end.year != null)
            {
                endString.Insert(0, String.Format("%04d", Math.Abs(end.year.Value))).Insert(0, end.year >= 0 ? "+" : "-");
            }

            return new GedcomxDateSimple(endString.ToString());
        }
Exemplo n.º 11
0
        /// <summary>
        /// Gets the duration between the two specified GEDCOM X dates.
        /// </summary>
        /// <param name="startDate">The simple start date.</param>
        /// <param name="endDate">The simple end date.</param>
        /// <returns>A <see cref="GedcomxDateDuration"/> representing the duration between the two specified dates.</returns>
        /// <exception cref="Gedcomx.Date.GedcomxDateException">
        /// Thrown if one of the input dates is null
        /// or
        /// Thrown if the start date occurs after the end date, or is equal to the end time.
        /// </exception>
        public static GedcomxDateDuration GetDuration(GedcomxDateSimple startDate, GedcomxDateSimple endDate)
        {
            if (startDate == null || endDate == null)
            {
                throw new GedcomxDateException("Start and End must be simple dates");
            }

            Date          start    = new Date(startDate, true);
            Date          end      = new Date(endDate, true);
            bool          hasTime  = false;
            StringBuilder duration = new StringBuilder();

            ZipDates(start, end);

            // Build the duration backwards so we can grab the correct diff
            // Also we need to roll everything up so we don't generate an invalid max year
            if (end.seconds != null)
            {
                while (end.seconds - start.seconds < 0)
                {
                    end.minutes -= 1;
                    end.seconds += 60;
                }
                if (end.seconds - start.seconds > 0)
                {
                    hasTime = true;
                    duration.Insert(0, 'S').Insert(0, String.Format("{0:00}", end.seconds - start.seconds));
                }
            }

            if (end.minutes != null)
            {
                while (end.minutes - start.minutes < 0)
                {
                    end.hours   -= 1;
                    end.minutes += 60;
                }
                if (end.minutes - start.minutes > 0)
                {
                    hasTime = true;
                    duration.Insert(0, 'M').Insert(0, String.Format("{0:00}", end.minutes - start.minutes));
                }
            }

            if (end.hours != null)
            {
                while (end.hours - start.hours < 0)
                {
                    end.day   -= 1;
                    end.hours += 24;
                }
                if (end.hours - start.hours > 0)
                {
                    hasTime = true;
                    duration.Insert(0, 'H').Insert(0, String.Format("{0:00}", end.hours - start.hours));
                }
            }

            if (hasTime)
            {
                duration.Insert(0, 'T');
            }

            if (end.day != null)
            {
                while (end.day - start.day < 0)
                {
                    end.day   += DateTime.DaysInMonth(end.year.Value, end.month.Value);
                    end.month -= 1;
                    if (end.month < 1)
                    {
                        end.year  -= 1;
                        end.month += 12;
                    }
                }
                if (end.day - start.day > 0)
                {
                    duration.Insert(0, 'D').Insert(0, String.Format("{0:00}", end.day - start.day));
                }
            }

            if (end.month != null)
            {
                while (end.month - start.month < 0)
                {
                    end.year  -= 1;
                    end.month += 12;
                }
                if (end.month - start.month > 0)
                {
                    duration.Insert(0, 'M').Insert(0, String.Format("{0:00}", end.month - start.month));
                }
            }

            if (end.year - start.year > 0)
            {
                duration.Insert(0, 'Y').Insert(0, String.Format("%04d", end.year - start.year));
            }

            String finalDuration = duration.ToString();

            if (end.year - start.year < 0 || duration.Equals(""))
            {
                throw new GedcomxDateException("Start Date must be less than End Date");
            }

            return(new GedcomxDateDuration("P" + finalDuration));
        }
Exemplo n.º 12
0
        /// <summary>
        /// Adds a duration to the specified simple date and returns the resulting simple date.
        /// </summary>
        /// <param name="startDate">The start date that will have the specified duration added.</param>
        /// <param name="duration">The duration to add to the specified simple date.</param>
        /// <returns>The <see cref="GedcomxDateSimple"/> date resulting from adding the duration to the specified date. </returns>
        /// <exception cref="Gedcomx.Date.GedcomxDateException">
        /// Throw if the start date is null
        /// or
        /// Thrown if the duration is null
        /// or
        /// Thrown if the resulting end year is beyond 9999.
        /// </exception>
        public static GedcomxDateSimple AddDuration(GedcomxDateSimple startDate, GedcomxDateDuration duration)
        {
            if (startDate == null)
            {
                throw new GedcomxDateException("Invalid Start Date");
            }

            if (duration == null)
            {
                throw new GedcomxDateException("Invalid Duration");
            }

            Date          end       = new Date(startDate, false);
            StringBuilder endString = new StringBuilder();

            // Initialize all the values we need in end based on the duration
            ZipDuration(end, duration);

            // Add Timezone offset to endString
            if (startDate.TzHours != null)
            {
                endString.Append(startDate.TzHours >= 0 ? "+" : "-").Append(String.Format("{0:00}", Math.Abs(startDate.TzHours.Value)));
                endString.Append(":").Append(String.Format("{0:00}", startDate.TzMinutes));
            }

            if (end.seconds != null)
            {
                if (duration.Seconds != null)
                {
                    end.seconds += duration.Seconds;
                }
                while (end.seconds >= 60)
                {
                    end.seconds -= 60;
                    end.minutes += 1;
                }
                endString.Insert(0, String.Format("{0:00}", end.seconds)).Insert(0, ":");
            }

            if (end.minutes != null)
            {
                if (duration.Minutes != null)
                {
                    end.minutes += duration.Minutes;
                }
                while (end.minutes >= 60)
                {
                    end.minutes -= 60;
                    end.hours   += 1;
                }
                endString.Insert(0, String.Format("{0:00}", end.minutes)).Insert(0, ":");
            }

            if (end.hours != null)
            {
                if (duration.Hours != null)
                {
                    end.hours += duration.Hours;
                }
                while (end.hours >= 24)
                {
                    end.hours -= 24;
                    end.day   += 1;
                }
                endString.Insert(0, String.Format("{0:00}", end.hours)).Insert(0, "T");
            }

            if (end.day != null)
            {
                if (duration.Days != null)
                {
                    end.day += duration.Days;
                }
                while (end.day >= DateTime.DaysInMonth(end.year.Value, end.month.Value))
                {
                    end.month += 1;
                    if (end.month > 12)
                    {
                        end.month -= 12;
                        end.year  += 1;
                    }
                    end.day -= DateTime.DaysInMonth(end.year.Value, end.month.Value);
                }
                endString.Insert(0, String.Format("{0:00}", end.day)).Insert(0, "-");
            }

            if (end.month != null)
            {
                if (duration.Months != null)
                {
                    end.month += duration.Months;
                }
                while (end.month > 12)
                {
                    end.month -= 12;
                    end.year  += 1;
                }
                endString.Insert(0, String.Format("{0:00}", end.month)).Insert(0, "-");
            }

            if (duration.Years != null)
            {
                end.year += duration.Years;
            }

            // After adding months to this year we could have bumped into or out of a non leap year
            // TODO fix this

            if (end.year > 9999)
            {
                throw new GedcomxDateException("New date out of range");
            }

            if (end.year != null)
            {
                endString.Insert(0, String.Format("%04d", Math.Abs(end.year.Value))).Insert(0, end.year >= 0 ? "+" : "-");
            }

            return(new GedcomxDateSimple(endString.ToString()));
        }