重學c#系列——datetime 和 datetimeoffset[二十一]

敖毛毛發表於2021-12-25

前言

簡單介紹一下datetime和 datetimeoffset.

正文

瞭解一個國家的文化,就要了解一個國家的歷史。

要了解datetimeoffset,那麼很有必要了解一下datetime。

表示時間上的一刻,通常以日期和當天的時間表示。
繼承
Object-> ValueType-> DateTime

那麼可以看到DateTime 是值型別了。

實際上了解Datetime 有兩個重要的引數,一個是:ticks 另一個是:kind。

ticks
Int64
一個日期和時間,以公曆 0001 年 1 月 1 日 00:00:00.000 以來所經歷的以 100 納秒為間隔的間隔數來表示。
kind
DateTimeKind
列舉值之一,該值指示 ticks 是指定了本地時間、協調世界時 (UTC),還是兩者皆未指定。

這裡有一個值得注意的是這個ticks 是以公曆 0001 年 1 月 1 日 00:00:00.000 開始計算的,且單位是100納秒。

比如說,我這個ticks 是200,那麼就是20000納秒了。

和秒的計算公式為:1 納秒=0.000000001 秒。

DateTimeKind 指定是本地,還是utc,或者不指定。

初始化如下:

public DateTime(long ticks, DateTimeKind kind)
{
  if (ticks < 0L || ticks > 3155378975999999999L)
	throw new ArgumentOutOfRangeException(nameof (ticks), SR.ArgumentOutOfRange_DateTimeBadTicks);
  if (kind < DateTimeKind.Unspecified || kind > DateTimeKind.Local)
	throw new ArgumentException(SR.Argument_InvalidDateTimeKind, nameof (kind));
  this._dateData = (ulong) (ticks | (long) kind << 62);
}

而其他年月日其實也就是轉換為tickes。

public DateTime(int year, int month, int day)
{
  this._dateData = (ulong) DateTime.DateToTicks(year, month, day);
}

然後DateToTicks:

[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static long DateToTicks(int year, int month, int day)
{
  if (year < 1 || year > 9999 || (month < 1 || month > 12) || day < 1)
	ThrowHelper.ThrowArgumentOutOfRange_BadYearMonthDay();
  int[] numArray = DateTime.IsLeapYear(year) ? DateTime.s_daysToMonth366 : DateTime.s_daysToMonth365;
  if (day > numArray[month] - numArray[month - 1])
	ThrowHelper.ThrowArgumentOutOfRange_BadYearMonthDay();
  int num = year - 1;
  return (long) (num * 365 + num / 4 - num / 100 + num / 400 + numArray[month - 1] + day - 1) * 864000000000L;
}

由上面可值,其他的轉換都是通過_dateData 來轉換。

那麼datetime 有什麼問題呢? 其實可以想象一個問題,就是這個設計的時候呢。

有一個local 還有 一個 UTC,那麼可能local就是UTC呢?完全可能,從這裡開始概念就開始出現偏差了。

public static DateTime Now
{
	get
	{
		DateTime utc = UtcNow;
		long offset = TimeZoneInfo.GetDateTimeNowUtcOffsetFromUtc(utc, out bool isAmbiguousLocalDst).Ticks;
		long tick = utc.Ticks + offset;
		if (tick > DateTime.MaxTicks)
		{
			return new DateTime(DateTime.MaxTicks, DateTimeKind.Local);
		}
		if (tick < DateTime.MinTicks)
		{
			return new DateTime(DateTime.MinTicks, DateTimeKind.Local);
		}
		return new DateTime(tick, DateTimeKind.Local, isAmbiguousLocalDst);
	}
}

DateTime.Now,那麼就是本地時間了,然後看下面這個東西。

static void Main(string[] args)
{
	DateTime d = DateTime.Now;

	DateTime d2 = d.ToUniversalTime();

	Console.WriteLine(d.Equals(d2));

	Console.Read();
}

返回為false,理論上他們應該是同一個時間。

那麼這個equal 是幹什麼呢?

public bool Equals(DateTime value)
{
	return InternalTicks == value.InternalTicks;
}

檢視一下:

internal long InternalTicks => (long)(_dateData & TicksMask);

這個時候只要_dateData 不相等,那麼就不相等, 是的因為時區不同,這個自然不同。

// Returns the tick count for this DateTime. The returned value is
// the number of 100-nanosecond intervals that have elapsed since 1/1/0001
// 12:00am.
//
public long Ticks => InternalTicks;

static void Main(string[] args)
{
	DateTime d = DateTime.Now;

	DateTime d2 = d.ToUniversalTime();

	Console.WriteLine(d2.Ticks);
	Console.WriteLine(d.Ticks);
	Console.WriteLine(d.Equals(d2));

	Console.Read();
}

計算一下相差時間:

static void Main(string[] args)
{
	DateTime d = DateTime.Now;

	DateTime d2 = d.ToUniversalTime();

	Console.WriteLine((d.Ticks - d2.Ticks)/60/60/10000000);

	Console.WriteLine(d.Equals(d2));

	Console.Read();
}

那麼把本地時間調成協調世界時會發生什麼呢?

這時候就是true了,所以一套程式碼在不同時區的機器上有不同的效果了。

當然,對於datetime 我們就需要做相容了,就是要判斷其時區做處理,這裡就不演示了,

DateTimeOffset是什麼呢? 和datetime 有什麼關係呢。

首先來看時間相等情況:

static void Main(string[] args)
{
	DateTimeOffset d = DateTimeOffset.Now;

	DateTimeOffset d2 = d.ToUniversalTime();

	Console.WriteLine(d.Equals(d2));

	Console.Read();
}

它似乎解決了我們前面的問題,那麼其實怎麼做的呢?

DateTimeOffset 有兩個重要的引數:

ticks
Int64
一個日期和時間,以 0001 年 1 月 1 日午夜 12:00:00 以來所經歷的以 100 納秒為間隔的間隔數來表示。
offset
TimeSpan
與協調世界時 (UTC) 之間的時間偏移量。

例項化:

// Constructs a DateTimeOffset from a tick count and offset
public DateTimeOffset(long ticks, TimeSpan offset)
{
	_offsetMinutes = ValidateOffset(offset);
	// Let the DateTime constructor do the range checks
	DateTime dateTime = new DateTime(ticks);
	_dateTime = ValidateDate(dateTime, offset);
}

這裡可以看到DateTimeOffset 就沒有了kind 這個概念年了,而是直接指名了這個offset概念,就是和utc到底相差多少時差。

而其相等,那麼也是用UTc來計算的。

public bool Equals(DateTimeOffset other) =>
UtcDateTime.Equals(other.UtcDateTime);

這樣,其實就解決了這個時區概念了。

至此datetime 和 datetimeoffset 概念和比較到此結束,那麼遇到具體問題也可以根據其特徵來分析了,該系列持續更新。

相關文章