はじめに

この記事は、GoまたはJavaScriptで日時処理を実装するエンジニアを対象にしています。特に、世界中のユーザーを扱うWebアプリケーションやAPIを開発している方に役立ちます。

この記事を読むことで、ローカルタイムをUTCに変換する際のサマータイム(夏時間)の扱い方が理解できます。GoのtimeパッケージとJavaScriptのIntl.DateTimeFormatを使った、タイムゾーンを考慮した日時変換の実装方法を習得できます。サマータイムの罠にハマらず、世界中のユーザーに正確な時刻情報を提供できるようになります。

前提知識

この記事を読み進める上で、以下の知識があるとスムーズです。

  • GoまたはJavaScriptの基本的な構文
  • UTCとローカルタイムの違いについての基礎知識
  • タイムゾーンの基本概念(IANAタイムゾーンデータベースについての知識があると尚良い)

サマータイムとは?なぜプログラマーは注意が必要なのか

サマータイム(Daylight Saving Time:DST)は、夏の間に時計を1時間進める制度です。日本では採用されていませんが、アメリカ、ヨーロッパ、オーストラリアなど多くの国で採用されています。

プログラマーにとってサマータイムは厄介な存在です。なぜなら、「2時から3時へ進める」というルールのため、3月のある日の午前2時は存在しないし、11月のある日の午前1時は2回存在するからです。これを正しく扱わないと、予約システムやスケジュールアプリで重大な不具合が発生します。

さらに、サマータイムの実施日は国・地域によって異なり、年によって変更されることもあります。2023年まで毎年実施されていたアレクサンドリア(エジプト)のサマータイムは、2024年から廃止されました。このような動的な変化に対応するため、プログラマーは単純な時差計算ではなく、適切なタイムゾーンデータベースを使用する必要があります。

GoとJavaScriptでの正確な時刻変換の実装方法

Goでの実装

Goでは、timeパッケージがタイムゾーンとサマータイムを自動的に処理してくれます。まずは基本的な変換から見ていきましょう。

