C#でUnixTimeを扱う（4.5以前）

- 現在時刻をUnixTimeへ変換

```csharp
var baseDt = new DateTimeOffset(1970, 1, 1, 0, 0, TimeSpan.Zero);
var unixtime = (DateTimeOffset.Now - baseDt).Ticks/10000000;
// DateTimeOffset.NowはDateTimeOffset.UtcNowでも良い 
```

- UnixTimeから変換

```csharp
long unixtime = 1519372282; // 変換元unixtime
var baseDt = new DateTimeOffset(1970, 1, 1, 0, 0, TimeSpan.Zero);
var dt = new DateTimeOffset(unixtime*10000000 + baseDt.Ticks, TimeSpan.Zero);
```
# 解説

UnixTimeは1970年1月1日0時0分0秒からの秒数なので、C#からUnixTimeへの変換は1970/1/1 00:00:00を引いてから秒に変換すれば良い。C#の時間は単位が100ナノ秒なので、10000000(=10*1000*10000)で割る。UnixTimeからの変換は逆の操作になる。

# DateTimeとDateTimeOffset

解説はこれですべてなのですが、C#で日時を扱うとき何クラスを使うべきか、という問題があります。結論は**常にDateTimeOffsetを使っておけ**、です。理由は`DateTime`は機能が不足していて使えないからです。

## DateTimeの特徴

- 基本的に日時しか扱えず、時差やタイムゾーンが扱えない。
- いちおう`Kind`プロパティで、`Kind.Utc`(UTC)、`Kind.Local`(現地時刻)、`Kind.Unspecified`(指定なし)の３種類が指定できる。
- 逆に言うと、UTCと現地時刻（≒アプリケーションが動くPCのタイムゾーン）以外が扱えない。
- `Kind`プロパティの値が異なっていても、普通に足し算引き算ができてしまう。（もちろん結果は間違い）
- `Kind`プロパティに`Kind.Unspecified`(指定なし)とか指定されても、どう扱えばよいか分からない。（多分タイムゾーンに関係ない、純粋な日時を値として使いたいときのプロパティ値か？）
- 夏時間？　そんなものはない。

という感じなので、いちいち`Kind`プロパティの値を気にして使用しなけらばならないし、`Kind.Unspecified`のときどう扱ってよいか分からないし、特に最大の問題は`Kind`プロパティの認知度が低くて、バグを誘発しやすい、ということです。

## DateTimeOffsetの使い方

そういう訳で、DateTimeOffsetを使おう、ということです。

- DateTimeOffsetは、内部で日時と標準時間との時差(`TimeSpan`)を保持している。
- 現在時刻は、`DateTimeOffset.Now`(タイムゾーンが現地時間)と`DateTimeOffset.UtcNow`(標準時間)の２種類の方法で取得できる。

```csharp
var dt1 = DateTimeOffset.Now;
var dt2 = DateTimeOffset.UtcNow;
Console.WriteLine($"{dt1}, {dt2}");
// → 2018/04/02 11:35:53 +09:00, 2018/04/02 2:35:53 +00:00
```

- コンストラクタで標準時間との時差を指定できる。現地時間を指定したい場合は、`TimeSpan`に`TimeZoneInfo.Local.BaseUtcOffset`を指定する。夏時間もコレを指定しておけば、勝手に考慮してくれる。

```csharp
var dt1 = new DateTimeOffset(2018, 1, 2, 12, 23, 45, TimeSpan.FromHours(-7));
Console.WriteLine($"{dt1}");
// → 2018/01/02 12:23:45 -07:00

var dt2 = new DateTimeOffset(2018, 1, 2, 12, 23, 45, TimeZoneInfo.Local.BaseUtcOffset);
Console.WriteLine($"{dt2}");
// → 2018/01/02 12:23:45 +09:00
```

- 足し算や引き算も、`TimeSpan`を考慮して計算してくれる。

```csharp
var dt1 = new DateTimeOffset(2018, 1, 2, 12, 23, 45, TimeSpan.FromHours(-7));
var dt2 = new DateTimeOffset(2018, 1, 2, 12, 23, 45, TimeSpan.FromHours(+9));
Console.WriteLine(dt2 - dt1);
// → -16:00:00
```

- `==`の比較は、同一の日時であれば`TimeSpan`が異っていても`True`になる。

```csharp
var dt1 = new DateTimeOffset(2018, 1, 2, 13, 23, 45, TimeSpan.FromHours(+1));
var dt2 = new DateTimeOffset(2018, 1, 2, 14, 23, 45, TimeSpan.FromHours(+2));
Console.WriteLine(dt1 == dt2); // → True
```

## 文字列から/への変換

プログラムで日時を扱うとき、一番多いのは現在時刻をどう取得するかですが、次に多いのは文字列から/への変換でしょう。

### 文字列 → DateTimeOffset

文字列 → `DateTimeOffset` が、`Parse()`メソッドと`ParseExact()`メソッドがあります。`Parse()`は書式を自動的に判別して解析してくれるのですが、日本で一番使われているだろう、年月日を数値で並べた書式（たとえば、20180102131415）は、判別してくれませんでした。そのため、`ParseExact()`を使うのですが、例のごとく引数に書式指定をします。

