diff --git a/NExtends.Tests/Primitives/DateTimes/DateTests.cs b/NExtends.Tests/Primitives/DateTimes/DateTests.cs new file mode 100644 index 0000000..53d06cb --- /dev/null +++ b/NExtends.Tests/Primitives/DateTimes/DateTests.cs @@ -0,0 +1,353 @@ +/* +Copyright 2013 Clay Anderson +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + http://www.apache.org/licenses/LICENSE-2.0 +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +using NExtends.Context; +using NExtends.Primitives.DateTimes; +using System; +using System.Globalization; +using Xunit; + +namespace NExtends.Tests.Primitives.DateTimes +{ + public class DateTests + { + [Fact] + public void CanConstructDefault() + { + Date d = new Date(); + + Assert.Equal(Date.MinValue.Year, d.Year); + Assert.Equal(Date.MinValue.Month, d.Month); + Assert.Equal(Date.MinValue.Day, d.Day); + Assert.Equal(Date.MinValue.DayOfWeek, d.DayOfWeek); + Assert.Equal(Date.MinValue.DayOfYear, d.DayOfYear); + } + + [Fact] + public void CanConstruct() + { + Date d = new Date(2013, 4, 5); + + Assert.Equal(2013, d.Year); + Assert.Equal(4, d.Month); + Assert.Equal(5, d.Day); + Assert.Equal(DayOfWeek.Friday, d.DayOfWeek); + Assert.Equal(31 + 28 + 31 + 5, d.DayOfYear); + Assert.Equal(635007168000000000, d.Ticks); + } + + [Fact] + public void CanConstructFromDateTime() + { + Date d1 = new Date(new DateTime(2001, 2, 3, 4, 5, 6, 7).AddTicks(8)); + Date d2 = new Date(2001, 2, 3); + Assert.Equal(d1, d2); + Assert.True(d1.Equals(d2)); + } + + [Fact] + public void ToDateRemovesAllTimePortion() + { + Date d1 = new DateTime(2001, 2, 3, 4, 5, 6, 7).AddTicks(8).ToDate(); + Date d2 = new Date(2001, 2, 3); + Assert.Equal(d1, d2); + Assert.True(d1.Equals(d2)); + } + + [Fact] + public void CanGetDateFromDateTime() + { + DateTime dt = new DateTime(2013, 4, 5, 6, 7, 8); + Date d = dt.ToDate(); + + Assert.Equal(dt.Year, d.Year); + Assert.Equal(dt.Month, d.Month); + Assert.Equal(dt.Day, d.Day); + } + + [Fact] + public void CanAddYears() + { + Date d1 = new Date(2013, 1, 30); + Date d2 = d1.AddYears(3); + + Assert.Equal(2016, d2.Year); + Assert.Equal(1, d2.Month); + Assert.Equal(30, d2.Day); + } + + [Fact] + public void CanAddMonths() + { + Date d1 = new Date(2013, 2, 12); + Date d2 = d1.AddMonths(4); + + Assert.Equal(2013, d2.Year); + Assert.Equal(6, d2.Month); + Assert.Equal(12, d2.Day); + } + + [Fact] + public void CanAddDays() + { + Date d1 = new Date(2012, 2, 29); + Date d2 = d1.AddDays(1); + + Assert.Equal(2012, d2.Year); + Assert.Equal(3, d2.Month); + Assert.Equal(1, d2.Day); + } + + [Fact] + public void CanAddTimeSpanWithoutTime() + { + Date d1 = new Date(2013, 4, 5); + Date d2 = d1 + TimeSpan.FromDays(3); + + Assert.Equal(2013, d2.Year); + Assert.Equal(4, d2.Month); + Assert.Equal(8, d2.Day); + } + + [Fact] + public void CanAddTimeSpanWithTime() + { + Date d1 = new Date(2013, 4, 5); + Date d2 = d1 + new TimeSpan(3, 4, 5, 6); + + Assert.Equal(2013, d2.Year); + Assert.Equal(4, d2.Month); + Assert.Equal(8, d2.Day); + } + + [Fact] + public void CanSubtractTimeSpanWithoutTime() + { + Date d1 = new Date(2013, 4, 5); + Date d2 = d1 - TimeSpan.FromDays(3); + + Assert.Equal(2013, d2.Year); + Assert.Equal(4, d2.Month); + Assert.Equal(2, d2.Day); + } + + [Fact] + public void CanSubtractTimeSpanWithTime() + { + Date d1 = new Date(2013, 4, 5); + Date d2 = d1 - new TimeSpan(3, 4, 5, 6); + + Assert.Equal(2013, d2.Year); + Assert.Equal(4, d2.Month); + Assert.Equal(1, d2.Day); + } + + [Fact] + public void CanSubtractDates() + { + Date d1 = new Date(2013, 4, 5); + Date d2 = new Date(2013, 4, 7); + TimeSpan ts = d2 - d1; + + Assert.Equal(2, ts.Days); + Assert.Equal(0, ts.Hours); + Assert.Equal(0, ts.Minutes); + Assert.Equal(0, ts.Seconds); + Assert.Equal(0, ts.Milliseconds); + } + + [Fact] + public void CanCompareDates() + { + Date d1 = new Date(2013, 4, 5); + Date d2 = new Date(2013, 4, 5); + Date d3 = new Date(2014, 4, 8); + + Assert.True(d1 == d2); + Assert.True(d1 != d3); + Assert.True(d1 <= d2); + Assert.True(d1 >= d2); + Assert.True(d1 < d1.AddDays(3)); + Assert.True(d1 < d1.AddMonths(4)); + Assert.True(d1 < d1.AddYears(5)); + Assert.True(d1 <= d1.AddDays(3)); + Assert.True(d1 <= d1.AddMonths(4)); + Assert.True(d1 <= d1.AddYears(5)); + Assert.True(d1 > d1.AddDays(-3)); + Assert.True(d1 > d1.AddMonths(-4)); + Assert.True(d1 > d1.AddYears(-5)); + Assert.True(d1 >= d1.AddDays(-3)); + Assert.True(d1 >= d1.AddMonths(-4)); + Assert.True(d1 >= d1.AddYears(-5)); + } + + [Fact] + public void CanImplicitCastToDateTime() + { + Date d = new Date(2013, 4, 5); + DateTime dt = d; + + Assert.Equal(d.Year, dt.Year); + Assert.Equal(d.Month, dt.Month); + Assert.Equal(d.Day, dt.Day); + Assert.Equal(d.DayOfWeek, dt.DayOfWeek); + Assert.Equal(d.DayOfYear, dt.DayOfYear); + Assert.Equal(0, dt.Hour); + Assert.Equal(0, dt.Minute); + Assert.Equal(0, dt.Second); + Assert.Equal(0, dt.Millisecond); + } + + [Fact] + public void CanExplicitCastToDateTime() + { + DateTime dt = new DateTime(2000, 1, 2, 3, 4, 5); + Date d = (Date)dt; + + Assert.Equal(dt.Year, d.Year); + Assert.Equal(dt.Month, d.Month); + Assert.Equal(dt.Day, d.Day); + } + + [Fact] + public void CanToShortString() + { + using (new CultureContext(CultureInfo.InvariantCulture)) + { + Date d = new Date(2013, 4, 5); + string s = d.ToShortString(); + + Assert.Equal("04/05/2013", s); + } + } + + [Fact] + public void CanToLongString() + { + using (new CultureContext(CultureInfo.InvariantCulture)) + { + Date d = new Date(2013, 4, 5); + string s = d.ToLongString(); + + Assert.Equal("Friday, 05 April 2013", s); + } + } + + [Fact] + public void CanToString() + { + using (new CultureContext(CultureInfo.InvariantCulture)) + { + Date d = new Date(2013, 4, 5); + string s = d.ToString(); + + Assert.Equal("04/05/2013", s); + } + } + + [Fact] + public void CanParse() + { + using (new CultureContext(CultureInfo.InvariantCulture)) + { + Date d = Date.Parse("2013-04-05"); + + Assert.Equal(2013, d.Year); + Assert.Equal(4, d.Month); + Assert.Equal(5, d.Day); + Assert.Equal(DayOfWeek.Friday, d.DayOfWeek); + Assert.Equal(31 + 28 + 31 + 5, d.DayOfYear); + } + } + + [Fact] + public void CanParseWithTime() + { + using (new CultureContext(CultureInfo.InvariantCulture)) + { + Date d = Date.Parse("2013-04-05 6:07:08 PM"); + + Assert.Equal(2013, d.Year); + Assert.Equal(4, d.Month); + Assert.Equal(5, d.Day); + Assert.Equal(DayOfWeek.Friday, d.DayOfWeek); + Assert.Equal(31 + 28 + 31 + 5, d.DayOfYear); + } + } + + [Fact] + public void CantParseInvalidString() + { + using (new CultureContext(CultureInfo.InvariantCulture)) + { + Assert.Throws(() => Date.Parse("abc")); + } + } + + [Fact] + public void CanTryParseValidString() + { + using (new CultureContext(CultureInfo.InvariantCulture)) + { + Date d; + bool successs = Date.TryParse("2013-04-05", out d); + + Assert.True(successs); + Assert.Equal(2013, d.Year); + Assert.Equal(4, d.Month); + Assert.Equal(5, d.Day); + Assert.Equal(DayOfWeek.Friday, d.DayOfWeek); + Assert.Equal(31 + 28 + 31 + 5, d.DayOfYear); + } + } + + [Fact] + public void CanTryParseInalidString() + { + using (new CultureContext(CultureInfo.InvariantCulture)) + { + Date d; + bool success = Date.TryParse("abc", out d); + + Assert.False(success); + } + } + + [Fact] + public void CanPresentRoundTripPattern() + { + using (new CultureContext(CultureInfo.InvariantCulture)) + { + Date d = new Date(2013, 4, 5); + string s1 = d.ToString("O"); + string s2 = d.ToString("o"); + + Assert.Equal(s1, s2); + Assert.Equal("2013-04-05", s1); + Assert.Equal("2013-04-05", s2); + } + } + + [Fact] + public void CanPresentSortablePattern() + { + using (new CultureContext(CultureInfo.InvariantCulture)) + { + Date d = new Date(2013, 4, 5); + string s = d.ToString("s"); + + Assert.Equal("2013-04-05", s); + } + } + } +} \ No newline at end of file diff --git a/NExtends.Tests/Primitives/DateTimes/PeriodTests.cs b/NExtends.Tests/Primitives/DateTimes/PeriodTests.cs index d412dcb..edf2291 100644 --- a/NExtends.Tests/Primitives/DateTimes/PeriodTests.cs +++ b/NExtends.Tests/Primitives/DateTimes/PeriodTests.cs @@ -1,7 +1,5 @@ using NExtends.Primitives.DateTimes; using System; -using System.Collections.Generic; -using System.Text; using Xunit; namespace NExtends.Tests.Primitives.DateTimes @@ -16,8 +14,8 @@ public void PeriodShouldWorkOnDates() var period = new Period(startsOn, endsOn); - Assert.Equal(startsOn, period.Start); - Assert.Equal(endsOn, period.End); + Assert.Equal(startsOn, period.StartsAt); + Assert.Equal(endsOn, period.EndsAt); } [Fact] @@ -28,8 +26,77 @@ public void PeriodShouldWorkOnDateTimes() var period = new Period(startsAt, endsAt); - Assert.Equal(startsAt, period.Start); - Assert.Equal(endsAt, period.End); + Assert.Equal(startsAt, period.StartsAt); + Assert.Equal(endsAt, period.EndsAt); + } + + [Fact] + public void PeriodCannotBeNegative() + { + Assert.Throws(() => + { + var period = new Period(new DateTime(2018, 10, 30), new DateTime(2018, 10, 28)); + }); + } + + [Fact] + public void PeriodCanBeZeroSize() + { + var startAndEnd = new DateTime(2018, 10, 30); + + var period = new Period(startAndEnd, startAndEnd); + } + + [Fact] + public void ITimeBlockEndCannotBeModifiedToBeNegative() + { + var startAndEnd = new DateTime(2018, 10, 30); + + ITimeBlock period = new Period(startAndEnd, startAndEnd); + + Assert.Throws(() => + { + period.ChangeEndsAt(new DateTime(2018, 10, 28)); + }); + } + + [Fact] + public void ITimeBlockStartCannotBeModifiedToBeNegative() + { + var startAndEnd = new DateTime(2018, 10, 28); + + ITimeBlock period = new Period(startAndEnd, startAndEnd); + + Assert.Throws(() => + { + period.ChangeStartsAt(new DateTime(2018, 10, 30)); + }); + } + + [Fact] + public void ITimeBlockDurationCannotBeModifiedToBeNegative() + { + var startAndEnd = new DateTime(2018, 10, 28); + + ITimeBlock period = new Period(startAndEnd, startAndEnd); + + Assert.Throws(() => + { + period.ChangeDuration(TimeSpan.FromSeconds(-1)); + }); + } + + [Fact] + public void ITimeBlockDurationCanBeDifferentFromEndMinusStart() + { + var startAndEnd = new DateTime(2018, 10, 28); + + ITimeBlock period = new Period(startAndEnd, startAndEnd); + + period = period.ChangeDuration(TimeSpan.FromSeconds(1)); + + Assert.Equal(TimeSpan.FromSeconds(1), period.Duration); + Assert.Equal(TimeSpan.FromSeconds(0), period.EndsAt - period.StartsAt); } } } diff --git a/NExtends.Tests/Primitives/Strings/StringExtensionsTests.cs b/NExtends.Tests/Primitives/Strings/StringExtensionsTests.cs index 2d66125..1ca5b85 100644 --- a/NExtends.Tests/Primitives/Strings/StringExtensionsTests.cs +++ b/NExtends.Tests/Primitives/Strings/StringExtensionsTests.cs @@ -67,6 +67,7 @@ public void ContainsIgnoreCaseShouldWork(string match, string chain) Assert.True(result); } + [Theory] [InlineData("toto")] [InlineData(null)] [InlineData("")] diff --git a/NExtends.Tests/Primitives/TimeSpans/TimeSpan.extensions.tests.cs b/NExtends.Tests/Primitives/TimeSpans/TimeSpan.extensions.tests.cs index 3aeeef6..3e9c7d1 100644 --- a/NExtends.Tests/Primitives/TimeSpans/TimeSpan.extensions.tests.cs +++ b/NExtends.Tests/Primitives/TimeSpans/TimeSpan.extensions.tests.cs @@ -1,5 +1,6 @@ using NExtends.Primitives.TimeSpans; using System; +using System.Globalization; using Xunit; namespace NExtends.Tests.Primitives.TimeSpans @@ -20,10 +21,10 @@ public void TimespanMultiply() [Theory] [InlineData(0, true, "-")] [InlineData(0, false, "-")] - [InlineData(9, true, "+09mn")] - [InlineData(9, false, "09mn")] - [InlineData(-9, true, "-09mn")] - [InlineData(-9, false, "-09mn")] + [InlineData(9, true, "+09m")] + [InlineData(9, false, "09m")] + [InlineData(-9, true, "-09m")] + [InlineData(-9, false, "-09m")] [InlineData(69, true, "+1h09")] [InlineData(69, false, "1h09")] [InlineData(-69, true, "-1h09")] @@ -32,8 +33,8 @@ public void TimespanMultiply() public void TimeSpanToHoursShouldWork(int timeInMinutes, bool showSign, string expected) { var timespan = TimeSpan.FromMinutes(timeInMinutes); - var initials = new TimeInitials("mn", "h", "j"); - var result = TimeSpanExtensions.ToHours(timespan, initials, showSign); + var culture = CultureInfo.GetCultureInfo("fr-FR"); + var result = TimeSpanExtensions.ToHours(timespan, culture, showSign); Assert.Equal(expected, result); } @@ -53,8 +54,8 @@ public void TimeSpanToHoursShouldWork(int timeInMinutes, bool showSign, string e public void TimeSpanToDaysShouldWork(int timeInMinutes, bool showSign, string expected) { var timespan = TimeSpan.FromMinutes(timeInMinutes); - var initials = new TimeInitials("mn", "h", "j"); - var result = TimeSpanExtensions.ToDays(timespan, initials, showSign); + var culture = CultureInfo.GetCultureInfo("fr-FR"); + var result = TimeSpanExtensions.ToDays(timespan, culture, showSign); Assert.Equal(expected, result); } diff --git a/NExtends/NExtends.csproj b/NExtends/NExtends.csproj index ea2471c..ac527b5 100644 --- a/NExtends/NExtends.csproj +++ b/NExtends/NExtends.csproj @@ -3,11 +3,12 @@ netstandard2.0 {6319D598-FF7C-4B48-8C46-53F1E87C8220} + 2.3.7 - + diff --git a/NExtends/Primitives/DateTimes/Date.cs b/NExtends/Primitives/DateTimes/Date.cs new file mode 100644 index 0000000..c57b211 --- /dev/null +++ b/NExtends/Primitives/DateTimes/Date.cs @@ -0,0 +1,337 @@ +/* +The MIT License (MIT) + +Copyright (c) 2015 Clay Anderson + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +*/ + +using System; +using System.Globalization; +using System.Runtime.Serialization; + +namespace NExtends.Primitives.DateTimes +{ + [Serializable] + public struct Date : IComparable, IFormattable, ISerializable, IComparable, IEquatable + { + private readonly DateTime _dt; + + public static readonly Date MaxValue = new Date(DateTime.MaxValue); + public static readonly Date MinValue = new Date(DateTime.MinValue); + + public Date(int year, int month, int day) + { + this._dt = new DateTime(year, month, day); + } + + public Date(DateTime dateTime) + { + this._dt = dateTime.AddTicks(-dateTime.Ticks % TimeSpan.TicksPerDay); + } + + private Date(SerializationInfo info, StreamingContext context) + { + this._dt = DateTime.FromFileTime(info.GetInt64("ticks")); + } + + public static TimeSpan operator -(Date d1, Date d2) + { + return d1._dt - d2._dt; + } + + public static Date operator -(Date d, TimeSpan t) + { + return new Date(d._dt - t); + } + + public static bool operator !=(Date d1, Date d2) + { + return d1._dt != d2._dt; + } + + public static Date operator +(Date d, TimeSpan t) + { + return new Date(d._dt + t); + } + + public static bool operator <(Date d1, Date d2) + { + return d1._dt < d2._dt; + } + + public static bool operator <=(Date d1, Date d2) + { + return d1._dt <= d2._dt; + } + + public static bool operator ==(Date d1, Date d2) + { + return d1._dt == d2._dt; + } + + public static bool operator >(Date d1, Date d2) + { + return d1._dt > d2._dt; + } + + public static bool operator >=(Date d1, Date d2) + { + return d1._dt >= d2._dt; + } + + public static implicit operator DateTime(Date d) + { + return d._dt; + } + + public static explicit operator Date(DateTime d) + { + return new Date(d); + } + + public int Day + { + get + { + return this._dt.Day; + } + } + + public DayOfWeek DayOfWeek + { + get + { + return this._dt.DayOfWeek; + } + } + + public int DayOfYear + { + get + { + return this._dt.DayOfYear; + } + } + + public int Month + { + get + { + return this._dt.Month; + } + } + + public static Date Today + { + get + { + return new Date(DateTime.Today); + } + } + + public int Year + { + get + { + return this._dt.Year; + } + } + + public long Ticks + { + get + { + return this._dt.Ticks; + } + } + + public Date AddDays(int value) + { + return new Date(this._dt.AddDays(value)); + } + + public Date AddMonths(int value) + { + return new Date(this._dt.AddMonths(value)); + } + + public Date AddYears(int value) + { + return new Date(this._dt.AddYears(value)); + } + + public static int Compare(Date d1, Date d2) + { + return d1.CompareTo(d2); + } + + public int CompareTo(Date other) + { + return this._dt.CompareTo(other._dt); + } + + public int CompareTo(object obj) + { + return this._dt.CompareTo(obj); + } + + public static int DaysInMonth(int year, int month) + { + return DateTime.DaysInMonth(year, month); + } + + public bool Equals(Date other) + { + return this._dt.Equals(other._dt); + } + + public override bool Equals(object obj) + { + return obj is Date && this._dt.Equals(((Date)obj)._dt); + } + + public override int GetHashCode() + { + return this._dt.GetHashCode(); + } + + public static bool Equals(Date d1, Date d2) + { + return d1._dt.Equals(d2._dt); + } + + void ISerializable.GetObjectData(SerializationInfo info, StreamingContext context) + { + info.AddValue("ticks", this._dt.Ticks); + } + + public static bool IsLeapYear(int year) + { + return DateTime.IsLeapYear(year); + } + + public static Date Parse(string s) + { + return new Date(DateTime.Parse(s)); + } + + public static Date Parse(string s, IFormatProvider provider) + { + return new Date(DateTime.Parse(s, provider)); + } + + public static Date Parse(string s, IFormatProvider provider, DateTimeStyles style) + { + return new Date(DateTime.Parse(s, provider, style)); + } + + public static Date ParseExact(string s, string format, IFormatProvider provider) + { + return new Date(DateTime.ParseExact(s, format, provider)); + } + + public static Date ParseExact(string s, string format, IFormatProvider provider, DateTimeStyles style) + { + return new Date(DateTime.ParseExact(s, format, provider, style)); + } + + public static Date ParseExact(string s, string[] formats, IFormatProvider provider, DateTimeStyles style) + { + return new Date(DateTime.ParseExact(s, formats, provider, style)); + } + + public TimeSpan Subtract(Date value) + { + return this - value; + } + + public Date Subtract(TimeSpan value) + { + return this - value; + } + + public string ToLongString() + { + return this._dt.ToLongDateString(); + } + + public string ToShortString() + { + return this._dt.ToShortDateString(); + } + + public override string ToString() + { + return this.ToShortString(); + } + + public string ToString(IFormatProvider provider) + { + return this._dt.ToString(provider); + } + + public string ToString(string format) + { + if (format == "O" || format == "o" || format == "s") + { + return this.ToString("yyyy-MM-dd"); + } + + return this._dt.ToString(format); + } + + public string ToString(string format, IFormatProvider formatProvider) + { + return this._dt.ToString(format, formatProvider); + } + + public static bool TryParse(string s, out Date result) + { + DateTime d; + bool success = DateTime.TryParse(s, out d); + result = new Date(d); + return success; + } + + public static bool TryParse(string s, IFormatProvider provider, DateTimeStyles style, out Date result) + { + DateTime d; + bool success = DateTime.TryParse(s, provider, style, out d); + result = new Date(d); + return success; + } + + public static bool TryParseExact(string s, string format, IFormatProvider provider, DateTimeStyles style, out Date result) + { + DateTime d; + bool success = DateTime.TryParseExact(s, format, provider, style, out d); + result = new Date(d); + return success; + } + + public static bool TryParseExact(string s, string[] formats, IFormatProvider provider, DateTimeStyles style, out Date result) + { + DateTime d; + bool success = DateTime.TryParseExact(s, formats, provider, style, out d); + result = new Date(d); + return success; + } + } +} \ No newline at end of file diff --git a/NExtends/Primitives/DateTimes/DateTime.extensions.cs b/NExtends/Primitives/DateTimes/DateTime.extensions.cs index f50792b..f51670d 100644 --- a/NExtends/Primitives/DateTimes/DateTime.extensions.cs +++ b/NExtends/Primitives/DateTimes/DateTime.extensions.cs @@ -364,13 +364,18 @@ public static List GetPreviousPeriods(this DateTime date, TimeSliceMode // Date we look => last date of previous range var period = GetIncludingPeriod(startOfPeriodSeek.AddDays(-1), timeSliceMode); - periods.Add(new Period(period.Start, period.End)); + periods.Add(new Period(period.StartsAt, period.EndsAt)); // for next iteration - startOfPeriodSeek = period.Start; + startOfPeriodSeek = period.StartsAt; } return periods; } + + public static Date ToDate(this DateTime dt) + { + return new Date(dt); + } } } diff --git a/NExtends/Primitives/DateTimes/ITimeBlock.cs b/NExtends/Primitives/DateTimes/ITimeBlock.cs new file mode 100644 index 0000000..944da28 --- /dev/null +++ b/NExtends/Primitives/DateTimes/ITimeBlock.cs @@ -0,0 +1,15 @@ +using System; + +namespace NExtends.Primitives.DateTimes +{ + public interface ITimeBlock + { + DateTime StartsAt { get; } + DateTime EndsAt { get; } + TimeSpan Duration { get; } + + ITimeBlock ChangeStartsAt(DateTime startsAt); + ITimeBlock ChangeEndsAt(DateTime endsAt); + ITimeBlock ChangeDuration(TimeSpan duration); + } +} diff --git a/NExtends/Primitives/DateTimes/NegativeDurationException.cs b/NExtends/Primitives/DateTimes/NegativeDurationException.cs new file mode 100644 index 0000000..bee50e5 --- /dev/null +++ b/NExtends/Primitives/DateTimes/NegativeDurationException.cs @@ -0,0 +1,15 @@ +using System; +using System.Runtime.Serialization; + +namespace NExtends.Primitives.DateTimes +{ + [Serializable] + public class NegativeDurationException : ArgumentException + { + protected NegativeDurationException(SerializationInfo info, StreamingContext context) + : base(info, context) { } + + public NegativeDurationException(string paramName) + : base("You cannot create or update a period to a negative duration", paramName) { } + } +} diff --git a/NExtends/Primitives/DateTimes/Period.cs b/NExtends/Primitives/DateTimes/Period.cs index d4c88b2..e0ca118 100644 --- a/NExtends/Primitives/DateTimes/Period.cs +++ b/NExtends/Primitives/DateTimes/Period.cs @@ -2,16 +2,78 @@ namespace NExtends.Primitives.DateTimes { - public struct Period + public struct Period : ITimeBlock { - public DateTime Start { get; } - public DateTime End { get; } - public TimeSpan Duration { get { return End - Start; } } + public DateTime StartsAt { get; } + public DateTime EndsAt { get; } + public TimeSpan Duration { get; } - public Period(DateTime start, DateTime end) + public Date StartsOn { get; } + public Date EndsOn { get; } + + [Obsolete("You should use StartsAt or StartsOn instead")] + public DateTime Start => StartsAt; + [Obsolete("You should use EndsAt or EndsOn instead")] + public DateTime End => EndsAt; + + public Period(DateTime startsAt, DateTime endsAt) + : this(startsAt, endsAt, endsAt - startsAt) { } + + public Period(DateTime startsAt, DateTime endsAt, TimeSpan duration) + { + if (duration < TimeSpan.Zero) + { + throw new NegativeDurationException(nameof(duration)); + } + + StartsAt = startsAt; + EndsAt = endsAt; + Duration = duration; + + StartsOn = new Date(startsAt); + EndsOn = new Date(startsAt); + } + + public Period(Date startsOn, Date endsOn) { - Start = start; - End = end; + StartsOn = startsOn; + EndsOn = endsOn; + StartsAt = new DateTime(startsOn.Ticks); + EndsAt = new DateTime(endsOn.Ticks); + Duration = endsOn - startsOn; + + if (Duration < TimeSpan.Zero) + { + throw new NegativeDurationException(nameof(endsOn)); + } + } + + ITimeBlock ITimeBlock.ChangeStartsAt(DateTime startsAt) + { + if (startsAt > EndsAt) + { + throw new NegativeDurationException(nameof(startsAt)); + } + + return new Period(startsAt, EndsAt); + } + ITimeBlock ITimeBlock.ChangeEndsAt(DateTime endsAt) + { + if (endsAt < StartsAt) + { + throw new NegativeDurationException(nameof(endsAt)); + } + + return new Period(StartsAt, endsAt); + } + ITimeBlock ITimeBlock.ChangeDuration(TimeSpan duration) + { + if (duration < TimeSpan.Zero) + { + throw new NegativeDurationException(nameof(duration)); + } + + return new Period(StartsAt, EndsAt, duration); } } } diff --git a/NExtends/Primitives/TimeSpans/TimeInitials.cs b/NExtends/Primitives/TimeSpans/TimeInitials.cs index f956f92..059edc7 100644 --- a/NExtends/Primitives/TimeSpans/TimeInitials.cs +++ b/NExtends/Primitives/TimeSpans/TimeInitials.cs @@ -1,7 +1,13 @@ -namespace NExtends.Primitives.TimeSpans +using System.Globalization; + +namespace NExtends.Primitives.TimeSpans { public class TimeInitials { + private static readonly TimeInitials _french = new TimeInitials("m", "h", "j"); + private static readonly TimeInitials _german = new TimeInitials("M", "St", "T"); + private static readonly TimeInitials _english = new TimeInitials("m", "h", "d"); + public string MinutesInitial { get; } public string HoursInitial { get; } public string DaysInitial { get; } @@ -12,5 +18,18 @@ public TimeInitials(string minutesInitial, string hoursInitial, string daysIniti HoursInitial = hoursInitial; DaysInitial = daysInitial; } + + public static TimeInitials FromCulture(CultureInfo culture) + { + switch(culture.Name) + { + case "fr-FR": + return _french; + case "de-DE": + return _german; + default: + return _english; + } + } } } diff --git a/NExtends/Primitives/TimeSpans/TimeSpan.extensions.cs b/NExtends/Primitives/TimeSpans/TimeSpan.extensions.cs index b1ec76b..c95b7ee 100644 --- a/NExtends/Primitives/TimeSpans/TimeSpan.extensions.cs +++ b/NExtends/Primitives/TimeSpans/TimeSpan.extensions.cs @@ -46,29 +46,39 @@ public static TimeSpan Sum(this IEnumerable source, Func TimeSpan.Zero) @@ -91,17 +101,26 @@ public static string ToHours(this TimeSpan timeSpan, TimeInitials initials, bool { sb.Append(initials.MinutesInitial); } + return sb.ToString(); } - public static string ToDays(this TimeSpan span, TimeInitials initials, bool showSign = false) + public static string ToDays(this TimeSpan span, bool showSign = false) + { + return ToDays(span, CultureInfo.CurrentCulture, showSign); + } + + public static string ToDays(this TimeSpan span, CultureInfo culture, bool showSign = false) { if (span == TimeSpan.Zero) { return "-"; } + var absSpan = new TimeSpan(Math.Abs(span.Ticks)); var sb = new StringBuilder(); + var initials = TimeInitials.FromCulture(culture); + if (showSign && span > TimeSpan.Zero) { sb.Append("+"); @@ -110,7 +129,9 @@ public static string ToDays(this TimeSpan span, TimeInitials initials, bool show { sb.Append("-"); } + sb.AppendFormat(CultureInfo.InvariantCulture, "{0} " + initials.DaysInitial, absSpan.TotalDays.RealRound(5)); + return sb.ToString(); } }