[노션] 수면시간 기록! 2230-0600이라고 입력하면 7.5시간이라는 값을 계산하는 수식만들기

2023.05.15

처음으로 노션을 사용해보기로 했습니다.

아무리 노션이 요즘 핫하다고는 하나, 애당초 저는 계획성도 없고 메모하는 습관도 없기 때문에 저한테는 그저 '돼지 목에 진주 목걸이’일 뿐이었습니다.

유튜브 영상으로 다른 사람들은 어떻게 사용하는지 둘러보고 한 3개 정도 괜찮다고 생각한 템플렛을 사용해봤는데 음…

나에게 맞지 않는 옷을 억지로 입은 느낌이랄까… 뭔가 나랑 잘 안 맞더라고요.

처음부터 복잡하게 사용하면 금방 나가떨어질 거라고 생각했던 저는 일단 간단한 '한 줄 일기’부터 시작하기로 했습니다.

이왕 일기처럼 매일 쓸 거면 내가 하루에 얼마나 자는지, 한 달 동안 하루 평균 몇 시간을 자는지, 더 나아가서는 내 인생 중 1년 동안 수면에 투자하는 시간은 얼마나 될지 등이 궁금해질 것 같아서 수면시간도 기록하기로 했습니다.

그런데….!!!!

수면시간을 기록하기 위해서는 취침시각과 기상시각이 있어야 하는데 이 2개의 시각을 입력하는 작업이 저에게는 굉장히 불편했습니다.

뭔가 더 편한 방법이 없나 하고 검색해봤는데 제 검색능력이 부족한 것일까요? 아무리 검색을 해도 원하는 답이 나오지 않아서 직접 수식을 만들어봤습니다.

일반적으로 알려진 날짜 및 시간을 계산하는 방법

필요한 정보를 얻기 위해 검색을 했더니 databetween을 이용한 방법이 있었습니다. 시작시간과 끝나는 시간을 입력하면 함수가 알아서 계산해주는 방법이었습니다. 다만, 이 방법은 시작시간과 끝나는 시간, 이렇게 2개의 속성이 필요합니다.

이미 데이터베이스에 날짜 속성이 만들어져 있는 상태에서 불필요하게 날짜 속성을 1개 또는 2개를 더 만들어야 한다는 이야기고, 더군다나 날짜뿐 아니라 시간을 입력하기 위해서는 날짜 속성을 클릭해서 캘린더에서 날짜를 선택하고, 거기에 시간포함On으로 변경한 뒤 다시 마우스를 위로 가져가서 시간을 클릭해서 입력해야 하는 작업은 저에게는 너무나도 번거로운 일이었습니다.

[노션] 번거로운 시간입력 작업
[노션] 번거로운 시간입력 작업

두 번 클릭할 일을 한 번으로 줄여도 모자랄 판에, 오히려 클릭수를 두 번이나 더 늘린다니요. 주객이 전도되는 상황입니다.

굳이 속성을 추가하지 않고 1개의 Date 속성에서 시작일과 종료일을 설정해서 하는 방법도 있긴 한데, 이 역시 시간을 입력하는 과정은 마찬가지였습니다.

어느 유튜버가 말하더군요. 노션을 하는 거 자체가 일이 되어서는 안 된다고… 입력은 되도록 심플해야 한다고 말이죠.

텍스트 형식으로 입력해서 시간을 계산하는 방법 (★)

[노션] 시작시간과 종료시간을 입력해서 시간 계산하기
[노션] 시작시간과 종료시간을 입력해서 시간 계산하기

결과물입니다. Sleep 속성에 시간을 입력하면 Duration 속성에 결과값이 자동으로 계산되어 출력됩니다.

제가 만든 수식은 아래와 같습니다. 최적의 코드라고 말씀드릴 순 없지만, 일단 잘 작동하기 때문에 필요하신 분들은 그냥 가져다 쓰시면 됩니다. 변수를 사용하지 못하니 코드가 이렇게나 길어지는군요.

2024.01.01 내용 추가
  • 2023년 9월에 노션 포뮬러가 V2.0으로 업데이트되었습니다.
  • slice 함수가 substring으로 변경되면서 위의 코드는 이제 작동하지 않습니다.
  • 업데이트 전에 입력하신 코드는 노션에서 자동으로 변경해주었기 때문에 이상없이 작동합니다.
  • 업데이트 후에 이 포스팅을 보고 계신 분들은 수정한 아래의 코드를 적용해주시기 바랍니다.
  • 수정사항 내역 : https://www.notion.so/ko-kr/help/guides/new-formulas-whats-changed
