< Summary

Line coverage
0%
Covered lines: 0
Uncovered lines: 150
Coverable lines: 150
Total lines: 505
Line coverage: 0%
Branch coverage
0%
Covered branches: 0
Total branches: 98
Branch coverage: 0%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Cyclomatic complexity NPath complexity Sequence coverage
.ctor(...)100%110%
GetYearOffset(...)0%24240%
GetGregorianYear(...)100%110%
IsValidYear(...)100%110%
CheckTicksRange(...)0%440%
ThrowOutOfRange()100%110%
AddMonths(...)0%14140%
AddYears(...)100%110%
GetDayOfMonth(...)100%110%
GetDayOfWeek(...)100%110%
GetDayOfYear(...)100%110%
GetDaysInMonth(...)0%10100%
GetDaysInYear(...)0%660%
GetEra(...)0%440%
GetMonth(...)100%110%
GetMonthsInYear(...)100%110%
GetYear(...)0%440%
GetYear(...)0%660%
IsLeapDay(...)0%10100%
ValidateYearInEra(...)100%110%
GetLeapMonth(...)100%110%
IsLeapMonth(...)0%440%
IsLeapYear(...)0%440%
ToDateTime(...)100%110%
GetWeekOfYear(...)100%110%
ToFourDigitYear(...)0%660%

File(s)

