فصل ۶: DateTime، DateTimeOffset و Time Zoneها
فروش یا انتشار این ترجمه منوط به داشتن مجوز لازم از صاحب حقوق اثر است.
Constructorهای TimeSpan عبارتاند از:
public TimeSpan (int hours, int minutes, int seconds);
public TimeSpan (int days, int hours, int minutes, int seconds);
public TimeSpan (int days, int hours, int minutes, int seconds,
int milliseconds);
public TimeSpan (int days, int hours, int minutes, int seconds,
int milliseconds, int microseconds);
public TimeSpan (long ticks); // Each tick = 100ns
Methodهای Static از خانوادهٔ From... زمانی راحتترند که میخواهید Interval را فقط با یک Unit مانند Minute یا Hour مشخص کنید:
public static TimeSpan FromDays (double value);
public static TimeSpan FromHours (double value);
public static TimeSpan FromMinutes (double value);
public static TimeSpan FromSeconds (double value);
public static TimeSpan FromMilliseconds (double value);
public static TimeSpan FromMicroseconds (double value);
برای مثال:
Console.WriteLine (new TimeSpan (2, 30, 0)); // 02:30:00
Console.WriteLine (TimeSpan.FromHours (2.5)); // 02:30:00
Console.WriteLine (TimeSpan.FromHours (-2.5)); // -02:30:00
TimeSpan Operatorهای < و > و همچنین + و - را Overload میکند. Expression زیر TimeSpanای برابر 2.5 Hour ایجاد میکند:
TimeSpan.FromHours(2) + TimeSpan.FromMinutes(30);
Expression بعدی یک Second کمتر از 10 Day است:
TimeSpan.FromDays(10) - TimeSpan.FromSeconds(1); // 9.23:59:59
با همین Expression میتوان Propertyهای Integer با نامهای Days، Hours، Minutes، Seconds و Milliseconds را نشان داد:
TimeSpan nearlyTenDays = TimeSpan.FromDays(10) - TimeSpan.FromSeconds(1);
Console.WriteLine (nearlyTenDays.Days); // 9
Console.WriteLine (nearlyTenDays.Hours); // 23
Console.WriteLine (nearlyTenDays.Minutes); // 59
Console.WriteLine (nearlyTenDays.Seconds); // 59
Console.WriteLine (nearlyTenDays.Milliseconds); // 0
در مقابل، Propertyهای Total... Valueهایی از نوع double برمیگردانند که کل Time Span را توصیف میکنند:
Console.WriteLine (nearlyTenDays.TotalDays); // 9.99998842592593
Console.WriteLine (nearlyTenDays.TotalHours); // 239.999722222222
Console.WriteLine (nearlyTenDays.TotalMinutes); // 14399.9833333333
Console.WriteLine (nearlyTenDays.TotalSeconds); // 863999
Console.WriteLine (nearlyTenDays.TotalMilliseconds); // 863999000
Method Static با نام Parse عمل معکوس ToString را انجام میدهد و String را به TimeSpan تبدیل میکند. TryParse نیز همین کار را انجام میدهد، اما اگر Conversion شکست بخورد بهجای Throw کردن Exception مقدار false برمیگرداند.
کلاس XmlConvert نیز Methodهایی برای Conversion بین TimeSpan و string دارد که Protocolهای استاندارد Formatting در XML را رعایت میکنند.
Value پیشفرض یک TimeSpan برابر TimeSpan.Zero است.
TimeSpan همچنین میتواند Time of Day، یعنی مدت گذشته از Midnight، را نمایش دهد. برای دریافت Time of Day جاری، DateTime.Now.TimeOfDay را فراخوانی کنید.
DateTime و DateTimeOffset
DateTime و DateTimeOffset Structهای Immutable برای نمایش Date و در صورت نیاز Time هستند. Resolution آنها 100 ns است و Range سالهای 0001 تا 9999 را پوشش میدهند.
DateTimeOffset از نظر Function شبیه DateTime است. Feature متمایز آن این است که Offset از Coordinated Universal Time یا UTC را نیز ذخیره میکند؛ این موضوع هنگام Comparison بین Valueهای مربوط به Time Zoneهای مختلف Result معنادارتری ایجاد میکند.
انتخاب بین DateTime و DateTimeOffset
DateTime و DateTimeOffset در نحوهٔ مدیریت Time Zone تفاوت دارند. یک DateTime یک Flag سهحالته دارد که نشان میدهد Value نسبت به کدام مورد است:
- Local Time روی Computer جاری.
- UTC، معادل مدرن Greenwich Mean Time.
- Unspecified.
DateTimeOffset دقیقتر است و Offset از UTC را بهصورت TimeSpan ذخیره میکند:
July 01 2019 03:00:00 -06:00
این موضوع بر Equality Comparison اثر میگذارد و عامل اصلی در انتخاب بین DateTime و DateTimeOffset است. بهطور مشخص:
DateTime در Comparison، Flag سهحالته را نادیده میگیرد و دو Value را وقتی برابر میداند که Year، Month، Day، Hour، Minute و سایر اجزای آنها برابر باشند.DateTimeOffset دو Value را زمانی برابر میداند که به یک Point در Time اشاره کنند.
بنابراین DateTime دو Value زیر را متفاوت میداند، در حالی که DateTimeOffset آنها را برابر میداند:
July 01 2019 09:00:00 +00:00 (GMT)
July 01 2019 03:00:00 -06:00 (local time, Central America)
در بیشتر Caseها Logic برابری DateTimeOffset ترجیح داده میشود. برای مثال هنگام محاسبهٔ اینکه کدامیک از دو Event بینالمللی جدیدتر است، DateTimeOffset بهصورت ضمنی Answer صحیح میدهد. به همین شکل، Hackerای که در حال برنامهریزی Distributed Denial of Service Attack است احتمالاً DateTimeOffset را انتخاب میکند! انجام همین کار با DateTime نیازمند Standard کردن کل Application روی یک Time Zone واحد، معمولاً UTC، است.
این کار به دو دلیل مشکلساز است:
- برای User-friendly بودن،
DateTimeهای UTC پیش از Formatting باید بهصورت صریح به Local Time تبدیل شوند. - بهسادگی ممکن است این موضوع فراموش شود و یک
DateTime Local وارد Logic شود.
با این حال DateTime برای مشخص کردن Value نسبت به Computer Local در Runtime بهتر است. برای مثال اگر بخواهید در تمام Officeهای بینالمللی خود برای Sunday آینده ساعت 3 A.M. به وقت Local، زمانی که فعالیت کمترین است، Archive زمانبندی کنید، DateTime مناسبتر است چون Local Time هر Site را رعایت میکند.
Time Zoneها و Equality Comparison در بخش «Dates and Time Zones» در صفحهٔ 312 با عمق بیشتری بررسی میشوند.
ساختن DateTime
DateTime Constructorهایی تعریف میکند که Integerهای Year، Month و Day و بهصورت اختیاری Hour، Minute، Second، Millisecond و از .NET 7 به بعد Microsecond را میپذیرند:
public DateTime (int year, int month, int day);
public DateTime (int year, int month, int day,
int hour, int minute, int second, int millisecond);
اگر فقط Date را مشخص کنید، Time بهطور ضمنی Midnight یعنی 0:00 قرار میگیرد.
Constructorهای DateTime همچنین اجازه میدهند DateTimeKind را تعیین کنید؛ Enumای با Valueهای زیر:
Unspecified, Local, Utc
این Valueها متناظر با Flag سهحالتهٔ بخش قبلی هستند. Unspecified پیشفرض است و به این معناست که DateTime نسبت به Time Zone بیطرف است. Local یعنی نسبت به Local Time Zone روی Computer جاری. یک DateTime Local اطلاعاتی دربارهٔ اینکه دقیقاً به کدام Time Zone اشاره میکند در خود ندارد.
همچنین برخلاف DateTimeOffset، Offset عددی از UTC را ذخیره نمیکند.
Property با نام Kind در DateTime مقدار DateTimeKind آن را برمیگرداند.
Constructorهای DateTime برای پذیرش Object از نوع Calendar نیز Overload شدهاند. این کار اجازه میدهد Date را با هر یک از Subclassهای Calendar تعریفشده در System.Globalization مشخص کنید:
DateTime d = new DateTime (5767, 1, 1,
new System.Globalization.HebrewCalendar());
Console.WriteLine (d); // 12/12/2006 12:00:00 AM
Formatting تاریخ در این Example به Settings Control Panel Computer بستگی دارد. یک DateTime همیشه از Calendar پیشفرض Gregorian استفاده میکند؛ در این Example، Conversion یکباره هنگام Construction انجام میشود. برای اجرای Computation با Calendar دیگر باید Methodهای همان Subclass از Calendar را به کار ببرید.
همچنین میتوانید DateTime را با یک Value منفرد از نوع long به نام ticks بسازید، که تعداد Intervalهای 100-ns از Midnight در 01/01/0001 است.
برای Interoperability، DateTime Methodهای Static با نام FromFileTime و FromFileTimeUtc را برای Conversion از Windows File Time، که بهصورت long مشخص میشود، و FromOADate را برای Conversion از OLE Automation Date/Time، که بهصورت double مشخص میشود، ارائه میکند.
برای ساخت DateTime از String، Method Static با نام Parse یا ParseExact را فراخوانی کنید. هر دو Method Flagها و Format Providerهای اختیاری میپذیرند؛ ParseExact یک Format String نیز میپذیرد. Parsing در بخش «Formatting and Parsing» در صفحهٔ 317 با جزئیات بیشتر بررسی میشود.
ساختن DateTimeOffset
DateTimeOffset مجموعهٔ مشابهی از Constructorها دارد. تفاوت این است که UTC Offset را نیز بهصورت TimeSpan مشخص میکنید:
public DateTimeOffset (int year, int month, int day,
int hour, int minute, int second,
TimeSpan offset);
public DateTimeOffset (int year, int month, int day,
int hour, int minute, int second, int millisecond,
TimeSpan offset);
TimeSpan باید تعداد صحیحی از Minuteها باشد؛ در غیر این صورت Exception رخ میدهد.
DateTimeOffset همچنین Constructorهایی برای پذیرش Object از نوع Calendar، Value از نوع long ticks و Methodهای Static با نام Parse و ParseExact دارد که String را میپذیرند.
میتوانید یک DateTimeOffset را از DateTime موجود با Constructorهای زیر بسازید:
public DateTimeOffset (DateTime dateTime);
public DateTimeOffset (DateTime dateTime, TimeSpan offset);
یا از Implicit Cast استفاده کنید:
DateTimeOffset dt = new DateTime (2000, 2, 3);
اگر Offset را مشخص نکنید، بر اساس Value مربوط به DateTime با Ruleهای زیر استنباط میشود:
- اگر
DateTimeKind برابر Utc باشد، Offset صفر است. - اگر
DateTimeKind برابر Local یا Unspecified یعنی پیشفرض باشد، Offset از Time Zone Local جاری گرفته میشود.
برای Conversion در جهت دیگر، DateTimeOffset سه Property دارد که Value از نوع DateTime برمیگردانند:
UtcDateTime یک DateTime با Time از نوع UTC برمیگرداند.LocalDateTime یک DateTime در Local Time Zone جاری برمیگرداند و در صورت نیاز Conversion انجام میدهد.DateTime یک DateTime در Zoneای که Value با آن مشخص شده، با Kind برابر Unspecified برمیگرداند؛ یعنی UTC Time بهاضافهٔ Offset را برمیگرداند.
DateTime/DateTimeOffset جاری
هر دو Type با نام DateTime و DateTimeOffset Property Static با نام Now دارند که Date و Time جاری را برمیگرداند:
Console.WriteLine (DateTime.Now); // 11/11/2019 1:23:45 PM
Console.WriteLine (DateTimeOffset.Now); // 11/11/2019 1:23:45 PM -06:00
DateTime همچنین Property با نام Today دارد که فقط بخش Date را برمیگرداند:
Console.WriteLine (DateTime.Today); // 11/11/2019 12:00:00 AM
Property Static با نام UtcNow Date و Time جاری در UTC را برمیگرداند:
Console.WriteLine (DateTime.UtcNow); // 11/11/2019 7:23:45 AM
Console.WriteLine (DateTimeOffset.UtcNow); // 11/11/2019 7:23:45 AM +00:00
Precision تمام این Methodها به Operating System وابسته است و معمولاً در Range حدود 10 تا 20 ms قرار دارد.
کار با Date و Time
DateTime و DateTimeOffset مجموعهای مشابه از Instance Propertyها دارند که Componentهای مختلف Date/Time را برمیگردانند:
DateTime dt = new DateTime (2000, 2, 3, 10, 20, 30);
Console.WriteLine (dt.Year); // 2000
Console.WriteLine (dt.Month); // 2
Console.WriteLine (dt.Day); // 3
Console.WriteLine (dt.DayOfWeek); // Thursday
Console.WriteLine (dt.DayOfYear); // 34
Console.WriteLine (dt.Hour); // 10
Console.WriteLine (dt.Minute); // 20
Console.WriteLine (dt.Second); // 30
Console.WriteLine (dt.Millisecond); // 0
Console.WriteLine (dt.Ticks); // 630851700300000000
Console.WriteLine (dt.TimeOfDay); // 10:20:30 (returns a TimeSpan)
DateTimeOffset همچنین Property با نام Offset از نوع TimeSpan دارد.
هر دو Type برای Computation، Instance Methodهای زیر را ارائه میکنند که بیشترشان Argument از نوع double یا int میپذیرند:
AddYears AddMonths AddDays
AddHours AddMinutes AddSeconds AddMilliseconds AddTicks
همهٔ این Methodها یک DateTime یا DateTimeOffset جدید برمیگردانند و مواردی مانند Leap Year را در نظر میگیرند. برای Subtract کردن میتوانید Value منفی بدهید.
Method با نام Add یک TimeSpan را به DateTime یا DateTimeOffset اضافه میکند. Operator با علامت + نیز برای همین کار Overload شده است:
TimeSpan ts = TimeSpan.FromMinutes (90);
Console.WriteLine (dt.Add (ts));
Console.WriteLine (dt + ts); // same as above
همچنین میتوانید TimeSpan را از DateTime/DateTimeOffset کم کنید و یک DateTime/DateTimeOffset را از دیگری کم کنید. حالت دوم یک TimeSpan میدهد:
DateTime thisYear = new DateTime (2015, 1, 1);
DateTime nextYear = thisYear.AddYears (1);
TimeSpan oneYear = nextYear - thisYear;
Formatting و Parsing مربوط به DateTime
فراخوانی ToString روی DateTime Result را به شکل Short Date شامل Numberها و سپس Long Time شامل Secondها Format میکند؛ برای مثال:
11/11/2019 11:50:30 AM
بهصورت پیشفرض Control Panel مربوط به Operating System مواردی مانند اینکه Day، Month یا Year ابتدا بیاید، استفاده از Leading Zero و استفاده از Time با فرمت 12 یا 24 Hour را تعیین میکند.
فراخوانی ToString روی DateTimeOffset مشابه است، با این تفاوت که Offset نیز برگردانده میشود:
11/11/2019 11:50:30 AM -06:00
Methodهای ToShortDateString و ToLongDateString فقط بخش Date را برمیگردانند. Format مربوط به Long Date نیز توسط Control Panel تعیین میشود؛ برای مثال Wednesday, 11 November 2015. Methodهای ToShortTimeString و ToLongTimeString فقط بخش Time را برمیگردانند، مانند 17:10:10؛ مورد اول Second را حذف میکند.
چهار Methodی که توضیح داده شد در واقع Shortcut چهار Format String متفاوتاند. ToString برای پذیرش Format String و Provider Overload شده است و اجازه میدهد Range وسیعی از Optionها را تعیین کنید و نحوهٔ اعمال Regional Settingها را کنترل کنید. این موضوع در بخش «Formatting and Parsing» در صفحهٔ 317 توضیح داده میشود.
Methodهای Static از خانوادهٔ Parse/TryParse و ParseExact/TryParseExact عمل معکوس ToString را انجام میدهند و String را به DateTime یا DateTimeOffset تبدیل میکنند. این Methodها نیز Overloadهایی برای Format Provider دارند. Methodهای Try* بهجای Throw کردن FormatException مقدار false برمیگردانند.
Valueهای Null برای DateTime و DateTimeOffset
چون DateTime و DateTimeOffset Struct هستند، ذاتاً Nullable نیستند. وقتی به Nullability نیاز دارید دو راه وجود دارد:
- استفاده از Nullable Type مانند
DateTime? یا DateTimeOffset?. - استفاده از Field Static با نام
DateTime.MinValue یا DateTimeOffset.MinValue که Value پیشفرض این Typeها هستند.
Nullable Type معمولاً بهترین روش است، زیرا Compiler به جلوگیری از Mistake کمک میکند. DateTime.MinValue برای Backward Compatibility با Code نوشتهشده پیش از C# 2.0، یعنی قبل از معرفی Nullable Value Type، مفید است.
DateOnly و TimeOnly
Structهای DateOnly و TimeOnly از .NET 6 برای زمانی هستند که فقط میخواهید Date یا Time را نمایش دهید.
DateOnly شبیه DateTime است اما Component مربوط به Time ندارد. همچنین DateTimeKind ندارد؛ در عمل همیشه Unspecified است و مفهومی از Local یا Utc ندارد. روش تاریخی پیش از DateOnly استفاده از DateTime با Time صفر یعنی Midnight بود. مشکل این Approach این است که اگر Time غیرصفر به Code راه پیدا کند، Equality Comparison شکست میخورد.
TimeOnly شبیه DateTime است اما Component مربوط به Date ندارد. TimeOnly برای ثبت Time of Day طراحی شده و برای Applicationهایی مانند ثبت Alarm Time یا Opening Hour مناسب است.
Dateها و Time Zoneها
در این بخش با جزئیات بیشتر بررسی میکنیم Time Zoneها چگونه روی DateTime و DateTimeOffset اثر میگذارند. همچنین Type با نام TimeZoneInfo را بررسی میکنیم که اطلاعاتی دربارهٔ Offsetهای Time Zone و Daylight Saving Time فراهم میکند.
DateTime و Time Zoneها
DateTime در مدیریت Time Zone سادهانگارانه عمل میکند. در داخل، یک DateTime را با دو Piece از Information ذخیره میکند:
- Number با اندازهٔ 62 Bit که Count مربوط به Tickها از 1/1/0001 را نشان میدهد.
- Enum با اندازهٔ 2 Bit که
DateTimeKind یعنی Unspecified، Local یا Utc را نشان میدهد.
هنگام مقایسهٔ دو Instance از DateTime فقط Tick Valueها Compare میشوند و DateTimeKind نادیده گرفته میشود:
DateTime dt1 = new DateTime (2000, 1, 1, 10, 20, 30, DateTimeKind.Local);
DateTime dt2 = new DateTime (2000, 1, 1, 10, 20, 30, DateTimeKind.Utc);
Console.WriteLine (dt1 == dt2); // True
DateTime local = DateTime.Now;
DateTime utc = local.ToUniversalTime();
Console.WriteLine (local == utc); // False
Instance Methodهای ToUniversalTime/ToLocalTime به Universal/Local Time تبدیل میکنند. این Methodها Time Zone Settingهای جاری Computer را اعمال میکنند و DateTime جدیدی با DateTimeKind برابر Utc یا Local برمیگردانند. اگر ToUniversalTime را روی DateTimeای که از قبل Utc است، یا ToLocalTime را روی Valueای که از قبل Local است فراخوانی کنید Conversion انجام نمیشود. اما اگر روی DateTime با Kind برابر Unspecified فراخوانی کنید Conversion انجام میشود.
با Method Static با نام DateTime.SpecifyKind میتوانید DateTimeای بسازید که فقط از نظر Kind با دیگری تفاوت دارد:
DateTime d = new DateTime (2015, 12, 12); // Unspecified
DateTime utc = DateTime.SpecifyKind (d, DateTimeKind.Utc);
Console.WriteLine (utc); // 12/12/2015 12:00:00 AM
DateTimeOffset و Time Zoneها
در داخل، DateTimeOffset از Fieldای از نوع DateTime که Value آن همیشه UTC است و Fieldی Integer با اندازهٔ 16 Bit برای UTC Offset بر حسب Minute تشکیل شده است. Comparisonها فقط DateTime یعنی UTC را بررسی میکنند؛ Offset عمدتاً برای Formatting استفاده میشود.
Methodهای ToUniversalTime/ToLocalTime یک DateTimeOffset برمیگردانند که همان Point در Time را با Offset از نوع UTC یا Local نمایش میدهد. برخلاف DateTime، این Methodها Value زیربنایی Date/Time را تغییر نمیدهند و فقط Offset را تغییر میدهند:
DateTimeOffset local = DateTimeOffset.Now;
DateTimeOffset utc = local.ToUniversalTime();
Console.WriteLine (local.Offset); // -06:00:00 (in Central America)
Console.WriteLine (utc.Offset); // 00:00:00
Console.WriteLine (local == utc); // True
برای وارد کردن Offset در Comparison باید از Method با نام EqualsExact استفاده کنید:
Console.WriteLine (local.EqualsExact (utc)); // False
TimeZoneInfo
کلاس TimeZoneInfo اطلاعاتی دربارهٔ نام Time Zone، UTC Offset و Ruleهای Daylight Saving Time فراهم میکند.
TimeZone
Method Static با نام TimeZone.CurrentTimeZone یک TimeZone برمیگرداند:
TimeZone zone = TimeZone.CurrentTimeZone;
Console.WriteLine (zone.StandardName); // Pacific Standard Time
Console.WriteLine (zone.DaylightName); // Pacific Daylight Time
Method با نام GetDaylightChanges Information مشخص Daylight Saving Time را برای Year دادهشده برمیگرداند:
DaylightTime day = zone.GetDaylightChanges (2019);
Console.WriteLine (day.Start.ToString ("M")); // 10 March
Console.WriteLine (day.End.ToString ("M")); // 03 November
Console.WriteLine (day.Delta); // 01:00:00
TimeZoneInfo
Property Static با نام TimeZoneInfo.Local یک Object از نوع TimeZoneInfo بر اساس Local Settingهای جاری برمیگرداند. کد زیر Result را در صورت اجرا در California نشان میدهد:
TimeZoneInfo zone = TimeZoneInfo.Local;
Console.WriteLine (zone.StandardName); // Pacific Standard Time
Console.WriteLine (zone.DaylightName); // Pacific Daylight Time
Methodهای IsDaylightSavingTime و GetUtcOffset بهشکل زیر کار میکنند:
DateTime dt1 = new DateTime (2019, 1, 1); // DateTimeOffset works, too
DateTime dt2 = new DateTime (2019, 6, 1);
Console.WriteLine (zone.IsDaylightSavingTime (dt1)); // True
Console.WriteLine (zone.IsDaylightSavingTime (dt2)); // False
Console.WriteLine (zone.GetUtcOffset (dt1)); // -08:00:00
Console.WriteLine (zone.GetUtcOffset (dt2)); // -07:00:00
برای دریافت TimeZoneInfo مربوط به هر Time Zone دنیا، Method با نام FindSystemTimeZoneById را با Zone ID فراخوانی کنید. به دلایلی که بهزودی روشن میشود به Western Australia میرویم:
TimeZoneInfo wa = TimeZoneInfo.FindSystemTimeZoneById
("W. Australia Standard Time");
Console.WriteLine (wa.Id); // W. Australia Standard Time
Console.WriteLine (wa.DisplayName); // (GMT+08:00) Perth
Console.WriteLine (wa.BaseUtcOffset); // 08:00:00
Console.WriteLine (wa.SupportsDaylightSavingTime); // True
Property با نام Id متناظر با Valueای است که به FindSystemTimeZoneById داده میشود. Method Static با نام GetSystemTimeZones تمام Time Zoneهای دنیا را برمیگرداند؛ بنابراین میتوانید تمام Stringهای معتبر Zone ID را چنین List کنید:
foreach (TimeZoneInfo z in TimeZoneInfo.GetSystemTimeZones())
Console.WriteLine (z.Id);
Method Static با نام ConvertTime یک DateTime یا DateTimeOffset را از Time Zoneای به دیگری تبدیل میکند. میتوانید فقط Destination TimeZoneInfo یا هم Source و هم Destination را بدهید. همچنین با ConvertTimeFromUtc و ConvertTimeToUtc میتوانید مستقیماً از یا به UTC Convert کنید.
برای کار با Daylight Saving Time، TimeZoneInfo Methodهای اضافی زیر را فراهم میکند:
IsInvalidTime وقتی DateTime در Hour یا Delta حذفشده هنگام جلو رفتن Clock قرار دارد، true برمیگرداند.IsAmbiguousTime وقتی DateTime یا DateTimeOffset در Hour یا Delta تکرارشده هنگام عقب رفتن Clock قرار دارد، true برمیگرداند.GetAmbiguousTimeOffsets Arrayای از TimeSpan برمیگرداند که Choiceهای معتبر Offset برای DateTime یا DateTimeOffset مبهم را نشان میدهد.
از TimeZoneInfo نمیتوانید Dateهای سادهای که Start و End مربوط به Daylight Saving Time را نشان میدهند دریافت کنید. در عوض باید GetAdjustmentRules را فراخوانی کنید که Summary Declarative از تمام Ruleهای Daylight Saving را که برای همهٔ Yearها اعمال میشوند برمیگرداند. هر Rule، DateStart و DateEnd دارد که Range تاریخ معتبر Rule را نشان میدهد:
foreach (TimeZoneInfo.AdjustmentRule rule in wa.GetAdjustmentRules())
Console.WriteLine ("Rule: applies from " + rule.DateStart +
" to " + rule.DateEnd);
Western Australia نخستین بار در سال 2006 و در میانهٔ Season، Daylight Saving Time را معرفی کرد و سپس در 2009 آن را لغو کرد. این اتفاق به Rule ویژهای برای Year اول نیاز داشت؛ بنابراین دو Rule وجود دارد:
Rule: applies from 1/01/2006 12:00:00 AM to 31/12/2006 12:00:00 AM
Rule: applies from 1/01/2007 12:00:00 AM to 31/12/2009 12:00:00 AM
هر AdjustmentRule یک Property با نام DaylightDelta از نوع TimeSpan دارد که تقریباً در همهٔ Caseها یک Hour است، و Propertyهایی با نام DaylightTransitionStart و DaylightTransitionEnd. دو مورد اخیر از Type با نام TimeZoneInfo.TransitionTime هستند که Propertyهای زیر را دارد:
public bool IsFixedDateRule { get; }
public DayOfWeek DayOfWeek { get; }
public int Week { get; }
public int Day { get; }
public int Month { get; }
public DateTime TimeOfDay { get; }
Transition Time کمی پیچیده است، زیرا باید هم Date ثابت و هم Date شناور را نمایش دهد. Example از Date شناور عبارت «آخرین Sunday در March» است. Ruleهای Interpretation یک Transition Time چنیناند:
- اگر برای End Transition،
IsFixedDateRule برابر true، Day برابر 1، Month برابر 1 و TimeOfDay برابر DateTime.MinValue باشد، Daylight Saving Time در آن Year End ندارد. این فقط در نیمکرهٔ جنوبی و هنگام معرفی اولیهٔ Daylight Saving در Region میتواند رخ دهد. - در غیر این صورت اگر
IsFixedDateRule برابر true باشد، Propertyهای Month، Day و TimeOfDay Start یا End مربوط به Adjustment Rule را تعیین میکنند. - در غیر این صورت اگر
IsFixedDateRule برابر false باشد، Propertyهای Month، DayOfWeek، Week و TimeOfDay Start یا End Adjustment Rule را تعیین میکنند.
در حالت آخر، Week به Week ماه اشاره میکند و Value برابر 5 به معنی آخرین Week است. میتوانیم این موضوع را با Enumerate کردن Adjustment Ruleهای Time Zone با نام wa نشان دهیم:
foreach (TimeZoneInfo.AdjustmentRule rule in wa.GetAdjustmentRules())
{
Console.WriteLine ("Rule: applies from " + rule.DateStart +
" to " + rule.DateEnd);
Console.WriteLine (" Delta: " + rule.DaylightDelta);
Console.WriteLine (" Start: " + FormatTransitionTime
(rule.DaylightTransitionStart, false));
Console.WriteLine (" End: " + FormatTransitionTime
(rule.DaylightTransitionEnd, true));
Console.WriteLine();
}
در FormatTransitionTime Ruleهای توضیحدادهشده را رعایت میکنیم:
static string FormatTransitionTime (TimeZoneInfo.TransitionTime tt,
bool endTime)
{
if (endTime && tt.IsFixedDateRule
&& tt.Day == 1 && tt.Month == 1
&& tt.TimeOfDay == DateTime.MinValue)
return "-";
string s;
if (tt.IsFixedDateRule)
s = tt.Day.ToString();
else
s = "The " +
"first second third fourth last".Split() [tt.Week - 1] +
" " + tt.DayOfWeek + " in";
return s + " " + DateTimeFormatInfo.CurrentInfo.MonthNames [tt.Month-1]
+ " at " + tt.TimeOfDay.TimeOfDay;
}
Daylight Saving Time و DateTime
اگر از DateTimeOffset یا DateTime از نوع UTC استفاده کنید، Equality Comparison تحتتأثیر Daylight Saving Time قرار نمیگیرد. اما در DateTimeهای Local، Daylight Saving میتواند مشکلساز باشد.
Ruleها را میتوان چنین خلاصه کرد:
- Daylight Saving روی Local Time اثر میگذارد اما روی UTC Time نه.
- هنگامی که Clock به عقب برمیگردد، Comparisonهایی که به جلو رفتن Time تکیه دارند اگر و فقط اگر از
DateTimeهای Local استفاده کنند میشکنند.