書式一覧については、コピペしてもしょうがないので、[MSのドキュメント](https://docs.microsoft.com/ja-jp/dotnet/standard/base-types/standard-date-and-time-format-strings)を参照してください。（そのうち表で書くかも）

- 年月日時分秒

```csharp
var str = @"20180102131415";
var dt = DateTimeOffset.ParseExact(str, "yyyyMMddHHmmss", CultureInfo.CurrentCulture);
Console.WriteLine($"{dt.ToLocalTime()}");
// → 2018/01/02 13:14:15 +09:00
```

- 年月日時分秒＋Z

```csharp
var str = @"20180102131415Z";
var dt = DateTimeOffset.ParseExact(str, "yyyyMMddHHmmssZ", CultureInfo.CurrentCulture);
Console.WriteLine($"{dt.ToLocalTime()}");
// → 2018/01/02 22:14:15 +09:00
```

- 年月日時分秒＋時差

```csharp
var str = @"20180102131415-0700";
var dt = DateTimeOffset.ParseExact(str, "yyyyMMddHHmmsszzzzz", CultureInfo.CurrentCulture);
Console.WriteLine($"{dt.ToLocalTime()}");
// → 2018/01/03 5:14:15 +09:00
```

- 年月日時分秒＋ミリ秒

```csharp
var str = @"20180102131415.789-0700";
var dt = DateTimeOffset.ParseExact(str, "yyyyMMddHHmmss.fffzzzzz", CultureInfo.CurrentCulture);
```

ちなみに年が２桁の場合は、29(2000年扱い)と30(1900年扱い)で境界があります。

### DateTimeOffset → 文字列

`ToString()`の引数で、書式指定ができます。書式については[MSのドキュメント](https://docs.microsoft.com/ja-jp/dotnet/standard/base-types/standard-date-and-time-format-strings)を参照してください。

```csharp
var dt = new DateTimeOffset(2018, 1, 2, 12, 23, 45, TimeZoneInfo.Local.BaseUtcOffset);
Console.WriteLine(dt.ToString("yyyy/MM/dd tt hh:mm:ss"));
// → 2018/01/02 午後 12:23:45

Console.WriteLine(dt.ToString("yyyy/MM/dd HH:mm:ss"));
// → 2018/01/02 12:23:45
```

※「午後12時」になってしまう。「午後0時」にはできない。

# うるう秒の扱い

もはやUnixTimeとなんら関係もないですが、うるう秒についても補足をしておきます。

## プログラム内での扱い

まずC#のプログラム内では、うるう秒を考慮する必要はありません。なぜなら、**Windowsにはうるう秒が無い**からです。`DateTimeOffset`のドキュメントを見ても、秒がとりうる値は0～59になっています。（ちなみにWindowsの世界でうるう秒がないのは、多分MSがうるう秒導入反対の立場をとっているからだと思います）

## 時刻同期

では、実際うるう秒があった場合どうなるでしょううか。これはコンピューター間の時刻同期がどうなされるか、で決まるのですが、時刻同期はたいていNTPを使って行われます。そしてNTPはNTPサーバーとNTPクライアントの２つの立場があります。

- NTPクライアント

NTPサーバーに合わせるだけ。

- NTPサーバー
    - NTPサーバーがWindowsの場合：<br>
Windowsの世界にはうるう秒が無いので、現実の世界でうるう秒入るとき、その前後の１秒の長さを調整して、うるう秒を挿入する。そのため「60秒」は存在しない。（ちなみに117時報サービスも、１秒の長さの調整でうるう秒を挿入します）

    - NTPサーバーがLinux系の場合：<br>
NTPクライアントがWindowsの場合、**うるう秒の挿入を無視**する。次の時刻同期のタイミングで時間が合う。つまり、うるう秒の挿入～時刻同期の間まで、１秒ずれることになる。

## 時間の計算

1時間を加える、という処理を例にすると、Windowsの世界ではうるう秒が無いので常に1時間＝3600秒であり、問題がありません。むしろJava(Linux系)の場合、3600秒と3601秒の場合があって、困ることになります（そのため、仕様書に「1時間後」ではなく「3600秒後」と書くようにする）。

## うるう秒をまとめると…

WindowsとLinuxが混在しているシステムでは、１秒ずれることがあるかもしれない、ということを意味します。これはNTPサーバーがLinuxの場合だけ考慮すれば良い、ということでは無いです。

例えば、１時間後にファイルを送信する、という処理があった場合、Windowsでは3600秒後に送信するが、Linuxでは3601秒後を期待していた、ということがあるからです。

とは言うものの、現実に問題が発生するかと言われると、タイムスタンプが超重要なシステムでも無い限り、私の経験では問題が発生したことはありませんでした。ただし今後はどうなるかは分かりません。現在は、まだ１秒のずれがシステムに大きな問題を起こすほど、シビアな時間管理が必要ないのですが、未来については、もしかしたら１秒がシステムにとって重要になるかもしれません。

# まとめ

- `DateTime`はオワコン。`DateTimeOffset`を使え。
- UnixTimeの変換は、基準日（1970年1月1日0:00）を足し引きして、秒に変換すれば良い。`DateTimeOffset`の単位は100ナノ秒なので、10000000(=10*1000*1000)を掛けたり割ったりすれば良い。
- 時差は`DateTimeOffset`のコンストラクタ、`TimeSpan`で指定。`TimeSpan`に現地時間を指定するなら`TimeZoneInfo.Local.BaseUtcOffset`。
- 現在時刻は、`DateTimeOffset.Now`（現地時間）か`DateTimeOffset.UtcNow`（標準時間）。
- 時間の計算は、`DateTimeOffset`を使っておけば、時差も考慮されるので、思考停止で使える。
- 文字列からの変換は`ParseExact()`、文字列への変換は`ToString()`。書式は例のごとく日付書式を引数に指定する。

# 参考文献

- [グローバル化はなぜ日時処理問題を引き起こすのか](https://www.slideshare.net/atsushikambara/handling-globaldatetime-27589220)

日付について書かれたサイトは数多いですが、多分このスライドショーが一番分かりやすいです。