노션 포뮬러 2.0 업데이트 일부
[노션] 포뮬러 2.0 업데이트 일부내용
if(equal(length(prop("Sleep")), 9), if(equal(sign((toNumber(slice(prop("Sleep"), 5, 7)) * 60 + toNumber(slice(prop("Sleep"), 7))) - (toNumber(slice(prop("Sleep"), 0, 2)) * 60 + toNumber(slice(prop("Sleep"), 2, 4)))), -1), round((toNumber(slice(prop("Sleep"), 5, 7)) * 60 + toNumber(slice(prop("Sleep"), 7)) - (toNumber(slice(prop("Sleep"), 0, 2)) * 60 + toNumber(slice(prop("Sleep"), 2, 4))) + 1440) / 60 * 10) / 10, round((toNumber(slice(prop("Sleep"), 5, 7)) * 60 + toNumber(slice(prop("Sleep"), 7)) - (toNumber(slice(prop("Sleep"), 0, 2)) * 60 + toNumber(slice(prop("Sleep"), 2, 4)))) / 60 * 10) / 10), 0)
if(equal(length(prop("Sleep")), 9), if(equal(sign((toNumber(substring(prop("Sleep"), 5, 7)) * 60 + toNumber(substring(prop("Sleep"), 7))) - (toNumber(substring(prop("Sleep"), 0, 2)) * 60 + toNumber(substring(prop("Sleep"), 2, 4)))), -1), round((toNumber(substring(prop("Sleep"), 5, 7)) * 60 + toNumber(substring(prop("Sleep"), 7)) - (toNumber(substring(prop("Sleep"), 0, 2)) * 60 + toNumber(substring(prop("Sleep"), 2, 4))) + 1440) / 60 * 10) / 10, round((toNumber(substring(prop("Sleep"), 5, 7)) * 60 + toNumber(substring(prop("Sleep"), 7)) - (toNumber(substring(prop("Sleep"), 0, 2)) * 60 + toNumber(substring(prop("Sleep"), 2, 4)))) / 60 * 10) / 10), 0)

위의 수식을 이용하기 위해서 다음의 3가지를 알아두셔야 합니다.

주의할 점
  1. 위 수식의 녹색으로 보이는 Sleep값은 본인에게 맞는 속성값으로 바꾸셔야 됩니다.
  2. 수면시간뿐만 아니라 다른 용도로도 사용가능합니다. 예를 들면 작업시간, 공부시간 등.
    단, 시작시간과 종료시간의 차이가 23시간 59분을 초과하지 않아야 합니다.
  3. 입력방법은 2230-0600 또는 0200-0930과 같이 24시간제처럼 입력해야 합니다.
    보시면 아시겠지만 앞의 4자리 숫자는 취침시간이고 뒤의 4자리 숫자는 기상시간입니다.
    다른 용도로 사용하실 분들은 앞이 시작시간, 위가 종료시간입니다.
    2개의 시간사이에는 -기호가 아니더라도 아무 문자라도 상관없습니다. 가독성을 높이기 위함일 뿐, 수식에는 영향을 미치지 않습니다. 다만, 시간 사이에 아무것도 넣지 않으면 위 수식은 작동하지 않습니다.

지금부터는 어떻게 저런 수식이 나왔는지에 대한 내용을 쓸 건데 관심이 없으시거나 코딩을 모르시는 분들은 굳이 읽지 않으시는 것이 정신 건강에 좋습니다.

수식을 만들기까지의 순서와 프로세스

자, 그럼 제가 어떻게 수식을 구성했는지 알려드리겠습니다.

입력은 심플하게

여담이지만 저는 날짜형식은 YYYYMMDD를, 그리고 시간형식은 HHmm을 선호합니다.
2023년 1월 3일(X) → 20230103 (O)
오후 1시 3분 (X) → 1303 (O)

제가 원하는 입력값은 아래와 같습니다.

예를 들면 새벽 1시 30분에 잠자리에 들고 아침 7시에 일어났다면 수면시간은 5시간 30분이 됩니다.
제가 선호하는 방식이라면 01300700만 입력하고 0530이라는 값을 얻어내는 것이지만 추후에 월단위, 연단위로 합산해야 하기 때문에 시간단위로 바꿔줄 필요가 있습니다.
즉 0530이 아닌 5.5(시간)가 나와야 합니다.

그런데 01300700으로 하니 가독성이 떨어지는군요. 중간에 하이픈을 넣어보도록 하겠습니다.

0130-0700

훨씬 나아졌습니다.

시간을 계산하기 위한 프로세스

위처럼 포맷이 정해졌으니 실제로 계산을 해야겠죠. 단순하게 0700에서 0130을 빼면 좋겠지만 호락호락하지 않습니다. 그냥 저대로 계산하면 570이 나올 테니까요.

원하는 답을 얻기 위해서는 위의 4자리 숫자를 분단위로 바꿔줄 필요가 있습니다.

7시 → 420분, 1시 30분 → 90분 이런 식으로 말이죠.

420분 – 90분 = 330분 = 5시간 30분 = 5.5시간

제가 원하는 값입니다.

자정 전에 잠들면 생기는 문제

그런데 자정 전에 잠들게 되면 문제가 하나 생기게 됩니다. 예를 들어 밤 10시에 잠이 들었다고 가정하면 2200-0700이라고 입력해야 합니다.

