< Summary

Line coverage
0%
Covered lines: 0
Uncovered lines: 175
Coverable lines: 175
Total lines: 438
Line coverage: 0%
Branch coverage
0%
Covered branches: 0
Total branches: 92
Branch coverage: 0%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

File(s)

https://raw.githubusercontent.com/dotnet/runtime/811a7eabb75c42db53440e8ba3f60c07511cfd1f/src/libraries/System.Private.CoreLib/src/System/Globalization/PersianCalendar.cs

#LineLine coverage
 1// Licensed to the .NET Foundation under one or more agreements.
 2// The .NET Foundation licenses this file to you under the MIT license.
 3
 4using System.Diagnostics;
 5
 6namespace System.Globalization
 7{
 8    /// <summary>
 9    /// Modern Persian calendar is a solar observation based calendar. Each new year begins on the day when the vernal e
 10    /// The epoch is the date of the vernal equinox prior to the epoch of the Islamic calendar (March 19, 622 Julian or 
 11    /// There is no Persian year 0. Ordinary years have 365 days. Leap years have 366 days with the last month (Esfand) 
 12    /// </summary>
 13    /// <remarks>
 14    ///  Calendar support range:
 15    ///      Calendar    Minimum     Maximum
 16    ///      ==========  ==========  ==========
 17    ///      Gregorian   0622/03/22   9999/12/31
 18    ///      Persian     0001/01/01   9378/10/13
 19    /// </remarks>
 20    public class PersianCalendar : Calendar
 21    {
 022        public static readonly int PersianEra = 1;
 23
 024        private static readonly long s_persianEpoch = new DateTime(622, 3, 22).Ticks / TimeSpan.TicksPerDay;
 25        private const int ApproximateHalfYear = 180;
 26
 27        private const int DatePartYear = 0;
 28        private const int DatePartDayOfYear = 1;
 29        private const int DatePartMonth = 2;
 30        private const int DatePartDay = 3;
 31        private const int MonthsPerYear = 12;
 32
 033        private static ReadOnlySpan<int> DaysToMonth => [0, 31, 62, 93, 124, 155, 186, 216, 246, 276, 306, 336, 366];
 34
 35        private const int MaxCalendarYear = 9378;
 36        private const int MaxCalendarMonth = 10;
 37        private const int MaxCalendarDay = 13;
 38
 39        // Persian calendar (year: 1, month: 1, day:1 ) = Gregorian (year: 622, month: 3, day: 22)
 40        // This is the minimal Gregorian date that we support in the PersianCalendar.
 041        private static readonly DateTime s_minDate = new DateTime(622, 3, 22);
 042        private static readonly DateTime s_maxDate = DateTime.MaxValue;
 43
 044        public override DateTime MinSupportedDateTime => s_minDate;
 45
 046        public override DateTime MaxSupportedDateTime => s_maxDate;
 47
 048        public override CalendarAlgorithmType AlgorithmType => CalendarAlgorithmType.SolarCalendar;
 49
 050        public PersianCalendar()
 51        {
 052        }
 53
 054        internal override CalendarId BaseCalendarID => CalendarId.GREGORIAN;
 55
 056        internal override CalendarId ID => CalendarId.PERSIAN;
 57
 58        private static long GetAbsoluteDatePersian(int year, int month, int day)
 59        {
 060            if (year < 1 || year > MaxCalendarYear || month < 1 || month > 12)
 61            {
 062                throw new ArgumentOutOfRangeException(null, SR.ArgumentOutOfRange_BadYearMonthDay);
 63            }
 64
 65            // day is one based, make 0 based since this will be the number of days we add to beginning of year below
 066            int ordinalDay = DaysInPreviousMonths(month) + day - 1;
 067            int approximateDaysFromEpochForYearStart = (int)(CalendricalCalculationsHelper.MeanTropicalYearInDays * (yea
 068            long yearStart = CalendricalCalculationsHelper.PersianNewYearOnOrBefore(s_persianEpoch + approximateDaysFrom
 069            yearStart += ordinalDay;
 070            return yearStart;
 71        }
 72
 73        internal static void CheckTicksRange(long ticks)
 74        {
 075            if (ticks < s_minDate.Ticks || ticks > s_maxDate.Ticks)
 76            {
 077                throw new ArgumentOutOfRangeException(
 078                    "time",
 079                    ticks,
 080                    SR.Format(SR.ArgumentOutOfRange_CalendarRange, s_minDate, s_maxDate));
 81            }
 082        }
 83
 84        internal static void CheckEraRange(int era)
 85        {
 086            if (era != CurrentEra && era != PersianEra)
 87            {
 088                throw new ArgumentOutOfRangeException(nameof(era), era, SR.ArgumentOutOfRange_InvalidEraValue);
 89            }
 090        }
 91
 92        internal static void CheckYearRange(int year, int era)
 93        {
 094            CheckEraRange(era);
 095            if (year < 1 || year > MaxCalendarYear)
 96            {
 097                throw new ArgumentOutOfRangeException(
 098                    nameof(year),
 099                    year,
 0100                    SR.Format(SR.ArgumentOutOfRange_Range, 1, MaxCalendarYear));
 101            }
 0102        }
 103
 104        internal static void CheckYearMonthRange(int year, int month, int era)
 105        {
 0106            CheckYearRange(year, era);
 0107            if (year == MaxCalendarYear)
 108            {
 0109                if (month > MaxCalendarMonth)
 110                {
 0111                    throw new ArgumentOutOfRangeException(
 0112                        nameof(month),
 0113                        month,
 0114                        SR.Format(SR.ArgumentOutOfRange_Range, 1, MaxCalendarMonth));
 115                }
 116            }
 117
 0118            if (month < 1 || month > 12)
 119            {
 0120                ThrowHelper.ThrowArgumentOutOfRange_Month(month);
 121            }
 0122        }
 123
 124        private static int MonthFromOrdinalDay(int ordinalDay)
 125        {
 0126            Debug.Assert(ordinalDay <= 366);
 0127            int index = 0;
 0128            while (ordinalDay > DaysToMonth[index])
 129            {
 0130                index++;
 131            }
 132
 0133            return index;
 134        }
 135
 136        private static int DaysInPreviousMonths(int month)
 137        {
 0138            Debug.Assert(1 <= month && month <= 12);
 139            // months are one based but for calculations use 0 based
 0140            --month;
 0141            return DaysToMonth[month];
 142        }
 143
 144        private int GetDatePart(long ticks, int part)
 145        {
 0146            CheckTicksRange(ticks);
 147
 148            // Get the absolute date. The absolute date is the number of days from January 1st, 1 A.D.
 149            // 1/1/0001 is absolute date 1.
 0150            long numDays = ticks / TimeSpan.TicksPerDay + 1;
 151
 152            // Calculate the appromixate Persian Year.
 0153            long yearStart = CalendricalCalculationsHelper.PersianNewYearOnOrBefore(numDays);
 0154            int y = (int)(Math.Floor(((yearStart - s_persianEpoch) / CalendricalCalculationsHelper.MeanTropicalYearInDay
 0155            Debug.Assert(y >= 1);
 156
 0157            if (part == DatePartYear)
 158            {
 0159                return y;
 160            }
 161
 162            // Calculate the Persian Month.
 0163            int ordinalDay = (int)(numDays - CalendricalCalculationsHelper.GetNumberOfDays(this.ToDateTime(y, 1, 1, 0, 0
 0164            if (part == DatePartDayOfYear)
 165            {
 0166                return ordinalDay;
 167            }
 168
 0169            int m = MonthFromOrdinalDay(ordinalDay);
 0170            Debug.Assert(ordinalDay >= 1);
 0171            Debug.Assert(m >= 1 && m <= 12);
 0172            if (part == DatePartMonth)
 173            {
 0174                return m;
 175            }
 176
 0177            int d = ordinalDay - DaysInPreviousMonths(m);
 0178            Debug.Assert(1 <= d);
 0179            Debug.Assert(d <= 31);
 180
 181            // Calculate the Persian Day.
 0182            if (part == DatePartDay)
 183            {
 0184                return d;
 185            }
 186
 187            // Incorrect part value.
 0188            throw new InvalidOperationException(SR.InvalidOperation_DateTimeParsing);
 189        }
 190
 191        // Exactly the same as GetDatePart, except computing all of
 192        // year/month/day rather than just one of them. Used when all three
 193        // are needed rather than redoing the computations for each.
 194        internal void GetDate(long ticks, out int year, out int month, out int day)
 195        {
 0196            CheckTicksRange(ticks);
 197
 198            // Get the absolute date. The absolute date is the number of days from January 1st, 1 A.D.
 199            // 1/1/0001 is absolute date 1.
 0200            long numDays = ticks / TimeSpan.TicksPerDay + 1;
 201
 202            // Calculate the appromixate Persian Year.
 0203            long yearStart = CalendricalCalculationsHelper.PersianNewYearOnOrBefore(numDays);
 0204            year = (int)(Math.Floor(((yearStart - s_persianEpoch) / CalendricalCalculationsHelper.MeanTropicalYearInDays
 0205            Debug.Assert(year >= 1);
 206
 207            // Calculate the Persian Month.
 0208            int ordinalDay = (int)(numDays - CalendricalCalculationsHelper.GetNumberOfDays(this.ToDateTime(year, 1, 1, 0
 209
 0210            month = MonthFromOrdinalDay(ordinalDay);
 0211            Debug.Assert(ordinalDay >= 1);
 0212            Debug.Assert(month >= 1 && month <= 12);
 213
 0214            day = ordinalDay - DaysInPreviousMonths(month);
 0215            Debug.Assert(1 <= day);
 0216            Debug.Assert(day <= 31);
 0217        }
 218
 219        public override DateTime AddMonths(DateTime time, int months)
 220        {
 0221            if (months < -120000 || months > 120000)
 222            {
 0223                throw new ArgumentOutOfRangeException(
 0224                    nameof(months),
 0225                    months,
 0226                    SR.Format(SR.ArgumentOutOfRange_Range, -120000, 120000));
 227            }
 228
 229            // Get the date in Persian calendar.
 0230            GetDate(time.Ticks, out int y, out int m, out int d);
 0231            int i = m - 1 + months;
 0232            if (i >= 0)
 233            {
 0234                m = i % 12 + 1;
 0235                y += i / 12;
 236            }
 237            else
 238            {
 0239                m = 12 + (i + 1) % 12;
 0240                y += (i - 11) / 12;
 241            }
 0242            int days = GetDaysInMonth(y, m);
 0243            if (d > days)
 244            {
 0245                d = days;
 246            }
 247
 0248            long ticks = GetAbsoluteDatePersian(y, m, d) * TimeSpan.TicksPerDay + time.Ticks % TimeSpan.TicksPerDay;
 0249            CheckAddResult(ticks, MinSupportedDateTime, MaxSupportedDateTime);
 0250            return new DateTime(ticks);
 251        }
 252
 253        public override DateTime AddYears(DateTime time, int years)
 254        {
 0255            return AddMonths(time, years * 12);
 256        }
 257
 258        public override int GetDayOfMonth(DateTime time)
 259        {
 0260            return GetDatePart(time.Ticks, DatePartDay);
 261        }
 262
 0263        public override DayOfWeek GetDayOfWeek(DateTime time) => time.DayOfWeek;
 264
 265        public override int GetDayOfYear(DateTime time)
 266        {
 0267            return GetDatePart(time.Ticks, DatePartDayOfYear);
 268        }
 269
 270        public override int GetDaysInMonth(int year, int month, int era)
 271        {
 0272            CheckYearMonthRange(year, month, era);
 273
 0274            if ((month == MaxCalendarMonth) && (year == MaxCalendarYear))
 275            {
 0276                return MaxCalendarDay;
 277            }
 278
 0279            int daysInMonth = DaysToMonth[month] - DaysToMonth[month - 1];
 0280            if ((month == MonthsPerYear) && !IsLeapYear(year))
 281            {
 0282                Debug.Assert(daysInMonth == 30);
 0283                --daysInMonth;
 284            }
 285
 0286            return daysInMonth;
 287        }
 288
 289        public override int GetDaysInYear(int year, int era)
 290        {
 0291            CheckYearRange(year, era);
 0292            if (year == MaxCalendarYear)
 293            {
 0294                return DaysToMonth[MaxCalendarMonth - 1] + MaxCalendarDay;
 295            }
 296
 0297            return IsLeapYear(year, CurrentEra) ? 366 : 365;
 298        }
 299
 300        public override int GetEra(DateTime time)
 301        {
 0302            CheckTicksRange(time.Ticks);
 0303            return PersianEra;
 304        }
 305
 0306        public override int[] Eras => [PersianEra];
 307
 308        public override int GetMonth(DateTime time)
 309        {
 0310            return GetDatePart(time.Ticks, DatePartMonth);
 311        }
 312
 313        public override int GetMonthsInYear(int year, int era)
 314        {
 0315            CheckYearRange(year, era);
 0316            if (year == MaxCalendarYear)
 317            {
 0318                return MaxCalendarMonth;
 319            }
 320
 0321            return 12;
 322        }
 323
 324        public override int GetYear(DateTime time)
 325        {
 0326            return GetDatePart(time.Ticks, DatePartYear);
 327        }
 328
 329        public override bool IsLeapDay(int year, int month, int day, int era)
 330        {
 331            // The year/month/era value checking is done in GetDaysInMonth().
 0332            int daysInMonth = GetDaysInMonth(year, month, era);
 0333            if (day < 1 || day > daysInMonth)
 334            {
 0335                throw new ArgumentOutOfRangeException(
 0336                    nameof(day),
 0337                    day,
 0338                    SR.Format(SR.ArgumentOutOfRange_Day, daysInMonth, month));
 339            }
 340
 0341            return IsLeapYear(year, era) && month == 12 && day == 30;
 342        }
 343
 344        public override int GetLeapMonth(int year, int era)
 345        {
 0346            CheckYearRange(year, era);
 0347            return 0;
 348        }
 349
 350        public override bool IsLeapMonth(int year, int month, int era)
 351        {
 0352            CheckYearMonthRange(year, month, era);
 0353            return false;
 354        }
 355
 356        public override bool IsLeapYear(int year, int era)
 357        {
 0358            CheckYearRange(year, era);
 359
 0360            if (year == MaxCalendarYear)
 361            {
 0362                return false;
 363            }
 364
 0365            return (GetAbsoluteDatePersian(year + 1, 1, 1) - GetAbsoluteDatePersian(year, 1, 1)) == 366;
 366        }
 367
 368        public override DateTime ToDateTime(int year, int month, int day, int hour, int minute, int second, int millisec
 369        {
 370            // The year/month/era checking is done in GetDaysInMonth().
 0371            int daysInMonth = GetDaysInMonth(year, month, era);
 0372            if (day < 1 || day > daysInMonth)
 373            {
 0374                throw new ArgumentOutOfRangeException(
 0375                    nameof(day),
 0376                    day,
 0377                    SR.Format(SR.ArgumentOutOfRange_Day, daysInMonth, month));
 378            }
 379
 0380            long lDate = GetAbsoluteDatePersian(year, month, day);
 381
 0382            if (lDate < 0)
 383            {
 0384                throw new ArgumentOutOfRangeException(null, SR.ArgumentOutOfRange_BadYearMonthDay);
 385            }
 386
 0387            return new DateTime(lDate * TimeSpan.TicksPerDay + TimeToTicks(hour, minute, second, millisecond));
 388        }
 389
 390        private const int DefaultTwoDigitYearMax = 1410;
 391
 392        public override int TwoDigitYearMax
 393        {
 394            get
 395            {
 0396                if (_twoDigitYearMax == -1)
 397                {
 0398                    _twoDigitYearMax = GetSystemTwoDigitYearSetting(ID, DefaultTwoDigitYearMax);
 399                }
 400
 0401                return _twoDigitYearMax;
 402            }
 403            set
 404            {
 0405                VerifyWritable();
 0406                if (value < 99 || value > MaxCalendarYear)
 407                {
 0408                    throw new ArgumentOutOfRangeException(
 0409                        nameof(value),
 0410                        value,
 0411                        SR.Format(SR.ArgumentOutOfRange_Range, 99, MaxCalendarYear));
 412                }
 413
 0414                _twoDigitYearMax = value;
 0415            }
 416        }
 417
 418        public override int ToFourDigitYear(int year)
 419        {
 0420            ArgumentOutOfRangeException.ThrowIfNegative(year);
 0421            if (year < 100)
 422            {
 0423                return base.ToFourDigitYear(year);
 424            }
 425
 0426            if (year > MaxCalendarYear)
 427            {
 0428                throw new ArgumentOutOfRangeException(
 0429                    nameof(year),
 0430                    year,
 0431                    SR.Format(SR.ArgumentOutOfRange_Range, 1, MaxCalendarYear));
 432            }
 433
 0434            return year;
 435        }
 436    }
 437}
 438