< Summary

Line coverage
0%
Covered lines: 0
Uncovered lines: 18
Coverable lines: 18
Total lines: 505
Line coverage: 0%
Branch coverage
N/A
Covered branches: 0
Total branches: 0
Branch coverage: N/A
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Cyclomatic complexity NPath complexity Sequence coverage
.ctor(...)100%110%
.ctor(...)100%110%

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
 022        internal EraInfo(int era, int startYear, int startMonth, int startDay, int yearOffset, int minEraYear, int maxEr
 23        {
 024            this.era = era;
 025            this.yearOffset = yearOffset;
 026            this.minEraYear = minEraYear;
 027            this.maxEraYear = maxEraYear;
 028            this.ticks = new DateTime(startYear, startMonth, startDay).Ticks;
 029        }
 30
 031        internal EraInfo(int era, int startYear, int startMonth, int startDay, int yearOffset, int minEraYear, int maxEr
 032                          string eraName, string abbrevEraName, string englishEraName)
 33        {
 034            this.era = era;
 035            this.yearOffset = yearOffset;
 036            this.minEraYear = minEraYear;
 037            this.maxEraYear = maxEraYear;
 38            // codeql[cs/leap-year/unsafe-date-construction-from-two-elements] - A DateTime object is created using valu
 039            this.ticks = new DateTime(startYear, startMonth, startDay).Ticks;
 040            this.eraName = eraName;
 041            this.abbrevEraName = abbrevEraName;
 042            this.englishEraName = englishEraName;
 043        }
 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        //
 55        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.
 65        internal GregorianCalendarHelper(Calendar cal, EraInfo[] eraInfo)
 66        {
 67            m_Cal = cal;
 68            m_EraInfo = eraInfo;
 69            m_maxYear = eraInfo[0].maxEraYear;
 70            m_minYear = eraInfo[0].minEraYear;
 71            _minSupportedTicks = cal.MinSupportedDateTime.Ticks;
 72            _maxSupportedTicks = cal.MaxSupportedDateTime.Ticks;
 73        }
 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        {
 81            if (year < 0)
 82            {
 83                if (throwOnError)
 84                {
 85                    throw new ArgumentOutOfRangeException(nameof(year), SR.ArgumentOutOfRange_NeedNonNegNum);
 86                }
 87                return -1;
 88            }
 89
 90            if (era == Calendar.CurrentEra)
 91            {
 92                era = m_Cal.CurrentEraValue;
 93            }
 94
 95            var eras = m_EraInfo;
 96            for (int i = 0; i < eras.Length; i++)
 97            {
 98                EraInfo eraInfo = eras[i];
 99                if (era == eraInfo.era)
 100                {
 101                    if (year >= eraInfo.minEraYear)
 102                    {
 103                        if (year <= eraInfo.maxEraYear)
 104                        {
 105                            return eraInfo.yearOffset;
 106                        }
 107                        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
 119                            int remainingYears = year - eraInfo.maxEraYear;
 120
 121                            for (int j = i - 1; j >= 0; j--)
 122                            {
 123                                if (remainingYears <= eras[j].maxEraYear)
 124                                {
 125                                    return eraInfo.yearOffset;
 126                                }
 127                                remainingYears -= eras[j].maxEraYear;
 128                            }
 129                        }
 130                    }
 131
 132                    if (throwOnError)
 133                    {
 134                        throw new ArgumentOutOfRangeException(
 135                                    nameof(year),
 136                                    SR.Format(
 137                                        SR.ArgumentOutOfRange_Range,
 138                                        eraInfo.minEraYear,
 139                                        eraInfo.maxEraYear));
 140                    }
 141
 142                    break; // no need to iterate more on eras.
 143                }
 144            }
 145
 146            if (throwOnError)
 147            {
 148                throw new ArgumentOutOfRangeException(nameof(era), SR.ArgumentOutOfRange_InvalidEraValue);
 149            }
 150            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        {
 165            return GetYearOffset(year, era, throwOnError: true) + year;
 166        }
 167
 168        internal bool IsValidYear(int year, int era)
 169        {
 170            return GetYearOffset(year, era, throwOnError: false) >= 0;
 171        }
 172
 173        internal void CheckTicksRange(long ticks)
 174        {
 175            if (ticks < _minSupportedTicks || ticks > _maxSupportedTicks) ThrowOutOfRange();
 176
 177            void ThrowOutOfRange()
 178            {
 179                throw new ArgumentOutOfRangeException(
 180                            "time",
 181                            SR.Format(
 182                                CultureInfo.InvariantCulture,
 183                                SR.ArgumentOutOfRange_CalendarRange,
 184                                m_Cal.MinSupportedDateTime,
 185                                m_Cal.MaxSupportedDateTime));
 186            }
 187        }
 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        {
 208            if (months < -120000 || months > 120000)
 209            {
 210                throw new ArgumentOutOfRangeException(
 211                            nameof(months),
 212                            SR.Format(
 213                                SR.ArgumentOutOfRange_Range,
 214                                -120000,
 215                                120000));
 216            }
 217            CheckTicksRange(time.Ticks);
 218
 219            time.GetDate(out int y, out int m, out int d);
 220            int i = m - 1 + months;
 221            if (i >= 0)
 222            {
 223                m = i % 12 + 1;
 224                y += i / 12;
 225            }
 226            else
 227            {
 228                m = 12 + (i + 1) % 12;
 229                y += (i - 11) / 12;
 230            }
 231            ReadOnlySpan<int> daysArray = (y % 4 == 0 && (y % 100 != 0 || y % 400 == 0)) ? GregorianCalendar.DaysToMonth
 232            int days = (daysArray[m] - daysArray[m - 1]);
 233
 234            if (d > days)
 235            {
 236                d = days;
 237            }
 238            long ticks = GregorianCalendar.DateToTicks(y, m, d) + time.TimeOfDay.Ticks;
 239            Calendar.CheckAddResult(ticks, m_Cal.MinSupportedDateTime, m_Cal.MaxSupportedDateTime);
 240            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        {
 253            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        {
 261            CheckTicksRange(time.Ticks);
 262            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        {
 272            CheckTicksRange(time.Ticks);
 273            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        {
 281            CheckTicksRange(time.Ticks);
 282            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            //
 293            year = GetGregorianYear(year, era);
 294            if (month < 1 || month > 12)
 295            {
 296                ThrowHelper.ThrowArgumentOutOfRange_Month(month);
 297            }
 298            ReadOnlySpan<int> days = ((year % 4 == 0 && (year % 100 != 0 || year % 400 == 0)) ? GregorianCalendar.DaysTo
 299            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            //
 310            year = GetGregorianYear(year, era);
 311            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        {
 317            long ticks = time.Ticks;
 318            // The assumption here is that m_EraInfo is listed in reverse order.
 319            foreach (EraInfo eraInfo in m_EraInfo)
 320            {
 321                if (ticks >= eraInfo.ticks)
 322                {
 323                    return eraInfo.era;
 324                }
 325            }
 326            throw new ArgumentOutOfRangeException(nameof(time), SR.ArgumentOutOfRange_Era);
 327        }
 328
 329        public int[] Eras
 330        {
 331            get
 332            {
 333                EraInfo[] eraInfo = m_EraInfo;
 334                var eras = new int[eraInfo.Length];
 335                for (int i = 0; i < eraInfo.Length; i++)
 336                {
 337                    eras[i] = eraInfo[i].era;
 338                }
 339                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        {
 348            CheckTicksRange(time.Ticks);
 349            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        {
 356            ValidateYearInEra(year, era);
 357            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        {
 365            long ticks = time.Ticks;
 366            CheckTicksRange(ticks);
 367            foreach (EraInfo eraInfo in m_EraInfo)
 368            {
 369                if (ticks >= eraInfo.ticks)
 370                {
 371                    return time.Year - eraInfo.yearOffset;
 372                }
 373            }
 374            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        {
 382            long ticks = time.Ticks;
 383            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
 390                if (ticks >= eraInfo.ticks && year > eraInfo.yearOffset)
 391                {
 392                    return year - eraInfo.yearOffset;
 393                }
 394            }
 395            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()
 404            if (day < 1 || day > GetDaysInMonth(year, month, era))
 405            {
 406                throw new ArgumentOutOfRangeException(
 407                            nameof(day),
 408                            SR.Format(
 409                                SR.ArgumentOutOfRange_Range,
 410                                1,
 411                                GetDaysInMonth(year, month, era)));
 412            }
 413
 414            if (!IsLeapYear(year, era))
 415            {
 416                return false;
 417            }
 418
 419            if (month == 2 && day == 29)
 420            {
 421                return true;
 422            }
 423
 424            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.
 429        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        {
 435            ValidateYearInEra(year, era);
 436            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        {
 443            ValidateYearInEra(year, era);
 444            if (month < 1 || month > 12)
 445            {
 446                throw new ArgumentOutOfRangeException(
 447                            nameof(month),
 448                            SR.Format(
 449                                SR.ArgumentOutOfRange_Range,
 450                                1,
 451                                12));
 452            }
 453            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        {
 461            year = GetGregorianYear(year, era);
 462            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        {
 469            year = GetGregorianYear(year, era);
 470            long ticks = GregorianCalendar.DateToTicks(year, month, day) + Calendar.TimeToTicks(hour, minute, second, mi
 471            CheckTicksRange(ticks);
 472            return new DateTime(ticks);
 473        }
 474
 475        public int GetWeekOfYear(DateTime time, CalendarWeekRule rule, DayOfWeek firstDayOfWeek)
 476        {
 477            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.
 480            return GregorianCalendar.GetDefaultInstance().GetWeekOfYear(time, rule, firstDayOfWeek);
 481        }
 482
 483        public int ToFourDigitYear(int year, int twoDigitYearMax)
 484        {
 485            ArgumentOutOfRangeException.ThrowIfNegative(year);
 486
 487            if (year < 100)
 488            {
 489                return (twoDigitYearMax / 100 - (year > twoDigitYearMax % 100 ? 1 : 0)) * 100 + year;
 490            }
 491
 492            if (year < m_minYear || year > m_maxYear)
 493            {
 494                throw new ArgumentOutOfRangeException(
 495                            nameof(year),
 496                            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.
 501            return year;
 502        }
 503    }
 504}
 505