// // PersianCalendar.cs: Implements the Persian calendar // // Authors: // Roozbeh Pournader (roozbeh@farsiweb.info) // Ulrich Kunitz // // Copyright (C) 2002 Ulrich Kunitz // Copyright (C) 2004 Novell, Inc (http://www.novell.com) // Copyright (C) 2005 Sharif FarsiWeb, Inc. (http://www.farsiweb.info) // // 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. // #if NET_2_0 namespace System.Globalization { using System; using System.IO; /// /// This is the Persian calendar of Iran, also known as the Iranian /// calendar or the Jalaali calendar. The Afghan calendar may or may /// not be different, as different sources disagree about it. /// /// /// The implemented algorithm is the simple 33-year arithmetic /// calendar, which is not the same as the official Iranian calendar. /// But this arithmetic calendar has been confirmed to produce the /// same results as the official Iranian calendar at least /// from 1925 C.E., when the calendar was officially introduced, /// to 2088 C.E. This is the same algorithm that is used in .NET. /// /// The Iranian law explicitly mentions that the true solar year /// should be used, which requires astronomical calculations of the /// March equinox and the solar apparent noon. The exact locale for /// observation of the apparent noon is not mentioned in the 1925 law, /// but the current practice is using the 52.5 E meridian, which is /// the meridian defining the official timezone of Iran. /// /// Also, please note that implementing the Persian calendar /// using the 2820-year arithmetic algorithm, as suggested by /// Ahmad Birashk and others, is less accurate than the 33-year /// calendar: first, it fails earlier than the 33-year cycle in /// matching the official astronomical calendar (first failure is /// in 2025 C.E.), and second, the 2820-year suggested rule is based /// on the mean tropical year, not the mean March equinoctial year. /// /// public class PersianCalendar : Calendar { /// /// Constructor. /// public PersianCalendar() { M_AbbrEraNames = new string[] {"A.P."}; M_EraNames = new string[] {"Anno Persico"}; if (M_TwoDigitYearMax == 99) // FIXME: the .NET documentation does not mention the default value, // This is the value mentioned in the .NET documentation example result. M_TwoDigitYearMax = 1410; } /// /// The era number for the Anno Persico (A.P.) era, called /// plain PersianEra. /// public static readonly int PersianEra = 1; /// /// The /// ticks for first day of /// year 1 A.P. /// internal const long M_MinTicks = 196036416000000000L; /// /// The minimum year in the A.P. era supported. /// internal const int M_MinYear = 1; /// Overridden. Gives the eras supported by the Persian /// calendar as an array of integers. /// public override int[] Eras { get { return new int[] { PersianEra }; } } // FIXME: the .NET documentation does not mention the default value, // This is the value mentioned in the .NET documentation example result. int twoDigitYearMax = 1410; public override int TwoDigitYearMax { get { return twoDigitYearMax; } set { CheckReadOnly (); M_ArgumentInRange ("value", value, 100, M_MaxYear); twoDigitYearMax = value; } } /// /// A protected member checking a /// value. /// /// The /// /// to check. /// /// /// The exception is thrown if the /// parameter is before the /// year 1 A.P. /// internal void M_CheckDateTime(DateTime time) { if (time.Ticks < M_MinTicks) throw new ArgumentOutOfRangeException( "time", "Only positive Persian years are supported."); } /// /// A protected method checking the era number. /// /// The era number. /// /// The exception is thrown if the era is not equal /// . /// internal void M_CheckEra(ref int era) { if (era == CurrentEra) era = PersianEra; if (era != PersianEra) throw new ArgumentException("Era value was not valid."); } /// /// A protected method checking calendar year and the era number. /// /// An integer representing the calendar year. /// /// The era number. /// /// The exception is thrown if the era is not equal /// . /// /// /// The exception is thrown if the calendar year is outside of /// the allowed range. /// internal override void M_CheckYE(int year, ref int era) { M_CheckEra(ref era); if (year < M_MinYear || year > M_MaxYear) throw new ArgumentOutOfRangeException( "year", "Only Persian years between 1 and 9378," + " inclusive, are supported."); } /// /// A protected method checking the calendar year, month, and /// era number. /// /// An integer representing the calendar year. /// /// An integer giving the calendar month. /// /// The era number. /// /// The exception is thrown if the era is not equal /// . /// /// /// The exception is thrown if the calendar year or month is /// outside of the allowed range. /// internal void M_CheckYME(int year, int month, ref int era) { M_CheckYE(year, ref era); if (month < 1 || month > 12) throw new ArgumentOutOfRangeException("month", "Month must be between one and twelve."); else if (year == M_MaxYear && month > 10) throw new ArgumentOutOfRangeException("month", "Months in year 9378 must be between one and ten."); } /// /// A protected method checking the calendar day, month, and year /// and the era number. /// /// An integer representing the calendar year. /// /// An integer giving the calendar month. /// /// An integer giving the calendar day. /// /// The era number. /// /// The exception is thrown if the era is not equal /// . /// /// /// The exception is thrown if the calendar year, month, or day is /// outside of the allowed range. /// internal void M_CheckYMDE(int year, int month, int day, ref int era) { M_CheckYME(year, month, ref era); M_ArgumentInRange("day", day, 1, GetDaysInMonth(year, month, era)); if (year == M_MaxYear && month == 10 && day > 10) throw new ArgumentOutOfRangeException("day", "Days in month 10 of year 9378 must" + " be between one and ten."); } internal const int epoch = 226895; // FIXME: this may need a "static". I don't know enough C#. internal int fixed_from_dmy(int day, int month, int year) { int k = epoch - 1; k += 365 * (year - 1); k += (8 * year + 21) / 33; if (month <= 7) k += 31 * (month - 1); else k += 30 * (month - 1) + 6; k += day; return k; } // FIXME: this may need a "static". I don't know enough C#. internal int year_from_fixed(int date) { return (33 * (date - epoch) + 3) / 12053 + 1; } // FIXME: this may need a "static". I don't know enough C#. internal void my_from_fixed(out int month, out int year, int date) { int day; year = year_from_fixed(date); day = date - fixed_from_dmy (1, 1, year); if (day < 216) month = day / 31 + 1; else month = (day - 6) / 30 + 1; } // FIXME: this may need a "static". I don't know enough C#. internal void dmy_from_fixed(out int day, out int month, out int year, int date) { year = year_from_fixed(date); day = date - fixed_from_dmy (1, 1, year); if (day < 216) { month = day / 31 + 1; day = day % 31 + 1; } else { month = (day-6) / 30 + 1; day = (day-6) % 30 + 1; } } // FIXME: this may need a "static". I don't know enough C#. internal bool is_leap_year(int year) { return (25 * year + 11) % 33 < 8; } /// /// Overrideden. Adds months to a given date. /// /// The /// to which to add /// months. /// /// The number of months to add. /// A new value, that /// results from adding to the specified /// DateTime. /// /// The exception is thrown if the /// return value is not in the years /// between 1 A.P. and 9999 C.E., inclusive. /// public override DateTime AddMonths(DateTime time, int months) { int rd = CCFixed.FromDateTime(time); int day, month, year; dmy_from_fixed(out day, out month, out year, rd); month += months; year += CCMath.div_mod(out month, month, 12); rd = fixed_from_dmy(day, month, year); DateTime t = CCFixed.ToDateTime(rd); t = t.Add(time.TimeOfDay); M_CheckDateTime(t); return t; } /// /// Overridden. Adds years to a given date. /// /// The /// to which to add /// years. /// /// The number of years to add. /// A new value, that /// results from adding to the specified /// DateTime. /// /// The exception is thrown if the /// return value is not in the years /// between 1 A.P. and 9999 C.E., inclusive. /// public override DateTime AddYears(DateTime time, int years) { int rd = CCFixed.FromDateTime(time); int day, month, year; dmy_from_fixed(out day, out month, out year, rd); year += years; rd = fixed_from_dmy(day, month, year); DateTime t = CCFixed.ToDateTime(rd); t = t.Add(time.TimeOfDay); M_CheckDateTime(t); return t; } /// /// Overriden. Gets the day of the month from /// . /// /// The /// that specifies a /// date. /// /// An integer giving the day of months, starting with 1. /// /// /// The exception is thrown if the /// parameter is not in the years /// between 1 A.P. and 9999 C.E., inclusive. /// public override int GetDayOfMonth(DateTime time) { int day, month, year; M_CheckDateTime(time); int rd = CCFixed.FromDateTime(time); dmy_from_fixed(out day, out month, out year, rd); return day; } /// /// Overriden. Gets the day of the week from the specified date. /// /// The /// that specifies a /// date. /// /// An integer giving the day of months, starting with 1. /// /// /// The exception is thrown if the /// parameter is not in the years /// between 1 A.P. and 9999 C.E., inclusive. /// public override DayOfWeek GetDayOfWeek(DateTime time) { M_CheckDateTime(time); int rd = CCFixed.FromDateTime(time); return (DayOfWeek)CCFixed.day_of_week(rd); } /// /// Overridden. Gives the number of the day in the year. /// /// The /// that specifies a /// date. /// /// An integer representing the day of the year, /// starting with 1. /// /// The exception is thrown if the /// parameter is not in the years /// between 1 A.P. and 9999 C.E., inclusive. /// public override int GetDayOfYear(DateTime time) { M_CheckDateTime(time); int rd = CCFixed.FromDateTime(time); int year = year_from_fixed(rd); int rd1_1 = fixed_from_dmy(1, 1, year); return rd - rd1_1 + 1; } /// /// Overridden. Gives the number of days in the specified month /// of the given year and era. /// /// An integer that gives the year. /// /// An integer that gives the month, starting /// with 1. /// An integer that gives the era of the specified /// year. /// An integer that gives the number of days of the /// specified month. /// /// The exception is thrown, if , /// ,or is outside /// the allowed range. /// public override int GetDaysInMonth(int year, int month, int era) { M_CheckYME(year, month, ref era); if (month <= 6) { return 31; } else if (month == 12 && !is_leap_year(year)) { return 29; } else { return 30; } } /// /// Overridden. Gives the number of days of the specified /// year of the given era. /// /// An integer that specifies the year. /// /// An ineger that specifies the era. /// /// An integer that gives the number of days of the /// specified year. /// /// The exception is thrown, if /// or are outside the /// allowed range. /// public override int GetDaysInYear(int year, int era) { M_CheckYE(year, ref era); return is_leap_year(year) ? 366 : 365; } /// /// Overridden. Gives the era of the specified date. /// /// The /// that specifies a /// date. /// /// An integer representing the era of the calendar. /// /// /// The exception is thrown if the /// parameter is not in the years /// between 1 A.P. and 9999 C.E., inclusive. /// public override int GetEra(DateTime time) { M_CheckDateTime(time); return PersianEra; } /// /// Overridden. Gives the number of the month of the specified /// date. /// /// The /// that specifies a /// date. /// /// An integer representing the month, /// starting with 1. /// /// The exception is thrown if the /// parameter is not in the years /// between 1 A.P. and 9999 C.E., inclusive. /// public override int GetMonth(DateTime time) { M_CheckDateTime(time); int rd = CCFixed.FromDateTime(time); int month, year; my_from_fixed(out month, out year, rd); return month; } /// /// Overridden. Gives the number of months in the specified year /// and era. /// /// An integer that specifies the year. /// /// An integer that specifies the era. /// /// An integer that gives the number of the months in the /// specified year. /// /// The exception is thrown, if the year or the era are not valid. /// public override int GetMonthsInYear(int year, int era) { M_CheckYE(year, ref era); return 12; } /// /// Overridden. Gives the number of the year of the specified /// date. /// /// The /// that specifies a /// date. /// /// An integer representing the year, /// starting with 1. /// /// The exception is thrown if the /// parameter is not in the years /// between 1 A.P. and 9999 C.E., inclusive. /// public override int GetYear(DateTime time) { M_CheckDateTime(time); int rd = CCFixed.FromDateTime(time); return year_from_fixed(rd); } /// /// Overridden. Tells whether the given day /// is a leap day. /// /// An integer that specifies the year in the /// given era. /// /// An integer that specifies the month. /// /// An integer that specifies the day. /// /// An integer that specifies the era. /// /// A boolean that tells whether the given day is a leap /// day. /// /// /// The exception is thrown, if the year, month, day, or era is not /// valid. /// public override bool IsLeapDay(int year, int month, int day, int era) { M_CheckYMDE(year, month, day, ref era); return is_leap_year(year) && month == 12 && day == 30; } /// /// Overridden. Tells whether the given month /// is a leap month. /// /// An integer that specifies the year in the /// given era. /// /// An integer that specifies the month. /// /// An integer that specifies the era. /// /// A boolean that tells whether the given month is a leap /// month. /// /// /// The exception is thrown, if the year, month, or era is not /// valid. /// public override bool IsLeapMonth(int year, int month, int era) { M_CheckYME(year, month, ref era); return false; } /// /// Overridden. Tells whether the given year /// is a leap year. /// /// An integer that specifies the year in the /// given era. /// /// An integer that specifies the era. /// /// A boolean that tells whether the given year is a leap /// year. /// /// /// The exception is thrown, if the year or era is not /// valid. /// public override bool IsLeapYear(int year, int era) { M_CheckYE(year, ref era); return is_leap_year(year); } /// /// Overridden. Creates the /// from the parameters. /// /// An integer that gives the year in the /// . /// /// An integer that specifies the month. /// /// An integer that specifies the day. /// /// An integer that specifies the hour. /// /// An integer that specifies the minute. /// /// An integer that gives the second. /// /// An integer that gives the /// milliseconds. /// /// An integer that specifies the era. /// /// A /// representig the date and time. /// /// /// The exception is thrown, if at least one of the parameters /// is out of range. /// public override DateTime ToDateTime(int year, int month, int day, int hour, int minute, int second, int milliseconds, int era) { M_CheckYMDE(year, month, day, ref era); M_CheckHMSM(hour, minute, second, milliseconds); int rd = fixed_from_dmy(day, month, year); return CCFixed.ToDateTime(rd, hour, minute, second, milliseconds); } // FIXME: Calendar.cs and HebrewCalendar.cs are different in // how they handle this. I have randomly chosen the // HebrewCalendar.cs implementation. public override int ToFourDigitYear (int year) { M_ArgumentInRange ("year", year, 0, 99); int baseExtra = this.twoDigitYearMax % 100; int baseCentury = this.twoDigitYearMax - baseExtra; if (year <= baseExtra) return baseCentury + year; else return baseCentury + year - 100; } } // class PersianCalendar } // namespace System.Globalization #endif