SharePoint 2013 のアイテムやドキュメントの更新日時のフォーマットについて

こんにちは。
SharePoint サポートチームの荒川です。

最近ユーザーから、SharePoint 2013 のアイテムやドキュメントの更新日時のフォーマットについての質問を受ける機会がありました。
SharePoint 2013 では以下のようにドキュメントの更新日時の情報が「 {n} 日前」のように、わかりやすく表示されます。

この機能は SharePoint 2013 で新たに追加された「フレンドリ形式」と呼ばれるもので、列の設定から従来の表示形式に戻すことも可能です。

フレンドリ形式ではユーザーがタイムゾーンを考慮せず感覚的におよその日時を把握できるため、一見して便利な機能に見えますが、一体どのようなルールで動作しているのかが気になるところですね。今回の投稿ではSharePoint 2013 のアイテムやドキュメントの更新日時のフォーマットについて調べた結果を共有したいと思います。

検証ベースでの動作結果
今回このルールに関する質問を受けて、更新日時のフォーマットの仕様に関する公開情報を探してみたのですが、現時点で公開されている情報は見当たりませんでした。
このため独自に調査を行った結果、2015 年 5 月 CU 時点において、以下の動作となっていること確認しました。

30 秒未満  数秒前
120 秒未満  約 1 分前
2 ~ 50 分以内  {n} 分前
110 分以内  約 1 時間前
111 分以降 ~ 日を跨ぐまで  {n} 時間前
日を跨いだ後  昨日 ({hh:mm})
翌々日以降 ~ 週を跨ぐまで  {v} 曜日 ({hh:mm})
週を跨いで以降 ~ 6 日以内  {n} 日前
7 日以降 ~ 当年中  {mm} 月 {dd} 日
翌年以降  {yyyy} 年 {mm} 月 {dd} 日

なお、2015 年 6 月現在、こちらの仕様は公式ドキュメントでは公開されておらず、今後の製品のバージョンにより動作が変更される可能性がありますのでご注意ください。(恐らく可能性は低いと思いますが。。)

技術的な詳細
SharePoint 2013 のアイテムやドキュメントの更新日時のフォーマットは、以下のスクリプト内の GetRelativeDateTimeString 関数により出力されています。

C:\Program Files\Common Files\microsoft shared\Web Server Extensions\15\TEMPLATE\LAYOUTS\SP.dateTimeUtil.js

デバッグ用の js ファイルを開いて内容を確認すると以下のコードが確認できます。

C:\Program Files\Common Files\microsoft shared\Web Server Extensions\15\TEMPLATE\LAYOUTS\SP.dateTimeUtil.debug.js

function GetRelativeDateTimeString(g) {   var b = null, c = null, d = g.split("|"); if (d[0] == "0")       return g.substring(2);   var a = d[1] == "1", h = d[2], e = d.length >= 4 ? d[3] : null, f = d.length >= 5 ? d[4] : null;   switch (h) {   case "1":      b = a ? Strings.STS.L_RelativeDateTime_AFewSecondsFuture : Strings.STS.L_RelativeDateTime_AFewSeconds;      break;  case "2":      b = a ? Strings.STS.L_RelativeDateTime_AboutAMinuteFuture : Strings.STS.L_RelativeDateTime_AboutAMinute;      break;   case "3":      c = GetLocalizedCountValue(a ? Strings.STS.L_RelativeDateTime_XMinutesFuture : Strings.STS.L_RelativeDateTime_XMinutes, a ? Strings.STS.L_RelativeDateTime_XMinutesFutureIntervals : Strings.STS.L_RelativeDateTime_XMinutesIntervals, Number(e));      break;   case "4":      b = a ? Strings.STS.L_RelativeDateTime_AboutAnHourFuture : Strings.STS.L_RelativeDateTime_AboutAnHour;      break;   case "5":      if (e == null)         b = a ? Strings.STS.L_RelativeDateTime_Tomorrow : Strings.STS.L_RelativeDateTime_Yesterday;      else         c = a ? Strings.STS.L_RelativeDateTime_TomorrowAndTime : Strings.STS.L_RelativeDateTime_YesterdayAndTime;      break;  case "6":      c = GetLocalizedCountValue(a ? Strings.STS.L_RelativeDateTime_XHoursFuture : Strings.STS.L_RelativeDateTime_XHours, a ? Strings.STS.L_RelativeDateTime_XHoursFutureIntervals : Strings.STS.L_RelativeDateTime_XHoursIntervals, Number(e));      break;   case "7":      if (f == null)         b = e;      else      c = Strings.STS.L_RelativeDateTime_DayAndTime;      break;   case "8":      c = GetLocalizedCountValue(a ? Strings.STS.L_RelativeDateTime_XDaysFuture : Strings.STS.L_RelativeDateTime_XDays, a ? Strings.STS.L_RelativeDateTime_XDaysFutureIntervals : Strings.STS.L_RelativeDateTime_XDaysIntervals, Number(e));      break;   case "9":      b = Strings.STS.L_RelativeDateTime_Today;   }   if (c != null) {      b = c.replace("{0}", e);      if (f != null)         b = b.replace("{1}", f);   }   return b;}

