mamori017.log

歴史的クソブログ

サマータイムの調整時間をTimeZoneInfo.ConvertTimeで変換した

サマータイムについて話をしていて、仮に実施されるとなると結局UTCオフセット時間ありきで考えるのが良い*1という話になり、 既存のDateTime型データはTimeSpanを使用してDateTimeOffset型として扱うという方針でその場が何となく纏まりました。 その話の流れから、そもそも調整範囲の時間を変換するとどういう動きをするのか見てみたかったので試してみました。

環境

タイムゾーンが日本(UTC+09:00)だとサマータイムが設定されていないので、 ローカル環境のタイムゾーンを太平洋標準時(UTC-08:00)に変更しました。

f:id:mamori017:20180828113554p:plain

太平洋標準時でのサマータイム

期間は2007年以降3月の第2日曜日午前2:00から11月の第1日曜日午前2:00の間、 適用期間中は調整時間として1時間早く進み、期間終了時に1時間巻き戻るようになっています。 2018年は3月11日(日)午前2:00から11月4日(日)午前2:00までのあいだ適用されます。

太平洋夏時間 - Wikipedia

TimeZoneInfo

タイムゾーンを太平洋標準時に設定しているのでTimeZoneInfoで取得できるローカルの情報は(UTC-08:00) 太平洋標準時です。

TimeZoneInfo timeZoneInfo = TimeZoneInfo.FindSystemTimeZoneById(TimeZoneInfo.Local.Id);
名前
timeZoneInfo (UTC-08:00) 太平洋標準時 (米国およびカナダ)
BaseUtcOffset -08:00:00
DaylightName 太平洋夏時間
DisplayName (UTC-08:00) 太平洋標準時 (米国およびカナダ)
Id Pacific Standard Time
StandardName 太平洋標準時
SupportsDaylightSavingTime true

SupportsDaylightSavingTimeプロパティは指定したタイムゾーンでのサマータイムの設定有無になります。 設定がある場合はTimeZoneInfo.GetAdjustmentRulesでサマータイム期間内の調整情報を取得することができます。 現時点(2018/8)で太平洋標準時のGetAdjustmentRulesの結果は2件取得できます。 これは2007年にサマータイムの適用ルールが変更されているためです。

また、現時点日本標準時サマータイムは導入されていないのでTimeZoneInfo.SupportsDaylightSavingTimeはfalse、TimeZoneInfo.GetAdjustmentRulesは存在しません。

TimeZoneInfo.ConvertTime

2018年3月11日 02:00:00から2:59:59の間をDateTimeに指定してTimeZoneInfo.ConvertTimeを実行したところ、 DateTimeKindがLocalとUnspecifiedの値を持つオブジェクトは例外を出力しました。 太平洋標準時のサマータイムの調整情報が適用され、存在しない時刻として判断されるようです。

System.ArgumentException: '指定された DateTime は無効な時間を表しています。 たとえば、時計を進めると、進めた分の時間が無効になります。

DateTimeKindがUtcのデータは、DateTimeオブジェクトを宣言した際に世界標準時として2018/3/11 2:00:00を設定しているので、 TimeZoneInfo.ConvertTimeを実行したときにローカルタイムゾーンの太平洋標準時(UTC-08:00)との差分が適用され、8時間巻き戻った結果が出力されています。

// 例外が発生する
DateTime dateTimeLocalConvert =  TimeZoneInfo.ConvertTime(dateTimeLocal, timeZoneInfo);
// UTC上の2018/3/11 2:00:00からローカルタイムゾーンの太平洋標準時(UTC-08:00)分巻き戻った時刻が出力される
DateTime dateTimeUtcConvert = TimeZoneInfo.ConvertTime(dateTimeUtc, timeZoneInfo);
// 例外が発生する
DateTime dateTimeUnspecifiedConvert = TimeZoneInfo.ConvertTime(dateTimeUnspecified, timeZoneInfo);
名前 値(UTC)
dateTimeUtcConvert {2018/03/10 18:00:00}
Date {2018/03/10 0:00:00}
Day 10
DayOfWeek Saturday
DayOfYear 69
Hour 18
Kind Unspecified
Millisecond 0
Minute 0
Month 3
Second 0
Ticks 636563016000000000
TimeOfDay {18:00:00}
Year 2018

同様に時刻を3:00に変更して結果を見てみます。2018/03/11 03:00:00はサマータイム適用後にも存在する時刻(実際は2018/03/11 2:00で1時間調整された時刻 )になるので例外は発生しませんでした。

// サマータイム適用後にも存在する時刻なので正常な結果が出力される
DateTime dateTimeLocalConvert =  TimeZoneInfo.ConvertTime(new DateTime(2018, 3, 11, 3, 0, 0, DateTimeKind.Local), timeZoneInfo);
// UTC上の2018/3/11 2:00:00からローカルタイムゾーンの太平洋標準時(UTC-08:00)分巻き戻った時刻が出力される
DateTime dateTimeUtcConvert = TimeZoneInfo.ConvertTime(new DateTime(2018, 3, 11, 3, 0, 0, DateTimeKind.Utc), timeZoneInfo);
// サマータイム適用後にも存在する時刻なので正常な結果が出力される
DateTime dateTimeUnspecifiedConvert = TimeZoneInfo.ConvertTime(new DateTime(2018, 3, 11, 3, 0, 0, DateTimeKind.Unspecified), timeZoneInfo);
名前 値(Local) 値(Utc) 値(Unspecified)
Date {2018/03/11 0:00:00} {2018/03/10 0:00:00} {2018/03/11 0:00:00}
Day 11 10 11
DayOfWeek Sunday Saturday Sunday
DayOfYear 70 69 70
Hour 3 19 3
Kind Unspecified Unspecified Unspecified
Millisecond 0 0 0
Minute 0 0 0
Month 3 3 3
Second 0 0 0
Ticks 636563340000000000 636563052000000000 636563340000000000
TimeOfDay {03:00:00} {19:00:00} {03:00:00}
Year 2018 2018 2018

