Python

『Google Calendarの繰り返し』の勤怠をPythonに取り込む(クラスを改修)

こんにちは Tomoです。

今回は『Google Calendarの繰り返し』の勤怠をPythonに取り込みます。

ただし、今回は前回作成したクラスに対しての説明となります。

事前準備

前回の続きとなります

以下の内容を確認してください。


Google Calendarの予定を繰り返す

まずは、Google Calendarに予約を登録します。

予約登録の詳細画面で設定できます。

上部にある『繰り返さない』のリストをクリックします。

今回はは、タイトルを『勤怠』として『2020年01月06日』『9時30分』から、『18時30分』の時間を指定します。繰り返しは『毎週平日(月~金)』を選択します。

あとは、保存し、カレンダーデータをエクスポートしてPythonが読み込むディレクトリに配置します。

繰り返しデータの構成

データの配置が終わったらプログラムを回収しますが、その前に繰り返しデータの構成について説明します。

なお、これらの情報についてはエクスポートデータから判断しています。このため、正しい表現でないかもしれませんがご了承下さい。

    
BEGIN:VEVENT
DTSTART;TZID=Asia/Tokyo:20200106T093000
DTEND;TZID=Asia/Tokyo:20200106T183000
RRULE:FREQ=WEEKLY;BYDAY=FR,MO,TH,TU,WE
DTSTAMP:20200102T115043Z
UID:3i3lumpvedrodm1b3m3stqbsel@google.com
CREATED:20200102T115037Z
DESCRIPTION:
LAST-MODIFIED:20200102T115037Z
LOCATION:
SEQUENCE:0
STATUS:CONFIRMED
SUMMARY:勤怠
TRANSP:OPAQUE
END:VEVENT
    

繰り返しの場合の構成でポイントになるのは以下の3点です。

  • DTSTART;TZID=Asia/Tokyo:20200106T093000
  • DTEND;TZID=Asia/Tokyo:20200106T183000
  • RRULE:FREQ=WEEKLY;BYDAY=FR,MO,TH,TU,WE

DTSTARTとDTENDについては、類似しているので同様の説明にします。

DTSTARTの扱い

単体の予定と違うところは、区切り文字が「:(コロン)」から「;(セミコロン)」に代わっていることです。

そして繰り返しデータの場合は、日時情報はDTSTARTとDTENDのデータしかありません。

このため、この日以降の日付がすべて繰り返しの対象となります。

RRULEの扱い

繰り返しの場合は、この項目が重要になっています。

FREQ=WEEKLYが週単位を表しているようです。

BYDAY=FR,MO,TH,TU,WEこちらは、月~金を表しているようです。

毎週平日(月~金)をこのルールで表しています。

上記のデータを考慮してコーディングを行います。

カレンダーのイベントクラスの改修

カレンダーのイベントクラスの改修ですが、以下のメソッド単位で記載します。

ちなみにカレンダークラスには、Googleカレンダの予定情報単位「BEGIN:VEVENT」から「END:VEVENT」の間の項目を格納します。

  • ___init___(コンストラクタ)
  • set_summary
  • set_dtstart
  • set_dtend
  • set_exdate
  • set_rule
  • __dt_to_date

それぞれの内容について記載します。

___init___(コンストラクタ)

コンストラクタの内容です。

    
class CalendaEventClass():

    def ___init___(self):

        self.summary        = none
        self.stat_date_time = none
        self.end_date_time  = none
        self.repeat         = False
        self.freq           = none
        self.byday          = none
        self.until          = none
        self.exdate         = none

    

前回からの違いは、「repeart」以下の変数を追加します。

「repeat」は繰り返しのイベントをBoolean型で判定します。

繰り返しのときは「True」繰り返さないときは「False」にします。

「freq」は繰り返しの単位?です。「WEEKLY」を設定します。ただし、毎月1回などの繰り返しの場合は「Month」が入るようです。

「byday」は「月~金」のFR,MO,TH,TU,WEを設定します。

「until」は今回は使いませんが、繰り返しで「ある日付から予定を削除した」ときにつかう日時です。

「exdate」も今回は使いませんが、勤怠で、有給や、病欠やすむときの日付を格納する場所になります。

set_summary

summaryの内容を設定します。なお、コンストラクタに書く変数はインスタンス変数と呼ぶようです。

    

    def set_summary(self, ical_data):
        """
        ical形式のSUMMARYをString型で設定する.

        例) SUMMARY:勤怠

        Parameters
        ----------
        ical_data : String
             ical形式のデータ

        """
        # 「:」の前後をlistに格納
        summary_list = ical_data.split(':')

        # summaryを格納
        summary = summary_list[1]

        # インスタンス変数に格納
        self.summary = summary

    