上記コードでは引数により受け渡された条件に従って、所定のフォーマットで日付文字列を出力しています。
この所定のフォーマットの部分は、言語ごとに異なるリソースを使用しており、日本語版 SharePoint では以下のファイル内で定義されています。

C:\Program Files\Common Files\microsoft shared\Web Server Extensions\15\TEMPLATE\LAYOUTS\1041\initstrings.js

デバッグ用の js ファイルから主要な部分を抜き出したものが以下となります。

C:\Program Files\Common Files\microsoft shared\Web Server Extensions\15\TEMPLATE\LAYOUTS\1041\initstrings.debug.js

Strings.STS.L_RelativeDateTime_AFewSecondsFuture = "数秒後";Strings.STS.L_RelativeDateTime_AboutAMinuteFuture = "約 1 分後";Strings.STS.L_RelativeDateTime_XMinutes = "{0} 分前";Strings.STS.L_RelativeDateTime_AboutAnHour = "約 1 時間前";Strings.STS.L_RelativeDateTime_YesterdayAndTime = "昨日 ({0})";Strings.STS.L_RelativeDateTime_XHours = "{0} 時間前";Strings.STS.L_RelativeDateTime_DayAndTime = "{0} ({1})";Strings.STS.L_RelativeDateTime_XDays = "{0} 日前";

GetRelativeDateTimeString javascript 関数に引き渡すデータは、SPRelativeDateTime.GetRelativeDateTimeJavaScriptString メソッドにより生成されています。

SPRelativeDateTime.GetRelativeDateTimeJavaScriptString method https://msdn.microsoft.com/en-us/library/office/microsoft.sharepoint.utilities.sprelativedatetime.getrelativedatetimejavascriptstring.aspx ***************************************** Gets a string representing the relative value of a DateTime. The string should be parsed by the javascript method GetRelativeDateTimeString(). The return value is in the form mode|args mode=0: Special case where args is passed through as the display text mode=1: Standard friendly relative display mode, eg "In 3 hours" Format: mode|bFuture|bucket|args bFuture refers to if localThen is at a later time than nowUTC bucket is the type of string to output args contains the values to plug into the string referenced by the bucket *****************************************

GetRelativeDateTimeJavaScriptString 関数の返り値は、mode|bFuture|bucket|args の形になっており、例えば返り値が 1|0|8|5 の場合、以下のように処理されます。

1 => Standard friendly relative display mode #1 の場合はフォーマット変換し、0 の場合は変換せずそのまま表示します。
0 => bFuture #false の場合は過去の日付として、true の場合は未来の日付として処理します。
8 => type of string to output #先述の GetRelativeDateTimeString 関数内の処理で switch 構文の選択肢として使用される値です。
5 => フォーマット対象のデータ

SharePoint 管理 PowerShell から直接以下のように GetRelativeDateTimeJavaScriptString 関数を呼び出すことで、SharePoint における実際の動作をテストできます。

