見出し画像

Notes C API探訪: TIME型の代わりになるをクラスを考える

以前の記事で紹介したTIME型は、TIMEDATE型と他のデータ型との変換のために、年や時間などの日時に関して、分解されたデータを格納しておく、クッション的なデータ型です。
ここで、定義を再掲しておきます。

#include <misc.h>
typedef struct {
	int year;					/* 1-32767 */
	int month;					/* 1-12 */
	int day;					/* 1-31 */
	int weekday;				/* 1-7, Sunday is 1 */
	int hour;					/* 0-23 */
	int minute;					/* 0-59 */
	int second;					/* 0-59 */
	int hundredth;				/* 0-99 */
	int dst;					/* FALSE or TRUE */
	int zone;					/* Typically -12 to +12, but can include minutes. If this is the  */
								/* case, the two digit minutes are first, followed by the hour.   */
								/* E.g., GMT-04:30 would be 3004, while GMT+05:30 would be -3005. */
	TIMEDATE GM;
} TIME;

このデータ型は、おおむね問題ないんですが、一般的な仕様としては、2点ほど問題があると考えています。
1つ目は、1秒以下の時間単位です。ここでは hundredth がそれにあたります。TIMEDATE型の最小時間単位がおそらくそうなのでしょうから仕方がないのですが、単位が10msが1というえらく中途半端な設定なのです。これは、せめて1msまでは下げたいところです。
2つ目はタイムゾーンです。この zone という値は、一般的な協定世界時(UTC)表記とプラスマイナスが逆です。例えば、日本時間をUTCオフセットで表すと「UTC+9:00」となります。しかし、このzone値は「-9」と表します。さらに、60分以下の時差の地域では、4桁の数値で表す変則的な構造を持っています(この点についても前回の記事で紹介しています)。

これらの問題を考えた時に、TIME型をそのままラップしても、少なくとも10ms以下のデータは欠落してしまいます。タイムゾーンも扱いにくい。ということで、以下のようなクラスを考えました。

// nxpp/include/nxpp/nxpp_timedate.hpp
#ifndef NXPP_TIMEDATE_HPP
#define NXPP_TIMEDATE_HPP

// ...

namespace nxpp {

class TimeDate;

/**
* @brief 時間クラス(TIME型の代替)
*/
class Time
{
 int year_; ///< 年
 int month_; ///< 月
 int day_; ///< 日
 int hour_; ///< 時
 int minute_; ///< 分
 int second_; ///< 秒
 int millisecond_; ///< ミリ秒
 bool isDaylightTime_; ///< 夏時間の有無
 int offsetSecond_; ///< UTCオフセット秒

public:
 /**
  * @brief デフォルトコンストラクタ
  */
 Time() : year_(0), month_(0), day_(0)
 , hour_(0), minute_(0), second_(0), millisecond_(0)
 , isDaylightTime_(false), offsetSecond_(0)
 {}

 /**
  * @brief コンストラクタ
  * @param year 年
  * @param month 月
  * @param day 日
  * @param hour 時
  * @param minute 分
  * @param second 秒
  * @param millisecond ミリ秒
  * @param isDaylightTime 夏時間の有無
  * @param offsetSecond UTCオフセット秒
  */
 Time(int year, int month, int day,
      int hour, int minute, int second, int millisecond,
      bool isDaylightTime, int offsetSecond
      ) : year_(year), month_(month), day_(day)
 , hour_(hour), minute_(minute), second_(second), millisecond_(millisecond)
 , isDaylightTime_(isDaylightTime), offsetSecond_(offsetSecond) {}

 /**
  * @return 年
  */
 int year() const { return year_; }
 
 /**
  * @return 月
  */
 int month() const { return month_; }
 
 /**
  * @return 日
  */
 int day() const { return day_; }
 
 /**
  * @return 時
  */
 int hour() const { return hour_; }
 
 /**
  * @return 分
  */
 int minute() const { return minute_; }
 
 /**
  * @return 秒
  */
 int second() const { return second_; }
 
 /**
  * @return ミリ秒
  */
 int millisecond() const { return millisecond_; }
 
 /**
  * @return 夏時間の有無
  */
 bool isDaylightTime() const { return isDaylightTime_; }
 
 /**
  * @return UTCオフセット秒
  */
 int offsetSeconds() const { return offsetSecond_; }

// ...(続く)

TIME型からの改善点としては、最小単位をミリ秒にしたこと、タイムゾーンをUTCオフセットと同じプラス/マイナスの表記とし、かつ秒単位にしたことです。なお、このタイムゾーンの考え方は、QtのQTimeZoneを参考にしました。

次に、このUTCオフセットとTIME::zone値を相互に交換できる静的メソッドを定義します。

// ...(続き)