「"""」から「"""」の間にかかれているのはコメントになります。コメントについては省略します。

エクスポートした内容をそのまま読み込んで処理をします。

内容については、以前の投稿にかいているので省略します。

こちらに記載しています。

stat_date_time

DTSTARTの内容を設定します。このメソッドについては、コーディングの最適化は行っていません。

最適化を行っていないので結構長くなっています。

    

    def set_dtstart(self, ical_data):
        """
        ical形式の開始時間DTSTARTをdatetime型で設定する.
        また、繰り返しの場合に繰り返し用のフラグを有効にする.

        「;」繰り返しのイベント
        例) DTSTART;TZID=Asia/Tokyo:20200106T093000

        「:」単体イベント
        例) DTSTART:20200105T003000Z

        Parameters
        ----------
        ical_data : String
             ical形式のデータ

        """

        if 'DTSTART:' in ical_data:

            # boolean型の繰り返しなしでインスタンス変数に格納
            self.repeat = False

            # 「:」の前後をlistに格納
            dt_list = ical_data.split(':')

            # 開始時刻を格納
            dt_start = dt_list[1]

            # datetime型に変換しインスタンス変数に格納
            self.stat_date_time = self.__dt_to_date(dt_start)

        else:

            # boolean型の繰り返しありでインスタンス変数に格納
            self.repeat = True

            # 「;」の前後をlistに格納
            dt_list = ical_data.split(';')

            # 「TZID」が含まれているか確認
            if 'TZID' in dt_list[1]:

                # 「:」の前後をlistに格納
                dt_list = ical_data.split(':')

                # 「=」の前後をlistに格納
                dt_tz_list = dt_list[0].split('=')

                # タイムゾーンを格納
                dt_tz = dt_tz_list[0]

                # 開始時刻を格納
                dt_start = dt_list[1]

                # datetime型に変換しインスタンス変数に格納
                self.stat_date_time = self.__dt_to_date(dt_start, dt_tz)

            else:

                # 「:」の前後をlistに格納
                dt_list = ical_data.split(':')

                # 開始時刻を格納
                dt_start = dt_list[1]

                # datetime型に変換しインスタンス変数に格納
                self.stat_date_time = self.__dt_to_date(dt_start)

    

基本的にコードにコメントを書いているので省略しますが、以下3点だけ記載します。

1点目は、「:」と「;」の違いで繰り返しかどうか判断しています。ここで「self.repeat」に「True/False」を設定しています。

2点目は、「 self.__dt_to_date」とプライベートメソッドを使っています。こちらについては以降に記載しています。

3点目は、TZIDでタイムゾーンの抽出を行っていますが、今回は省略します。とりあえずで記載しています。

set_dtend

DTSTARTの内容とほぼ同じなので説明は省略します。

    

    def set_dtend(self, ical_data):
        """
        ical形式のルールを取得し値を設定する.
        また、繰り返しの場合に繰り返し用のフラグを有効にする.

        「;」繰り返しのイベント
        例) DTEND;TZID=Asia/Tokyo:20200106T183000

        「:」単体イベント
        例) DTEND:20200105T093000Z

        Parameters
        ----------
        ical_data : String
             ical形式のデータ

        """

        if 'DTEND:' in ical_data:

            # 「:」の前後をlistに格納
            dt_list = ical_data.split(':')

            # 終了時刻を格納
            dt_end = dt_list[1]

            # datetime型に変換しインスタンス変数に格納
            self.end_date_time = self.__dt_to_date(dt_end)

        else:

            # 「;」の前後をlistに格納
            dt_list = ical_data.split(';')

            # 「TZID」が含まれているか確認
            if 'TZID' in dt_list[1]:

                # 「:」の前後をlistに格納
                dt_list = ical_data.split(':')

                # 「=」の前後をlistに格納
                dt_tz_list = dt_list[0].split('=')

                # タイムゾーンを格納
                dt_tz = dt_tz_list[0]

                # 開始時刻を格納
                dt_end = dt_list[1]

                # datetime型に変換しインスタンス変数に格納
                self.end_date_time = self.__dt_to_date(dt_end, dt_tz)

            else:

                # 「:」の前後をlistに格納
                dt_list = ical_data.split(':')

                # 開始時刻を格納
                dt_end = dt_list[1]

                # datetime型に変換しインスタンス変数に格納
                self.end_date_time = self.__dt_to_date(dt_end)

    