$web = Get-SPWeb https://server01$now = Get-Date -Date "2015-03-05 00:00:00Z"$nowUTC = $now.ToUniversalTime() #30 秒未満 => 数秒前$localThen = $now.AddDays(0).AddHours(0).AddMinutes(0).AddSeconds(-29)[Microsoft.SharePoint.Utilities.SPRelativeDateTime]::GetRelativeDateTimeJavaScriptString($web,$nowUTC,$localThen,$true)=>1|0|1$localThen = $now.AddDays(0).AddHours(0).AddMinutes(0).AddSeconds(-30)[Microsoft.SharePoint.Utilities.SPRelativeDateTime]::GetRelativeDateTimeJavaScriptString($web,$nowUTC,$localThen,$true)=>1|0|2 #120 秒未満 => 約 1 分前$localThen = $now.AddDays(0).AddHours(0).AddMinutes(0).AddSeconds(-119)[Microsoft.SharePoint.Utilities.SPRelativeDateTime]::GetRelativeDateTimeJavaScriptString($web,$nowUTC,$localThen,$true)=>1|0|2$localThen = $now.AddDays(0).AddHours(0).AddMinutes(0).AddSeconds(-120)[Microsoft.SharePoint.Utilities.SPRelativeDateTime]::GetRelativeDateTimeJavaScriptString($web,$nowUTC,$localThen,$true)=>1|0|3|2 #2 ~ 50 分以内 => {n} 分前$localThen = $now.AddDays(0).AddHours(0).AddMinutes(-50).AddSeconds(0)[Microsoft.SharePoint.Utilities.SPRelativeDateTime]::GetRelativeDateTimeJavaScriptString($web,$nowUTC,$localThen,$true)=>1|0|3|50$localThen = $now.AddDays(0).AddHours(0).AddMinutes(-51).AddSeconds(0)[Microsoft.SharePoint.Utilities.SPRelativeDateTime]::GetRelativeDateTimeJavaScriptString($web,$nowUTC,$localThen,$true)=>1|0|4 #110 分以内 => 約 1 時間前$localThen = $now.AddDays(0).AddHours(0).AddMinutes(-110).AddSeconds(0)[Microsoft.SharePoint.Utilities.SPRelativeDateTime]::GetRelativeDateTimeJavaScriptString($web,$nowUTC,$localThen,$true)=>1|0|4$localThen = $now.AddDays(0).AddHours(0).AddMinutes(-111).AddSeconds(0)[Microsoft.SharePoint.Utilities.SPRelativeDateTime]::GetRelativeDateTimeJavaScriptString($web,$nowUTC,$localThen,$true)=>1|0|6|2 #111 分以降~日を跨ぐまで => {n} 時間前$localThen = $now.AddDays(0).AddHours(-9).AddMinutes(0).AddSeconds(0)[Microsoft.SharePoint.Utilities.SPRelativeDateTime]::GetRelativeDateTimeJavaScriptString($web,$nowUTC,$localThen,$true)=>1|0|6|9$localThen = $now.AddDays(0).AddHours(-9).AddMinutes(0).AddSeconds(-1)[Microsoft.SharePoint.Utilities.SPRelativeDateTime]::GetRelativeDateTimeJavaScriptString($web,$nowUTC,$localThen,$true)=>1|0|5|23:59 #日を跨いだ後 => 昨日 ({hh:mm}) $localThen = $now.AddDays(-1).AddHours(0).AddMinutes(0).AddSeconds(0)[Microsoft.SharePoint.Utilities.SPRelativeDateTime]::GetRelativeDateTimeJavaScriptString($web,$nowUTC,$localThen,$true)=>1|0|5|9:00 #翌々日以降~週を跨ぐまで => {v} 曜日 ({hh:mm}) $localThen = $now.AddDays(-2).AddHours(0).AddMinutes(0).AddSeconds(0)[Microsoft.SharePoint.Utilities.SPRelativeDateTime]::GetRelativeDateTimeJavaScriptString($web,$nowUTC,$localThen,$true)=>1|0|7|火曜日|9:00$localThen = $now.AddDays(-4).AddHours(0).AddMinutes(0).AddSeconds(0)[Microsoft.SharePoint.Utilities.SPRelativeDateTime]::GetRelativeDateTimeJavaScriptString($web,$nowUTC,$localThen,$true)=>1|0|7|日曜日|9:00$localThen = $now.AddDays(-4).AddHours(-9).AddMinutes(0).AddSeconds(-1)[Microsoft.SharePoint.Utilities.SPRelativeDateTime]::GetRelativeDateTimeJavaScriptString($web,$nowUTC,$localThen,$true)=>1|0|8|5 #週を跨いで以降 ~ 6 日以内 => {n} 日前$localThen = $now.AddDays(-6).AddHours(0).AddMinutes(0).AddSeconds(0)[Microsoft.SharePoint.Utilities.SPRelativeDateTime]::GetRelativeDateTimeJavaScriptString($web,$nowUTC,$localThen,$true)=>1|0|8|6 #7日以降 ~ 当年中 => {mm}月{dd}日$localThen = $now.AddDays(-7).AddHours(0).AddMinutes(0).AddSeconds(0)[Microsoft.SharePoint.Utilities.SPRelativeDateTime]::GetRelativeDateTimeJavaScriptString($web,$nowUTC,$localThen,$true)=>0|2月26日$localThen = $now.AddDays(-63).AddHours(0).AddMinutes(0).AddSeconds(0)[Microsoft.SharePoint.Utilities.SPRelativeDateTime]::GetRelativeDateTimeJavaScriptString($web,$nowUTC,$localThen,$true)=>0|1月1日 #翌年以降 => {yyyy} 年 {mm} 月 {dd} 日$localThen = $now.AddDays(-64).AddHours(0).AddMinutes(0).AddSeconds(0)[Microsoft.SharePoint.Utilities.SPRelativeDateTime]::GetRelativeDateTimeJavaScriptString($web,$nowUTC,$localThen,$true)=>0|2014年12月31日

まとめ
SharePoint 2013 のアイテムやドキュメントの更新日時のフォーマットは単に yyyy/MM/dd hh:mm の形式とするのではなく、先述のとおり一定のルールに基づきユーザーが感覚的に時系列を把握できるように工夫されています。細かい変更にはなりますが、時にはこうした観点で SharePoint のユーザーインターフェイスを眺めてみるのも面白いかもしれません。