背景
掲題の件について、AppServiceのドキュメントでは以下のようにあります。
アプリケーション設定 WEBSITE_TIME_ZONE
で指定ができる。設定値はOSで異なります。
- Windows:
Tokyo Standard Time
(tzutil /L
より) - Linux:
Asia/Tokyo
(https://en.wikipedia.org/wiki/List_of_tz_database_time_zones)
さて、App Serviceの上で動作しているAzure Functionではどうなのかというと、同様にドキュメントがありますが、Linuxの従量課金はサポートしていないとのこと。
では確認してみましょう、というのがこの記事です。AzureではデフォルトのタイムゾーンはUTCになっているとはよく聞きますが、そこもついでに。
Azure Functionの環境情報
- OS: Windows/Linux
- ランタイム: .NET Core 3.1 (C#)
- プラン: 従量課金/Premium
- リージョン: 東日本
先に結果
ドキュメント通り、Linuxの従量課金のみWEBSITE_TIME_ZONEの設定が効かないことがわかりました。(2022/01/18)
デフォルトのタイムゾーンはUTCになっているようです。
WEBSITE_TIME_ZONE設定は、Windowsの従量課金、WindowsのPremium、LinuxのPremiumでできます。
またリージョンは関係ありませんでした。
Region | OS | Plan | WEBSITE_TIME_ZONE | "timezone(Id)" |
---|---|---|---|---|
東日本 | Linux | 従量課金 | (なし) | Etc/UTC |
東日本 | Linux | 従量課金 | Asia/Tokyo | Etc/UTC |
東日本 | Windows | 従量課金 | (なし) | UTC |
東日本 | Windows | 従量課金 | Tokyo Standard Time | Tokyo Standard Time |
東日本 | Linux | Premium | (なし) | Etc/UTC |
東日本 | Linux | Premium | Asia/Tokyo | Asia/Tokyo |
東日本 | Windows | Premium | (なし) | UTC |
東日本 | Windows | Premium | Tokyo Standard Time | Tokyo Standard Time |
検証
検証用コード
func new
でC#用のHTTP Triggerのテンプレートを作り、少し書き換えてJSONを返すようにしました。
using System;
using System.IO;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Azure.WebJobs;
using Microsoft.Azure.WebJobs.Extensions.Http;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Logging;
using Newtonsoft.Json;
using System.Collections.Generic;
namespace az_function_datetime
{
public static class ViewDateTime
{
[FunctionName("ViewDateTime")]
public static async Task<IActionResult> Run(
[HttpTrigger(AuthorizationLevel.Function, "get", "post", Route = null)] HttpRequest req,
ILogger log)
{
log.LogInformation("C# HTTP trigger function processed a request.");
var now = DateTime.Now;
var utcNow = DateTime.UtcNow;
var timezone = TimeZoneInfo.Local;
var envWebsiteTimeZone = Environment.GetEnvironmentVariable("WEBSITE_TIME_ZONE") ?? "(none)";
return new OkObjectResult(new Dictionary<string, object>{
["now"] = now,
["utcNow"] = utcNow,
["timezone"] = timezone,
["envWebsiteTimeZone"] = envWebsiteTimeZone
});
}
}
}
func startでローカル実行(システム時間=日本)
{
"now": "2022-01-18T13:17:10.6813074+09:00",
"utcNow": "2022-01-18T04:17:10.681313Z",
"timezone": {
"Id": "Tokyo Standard Time",
"DisplayName": "(UTC+09:00) 大阪、札幌、東京",
"StandardName": "東京 (標準時)",
"DaylightName": "東京 (夏時間)",
"BaseUtcOffset": "09:00:00",
"AdjustmentRules": null,
"SupportsDaylightSavingTime": false
}
}
まぁ、概ね期待通りの動作ですね。
各環境検証結果
このレスポンスをOS * Plan * 設定有無の8通りでレスポンスがどうなかったを調べたのが以下です。
思ったよりtimezoneinfoがノイズになってしまったので、gistにまとめました。結果は最初に書いた通りです。
そういえば、ランタイム毎の違いはあるんでしょうか?私はAzure FunctionではC#で生きていくと決めたので調査はしません・・・
蛇足: C#のDateTime型はめんどくさい
C#のDateTime型はtickと呼ばれる西暦0001年1月1日00:00:00からの100マイクロ秒単位の経過時間(62bit)と、DateTimeKind(2bit)で日時を表現します。
タイムゾーンやオフセットなどの情報は持っていないため、Kind値が異なるDateTime型同士は正しく比較できません。(DateTimeOffset型はオフセットがありオフセットを考慮した比較も可能です。)
その代わり、DateTimeKindで「2=Local(システム時間)」「1=UTC」「0=Unspecified(その他)」の3つの区分値を使い、何の時間かを表現しています。
DateTime.Nowで得られる日時は「Local」になります。DateTime.UtcNowで得られる日時は「Utc」になります。UTCはわかりますね。Localは何でしょうか。
例えばOSなどのシステムのタイムゾーンが日本時間なら、「Local」は日本時間と見なされ、UTC時間よりも9時間(32400秒)進んでいることになります。Kindが異なる場合はこの差を足し引きしてLocalとUtcを変換できるようになっています。
実際、DateTime.Now.Ticks - DateTime.UtcNow.Ticksとすると、約324000000000Tickが得られます。(同じ時間なはずですが、そういう仕組みになっています。)
このおかげでLocalかUTCでの間であれば、DateTime.ToUniversalTimeやDateTime.ToLocalTimeを使うことで相互の変換は正しく行えます。
しかし時間はシステム時間とUTC時間だけではありません。
なので、TimeZoneInfoのConvertTimeを使うことで任意のタイムゾーンの変換をサポートしています。あるタイムゾーンに変換を行うと、システムorUTC→あるタイムゾーンの時差の差を元にしてTicksを再計算し、新しいDateTimeを返します。しかし、その瞬間DateTimeKindはUnspecifiedになります。
そして、ここから先は自己責任の世界となり、責任をもって「あるタイムゾーンのDatetime」として運用しなければなりません。
このDateTimeをうっかり、Utc時間やLocal時間に変換すると予期しない結果になります。これを戻すには、またあるタイムゾーンを使ってTimezoneInfo.ConvertTimeToUtcで一旦Utc時間に変換することです。
こんな感じでC#のDateTimeは小難しい事情を抱えています。わかればなんてことはないんですが、DateTimeという1個の型みてそこまで見抜くのは至難の業だと思います。もしUnspecifiedなDateTimeを見かけたら不幸に思いましょう。
というわけで(Unspecifiedの下りは全く関係なかったですが)、C#の「システム時間」は動作環境によるため、Azure Functionでの挙動を調べてみたかったということでした。
C#のDateTimeに疲れたら、Nodatimeを使ってみるといいかもしれません。
(C#に限らず時間の扱いは言語、フォーマット、システムどれをとっても面倒なので別途まとめたい。素晴らしい記事は既にたくさんあるけども。)