위의 계산대로 하면 0700에서 2200을 빼게 되고 결과는 -15시간이 됩니다. 수면시간을 기록하는데 음수 값은 있을 수 없고, 실제 수면시간을 계산해보면 9시간이란 걸 알 수 있습니다.

이것을 해결하는 방법은 간단합니다. 기상시각을 07시가 아니라 24시간을 더한 31시로 바꾸면 됩니다.

하지만 실제로 31시(3100)로 입력하는 것은 불편하기 때문에 계산할때만 적용해주면 되겠죠?

따라서 자정 전에 잠드는 경우, 24시간(=1440분)을 더해주면 원하는 값이 나옵니다.

이 수식에 필요한 노션 연산자 및 함수들

프로세스까지 정해졌으니 어떠한 연산자와 함수가 필요한지 알아보겠습니다.

length(text)

length(text)

문자열의 길이를 반환합니다.

  • length(“2230-0600") == 9

if(boolean, value, value)

if(boolean, value, value)

if(false, “yes", “no") == “no"
다른 값을 기준으로 두 옵션 간을 전환합니다.

  1. 2230-0600처럼 9글자인지를 판단하기 위한 함수입니다. 중간에 -기호가 있기 때문입니다. -기호를 빼고 22300600처럼 입력하면 계산을 실행하지 않고 0으로 대체하고 종료합니다.
    if(length(“2230-0600"), 왼쪽 값이 9이면 계산 실행, 아니면 결과값 0으로 하고 수식 종료)
  2. 자정 전에 취침했을 때와 자정 후에 취침했을 때의 계산 방법이 다르기 때문에 결과값을 기준으로 자정 전인지 후인지를 판단합니다.
    자정 전이면 앞의 숫자가 뒤의 숫자보다 크기 때문에 결과값이 음수가 됩니다.

if(결과값이 음수?, 맞으면 자정 전이므로 결과값에 1440분(24시간)을 더해주고, 아니면 단순히 결과값 그대로)
1440분을 더해주는 이유 : 당일 6시 – 전날 22시 = 30시 – 22시

substring(text, number[, number])

substring(text, number[, number])

시작 색인(포함)부터 끝 색인(선택적, 제외)까지 문자열에서 하위 문자열을 추출합니다.

  • substring(“Hello world", 1, 5) == “ello"
  • substring(“notion", 3) == “ion"

입력받은 2230-0600을 각각

  • 22(시)
  • 30(분)
  • 06(시)
  • 00(분)

4개의 값으로 분리합니다

  • substring(“2230-0600", 0, 2) → 22
  • substring(“2230-0600", 2, 4) → 30
  • substring(“2230-0600", 5, 7) → 06
  • substring(“2230-0600", 7) → 00

toNumber(value)

toNumber(value)

텍스트에서 숫자를 구문 분석합니다.

  • toNumber(“42") == 42
  • toNumber(false) == 0

위에서 분리한 4개의 숫자는 숫자처럼 보이는 텍스트 형식이므로, 실제 숫자 데이터로 변환

  • toNumber(“22") → 22
  • toNumber(“30") → 30
  • toNumber(“06") → 06
  • toNumber(“00") → 00

round(number)

round(number)

가장 가까운 정수로 반올림한 숫자 값을 반환합니다.

  • round(4.4) == 4
  • round(4.5) == 5

분단위로 변환한 것을 다시 시간단위로 변환하기 위해 60으로 나누면 딱 맞아떨어지지 않는 경우가 있습니다.

예를 들어 500분(8시간 20분)을 60으로 나누면 8.333333333333이 돼버려서 보기가 좋지 않습니다. 소수점 한 자릿수까지만 표시하기 위한 작업입니다.

  • round(7.5 * 10) / 10 → 7.5
  • round(8.53333333333 * 10) / 10 → 8.5

sign(number)

sign(number)

x가 양수인지, 음수인지 또는 0인지를 나타내는 x의 부호를 반환합니다.

  • sign(4) == 1
  • sign(-9) == -1
  • sign(0) == 0

sign(종료시간에서 시작시간을 뺀 값) → 음수이면 -1

equal(value, value)

equal(value, value)

인수가 서로 같으면 true를 반환하고 그렇지 않으면 false를 반환합니다.

  • equal(3 * 5 == 15) == true
  • equal(false, not true) == true

여기서 쓰인 용도 : equal(sign함수 결과값, -1)

위의 sign함수에서 나온 결과값이 음수이면 -1을 반환하므로 equal(-1, -1) 형식으로 결과값이 음수이면 계산식에서 1440분(24시간)을 더해줌)

와~~ 정리한다고 하긴 했는데, 많이 난해해 보이네요.

정리

처음이라 이것저것 건드려보는 재미가 있긴 한데, 언제까지 계속해 나갈 수 있을지 모르겠습니다.

새로운 것들은 계속 생겨나고 배울것들은 점점 많아지고 있는데 따라가기가 쉽지는 않네요.

블로그

Posted by 요로코비