はじめに
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 ベースのタスク管理テンプレートを、来月の海外のアンバサダーのアドベントカレンダーで紹介します。それよりも早く取得したい人は私のマーケットプレースから取得してみてください。