TimeZoneInfo.ConvertTimeで調整時間に該当する時刻を設定した場合は勝手に調整してくれるとうれしいんですがそうもいかないみたいです。

*1:あくまで某所で動いている何かの話

リンクとして追加したコードを含むプロジェクトをAppveyorでビルドする

f:id:mamori017:20180802111441p:plain

.NET Frameworkのプロジェクトで一部のコードを別のソリューションからリンクで参照するということはよくあるケースだと思います。 GitHubでバージョン管理している.NETプロジェクトはAppVeyorを使用してビルドしているのですが、 AppVeyorでこの関係性を持ったプロジェクトをビルドするのは厳しいと、調べもせず勝手に思っていたためビルド対象から除外していました。 *1

しかし考えてみれば、AppVeyor内のビルド環境に参照先のファイルが存在するリポジトリさえクローンできればビルドは通るはず。 感覚的にはnuget restore的に、ビルド前にAppVeyor内でgit cloneしてやれば解決するじゃないかと思ったのでやってみました。

前提として、ローカル環境のビルド対象(TargetProject)リポジトリと参照先(ReferenceProject)リポジトリは同階層にいます。

設定

appveyor.ymlのbefore_build:項目に参照したいプロジェクトのリポジトリをクローンするよう追記します。これだけ。

AppVeyor setting for .NET projects with link refer ...

これでビルド対象(TargetProject)リポジトリと参照先(ReferenceProject)リポジトリがローカル環境と同様、 同じディレクトリ内に存在することになります。

ビルド

GitHubへプッシュしたらあっさりビルドが通りました。

f:id:mamori017:20180802140447p:plain

余談というか失敗

難しく考えすぎて参照するリポジトリのクローン先をビルド対象のディレクトリ内に設定していました。 クローン先ディレクトリを作成してそこを参照させようというのが狙いです。

before_build:
  - nuget restore TargetProject.sln
  - mkdir .\TargetProject\ReferenceProject
  - git clone -q --branch=master https://githostserver/ReferenceProject.git .\TargetProject\ReferenceProject

before_buildへの記述が実行されたディレクトリの状態。

./
├ /.vs
├ /TargetProject
│ ├─ /ReferenceProject(クローン先)
│ ├─ /bin
│ ├─ /obj
│ ├─ /Properties
│ ├─ Class1.cs
│ ├─ Class2.cs
│ ├─ Class3.cs
│ └─ TargetProject.csproj
└ TargetProject.sln

これではTargetProject.slnがローカルと同様の設定のまま、参照先コードのパスだけが変わってしまっているためビルドが通りません。 クローンしたファイルを認識させるにはプロジェクトファイルの構成を変更する必要があります。

参照しているコードをReference.csとすると、TargetProject.csprojの元の状態はこのようになっています。

<ItemGroup>
  <Compile Include="..\..\ReferenceProject\ReferenceProject\Reference.cs">
    <Link>ReferenceProject\Reference.cs</Link>
  </Compile>
</ItemGroup>

ローカルのビルド環境ではこの参照パスが通りますが、AppVeyorでビルドする場合はクローン先のパスが変わってしまっているので、 ソリューション構成がDebugの時はローカル環境でのビルド、Releaseの時はAppVeyorでのビルド用に設定を変更し、それぞれで参照先が異なる状態にしました。

<ItemGroup>
  <Compile Include="..\..\ReferenceProject\ReferenceProject\Reference.cs" Condition=" '$(Configuration)' == 'Debug' ">
    <Link>ReferenceProject\Reference.cs</Link>
  </Compile>
  <Compile Include=".\ReferenceProject\ReferenceProject\Reference.cs" Condition=" '$(Configuration)' == 'Release' ">
    <Link>ReferenceProject\Reference.cs</Link>
  </Compile>
</ItemGroup>

MSBuild Conditions

これでもビルドは通るのですが、参照コードの増減があるたびに.csprojを手で書き換えないといけないので、リスクがあるうえ面倒です。 appveyor.ymlに1行追加することと比べるとデメリットしかないと思うのでこの手法は取るべきではないです。 何より早く気づけという話でした。

*1:リンクで参照せず、実体をコピーしてビルドするプロジェクトを別に作成して管理とビルドをしていた。恥ずかしい。

Visual Studioのデバッグシンボルをローカルにキャッシュした

f:id:mamori017:20180607130838p:plain

某所からVisual Studioデバッグが遅いと言われたので確認してみたところ、 デバッグシンボルがローカルにキャッシュされるよう設定されていなかった。*1

これではデバッグの都度サーバーからシンボルを読み込むことになり効率が悪いので、デバッグシンボルをローカルにキャッシュするよう設定することにした。

設定方法

f:id:mamori017:20180607130833p:plain

  • ディレクトリパスを入力したら「すべてのシンボルを読み込む」をクリックする。 シンボルサーバーから読み込んだデータがキャッシュされる。

f:id:mamori017:20180607130836p:plain

  • シンボルの読み込みが終わったら[OK]をクリックする。

  • シンボルファイルの場所のリストに「Microsoftシンボルサーバー」が無い場合はサーバーの場所を指定する。

Microsoft シンボル サーバーの場所は、http://msdl.microsoft.com/download/symbols です。

シンボルを使用したデバッグ

*1:デフォルトでキャッシュされていた気がするが設定されてなかった。