set_exdate

こちらも記載していますが、省略します。

    

    def set_exdate(self, ical_data):
        """
        ical形式のEXDATAを取得し値を設定する.

        例) EXDATE;TZID=Asia/Tokyo:20200113T093000

        Parameters
        ----------
        ical_data : String
             ical形式のデータ

        """
        # 「;」の前後をlistに格納
        dt_list = ical_data.split(';')

        # 「TZID」が含まれているか確認
        if 'TZID' in dt_list[1]:

            # 「:」の前後をlistに格納
            dt_list = ical_data.split(':')

            # 「=」の前後をlistに格納
            dt_tz_list = dt_list[0].split('=')

            # タイムゾーンを格納
            dt_tz = dt_tz_list[0]

            # EXDATEを格納
            dt_exdate = dt_list[1]

            #クラスに変数が定義されているか
            if hasattr(self, 'exdate'):
                # datetime型に変換しインスタンス変数に格納
                self.exdate.append(self.__dt_to_date(dt_exdate, dt_tz))
            else:
                # datetime型に変換しインスタンス変数に格納(最初にlist初期化して格納)
                self.exdate = [self.__dt_to_date(dt_exdate, dt_tz)]
        else:
            # 「:」の前後をlistに格納
            dt_list = ical_data.split(':')

            # EXDATEを格納
            dt_exdate = dt_list[1]

            #クラスに変数が定義されているか
            if hasattr(self, 'exdate'):
                # datetime型に変換しインスタンス変数に格納
                self.exdate.append(self.__dt_to_date(dt_exdate))
            else:
                # datetime型に変換しインスタンス変数に格納(list初期化)
                self.exdate = [self.__dt_to_date(dt_exdate)]

    

set_rule

今回のポイントになります。

    

    def set_rule(self, ical_data):
        """
        ical形式の終了時間DTENDをdatetime型で設定する.
        また、FREEQ(頻度)  BYDAY UNTIL(期限)を分解して格納する

        繰り返しのイベント(平日)
        例) RRULE:FREQ=WEEKLY;BYDAY=FR,MO,TH,TU,WE

        繰り返しのイベント(平日 ある日以降削除)
        例) RRULE:FREQ=WEEKLY;UNTIL=20200129T145959Z;BYDAY=FR,MO,TH,TU,WE

        繰り返しのイベント(毎日)
        例) RRULE:FREQ=DAILY

        それ以外については別途必要に応じて検討

        Parameters
        ----------
        ical_data : String
       """

        # 「:」の前後をlistに格納
        rule_list = ical_data.split(':')

        # 「;」の前後をlistに格納
        element_list = rule_list[1].split(';')

        # 要素数分処理を繰り返す
        for element in element_list:

            # FREQの場合
            if  'FREQ' in element:
                print(element)
                # freq_listを取得「=」の前後をlistに格納
                freq_list = element.split('=')

                # freqを取得「=」の右側を格納
                freq = freq_list[1]

                # インスタンス変数に格納
                self.freq = freq

            # UNTILの場合
            if 'UNTIL' in element:

                # until_listを取得「=」の前後をlistに格納
                until_list = element.split('=')

                # unitlを取取得「=」の右側を格納
                until = until_list[1]

                # datetime型に変換しインスタンス変数に格納
                self.until = self.__dt_to_date(until)

            # BYDAYの場合
            if 'BYDAY' in element:

                # byday_listを取得「=」の前後をlistに格納
                byday_list = element.split('=')

                # unitlを取取得「=」の右側を格納
                until = byday_list[1]

                # list型の状態でインスタンス変数に格納
                self.byday = until.split(',')

    

こちらもコメントをかいているので基本省略しますが、ポイントだけ書きます。

1点目は、「UNTIL」をdatetime型でインスタンス変数に格納しています。

2点目は、「BYDAY」をlist型でインスタンス変数に格納しています。

__dt_to_date

最後は、プライベートメソッドです。プライベートメソッドは、このクラスの中で使うメソッドです。

プライベートメソッドは、「__」をメソッドの前に書くようです。

※Pythonについては、勉強中で実際に外部かあアクセスできるか試していないです。

    

    def __dt_to_date(self, dt, tz=None):
        """
        ISO 8601形式の時間をdatetime型で設定する.

        Parameters
        ----------
        dt : str
             ISO 8601形式のデータ

        tz : str
             TimeZone形式(未使用)
             例) Asia/Tokyo
        """
        # タイムゾーンを設定
        jst = datetime.timezone(datetime.timedelta(hours=+9), 'JST')

        # タイムゾーンで変換した時刻を返却
        return dateutil.parser.parse(dt).astimezone(jst)

    