Go
package main import ( "fmt" "time" ) func main() { // 東京のタイムゾーンを取得 loc, err := time.LoadLocation("Asia/Tokyo") if err != nil { panic(err) } // 東京のローカル時刻を作成(サマータイムは適用されない) localTime := time.Date(2024, 7, 15, 15, 30, 0, 0, loc) // UTCに変換 utcTime := localTime.UTC() fmt.Printf("東京: %s\n", localTime.Format("2006-01-02 15:04:05 MST")) fmt.Printf("UTC: %s\n", utcTime.Format("2006-01-02 15:04:05 MST")) }

次に、サマータイムが適用されるニューヨークでの例を見てみましょう。

Go
package main import ( "fmt" "time" ) func main() { // ニューヨークのタイムゾーンを取得 loc, _ := time.LoadLocation("America/New_York") // 夏時間期間中の日付(2024年7月15日) summerTime := time.Date(2024, 7, 15, 15, 30, 0, 0, loc) // 標準時間期間中の日付(2024年12月15日) standardTime := time.Date(2024, 12, 15, 15, 30, 0, 0, loc) fmt.Printf("夏時間: %s (UTC: %s)\n", summerTime.Format("2006-01-02 15:04:05 MST"), summerTime.UTC().Format("15:04")) fmt.Printf("標準時間: %s (UTC: %s)\n", standardTime.Format("2006-01-02 15:04:05 MST"), standardTime.UTC().Format("15:04")) }

重要なポイントは、time.LoadLocation()で正確なタイムゾーンデータベースを読み込むことです。これにより、サマータイムのルールが自動的に適用されます。

JavaScriptでの実装

JavaScriptでは、状況が少し複雑です。従来のDateオブジェクトだけでは、タイムゾーンの処理が不十分でした。そこで、現代的なアプローチとしてIntl.DateTimeFormatを使用します。

Javascript
// ユーザーのローカル時刻をUTCに変換する関数 function localToUTC(date, timeZone) { // 指定されたタイムゾーンで日付をフォーマット const formatter = new Intl.DateTimeFormat('en-US', { timeZone: timeZone, year: 'numeric', month: '2-digit', day: '2-digit', hour: '2-digit', minute: '2-digit', second: '2-digit', hour12: false }); // タイムゾーンオフセットを取得 const utcFormatter = new Intl.DateTimeFormat('en-US', { timeZone: 'UTC', timeZoneName: 'longOffset' }); const parts = formatter.formatToParts(date); const dateParts = {}; parts.forEach(part => { if (part.type !== 'literal') { dateParts[part.type] = part.value; } }); // 年月日時分秒を数値に変換 const year = parseInt(dateParts.year); const month = parseInt(dateParts.month) - 1; // JavaScriptの月は0始まり const day = parseInt(dateParts.day); const hour = parseInt(dateParts.hour); const minute = parseInt(dateParts.minute); const second = parseInt(dateParts.second); // 指定されたタイムゾーンの時刻をUTCとして解釈 return new Date(Date.UTC(year, month, day, hour, minute, second)); } // 使用例 const tokyoTime = new Date(); const utcTime = localToUTC(tokyoTime, 'Asia/Tokyo'); console.log(`東京時刻: ${tokyoTime.toLocaleString('ja-JP', {timeZone: 'Asia/Tokyo'})}`); console.log(`UTC時刻: ${utcTime.toISOString()}`);

より簡潔な方法として、TemporalAPI(実験的)を使用することもできます:

Javascript
// Temporal APIを使用した例(2024年時点で実験的機能) function convertToUTC(temporalDateTime, timeZone) { // 特定のタイムゾーンの日時を作成 const zonedDateTime = temporalDateTime.toZonedDateTime(timeZone); // UTCに変換 return zonedDateTime.toInstant().toZonedDateTimeISO('UTC'); } // 使用例(ブラウザの実装により動作しない場合があります) if (typeof Temporal !== 'undefined') { const localDateTime = Temporal.PlainDateTime.from('2024-07-15T15:30:00'); const utcDateTime = convertToUTC(localDateTime, 'America/New_York'); console.log(utcDateTime.toString()); }

ハマった点やエラー解決

問題1: JavaScriptのDateオブジェクトのタイムゾーン処理

JavaScriptのDateオブジェクトは、内部的に常にUTCで時刻を保持していますが、メソッドの多くはローカルタイムゾーンで動作します。これにより、次のような混乱が生じます:

Javascript
// 誤ったアプローチ const date = new Date('2024-07-15T15:30:00'); // これはローカル時刻として解釈される console.log(date.toISOString()); // 期待と異なる結果になることがある

解決策

常に明示的にタイムゾーンを指定し、Date.UTC()を使用してUTC時刻を作成します:

Javascript
// 正しいアプローチ function createUTCDate(year, month, day, hour, minute, second, timeZone) { // タイムゾーンを考慮した日時を作成 const dateStr = `${year}-${String(month).padStart(2, '0')}-${String(day).padStart(2, '0')}T${String(hour).padStart(2, '0')}:${String(minute).padStart(2, '0')}:${String(second).padStart(2, '0')}`; // 指定されたタイムゾーンで日時を解釈 const dateInTimeZone = new Date(dateStr + ' ' + timeZone); // UTCに変換 return new Date(dateInTimeZone.toLocaleString('en-US', {timeZone: 'UTC'})); }

問題2: サマータイムの不存在する時刻

前述した通り、サマータイム導入日の午前2時は存在しません。これを無視して日時を作成すると、予期しない結果になります:

Javascript
// 2024年3月10日の午前2:30(存在しない時刻) const nonExistentTime = new Date('2024-03-10T02:30:00'); console.log(nonExistentTime.toString()); // 自動的に3:30として解釈される

解決策

日付の妥当性を検証する関数を作成します:

Javascript
function isValidTime(year, month, day, hour, minute, timeZone) { const formatter = new Intl.DateTimeFormat('en-US', { timeZone: timeZone, year: 'numeric', month: '2-digit', day: '2-digit', hour: '2-digit', minute: '2-digit', hour12: false }); // 指定された時刻を作成 const testDate = new Date(Date.UTC(year, month - 1, day, hour, minute)); // 実際に解釈された時刻を取得 const parts = formatter.formatToParts(testDate); const actualHour = parseInt(parts.find(p => p.type === 'hour').value); const actualMinute = parseInt(parts.find(p => p.type === 'minute').value); // 要求された時刻と実際の時刻を比較 return hour === actualHour && minute === actualMinute; } // 使用例 console.log(isValidTime(2024, 3, 10, 2, 30, 'America/New_York')); // false console.log(isValidTime(2024, 3, 10, 3, 30, 'America/New_York')); // true

問題3: タイムゾーンデータの更新

タイムゾーンのルールは変更されることがあります。例えば、サマータイムの実施有無や、実施日が変更されることがあります。

解決策

Goでは、システムのタイムゾーンデータベースを定期的に更新します:

Bash
# Ubuntu/Debian sudo apt-get update && sudo apt-get install tzdata # macOS sudo softwareupdate -i -a

JavaScriptでは、ブラウザやNode.jsのバージョンを最新に保ちます。特に、国際化API(Intl)の実装は、ECMAScriptの仕様に従って定期的に更新されます。

まとめ

本記事では、GoとJavaScriptでローカルタイムをUTCに変換する際のサマータイム対応について解説しました。

  • Goのtimeパッケージは、タイムゾーンデータベースを自動的に読み込み、サマータイムを考慮した正確な変換が可能
  • JavaScriptのIntl.DateTimeFormatを使用することで、ブラウザ側でもタイムゾーンとサマータイムを正しく処理
  • 存在しない時刻(サマータイム導入日の2時)への対応が重要で、適切な検証が必要
  • タイムゾーンデータの更新を定期的に行い、最新のルールに対応する必要がある

この記事を通して、サマータイムを含む世界中のタイムゾーンに対応した、信頼性の高い日時処理の実装方法を習得できました。次のステップとして、タイムゾーンの違いによる日付の境界線の扱いや、レガシーブラウザでのフォールバック処理についても学習することをお勧めします。

参考資料