日付文字列から日付を得る関数: Notion Tips (159)

はじめに

Notion Tips の第159回目はこれまで説明してきた正規表現などを使って、日付文字列から日付を得る関数を解説します。基本的には日付メンションで生成される規則的な文字列を日付に変換します。ただし、現状 Notion がサポートしている全ての言語で動作するものを目指しました。

作成した数式

昨日、この関数の前半部分までを解説しました。今回、後半部分も含めた関数全体を公開します。

lets(
    name, prop("updateDate").replaceAll(" de ", " "),
    /* months */
    months, ".".repeat(12).split("").map(((202401+index)*100+1).parseDate().formatDate("MMMM")),
    /* timezone */
    timezone, "00" + today().formatDate("Z"),
    /* normalize name */
    normalizeName, 
        ifs(
            name.contains("년"),
                /* Korean */
                name
                    .replaceAll("오전 (\d+:\d+)", "$1 AM")
                    .replaceAll("오후 (\d+:\d+)", "$1 PM")
                    + " A",
            name.test(".*[上下]午.*"),
                /* 繁体中文 */
                name
                    .replaceAll("([年月])", "$1 ")
                    .replaceAll("上午(\d+:\d+)", "$1 AM")
                    .replaceAll("下午(\d+:\d+)", "$1 PM")
                    + " A",
            name.test("^\d\d\d\d"),
                /* 日本語、簡体中文 */
                name
                    .replaceAll("([年月])", "$1 "),
            name.test("^[A-Z]"),
                /* English */
                name
                    .replaceAll("([A-Z]\w+) (\d+), (\d\d\d\d)", "$3 $1 $2")
                    + (name.test(".*(AM|PM).*") ? " A" : ""),
            name
                .replaceAll("(\d+)\.? ([^ ]+) (\d\d\d\d)", "$3 $2 $1")
        )
        .replaceAll(months.at(0), "1")
        .replaceAll(months.at(1), "2")
        .replaceAll(months.at(2), "3")
        .replaceAll(months.at(3), "4")
        .replaceAll(months.at(4), "5")
        .replaceAll(months.at(5), "6")
        .replaceAll(months.at(6), "7")
        .replaceAll(months.at(7), "8")
        .replaceAll(months.at(8), "9")
        .replaceAll(months.at(9), "10")
        .replaceAll(months.at(10), "11")
        .replaceAll(months.at(11), "12"),
    array, normalizeName.split(" "),
    arraySize, array.length(),
    first, array.first(),
    second, array.at(1),
    third, array.at(2),
    last, array.last(),
    arrowIndex, array.findIndex(current == "→"),
    noArrow, arrowIndex == -1,
    hasAmPm, last == "A",
    lastHasTime, array.at(-hasAmPm.toNumber()*2 -1).test("^\d+[:\.]\d+.*"),
    ymd1, array.slice(0, 3).map(current.toNumber() * 100.pow(2-index)).sum(),
    ans,
        ifs(
            /* noArrow, hasTime, hasAmPm */
            noArrow && lastHasTime && hasAmPm,
                lets(
                    hm, array.at(3).match("\d+").map(current.toNumber()),
                    ap, array.at(4).match("^PM") ? 12 : 0,
                    offset, (hm.last() == 0).toNumber(),
                    ymdhm1, ymd1 * 10000 + ((hm.first() % 12) + ap) * 100 + hm.last() + offset,
                    (ymdhm1.substring(0,8) + "T" + ymdhm1.substring(8) + timezone)
                        .parseDate()
                        .dateSubtract(offset, "minutes")
                ),
            /* noArrow, hasTime, !hasAmPm */
            noArrow && lastHasTime,
                lets(
                    hm, array.at(3).match("\d+").map(current.toNumber() * 100.pow(1-index)).sum(),
                    offset, (hm % 100 == 0).toNumber(),
                    ymdhm1, ymd1 * 10000 + hm + offset,
                    (ymdhm1.substring(0,8) + "T" + ymdhm1.substring(8) + timezone)
                        .parseDate()
                        .dateSubtract(offset, "minutes")
                )
            ,
            /* noArrow */
            noArrow,
                ymd1.parseDate(),
            /* !noArrow, hasTime, hasAmPm : time only */
            lastHasTime && hasAmPm && arraySize - arrowIndex == 4,
                lets(
                    hm1, array.at(3).match("\d+").map(current.toNumber()),
                    hm2, array.at(6).match("\d+").map(current.toNumber()),
                    ap1, array.at(4).match("^PM") ? 12 : 0,
                    ap2, array.at(7).match("^PM") ? 12 : 0,
                    offset1, (hm1.last() == 0).toNumber(),
                    offset2, (hm2.last() == 0).toNumber(),
                    ymdhm1, ymd1 * 10000 + ((hm1.first() % 12) + ap1) * 100 + hm1.last() + offset1,
                    ymdhm2, ymd1 * 10000 + ((hm2.first() % 12) + ap2) * 100 + hm2.last() + offset2,
                    str1, ymdhm1.substring(0, 8) + "T" + ymdhm1.substring(8) + timezone,
                    str2, ymdhm2.substring(0, 8) + "T" + ymdhm2.substring(8) + timezone,
                    str1.parseDate().dateSubtract(offset1, "minutes")
                        .dateRange(str2.parseDate().dateSubtract(offset2, "minutes"))
                ),
            /* !noArrow, hasTime, hasAmPm, date and time */
            lastHasTime && hasAmPm && arraySize - arrowIndex == 7,
                lets(
                    ymd2, array.slice(6, 9).map(current.toNumber() * 100.pow(2-index)).sum(),
                    hm1, array.at(3).match("\d+").map(current.toNumber()),
                    hm2, array.at(9).match("\d+").map(current.toNumber()),
                    ap1, array.at(4).match("^PM") ? 12 : 0,
                    ap2, array.at(10).match("^PM") ? 12 : 0,
                    offset1, (hm1.last() == 0).toNumber(),
                    offset2, (hm2.last() == 0).toNumber(),
                    ymdhm1, ymd1 * 10000 + ((hm1.first() % 12) + ap1) * 100 + hm1.last() + offset1,
                    ymdhm2, ymd2 * 10000 + ((hm2.first() % 12) + ap2) * 100 + hm2.last() + offset2,
                    str1, ymdhm1.substring(0, 8) + "T" + ymdhm1.substring(8) + timezone,
                    str2, ymdhm2.substring(0, 8) + "T" + ymdhm2.substring(8) + timezone,
                    str1.parseDate().dateSubtract(offset1, "minutes")
                        .dateRange(str2.parseDate().dateSubtract(offset2, "minutes"))
                ),
            /* !noArrow, hasTime, !hasAmPm : time only */
            lastHasTime && arraySize - arrowIndex == 2,
                lets(
                    hm1, array.at(3).match("\d+").map(current.toNumber() * 100.pow(1-index)).sum(),
                    hm2, array.at(5).match("\d+").map(current.toNumber() * 100.pow(1-index)).sum(),
                    offset1, (hm1 % 100 == 0).toNumber(),
                    offset2, (hm2 % 100 == 0).toNumber(),
                    ymdhm1, ymd1 * 10000 + hm1 + offset1,
                    ymdhm2, ymd1 * 10000 + hm2 + offset2,
                    str1, ymdhm1.substring(0, 8) + "T" + ymdhm1.substring(8) + timezone,
                    str2, ymdhm2.substring(0, 8) + "T" + ymdhm2.substring(8) + timezone,
                    str1.parseDate().dateSubtract(offset1, "minutes")
                        .dateRange(str2.parseDate().dateSubtract(offset2, "minutes"))
                ),
            /* !noArrow, hasTime, hasAmPm, date and time */
            lastHasTime && arraySize - arrowIndex == 5,
                lets(
                    ymd2, array.slice(5, 8).map(current.toNumber() * 100.pow(2-index)).sum(),
                    hm1, array.at(3).match("\d+").map(current.toNumber() * 100.pow(1-index)).sum(),
                    hm2, array.at(8).match("\d+").map(current.toNumber() * 100.pow(1-index)).sum(),
                    offset1, (hm1 % 100 == 0).toNumber(),
                    offset2, (hm2 % 100 == 0).toNumber(),
                    ymdhm1, ymd1 * 10000 + hm1 + offset1,
                    ymdhm2, ymd1 * 10000 + hm2 + offset2,
                    str1, ymdhm1.substring(0, 8) + "T" + ymdhm1.substring(8) + timezone,
                    str2, ymdhm2.substring(0, 8) + "T" + ymdhm2.substring(8) + timezone,
                    str1.parseDate().dateSubtract(offset1, "minutes")
                        .dateRange(str2.parseDate().dateSubtract(offset2, "minutes"))
                ),
            /* !noArrow, !hasTime */
                lets(
                    ymd2, array.slice(4,7).map(current.toNumber() * 100.pow(2-index)).sum(),
                    ymd1.parseDate().dateRange(ymd2.parseDate())
            )
        ),
    ans
)

全言語での動作確認

全ての言語の文字列を用意してテストしてみました。現在、Swedish にしたときの結果を示します。 Danish, Dutch, Swedish は 12 月を december と表記するので、それらは正しく日付に変換されています。また、Korean, Japanese, Chinese については元々月の中に数値が含まれているので、これも正しく変換されています。言語を切り替えた時に、それぞれの言語のものが正しく変換されたことを確認しました。

全言語での動作確認

日付パターンの動作確認

さらに、さまざまなパターンの日付メンションに対して正しく動作することを確認しています。日付メンションは描画は相対日付ですが、文字列としては日本語の場合 24 時間制の日付文字列となるため、このように日付を抽出できます。

日付パターンの動作確認

おわりに

これまで作成してきた中で一番有用で一番長い数式になりました。これを含めた 5 つのオートメーションを搭載した GTD ベースのタスク管理テンプレートを、来月の海外のアンバサダーのアドベントカレンダーで紹介します。それよりも早く取得したい人は私のマーケットプレースから取得してみてください。

hkob.notion.site