このメソッドは、ISO 8601形式の時間をdatetime型で設定しています。

開始日時、や終了日時等をインスタンス変数に格納する際にdatetime型として格納するときに使います。

なお、タイムゾーンは使用していませんが、引数として設定しています。

まとめ

今回は繰り返しの予定と、カレンダーのイベントのクラスにメソッドを追加しました。

コーディングについては、自分でも最適化はできてない気がしますがとりあえず動けばいい感じでコーディングしています。

次回は呼び出し側のコーティングを記載する予定です。

最後にまとめたソースを記載します。

    

class CalendaEventClass():

    def ___init___(self):

        self.summary        = none
        self.stat_date_time = none
        self.end_date_time  = none
        self.repeat         = False
        self.freq           = none
        self.byday          = none
        self.until          = none
        self.exdate         = none

    def set_summary(self, ical_data):
        """
        ical形式のSUMMARYをString型で設定する.

        例) SUMMARY:勤怠

        Parameters
        ----------
        ical_data : String
             ical形式のデータ

        """
        # 「:」の前後をlistに格納
        summary_list = ical_data.split(':')

        # summaryを格納
        summary = summary_list[1]

        # インスタンス変数に格納
        self.summary = summary


    def set_dtstart(self, ical_data):
        """
        ical形式の開始時間DTSTARTをdatetime型で設定する.
        また、繰り返しの場合に繰り返し用のフラグを有効にする.

        「;」繰り返しのイベント
        例) DTSTART;TZID=Asia/Tokyo:20200106T093000

        「:」単体イベント
        例) DTSTART:20200105T003000Z

        Parameters
        ----------
        ical_data : String
             ical形式のデータ

        """

        if 'DTSTART:' in ical_data:

            # boolean型の繰り返しなしでインスタンス変数に格納
            self.repeat = False

            # 「:」の前後をlistに格納
            dt_list = ical_data.split(':')

            # 開始時刻を格納
            dt_start = dt_list[1]

            # datetime型に変換しインスタンス変数に格納
            self.stat_date_time = self.__dt_to_date(dt_start)

        else:

            # boolean型の繰り返しありでインスタンス変数に格納
            self.repeat = True

            # 「;」の前後をlistに格納
            dt_list = ical_data.split(';')

            # 「TZID」が含まれているか確認
            if 'TZID' in dt_list[1]:

                # 「:」の前後をlistに格納
                dt_list = ical_data.split(':')

                # 「=」の前後をlistに格納
                dt_tz_list = dt_list[0].split('=')

                # タイムゾーンを格納
                dt_tz = dt_tz_list[0]

                # 開始時刻を格納
                dt_start = dt_list[1]

                # datetime型に変換しインスタンス変数に格納
                self.stat_date_time = self.__dt_to_date(dt_start, dt_tz)

            else:

                # 「:」の前後をlistに格納
                dt_list = ical_data.split(':')


                # 開始時刻を格納
                dt_start = dt_list[1]

                # datetime型に変換しインスタンス変数に格納
                self.stat_date_time = self.__dt_to_date(dt_start)


    def set_dtend(self, ical_data):
        """
        ical形式のルールを取得し値を設定する.
        また、繰り返しの場合に繰り返し用のフラグを有効にする.

        「;」繰り返しのイベント
        例) DTEND;TZID=Asia/Tokyo:20200106T183000

        「:」単体イベント
        例) DTEND:20200105T093000Z

        Parameters
        ----------
        ical_data : String
             ical形式のデータ

        """

        if 'DTEND:' in ical_data:

            # 「:」の前後をlistに格納
            dt_list = ical_data.split(':')

            # 終了時刻を格納
            dt_end = dt_list[1]

            # datetime型に変換しインスタンス変数に格納
            self.end_date_time = self.__dt_to_date(dt_end)

        else:

            # 「;」の前後をlistに格納
            dt_list = ical_data.split(';')

            # 「TZID」が含まれているか確認
            if 'TZID' in dt_list[1]:

                # 「:」の前後をlistに格納
                dt_list = ical_data.split(':')

                # 「=」の前後をlistに格納
                dt_tz_list = dt_list[0].split('=')

                # タイムゾーンを格納
                dt_tz = dt_tz_list[0]

                # 開始時刻を格納
                dt_end = dt_list[1]

                # datetime型に変換しインスタンス変数に格納
                self.end_date_time = self.__dt_to_date(dt_end, dt_tz)

            else:

                # 「:」の前後をlistに格納
                dt_list = ical_data.split(':')

                # 開始時刻を格納
                dt_end = dt_list[1]

                # datetime型に変換しインスタンス変数に格納
                self.end_date_time = self.__dt_to_date(dt_end)

    def set_exdate(self, ical_data):
        """
        ical形式のEXDATAを取得し値を設定する.

        例) EXDATE;TZID=Asia/Tokyo:20200113T093000

        Parameters
        ----------
        ical_data : String
             ical形式のデータ

        """
        # 「;」の前後をlistに格納
        dt_list = ical_data.split(';')

        # 「TZID」が含まれているか確認
        if 'TZID' in dt_list[1]:

            # 「:」の前後をlistに格納
            dt_list = ical_data.split(':')

            # 「=」の前後をlistに格納
            dt_tz_list = dt_list[0].split('=')

            # タイムゾーンを格納
            dt_tz = dt_tz_list[0]

            # EXDATEを格納
            dt_exdate = dt_list[1]

            #クラスに変数が定義されているか
            if hasattr(self, 'exdate'):
                # datetime型に変換しインスタンス変数に格納
                self.exdate.append(self.__dt_to_date(dt_exdate, dt_tz))
            else:
                # datetime型に変換しインスタンス変数に格納(最初にlist初期化して格納)
                self.exdate = [self.__dt_to_date(dt_exdate, dt_tz)]
        else:
            # 「:」の前後をlistに格納
            dt_list = ical_data.split(':')

            # EXDATEを格納
            dt_exdate = dt_list[1]

            #クラスに変数が定義されているか
            if hasattr(self, 'exdate'):
                # datetime型に変換しインスタンス変数に格納
                self.exdate.append(self.__dt_to_date(dt_exdate))
            else:
                # datetime型に変換しインスタンス変数に格納(list初期化)
                self.exdate = [self.__dt_to_date(dt_exdate)]

    def set_rule(self, ical_data):
        """
        ical形式の終了時間DTENDをdatetime型で設定する.
        また、FREEQ(頻度)  BYDAY UNTIL(期限)を分解して格納する

        繰り返しのイベント(平日)
        例) RRULE:FREQ=WEEKLY;BYDAY=FR,MO,TH,TU,WE

        繰り返しのイベント(平日 ある日以降削除)
        例) RRULE:FREQ=WEEKLY;UNTIL=20200129T145959Z;BYDAY=FR,MO,TH,TU,WE

        繰り返しのイベント(毎日)
        例) RRULE:FREQ=DAILY

        それ以外については別途必要に応じて検討

        Parameters
        ----------
        ical_data : String
       """

        # 「:」の前後をlistに格納
        rule_list = ical_data.split(':')

        # 「;」の前後をlistに格納
        element_list = rule_list[1].split(';')

        # 要素数分処理を繰り返す
        for element in element_list:

            # FREQの場合
            if  'FREQ' in element:
                print(element)
                # freq_listを取得「=」の前後をlistに格納
                freq_list = element.split('=')

                # freqを取得「=」の右側を格納
                freq = freq_list[1]

                # インスタンス変数に格納
                self.freq = freq

            # UNTILの場合
            if 'UNTIL' in element:

                # until_listを取得「=」の前後をlistに格納
                until_list = element.split('=')

                # unitlを取取得「=」の右側を格納
                until = until_list[1]

                # datetime型に変換しインスタンス変数に格納
                self.until = self.__dt_to_date(until)

            # BYDAYの場合
            if 'BYDAY' in element:

                # byday_listを取得「=」の前後をlistに格納
                byday_list = element.split('=')

                # unitlを取取得「=」の右側を格納
                until = byday_list[1]

                # list型の状態でインスタンス変数に格納
                self.byday = until.split(',')


    def __dt_to_date(self, dt, tz=None):
        """
        ISO 8601形式の時間をdatetime型で設定する.

        Parameters
        ----------
        dt : str
             ISO 8601形式のデータ

        tz : str
             TimeZone形式(未使用)
             例) Asia/Tokyo
        """
        # タイムゾーンを設定
        jst = datetime.timezone(datetime.timedelta(hours=+9), 'JST')

        # タイムゾーンで変換した時刻を返却
        return dateutil.parser.parse(dt).astimezone(jst)


    

コメント

0 件のコメント:

コメントを投稿

コメントをお待ちしています。