https://raw.githubusercontent.com/dotnet/runtime/811a7eabb75c42db53440e8ba3f60c07511cfd1f/src/libraries/System.Private.CoreLib/src/System/Globalization/GregorianCalendarHelper.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
 4namespace System.Globalization
 5{
 6    // Gregorian Calendars use Era Info
 7    internal sealed class EraInfo
 8    {
 9        internal int era;          // The value of the era.
 10        internal long ticks;    // The time in ticks when the era starts
 11        internal int yearOffset;   // The offset to Gregorian year when the era starts.
 12                                   // Gregorian Year = Era Year + yearOffset
 13                                   // Era Year = Gregorian Year - yearOffset
 14        internal int minEraYear;   // Min year value in this era. Generally, this value is 1, but this may
 15                                   // be affected by the DateTime.MinValue;
 16        internal int maxEraYear;   // Max year value in this era. (== the year length of the era + 1)
 17
 18        internal string? eraName;    // The era name
 19        internal string? abbrevEraName;  // Abbreviated Era Name
 20        internal string? englishEraName; // English era name
 21
 22        internal EraInfo(int era, int startYear, int startMonth, int startDay, int yearOffset, int minEraYear, int maxEr
 23        {
 24            this.era = era;
 25            this.yearOffset = yearOffset;
 26            this.minEraYear = minEraYear;
 27            this.maxEraYear = maxEraYear;
 28            this.ticks = new DateTime(startYear, startMonth, startDay).Ticks;
 29        }
 30
 31        internal EraInfo(int era, int startYear, int startMonth, int startDay, int yearOffset, int minEraYear, int maxEr
 32                          string eraName, string abbrevEraName, string englishEraName)
 33        {
 34            this.era = era;
 35            this.yearOffset = yearOffset;
 36            this.minEraYear = minEraYear;
 37            this.maxEraYear = maxEraYear;
 38            // codeql[cs/leap-year/unsafe-date-construction-from-two-elements] - A DateTime object is created using valu
 39            this.ticks = new DateTime(startYear, startMonth, startDay).Ticks;
 40            this.eraName = eraName;
 41            this.abbrevEraName = abbrevEraName;
 42            this.englishEraName = englishEraName;
 43        }
 44    }
 45
 46    // This calendar recognizes two era values:
 47    // 0 CurrentEra (AD)
 48    // 1 BeforeCurrentEra (BC)
 49    internal sealed class GregorianCalendarHelper
 50    {
 51        //
 52        // This is the max Gregorian year can be represented by DateTime class.  The limitation
 53        // is derived from DateTime class.
 54        //
 055        internal int MaxYear => m_maxYear;
 56
 57        private readonly int m_maxYear;
 58        private readonly int m_minYear;
 59        private readonly Calendar m_Cal;
 60        private readonly EraInfo[] m_EraInfo;
 61        private readonly long _minSupportedTicks;
 62        private readonly long _maxSupportedTicks;
 63
 64        // Construct an instance of gregorian calendar.
 065        internal GregorianCalendarHelper(Calendar cal, EraInfo[] eraInfo)
 66        {
 067            m_Cal = cal;
 068            m_EraInfo = eraInfo;
 069            m_maxYear = eraInfo[0].maxEraYear;
 070            m_minYear = eraInfo[0].minEraYear;
 071            _minSupportedTicks = cal.MinSupportedDateTime.Ticks;
 072            _maxSupportedTicks = cal.MaxSupportedDateTime.Ticks;
 073        }
 74
 75        // EraInfo.yearOffset:  The offset to Gregorian year when the era starts. Gregorian Year = Era Year + yearOffset
 76        //                      Era Year = Gregorian Year - yearOffset
 77        // EraInfo.minEraYear:  Min year value in this era. Generally, this value is 1, but this may be affected by the 
 78        // EraInfo.maxEraYear:  Max year value in this era. (== the year length of the era + 1)
 79        private int GetYearOffset(int year, int era, bool throwOnError)
 80        {
 081            if (year < 0)
 82            {
 083                if (throwOnError)
 84                {
 085                    throw new ArgumentOutOfRangeException(nameof(year), SR.ArgumentOutOfRange_NeedNonNegNum);
 86                }
 087                return -1;
 88            }
 89
 090            if (era == Calendar.CurrentEra)
 91            {
 092                era = m_Cal.CurrentEraValue;
 93            }
 94
 095            var eras = m_EraInfo;
 096            for (int i = 0; i < eras.Length; i++)
 97            {
 098                EraInfo eraInfo = eras[i];
 099                if (era == eraInfo.era)
 100                {
 0101                    if (year >= eraInfo.minEraYear)
 102                    {
 0103                        if (year <= eraInfo.maxEraYear)
 104                        {
 0105                            return eraInfo.yearOffset;
 106                        }
 0107                        else if (!LocalAppContextSwitches.EnforceJapaneseEraYearRanges)
 108                        {
 109                            // If we got the year number exceeding the era max year number, this still possible be valid
 110                            // introducing new eras after the era we are checking. we'll loop on the eras after the era 
 111                            // can exist in one of these eras. otherwise, we'll throw.
 112                            // Note, we always return the offset associated with the requested era.
 113                            //
 114                            // Here is some example:
 115                            // if we are getting the era number 4 (Heisei) and getting the year number 32. if the era 4 
 116                            // then year 32 exceeded the range of era 4 and we'll try to find out if the years differenc
 117                            // the subsequent eras (e.g era 5 and up)
 118
 0119                            int remainingYears = year - eraInfo.maxEraYear;
 120
 0121                            for (int j = i - 1; j >= 0; j--)
 122                            {
 0123                                if (remainingYears <= eras[j].maxEraYear)
 124                                {
 0125                                    return eraInfo.yearOffset;
 126                                }
 0127                                remainingYears -= eras[j].maxEraYear;
 128                            }
 129                        }
 130                    }
 131
 0132                    if (throwOnError)
 133                    {
 0134                        throw new ArgumentOutOfRangeException(
 0135                                    nameof(year),
 0136                                    SR.Format(
 0137                                        SR.ArgumentOutOfRange_Range,
 0138                                        eraInfo.minEraYear,
 0139                                        eraInfo.maxEraYear));
 140                    }
 141
 142                    break; // no need to iterate more on eras.
 143                }
 144            }
 145
 0146            if (throwOnError)
 147            {
 0148                throw new ArgumentOutOfRangeException(nameof(era), SR.ArgumentOutOfRange_InvalidEraValue);
 149            }
 0150            return -1;
 151        }
 152
 153        /*=================================GetGregorianYear==========================
 154        **Action: Get the Gregorian year value for the specified year in an era.
 155        **Returns: The Gregorian year value.
 156        **Arguments:
 157        **      year    the year value in Japanese calendar
 158        **      era     the Japanese emperor era value.
 159        **Exceptions:
 160        **      ArgumentOutOfRangeException if year value is invalid or era value is invalid.
 161        ============================================================================*/
 162
 163        internal int GetGregorianYear(int year, int era)
 164        {
 0165            return GetYearOffset(year, era, throwOnError: true) + year;
 166        }
 167
 168        internal bool IsValidYear(int year, int era)
 169        {
 0170            return GetYearOffset(year, era, throwOnError: false) >= 0;
 171        }
 172
 173        internal void CheckTicksRange(long ticks)
 174        {
 0175            if (ticks < _minSupportedTicks || ticks > _maxSupportedTicks) ThrowOutOfRange();
 176
 177            void ThrowOutOfRange()
 178            {
 0179                throw new ArgumentOutOfRangeException(
 0180                            "time",
 0181                            SR.Format(
 0182                                CultureInfo.InvariantCulture,
 0183                                SR.ArgumentOutOfRange_CalendarRange,
 0184                                m_Cal.MinSupportedDateTime,
 0185                                m_Cal.MaxSupportedDateTime));
 186            }
 0187        }
 188
 189        // Returns the DateTime resulting from adding the given number of
 190        // months to the specified DateTime. The result is computed by incrementing
 191        // (or decrementing) the year and month parts of the specified DateTime by
 192        // value months, and, if required, adjusting the day part of the
 193        // resulting date downwards to the last day of the resulting month in the
 194        // resulting year. The time-of-day part of the result is the same as the
 195        // time-of-day part of the specified DateTime.
 196        //
 197        // In more precise terms, considering the specified DateTime to be of the
 198        // form y / m / d + t, where y is the
 199        // year, m is the month, d is the day, and t is the
 200        // time-of-day, the result is y1 / m1 / d1 + t,
 201        // where y1 and m1 are computed by adding value months
 202        // to y and m, and d1 is the largest value less than
 203        // or equal to d that denotes a valid day in month m1 of year
 204        // y1.
 205        //
 206        public DateTime AddMonths(DateTime time, int months)
 207        {
 0208            if (months < -120000 || months > 120000)
 209            {
 0210                throw new ArgumentOutOfRangeException(
 0211                            nameof(months),
 0212                            SR.Format(
 0213                                SR.ArgumentOutOfRange_Range,
 0214                                -120000,
 0215                                120000));
 216            }
 0217            CheckTicksRange(time.Ticks);
 218
 0219            time.GetDate(out int y, out int m, out int d);
 0220            int i = m - 1 + months;
 0221            if (i >= 0)
 222            {
 0223                m = i % 12 + 1;
 0224                y += i / 12;
 225            }
 226            else
 227            {
 0228                m = 12 + (i + 1) % 12;
 0229                y += (i - 11) / 12;
 230            }
 0231            ReadOnlySpan<int> daysArray = (y % 4 == 0 && (y % 100 != 0 || y % 400 == 0)) ? GregorianCalendar.DaysToMonth
 0232            int days = (daysArray[m] - daysArray[m - 1]);
 233
 0234            if (d > days)
 235            {
 0236                d = days;
 237            }
 0238            long ticks = GregorianCalendar.DateToTicks(y, m, d) + time.TimeOfDay.Ticks;
 0239            Calendar.CheckAddResult(ticks, m_Cal.MinSupportedDateTime, m_Cal.MaxSupportedDateTime);
 0240            return new DateTime(ticks);
 241        }
 242
 243        // Returns the DateTime resulting from adding the given number of
 244        // years to the specified DateTime. The result is computed by incrementing
 245        // (or decrementing) the year part of the specified DateTime by value
 246        // years. If the month and day of the specified DateTime is 2/29, and if the
 247        // resulting year is not a leap year, the month and day of the resulting
 248        // DateTime becomes 2/28. Otherwise, the month, day, and time-of-day
 249        // parts of the result are the same as those of the specified DateTime.
 250        //
 251        public DateTime AddYears(DateTime time, int years)
 252        {
 0253            return AddMonths(time, years * 12);
 254        }
 255
 256        // Returns the day-of-month part of the specified DateTime. The returned
 257        // value is an integer between 1 and 31.
 258        //
 259        public int GetDayOfMonth(DateTime time)
 260        {
 0261            CheckTicksRange(time.Ticks);
 0262            return time.Day;
 263        }
 264
 265        // Returns the day-of-week part of the specified DateTime. The returned value
 266        // is an integer between 0 and 6, where 0 indicates Sunday, 1 indicates
 267        // Monday, 2 indicates Tuesday, 3 indicates Wednesday, 4 indicates
 268        // Thursday, 5 indicates Friday, and 6 indicates Saturday.
 269        //
 270        public DayOfWeek GetDayOfWeek(DateTime time)
 271        {
 0272            CheckTicksRange(time.Ticks);
 0273            return time.DayOfWeek;
 274        }
 275
 276        // Returns the day-of-year part of the specified DateTime. The returned value
 277        // is an integer between 1 and 366.
 278        //
 279        public int GetDayOfYear(DateTime time)
 280        {
 0281            CheckTicksRange(time.Ticks);
 0282            return time.DayOfYear;
 283        }
 284
 285        // Returns the number of days in the month given by the year and
 286        // month arguments.
 287        //
 288        public int GetDaysInMonth(int year, int month, int era)
 289        {
 290            //
 291            // Convert year/era value to Gregorain year value.
 292            //
 0293            year = GetGregorianYear(year, era);
 0294            if (month < 1 || month > 12)
 295            {
 0296                ThrowHelper.ThrowArgumentOutOfRange_Month(month);
 297            }
 0298            ReadOnlySpan<int> days = ((year % 4 == 0 && (year % 100 != 0 || year % 400 == 0)) ? GregorianCalendar.DaysTo
 0299            return days[month] - days[month - 1];
 300        }
 301
 302        // Returns the number of days in the year given by the year argument for the current era.
 303        //
 304
 305        public int GetDaysInYear(int year, int era)
 306        {
 307            //
 308            // Convert year/era value to Gregorain year value.
 309            //
 0310            year = GetGregorianYear(year, era);
 0311            return (year % 4 == 0 && (year % 100 != 0 || year % 400 == 0)) ? 366 : 365;
 312        }
 313
 314        // Returns the era for the specified DateTime value.
 315        public int GetEra(DateTime time)
 316        {
 0317            long ticks = time.Ticks;
 318            // The assumption here is that m_EraInfo is listed in reverse order.
 0319            foreach (EraInfo eraInfo in m_EraInfo)
 320            {
 0321                if (ticks >= eraInfo.ticks)
 322                {
 0323                    return eraInfo.era;
 324                }
 325            }
 0326            throw new ArgumentOutOfRangeException(nameof(time), SR.ArgumentOutOfRange_Era);
 327        }
 328
 329        public int[] Eras
 330        {
 331            get
 332            {
 0333                EraInfo[] eraInfo = m_EraInfo;
 0334                var eras = new int[eraInfo.Length];
 0335                for (int i = 0; i < eraInfo.Length; i++)
 336                {
 0337                    eras[i] = eraInfo[i].era;
 338                }
 0339                return eras;
 340            }
 341        }
 342
 343        // Returns the month part of the specified DateTime. The returned value is an
 344        // integer between 1 and 12.
 345        //
 346        public int GetMonth(DateTime time)
 347        {
 0348            CheckTicksRange(time.Ticks);
 0349            return time.Month;
 350        }
 351
 352        // Returns the number of months in the specified year and era.
 353        // Always return 12.
 354        public int GetMonthsInYear(int year, int era)
 355        {
 0356            ValidateYearInEra(year, era);
 0357            return 12;
 358        }
 359
 360        // Returns the year part of the specified DateTime. The returned value is an
 361        // integer between 1 and 9999.
 362        //
 363        public int GetYear(DateTime time)
 364        {
 0365            long ticks = time.Ticks;
 0366            CheckTicksRange(ticks);
 0367            foreach (EraInfo eraInfo in m_EraInfo)
 368            {
 0369                if (ticks >= eraInfo.ticks)
 370                {
 0371                    return time.Year - eraInfo.yearOffset;
 372                }
 373            }
 0374            throw new ArgumentException(SR.Argument_NoEra);
 375        }
 376
 377        // Returns the year that match the specified Gregorian year. The returned value is an
 378        // integer between 1 and 9999.
 379        //
 380        public int GetYear(int year, DateTime time)
 381        {
 0382            long ticks = time.Ticks;
 0383            foreach (EraInfo eraInfo in m_EraInfo)
 384            {
 385                // while calculating dates with JapaneseLuniSolarCalendar, we can run into cases right after the start o
 386                // and still belong to the month which is started in previous era. Calculating equivalent calendar date 
 387                // using the new era info which will have the year offset equal to the year we are calculating year = m_
 388                // which will end up with zero as calendar year.
 389                // We should use the previous era info instead to get the right year number. Example of such date is Feb
 0390                if (ticks >= eraInfo.ticks && year > eraInfo.yearOffset)
 391                {
 0392                    return year - eraInfo.yearOffset;
 393                }
 394            }
 0395            throw new ArgumentException(SR.Argument_NoEra);
 396        }
 397
 398        // Checks whether a given day in the specified era is a leap day. This method returns true if
 399        // the date is a leap day, or false if not.
 400        //
 401        public bool IsLeapDay(int year, int month, int day, int era)
 402        {
 403            // year/month/era checking is done in GetDaysInMonth()
 0404            if (day < 1 || day > GetDaysInMonth(year, month, era))
 405            {
 0406                throw new ArgumentOutOfRangeException(
 0407                            nameof(day),
 0408                            SR.Format(
 0409                                SR.ArgumentOutOfRange_Range,
 0410                                1,
 0411                                GetDaysInMonth(year, month, era)));
 412            }
 413
 0414            if (!IsLeapYear(year, era))
 415            {
 0416                return false;
 417            }
 418
 0419            if (month == 2 && day == 29)
 420            {
 0421                return true;
 422            }
 423
 0424            return false;
 425        }
 426
 427        // Giving the calendar year and era, ValidateYearInEra will validate the existence of the input year in the inpu
 428        // This method will throw if the year or the era is invalid.
 0429        public void ValidateYearInEra(int year, int era) => GetYearOffset(year, era, throwOnError: true);
 430
 431        // Returns the leap month in a calendar year of the specified era.
 432        // This method always returns 0 as all calendars using this method don't have leap months.
 433        public int GetLeapMonth(int year, int era)
 434        {
 0435            ValidateYearInEra(year, era);
 0436            return 0;
 437        }
 438
 439        // Checks whether a given month in the specified era is a leap month.
 440        // This method always returns false as all calendars using this method don't have leap months.
 441        public bool IsLeapMonth(int year, int month, int era)
 442        {
 0443            ValidateYearInEra(year, era);
 0444            if (month < 1 || month > 12)
 445            {
 0446                throw new ArgumentOutOfRangeException(
 0447                            nameof(month),
 0448                            SR.Format(
 0449                                SR.ArgumentOutOfRange_Range,
 0450                                1,
 0451                                12));
 452            }
 0453            return false;
 454        }
 455
 456        // Checks whether a given year in the specified era is a leap year. This method returns true if
 457        // year is a leap year, or false if not.
 458        //
 459        public bool IsLeapYear(int year, int era)
 460        {
 0461            year = GetGregorianYear(year, era);
 0462            return year % 4 == 0 && (year % 100 != 0 || year % 400 == 0);
 463        }
 464
 465        // Returns the date and time converted to a DateTime value.  Throws an exception if the n-tuple is invalid.
 466        //
 467        public DateTime ToDateTime(int year, int month, int day, int hour, int minute, int second, int millisecond, int 
 468        {
 0469            year = GetGregorianYear(year, era);
 0470            long ticks = GregorianCalendar.DateToTicks(year, month, day) + Calendar.TimeToTicks(hour, minute, second, mi
 0471            CheckTicksRange(ticks);
 0472            return new DateTime(ticks);
 473        }
 474
 475        public int GetWeekOfYear(DateTime time, CalendarWeekRule rule, DayOfWeek firstDayOfWeek)
 476        {
 0477            CheckTicksRange(time.Ticks);
 478            // Use GregorianCalendar to get around the problem that the implementation in Calendar.GetWeekOfYear()
 479            // can call GetYear() that exceeds the supported range of the Gregorian-based calendars.
 0480            return GregorianCalendar.GetDefaultInstance().GetWeekOfYear(time, rule, firstDayOfWeek);
 481        }
 482
 483        public int ToFourDigitYear(int year, int twoDigitYearMax)
 484        {
 0485            ArgumentOutOfRangeException.ThrowIfNegative(year);
 486
 0487            if (year < 100)
 488            {
 0489                return (twoDigitYearMax / 100 - (year > twoDigitYearMax % 100 ? 1 : 0)) * 100 + year;
 490            }
 491
 0492            if (year < m_minYear || year > m_maxYear)
 493            {
 0494                throw new ArgumentOutOfRangeException(
 0495                            nameof(year),
 0496                            SR.Format(SR.ArgumentOutOfRange_Range, m_minYear, m_maxYear));
 497            }
 498
 499            // If the year value is above 100, just return the year value.  Don't have to do
 500            // the TwoDigitYearMax comparison.
 0501            return year;
 502        }
 503    }
 504}
 505