 /**
  * @brief UTCオフセット秒からTIME::zone値に変換する
  * @param secs UTCオフセット秒
  * @param isDaylightTime 夏時間の有無
  * @return TIME::zone値
  */
 static int offsetSecondsToZone(int secs, bool isDaylightTime = false) {
   int mins = secs / 60;
   int minsEx = mins % 60;
   int dst = isDaylightTime ? TRUE : FALSE;
   return (mins / -60 + dst) + (minsEx == 0 ? 0 : minsEx * -100);
 }

 /**
  * @brief TIME::zone値からUTCオフセット秒に変換する
  * @param zone TIME::zone値
  * @param isDaylightTime 夏時間の有無
  * @return UTCオフセット秒
  */
 static int zoneToOffsetSeconds(int zone, bool isDaylightTime = false) {
   int hours = zone;
   int mins = 0;
   int dst = isDaylightTime ? TRUE : FALSE;
   if (hours < 100 && hours > -100) {
     mins = (hours - dst) * -60;
   }
   else {
     mins = hours / 100;
     hours = hours % 100;
     mins = (hours - dst) * -60 - mins;
   }
   return mins * 60;
 }

// ...(続く)

次に、TIME型とTIMEDATE型とを相互にやり取りするNotes C API関数をラップします。

// ...(続き)

 /**
  * @brief TIMEDATE型からTIME型に変換する
  * @param td TIMEDATE型のデータ
  * @return TIME型のデータ
  */
 static TIME fromTIMEDATE(const TIMEDATE &td) {
   TIME time;
   time.GM = td;
   TimeGMToLocal(&time);
   return time;
 }

 /**
  * @brief TIME型からTIMEDATE型に変換する
  * @param time TIME型のデータ
  * @return TIMEDATE型のデータ
  */
 static TIMEDATE toTIMEDATE(const TIME &time); // TimeDateクラスの定義が事前に必要

// ...
}; // class Time

// ...(class TimeDateの定義)

/**
* @brief TIME型からTIMEDATE型に変換する
* @param time TIME型のデータ
* @return TIMEDATE型のデータ
*/
inline TIMEDATE Time::toTIMEDATE(const TIME &time) {
 TIME t = time;
 if (TimeLocalToGM(&t) != FALSE) { // Success=FALSE
   t.GM = TimeDate::getConstant<TIMEDATE_MINIMUM>();
 }
 return t.GM;
}

// ...(続く)

これらを利用して、最終的にラップしたクラス、TimeとTimeDateとの相互交換を定義します。

// ...(class Time定義内の続き)

 /**
  * @brief TimeDateオブジェクトに変換する
  * @return TimeDateオブジェクト
  */
 TimeDate toTimeDate() const;

 /**
  * @brief TimeDateオブジェクトから作成する
  * @param td TimeDateオブジェクト
  * @return Timeオブジェクト
  */
 static Time fromTimeDate(const TimeDate &td);
};

// ...(class TimeDateの定義)

/**
* @brief TimeDateオブジェクトに変換する
* @return TimeDateオブジェクト
*/
inline TimeDate Time::toTimeDate() const {
 TIME time;
 time.year = year_;
 time.month = month_;
 time.day = day_;
 time.hour = hour_;
 time.minute = minute_;
 time.second = second_;
 time.hundredth = millisecond_ / 10;
 time.dst = isDaylightTime_ ? TRUE : FALSE;
 time.zone = offsetSecondsToZone(offsetSecond_, isDaylightTime_);
 return toTIMEDATE(time);
}

/**
* @brief TimeDateオブジェクトから作成する
* @param td TimeDateオブジェクト
* @return Timeオブジェクト
*/
inline Time Time::fromTimeDate(const TimeDate &td) {
 TIME time = fromTIMEDATE(td);
 return Time(time.year, time.month, time.day,
             time.hour, time.minute, time.second, time.hundredth * 10,
             time.dst == TRUE, zoneToOffsetSeconds(time.zone)
             );
}

} // namespace nxpp

#endif // NXPP_TIMEDATE_HPP

これにてTimeクラスの定義は完了です。

まとめ

日本で暮らし、日本で仕事をし、日本時間でアプリケーションを開発していると、グローバルなアプリケーションを開発する時に「現地時間で表記?夏時間って何?」となってしまいます。こういう事態に陥らないよう、常に世界時間は意識していたいものです。

ちなみに、最近はMicrosoft Graph APIを用いたアプリケーションを開発したことがあり、あのようなグローバルなAPIを使う場合、否が応でも世界時間を意識せざるを得ません。デジタル社会で日本はながらく鎖国状態でしたから、いい傾向なのではないでしょうか。

次回は、ここでスルーしたTimeDateクラス(TIMEDATE型のラップクラス)についてご紹介します。

この記事が気に入ったらサポートをしてみませんか?