今回の結論
private static long _unixTimeMillisecond_baseTicks = new DateTime(1970, 1, 1).Ticks / TimeSpan.TicksPerMillisecond;
public long GetUnixTimeMillsecond() =>
DateTime.UtcNow.Ticks / TimeSpan.TicksPerMillisecond - _unixTimeMillisecond_baseTicks;
今回のお話は、C#でUnixTimeを作る方法です。
システムの時間を拾って、longの値にします。
UnixTimeは、秒単位のものと、ミリ秒のものをよく見かけます。
今回は、ミリ秒を対象とします。
ネットでちょっと探してみると、以下の様なコードが見つかります。
long GetUnixTimeNow() => (long)(DateTime.UtcNow.Subtract(new DateTime(1970, 1, 1))).TotalMilliseconds;
long GetUnixTime2() => DateTimeOffset.UtcNow.ToUnixTimeMilliseconds();
実はこの2つ、概ね同じ値を返すのですが、ちょっと値が変わる時があります。
ちょっとテストコードで、試してみます。
[TestMethod()]
public void UnixTimeTest()
{
var dt = new DateTime(638396640000009999, DateTimeKind.Utc);
var a = (long)dt.Subtract(_baseTime).TotalMilliseconds; // 1704067200001
var b = new DateTimeOffset(dt).ToUnixTimeMilliseconds();// 1704067200000
Assert.IsTrue(a == b); // 一致しない
}
aの方で、TimeSpan.TotalMillsecondsを呼んでますが、これはdoubleを返します。
このため、境界値付近で、誤差が発生するんですね。
もちろん、Roundとかで補正することもできなくはないのですが、パフォーマンス面では不利になりますし、お勧めできません。
では、DateTimeOffset.ToUnixTimeMilliseconds でOKかという事になりますが、基本的に問題は無いですが、パフォーマンス面ではベストではありません。
それでは、以下はパフォーマンスについて検証を行っていきます。
public class UnixTimeBench
{
private static DateTime _unixTime_BaseTime = new DateTime(1970, 1, 1);
[Benchmark]
public long A_DateTime_Now_TimeSpan_TotalMilliseconds() =>
(long)(DateTime.Now.ToUniversalTime().Subtract(_unixTime_BaseTime).TotalMilliseconds);
[Benchmark]
public long B_DateTime_UtcNow_TimeSpan_TotalMilliseconds() =>
(long)(DateTime.UtcNow.Subtract(_unixTime_BaseTime).TotalMilliseconds);
[Benchmark]
public long C_DateTimeOffset_UtcNow_ToUnixTimeMilliseconds() =>
DateTimeOffset.UtcNow.ToUnixTimeMilliseconds();
private static long _unixTimeMillisecond_baseTicks = new DateTime(1970, 1, 1).Ticks / TimeSpan.TicksPerMillisecond;
[Benchmark]
public long D_DateTime_UtcNow_SelfCalc() =>
DateTime.UtcNow.Ticks / TimeSpan.TicksPerMillisecond - _unixTimeMillisecond_baseTicks;
}
//| Method | Mean | Error | StdDev |
//|----------------------------------------------- |---------:|---------:|---------:|
//| A_DateTime_Now_TimeSpan_TotalMilliseconds | 53.29 ns | 0.455 ns | 0.404 ns |
//| B_DateTime_UtcNow_TimeSpan_TotalMilliseconds | 36.93 ns | 0.134 ns | 0.126 ns |
//| C_DateTimeOffset_UtcNow_ToUnixTimeMilliseconds | 34.54 ns | 0.134 ns | 0.119 ns |
//| D_DateTime_UtcNow_SelfCalc | 31.94 ns | 0.217 ns | 0.193 ns |
実行環境は、.Net8です。
では説明をしていきますが、速い方から行きます。
Dは、Cとほぼ同じ事をしています。Dは無駄が無く、最速です。
では、Cがなぜ若干遅いのかというと、いちどDateTimeOffsetにしている関係で、
内部で、2つのフィールドが作成されます。
private readonly DateTime _dateTime;
private readonly short _offsetMinutes;
今回のUnixTimeを求めるのに、UtcTimeのため、_offsetMinutesは不要です。その分遅くなっています。
A,Bは、上記の値の問題もありますのでお勧めしませんが、速度に関して分析します。
Bは、long型であるTicksをdoubleに変えて計算し、longに戻す処理があり、その分遅くなっています。
Aは、Bに加え、ローカルタイム(今回は日本時間)を取得し、UtcTimeに戻す処理が入ってるので、その分さらに遅くなってしまっています。